Files
dh-inspeccion-v2/api-v3/src/cli/dev-seed-mendoza-demo.ts
T

217 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<AuthPrincipal> {
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<void> {
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<string, unknown>;
}) => {
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 ÁreaOperadora. 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<string, unknown> => {
const values: Record<string, unknown> = {};
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;
});