Saltar a contenido

Roadmap — Features de notificación pendientes

Backlog de notificaciones a implementar sobre Novu, ordenado por esfuerzo/valor. La mecánica de implementación está en guia-desarrollo.md.

Estado actual (MVP cerrado 2026-08-03)

Notificación Destinatario Vía
✅ Pedido creado Vendedor del pedido Evento order.created → order-notifications.listener.ts
✅ Cobro confirmado / anulado Vendedor Hook en NotificationsGateway.sendToUser
✅ Notificación de prueba Usuario autenticado POST /notifications/novu-test

Fase 1 — Pagos y cobros (prioridad)

Objetivo: el ciclo solicitud → aprobación de cobros/pagos queda completamente notificado y persistente, para el solicitante y para los autorizadores.

# Notificación Destinatario Estado hoy Qué falta
1.1 Solicitud de cobro/pago creada Solicitante (creador) Nada — el creador no recibe ninguna confirmación de que su solicitud quedó registrada Emitir evento payment.request.created desde payments.service.ts (en los caminos que hoy llaman sendToRole — líneas ~338 y ~416) + listener Novu: "Tu solicitud de cobro #N para 'Cliente X' fue registrada y espera aprobación"
1.2 Solicitud de cobro/pago creada Rol Autorizador Cobros Solo socket efímero: sendToRole('Autorizador Cobros', 'new_payment_draft') — si el autorizador no está conectado, no se entera Topics de Novu (mecánica abajo): "Nueva solicitud de cobro #N de {creador} por {monto} pendiente de aprobación", url → bandeja de aprobaciones
1.3 Cobro/pago confirmado Creador de la solicitud ✅ Ya persistido — hook sendToUser → payment_confirmed (efectivo payments.service.ts:~221, aprobación :~1102) Auditar que todos los caminos de confirmación pasen por sendToUser (ej: POS pos_payment_confirmed usa otro evento — verificar)
1.4 Cobro/pago anulado Creador de la solicitud ✅ Ya persistido (CANCELLED_PAYMENT → título "Cobro anulado") —

Orden de implementación sugerido:

  1. 1.1 primero (esfuerzo bajo, sin dependencias): es la Receta 2 de la guía tal cual — evento nuevo + listener. Entrega valor inmediato.
  2. 1.2 requiere Topics (esfuerzo medio) — es la pieza de infraestructura nueva de esta fase:
  3. NovuService.addToTopic(topicKey, subscriberIds) — nuevo método (API: POST /v1/topics/{key}/subscribers; crear el topic si no existe con POST /v1/topics).
  4. Suscribir al login del SSO según roles del JWT — topic por rol, slug normalizado: role:autorizador-cobros.
  5. Disparar con to: [{ type: 'Topic', topicKey: 'role:autorizador-cobros' }].
  6. Decisión pendiente: re-sync de membresías cuando cambian los roles (¿en cada login alcanza?).
  7. 1.3 cierra la fase: auditoría de caminos de confirmación + tests.

Notas: - En la rama actual, los pagos salientes (outgoing-payments.controller.ts) delegan en el mismo PaymentsService, así que 1.1–1.4 cubren cobros y pagos a la vez. Si más adelante se separa un OutgoingPaymentsService con roles propios (Tesorería/Compras), replicar el patrón con topics role:tesoreria / role:compras. - Los títulos del hook del gateway (NOVU_EVENT_TITLES en notifications.gateway.ts) hoy solo mapean payment_confirmed y CANCELLED_PAYMENT; al sumar eventos nuevos por esa vía, agregar el título ahí.

⚠️ Al agregar listeners sobre flujos que hoy también notifican por socket, cuidar el doble aviso (toast + Inbox está bien; dos entradas en el Inbox no).

Fase 2 — Otros dominios: eventos que YA existen y solo necesitan listener (esfuerzo bajo)

Estos eventos ya se emiten con EventEmitter2; falta el listener Novu (Receta 1 de la guía). El payload actual puede necesitar enriquecerse (Receta 2) si no trae salesPersonCode/docNum.

Evento existente Emisor Notificar a Propuesta de contenido
transfer.draft.created/updated/cancelled inventory.service.ts, payments.service.ts Vendedor (SalesPersonCode del payload) "Traslado #N creado/actualizado/cancelado"
transfer.completed inventario Vendedor solicitante "Tu traslado #N fue completado"
service_call.created/updated service-calls.service.ts Técnico asignado (TechnicianCode → mapear a salesPersonCode vía user-cache) "Nuevo caso #N asignado"
service_call.solution_added service calls Técnico / creador "Se agregó una solución al caso #N"
activity.created/closed activities.service.ts Responsable de la actividad "Nueva actividad: {subject}"

