Inicio / Arquitectura

Mi arquitectura

// Cómo estructuro una app Flutter para que escale, se pueda testear y siga siendo fácil de mantener con el paso del tiempo.

Por qué Clean Architecture

01

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.

02

Dependencias hacia el dominio

Presentation y Data dependen del dominio, nunca al revés. El dominio es Dart puro: no conoce Flutter, ni la red, ni el almacenamiento.

03

Cache-first

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.

04

Todo se puede testear

Cada pieza recibe sus dependencias por constructor. Así casos de uso, repositorios, mappers y BLoCs se prueban de forma aislada.

Cuatro capas, una responsabilidad cada una

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.

PresentationBLoC · Cubit · páginas · widgets
→
Domainentidades · casos de uso · contratos
←
Datadatasources · DTO/DBO · mappers · repositorios
↑ CORE DA SOPORTE A TODAS LAS CAPAS ↑
Coreservicios · inyección de dependencias · red · navegación · errores · configuración
Core lib/core

Infraestructura compartida

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.

Domain lib/layers/domain

Reglas de negocio

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.

Data lib/layers/data

Acceso a datos

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.

Presentation lib/layers/presentation

Interfaz de usuario

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.

Del toque a la pantalla

Así viaja un dato desde que el usuario toca la pantalla hasta que ve el resultado. Cada paso tiene una sola responsabilidad.

Paso 1

Evento de UI

Presentation

El usuario interactúa y la vista envía un evento al BLoC.

Paso 2

BLoC

Presentation

Emite un estado de carga y delega en el caso de uso. No contiene lógica de negocio.

Paso 3

Caso de uso

Domain

Representa una única acción de negocio y pide los datos al repositorio a través de su contrato.

Paso 4

Repositorio

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.

Paso 5

Datasource

Data

Es la única pieza que llama a la API o al almacenamiento. Devuelve DTOs o DBOs, nunca entidades.

Paso 6

Vuelta a la UI

Presentation

La entidad sube hasta el BLoC, que emite éxito o error, y la vista se repinta según el nuevo estado.

BLoC o Cubit, según el caso

BLoC Pantallas completas

Cuando hay varios eventos

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.

Cubit Componentes aislados

Cuando basta con un cambio de estado

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.

Reglas del día a día

  • Entidades inmutables: campos final y copyWith, sin lógica ni serialización.
  • DTOs y DBOs no salen de la capa de datos: hacia arriba solo viajan entidades.
  • Ninguna llamada a la API fuera de los datasources.
  • Sin lógica de negocio en la interfaz ni en el BLoC: para eso están los casos de uso.
  • La navegación, los diálogos y los snackbars se lanzan desde el BLoC, nunca desde un widget.
  • Una clase por archivo: una entidad, un DTO o un widget por fichero.
  • Antes de crear un widget nuevo, reutilizo o extiendo uno existente.
  • Carpetas en kebab-case, archivos en snake_case y clases con sufijo de tipo (UserEntity, UserRepositoryImpl).
  • Análisis estático estricto y formato a 120 columnas desde el primer commit.
  • Un CHANGELOG versionado que se actualiza con cada entrega.

Tests en cada feature

Casos de uso Lógica de negocio aislada
Repositorios Incluida la estrategia cache-first
Mappers Conversión DTO / DBO ↔ entidad
BLoCs Secuencia de estados con bloc_test

Las dependencias se simulan con mockito, así cada test comprueba una sola pieza sin depender de la red ni del almacenamiento.

Herramientas de cada proyecto

Flutter Dart BLoC Cubit Sealed classes GetIt injectable Dio easy_localization
flutter_secure_storage shared_preferences Hive SQLite Firebase Messaging mockito bloc_test

¿Quieres esta arquitectura en tu app?