import 'reflect-metadata'; import { randomUUID } from 'node:crypto'; import { NestFactory } from '@nestjs/core'; import { DataSource } from 'typeorm'; import { AppModule } from '../app.module'; import { AssetsService } from '../asset-master/assets.service'; import { AssetOperationalRelationsService } from '../asset-master/asset-operational-relations.service'; import { AssetProvenanceService } from '../asset-master/asset-provenance.service'; import { AssetRegistryService } from '../asset-master/asset-registry.service'; import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context'; import { AreaOrganizationRole, AssetDataOrigin, AssetInformationStatus, AssetOperationalStatus, AssetSourceDocumentRelationType, OrganizationKind, SourceDocumentType, } from '../database/entities'; const DEMO_PREFIX = 'DEV-DEMO-'; const SOURCE_REFERENCE = 'DH-DEV-DEMO:ITN124/2026'; function fakeRequest(): RequestWithContext { return { requestId: `dev-seed-${randomUUID()}`, ip: '127.0.0.1', socket: { remoteAddress: '127.0.0.1' }, header: () => 'dhv2-dev-seed', } as unknown as RequestWithContext; } async function buildPrincipal(dataSource: DataSource): Promise { const [user] = (await dataSource.query(` SELECT id, username, first_name AS "firstName", last_name AS "lastName", email, must_change_password AS "mustChangePassword" FROM users WHERE status = 'ACTIVE' ORDER BY CASE WHEN username = 'admin' THEN 0 ELSE 1 END, created_at LIMIT 1 `)) as Array<{ id: string; username: string; firstName: string; lastName: string; email: string | null; mustChangePassword: boolean }>; if (!user) throw new Error('No hay un usuario activo para registrar la auditoría del demo'); const roles = (await dataSource.query(` SELECT role.code FROM user_roles ur JOIN roles role ON role.id = ur.role_id WHERE ur.user_id = $1 `, [user.id])) as Array<{ code: string }>; const permissions = (await dataSource.query(` SELECT DISTINCT permission.code FROM user_roles ur JOIN role_permissions rp ON rp.role_id = ur.role_id JOIN permissions permission ON permission.id = rp.permission_id WHERE ur.user_id = $1 `, [user.id])) as Array<{ code: string }>; return { userId: user.id, username: user.username, sessionId: 'development-seed', firstName: user.firstName, lastName: user.lastName, email: user.email, mustChangePassword: user.mustChangePassword, roles: roles.map((row) => row.code), permissions: permissions.map((row) => row.code), transport: 'cookie', }; } async function main(): Promise { const app = await NestFactory.createApplicationContext(AppModule, { logger: ['error', 'warn'] }); try { const dataSource = app.get(DataSource); const assets = app.get(AssetsService); const relations = app.get(AssetOperationalRelationsService); const provenance = app.get(AssetProvenanceService); const registry = app.get(AssetRegistryService); const principal = await buildPrincipal(dataSource); const request = fakeRequest(); const [existing] = (await dataSource.query( `SELECT COUNT(*)::integer AS count FROM assets WHERE code LIKE $1 OR source_reference = $2`, [`${DEMO_PREFIX}%`, SOURCE_REFERENCE], )) as Array<{ count: number }>; if (Number(existing?.count ?? 0) > 0) { throw new Error('Ya existen datos DEV-DEMO. Ejecutá primero scripts/dev-clean-demo.sh si querés reconstruir el ejemplo.'); } const typeRows = (await dataSource.query(` SELECT id, code FROM asset_types WHERE code = ANY($1::varchar[]) AND is_active = true `, [['area', 'empresa', 'planta', 'tanque']])) as Array<{ id: string; code: string }>; const types = new Map(typeRows.map((row) => [row.code, row.id])); const missing = ['area', 'empresa', 'planta', 'tanque'].filter((code) => !types.has(code)); if (missing.length) { throw new Error(`Faltan tipos técnicos (${missing.join(', ')}). En Maestro > Tipos y atributos ejecutá “Completar catálogo técnico”.`); } const attributeRows = (await dataSource.query(` SELECT d.id, d.code, t.code AS type_code FROM asset_attribute_definitions d JOIN asset_types t ON t.id = d.asset_type_id WHERE t.code = 'tanque' AND d.is_active = true `)) as Array<{ id: string; code: string; type_code: string }>; const tankAttrs = new Map(attributeRows.map((row) => [row.code, row.id])); const createAsset = async (input: { code: string; name: string; typeCode: string; parentId?: string | null; areaId?: string | null; companyId?: string | null; description: string; attributes?: Record; }) => { const created = await assets.create({ code: input.code, name: input.name, typeId: types.get(input.typeCode)!, parentId: input.parentId ?? null, operationalAreaId: input.areaId ?? null, operatorCompanyId: input.companyId ?? null, description: input.description, informationStatus: AssetInformationStatus.DRAFT, attributes: input.attributes ?? {}, }, principal, request); await provenance.update(created.id, { origin: AssetDataOrigin.PROVIDED_DOCUMENT, sourceName: 'DH DEV DEMO · Informe Técnico Nº 124/2026', sourceReference: SOURCE_REFERENCE, observedAt: '2026-06-25T12:00:00-03:00', notes: 'DATO DE PRUEBA. Caso construido a partir del informe técnico recibido para validar nomenclatura, jerarquía y flujo. No constituye carga maestra real ni validación de vigencia.', }, principal, request); return created; }; const area = await createAsset({ code: 'DEV-DEMO-AREA-ATAMISQUI', name: '[PRUEBA] Atamisqui', typeCode: 'area', description: 'Área usada exclusivamente como ejemplo de desarrollo a partir del Informe Técnico Nº 124/2026.', }); const company = await createAsset({ code: 'DEV-DEMO-EMP-PS', name: '[PRUEBA] Petróleos Sudamericanos Energy S.A.', typeCode: 'empresa', description: 'Organización de prueba para validar la relación Área–Operadora. No implica vigencia administrativa real.', }); await registry.upsertOrganizationProfile(company.id, { organizationKind: OrganizationKind.COMPANY, legalName: 'Petróleos Sudamericanos Energy S.A.', notes: 'Perfil de prueba DH DEV DEMO.', }, principal, request); const sourceDocument = await registry.createSourceDocument({ documentType: SourceDocumentType.TECHNICAL_REPORT, documentNumber: 'IT 124/2026 · DH-DEV-DEMO', title: 'DH DEV DEMO · Informe Técnico Nº 124/2026', issuer: 'Dirección de Hidrocarburos · Gobierno de Mendoza', documentDate: '2026-06-25', externalReference: SOURCE_REFERENCE, notes: 'Documento de referencia cargado exclusivamente para el caso de prueba del sistema.', }, principal, request); await relations.create({ areaId: area.id, companyId: company.id, relationRole: AreaOrganizationRole.OPERATOR, sourceDocumentId: sourceDocument.id, reason: 'Relación creada para el caso de prueba DH DEV DEMO basado en ITN 124/2026.', }, principal, request); const plant = await createAsset({ code: 'DEV-DEMO-PLANTA-3PB', name: '[PRUEBA] Planta de Entrega 3 PB', typeCode: 'planta', parentId: area.id, areaId: area.id, companyId: company.id, description: 'Instalación seleccionada como referencia en el Informe Técnico Nº 124/2026.', }); const tankAttributes = (capacity: number): Record => { const values: Record = {}; const capacityId = tankAttrs.get('capacidad_nominal'); const productId = tankAttrs.get('producto_servicio'); if (capacityId) values[capacityId] = capacity; if (productId) values[productId] = 'Petróleo'; return values; }; const tank57 = await createAsset({ code: 'DEV-DEMO-TK-57', name: '[PRUEBA] Tanque TK-57', typeCode: 'tanque', parentId: plant.id, areaId: area.id, companyId: company.id, description: 'Tanque de prueba citado en ITN 124/2026; capacidad informada 480 m³.', attributes: tankAttributes(480), }); const tank58 = await createAsset({ code: 'DEV-DEMO-TK-58', name: '[PRUEBA] Tanque TK-58', typeCode: 'tanque', parentId: plant.id, areaId: area.id, companyId: company.id, description: 'Tanque de prueba citado en ITN 124/2026; capacidad informada 320 m³.', attributes: tankAttributes(320), }); for (const assetId of [area.id, company.id, plant.id, tank57.id, tank58.id]) { await registry.linkDocument(assetId, sourceDocument.id, { relationType: AssetSourceDocumentRelationType.SOURCE }, principal, request); } await assets.changeOperationalStatus(tank57.id, { status: AssetOperationalStatus.OUT_OF_SERVICE }, principal, request); await assets.changeOperationalStatus(tank58.id, { status: AssetOperationalStatus.OUT_OF_SERVICE }, principal, request); console.log('DEMO DH creado correctamente'); console.log('Área: [PRUEBA] Atamisqui'); console.log('Organización operadora: [PRUEBA] Petróleos Sudamericanos Energy S.A.'); console.log('Instalación: [PRUEBA] Planta de Entrega 3 PB'); console.log('Equipos: [PRUEBA] TK-57 (480 m³), TK-58 (320 m³)'); console.log(`Marca de limpieza: ${SOURCE_REFERENCE}`); } finally { await app.close(); } } main().catch((error) => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });