import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import test from 'node:test'; function source(path: string): string { return readFileSync(resolve(process.cwd(), path), 'utf8'); } test('F6.1 conserva una jerarquía territorial única y Empresa fuera del árbol físico', () => { const structure = source('src/asset-master/inventory-structure.service.ts'); const dto = source('src/asset-master/dto/create-inventory-structure.dto.ts'); assert.match(dto, /'EMPRESA',[\s\S]*'DEPARTAMENTO',[\s\S]*'AREA',[\s\S]*'YACIMIENTO',[\s\S]*'INSTALACION',[\s\S]*'SUBINSTALACION'/); assert.match(structure, /EMPRESA:\s*null/); assert.match(structure, /DEPARTAMENTO:\s*null/); assert.match(structure, /AREA:\s*'departamento'/); assert.match(structure, /YACIMIENTO:\s*'area'/); assert.match(structure, /INSTALACION:\s*'yacimiento'/); assert.match(structure, /SUBINSTALACION:\s*'instalacion'/); assert.match(structure, /Departamento → Área → Yacimiento → Instalación → Subinstalación/); }); test('F6.1 usa Área + Operadora como contexto de planificación y no mezcla Yacimiento con Área', () => { const lifecycle = source('src/inspection-visits/inspection-visit-lifecycle.service.ts'); const visits = source('src/inspection-visits/inspection-visits.service.ts'); assert.match(lifecycle, /El contexto operativo de una Inspección es Área \+ Operadora vigente/); assert.doesNotMatch(lifecycle, /Área\/Yacimiento/); assert.match(visits, /FROM area_company_relations/); assert.match(visits, /relation\.relation_role = 'OPERATOR'/); }); test('F6.1 mantiene múltiples Actas por Inspección pero un solo borrador simultáneo', () => { const act = source('src/database/entities/inspection-act.entity.ts'); const lifecycle = source('src/inspection-visits/inspection-visit-lifecycle.service.ts'); assert.match(act, /uq_inspection_acts_one_draft_per_visit/); assert.match(act, /where: "status = 'DRAFT'"/); assert.match(act, /Una Inspección puede acumular múltiples Actas/); assert.match(lifecycle, /Todas las Actas deben estar SELLADAS antes de cerrar la Inspección/); }); test('F6.1 permite Hallazgos directos sólo en Yacimiento, Instalación y Subinstalación y conserva OTROS', () => { const field = source('src/inspection-visits/field-findings.service.ts'); const resolver = source('src/inspection-findings/f3-finding-catalog-resolver.service.ts'); assert.match(field, /\['yacimiento', 'instalacion', 'subinstalacion'\]\.includes\(assetTypeCode\)/); assert.match(field, /assetTypeCode !== 'yacimiento' && !row\.inventoryFamilyId/); assert.match(field, /canAddAnother: true/); assert.match(resolver, /code: 'OTHER'/); assert.match(resolver, /label: 'OTROS'/); }); test('F6.1 abre Inspecciones desde Android usando el lifecycle canónico', () => { const controller = source('src/inspection-visits/inspection-visits.controller.ts'); assert.match(controller, /@Post\('mobile\/open'\)/); assert.match(controller, /leadInspectorUserId: principal\.userId/); assert.match(controller, /this\.lifecycle\.plan\(created\.id/); assert.match(controller, /this\.lifecycle\.start\(created\.id/); }); test('F6.1 separa oficialización GEDO de la activación del vencimiento no urgente', () => { const report = source('src/inspection-reports/inspection-report-workflow.service.ts'); const act = source('src/database/entities/inspection-act.entity.ts'); assert.match(report, /GEDO oficializa el INF, pero no equivale por sí solo a la notificación/); assert.match(report, /deadlineActivation: 'PENDING_NOTIFICATION_EVENT'/); assert.match(act, /NOTIFICATION_DATE = 'GEDO_DATE'/); });