# Plan de puesta en marcha (rollout) — fase 1a > **Regla de oro**: nunca se salta un modo, y ningún canal pasa a `full` sin > confirmación explícita del usuario. El riesgo nº1 es spamear a los usuarios. ## Modos (env `MODE`) | Modo | Envía | Para qué | |---|---|---| | `dry-run` | **no** | calcula el payload, resuelve destinatario, loguea. Detecta errores de datos/plantillas sin tocar a nadie. No reserva en `notification_sends`. | | `shadow` | **no** | igual que dry-run pero pensado para correr ≥1 día en paralelo con el sistema viejo y comparar decisiones (logs estructurados). | | `canary` | **solo cohorte** | envía únicamente a `CANARY_USER_IDS` / `CANARY_SUBS_IDS` (dispositivos/suscripciones propias). | | `full` | **todos** | operación normal. | Salvaguardas activas en todos los modos: `KILL_SWITCH=1` (parada total), límite `LIMIT_PER_USER_DAY`, circuit breaker `LIMIT_MAX_PER_BATCH`, idempotencia persistente `notification_sends` (único por `notificationId+channel`). ## Exclusión mutua por canal (obligatoria) El sistema viejo (Meteor) y el nuevo **jamás** deben enviar por el mismo canal a la vez. Se controla con: - **Meteor** (`todos-contra-el-fuego-web`): `settings.private.notifDisabledChannels`, un array con `"mobile"` y/o `"web"`. Si un canal está ahí, el observer/cron viejos lo ignoran (guard en `notificationsProcess.js`). Cambio reversible sin desplegar código: editar settings y reiniciar el proceso Meteor. - **Servicio nuevo** (`OWNED_CHANNELS`): `fcm` y/o `email`. Solo procesa los que posee. Mapa de canales: `mobile ↔ fcm`, `web ↔ email`. > **Orden seguro de cutover de un canal**: (1) añadir el canal a > `notifDisabledChannels` en Meteor y reiniciar → el viejo deja de enviarlo; > (2) confirmar en logs que el viejo ya no lo toca; (3) añadir el canal a > `OWNED_CHANNELS` del servicio y subir el modo. **Nunca** al revés. ## Secuencia (por canal, empezando por FCM) FCM es el primer cutover y el más seguro: **el canal FCM del viejo está muerto desde jun-2024** (API legacy GCM apagada → 404), así que no hay envío real que solapar. Ver `plan-modernizacion/ESTADO.md`. ### Paso 0 — Infra en shiva (una vez) 1. Node 22 standalone (nvm o binario), **no** el Node 8 del sistema. 2. Redis local (`apt install redis-server` o binario/contenedor). 3. Clonar el repo, `npm ci && npm run build`. 4. Config desde el repo privado `tcef-private-config`: - `MONGO_URL` (rsmain, db `fuegos`), `MAIL_URL`, `GMAPS_KEY`, `FIRE_ICON_URL`, `ROOT_URL`. - **Service account de Firebase** (`FCM_SERVICE_ACCOUNT=/ruta/al.json`) — descargar de la consola Firebase del proyecto `org.comunes.fires` (`angular-cosmos-108908`) → Configuración → Cuentas de servicio → Generar clave privada. Guardar **solo** en el repo privado / disco de shiva. ### Paso 1 — dry-run FCM (unos ciclos) ``` MODE=dry-run OWNED_CHANNELS=fcm KILL_SWITCH=0 ``` - Corre varios imports NASA (cada 15 min). Verifica en logs: payloads bien formados, cuántos users sin token, cuántos con token. **No** se envía nada. - Criterio de avance: sin errores de datos/plantilla; volumen esperado. ### Paso 2 — shadow FCM (≥1 día) ``` MODE=shadow OWNED_CHANNELS=fcm ``` - Compara decisiones con el sistema viejo (que en FCM no envía nada real igual). - Criterio de avance: sin discrepancias inesperadas; sin duplicados en `notification_sends`. ### Paso 3 — cutover + canary FCM (dispositivos propios) 1. En Meteor: `notifDisabledChannels: ["mobile"]`, reiniciar `tcef_web*`. 2. Verificar que el viejo ya no procesa mobile. 3. Servicio: ``` MODE=canary OWNED_CHANNELS=fcm CANARY_USER_IDS= FCM_SERVICE_ACCOUNT=... ``` 4. Instala la app Flutter en un dispositivo propio con sesión iniciada, genera un fuego sintético cerca de tu suscripción → debe llegar la push (formato e idioma correctos). Verifica `notification_sends` (status `sent`) y logs de firebase-admin. 5. Simulacro de idempotencia: mata el worker a mitad de batch y reinícialo → cero reenvíos (comprobar `notification_sends`). - Criterio de avance: push llega bien a la cohorte varios días, sin duplicados. ### Paso 4 — full FCM (**requiere confirmación explícita del usuario**) ``` MODE=full OWNED_CHANNELS=fcm ``` - Los `fireBaseToken` llevan ~2 años sin uso → alta tasa de token muerto en el primer envío. Es esperado: se purgan (`fireBaseToken=null`), no se reintenta. - Vigilar el circuit breaker y el límite por usuario/día. ### Paso 5 — email (repetir la secuencia, solo tras FCM estable) - dry-run → shadow (**aquí el viejo SÍ envía email**, así que el shadow es crítico: comparar a quién marcó el viejo como `emailNotified` vs. a quién enviaríamos, volcar discrepancias) → cutover (`notifDisabledChannels: ["mobile","web"]` en Meteor) → canary (buzón de test vía `MAIL_REDIRECT_TO`) → full (con confirmación). ## Rollback (por canal) 1. Servicio: `KILL_SWITCH=1` (parada inmediata) o quitar el canal de `OWNED_CHANNELS`. 2. Meteor: quitar el canal de `notifDisabledChannels`, reiniciar → el viejo reasume ese canal. 3. Nada se pierde: los docs pendientes siguen en `notifications` con `notified`/`emailNotified` sin marcar. ## Checklist de verificación (por paso) - [ ] Tests en verde (`npm test`). - [ ] dry-run: payloads OK, sin errores, volumen esperado. - [ ] shadow ≥1 ciclo NASA real sin discrepancias ni duplicados. - [ ] Exclusión por canal confirmada (viejo no toca el canal migrado). - [ ] Canary: entrega correcta a cohorte propia, formato/idioma OK. - [ ] Simulacro kill-a-mitad-de-batch → cero reenvíos. - [ ] Confirmación explícita del usuario antes de `full`.