Separación por capas
Cada responsabilidad vive en su capa. La interfaz no sabe de dónde vienen los datos y los datos no saben cómo se van a pintar.
// Cómo estructuro una app Flutter para que escale, se pueda testear y siga siendo fácil de mantener con el paso del tiempo.
Cada responsabilidad vive en su capa. La interfaz no sabe de dónde vienen los datos y los datos no saben cómo se van a pintar.
Presentation y Data dependen del dominio, nunca al revés. El dominio es Dart puro: no conoce Flutter, ni la red, ni el almacenamiento.
Los repositorios consultan primero los datos locales y solo van a red cuando no hay nada guardado. La app responde rápido y aguanta sin conexión.
Cada pieza recibe sus dependencias por constructor. Así casos de uso, repositorios, mappers y BLoCs se prueban de forma aislada.
Una app se divide en cuatro capas con reglas de dependencia estrictas. Las flechas indican quién conoce a quién: todo apunta al dominio y la capa core da soporte al resto.
Todo lo que usa la app entera: servicios de red, navegación, conectividad y logging, inyección de dependencias, configuración por entorno y gestión centralizada de errores. No importa nada del resto de capas.
Dart puro. Entidades inmutables, contratos de repositorio y un caso de uso por cada acción de negocio. Es la capa más estable porque no depende de ninguna otra.
Habla con el exterior. Datasources remotos y locales que devuelven DTOs y DBOs, mappers que los convierten en entidades y repositorios que deciden de dónde sale cada dato. Los DTOs nunca salen de esta capa.
Cada pantalla es una feature con su BLoC o Cubit, su página y sus widgets. La vista solo pinta estados y lanza eventos. La navegación, los diálogos y los avisos también se gestionan desde el BLoC.
Así viaja un dato desde que el usuario toca la pantalla hasta que ve el resultado. Cada paso tiene una sola responsabilidad.
Presentation
El usuario interactúa y la vista envía un evento al BLoC.
Presentation
Emite un estado de carga y delega en el caso de uso. No contiene lógica de negocio.
Domain
Representa una única acción de negocio y pide los datos al repositorio a través de su contrato.
Data
Aplica cache-first: busca en local y, si no hay datos, los pide a remoto, los guarda y los transforma en entidades con su mapper.
Data
Es la única pieza que llama a la API o al almacenamiento. Devuelve DTOs o DBOs, nunca entidades.
Presentation
La entidad sube hasta el BLoC, que emite éxito o error, y la vista se repinta según el nuevo estado.
Para features completas con varios eventos distintos o eventos que llevan datos. Los eventos se nombran en pasado, los estados son sustantivos y un único manejador despacha cada evento.
Para piezas pequeñas e independientes, como un reloj, un calendario, un interruptor o unas pestañas. Cambios de estado simples sin necesidad de eventos.
Toda operación asíncrona sigue el mismo ciclo: en curso → éxito o error. La vista decide qué pintar con una switch expression sobre el estado, sin cadenas de if/else.
final y copyWith, sin lógica ni serialización.UserEntity, UserRepositoryImpl).Las dependencias se simulan con mockito, así cada test comprueba una sola pieza sin depender de la red ni del almacenamiento.