También entran acá los casos por rol que quedaron fuera de Fase 1 (conciliación bancaria, Tesorería/Compras si se separa el servicio de egresos) — los Topics ya van a existir.

Fase 3 — Robustez y producción (antes de salir de pruebas)

  • [x] HMAC del subscriberId ✅ (ago 2026): backend firma con la secret key y expone GET /notifications/novu-identity; el Inbox manda subscriberHash. Novu rechaza sesiones sin firma válida. Nunca desactivar.
  • [x] Exponer Novu con HTTPS ✅ (ago 2026): desplegado en devops-vm (/opt/novu) detrás de Cloudflare Tunnel — novu-api.compulandia.com.py y novu-ws.compulandia.com.py.
  • ⚠️ Lección aprendida: si un hostname queda detrás de Cloudflare Access, los preflight OPTIONS se bloquean con 403 (los preflights nunca llevan cookies) y el Inbox muere. novu-api/novu-ws van con Bypass (son públicos por diseño, protegidos por HMAC/ApiKey); el dashboard sí queda detrás de Access.
  • [x] Registro de usuarios deshabilitado ✅ (2026-08-07): DISABLE_USER_REGISTRATION: "true" en el bloque environment: del servicio api del compose (ojo: agregarla solo al .env de compose NO la inyecta al contenedor). Verificado: /v1/auth/register responde "Account creation is disabled".

Hardening pendiente (borde Cloudflare + VM)

  • [ ] Rate limiting en Cloudflare para novu-api.compulandia.com.py/v1/inbox/session (ej: 30 req/min por IP).
  • [ ] WAF Custom Rule: en novu-api, bloquear paths que no empiecen con /v1/inbox salvo desde la IP de egress del backend OMS (el navegador solo necesita /v1/inbox/*; el resto es plano de administración).
  • [ ] Verificar que el compose de /opt/novu no publique puertos de MongoDB/Redis al host (solo red interna de Docker).
  • [ ] Backup de MongoDB de Novu (ahí viven las notificaciones persistidas y la config de la organización).
  • [ ] Mantener imágenes de Novu pineadas y actualizadas (CVEs en endpoints públicos ahora son explotables desde internet); WAF Managed Rules activas sobre los hostnames de Novu.
  • [ ] Borrar el usuario huérfano de la prueba de seguridad (prueba-seguridad-borrar@compulandia.com.py) de la colección users en Mongo.
  • [ ] Al pasar el OMS de staging a producción: usar el environment Production de la instancia de Novu (secret key y application identifier propios, recrear el workflow → nuevo NOVU_IN_APP_WORKFLOW_ID).
  • [ ] Revisar los tests preexistentes que fallan (3 en notifications.gateway.spec.ts, 4 en orders) — no son de Novu pero ensucian la señal de CI.

Fase 4 — Mejoras de producto (cuando haya tracción)

  • Preferencias por usuario: Novu trae UI de preferencias en el Inbox (activar/desactivar tipos). Requiere separar workflows por categoría (pedidos, cobros, traslados...) en vez del genérico único.
  • Email como segundo canal: agregar paso email a workflows críticos (ej: cobro anulado) con digest para no spamear. Requiere SMTP en el self-hosted.
  • Reemplazar Google Chat: las tarjetas hardcodeadas de google-chat.service.ts podrían ser un paso Chat de Novu (provider Google Chat) — se editan en el Dashboard sin deploy.
  • Migrar toasts efímeros restantes: evaluar si qr_payment_confirmed (Bancard) y otros merecen persistencia.
  • Deprecar la campana mock (Notification.tsx quedó huérfano — borrar) y el WebSocketNotifier.tsx comentado.

Criterio para decidir "¿esto va a Novu o al socket?"

Pregunta Sí → Novu (Inbox) Sí → Socket (como hoy)
¿El usuario debe enterarse aunque esté desconectado? ✅
¿Es una actualización de UI en vivo (kanban, contador, modal)? ✅
¿Tiene valor releerla después (historial)? ✅
¿Es de altísima frecuencia / bajo valor individual? ✅

Ambos a la vez es válido (ej: cobro confirmado = toast inmediato + Inbox persistente), es el patrón del MVP.