services/api/src/scripts/sendPurgeWarnings.js

Overview

Este archivo define un script de mantenimiento para el ciclo de cierre de cuenta. Su función es localizar cuentas ya anonimizadas que tienen la purga final programada dentro de los próximos 7 días, y preparar o emitir el aviso ACCOUNT_PURGE_WARNING para esas cuentas

El script está pensado para ejecutarse como módulo importable o como comando directo de Node. Exporta runSendPurgeWarnings para pruebas y reutilización, y solo abre conexión a MongoDB cuando el archivo se ejecuta como programa principal

[!NOTE] La idempotencia no se guarda en User. El script la resuelve consultando NotificationOutbox para detectar avisos previos ACCOUNT_PURGE_WARNING del mismo usuario

How it works

flowchart TD
    A[CLI o importacion directa] --> B[runSendPurgeWarnings]
    B --> C[Calcula ventana de 7 dias]
    C --> D[Busca usuarios con accountStatus anonymized_pending_purge]
    D --> E[Consulta NotificationOutbox]
    E --> F[Detecta usuarios ya avisados]
    F --> G[Procesa cada usuario]
    G --> H{dryRun}
    H -->|true| I[Solo cuenta el resultado]
    H -->|false| J[buildAccountExportUrl]
    J --> K[emitNotificationEvent]
    K --> L[ACCOUNT_PURGE_WARNING]
    G --> M[Acumula errores por usuario]
    B --> N[Devuelve resumen JSON]

La ejecución empieza con una ventana temporal fija de 7 días. Luego busca usuarios cuyo accountStatus es anonymized_pending_purge y cuya purgeScheduledAt cae entre now y now + 7 días

Antes de emitir nada, agrupa los usuarios por lote. Para cada lote, consulta NotificationOutbox y evita reenviar avisos a usuarios que ya tienen un registro ACCOUNT_PURGE_WARNING

Cuando dryRun es false, genera una URL de exportación de cuenta con buildAccountExportUrl, y luego emite el evento ACCOUNT_PURGE_WARNING con emitNotificationEvent

Key components

{
    "title": "runSendPurgeWarnings",
    "content": "Funci\u00f3n p\u00fablica [badge:public] que calcula qu\u00e9 cuentas deben recibir el aviso de purga y devuelve un resumen de la corrida. Soporta modo dry-run y modo apply.",
    "type": "function",
    "filePath": "services/api/src/scripts/sendPurgeWarnings.js",
    "badges": [
        "public"
    ]
}
{
    "title": "NotificationOutbox",
    "content": "Colecci\u00f3n consultada para detectar si un usuario ya recibi\u00f3 un evento ACCOUNT_PURGE_WARNING. El script la usa como fuente de idempotencia.",
    "type": "class",
    "filePath": "services/api/src/models/NotificationOutbox.js",
    "badges": [
        "public"
    ]
}
{
    "title": "User",
    "content": "Modelo consultado para encontrar cuentas con estado anonymized_pending_purge y una fecha de purga dentro de la ventana de 7 d\u00edas.",
    "type": "class",
    "filePath": "services/api/src/models/User.js",
    "badges": [
        "public"
    ]
}
{
    "title": "emitNotificationEvent",
    "content": "Funci\u00f3n del m\u00f3dulo de notificaciones que publica el evento ACCOUNT_PURGE_WARNING cuando el script corre en modo apply.",
    "type": "function",
    "filePath": "services/api/src/modules/notifications/notification.events.js",
    "badges": [
        "public"
    ]
}
{
    "title": "buildAccountExportUrl",
    "content": "Funci\u00f3n usada para construir la URL que se incluye en el payload del aviso de purga.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/account-export.service.js",
    "badges": [
        "public"
    ]
}

Data & fields

Opciones de ejecución de runSendPurgeWarnings

[
    {
        "name": "dryRun",
        "type": "boolean",
        "description": "Si es true, calcula resultados sin emitir eventos. Por defecto es true."
    },
    {
        "name": "batchSize",
        "type": "number",
        "description": "Cantidad máxima de usuarios por lote. Por defecto es 50."
    },
    {
        "name": "now",
        "type": "Date",
        "description": "Momento de referencia para calcular la ventana de 7 días. Por defecto es la fecha actual."
    }
]

Campos usados en la consulta de usuarios

[
    {
        "name": "accountStatus",
        "type": "string",
        "required": true,
        "description": "Debe ser anonymized_pending_purge para que el usuario entre al proceso."
    },
    {
        "name": "purgeScheduledAt",
        "type": "Date",
        "required": true,
        "description": "Debe ser mayor que now y menor o igual que now + 7 días."
    },
    {
        "name": "_id",
        "type": "ObjectId",
        "required": true,
        "description": "Identificador del usuario, usado para correlacionar el outbox y el evento."
    }
]

Campos consultados en NotificationOutbox

[
    {
        "name": "eventType",
        "type": "string",
        "required": true,
        "description": "Se filtra por ACCOUNT_PURGE_WARNING."
    },
    {
        "name": "recipientUserId",
        "type": "ObjectId",
        "required": true,
        "description": "Se usa para saber si el usuario ya fue avisado."
    }
]

Estructura del payload emitido

[
    {
        "name": "recipientUserId",
        "type": "ObjectId",
        "required": true,
        "description": "Usuario destinatario del aviso."
    },
    {
        "name": "recipientChannel",
        "type": "string",
        "required": true,
        "description": "Canal usado por el evento. El script fija primary_email."
    },
    {
        "name": "payload",
        "type": "object",
        "required": true,
        "description": "Contenido específico del aviso.",
        "children": [
            {
                "name": "purgeScheduledAt",
                "type": "Date",
                "required": true,
                "description": "Fecha programada para la purga definitiva."
            },
            {
                "name": "exportUrl",
                "type": "string",
                "required": true,
                "description": "URL construida para exportación de la cuenta."
            }
        ]
    }
]

