Pantallas o secciones que el usuario ve cuando no hay datos legítimos que mostrar — portafolio recién creado, historial sin transacciones, búsqueda sin resultados. No es un skeleton (cargando) ni un error state (falló la carga). Es la primera impresión real del producto después del onboarding.
Cinco elementos centrados, todos opcionales menos título y descripción. La ilustración/ícono no es un héroe — es orientación. Sin color de marca, sin entusiasmo gráfico.
Compra tu primer criptoactivo para empezar.
Lucide outline, color --color-neutral-300 (light) / --color-neutral-600 (dark). Sin color de marca — el empty state orienta, no celebra. En empties legítimos de pantalla completa el chip se reemplaza por spot art (ver abajo).
Body-large semibold. Una línea ideal, máx. dos. Directo y sin drama: "Tu portafolio está vacío", no "¡Tu historial te espera!".
1–2 líneas, color --color-fg-muted. Explica por qué está vacío y qué puede hacer el usuario.
Botón rounded primary, 44dp de alto. La acción que llenaría esta sección. Si la acción no es obvia, no forzar un CTA.
Alternativa sutil. Útil cuando hay un atajo opcional ("Limpiar filtros" en sin-resultados, "Comprar ETH" en el detalle de un activo).
Ícono → título: 16dp. Título → descripción: 8dp. Descripción → CTA: 24dp. Centrado vertical y horizontal en el área disponible.
Un glifo Lucide de 32 px en una pantalla de 844 no sostiene el espacio: los empty states legítimos de pantalla completa se sienten muertos. Se adopta un sistema de ilustración propio —outline + dos tintas, en la dirección que el README recomendó— para llenar ese espacio sin celebrar. El arte ilustra el objeto vacío (un recibo, una bandeja, una tarjeta, un sobre), nunca el resultado deseado. Sigue vigente la regla: la ilustración no es un héroe, es orientación; usa la paleta, pero no festeja.
Cuando compres, vendas o intercambies cripto, todo tu historial aparecerá aquí.
primary-500Terminaciones redondeadas, misma familia visual que Lucide para que no se lea como un cuerpo extraño. En dark el trazo sube a primary-300 por contraste.
primary-100 (light) / primary-500 @ ~30% (dark). Planos, sin degradados.
Un elemento de apoyo (línea de superficie, llave) en neutral-300 / neutral-600. Máximo 3 tintas en total.
Óptica centrada, aire interno generoso. Reemplaza el chip Ø96 en los empties que llevan arte.
Sin degradados, sin sombras, sin texturas, sin 3D, sin perspectiva. Estático: sin Lottie, sin GIF, sin loop (motion §1.5).
Sin mascotas, sin rostros. El arte es sereno, no festivo.
search-x en chip Ø96.cloud-off en el chip de error. No son un vacío legítimo.Cero caricatura cripto-bro: nada de cohetes, monedas con alas ni gráficas subiendo (screens-welcome). Elevado aquí a regla del sistema:
$ como motivo.Ejemplo negativo concreto (rechazado 2026-07-18): la animación "Revenue" de LottieFiles —persona en un puf con laptop, ventana con gráfico de barras subiendo y moneda de US$—. Insinúa rentabilidad en la pantalla de un usuario que aún no ha operado y contradice el modelo COP no-wallet. Es exactamente lo que no debe producirse.
Cada pieza vive como SVG suelto en assets/illustrations/empty-*.svg (tintas por variable CSS / currentColor: un solo archivo sirve light y dark) e inline en cada preview. En Flutter, el slot illustration del widget EmptyState reemplaza al icon en las variantes de pantalla completa legítimas.
Cada variante tiene título y copy que respetan el tono. Las de pantalla completa legítimas (b · e · f · g) llevan spot art; las circunstanciales (c) y las inline (d) conservan ícono-en-chip. Todas a ancho mobile real (~328dp).
Compra tu primer criptoactivo para empezar.
Cuando compres, vendas o intercambies cripto, todo tu historial aparecerá aquí.
screens-historial.No hay operaciones que coincidan. Prueba con otro rango o limpia los filtros.
Las operaciones que hagas con ETH aparecerán aquí.
Te avisaremos cuando haya algo importante sobre tu cuenta.
Registra una wallet externa para poder retirar tus criptoactivos.
Registra una cuenta bancaria a tu nombre para recibir pesos colombianos.
Misma anatomía, dos escalas según el espacio disponible. La inline vive dentro de una sección; la full-page ocupa todo el body entre app bar y bottom nav.
Centrado vertical y horizontal en el área entre top app bar y bottom nav. Para pantallas completas vacías: historial, notificaciones, PQRS. Con spot art cuando es un empty legítimo; con ícono 48dp en chip 96dp cuando no lleva arte (p. ej. sin resultados).
Cuando compres, vendas o intercambies cripto, todo tu historial aparecerá aquí.
MainAxisAlignment.center dentro de ExpandedMás compacto: ícono pequeño, padding reducido. Centrado horizontal pero no vertical — va donde irían los datos.
Las operaciones que hagas con ETH aparecerán aquí.
es-link sobre botón — menos peso visualConfundirlos rompe la confianza. El mismo espacio puede verse vacío legítimamente, con error de red, o cargando — el tratamiento visual y de copy debe dejarlos sin ambigüedad.
Cuando operes, tu historial aparecerá aquí.
| Cuándo | No hay datos porque el usuario aún no los generó |
| Ícono | Outline neutro · color muted |
| CTA | Acción positiva ("Comprar Bitcoin") |
| Tono | Orientación · invitación sutil |
| Cuándo | La petición al backend falló — red, timeout, 500 |
| Ícono | Color error-bg · ícono neutro (no alarma) |
| CTA | "Reintentar" outlined |
| Tono | Problema técnico · solución inmediata |
| Cuándo | Esperando respuesta del backend |
| Visual | Shimmer 1500ms linear · ref. foundations-motion §3d |
| CTA | N/A |
| Tono | Expectativa · transición a real con crossfade 200ms |
El copy de empty states es donde más fácil se desliza el tono de "neobank entusiasta". MiCapi no es eso. Las reglas son explícitas porque las trampas son comunes.
El ícono y los textos cambian de color. El CTA primario mantiene --color-primary-500 en ambos modos. Sin cambios estructurales.
Compra tu primer criptoactivo.
Cuando operes, tu historial aparecerá aquí.
Compra tu primer criptoactivo.
Cuando operes, tu historial aparecerá aquí.
Dos pantallas reales. En el dashboard, el balance card vacío arriba (variante del DS) + empty state portafolio debajo. En el historial, full-page con bottom nav presente.
Compra tu primer criptoactivo para empezar.
Cuando compres, vendas o intercambies cripto, todo tu historial aparecerá aquí.
El balance card vacío mantiene su forma (variante del DS). El empty state vive debajo, no lo reemplaza.
El bottom nav siempre presente, incluso con la pantalla vacía. El usuario sigue pudiendo navegar — la sección vacía no lo atrapa.
Historial usa el CTA primario "Hacer mi primera compra" — copy canónico de screens-historial, verbatim en la app. Es la única acción de la pantalla vacía.
"Compra tu primer Bitcoin" en el dashboard porque BTC es el activo más conocido. En el detalle de un activo específico, el CTA cambia ("Comprar ETH" en ETH detail).
El usuario que llega aquí acaba de completar KYC. Es el primer momento real con el producto — el tono define si el producto se siente sobrio y profesional, o vacío y frío.
Crear EmptyState con parámetros: icon, title, description, primaryAction, secondaryAction, size: EmptyStateSize.fullPage | .inline.
Center(child: Column(mainAxisSize: MainAxisSize.min, ...)) dentro de Expanded. El Column con min agrupa los hijos al centro.
Icon(LucideIcons.wallet, size: 48, color: colorScheme.outline). El chip detrás es un Container de 96dp con shape: BoxShape.circle.
Switch/case sobre el estado del provider: loading → Skeleton, error → ErrorState, data.isEmpty → EmptyState, default → la lista.
// Componente reutilizable class EmptyState extends StatelessWidget { final IconData icon; final String title; final String description; final Widget? primaryAction; final Widget? secondaryAction; final EmptyStateSize size; const EmptyState({...}); Widget build(BuildContext context) { final isFullPage = size == EmptyStateSize.fullPage; final iconSize = isFullPage ? 48.0 : 28.0; final chipSize = isFullPage ? 96.0 : 56.0; return Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: isFullPage ? 32 : 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: chipSize, height: chipSize, decoration: BoxDecoration( color: theme.colorScheme.surfaceVariant, shape: BoxShape.circle, ), child: Icon(icon, size: iconSize, color: theme.colorScheme.outline), ), const SizedBox(height: 16), Text(title, style: theme.textTheme.titleMedium), const SizedBox(height: 8), Text(description, style: theme.textTheme.bodyMedium, textAlign: TextAlign.center), if (primaryAction != null) ...[ const SizedBox(height: 24), primaryAction!, ], if (secondaryAction != null) ...[ const SizedBox(height: 4), secondaryAction!, ], ], ), ); } } // Builder pattern para distinguir estados: switch (state) { Loading() => TxListSkeleton(), Error(error: final e) => ErrorStateView(onRetry: refresh), Loaded(items: final items) when items.isEmpty => EmptyState( icon: LucideIcons.receipt, title: 'Aún no tienes transacciones', description: 'Cuando compres, vendas o intercambies cripto, todo tu historial aparecerá aquí.', primaryAction: FilledButton(onPressed: ..., child: Text('Hacer mi primera compra')), ), Loaded(items: final items) => TxList(items: items), };