chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
@@ -0,0 +1,303 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import {
PHASE_D3_FINDING_CATALOG_ITEMS,
PHASE_D3_FINDING_CATEGORIES,
} from '../seeds/phase-d3-finding-catalog';
const newPermissions = [
'finding_catalog.read',
'finding_catalog.manage',
'inspection_findings.read',
'inspection_findings.create',
'inspection_findings.update',
'inspection_findings.follow_up',
] as const;
const rolePermissionValues = `
('admin', 'finding_catalog.read'),
('admin', 'finding_catalog.manage'),
('admin', 'inspection_findings.read'),
('admin', 'inspection_findings.create'),
('admin', 'inspection_findings.update'),
('admin', 'inspection_findings.follow_up'),
('director', 'finding_catalog.read'),
('director', 'inspection_findings.read'),
('director', 'inspection_findings.follow_up'),
('supervisor', 'finding_catalog.read'),
('supervisor', 'finding_catalog.manage'),
('supervisor', 'inspection_findings.read'),
('supervisor', 'inspection_findings.create'),
('supervisor', 'inspection_findings.update'),
('supervisor', 'inspection_findings.follow_up'),
('inspector', 'finding_catalog.read'),
('inspector', 'inspection_findings.read'),
('inspector', 'inspection_findings.create'),
('inspector', 'inspection_findings.update'),
('inspector', 'inspection_findings.follow_up'),
('auditor', 'finding_catalog.read'),
('auditor', 'inspection_findings.read')
`;
function quoteIdentifier(identifier: string): string {
return `"${identifier.replaceAll('"', '""')}"`;
}
export class PhaseD3InspectionFindings1787155200000 implements MigrationInterface {
name = 'PhaseD3InspectionFindings1787155200000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE finding_categories (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
code varchar(80) NOT NULL,
name varchar(200) NOT NULL,
sort_order integer NOT NULL DEFAULT 0,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_finding_categories_code UNIQUE (code),
CONSTRAINT chk_finding_categories_sort_order CHECK (sort_order >= 0),
CONSTRAINT chk_finding_categories_name CHECK (LENGTH(TRIM(name)) > 0)
)
`);
await queryRunner.query(`
CREATE INDEX idx_finding_categories_active_order
ON finding_categories (is_active, sort_order)
`);
await queryRunner.query(`
CREATE TABLE finding_catalog_items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
category_id uuid NOT NULL,
code varchar(120) NOT NULL,
source_number integer NOT NULL,
title varchar(500) NOT NULL,
legal_basis text,
glossary text,
import_note text,
revision integer NOT NULL DEFAULT 1,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_finding_catalog_items_code UNIQUE (code),
CONSTRAINT uq_finding_catalog_items_source UNIQUE (category_id, source_number),
CONSTRAINT chk_finding_catalog_items_source CHECK (source_number > 0),
CONSTRAINT chk_finding_catalog_items_revision CHECK (revision > 0),
CONSTRAINT chk_finding_catalog_items_title CHECK (LENGTH(TRIM(title)) > 0),
CONSTRAINT fk_finding_catalog_items_category FOREIGN KEY (category_id)
REFERENCES finding_categories(id) ON DELETE RESTRICT
)
`);
await queryRunner.query(`
CREATE INDEX idx_finding_catalog_items_active
ON finding_catalog_items (category_id, is_active)
`);
await queryRunner.query(`
CREATE TABLE inspection_findings (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
act_id uuid NOT NULL,
asset_id uuid NOT NULL,
catalog_item_id uuid,
finding_number integer NOT NULL,
code varchar(40) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'OPEN',
title varchar(500) NOT NULL,
description text NOT NULL,
legal_basis text,
glossary text,
catalog_revision integer,
correction_due_on date,
company_response text,
company_response_received_on date,
company_committed_correction_on date,
next_control_on date,
current_version integer NOT NULL DEFAULT 0,
closed_at timestamptz,
closed_by uuid,
closure_notes text,
created_by uuid,
updated_by uuid,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_inspection_findings_number UNIQUE (act_id, finding_number),
CONSTRAINT uq_inspection_findings_code UNIQUE (code),
CONSTRAINT chk_inspection_findings_number CHECK (
finding_number BETWEEN 1 AND 999
),
CONSTRAINT chk_inspection_findings_code CHECK (
code ~ '^ACTA-[0-9]{4}-[0-9]{6}-H[0-9]{3}$'
),
CONSTRAINT chk_inspection_findings_status CHECK (
status IN ('OPEN', 'CLOSED', 'VOIDED')
),
CONSTRAINT chk_inspection_findings_content CHECK (
LENGTH(TRIM(title)) > 0 AND LENGTH(TRIM(description)) > 0
),
CONSTRAINT chk_inspection_findings_version CHECK (current_version >= 0),
CONSTRAINT chk_inspection_findings_catalog_revision CHECK (
catalog_revision IS NULL OR catalog_revision > 0
),
CONSTRAINT chk_inspection_findings_response CHECK (
(company_response IS NULL AND company_response_received_on IS NULL)
OR (
LENGTH(TRIM(COALESCE(company_response, ''))) > 0
AND company_response_received_on IS NOT NULL
)
),
CONSTRAINT chk_inspection_findings_commitment CHECK (
company_committed_correction_on IS NULL
OR company_response_received_on IS NOT NULL
),
CONSTRAINT chk_inspection_findings_closure CHECK (
status <> 'CLOSED'
OR (closed_at IS NOT NULL AND closed_by IS NOT NULL
AND LENGTH(TRIM(COALESCE(closure_notes, ''))) >= 10)
),
CONSTRAINT fk_inspection_findings_act FOREIGN KEY (act_id)
REFERENCES inspection_acts(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_findings_asset FOREIGN KEY (asset_id)
REFERENCES assets(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_findings_catalog_item FOREIGN KEY (catalog_item_id)
REFERENCES finding_catalog_items(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_findings_closed_by FOREIGN KEY (closed_by)
REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT fk_inspection_findings_created_by FOREIGN KEY (created_by)
REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT fk_inspection_findings_updated_by FOREIGN KEY (updated_by)
REFERENCES users(id) ON DELETE SET NULL
)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_findings_status_control
ON inspection_findings (status, next_control_on)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_findings_asset_status
ON inspection_findings (asset_id, status)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_findings_catalog_item
ON inspection_findings (catalog_item_id)
`);
await queryRunner.query(`
CREATE TABLE inspection_finding_versions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
finding_id uuid NOT NULL,
version_number integer NOT NULL,
event varchar(32) NOT NULL,
snapshot jsonb NOT NULL,
actor_user_id uuid,
actor_username varchar(80),
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_inspection_finding_versions_number
UNIQUE (finding_id, version_number),
CONSTRAINT chk_inspection_finding_versions_number CHECK (version_number > 0),
CONSTRAINT chk_inspection_finding_versions_event CHECK (
event IN ('CREATED', 'UPDATED', 'FOLLOW_UP_UPDATED')
),
CONSTRAINT fk_inspection_finding_versions_finding FOREIGN KEY (finding_id)
REFERENCES inspection_findings(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_finding_versions_actor FOREIGN KEY (actor_user_id)
REFERENCES users(id) ON DELETE SET NULL
)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_finding_versions_created_at
ON inspection_finding_versions (created_at)
`);
for (const category of PHASE_D3_FINDING_CATEGORIES) {
await queryRunner.query(`
INSERT INTO finding_categories (code, name, sort_order)
VALUES ($1, $2, $3)
`, [category.code, category.name, category.sortOrder]);
}
for (const item of PHASE_D3_FINDING_CATALOG_ITEMS) {
await queryRunner.query(`
INSERT INTO finding_catalog_items (
category_id, code, source_number, title, legal_basis, glossary, import_note
)
SELECT id, $2, $3, $4, $5, $6, $7
FROM finding_categories
WHERE code = $1
`, [
item.categoryCode,
item.code,
item.sourceNumber,
item.title,
item.legalBasis,
item.glossary,
item.importNote,
]);
}
await queryRunner.query(`
INSERT INTO permissions (code, description)
VALUES
('finding_catalog.read', 'Consultar el catálogo de hallazgos'),
('finding_catalog.manage', 'Administrar el catálogo de hallazgos'),
('inspection_findings.read', 'Consultar hallazgos y seguimiento'),
('inspection_findings.create', 'Crear hallazgos dentro de actas'),
('inspection_findings.update', 'Actualizar hallazgos en borrador'),
('inspection_findings.follow_up', 'Registrar respuestas y próximos controles')
ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description
`);
await queryRunner.query(`
WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues})
INSERT INTO role_permissions (role_id, permission_id)
SELECT role.id, permission.id
FROM mapping
INNER JOIN roles role ON role.code = mapping.role_code
INNER JOIN permissions permission ON permission.code = mapping.permission_code
ON CONFLICT (role_id, permission_id) DO NOTHING
`);
const appRole = process.env.DB_APP_USER;
if (!appRole) throw new Error('Missing required environment variable: DB_APP_USER');
const applicationRole = quoteIdentifier(appRole);
await queryRunner.query(`
GRANT SELECT, INSERT, UPDATE ON TABLE
finding_categories, finding_catalog_items, inspection_findings
TO ${applicationRole}
`);
await queryRunner.query(`
GRANT SELECT, INSERT ON TABLE inspection_finding_versions
TO ${applicationRole}
`);
await queryRunner.query(`
REVOKE DELETE ON TABLE
finding_categories, finding_catalog_items,
inspection_findings, inspection_finding_versions
FROM ${applicationRole}
`);
await queryRunner.query(`
REVOKE UPDATE ON TABLE inspection_finding_versions
FROM ${applicationRole}
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues})
DELETE FROM role_permissions role_permission
USING roles role, permissions permission, mapping
WHERE role_permission.role_id = role.id
AND role_permission.permission_id = permission.id
AND role.code = mapping.role_code
AND permission.code = mapping.permission_code
`);
await queryRunner.query(
`DELETE FROM permissions WHERE code = ANY($1::varchar[])
AND NOT EXISTS (
SELECT 1 FROM role_permissions WHERE permission_id = permissions.id
)`,
[newPermissions],
);
await queryRunner.query(`DROP TABLE inspection_finding_versions`);
await queryRunner.query(`DROP TABLE inspection_findings`);
await queryRunner.query(`DROP TABLE finding_catalog_items`);
await queryRunner.query(`DROP TABLE finding_categories`);
}
}