services/api/src/scripts/anonymizeExpiredAccounts.js
Overview
Este script implementa la fase de día 30 del cierre diferido de cuenta. Toma usuarios con accountStatus: 'pending_deletion' y anonymizationScheduledAt vencida, anonimiza su identidad pública, prepara la purga definitiva del día 60 y emite una notificación de cuenta anonimizada. También limpia documentos fuente del anunciante en listings anonimizados, porque esos archivos pueden contener datos personales y su borrado se trata como irreversible en esta fase.
El archivo funciona tanto como módulo importable como script ejecutable desde línea de comandos. Cuando se ejecuta directamente, carga variables de entorno, conecta a MongoDB, corre la rutina y imprime un resumen JSON del proceso.
[!IMPORTANT] Este script no sustituye
phoneniusername. Según el propio código, esos campos se reemplazan enpurgeExpiredAccounts.jsdurante el día 60.
How it works
flowchart TD
Start[Script entrypoint] --> Env[Load dotenv]
Env --> Mongo[Connect to MongoDB]
Mongo --> Runner[runAnonymizeExpiredAccounts]
Runner --> Query[Find due pending_deletion users]
Query -->|dry run| Count[Count matches only]
Query -->|apply| Batch[Process user batch]
Batch --> UserFlow[anonymizeSingleUser]
UserFlow --> Listings[Update protection listings]
Listings --> Docs[Delete source document assets]
Docs --> UserUpdate[Update user status and retention contact]
UserUpdate --> Notify[Emit ACCOUNT_ANONYMIZED]
Notify --> Summary[Return summary JSON]
Count --> Summary
Summary --> End[Print result and disconnect]
El flujo central empieza con una consulta paginada sobre User. En dry-run, el script solo cuenta coincidencias y avanza con skip. En modo apply, cada usuario procesado deja de coincidir con el filtro, y solo los que fallan se excluyen en esa misma corrida para evitar bucles interminables.
La transición real ocurre dentro de anonymizeSingleUser. Primero cifra el email actual, luego mueve los listings del usuario de protection a anonymized, limpia documentos fuente ya anonimizados, actualiza el usuario a anonymized_pending_purge y finalmente emite el evento ACCOUNT_ANONYMIZED.
[!NOTE] La limpieza de documentos fuente tiene un comportamiento tolerante a fallos. Si el borrado en ImageKit falla, el script conserva la entrada para reintentarla después y no bloquea la anonimización.
Key components
{
"title": "anonymizeSingleUser",
"content": "Aplica la transici\u00f3n de d\u00eda 30 para un usuario. Actualiza listings, cifra el contacto retenido, limpia tokens de restauraci\u00f3n y emite ACCOUNT_ANONYMIZED.",
"type": "function",
"filePath": "services/api/src/scripts/anonymizeExpiredAccounts.js",
"badges": [
"public"
]
}
{
"title": "runAnonymizeExpiredAccounts",
"content": "Recorre usuarios vencidos en modo dry-run o apply, procesa lotes y devuelve un resumen con conteos y errores.",
"type": "function",
"filePath": "services/api/src/scripts/anonymizeExpiredAccounts.js",
"badges": [
"public"
]
}
El helper interno deleteSourceDocumentsForAnonymizedListings(userId) recorre listings del usuario ya marcados como anonymized, intenta borrar sus assets de documentos fuente y vacía sourceDocuments solo después de un borrado exitoso. Si falla, registra solo código e identificador del listing.
Data & fields
[
{
"name": "user",
"type": "object",
"required": true,
"description": "Documento de usuario que ya fue cargado desde MongoDB. El script usa su _id, email y purgeScheduledAt."
},
{
"name": "now",
"type": "Date",
"required": true,
"description": "Marca temporal de referencia para fijar la transición y construir el enlace de exportación."
}
]
[
{
"name": "dryRun",
"type": "boolean",
"description": "Si es true, el script solo contabiliza coincidencias y no persiste cambios."
},
{
"name": "batchSize",
"type": "number",
"description": "Cantidad máxima de usuarios por lote. El valor por defecto es 50."
},
{
"name": "now",
"type": "Date",
"description": "Instante de evaluación de vencimiento. Por defecto usa new Date()."
}
]
[
{
"name": "mode",
"type": "'dry-run' | 'apply'",
"required": true,
"description": "Modo efectivo de ejecución."
},
{
"name": "batchSize",
"type": "number",
"required": true,
"description": "Tamaño de lote usado durante la corrida."
},
{
"name": "matched",
"type": "number",
"required": true,
"description": "Cantidad total de usuarios que coincidieron con el filtro."
},
{
"name": "anonymized",
"type": "number",
"required": true,
"description": "Cantidad de usuarios anonimizados con éxito."
},
{
"name": "errors",
"type": "number",
"required": true,
"description": "Cantidad de usuarios que produjeron un error."
},
{
"name": "errorUserIds",
"type": "string[]",
"required": true,
"description": "Lista de identificadores de usuarios con error."
}
]
Campos persistidos por este script
Listing.lifecycle.status— pasa deprotectionaanonymizedpara listings del usuario.Listing.lifecycle.anonymizedAt— registra la marca temporal de la anonimización.Listing.lifecycle.publicOwnerLabel— se fija enAnunciante de Plano.Listing.sourceDocuments— se vacía cuando el borrado de assets se completa con éxito.User.accountStatus— cambia depending_deletionaanonymized_pending_purge.User.anonymizedAt— se establece connow.User.retentionContact— guardaencryptField(user.email)para uso futuro.User.restorationTokenyUser.restorationTokenExpiresAt— se eliminan con$unset.
Behaviors
deleteSourceDocumentsForAnonymizedListings(userId)— busca listings anonimizados del usuario, borra assets de documentos fuente y limpiasourceDocumentssolo después del éxito.anonymizeSingleUser(user, now)— ejecuta la transición de dominio completa para un usuario vencido y dispara la notificación asociada.runAnonymizeExpiredAccounts(options)— pagina usuarios vencidos, ejecuta anonimización en modoapplyo solo cuenta endry-run, y devuelve un resumen estructurado.isMainModulebootstrap — detecta ejecución directa del archivo, carga.env, leeMONGODB_URI, interpreta--apply,--dry-runy--batch-size=, conecta a MongoDB y desconecta al final.
Workflows
sequenceDiagram
participant CLI as Script entrypoint
participant Env as dotenv
participant Mongo as mongoose
participant Runner as runAnonymizeExpiredAccounts
participant UserModel as User
participant ListingModel as Listing
participant MediaService as deleteListingSourceDocumentAssets
participant Notify as emitNotificationEvent
CLI->>Env: load env files
CLI->>Mongo: connect
CLI->>Runner: execute with mode and batch size
Runner->>UserModel: find due pending_deletion users
alt dry run
Runner->>Runner: count matches only
else apply
Runner->>ListingModel: update protection listings
Runner->>MediaService: delete source document assets
Runner->>UserModel: update user status
Runner->>Notify: emit ACCOUNT_ANONYMIZED
end
Runner-->>CLI: summary
CLI->>Mongo: disconnect
La secuencia refleja el patrón del script: primero se prepara el entorno, después se procesa la colección User, y por último se deja un resumen legible por consola. El envío de eventos ocurre después de persistir el cambio de estado de dominio.
Proceso operativo
- Preparar entorno | El script carga
../../.envy luegodotenv.config()estándar. - Elegir modo |
--applyactiva la ejecución real; en caso contrario, corre endry-run. - Determinar lote |
--batch-size=...fija el tamaño, con50como valor por defecto. - Conectar MongoDB | Usa
MONGODB_URIomongodb://127.0.0.1:27017/plano_backoffice. - Procesar usuarios | Ejecuta
runAnonymizeExpiredAccountsy acumula métricas. - Cerrar sesión | Imprime el resumen JSON y desconecta de MongoDB.
Gotchas & edge cases
[!WARNING] En modo
apply, una falla por usuario no detiene toda la corrida. El script registracodeyuserId, agrega el id afailedIdsy continúa con el siguiente lote.
[!WARNING] Los fallos al borrar documentos fuente no bloquean la anonimización. El código conserva el registro de
sourceDocumentspara reintento posterior y solo vacía el campo cuando el borrado termina bien.
[!NOTE] El script solo procesa usuarios con
accountStatus: 'pending_deletion'yanonymizationScheduledAt <= now. Los demás usuarios no entran en el lote.
[!CAUTION] El email retenido se cifra con
encryptField(user.email)y se guarda enretentionContact, pero el propio archivo indica que todavía no existe un consumidor para ese dato.
Related
Este script se conecta con varias piezas ya existentes del backend. Usa ../models/User.js y ../models/Listing.js para leer y actualizar estado de cuenta y lifecycle del anuncio. También depende de ../utils/encryption.js, ../modules/notifications/notification.events.js, ../modules/auth/account-export.service.js y ../modules/media/media.service.js para cifrado, eventos, enlace de exportación y borrado de assets.
Su comportamiento forma parte del ciclo diferido que el código comenta junto con purgeExpiredAccounts.js, donde el día 60 se completa la purga y se resuelven campos como email, phone y username.
[!NOTE] En esta unidad no hay rutas HTTP ni controladores. El archivo es un script de mantenimiento y orquestación interna.