Qué se hace y qué no se hace
Este documento cataloga las decisiones de diseño más importantes del sistema en formato directo: lo que se hace y lo que no se hace. No explica los fundamentos filosóficos detrás de cada decisión (eso es responsabilidad de los otros documentos de esta sección). Su propósito es servir como referencia rápida durante el desarrollo y la revisión de diseño.
Cada patrón prohibido existe porque fue evaluado, considerado y descartado por razones concretas. No son restricciones arbitrarias.
Flujos y arquitectura de navegación
Se hace
- Cada pantalla tiene exactamente una acción primaria. Esa acción es visible sin scroll.
- La acción más frecuente de cada sección está disponible en el primer nivel de navegación.
- Los flujos de múltiples pasos usan pantallas separadas o un stepper explícito, no un formulario largo en una sola pantalla.
- El botón de retroceso del sistema operativo funciona correctamente en todas las pantallas.
- Al abandonar un formulario con cambios sin guardar, el sistema pide confirmación con opciones descriptivas.
- Las rutas de navegación se definen en una única fuente de verdad (constantes de nombre de ruta). No se escriben rutas literales en los componentes.
- Los argumentos de navegación son objetos tipados del dominio, no mapas genéricos de strings.
No se hace
- No hay dos botones primarios visibles en la misma pantalla al mismo tiempo.
- No se usa
Navigator.pushAndRemoveUntilo su equivalente dentro de flujos de negocio. Solo se usa en logout y en la transición post-login. - No se hacen más de tres niveles de profundidad de navegación. Si un flujo los requiere, se revisa la arquitectura de información.
- No se navega hacia atrás automáticamente después de una acción sin confirmación explícita o sin que el usuario lo inicie.
- No se agregan pantallas de confirmación del tipo “¿Estás seguro?” sin contexto específico sobre lo que se confirma.
Componentes y estados visuales
Se hace
- Toda pantalla que carga datos remotos implementa los cuatro estados: cargando, vacío, error y con datos.
- El estado vacío incluye un ícono relacionado con el contenido esperado y, cuando aplica, una acción para crear el primer elemento.
- El estado de error describe el problema en términos de usuario y ofrece reintento cuando es una operación recuperable.
- Los indicadores de carga aparecen en el espacio donde aparecerán los datos, no como overlay sobre toda la pantalla.
- Los botones de envío muestran su propio indicador de carga durante el procesamiento, manteniéndose visibles en pantalla.
- Los chips de estado usan los colores semánticos del sistema: el color del chip comunica el estado sin necesidad de leer el texto.
No se hace
- No se bloquea toda la pantalla con un overlay de carga en operaciones que ocurren en background.
- No se muestra el mensaje “No hay datos” sin contexto. El estado vacío siempre explica por qué está vacío.
- No se muestran mensajes de error técnicos: sin códigos HTTP, sin nombres de excepción, sin rutas de archivo.
- No se usan colores de estado personalizados. El verde siempre es
tertiary(#388E3C). El rojo siempre eserror(#D32F2F). No hay variantes. - No se usa el widget
Cardnativo si se necesita control sobre sombra y borde. Se construye con el patrónContainer+BoxDecorationdel sistema.
Formularios
Se hace
- Los formularios visibles tienen un máximo de cinco campos simultáneos. Los formularios más largos se dividen en pasos.
- La validación de cada campo ocurre cuando el usuario sale del campo, no durante la escritura.
- Los errores de validación aparecen directamente debajo del campo afectado, con texto descriptivo sobre qué corregir.
- Los campos opcionales se marcan con la etiqueta “(opcional)” junto al nombre del campo.
- El historial de formularios con datos parciales se guarda localmente si el usuario abandona antes de completar.
No se hace
- No se valida en tiempo real mientras el usuario escribe. Interrumpe el flujo de escritura y genera ansiedad.
- No se muestran errores en banners o alertas al inicio del formulario. El error está en el campo, no en la cabecera.
- No se usan asteriscos para marcar campos obligatorios. La distinción se hace en los opcionales.
- No se deshabilita el scroll en formularios largos que deben presentarse en una sola pantalla. Si el scroll es necesario, el diseño tiene un problema.
- No se reinician todos los campos al encontrar un error en uno de ellos.
Retroalimentación y mensajes al usuario
Se hace
- Las acciones que guardan o envían datos siempre producen un mensaje de confirmación (SnackBar breve).
- Las operaciones que tardan más de 300ms muestran un indicador de progreso.
- Los mensajes de operación completada duran 3 segundos. Los de error duran 4 segundos. Los de advertencia duran 3 segundos.
- Solo se muestra un SnackBar a la vez. Si llega un segundo mientras hay uno visible, el primero se descarta.
- Los mensajes de estado offline informativos son distintos visualmente de los mensajes de error. El usuario sabe que no hay un problema, solo una sincronización pendiente.
No se hace
- No se muestran múltiples SnackBars simultáneos.
- No se muestran SnackBars para acciones que no tienen efecto secundario observable (abrir un menú, cambiar un tab).
- No se usan alertas modales (
showDialog) para mensajes informativos que no requieren decisión del usuario. - No se muestra un diálogo de error para operaciones de red recuperables. El SnackBar con acción de reintento es suficiente.
- No se usan notificaciones push para retroalimentar acciones que el usuario acaba de completar. Las notificaciones son para eventos que ocurren mientras el usuario no está en la pantalla.
Listas y datos
Se hace
- Las listas usan renderizado virtualizado (equivalente a
ListView.builderen Flutter,FlatListen React Native, virtualización en web). Nunca se renderizan todos los ítems al mismo tiempo. - Las listas que se sincronizan con un servidor implementan pull-to-refresh explícito.
- Los filtros con seis opciones o menos se presentan como chips con scroll horizontal. Los filtros con más opciones usan un selector o un panel de filtros expandible.
- Al cambiar un filtro, la lista actualiza su contenido inmediatamente con búsqueda local antes de consultar el servidor.
- Las imágenes en listas usan un placeholder de color sólido mientras se cargan. No hay espacio vacío.
No se hace
- No se cargan todos los elementos de una lista en memoria antes de mostrar el primero.
- No se usa
Column(children: items.map(...))o equivalente para listas de longitud variable. - No se actualiza una lista automáticamente mientras el usuario la está revisando (interrumpe el scroll y cambia las posiciones de los ítems). Si hay datos nuevos disponibles, se muestra una notificación no intrusiva.
- No se implementan filtros como menús desplegables cuando hay seis opciones o menos.
- No se implementa búsqueda que solo funciona contra el servidor. La búsqueda responde primero contra caché local.
Permisos y privacidad
Se hace
- Los permisos del sistema (ubicación, cámara, notificaciones) se solicitan en el contexto exacto donde van a ser usados.
- Antes de solicitar el permiso, se muestra una explicación breve de por qué se necesita y qué función habilita.
- Si el usuario deniega un permiso, el flujo que lo requiere se degrada de forma controlada: la funcionalidad que depende del permiso no está disponible, pero el resto de la aplicación funciona correctamente.
No se hace
- No se solicitan todos los permisos al inicio de la aplicación o en el onboarding.
- No se solicita un permiso por segunda vez de forma inmediata si el usuario lo acaba de denegar. Si el usuario denegó, se respeta esa decisión hasta que haya un nuevo contexto.
- No se silencia la denegación de un permiso sin informar al usuario sobre qué funcionalidad quedó deshabilitada.
Modo offline
Se hace
- Las operaciones críticas (registro de eventos, formularios de salida/llegada) funcionan sin conexión y se sincronizan cuando la red se recupera.
- El usuario sabe que está operando offline: hay un indicador visual persistente pero no intrusivo mientras no hay conexión.
- Cuando la sincronización ocurre después de operar offline, el usuario recibe confirmación de que sus datos fueron enviados.
- Los datos cacheados se muestran con indicación de antigüedad cuando no hay acceso a datos frescos.
No se hace
- No se deshabilita ningún flujo crítico por falta de conexión.
- No se muestra el estado offline como un error. Es una condición operacional esperada, no una falla.
- No se sincroniza automáticamente en background sin informar al usuario si la sincronización falló.
- No se descartan datos guardados offline sin confirmación explícita del usuario.
Código de interfaz
Se hace
- Todos los tokens de color se referencian por nombre (
AppColors.primary,Theme.of(context).colorScheme.primary). Nunca por valor hexadecimal directo. - Los textos usan la escala tipográfica del sistema. Los estilos tipográficos se referencian por nivel (
titleLarge,bodyMedium), no se construyen manualmente. - Los componentes estáticos se declaran como constantes cuando el lenguaje lo permite.
- Los componentes reutilizables se extraen a su propio archivo cuando se usan en más de dos lugares distintos.
No se hace
- No se escribe
Color(0xFFFF7043)directamente en ningún widget de producción. - No se construye
TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Color(...))directamente en widgets. Se usan los estilos del tema. - No se usan tamaños y márgenes numéricos literales (
SizedBox(height: 16)) sin referenciar el token de espaciado correspondiente cuando existe. - No se duplica lógica de estado visual entre pantallas similares. Si dos pantallas muestran el mismo tipo de dato, usan el mismo componente.
Resumen
- Los flujos tienen una acción primaria por pantalla. La navegación tiene máximo tres niveles de profundidad.
- Los cuatro estados de pantalla (carga, vacío, error, con datos) son obligatorios. Ninguno se diseña genéricamente.
- Los formularios muestran máximo cinco campos a la vez. La validación ocurre al salir del campo, no durante la escritura.
- El feedback al usuario es preciso: los errores describen qué corregir, no qué salió mal técnicamente.
- Las listas son siempre virtualizadas. Los filtros de seis opciones o menos son chips, no desplegables.
- El modo offline no es un caso borde. Los flujos críticos funcionan sin conexión por diseño.
- El código de interfaz usa tokens del sistema. Los valores numéricos literales de color o tipografía no existen en producción.