Operación y arquitectura de la eliminación permanente de empresas
Guía funcional y técnica del flujo de suspensión, eliminación lógica y eliminación permanente: modos, worker, protecciones, jobs durables, variables de entorno y límites de la versión actual.
Este documento explica cómo administrar el ciclo de vida de una empresa en Neges y cómo funciona el control plane de eliminación permanente. Sirve para operadores de Platform, desarrolladores y agentes de código que necesiten mantener el feature. Describe el comportamiento implementado al 2 de agosto de 2026; el código y las pruebas siguen siendo la fuente de verdad definitiva.
Resumen ejecutivo y límite más importante
Neges distingue tres acciones diferentes: suspender, eliminar de forma lógica y solicitar una eliminación permanente. Suspender y eliminar lógicamente son reversibles. La eliminación permanente usa un flujo separado, reforzado y durable que sólo aparece cuando la empresa ya está archivada.
Este cierre por defecto es intencional: evita que una configuración incompleta, un error de ownership o una expectativa equivocada sobre backups y proveedores externos termine en una pérdida parcial e imposible de auditar.
Suspender, eliminar y eliminar permanentemente
| Acción | Estado resultante | Datos | Reversible | Uso recomendado |
|---|---|---|---|---|
| Suspender | suspended | Se conservan íntegramente. | Sí, con Habilitar. | Pausa temporal por seguridad, cobro, soporte o revisión. |
| Eliminar | archived | Se conservan íntegramente. | Sí, con Restaurar. | Retiro lógico del circuito operativo y paso previo obligatorio al purge. |
| Eliminación permanente | purging y job durable | Se inventarían y, en una versión futura certificada, se eliminarán físicamente según política. | No una vez ejecutada físicamente. | Cierre definitivo, sujeto a controles técnicos, legales y de ownership. |
Cómo opera un administrador de Platform
Revisa el interruptor de infraestructura
TENANT_PURGE_WORKER_ENABLED debe estar en true si se espera que Strapi procese la cola. Si está en false o no existe, Configuración muestra un aviso explícito y el worker no toma jobs.
Tip: Cambiar una variable de entorno requiere reiniciar o redesplegar Strapi.Selecciona el modo en Platform / Configuración
Abre /admin/settings/tenant-purge, elige Vista previa, Staging o Producción, indica la contraseña actual y, opcionalmente, el motivo del cambio.
Tip: Si presionas Guardar sin cambiar el modo, la UI responde “No hay cambios para aplicar”.Revisa las protecciones técnicas
El acordeón resume ocho señales administradas por infraestructura. Producción no se puede guardar mientras cualquiera permanezca bloqueada.
Prepara la empresa
En /admin/tenants/:documentId usa Eliminar para dejar la empresa archived. Una empresa active o suspended no puede entrar directamente a la cola permanente.
Abre Eliminación permanente
La pantalla calcula el inventario, las filas contadas, los recursos sin ownership concluyente, los blockers y un digest del preview.
Confirma la solicitud
Escribe exactamente el slug de la empresa, la frase ELIMINAR.PERMANENTEMENTE y tu contraseña actual. Strapi genera un challenge de un solo uso válido por cinco minutos.
Sigue el job
Actualiza el estado y usa Verificar datos restantes, Reintentar o Cancelar cuando el estado lo permita. Conserva el documentId del job para soporte e investigación.
Modos de eliminación: Vista previa, Staging y Producción
| Modo | Preview | Crear jobs | Blockers al encolar | Objetivo |
|---|---|---|---|---|
| Vista previa | Sí. | No. | La cola permanece cerrada. | Inspeccionar recursos y configuración sin procesar solicitudes. |
| Staging | Sí. | Sí, para empresas archivadas no protegidas. | Puede encolar aunque existan blockers; el worker se detendrá y dejará evidencia. | Probar challenge, cola, freeze, reintento, cancelación y verificación de manera controlada. |
| Producción | Sí. | Sólo para empresas archivadas, no protegidas y sin blockers. | No admite blockers. | Reservado para operación certificada con todas las protecciones listas. |
El modo ya no es una variable de entorno. Se guarda como purgeMode en el single type platform_settings de PostgreSQL y se cambia desde /admin/settings/tenant-purge. Cada modificación aumenta revision y crea un registro en platform_setting_audits con actor, ambiente, valor anterior, valor nuevo, campos modificados y motivo.
Dos controles complementarios: infraestructura y operación
TENANT_PURGE_WORKER_ENABLED es un kill switch de infraestructura. Vive en el proceso de Strapi tanto en local como en Railway y no puede ser activado por la UI. purgeMode es una decisión operativa guardada en base de datos. Para que el worker procese un job deben cumplirse ambos controles: variable true y modo staging o production.
| Worker env | Modo DB | Resultado efectivo |
|---|---|---|
| false o ausente | Cualquiera | Worker detenido; la cola no se procesa. |
| true | preview | Worker iniciado a nivel de Strapi, pero tenant purge no toma jobs. |
| true | staging | Procesa como máximo un job por ciclo y permite probar blockers. |
| true | production | Procesa sólo el flujo elegible sin blockers. |
Qué son las ocho protecciones técnicas
Las protecciones son precondiciones de seguridad visibles en Configuración. No son simples adornos de UI: Strapi vuelve a evaluarlas en el backend. Para guardar Producción las ocho deben estar listas; además, el preview de una empresa puede añadir blockers específicos según sus datos.
| Protección | Qué comprueba | Fuente actual | Cómo se habilita |
|---|---|---|---|
| Challenge reforzado | Existe una clave HMAC de al menos 32 caracteres para tokens temporales. | TENANT_PURGE_CHALLENGE_SECRET | Secreto de infraestructura. |
| Barrera de Inbox | Strapi puede congelar escrituras y consultar residuos en neges-inbox. | TENANT_PURGE_INBOX_API_BASE_URL + TENANT_PURGE_INBOX_INTERNAL_TOKEN | URL y token interno válidos. |
| Retención PostgreSQL | La permanencia eventual en backups compartidos y WAL fue aceptada. | TENANT_PURGE_SHARED_BACKUP_RETENTION_ACCEPTED | Decisión legal y operacional documentada. |
| Retención de archivos | Soft delete y ownership histórico de archivos GCS fueron aceptados. | TENANT_PURGE_GCS_RETENTION_ACCEPTED | Política de retención y registro de ownership aprobados. |
| Retención tributaria | El tratamiento de documentos tributarios obligatorios está aprobado. | TENANT_PURGE_DTE_RETENTION_APPROVED | Decisión legal; sólo produce blocker si el tenant tiene DTE. |
| Propiedad de recursos | Se decidió cómo tratar recursos de ownership incierto, JSON o usuario. | TENANT_PURGE_UNRESOLVED_OWNERSHIP_ACCEPTED | Clasificación y aceptación explícita. |
| Adapters certificados | Los adaptadores destructivos pasaron fixtures, contratos y failure injection. | TENANT_PURGE_DESTRUCTIVE_ADAPTERS_READY | Sólo después de evidencia automatizada y revisión. |
| Ejecutor destructivo | Existe código certificado que realiza el borrado físico y genera recibo. | Hardcoded en false en esta versión. | No es configurable todavía; requiere una entrega de ingeniería separada. |
Blockers específicos de una empresa
| Código | Cuándo aparece | Resolución esperada |
|---|---|---|
| DTE_RETENTION_UNRESOLVED | Hay DTE y su disposición legal no está aprobada. | Separar las copias obligatorias y registrar la decisión legal. |
| SHARED_BACKUP_RETENTION_UNRESOLVED | No se aceptó la expiración eventual de PostgreSQL y WAL compartidos. | Aprobar retención o migrar a almacenamiento segregado/crypto-erased. |
| GCS_SOFT_DELETE_RETENTION_UNRESOLVED | No se aceptó soft delete u ownership de medios históricos. | Definir ventana de retención y completar ownership de archivos. |
| RESOURCE_OWNERSHIP_UNRESOLVED | Algún recurso no puede atribuirse con certeza o falló su conteo. | Clasificar recursos user-owned, JSON y legacy. |
| DESTRUCTIVE_ADAPTERS_NOT_READY | Los adaptadores destructivos no están certificados. | Completar pruebas de contrato, fixtures y fallas parciales. |
| DESTRUCTIVE_EXECUTION_FAIL_CLOSED | Todos los gates previos pasaron, pero no existe ejecutor físico en esta entrega. | Implementar y revisar el ejecutor en una versión futura. |
El catálogo resource-catalog.ts descubre content types tenant-aware, define la estrategia de ownership y cuenta hasta ocho recursos en paralelo. Los tipos globales no se borran; los recursos de ownership incierto se informan sin inventar un conteo. El digest SHA-256 del manifest evita aprobar un inventario y luego encolar otro distinto.
Arquitectura del control plane
El backoffice es la consola. Strapi conserva la autoridad: valida autenticación y rol Platform, aplica la allowlist de operadores destructivos, calcula el preview, crea el challenge, persiste los jobs y ejecuta el cron. PostgreSQL vuelve durable el proceso; cerrar el navegador o reiniciar el frontend no elimina el job.
neges-inbox participa como barrera externa: freeze impide nuevas escrituras asociadas al tenant y verify devuelve frozen, residualRows y conteos por tabla. GCS, backups y DTE se modelan actualmente como decisiones de retención y blockers; todavía no son ejecutados por un adaptador destructivo.
Flujo completo de una solicitud
- El usuario está autenticado y es administrador Platform.
- Su ID numérico está incluido en TENANT_PURGE_ADMIN_USER_IDS para acciones destructivas.
- La empresa está archived y no pertenece a TENANT_PURGE_PROTECTED_DOCUMENT_IDS.
- El origin del navegador coincide exactamente con TENANT_PURGE_ALLOWED_ORIGINS.
- El preview usado para confirmar sigue vigente.
- El slug y ELIMINAR.PERMANENTEMENTE coinciden exactamente.
- La contraseña actual es válida y el challenge no expiró ni fue consumido.
- No existe otro job activo para la misma empresa.
Jobs durables, cancelar, reintentar y verificar residuos
Durable significa que la solicitud y cada paso se almacenan en PostgreSQL, no sólo en memoria. Un reinicio de Strapi no borra la intención ni su estado. El job guarda tenant, usuario solicitante, digest, preview, opciones, intentos, lease, errores y timestamps. Los steps guardan por separado freeze, destructive-gate y verify.
| Acción | Estados admitidos | Efecto |
|---|---|---|
| Cancelar | queued, blocked o failed | Marca cancelled. Si ya estaba congelada, elimina la barrera de Inbox y devuelve la empresa a archived. |
| Reintentar | blocked o failed | Limpia el error, vuelve a queued y obliga a recalcular preview y gates. |
| Verificar residuos | blocked, failed o verification_pending desde la UI | Recalcula el inventario y consulta a Inbox; guarda conteos y blockers como evidencia. No borra datos. |
El worker usa un lease de 60 segundos con RAILWAY_REPLICA_ID o el PID local como owner. Si un proceso muere durante freezing y el lease vence, el siguiente ciclo marca el job failed con TENANT_PURGE_WORKER_LEASE_EXPIRED. La clave de idempotencia evita crear duplicados por doble clic o reintento de red.
Dónde vive el worker y cada cuánto corre
El worker está implementado en neges-strapi/config/cron-tasks.ts usando el cron integrado de Strapi. La expresión */1 * * * * ejecuta el ciclo cada minuto. processPending consulta Platform Settings y retorna sin trabajo si el resultado efectivo está apagado.
| Entorno | Proceso | Configuración |
|---|---|---|
| Local | El mismo proceso iniciado con yarn develop o yarn start en neges-strapi. | Variables del .env local y purgeMode de la PostgreSQL local. |
| Producción | El servicio neges-strapi desplegado en Railway. | Variables del servicio Railway y purgeMode de la PostgreSQL conectada. |
config/server.ts habilita el motor de cron si está activo tenant purge, notification outbox o domain verification. Por eso el cron global puede estar encendido aunque TENANT_PURGE_WORKER_ENABLED sea false; processPending aplica de nuevo el control específico y no procesa la cola de purge.
Variables de entorno
# Interruptor maestro de infraestructura. False o ausente = worker apagado.
TENANT_PURGE_WORKER_ENABLED=false
# IDs numéricos de usuarios Strapi autorizados a crear challenges y operar jobs.
TENANT_PURGE_ADMIN_USER_IDS=123,456
# Denylist de tenants que la UI nunca puede sobrepasar.
TENANT_PURGE_PROTECTED_DOCUMENT_IDS=documentId-critico
# Orígenes exactos permitidos para crear el challenge.
TENANT_PURGE_ALLOWED_ORIGINS=http://localhost:4300,https://backoffice.neges.cl
# Secreto aleatorio de 32 caracteres o más. No usar este ejemplo en producción.
TENANT_PURGE_CHALLENGE_SECRET=<secret-random-de-32-o-mas-caracteres>
# Barrera interna de neges-inbox.
TENANT_PURGE_INBOX_API_BASE_URL=http://localhost:3100/api
TENANT_PURGE_INBOX_INTERNAL_TOKEN=<token-interno>
# Aprobaciones explícitas. Mantener false hasta contar con evidencia real.
TENANT_PURGE_DTE_RETENTION_APPROVED=false
TENANT_PURGE_SHARED_BACKUP_RETENTION_ACCEPTED=false
TENANT_PURGE_GCS_RETENTION_ACCEPTED=false
TENANT_PURGE_UNRESOLVED_OWNERSHIP_ACCEPTED=false
TENANT_PURGE_DESTRUCTIVE_ADAPTERS_READY=false| Variable | Obligatoria | Propósito y comportamiento |
|---|---|---|
| TENANT_PURGE_WORKER_ENABLED | Sí para procesar | Kill switch. true, 1, yes u on habilitan; false, ausente u otro valor mantienen apagado. |
| TENANT_PURGE_ADMIN_USER_IDS | Sí para operar jobs | CSV de IDs numéricos Strapi. Vacía significa que nadie puede crear challenge, encolar, cancelar, reintentar ni verificar. |
| TENANT_PURGE_PROTECTED_DOCUMENT_IDS | Recomendada | CSV de documentId críticos. Es una denylist de infraestructura que la UI no puede sobrescribir. |
| TENANT_PURGE_ALLOWED_ORIGINS | Obligatoria en producción | CSV de origins exactos autorizados para pedir challenge. Incluye esquema, host y puerto, sin path. |
| TENANT_PURGE_CHALLENGE_SECRET | Sí para confirmar | Secreto HMAC aleatorio de al menos 32 caracteres. No se expone al navegador. |
| TENANT_PURGE_INBOX_API_BASE_URL | Sí para freeze/verify | Base interna de neges-inbox incluyendo su prefijo /api. |
| TENANT_PURGE_INBOX_INTERNAL_TOKEN | Sí para freeze/verify | Bearer privado compartido con Inbox. Nunca documentar el valor real. |
| TENANT_PURGE_DTE_RETENTION_APPROVED | Para DTE/Producción | Confirma que existe una decisión legal para documentos tributarios. |
| TENANT_PURGE_SHARED_BACKUP_RETENTION_ACCEPTED | Para Producción | Acepta la retención eventual en backup y WAL compartidos. |
| TENANT_PURGE_GCS_RETENTION_ACCEPTED | Para Producción | Acepta soft delete y la política de ownership/retención de archivos. |
| TENANT_PURGE_UNRESOLVED_OWNERSHIP_ACCEPTED | Para ownership incierto | Registra que existe una resolución explícita para recursos no atribuibles automáticamente. |
| TENANT_PURGE_DESTRUCTIVE_ADAPTERS_READY | Para Producción futura | Declara adapters certificados. En esta versión no basta para abrir el ejecutor físico. |
Seguridad, reautenticación y auditoría
- Autenticación normal y policy require-platform-admin en todos los endpoints.
- Allowlist adicional TENANT_PURGE_ADMIN_USER_IDS para acciones destructivas.
- Reautenticación con contraseña tanto al cambiar el modo como al crear el challenge.
- Challenge aleatorio almacenado sólo como HMAC, de un uso y con TTL de cinco minutos.
- Rate limit de un challenge por usuario y tenant dentro de cinco minutos.
- Origin allowlist para impedir confirmaciones desde una UI no autorizada.
- Confirmación exacta con slug y frase ELIMINAR.PERMANENTEMENTE.
- Digest del preview para detectar cambios entre inspección y solicitud.
- Idempotency key hasheada y un único job activo por tenant.
- Auditoría de modo con actor, email, ambiente, revisión y motivo opcional.
- Errores sanitizados antes de persistir y exponer mensajes del worker.
Operación segura e incidentes
Detención de emergencia
Configura TENANT_PURGE_WORKER_ENABLED=false en el entorno afectado y reinicia Strapi. Esto detiene nuevos ciclos, pero no borra jobs ni revierte automáticamente una empresa ya congelada.
Cierre operativo sin cambiar infraestructura
Cambia el modo a Vista previa desde Platform / Configuración. El cron sigue vivo para otros features, pero tenant purge deja de tomar jobs.
Inspección
Conserva documentId del tenant y del job. Revisa purgeStatus, currentStep, blockerCodes, errorCode, attempts, leaseExpiresAt y los tres tenant_purge_steps.
Recuperación
Si el job está blocked o failed, corrige la causa y usa Reintentar. Si la solicitud debe abandonarse, usa Cancelar para retirar la barrera de Inbox y volver a archived.
Evidencia
Ejecuta Verificar datos restantes y conserva su result. No cambies estados directamente en PostgreSQL salvo un runbook de incidente revisado.
Contratos API y modelo persistido
| Método y ruta | Responsabilidad |
|---|---|
| GET /api/admin/platform-settings/tenant-purge | Lee modo, resultado efectivo, infraestructura y frecuencia. |
| PUT /api/admin/platform-settings/tenant-purge | Reautentica, valida Producción y guarda modo auditado. |
| GET /api/admin/platform-settings/audits | Lista cambios de configuración paginados. |
| GET /api/admin/tenants/:documentId/purge-preview | Genera inventario, blockers, digest y elegibilidad. |
| POST /api/admin/tenants/:documentId/purge-challenges | Reautentica y emite challenge temporal. |
| POST /api/admin/tenants/:documentId/purge | Consume challenge y crea job durable. |
| GET /api/admin/tenant-purge-jobs/:jobDocumentId | Recupera job y steps. |
| POST .../:jobDocumentId/cancel | Cancela y descongela cuando corresponde. |
| POST .../:jobDocumentId/retry | Vuelve a encolar blocked o failed. |
| POST .../:jobDocumentId/verify | Recalcula recursos y consulta residuos de Inbox. |
| Entidad | Datos principales |
|---|---|
| platform_settings | purgeMode y revision. |
| platform_setting_audits | actor, ambiente, previousValue, nextValue, changedFields, reason y revision. |
| tenant_purge_challenges | tenant, usuario, digest, tokenHash, expiración, consumo y origin privado. |
| tenant_purge_jobs | tenant, requester, status, preview, digest, opciones, lease, errores, intentos y receipt futuro. |
| tenant_purge_steps | freeze, destructive-gate y verify con estado, intentos, resultado y errores. |
Mapa de implementación para desarrolladores y agentes de código
neges-backoffice/
src/app/features/platform-settings/ # modo, protecciones y auditoría
src/app/features/platform-tenants/
pages/platform-tenant-detail-page/ # suspender, eliminar, restaurar
pages/platform-tenant-purge-page/ # preview, confirmación y job
data-access/platform-tenant-purge.service.ts
neges-strapi/
config/cron-tasks.ts # ciclo */1 * * * *
config/server.ts # habilitación del cron de Strapi
src/policies/require-tenant-purge-admin.ts
src/api/platform-setting/ # purgeMode y auditoría
src/api/tenant-purge/ # preview, challenge, worker y acciones
src/api/tenant-purge-job/ # job durable
src/api/tenant-purge-step/ # evidencia por paso
src/api/tenant-purge-challenge/ # challenge temporal
src/api/tenant-purge/services/resource-catalog.ts
neges-inbox/
endpoint interno tenant-purge # freeze, unfreeze y verifyLas reglas decisivas deben permanecer en Strapi. El backoffice puede anticipar validaciones para UX, pero no debe decidir elegibilidad, gates ni estados finales. Al ampliar el inventario, resource-catalog.ts debe cubrir cada nuevo content type tenant-aware; su guard de cobertura existe para impedir que un recurso nuevo quede fuera silenciosamente.
El feature usa documentId como identificador público de Strapi 5. Los jobs, steps y challenges son content types de control, no datos de negocio del tenant, y se excluyen del catálogo destructivo. Los cambios en estos endpoints deben mantener DTOs estrictos y actualizar las pruebas del backoffice y de Strapi.
Pruebas y camino para habilitar borrado físico
- Fixtures que prueben todas las relaciones tenant, tenant_scalar, tenant_root, global, user_owned y unresolved.
- Contrato de cada adapter externo con timeout, idempotencia, 404 tolerable y respuestas inválidas.
- Failure injection entre cada paso para probar reanudación, lease vencido y fallas parciales.
- Borrado ordenado por dependencias y transacción cuando el motor lo permita.
- Recibo final firmado con conteos previstos, eliminados y residuos por proveedor.
- Verificación posterior que sólo marque completed cuando los residuos permitidos estén documentados.
- Runbook de backups, WAL, DTE, GCS soft delete y restauración accidental.
- Prueba multi-réplica para demostrar que dos workers no ejecutan el mismo job.
- Revisión de seguridad y legal antes de cambiar destructiveExecutorAvailable a true.
- Pruebas E2E desde una empresa fixture, incluyendo cancelar y reintentar.
Diagnóstico rápido
| Síntoma | Causa probable | Qué revisar |
|---|---|---|
| “El worker está deshabilitado” en local. | TENANT_PURGE_WORKER_ENABLED es false o no está definida. | .env de neges-strapi y reinicio del proceso local. |
| No se puede crear challenge. | Usuario fuera de allowlist, secret corto o origin distinto. | ADMIN_USER_IDS, CHALLENGE_SECRET y ALLOWED_ORIGINS exacto. |
| La empresa no permite encolar. | No está archived, está protegida, el modo es preview o Producción tiene blockers. | state, denylist, purgeMode y preview.blockers. |
| Producción no se guarda. | Alguna de las ocho protecciones está false. | Detalle de Protecciones técnicas. En esta versión el ejecutor siempre está bloqueado. |
| El job queda blocked después de freeze. | Comportamiento esperado de esta release o blocker de retención/ownership. | errorCode, blockerCodes y step destructive-gate. |
| El cron está activo pero no procesa purge. | Puede estar activo por outbox o dominios; el control efectivo de purge sigue apagado. | TENANT_PURGE_WORKER_ENABLED y purgeMode. |
| Puerto 1337 ocupado al iniciar Strapi. | Ya existe otro proceso Strapi local. | Cerrar el proceso que escucha el puerto; no es un problema de Railway ni del worker. |
¿Te fue útil este contenido?
Tu feedback nos ayuda a mejorar la documentación de Neges.
¿No encuentras lo que buscas?
Conversa con nuestro equipo de soporte en español. Respondemos por WhatsApp, correo o ticket en menos de 24 horas hábiles.