Screen · Notificaciones

Notificaciones

Centro de notificaciones in-app — destino de la campana del top app bar de Inicio (ruta /notifications). Lista cronológica de avisos sobre la cuenta y las operaciones del usuario; no es bandeja de marketing. Screen de validación: compone top-app-bar (interior), tx-list-like rows, empty-states, skeleton y alerts. Línea roja de cumplimiento: las notificaciones SAGRILAFT dicen únicamente "en revisión" — nunca el motivo.

/notifications 9 tipos · 3 categorías deep-link · GoRouter cumplimiento SAGRILAFT sin marketing
Layout anotado · con datos

Cada etiqueta marca la zona del DS que la provee. Pantalla interior: se llega con push desde la campana de Inicio, por eso lleva back arrow y no bottom nav (consistente con detalle de activo). Mezcla de tipos, leídas y no leídas, agrupadas por fecha.

top-app-bar · interior
date-head
no leída · dot + fondo
contenedor por tipo
9:41
Notificaciones
Hoy
Compra completada
0,00109 BTC acreditados a tu billetera.
hace 5 min
Inicio de sesión en un dispositivo nuevo
Pixel 8 · Bogotá · hoy 9:12
hace 18 min
Venta completada
$1.508.562 COP en camino a tu banco.
hace 2 h
Ayer
Tu cuenta está en revisión
Te avisaremos en cuanto termine.
ayer
Dinero devuelto
$100.300 COP devueltos a tu cuenta.
ayer
Ampliación de límite aprobada
Tu nuevo límite ya está disponible.
ayer
12 jun 2026
Depósito acreditado
0,0250 ETH recibidos tras confirmaciones.
12 jun
Identidad verificada
Tu cuenta está activa. Ya puedes operar.
12 jun

Composición

  1. top-app-bar · interiorLeading arrow-left (back, no chevron-left iOS) + título "Notificaciones". Trailing: una sola acción, marcar todas como leídas (check-check / done_all) en color de acción.
  2. Sin bottom navEs pantalla interior empujada con Navigator.push desde la campana de Inicio — igual que detalle de activo. El back devuelve; no compite con el bottom nav de las raíces.
  3. Agrupación por fechadate-head sticky: "Hoy" · "Ayer" · fecha absoluta para lo más viejo. Orden cronológico inverso (lo nuevo arriba).
  4. No leídaDoble señal sutil: fondo teñido primary 4.5% + dot primary a la derecha + título en peso 600. Leída: fondo plano, título 500, sin dot.
  5. Contenedor por categoríaÍcono en chip 40dp: teal para transacción, neutro para cuenta/seguridad, semántico (success/warning) cuando el estado lo amerita.
  6. Fila tappable → deep-linkToda la fila es un InkWell; al tocar abre la pantalla relevante respetando el guard de estado del mapa de navegación.
Anatomía de fila

Una notificación es una fila tipo tx-list: ícono por tipo, título, descripción de una línea, timestamp relativo e indicador de no-leída. Tres columnas — 40dp · 1fr · auto.

Compra completada
0,00109 BTC acreditados a tu billetera.
hace 5 min
1
2
3
4
5
1

Ícono por tipo · chip 40dp

Contenedor sobrio radius.sm+. Color del chip = categoría; glifo Lucide = evento concreto. Stroke 1.75, sin relleno.

2

Título · 13.5dp

Sentence case, peso 600 si no-leída / 500 si leída. Una línea; describe el hecho, sin signos de admiración.

3

Descripción · 1 línea · muted

Resumen accionable. Montos en mono · tabular-nums. Trunca con elipsis si excede; el detalle completo vive en el deep-link.

4

Timestamp relativo

Mono, fg-subtle: "hace 5 min", "hace 2 h", "ayer"; fecha absoluta al pasar 7 días. Localizado es-CO.

5

Indicador de no-leída

Dot primary 8dp + fondo teñido en la fila. Desaparece al leer (tap) o con "marcar todas como leídas".

Tipos de notificación · ícono + deep-link

Tres categorías. Cada tipo lleva su glifo y su destino al tocar. Solo avisos operativos, de cuenta y de seguridad — nunca promociones (las comerciales requieren opt-in y van aparte). Seguridad y operativas no dependen del opt-in de marketing.

TransacciónMovimientos de cripto y COP — contenedor teal.
Compra completada
0,00109 BTC acreditados a tu billetera.
/history
Venta completada
COP en camino a tu banco.
/history
Depósito acreditado
Cripto recibida tras confirmaciones.
/history
Retiro enviado
Pendiente / confirmado on-chain.
/history
Dinero devuelto
COP devuelto · orden no ejecutada en 24 h.
/history
Cuenta / KYCEstado de la cuenta y límites — contenedor neutro o semántico.
Identidad verificada
Tu cuenta está activa. Ya puedes operar.
/dashboard
Ampliación de límite
Aprobada / en revisión.
/profile/kyc-levels
Tu cuenta está en revisión
Sin motivo. Sin sospecha. Sin listas.
/support
SeguridadEventos de acceso — contenedor neutro, nunca dependen del opt-in.
Inicio de sesión nuevo
Dispositivo nuevo · modelo, ciudad y hora.
/profile/security

