Saltar al contenido principal
Versión: 1.2

Módulo: Notificaciones

Scope

Tipo de organizaciónAcceso
Empresa
Agencia
Reclutador independiente

Disponible para los tres tipos de organización. No requiere permisos especiales — cada usuario ve sus propias notificaciones.

Permisos por rol

Empresa (COMPANY)

AcciónDisponiblePermiso
Ver notificacionesAutenticado
Marcar como leídaAutenticado
Marcar todas leídasAutenticado
Gestionar preferenciasAutenticado
Recibir push notificationsToken FCM registrado

Agencia (AGENCY)

AcciónDisponiblePermiso
Ver notificacionesAutenticado
Marcar como leídaAutenticado
Marcar todas leídasAutenticado
Gestionar preferenciasAutenticado
Recibir push notificationsToken FCM registrado

Reclutador independiente (RECRUITER)

AcciónDisponiblePermiso
Ver notificacionesAutenticado
Marcar como leídaAutenticado
Marcar todas leídasAutenticado
Gestionar preferenciasAutenticado
Recibir push notificationsToken FCM registrado

¿Qué es?

El módulo de Notificaciones gestiona las alertas y comunicaciones para los usuarios dentro de la plataforma. Incluye:

  • Inbox de notificaciones — Lista paginada de notificaciones in-app con estados leída/no leída
  • Push notifications — Alertas del sistema vía Firebase Cloud Messaging (FCM)
  • Notificaciones locales — Mostrar notificaciones cuando la app está en foreground
  • Preferencias — Control granular de canales y categorías de notificación

¿Qué puede hacer el usuario?

Inbox

  • Ver lista paginada de notificaciones con infinite scroll
  • Ver badge de conteo de no leídas en el AppBar del home
  • Marcar una notificación como leída (tap)
  • Marcar todas las notificaciones como leídas
  • Ver título y cuerpo derivados del payload (localizado)
  • Distinguir tipo de notificación por icono y color

Push Notifications

  • Recibir push cuando la app está en background o terminada
  • Ver notificaciones como local notification cuando la app está en foreground
  • Tap en notificación abre la app (callback configurado)
  • Token FCM se registra automáticamente al iniciar la app
  • Token se refresca automáticamente si cambia

Preferencias

  • Activar/desactivar canales (push, email, in-app)
  • Activar/desactivar categorías (vacantes, candidatos, disputas, pagos, sistema)
  • Cambios se persisten optimistamente con rollback en error

Flujo del usuario

1. Accede a notificaciones desde el icono (campana) en el AppBar del home
2. Ve la lista de notificaciones con badge de no leídas
3. Toca una notificación no leída → se marca como leída
4. Usa "Leer todo" → marca todas como leídas
5. Hace scroll al fondo → carga siguiente página (infinite scroll)
6. Recibe push notification → aparece en system tray (background) o local notification (foreground)
7. Toca push notification → abre la app con callback
8. Accede a preferencias desde /notifications/preferences
9. Activa/desactiva switches → se guarda automáticamente

Reglas de negocio

  • Las notificaciones están scoped por userId + organizationId
  • readAt representa el estado de lectura del usuario, status representa dispatch (PENDING, SENT, FAILED, CANCELLED)
  • Las notificaciones incluyen display: { title, body } localizado según el locale del usuario
  • El badge muestra count de no leídas (máximo "99+")
  • Push tokens se registran al login y se desactivan al logout
  • Token refresh de FCM se propaga automáticamente al backend
  • Los tipos de notificación determinan icono y color en la UI:
    • vacanc* → work icon, secondary color
    • candidate* → people icon, accent color
    • dispute* → gavel icon, error color
    • support* → support agent icon, warning color
    • billing*/payout* → wallet icon, success color
    • registration*/validation* → verified icon, primary color
    • test_push* → notifications active icon
    • Otros → notifications icon, primary color

Restricciones

PermitidoNo permitido
Leer propias notificacionesVer notificaciones de otro usuario
Marcar propias como leídasModificar estado de dispatch
Gestionar propias preferenciasAcceder preferencias de otros
Registrar/desactivar propio tokenModificar tokens de otros

Arquitectura

Sigue Clean Architecture con estructura feature-first:

