deletion-inventory.service.js

Overview

Este módulo implementa un inventario de solo lectura para el estado de las eliminaciones de Requirements D-09. Reúne evidencia de Listing, Conversation, User y MediaFileClaim, y delega la decisión final a deletion-inventory.classify.js.

El flujo está dividido en cuatro secciones: requirements, claims, conversations y shared. DEFAULT_SECTIONS ejecuta solo las tres primeras, y shared queda como sección opcional. El generador emite candidatos, errores y un resumen final. fileciteturn1file0turn1file9

[!IMPORTANT] Las pruebas de MongoDB real y la guardia estática confirman que este archivo no ejecuta escrituras, no usa $merge ni $out, y no llama a ImageKit. fileciteturn1file2turn1file4

How it works

El punto de entrada es runDeletionInventory. Valida la configuración, prepara el contexto común y ejecuta las secciones pedidas en orden. Cada sección pagina por _id, limita el tiempo de consulta con maxTimeMS, clasifica el resultado y lo emite como registro normalizado. fileciteturn1file9turn1file16

flowchart TD
    Start[runDeletionInventory] --> Validate[Validate options]
    Validate --> Build[Create ctx and summary]

    Build --> Req[requirementsSection]
    Build --> Clm[claimsSection]
    Build --> Cnv[conversationsSection]
    Build --> Shr[sharedSection]

    Req --> ReqFind[Listing find pending requirements]
    ReqFind --> ReqEvidence[readEvidence]
    ReqEvidence --> ReqClass[classifyPendingRequirement]
    ReqClass --> Emit[Yield record]

    Clm --> ClmFind[MediaFileClaim find claims]
    ClmFind --> ClmEvidence[readEvidence]
    ClmEvidence --> ClmClass[classifyMediaClaim]
    ClmClass --> Emit

    Cnv --> CnvAgg[Conversation aggregate with lookup]
    CnvAgg --> CnvClass[classifyConversationReference]
    CnvClass --> CnvEmit[Skip valid references or yield record]

    Shr --> ShrAgg[Listing aggregate shared files]
    ShrAgg --> ShrClass[classifySharedFile]
    ShrClass --> ShrEmit[Yield record]

    Emit --> Summary[Update summary]
    CnvEmit --> Summary
    ShrEmit --> Summary
    Summary --> End[Yield final summary]

El flujo usa helpers pequeños para mantener el comportamiento predecible. record normaliza la salida, readEvidence aísla fallos por subconsulta, y errorCode evita exponer el mensaje completo del error. fileciteturn1file8turn1file16

Key components

Estos son los componentes principales del archivo. El módulo es funcional, pero su trabajo está separado en helpers internos y un generador exportado. fileciteturn1file9turn1file16

{
    "title": "runDeletionInventory",
    "content": "Generador asincrono exportado que valida opciones, ejecuta las secciones seleccionadas, emite candidatos y errores, y termina con un resumen global.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": [
        "public"
    ]
}
{
    "title": "requirementsSection",
    "content": "Recorre Listings con intent requirement y deletion.status pending. Relee owners, conversaciones activas, reservas, adjuntos pendientes, claims y stillPending para clasificar Requirements pendientes.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": []
}
{
    "title": "claimsSection",
    "content": "Recorre MediaFileClaim por _id. Cruza la claim con su Listing, calcula la identidad persistida del archivo y busca referencias desde otros Listings.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": []
}
{
    "title": "conversationsSection",
    "content": "Recorre Conversations con activeRequirementId. Hace lookup al Listing objetivo, clasifica la referencia y omite las referencias v\u00e1lidas sin emitir candidato.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": []
}
{
    "title": "sharedSection",
    "content": "Detecta archivos compartidos por varios Listings usando agregaci\u00f3n. Solo informa presencia de compartici\u00f3n y no prueba ausencia de otras referencias.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": []
}
{
    "title": "readEvidence",
    "content": "Ejecuta lecturas de evidencia una por una. Si una lectura falla, devuelve null para ese bloque y agrega un registro error.",
    "type": "function",
    "filePath": "services/api/src/modules/reconciliation/deletion-inventory.service.js",
    "badges": []
}

