tcef-notifications/README.md
vjrj a1ed206985 Fase 1b: cableado del matcher (ingesta polling, runner shadow/full, comparador)
- config: bloque matcher (MATCHER_MODE off/shadow/full, IRON_PASSWORD, audiencia,
  poll, batch) + validacion
- db: colecciones subscriptions/activefires/notifications_shadow/matcher_state +
  ensureMatcherIndexes (2dsphere en notifications_shadow para el dedupe 500m)
- matcher/context: MatchContext sobre Mongo real (candidatas geo $near 1000km +
  audiencia; dedupe geo $near 500m; lang del owner)
- matcher/ingest: polling de activefires por createdAt>checkpoint (Mongo 3.2 sin
  change streams), checkpoint persistido en matcher_state
- matcher/runner: loop shadow(->notifications_shadow)/full(->notifications),
  idempotente por dedupe+checkpoint
- matcher/compare: comparador de shadow (shadowOnly=spam, realOnly=faltantes,
  contentMismatches) — el gate antes del cutover
- index: arranca el matcher; fake-repo ampliado (upsert, sort, $gt/$lt)
- docs/env/README actualizados

73 tests en verde, typecheck y build OK.
2026-07-13 18:07:06 +02:00

79 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tcef-notifications
Microservicio de **envío de notificaciones** para *Todos contra el Fuego*
(alertas tempranas de incendios NASA FIRMS). Sustituye el envío de push que
hacía la web Meteor con la API legacy de GCM (apagada por Google en jun-2024) y
saca el envío del observer de Meteor a una cola controlada.
Fases **1a** (workers de envío) y **1b** (matching geoespacial) del
[plan de modernización](../plan-modernizacion/README.md). La 1a consume la
colección `notifications`; la 1b la **genera** (fuego → subs 2dsphere),
sustituyendo el matching O(fuegos×subs) de node-red. Telegram (1c) viene después.
## Qué hace
- **poller**: cada N s busca docs pendientes en `notifications` (con los nombres
de campo **correctos** — el cron viejo tenía un typo, ver
[`docs/legacy-behavior.md`](docs/legacy-behavior.md) §2) y encola un job por
canal.
- **fcm-worker**: envía push con `firebase-admin` (FCM HTTP v1), preservando el
payload que espera la app Flutter (`FLUTTER_NOTIFICATION_CLICK`, collapseKey =
`_id`, data). Purga tokens muertos sin reintentar.
- **email-worker**: `nodemailer` + las plantillas `new-fire` portadas verbatim.
- **idempotencia persistente**: colección `notification_sends`, índice único
`(notificationId, channel)` — sobrevive reinicios y replays de cola.
## Salvaguardas anti-spam
El riesgo nº1 es spamear a los usuarios. Por eso:
- **Modos graduales** `MODE`: `dry-run``shadow``canary``full`. Nunca se
salta un modo; `full` solo con confirmación explícita del usuario.
- **Exclusión mutua por canal**: `OWNED_CHANNELS` aquí + `notifDisabledChannels`
en Meteor. El viejo y el nuevo jamás envían por el mismo canal a la vez.
- **Kill switch**: `KILL_SWITCH=1` detiene todo envío al instante.
- **Límites**: máx envíos/usuario/día y circuit breaker por batch (para y
alerta).
Detalle del despliegue y la secuencia de cutover: [`docs/rollout.md`](docs/rollout.md).
## Desarrollo
```bash
npm ci
npm test # 47 tests (vitest + fake-repo en memoria, sin infra externa)
npm run typecheck
npm run build # -> dist/ (+ plantillas)
```
> **Nota MongoDB**: producción corre **MongoDB 3.2** (EOL), así que el servicio
> usa el driver `mongodb` **v3.7** (el moderno no conecta a 3.2) y no hay change
> streams (fase 1b usará polling). Los tests van contra un fake en memoria
> porque no hay mongod tan antiguo ejecutable en hosts modernos. Detalle en
> [`docs/legacy-behavior.md`](docs/legacy-behavior.md) §0.
## Ejecución
Config por env (ver [`.env.example`](.env.example)) o `config.json` (ver
`config.example.json`); env gana. Secretos (Mongo, `MAIL_URL`, service account
de Firebase) desde el repo privado `tcef-private-config` — nunca en este repo.
```bash
MODE=dry-run OWNED_CHANNELS=fcm MONGO_URL=... npm start
```
Despliegue en shiva: `deploy/tcef-notifications.service` (systemd) o
`deploy/pm2-tcef-notifications.json`. Node 22 standalone (no el Node 8 del
sistema) + Redis local. En fase 3 pasa a Docker Compose.
## Estado
- [x] **1a**: workers FCM v1 + email, poller, idempotencia, salvaguardas, tests.
- [x] **1a**: flag de cutover en Meteor (`notifDisabledChannels`).
- [x] **1a**: canary FCM verificado (push real entregada a dispositivos propios).
- [x] **1b**: matcher geoespacial (content i18n, geolib, `sealed` vía Iron —
compatible con la web —, dedupe 500 m), ingesta por polling, modos
`shadow`/`full`, comparador de shadow. Tests en verde.
- [ ] **1a full**: envío masivo FCM (~8176 users) — con el usuario, vigilando.
- [ ] **1b**: rollout `shadow` ≥3 días → comparar → cutover node-red → `full`.
- [ ] **1c**: envío Telegram.