Línea roja SAGRILAFT. Cuando una notificación toca una revisión de cumplimiento, dice únicamente "Tu cuenta/operación está en revisión" — nunca el motivo, ni la lista, ni indicio de sospecha. Ni en el título, ni en la descripción, ni en el deep-link. El destino es /support, no un detalle que exponga la causa.

Estados de la screen

Cargando · vacío · error. El chrome (app bar) se mantiene; cambia la zona de lista. El estado vacío usa copy ya aprobado en el vault.

A · CARGANDO
9:41
Notificaciones
B · VACÍO
9:41
Notificaciones
No tienes notificaciones
Te avisaremos cuando haya algo importante sobre tu cuenta.
C · ERROR DE CARGA
9:41
Notificaciones
No pudimos cargar tus notificaciones
Revisa tu conexión e intenta de nuevo.

Reglas de estado

  1. Cargando · skeletonFilas skeleton con shimmer 1500ms que respetan la grilla de la fila (chip + 2 líneas + timestamp). Crossfade 200ms a real. "Marcar todas" deshabilitado.
  2. Vacío · content-onlyempty-states variante sin CTA — el usuario llega a leer, no a actuar. Copy aprobado: "No tienes notificaciones" + "Te avisaremos cuando haya algo importante sobre tu cuenta."
  3. Error ≠ vacíoFalló la carga: ícono cloud-off en tinte error, mensaje accionable y "Reintentar" outlined. Nunca se confunde con el vacío legítimo.
  4. Acción según estado"Marcar todas como leídas" solo activa con no-leídas presentes; en cargando, vacío y error queda deshabilitada (neutral-300).
Light y dark · 360dp

Misma estructura, tokens semánticos por modo. En dark, el fondo de no-leída pasa a teal 10% y los contenedores teal/neutro usan sus equivalentes oscuros.

Light · con datos
9:41
Notificaciones
Hoy
Compra completada
0,00109 BTC acreditados a tu billetera.
hace 5 min
Inicio de sesión nuevo
Pixel 8 · Bogotá · hoy 9:12
hace 18 min
Tu cuenta está en revisión
Te avisaremos en cuanto termine.
ayer
Identidad verificada
Tu cuenta está activa. Ya puedes operar.
12 jun
Dark · con datos
9:41
Notificaciones
Hoy
Compra completada
0,00109 BTC acreditados a tu billetera.
hace 5 min
Inicio de sesión nuevo
Pixel 8 · Bogotá · hoy 9:12
hace 18 min
Tu cuenta está en revisión
Te avisaremos en cuanto termine.
ayer
Identidad verificada
Tu cuenta está activa. Ya puedes operar.
12 jun
Dark · vacío
9:41
Notificaciones
No tienes notificaciones
Te avisaremos cuando haya algo importante sobre tu cuenta.
Notas de implementación · Flutter

Local notifications + push (FCM), routing con GoRouter y el guard de estado del mapa de navegación.

Centro in-app + entrega del sistema

Lista in-app

Stream del backend → switch sobre el estado: loading → Skeleton, error → ErrorState(onRetry), data.isEmpty → EmptyState(bell-off), default → GroupedListView por día con date-head sticky.

Push · firebase_messaging

FCM en background/terminated. El payload trae type + route + params. Tópicos solo para operativas y seguridad — nunca marketing sin opt-in explícito.

Local notifications

flutter_local_notifications para foreground (heads-up). Canales Android separados: operativas, seguridad, cuenta. Sin emoji, sin sonido lúdico — tono institucional.

Deep-link · GoRouter

Cada notificación mapea a una ruta declarativa. Al tocar (in-app o desde push) → context.go(route). El redirect global aplica el guard de estado: si la cuenta está en revisión, ciertas rutas redirigen a /support.

Marcar como leída

Tap individual → markRead(id) optimista. "Marcar todas como leídas" (done_all) → markAllRead(); el badge de la campana en Inicio se deriva del conteo de no-leídas.

Línea roja SAGRILAFT

El type = accountReview renderiza copy fijo "Tu cuenta está en revisión". El cliente no recibe motivo en el payload — el backend nunca lo envía. Imposible filtrarlo en UI.

// Mapeo tipo → ruta (deep-link), con guard de estado
String routeFor(AppNotification n) => switch (n.type) {
  NotificationType.buy ||
  NotificationType.sell ||
  NotificationType.deposit ||
  NotificationType.withdrawal ||
  NotificationType.refund      => '/history/${n.txId}',
  NotificationType.identityOk   => '/dashboard',
  NotificationType.limitChange  => '/profile/kyc-levels',
  NotificationType.newDevice    => '/profile/security',
  NotificationType.accountReview => '/support',   // nunca expone el motivo
};

// GoRouter redirect — guard del mapa de navegación
redirect: (context, state) {
  final acct = ref.read(accountStatusProvider);
  if (acct.isUnderReview && _isGuarded(state.uri.path)) {
    return '/support';             // degrada sin revelar causa
  }
  return null;
}