Separar responsabilidades
La skill utiliza el perfilfeature en apps nuevas. Cada funcionalidad reúne presentación, dominio y datos. Presentación sigue MVVM: la vista compone la interfaz y el controlador mantiene estado observable y coordina acciones. Una app existente conserva su estructura y gestor de estado; el esqueleto de flutter create necesita todavía establecer estas responsabilidades.
main.dart inicializa dependencias y arranca la app; la composición global y las vistas tienen sus propios archivos.
Las flechas de llamada y las dependencias de código son conceptos distintos: el controlador utiliza un contrato de dominio; la implementación en datos también depende de ese contrato. El dominio no importa widgets, Flutter, navegación, SDK HTTP/Firebase ni implementaciones de datos.
Ubicar cada pieza
En dominio y datos usa
issel_core.dart; en presentación utiliza el barrel de widgets. Las carpetas por sí solas no garantizan límites: revisa también imports y tipos de retorno.
Contratos de datos
Lee los métodos y parámetros del servicio real antes de conectarlo. Separa la petición, la respuesta y los envoltorios de transporte. Unid generado por el servidor pertenece a la respuesta, y sólo debe enviarse si el contrato de creación lo admite. Los DTO convierten JSON a entidades mediante composición o mappers, sin herencia que duplique todos los campos por costumbre.
Para Firebase, el datasource encapsula el SDK y convierte fallos conocidos a AppException o directamente a AppFailure; el contrato devuelve la entidad de sesión de la app. Una UI de demostración puede usar un repositorio en memoria que implemente el mismo contrato. Identifica esa fuente en la documentación y reemplázala en composición cuando exista un backend real.
El kit no incluye cliente HTTP, repositorios de clientes, autenticación, servicios de toast ni infraestructura de persistencia. Los nombres de estos ejemplos pertenecen a la app.
Resultado de operación
AppResult<T> distingue un éxito con valor de un fallo esperado. No equivale al estado completo de la pantalla:
cause y stackTrace conservan el diagnóstico. La app define códigos como connection, credentials o operation_in_progress. Evita capturar errores de programación indiscriminadamente para convertirlos en fallos de negocio.
La vista consume el resultado una sola vez después de una acción:
build ni se guardan como eventos que se repitan en cada reconstrucción.
Ciclo de vida y concurrencia
Un controlador nuevo extiendeIsselController. Después de un await, comprueba isDisposed antes de mutar o notificar, incluso en catch y finally. La vista comprueba mounted por separado antes de usar contexto. El guardado puede terminar aunque la pantalla se haya cerrado; evitar notificaciones no cancela una petición ni deshace una escritura remota.
Protege el envío con isSaving en el controlador y deshabilita la acción en la vista. Para búsquedas simultáneas usa un identificador de petición y descarta resultados antiguos; bloquear el guardado no resuelve ese problema. Libera streams, timers y recursos antes de super.dispose() y define un único dueño de cada controlador. Los controladores de pantalla suelen vivir lo que vive su pantalla; tema y sesión pueden ser globales cuando esté justificado.
Evolucionar a full
Un caso de uso recibe contratos y valores Dart y devuelve entidades o resultados. Por ejemplo,
CreateSale puede validar crédito y coordinar una creación, y PrintSaleTicket puede reunir datos para impresión. No necesitas un caso de uso por método trivial, una clase genérica obligatoria ni NoParams.
La evolución se decide por responsabilidades, no por número de pantallas. Ambas variantes conservan el contrato que recibe la vista. Esta separación es compatible con las recomendaciones oficiales de arquitectura de Flutter, que tratan la capa de dominio adicional según complejidad.
Dividir las vistas
Extrae piezas con responsabilidad visual propia asrc/<feature>/presentation/widgets/: filtros, listado, resumen o bloque de error. Usa nombres del producto como CustomerFilters o CustomerSummary. Lleva una pieza a commons cuando realmente la compartan varias features.
En un State, coloca campos y getters, métodos de ciclo de vida, build y después callbacks/auxiliares. Una vista pequeña puede permanecer en un archivo; no es necesario extraer cada Row o Text. La feature de ejemplo demuestra cómo conservar estos límites sin añadir un gestor de estado externo.