import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import { loadF5InventoryAuthoritativeSource } from '../../src/reference-data/f5-authoritative-inventory-source'; function key(value: string): string { return value.normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .replace(/[^a-z0-9]+/g, ' ') .trim() .replace(/\s+/g, ' '); } test('F5 territory preload is exactly the authorized Tablas de yacimiento y areas workbook', () => { const source = loadF5InventoryAuthoritativeSource().areaSource; assert.equal(source.file, 'Tablas de yacimiento y areas.xlsx'); assert.equal(source.sheet, 'cr26e_tabla1'); assert.equal(source.sha256, '8260fcadebbcd631a4c95260d0a67c3ecb28d497b32decb02a1c0847be5afa78'); assert.equal(source.rows.length, 230); const areas = new Set(source.rows.map((row) => key(row.area))); const literalYacimientoNames = new Set(source.rows.map((row) => row.yacimiento.trim())); const yacimientos = new Set(source.rows.map((row) => key(row.yacimiento))); const areaYacimiento = new Set(source.rows.map((row) => `${key(row.area)}|${key(row.yacimiento)}`)); const departments = new Set(source.rows.map((row) => key(row.departamento))); const concessionTypes = new Set(source.rows.map((row) => key(row.tipoConcesion))); const operators = new Set(source.rows.map((row) => key(row.empresaOperadora))); assert.equal(areas.size, 64); // The authorized workbook contains 220 literal Yacimiento names. The F5 key // intentionally folds accents/punctuation/case, so one pair of literal names // collides into the same normalized key. Yacimiento identity is Area+Yacimiento, // never a globally-unique normalized name. assert.equal(literalYacimientoNames.size, 220); assert.equal(yacimientos.size, 219); assert.equal(areaYacimiento.size, 230); assert.equal(departments.size, 7); assert.equal(concessionTypes.size, 2); assert.equal(operators.size, 13); const literalNamesByKey = new Map>(); for (const row of source.rows) { const normalized = key(row.yacimiento); const literals = literalNamesByKey.get(normalized) ?? new Set(); literals.add(row.yacimiento.trim()); literalNamesByKey.set(normalized, literals); } assert.equal([...literalNamesByKey.values()].filter((items) => items.size > 1).length, 1); const literalAreasByYacimiento = new Map>(); const areasByYacimiento = new Map>(); for (const row of source.rows) { const literal = row.yacimiento.trim(); const literalMemberships = literalAreasByYacimiento.get(literal) ?? new Set(); literalMemberships.add(key(row.area)); literalAreasByYacimiento.set(literal, literalMemberships); const normalized = key(row.yacimiento); const memberships = areasByYacimiento.get(normalized) ?? new Set(); memberships.add(key(row.area)); areasByYacimiento.set(normalized, memberships); } const literalMembershipSurplus = [...literalAreasByYacimiento.values()] .reduce((total, memberships) => total + Math.max(0, memberships.size - 1), 0); const normalizedMembershipSurplus = [...areasByYacimiento.values()] .reduce((total, memberships) => total + Math.max(0, memberships.size - 1), 0); assert.equal(literalMembershipSurplus, 10); assert.equal(normalizedMembershipSurplus, 11); for (const area of areas) { const rows = source.rows.filter((row) => key(row.area) === area); assert.equal(new Set(rows.map((row) => key(row.departamento))).size, 1, `${area}: departamento`); assert.equal(new Set(rows.map((row) => key(row.tipoConcesion))).size, 1, `${area}: concesión`); assert.equal(new Set(rows.map((row) => key(row.empresaOperadora))).size, 1, `${area}: operadora`); } }); test('F5 technical catalog is exactly final_modelov2 and resolves internal IDEM references', () => { const source = loadF5InventoryAuthoritativeSource().catalogSource; assert.equal(source.file, 'final_modelov2.xlsx'); assert.equal(source.sheet, 'Hoja1'); assert.equal(source.sha256, 'c9a2d1db59fff2157162c41009b8c9042a3c7a3001649239a07732a3b8fca155'); assert.equal(source.installations.length, 14); assert.equal(source.subinstallations.length, 109); const installationKeys = new Set(source.installations.map((item) => key(item.name))); assert.equal(installationKeys.size, 14); for (const subinstallation of source.subinstallations) { assert.ok(installationKeys.has(key(subinstallation.installation)), `${subinstallation.name} parent`); assert.ok(subinstallation.findings.every((finding) => !/^idem\b/i.test(finding.trim())), `${subinstallation.name} has unresolved IDEM`); } for (const installation of source.installations) { assert.ok(installation.findings.every((finding) => !/^idem\b/i.test(finding.trim())), `${installation.name} has unresolved IDEM`); } const allFindings = new Map(); for (const finding of source.universalFindings) allFindings.set(key(finding), finding); for (const installation of source.installations) { for (const finding of installation.findings) allFindings.set(key(finding), finding); } for (const subinstallation of source.subinstallations) { for (const finding of subinstallation.findings) allFindings.set(key(finding), finding); } assert.equal(allFindings.size, 177); assert.deepEqual( new Set(source.universalFindings.map(key)), new Set([ 'ORDEN Y LIMPIEZA', 'CARTELERIA PREVENTIVA / INFORMATIVA', 'EXTINTORES', ].map(key)), ); }); test('F5 source contracts keep Empresa out of physical ownership and preserve sealed documents on merge', () => { const mergeSource = readFileSync('src/asset-master/inventory-merge.service.ts', 'utf8'); const fieldSource = readFileSync('src/inspection-visits/field-inventory.service.ts', 'utf8'); const contextMigration = readFileSync('src/database/migrations/1790087250000-f5-operational-context-compatibility.ts', 'utf8'); const catalogMigration = readFileSync('src/database/migrations/1790087300000-f5-authoritative-inventory-catalog.ts', 'utf8'); assert.match(mergeSource, /resolvePhysicalAreaId/); assert.match(mergeSource, /documentInvariantsBefore/); assert.match(mergeSource, /locked_sha256/); assert.match(mergeSource, /closure_sha256/); assert.match(mergeSource, /frozen_sha256/); assert.match(mergeSource, /gedo_pdf_sha256/); assert.match(mergeSource, /historicalReferencesRewritten:\s*false/); assert.doesNotMatch(mergeSource, /source\.operatorCompanyId\s*!==\s*canonical\.operatorCompanyId/); assert.doesNotMatch(mergeSource, /misma Área y Operadora/); assert.match(fieldSource, /Área de esta inspección/); assert.match(fieldSource, /area_company_relations/); assert.doesNotMatch(fieldSource, /asset\.operator_company_id\s*=\s*\$2::uuid/); assert.match(contextMigration, /public async up[\s\S]*installAreaOwnedGuard/); assert.match(contextMigration, /public async down[\s\S]*installLegacyPairedGuard/); assert.match(contextMigration, /La Empresa se cambia en la relación temporal del Área/); assert.doesNotMatch(catalogMigration, /unaccent\s*\(/i); assert.doesNotMatch(catalogMigration, /source_reference\s+IS\s+NULL/i); assert.match(catalogMigration, /F5_SOURCE_FAMILY_COUNT\s*=\s*123/); });