Data & fields

El archivo define constantes de ejecución, una forma estándar de registro y un resumen final con estadísticas por sección. También expone datos de apoyo para construir evidencia mínima y segura. fileciteturn1file0turn1file9turn1file16

Constantes de ejecución

  • INVENTORY_SCHEMA_VERSION [badge:read-only] number — Versión del esquema del inventario. Su valor es 1.
  • INVENTORY_SECTIONS [badge:read-only] string[] — Secciones válidas: requirements, claims, conversations, shared.
  • DEFAULT_SECTIONS [badge:read-only] string[] — Secciones por defecto: requirements, claims, conversations. No incluye shared.
  • DEFAULT_BATCH_SIZE [badge:read-only] number — Tamaño de lote por defecto. Su valor es 200.
  • MAX_BATCH_SIZE [badge:read-only] number — Límite superior permitido para batchSize. Su valor es 1000.
  • DEFAULT_LIMIT [badge:read-only] number — Límite total por defecto. Su valor es 10_000.
  • DEFAULT_MAX_TIME_MS [badge:read-only] number — Tiempo máximo por consulta. Su valor es 30_000.

Entrada de runDeletionInventory

[
    {
        "name": "sections",
        "type": "string[]",
        "description": "Secciones a ejecutar. Valores válidos: requirements, claims, conversations y shared."
    },
    {
        "name": "batchSize",
        "type": "number",
        "description": "Tamaño de lote por página. Debe estar entre 1 y 1000."
    },
    {
        "name": "limit",
        "type": "number",
        "description": "Límite total de candidatos a revisar por ejecución. Debe ser positivo."
    },
    {
        "name": "maxTimeMS",
        "type": "number",
        "description": "Tiempo máximo aplicado a cada consulta de base de datos."
    },
    {
        "name": "hooks",
        "type": "object",
        "description": "Callbacks opcionales para pruebas entre lecturas."
    },
    {
        "name": "now",
        "type": "Date",
        "description": "Marca temporal base usada para ageSeconds y observedAt."
    }
]

Registro emitido por las secciones

  • kind [badge:required] string — Tipo de registro. Puede ser requirement_pending, media_claim, conversation_reference, shared_file, error o summary.
  • id [badge:required] string — Identificador principal del candidato o del error.
  • related [badge:required] object — Relación mínima con otros identificadores.
  • observed [badge:required] object — Estado observado en el momento del inventario.
  • evidence [badge:required] array — Lista de fragmentos de evidencia con code y value.
  • classification [badge:required] string — Clasificación devuelta por el clasificador puro.
  • reasonCode [badge:required] string — Código de razón de la clasificación.
  • suggestedAction [badge:required] string — Acción sugerida por el clasificador.
  • requiresApproval [badge:required] boolean — Indica si la acción requiere aprobación.
  • authorizesExecution [badge:required] boolean — Indica si la salida autoriza ejecución automática.
  • ageSeconds [badge:optional] number | null — Antigüedad derivada de requestedAt o updatedAt.
  • observedAt [badge:required] string — Marca ISO del momento de observación.
  • related.ownerId — Dueño del Requirement o de la Conversation.
  • related.listingId — Listing asociado a una MediaFileClaim.
  • related.requirementId — Requirement activo asociado a una Conversation.
  • related.listingIds — Listings que comparten un archivo.
  • observed.deletionStatus — Estado de eliminación observado en Listing.
  • observed.attempts — Intentos de eliminación observados en Listing.
  • observed.publicationStatus — Estado de publicación observado.
  • observed.lifecycleStatus — Estado de ciclo de vida observado.
  • observed.accountStatus — Estado de cuenta del dueño, cuando existe.

