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 primero (esfuerzo bajo, sin dependencias): es la Receta 2 de la guía tal cual — evento nuevo + listener. Entrega valor inmediato.
- 1.2 requiere Topics (esfuerzo medio) — es la pieza de infraestructura nueva de esta fase:
NovuService.addToTopic(topicKey, subscriberIds)— nuevo método (API:POST /v1/topics/{key}/subscribers; crear el topic si no existe conPOST /v1/topics).- Suscribir al login del SSO según roles del JWT — topic por rol, slug normalizado:
role:autorizador-cobros. - Disparar con
to: [{ type: 'Topic', topicKey: 'role:autorizador-cobros' }]. - Decisión pendiente: re-sync de membresías cuando cambian los roles (¿en cada login alcanza?).
- 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 mandasubscriberHash. 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.pyynovu-ws.compulandia.com.py. - ⚠️ Lección aprendida: si un hostname queda detrás de Cloudflare Access, los preflight
OPTIONSse bloquean con 403 (los preflights nunca llevan cookies) y el Inbox muere.novu-api/novu-wsvan 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 bloqueenvironment:del servicioapidel compose (ojo: agregarla solo al.envde compose NO la inyecta al contenedor). Verificado:/v1/auth/registerresponde "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/inboxsalvo 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/novuno 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ónusersen 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.tspodrí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.tsxquedó huérfano — borrar) y elWebSocketNotifier.tsxcomentado.
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.