Resumen devuelto por la función

[
    {
        "name": "mode",
        "type": "string",
        "required": true,
        "description": "dry-run o apply, según el modo de ejecución."
    },
    {
        "name": "batchSize",
        "type": "number",
        "required": true,
        "description": "Tamaño de lote usado en la corrida."
    },
    {
        "name": "matched",
        "type": "number",
        "required": true,
        "description": "Total de usuarios encontrados por la consulta."
    },
    {
        "name": "alreadyWarned",
        "type": "number",
        "required": true,
        "description": "Usuarios omitidos porque ya tenían un outbox entry de aviso."
    },
    {
        "name": "warned",
        "type": "number",
        "required": true,
        "description": "Usuarios efectivamente procesados. En dry-run siempre retorna 0."
    },
    {
        "name": "errors",
        "type": "number",
        "required": true,
        "description": "Cantidad de usuarios con errores durante el procesamiento."
    },
    {
        "name": "errorUserIds",
        "type": "array",
        "required": true,
        "description": "Lista de IDs de usuarios que fallaron."
    }
]

Behaviors

  • runSendPurgeWarnings [badge:public] — Recorre usuarios elegibles, evita duplicados con NotificationOutbox, y devuelve un resumen de la corrida.
  • buildAccountExportUrl [badge:public] — Construye la URL de exportación usada dentro del payload del aviso.
  • emitNotificationEvent [badge:public] — Publica el evento ACCOUNT_PURGE_WARNING cuando el script no está en dry-run.
  • User.find [badge:public] — Busca usuarios con accountStatus = anonymized_pending_purge y purgeScheduledAt dentro de la ventana.
  • NotificationOutbox.find [badge:public] — Verifica si ya existe un aviso previo para cada usuario.
  • console.error [badge:public] — Registra errores por usuario sin detener toda la corrida.

[!IMPORTANT] El script no modifica accountStatus ni purgeScheduledAt. La lista elegible no se reduce sola entre corridas; la exclusión depende del outbox

Workflows

1. Cargar entorno | Si el archivo se ejecuta como programa principal, carga variables desde `../../.env` y luego desde el entorno actual.
2. Conectar a MongoDB | Usa `MONGODB_URI` o el valor por defecto `mongodb://127.0.0.1:27017/plano_backoffice`.
3. Resolver modo | `--apply` activa envío real. Sin ese flag, el script queda en `dry-run`.
4. Calcular lotes | Lee `--batch-size=<n>` y usa 50 si no recibe un valor válido.
5. Ejecutar procesamiento | Llama a `runSendPurgeWarnings` con `dryRun` y `batchSize`.
6. Imprimir resultado | Serializa el resumen final como JSON legible.
7. Desconectar | Cierra la conexión con MongoDB en un bloque `finally`.

Detailed runtime flow

El archivo detecta si fue invocado directamente comparando process.argv[1] con import.meta.url. Si coincide, prepara el entorno, conecta a MongoDB y ejecuta el script con parámetros derivados de la línea de comandos

El procesamiento interno usa un for (;;). Cada iteración carga un lote con User.find(query).select('_id purgeScheduledAt').skip(skip).limit(batchSize).lean(). Si el lote queda vacío, el ciclo termina

Luego, el script busca avisos previos en NotificationOutbox. Si un usuario ya tiene ACCOUNT_PURGE_WARNING, el script incrementa alreadyWarned y no vuelve a emitir el evento

Si no existe un aviso previo y el modo no es dry-run, el script construye una URL con buildAccountExportUrl y emite el evento con emitNotificationEvent(EVENT_TYPES.ACCOUNT_PURGE_WARNING, ...)

Gotchas & edge cases

[!WARNING] En dry-run, el script no crea eventos ni registros de outbox. El resumen puede mostrar coincidencias, pero warned retorna 0

[!WARNING] La idempotencia depende de NotificationOutbox. Si ya existe un registro ACCOUNT_PURGE_WARNING, el usuario se cuenta en alreadyWarned y se omite

[!CAUTION] El procesamiento es por lotes y usa skip. El script asume que el conjunto objetivo no cambia durante la corrida, porque la consulta no modifica los campos filtrados

[!NOTE] Los errores se aíslan por usuario. Un fallo en un registro no detiene la ejecución completa; solo incrementa errors y agrega el ID al arreglo errorUserIds

[!IMPORTANT] El valor por defecto del modo es dry-run. Para enviar avisos reales, el comando debe incluir --apply

Este script se integra con el ciclo de lifecycle de cuenta y con el sistema de notificaciones. El mismo patrón de script autónomo aparece en otros jobs del repositorio, y la salida esperada también es un resumen JSON fileciteturn0file9turn0file15

El evento ACCOUNT_PURGE_WARNING forma parte del sistema de notificaciones de lifecycle de cuenta, junto con otros eventos del ciclo como ACCOUNT_ANONYMIZED y ACCOUNT_PURGED fileciteturn0file15turn0file16

También reutiliza la lógica de exportación de cuenta definida en services/api/src/modules/auth/account-export.service.js, que ya aparece como dependencia de la capa de autenticación y retención de cuenta fileciteturn0file9turn0file10

  • Filtra usuarios anonymized_pending_purge.
  • Limita la selección a una ventana de 7 días.
  • Evita duplicados con NotificationOutbox.
  • Emite ACCOUNT_PURGE_WARNING solo en modo apply.
  • Devuelve un resumen JSON con contadores y errores.