Claude Code para iteración UI en Flutter: un bucle práctico para convertir historias de usuario en árboles de widgets, estado y navegación manteniendo los cambios modulares y fáciles de revisar.

text\nlib/\n features/\n orders/\n screens/\n widgets/\n state/\n routes.dart\n\n\nMantén los widgets pequeños y compuestos. Cuando un widget tiene entradas y salidas claras, puedes cambiar layout sin tocar la lógica de estado, y cambiar estado sin reescribir la UI. Prefiere widgets que reciban valores simples y callbacks, no estado global.\n\nUn bucle que se mantiene revisable:\n\n- Escribe una especificación UI de 3 a 6 líneas para el slice (qué aparece, qué hacen los taps, cómo luce loading/error).\n- Genera o edita solo los archivos mínimos necesarios (a menudo una pantalla y uno o dos widgets).\n- Ejecuta la pantalla y luego haz una pasada de limpieza (nombres, espaciado, eliminar props no usadas).\n- Haz commit con un mensaje que coincida con el slice.\n\nPonte una regla estricta: cada cambio debe ser fácil de revertir o aislar. Evita refactors incidentales mientras iteras una pantalla. Si detectas problemas no relacionados, apúntalos y arréglalos en un commit separado.\n\nSi tu herramienta soporta snapshots y rollback, usa cada slice como un punto de snapshot. Algunas plataformas vibe-coding como Koder.ai incluyen snapshots y rollback, que pueden hacer la experimentación más segura cuando pruebas un cambio UI audaz.\n\nUn hábito más que calma las primeras iteraciones: prefiere añadir widgets nuevos en lugar de editar compartidos. Los componentes compartidos son donde los cambios pequeños se vuelven grandes diffs.\n\n## Paso a paso: genera un árbol de widgets desde una historia de usuario\n\nEl trabajo UI rápido se mantiene seguro cuando separas pensar de teclear. Empieza obteniendo un plan claro del árbol de widgets antes de generar código.\n\n1) Pide solo un esquema del árbol de widgets. Quieres nombres de widgets, jerarquía y qué muestra cada parte. Nada de código todavía. Aquí detectas estados faltantes, pantallas vacías y elecciones de layout extrañas mientras todo sigue barato de cambiar.\n\n2) Pide un desglose de componentes con responsabilidades. Mantén cada widget enfocado: un widget renderiza el header, otro la lista, otro maneja empty/error. Si algo necesita estado más tarde, anótalo ahora pero no lo implementes todavía.\n\n3) Genera el scaffold de la pantalla y widgets estateless. Comienza con un único archivo de pantalla con contenido placeholder y TODOs claros. Mantén las entradas explícitas (parámetros del constructor) para que puedas enchufar estado real después sin reescribir el árbol.\n\n4) Haz una pasada separada para estilos y detalles de layout: espaciado, tipografía, theming y comportamiento responsive. Trata el estilado como un diff propio para que las revisiones sigan simples.\n\n### Un patrón de prompt que funciona\n\nPon restricciones al inicio para que el asistente no invente UI que no puedas enviar:\n\n- Dispositivos objetivo (solo teléfono, también tablet, orientación)\n- Restricciones de diseño (Material 3, colores del tema existente, reglas de espaciado)\n- Expectativas de navegación (comportamiento atrás, deep links si los hay)\n- Criterios de aceptación (qué debe ser visible y pulsable)\n- Límites del código existente (qué archivos/widgets deben permanecer, convenciones de nombres)\n\nEjemplo concreto: la historia es "Como usuario, puedo revisar mis elementos guardados y eliminar uno." Pide un árbol de widgets que incluya una app bar, una lista con filas de ítem y un estado vacío. Luego solicita un desglose como SavedItemsScreen, SavedItemTile, EmptySavedItems. Solo después, genera el scaffold con widgets estateless y datos ficticios, y finalmente añade estilo (divider, padding y un botón claro de eliminar) en una pasada separada.\n\n## Añade manejo de estado sin inflar el código UI\n\nLa iteración UI se rompe cuando cada widget empieza a tomar decisiones. Mantén el árbol de widgets tonto: debe leer estado y renderizar, no contener reglas de negocio.\n\nComienza nombrando los estados en palabras sencillas. La mayoría de features necesitan más que "loading" y "done":\n\n- Loading (primera carga o refresco)\n- Empty (sin datos aún)\n- Error (request fallido, permiso denegado)\n- Success (datos listos)\n- Entrada parcial (formulario iniciado pero no válido)\n\nLuego lista eventos que pueden cambiar el estado: taps, submit de formulario, pull-to-refresh, navegación atrás, retry y "usuario editó un campo." Hacer esto desde el inicio evita suposiciones más adelante.\n\n### Mantén el estado separado de los widgets\n\nElige un enfoque de estado por feature y aférrate a él. El objetivo no es "el mejor patrón", sino diffs consistentes.\n\nPara una pantalla pequeña, un controlador simple (como ChangeNotifier o ValueNotifier) suele ser suficiente. Pon la lógica en un solo sitio:\n\n- Entradas: eventos desde la UI (submit, refresh, edit)\n- Salida: un único objeto de estado que la UI pueda renderizar\n- Efectos secundarios: llamadas a APIs y solicitudes de navegación\n\nAntes de añadir código, escribe las transiciones de estado en frases. Ejemplo para una pantalla de login:\n\n"Cuando el usuario toca Sign in: poner Loading. Si el email es inválido: quedarse en Entrada parcial y mostrar un mensaje inline. Si la contraseña es incorrecta: poner Error con mensaje y habilitar Retry. Si tiene éxito: poner Success y navegar a Home."\n\nLuego genera el código Dart mínimo que coincida con esas frases. Las revisiones siguen simples porque puedes comparar el diff con las reglas.\n\n### Añade reglas comprobables para inputs inválidos\n\nHaz la validación explícita. Decide qué ocurre cuando los inputs son inválidos:\n\n- ¿Bloqueas el submit o lo permites y muestras errores?\n- ¿Qué campos muestran errores y cuándo?\n- ¿La navegación atrás descarta la entrada parcial o la conserva?\n\nCuando esas respuestas están escritas, tu UI se mantiene limpia y el código de estado pequeño.\n\n## Diseña flujos de navegación que coincidan con el comportamiento real del usuario\n\nLa buena navegación empieza como un mapa pequeño, no como un montón de rutas. Para cada historia de usuario, escribe cuatro momentos: dónde entra el usuario, el paso más probable siguiente, cómo cancelar y qué significa "atrás" (volver a la pantalla anterior o a un estado seguro).\n\n### Empieza con un mapa de rutas, luego fija qué viaja entre pantallas\n\nUn mapa de rutas simple debería responder las preguntas que suelen causar rework:\n\n- Entrada: qué pantalla se abre primero y desde dónde (pestaña, notificación, deep link)\n- Siguiente: la ruta principal hacia adelante tras la acción primaria\n- Cancelar: dónde cae el usuario si abandona el flujo\n- Atrás: si atrás está permitido y qué debe preservar\n- Fallback: a dónde ir si faltan datos requeridos\n\nLuego define los parámetros que se pasan entre pantallas. Sé explícito: IDs (productId, orderId), filtros (rango de fechas, estado) y datos en borrador (un formulario parcialmente completado). Si omites esto, acabarás metiendo estado en singletons globales o reconstruyendo pantallas para "encontrar" contexto.\n\n### Planea deep links y patrones de "retornar un resultado"\n\nLos deep links importan aunque no los lances el primer día. Decide qué ocurre cuando un usuario entra a mitad de flujo: ¿puedes cargar datos faltantes o debes redirigir a una pantalla de entrada segura?\n\nTambién decide qué pantallas deben retornar resultados. Ejemplo: una pantalla "Select Address" retorna un addressId y la pantalla de checkout se actualiza sin un refresh completo. Mantén la forma del resultado pequeña y tipada para que los cambios sean fáciles de revisar.\n\nAntes de codificar, señala casos límite: cambios no guardados (mostrar diálogo de confirmación), autenticación requerida (pausar y reanudar tras login) y datos faltantes o eliminados (mostrar error y una salida clara).\n\n## Haz que los cambios UI sean revisables y modulares\n\nCuando iteras rápido, el verdadero riesgo no es la UI "equivocada." Es la UI irrevisable. Si un compañero no puede decir qué cambió, por qué y qué permaneció estable, cada siguiente iteración se vuelve más lenta.\n\nUna regla que ayuda: fija las interfaces primero y luego permite mover los internos. Estabiliza props públicas de widgets (entradas), pequeños modelos UI y argumentos de rutas. Una vez nombrados y tipados, puedes remodelar el árbol de widgets sin romper el resto de la app.\n\n### Prefiere costuras pequeñas y estables\n\nPide un plan amigable con diffs antes de generar código. Quieres un plan que diga qué archivos cambiarán y cuáles deben permanecer intactos. Eso mantiene las revisiones focalizadas y previene refactors accidentales que cambien comportamiento.\n\nPatrones que mantienen diffs pequeños:\n\n- Mantén widgets públicos delgados: aceptan solo los datos y callbacks que necesitan, y evita alcanzar singletons.\n- Mueve reglas de negocio fuera de widgets temprano: pon decisiones en un controlador o view model, y la UI renderiza estado.\n- Cuando una pieza UI deja de cambiar cada hora, extráela a un widget reutilizable con una API clara y tipada.\n- Mantén los argumentos de ruta explícitos (un objeto argumento suele ser más limpio que muchos campos opcionales).\n- Añade un breve changelog en la descripción del PR: qué cambió, por qué y qué probar.\n\n### Un ejemplo concreto que a los revisores les gusta\n\nSupón la historia "Como comprador, puedo editar mi dirección de envío desde el checkout." Bloquea los args de la ruta primero: CheckoutArgs(cartId, shippingAddressId) permanece estable. Luego itera dentro de la pantalla. Cuando el layout se estabilice, divide en AddressForm, AddressSummary y SaveBar.\n\nSi cambia el manejo de estado (por ejemplo, la validación se mueve del widget a un CheckoutController), la revisión sigue legible: los archivos UI cambian mayormente en renderizado, mientras el controller muestra el cambio de lógica en un solo lugar.\n\n## Errores comunes y trampas al iterar con un asistente AI\n\nLa forma más rápida de enlentecerse es pedir al asistente que cambie todo a la vez. Si un commit toca layout, estado y navegación, los revisores no saben qué rompió y revertir se complica.\n\nUn hábito más seguro es una intención por iteración: dibuja el árbol de widgets, luego cablea el estado, luego conecta la navegación.\n\n### Errores que generan código desordenado\n\nUn problema común es dejar que el código generado invente un patrón nuevo en cada pantalla. Si una página usa Provider, la siguiente usa setState y la tercera introduce un controlador personalizado, la app se vuelve inconsistente rápido. Elige un pequeño conjunto de patrones y aplícalos.\n\nOtro error es poner trabajo asíncrono directamente dentro de build(). Puede parecer bien en una demo rápida, pero provoca llamadas repetidas en rebuilds, parpadeos y bugs difíciles de rastrear. Mueve la llamada a initState(), un view model o un controlador dedicado, y mantén build() enfocado en renderizar.\n\nLos nombres son una trampa silenciosa. Código que compila pero lee Widget1, data2 o temp hace los refactors futuros dolorosos. Nombres claros también ayudan al asistente a producir mejores cambios de seguimiento porque la intención es obvia.\n\nGuardrails que previenen lo peor:\n\n- Cambia una de: layout, estado o navegación por iteración\n- Reutiliza el mismo patrón de estado en la feature\n- No llamadas de red o DB dentro de build()\n- Renombra placeholders antes de añadir más funcionalidad\n- Prefiere extraer widgets sobre añadir más anidación\n\n### La trampa de la anidación\n\nUn arreglo visual típico es añadir otro Container, Padding, Align y SizedBox hasta que quede bien. Después de unas pasadas, el árbol se vuelve ilegible.\n\nSi un botón está desalineado, primero prueba a quitar wrappers, usar un único widget de layout padre o extraer un widget pequeño con sus propias restricciones.\n\nEjemplo: una pantalla de checkout donde el precio total salta al cargar. Un asistente podría envolver la fila de precio en más widgets para "estabilizarla". Una solución más limpia es reservar espacio con un placeholder de carga simple manteniendo la estructura de la fila sin cambios.\n\n## Lista rápida antes de hacer commit en la siguiente iteración UI\n\nAntes de commitear, haz una pasada de dos minutos que verifique el valor al usuario y te proteja de regresiones sorpresivas. El objetivo no es la perfección, es asegurarte de que esta iteración sea fácil de revisar, probar y deshacer.\n\n### Checklist previo al commit\n\nLee la historia de usuario una vez, luego verifica estos puntos contra la app en ejecución (o al menos contra un widget test simple):\n\n- El árbol de widgets coincide con la historia: elementos clave de los criterios de aceptación existen y son visibles. Texto, botones y espacios vacíos se sienten intencionales.\n- Todos los estados son alcanzables: Loading, Error y Empty no están solo esbozados en código. Puedes activar cada uno (incluso con una bandera debug temporal) y tiene un aspecto aceptable.\n- La navegación y el comportamiento atrás tienen sentido: Atrás vuelve a la pantalla esperada, los diálogos se cierran correctamente y los deep links (si se usan) aterrizan en un lugar sensato.\n- Los diffs se mantienen pequeños y con dueño: Los cambios se limitan a un pequeño conjunto de archivos con responsabilidad clara. No refactors incidentales.\n- Rollback limpio: Si reviertes este commit, otras pantallas siguen compilando y ejecutándose. Elimina flags temporales o assets placeholder que puedan romper más tarde.\n\nUna comprobación de realidad rápida: si añadiste una pantalla de Order details nueva, deberías poder (1) abrirla desde la lista, (2) ver un spinner de carga, (3) simular un error, (4) ver una orden vacía y (5) pulsar atrás para volver a la lista sin saltos raros.\n\nSi tu flujo soporta snapshots y rollback, toma un snapshot antes de cambios UI mayores. Algunas plataformas como Koder.ai lo soportan y ayudan a iterar más rápido sin poner en riesgo la rama principal.\n\n## Un ejemplo realista: de la historia de usuario a pantallas en tres iteraciones\n\nHistoria: "Como comprador, puedo explorar ítems, abrir una página de detalle, guardar un ítem en favoritos y luego ver mis favoritos." La meta es pasar de palabras a pantallas en tres pasos pequeños y revisables.\n\nIteración 1: céntrate solo en la pantalla de lista de exploración. Crea un árbol de widgets suficiente para renderizar pero no ligado a datos reales: un Scaffold con un AppBar, un ListView de filas placeholder y UI clara para loading y empty. Mantén el estado simple: loading (muestra un CircularProgressIndicator), empty (mensaje corto y quizá un botón Reintentar) y ready (muestra la lista).\n\nIteración 2: añade la pantalla de detalle y la navegación. Sé explícito: onTap hace push de una ruta y pasa un objeto de parámetros pequeño (por ejemplo: item id, title). Empieza la página de detalle en modo solo lectura con título, descripción placeholder y un botón de Favorito. La idea es cumplir la historia: lista -> detalle -> atrás, sin flujos extra.\n\nIteración 3: introduce actualizaciones de estado de Favoritos y feedback UI. Añade una fuente única de verdad para favoritos (aunque sea en memoria) y conéctala a ambas pantallas. Tocar Favorito actualiza el icono inmediatamente y muestra una confirmación pequeña (como un SnackBar). Luego añade una pantalla Favorites que lea el mismo estado y maneje el estado vacío.\n\nUn diff revisable típicamente parece así:\n\n- browse_list_screen.dart: árbol de widgets más UI loading/empty/ready\n- item_details_screen.dart: layout y acepta parámetros de navegación\n- favorites_store.dart: holder mínimo de estado y métodos de actualización\n- app_routes.dart: rutas y helpers de navegación tipados\n- favorites_screen.dart: lee estado y muestra empty/list UI\n\nSi un archivo se convierte en "el lugar donde pasa todo", divídelo antes de seguir. Archivos pequeños con nombres claros aceleran la siguiente iteración y la hacen más segura.\n\n## Siguientes pasos: hacer que el bucle sea repetible entre features\n\nSi el flujo solo funciona cuando estás "en la zona", se romperá cuando cambies de pantalla o un compañero toque la feature. Haz el bucle un hábito escribiéndolo y poniendo guardrails alrededor del tamaño del cambio.\n\n### Crea una plantilla de prompt reutilizable\n\nUsa una plantilla de equipo para que cada iteración comience con las mismas entradas y produzca el mismo tipo de salida. Manténla corta pero específica:\n\n- Historia de usuario + criterios de aceptación (qué significa "hecho")\n- Restricciones UI (sistema de diseño, espaciado, componentes a reutilizar)\n- Reglas de estado (dónde vive el estado, qué puede ser local vs compartido)\n- Reglas de navegación (rutas, deep links, comportamiento atrás)\n- Reglas de salida (archivos a tocar, tests a actualizar, qué explicar en el diff)\n\nEsto reduce las probabilidades de que el asistente invente nuevos patrones a mitad de feature.\n\n### Define qué es "pequeño" para que los diffs sean predecibles\n\nElige una definición de pequeño que sea fácil de aplicar en la revisión de código. Por ejemplo, limita cada iteración a un número reducido de archivos y separa refactors UI de cambios de comportamiento.\n\nUn conjunto simple de reglas:\n\n- No más de 3 a 5 archivos cambiados por iteración\n- Una nueva widget o un paso de navegación por iteración\n- No introducir un nuevo enfoque de gestión de estado a mitad del bucle\n- Cada cambio debe compilar y ejecutarse antes de la siguiente iteración\n\nAñade puntos de control para poder deshacer un mal paso rápidamente. Como mínimo, etiqueta commits o guarda checkpoints locales antes de refactors mayores. Si tu flujo soporta snapshots y rollback, úsalos agresivamente.\n\nSi quieres un flujo basado en chat que genere y refine apps Flutter de extremo a extremo, Koder.ai incluye un modo de planificación que te ayuda a revisar un plan y los cambios de archivo esperados antes de aplicarlos.Usa una especificación UI pequeña y comprobable primero. Escribe 3–6 líneas que cubran:\n\n- Qué aparece (widgets/componentes clave)\n- Qué hace el tap (una interacción principal)\n- Cómo se ven loading/error/empty\n- Cómo verificarlo en 30 segundos\n\nLuego construye solo ese slice (a menudo una pantalla + 1–2 widgets).
Convierte la historia en cuatro cubos:\n\n- Pantallas: qué cambia y qué permanece igual\n- Componentes: widgets nuevos y dónde viven\n- Estados: loading, empty, error, success (qué muestra cada uno)\n- Eventos: taps, back, retry, refresh, ediciones de formulario\n\nSi no puedes describir la comprobación de aceptación rápidamente, la historia todavía está demasiado difusa para un diff limpio.
Empieza generando solo un esquema del árbol de widgets (nombres + jerarquía + qué muestra cada parte). Nada de código.\n\nDespués pide un desglose de responsabilidades de componentes (qué posee cada widget).\n\nSolo entonces, genera el scaffold estateless con entradas explícitas (valores + callbacks), y haz el estilo en una pasada separada.
Trátalo como una regla dura: una intención por iteración.\n\n- Iteración A: árbol/layout\n- Iteración B: enlace de estado\n- Iteración C: enlace de navegación\n\nSi un solo commit cambia layout, estado y rutas juntos, los revisores no sabrán qué causó un bug y revertir será complejo.
Mantén los widgets “tontos”: deben renderizar estado, no decidir reglas de negocio.\n\nUn por defecto práctico:\n\n- Crea un controlador/view-model que gestione eventos y trabajo asíncrono\n- Expón un único objeto de estado (loading/empty/error/success)\n- La UI lee el estado y llama callbacks (retry, submit, toggle)\n\nEvita llamadas asíncronas dentro de build()—provocan llamadas repetidas en rebuilds.
Define estados y transiciones en lenguaje claro antes de codificar.\n\nPatrón ejemplo:\n\n- Loading: muestra spinner/skeleton\n- Empty: muestra mensaje + acción (como Retry)\n- Error: muestra error inline + Retry\n- Success: renderiza contenido\n\nLuego lista eventos que mueven entre ellos (refresh, retry, submit, edit). El código es más fácil de comparar con las reglas escritas.
Escribe un pequeño “mapa de flujo” para la historia:\n\n- Entrada: de dónde viene el usuario\n- Siguiente: paso principal hacia adelante\n- Cancelar: dónde aterrizan si abandonan\n- Atrás: qué se preserva o descarta\n- Fallback: qué pasa si faltan datos necesarios\n\nTambién fija qué viaja entre pantallas (IDs, filtros, datos en borrador) para no esconder contexto en singletons globales.
Por defecto, usa carpetas organizadas por feature para que los cambios se mantengan contenidos. Por ejemplo:\n\n- lib/features/<feature>/screens/\n- lib/features/<feature>/widgets/\n- lib/features/<feature>/state/\n- lib/features/<feature>/routes.dart\n\nMantén cada iteración enfocada en una carpeta de feature y evita refactors accidentales en otras partes.
Una regla simple: estabiliza interfaces, no internos.\n\n- Mantén las props públicas de widgets pequeñas y tipadas\n- Prefiere pasar valores + callbacks sobre leer estado global\n- Argumentos de rutas explícitos (frecuentemente un único objeto args)\n- Extrae un widget cuando deja de cambiar cada hora\n\nA los revisores les importa que entradas/salidas se mantengan estables aunque la disposición cambie.
Haz un chequeo rápido de dos minutos:\n\n- ¿Puedes activar loading, empty, error, success y lucen aceptables?\n- ¿El atrás vuelve donde esperas (sin saltos extraños)?\n- ¿Cambiaron solo un pequeño conjunto de archivos con responsabilidad clara?\n- ¿Hay flags temporales/placeholder que puedan romper después?\n\nSi tu flujo lo permite (por ejemplo snapshots/rollback), toma un snapshot antes de un refactor mayor para poder revertir con seguridad.