lib/features/notifications/
├── application/
│ ├── notification_preferences_cubit.dart
│ ├── notification_preferences_state.dart
│ ├── notifications_cubit.dart
│ └── notifications_state.dart
├── data/
│ ├── datasources/
│ │ └── notifications_remote_datasource.dart
│ ├── models/
│ │ ├── notification_model.dart
│ │ ├── notification_preferences_model.dart
│ │ └── push_device_token_model.dart
│ └── repositories/
│ └── notifications_repository_impl.dart
├── domain/
│ ├── entities/
│ │ ├── notification_entity.dart
│ │ ├── notification_preferences_entity.dart
│ │ └── push_device_token_entity.dart
│ ├── repositories/
│ │ └── notifications_repository.dart
│ └── usecases/
│ ├── deactivate_push_token_usecase.dart
│ ├── get_notification_preferences_usecase.dart
│ ├── get_unread_count_usecase.dart
│ ├── list_notifications_usecase.dart
│ ├── mark_all_notifications_read_usecase.dart
│ ├── mark_notification_read_usecase.dart
│ ├── register_push_token_usecase.dart
│ └── update_notification_preferences_usecase.dart
└── presentation/
├── pages/
│ ├── notification_preferences_page.dart
│ └── notifications_page.dart
└── widgets/
├── notification_card.dart
├── notifications_empty_view.dart
├── notifications_error_view.dart
└── notifications_list_view.dart

Core Services (lib/core/services/)

├── local_notification_service.dart # flutter_local_notifications wrapper
└── push_notification_service.dart # Firebase Cloud Messaging wrapper

Flujo de datos

UI (Widget) → Cubit → UseCase → Repository → RemoteDatasource → API

Push notifications siguen un flujo diferente:

FCM → PushNotificationService → LocalNotificationService (foreground)
→ onNotificationTap callback (tap)
→ onTokenReceived callback → RegisterPushTokenUseCase → API

Entidades

NotificationEntity

CampoTipoDescripción
idStringUUID de la notificación
organizationIdString?Organización del usuario
userIdStringUsuario destinatario
typeStringTipo de notificación (vacancy.published, candidate.submitted, etc.)
channelStringCanal (IN_APP, PUSH, EMAIL)
statusStringEstado de dispatch (PENDING, SENT, FAILED, CANCELLED)
payloadMap<String, dynamic>Datos del evento + display localizado
isReadboolSi fue leída por el usuario
createdAtDateTimeFecha de creación
sentAtDateTime?Fecha de envío
readAtDateTime?Fecha de lectura

Helpers:

  • displayTitle → extrae título del payload (payload.displayTitle o payload.display.title)
  • displayBody → extrae cuerpo del payload (payload.display.body o payload.displayBody)

NotificationPreferencesEntity

CampoTipoDescripción
pushEnabledboolPush notifications habilitadas
emailEnabledboolEmail notifications habilitadas
inAppEnabledboolIn-app notifications habilitadas
vacancyUpdatesboolNotificaciones de vacantes
candidateUpdatesboolNotificaciones de candidatos
disputeUpdatesboolNotificaciones de disputas
billingUpdatesboolNotificaciones de pagos/wallet
systemUpdatesboolNotificaciones de sistema

PushDeviceTokenEntity

CampoTipoDescripción
idStringUUID del registro
organizationIdString?Organización
userIdStringUsuario propietario
platformStringPlataforma (android, ios)
isActiveboolSi el token está activo
lastSeenAtDateTimeÚltima actividad
createdAtDateTimeFecha de registro
updatedAtDateTimeÚltima actualización
deviceIdString?Identificador del dispositivo
appVersionString?Versión de la app
localeString?Idioma del dispositivo
disabledAtDateTime?Fecha de desactivación

UseCases

UseCaseParámetrosRetornoDescripción
ListNotificationsUseCasepage, pageSize, search, sort, order, unreadPaginatedResponse<NotificationEntity>Lista notificaciones paginadas
GetUnreadCountUseCaseintConteo de no leídas
MarkNotificationReadUseCasenotificationIdNotificationEntityMarca una como leída
MarkAllNotificationsReadUseCaseint (updatedCount)Marca todas como leídas
RegisterPushTokenUseCasetoken, platform, deviceId?, appVersion?, locale?PushDeviceTokenEntityRegistra token FCM
DeactivatePushTokenUseCasetokenIdPushDeviceTokenEntityDesactiva token FCM
GetNotificationPreferencesUseCaseNotificationPreferencesEntityObtiene preferencias
UpdateNotificationPreferencesUseCaseNotificationPreferencesEntityNotificationPreferencesEntityActualiza preferencias

Estados

NotificationsState

enum NotificationsStatus { initial, loading, success, error }

class NotificationsState {
final NotificationsStatus status;
final List<NotificationEntity> notifications;
final PageMeta? meta;
final int unreadCount;
final int currentPage;
final bool isLoadingMore;
final String? errorMessage;
final String? pushTokenId;
}