Resumen final

El último valor emitido por runDeletionInventory es un objeto de summary. Guarda el estado global de la corrida y las estadísticas por sección.

  • kind [badge:required] string — Siempre summary.
  • schemaVersion [badge:required] number — Versión del esquema de inventario.
  • observedAt [badge:required] string — Marca ISO de inicio de la corrida.
  • sections [badge:required] object — Estadísticas por sección ejecutada.
  • failed [badge:required] boolean — true si alguna sección registró errores.

Cada sección usa una forma base común.

  • scanned — Documentos recorridos en páginas.
  • emitted — Registros emitidos por la sección.
  • limitReached — Indica que la sección cortó por el límite configurado.
  • errors — Cantidad de errores acumulados.
  • classifications — Conteo por valor de classification.

Algunas secciones agregan campos propios.

  • conversations.validReferences — Cuenta referencias válidas que no se emiten.
  • shared.presenceOnly — Marca que la sección solo informa presencia de compartición.

Behaviors

El archivo contiene helpers pequeños y funciones de sección con responsabilidades muy concretas. Todas mantienen el estilo de solo lectura del módulo. fileciteturn1file8turn1file10turn1file16

Helpers

  • step(hooks, name) — Ejecuta un hook de prueba si existe. No afecta la lógica productiva.
  • errorCode(error) — Normaliza el código del error y recorta el resultado a 64 caracteres. Nunca usa el mensaje completo.
  • ageSeconds(date, now) — Calcula la antigüedad en segundos cuando recibe un Date. Devuelve null en otros casos.
  • imageKitFileIdsOf(listing) — Extrae fileId de media con provider = "imagekit" y de sourceDocuments.
  • referencedFileIds(listing) — Devuelve un Set con todos los fileId referenciados por media y sourceDocuments.
  • record(...) — Construye la forma común de salida. Copia los campos de la decisión y añade ageSeconds y observedAt. fileciteturn1file8turn1file16
  • readEvidence(section, readers, errors) — Ejecuta varias lecturas de evidencia. Cada fallo agrega un error y deja ese bloque en null.
  • newStats() — Crea el acumulador base de una sección.
  • count(stats, item) — Incrementa emitted y acumula la clasificación emitida.

requirementsSection

  • Filtra Listing con intent: "requirement" y deletion.status: "pending".
  • Paginiza por _id y usa proyección mínima.
  • Reúne owners, refs, reservations, attach, claims y stillPending.
  • Relee stillPending al final del lote para detectar cambios durante la lectura.
  • Clasifica con classifyPendingRequirement. fileciteturn1file0turn1file18

claimsSection

  • Lee MediaFileClaim por _id y proyecta listingId, state, holder y updatedAt.
  • Busca el Listing dueño y los Listings que referencian el archivo.
  • Usa persistedFileIdentity(listing, claim._id).present para comprobar identidad persistida. fileciteturn1file0turn1file9
  • Construye evidencia con listing y references.
  • Clasifica con classifyMediaClaim. fileciteturn1file0turn1file18

conversationsSection

  • Lee Conversation con activeRequirementId no nulo.
  • Usa Conversation.aggregate con $lookup hacia Listing.
  • Construye evidencia sobre el target, el intent, la propiedad compartida y el estado de eliminación.
  • Si la decisión es VALID_REFERENCE, incrementa validReferences y no emite candidato. fileciteturn1file1turn1file18
  • En otros casos, emite un conversation_reference.

sharedSection

  • Usa una agregación de Listing con allowDiskUse: true.
  • Calcula la unión de media.fileId y sourceDocuments.fileId.
  • Agrupa por archivo y conserva solo los que aparecen en más de un Listing.
  • Marca presenceOnly = true. Ese dato solo prueba compartición presente. No prueba ausencia de otras referencias. fileciteturn1file10turn1file1
  • Clasifica con classifySharedFile.

