Fase 1a: microservicio tcef-notifications (workers FCM v1 + email)

Sustituye el envío de push por la API legacy de GCM (muerta desde jun-2024) y
saca el envío del observer de Meteor a una cola BullMQ controlada.

- poller: consume 'notifications' pendientes (campos correctos; el cron viejo
  tenía un typo nofitied/emailNofitied que nunca casaba)
- fcm-worker: firebase-admin (FCM HTTP v1), payload compatible con la app
  Flutter (FLUTTER_NOTIFICATION_CLICK, collapseKey=_id, data); purga tokens
  muertos sin reintentar
- email-worker: nodemailer + plantillas new-fire portadas verbatim
- idempotencia persistente: notification_sends, indice unico (notificationId,
  channel) -> sobrevive reinicios y replays
- salvaguardas anti-spam: modos dry-run/shadow/canary/full, exclusion mutua por
  canal (OWNED_CHANNELS + notifDisabledChannels en Meteor), kill switch,
  limites por usuario/dia y circuit breaker por batch
- docs/legacy-behavior.md (caracterizacion) + docs/rollout.md
- 47 tests (vitest + mongodb-memory-server), typecheck y build OK
- deploy: systemd + pm2 (Node 22 standalone)

Falta el service account de Firebase para envio real de push (pedir al usuario).
This commit is contained in:
vjrj 2026-07-13 00:10:29 +02:00
commit d20e168c90
39 changed files with 8378 additions and 0 deletions

View file

@ -0,0 +1,22 @@
{
"//": "Alternativa pm2 (mientras shiva siga con pm2). Node 22 standalone, NO el node del sistema. Los secretos van en el .env leído por dotenv o exportados antes de arrancar pm2. En fase 3 esto pasa a Docker Compose.",
"apps": [
{
"name": "tcef-notifications",
"cwd": "/opt/tcef-notifications",
"script": "dist/index.js",
"interpreter": "/opt/node22/bin/node",
"node_args": "--enable-source-maps",
"instances": 1,
"exec_mode": "fork",
"max_restarts": 10,
"restart_delay": 5000,
"env": {
"MODE": "dry-run",
"OWNED_CHANNELS": "",
"KILL_SWITCH": "0",
"CONFIG_FILE": "/opt/tcef-notifications/config.json"
}
}
]
}

View file

@ -0,0 +1,31 @@
# systemd unit para tcef-notifications en shiva (o servidor limpio, fase 3).
# Requiere Node 22 standalone (NO el Node 8 del sistema) y Redis local.
#
# sudo cp deploy/tcef-notifications.service /etc/systemd/system/
# sudo systemctl daemon-reload
# sudo systemctl enable --now tcef-notifications
# journalctl -u tcef-notifications -f
#
# El EnvironmentFile lleva los SECRETOS (Mongo, MAIL_URL, ruta al service
# account de Firebase) — permisos 600, fuera de git, desde tcef-private-config.
[Unit]
Description=tcef-notifications (FCM v1 + email sender)
After=network-online.target redis-server.service
Wants=network-online.target
[Service]
Type=simple
User=incinera
WorkingDirectory=/opt/tcef-notifications
EnvironmentFile=/opt/tcef-notifications/secrets/tcef-notifications.env
# Ajusta la ruta al Node 22 standalone instalado en shiva:
ExecStart=/opt/node22/bin/node --enable-source-maps dist/index.js
Restart=on-failure
RestartSec=5
# Kill switch de emergencia sin editar la env: systemctl stop tcef-notifications
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target