NotificationPreferencesState

enum NotificationPreferencesStatus { initial, loading, success, error }

class NotificationPreferencesState {
final NotificationPreferencesStatus status;
final NotificationPreferencesEntity? preferences;
final bool isSaving;
final String? errorMessage;
}

Endpoints API

Notificaciones (sin prefijo /api)

MétodoEndpointDescripción
GET/notificationsListar notificaciones (paginado, filtros)
GET/notifications/unread-countConteo de no leídas
PATCH/notifications/{id}/readMarcar como leída
PATCH/notifications/read-allMarcar todas como leídas

Push tokens

MétodoEndpointDescripción
POST/notifications/push-tokensRegistrar token FCM
DELETE/notifications/push-tokens/{id}Desactivar token

Preferencias

MétodoEndpointDescripción
GET/notifications/preferencesObtener preferencias
PATCH/notifications/preferencesActualizar preferencias

Test (desarrollo)

MétodoEndpointDescripción
POST/notifications/test-pushEnviar push de prueba
RutaNombrePágina
/notificationsnotificationsNotificationsPage
/notifications/preferencesnotificationPreferencesNotificationPreferencesPage

El HomeAppBar navega con context.push('/notifications').

Widgets principales

WidgetResponsabilidad
NotificationsPageEntry point con BlocProvider y vista principal
NotificationsListViewLista con infinite scroll y paginación
NotificationCardTarjeta individual con icono, color por tipo, time ago
NotificationsEmptyViewEstado vacío con icono y mensaje
NotificationsErrorViewEstado error con retry
NotificationPreferencesPageGestión de preferencias con switches

Core Services

ServicioResponsabilidad
PushNotificationServiceFCM: permisos, token, foreground/background messages, tap
LocalNotificationServiceflutter_local_notifications: mostrar en foreground

Dependencias

  • flutter_bloc — State management (Cubit pattern)
  • firebase_messaging — Push notifications vía FCM
  • flutter_local_notifications — Mostrar notificaciones en foreground
  • intl — Formateo de fechas relativas
  • dio — HTTP client (via ApiClient centralizado)

Registro DI (get_it)

  • Core services: LazySingleton (PushNotificationService, LocalNotificationService)
  • Datasource: LazySingleton
  • Repository: LazySingleton
  • UseCases: LazySingleton (8 use cases)
  • NotificationsCubit: Factory (nueva instancia por pantalla)
  • NotificationPreferencesCubit: Factory (nueva instancia por pantalla)

Manejo de errores

  1. Datasource — deja propagar DioException sin catch
  2. Repository — captura DioException y AppException, mapea a Failure tipados via NetworkErrorHandler
  3. Cubit — emite errorMessage en el estado
  4. UIBlocBuilder muestra estado de error con botón retry

Paginación (Infinite Scroll)

  • NotificationListener<ScrollNotification> detecta scroll a 200px del fondo
  • Dispara loadMore() en el cubit
  • currentPage se incrementa y se acumulan items
  • hasNextPage se deriva del PageMeta del backend
  • Loader circular al pie mientras carga
  • Guard con isLoadingMore previene peticiones duplicadas

Formateo de tiempo relativo

Usa localización (no strings hardcodeados):

  • < 1 mincontext.l10n.notificationsTimeNow
  • < 60 mincontext.l10n.notificationsTimeMinutes(n)
  • < 24hcontext.l10n.notificationsTimeHours(n)
  • < 7dcontext.l10n.notificationsTimeDays(n)
  • ≥ 7dDateFormat('dd MMM', 'es').format(date)

Localización

Todas las cadenas visibles usan context.l10n.keyName. Las claves relevantes están en lib/l10n/app_es.arb y lib/l10n/app_en.arb con prefijos notifications* y notificationPreferences*.

Keys principales

KeyESEN
notificationsTitleNotificacionesNotifications
notificationsMarkAllReadLeer todoRead all
notificationsEmptySin notificacionesNo notifications
notificationsEmptyMessageAquí aparecerán tus notificacionesYour notifications will appear here
notificationsTimeNowAhoraNow
notificationsTimeMinutesHace {minutes} min{minutes} min ago
notificationsTimeHoursHace {hours}h{hours}h ago
notificationsTimeDaysHace {days}d{days}d ago
notificationPreferencesTitlePreferencias de notificacionesNotification preferences
notificationPreferencesChannelsCANALESCHANNELS
notificationPreferencesCategoriesCATEGORÍASCATEGORIES