runDeletionInventory

  • Valida que las secciones existan en INVENTORY_SECTIONS.
  • Valida que batchSize esté entre 1 y MAX_BATCH_SIZE.
  • Valida que limit sea un entero positivo.
  • Construye ctx con observedAt calculado desde now.
  • Ejecuta cada sección en el orden pedido.
  • Marca failed = true si alguna sección registra errores.
  • Emite un summary final al terminar.

Workflows

La ejecución real sigue una secuencia corta y determinista. El diseño busca acotar memoria y limitar el alcance de cada lote. fileciteturn1file0turn1file16

1. Validar configuración | Comprueba secciones, batchSize y limit antes de iniciar.
2. Preparar contexto | Crea ctx con observedAt, now, maxTimeMS y hooks.
3. Recorrer secciones | Ejecuta requirements, claims, conversations y shared, si fueron solicitadas.
4. Leer evidencia | Hace consultas mínimas y maneja fallos por bloque de evidencia.
5. Clasificar y emitir | Convierte la evidencia en registros normalizados.
6. Cerrar corrida | Emite un summary con estadísticas y marca failed si hubo errores.

Gotchas & edge cases

El archivo contiene varias reglas de control de fallos. Todas están documentadas en el código y en las pruebas de integración. fileciteturn1file0turn1file2

[!WARNING] Si una lectura de evidencia falla, el bloque afectado queda en null y se agrega un registro error. Los candidatos de ese bloque pueden salir UNRESOLVED. fileciteturn1file8turn1file18

[!WARNING] Si falla la consulta de página, la sección se detiene de inmediato. No intenta continuar con el siguiente lote. fileciteturn1file8turn1file10

[!NOTE] No hay aislamiento de instantánea entre colecciones. El archivo relee stillPending al final del lote y cruza lecturas en claims. Las contradicciones se clasifican VERIFY_FIRST. fileciteturn1file0turn1file18

[!NOTE] sharedSection solo informa compartición observada. Un resultado parcial nunca prueba que un archivo no tenga otras referencias fuera del lote. fileciteturn1file1turn1file10

[!CAUTION] limit puede cortar la ejecución antes de revisar todos los candidatos. En ese caso, limitReached queda en true. fileciteturn1file9turn1file10

[!TIP] DEFAULT_SECTIONS omite shared. Esa sección solo corre si se pide explícitamente.

Este módulo se conecta con piezas puras y con lecturas de modelo muy concretas. No expone rutas HTTP, y su salida está pensada para consumo interno. fileciteturn1file0turn1file9

  • services/api/src/modules/reconciliation/deletion-inventory.classify.js — Contiene los clasificadores puros que deciden la salida final. El archivo los importa para requirements, claims, conversations y shared. fileciteturn1file0turn1file1turn1file18
  • services/api/src/modules/conversations/requirement-association.service.js — Aporta countReservationsForRequirements y countPendingAttachForRequirements. El inventario los usa en requirementsSection.
  • services/api/src/modules/reconciliation/erasing-claim-evidence.js — Aporta persistedFileIdentity. El inventario lo usa para validar claims. fileciteturn1file0turn1file9
  • services/api/src/models/Listing.js — Fuente principal para Requirements, referencias compartidas y targets de conversación. fileciteturn1file0turn1file10
  • services/api/src/models/Conversation.js — Fuente principal para referencias activas de Requirements. fileciteturn1file0turn1file1
  • services/api/src/models/User.js — Fuente para estado de cuenta del owner en Requirements.
  • services/api/src/models/MediaFileClaim.js — Fuente para claims y sus estados. fileciteturn1file0turn1file18
  • services/api/integration/mongo/e6-1-deletion-inventory.mongo.js — Prueba de integración sobre MongoDB real. Verifica clasificaciones, paginación, concurrencia y cero escrituras.
  • services/api/test/e6-1-inventory-static.test.js — Guardia estática que confirma ausencia de escrituras, $merge y $out.