diff --git a/api-v3/src/database/migrations/1790000400000-f4-remove-director-report-legacy.ts b/api-v3/src/database/migrations/1790000400000-f4-remove-director-report-legacy.ts index da4f54f..96b7ff8 100644 --- a/api-v3/src/database/migrations/1790000400000-f4-remove-director-report-legacy.ts +++ b/api-v3/src/database/migrations/1790000400000-f4-remove-director-report-legacy.ts @@ -1,10 +1,17 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; +const legacyDirectorPermissions = [ + ['inspection_reports.revise', 'Cargar versiones corregidas del informe para revisión directiva'], + ['inspection_reports.review', 'Aprobar la versión vigente del informe'], + ['inspection_reports.sign_final', 'Firmar electrónicamente el informe final aprobado'], +] as const; + /** - * Retira definitivamente el circuito legacy Director -> revisión -> aprobación -> firma. + * Retira definitivamente el circuito activo legacy Director -> revisión -> aprobación -> firma. * - * current_revision_number se conserva porque pertenece al versionado útil del INF y no - * al circuito de aprobación retirado. + * current_revision_number e inspection_report_revisions se conservan porque forman parte del + * versionado útil del INF. inspection_report_signatures también se conserva como archivo + * histórico: F4 quita sus endpoints/permisos pero no destruye evidencia documental ya emitida. */ export class F4RemoveDirectorReportLegacy1790000400000 implements MigrationInterface { name = 'F4RemoveDirectorReportLegacy1790000400000'; @@ -17,6 +24,28 @@ export class F4RemoveDirectorReportLegacy1790000400000 implements MigrationInter WHERE recipient_kind = 'DIRECTOR' `); + // Retira permisos huérfanos del circuito anterior para que tampoco aparezcan + // como capacidades administrables en RBAC una vez eliminados sus endpoints. + await queryRunner.query(` + DELETE FROM role_permissions + WHERE permission_id IN ( + SELECT id FROM permissions + WHERE code IN ( + 'inspection_reports.revise', + 'inspection_reports.review', + 'inspection_reports.sign_final' + ) + ) + `); + await queryRunner.query(` + DELETE FROM permissions + WHERE code IN ( + 'inspection_reports.revise', + 'inspection_reports.review', + 'inspection_reports.sign_final' + ) + `); + await queryRunner.query(`ALTER TABLE inspection_reports DROP COLUMN IF EXISTS approved_revision_id`); await queryRunner.query(`ALTER TABLE inspection_reports DROP COLUMN IF EXISTS approved_by`); await queryRunner.query(`ALTER TABLE inspection_reports DROP COLUMN IF EXISTS approved_at`); @@ -26,9 +55,9 @@ export class F4RemoveDirectorReportLegacy1790000400000 implements MigrationInter } public async down(queryRunner: QueryRunner): Promise { - // El rollback reconstruye la forma del esquema, pero no recrea entregas Director - // ni datos de aprobación eliminados. El backup PRE-F4 es la fuente de recuperación - // si alguna vez fuera necesario volver a ese circuito. + // El rollback reconstruye el contrato técnico legacy, pero no recrea entregas Director + // ni valores de aprobación eliminados. El backup PRE-F4 sigue siendo la fuente de + // recuperación de esos datos históricos si alguna vez fuera necesario volver al circuito. await queryRunner.query(` ALTER TABLE inspection_reports ADD COLUMN IF NOT EXISTS review_status varchar(32) NOT NULL DEFAULT 'PENDING_REVIEW' @@ -38,5 +67,47 @@ export class F4RemoveDirectorReportLegacy1790000400000 implements MigrationInter await queryRunner.query(`ALTER TABLE inspection_reports ADD COLUMN IF NOT EXISTS approved_at timestamptz`); await queryRunner.query(`ALTER TABLE inspection_reports ADD COLUMN IF NOT EXISTS review_note varchar(1000)`); await queryRunner.query(`ALTER TABLE inspection_reports ADD COLUMN IF NOT EXISTS signed_at timestamptz`); + + await queryRunner.query(` + DO $$ BEGIN + ALTER TABLE inspection_reports + ADD CONSTRAINT chk_inspection_reports_review_status + CHECK (review_status IN ('PENDING_REVIEW','APPROVED','SIGNED')); + EXCEPTION WHEN duplicate_object THEN NULL; END $$; + `); + await queryRunner.query(` + DO $$ BEGIN + ALTER TABLE inspection_reports + ADD CONSTRAINT fk_inspection_reports_approved_by + FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL; + EXCEPTION WHEN duplicate_object THEN NULL; END $$; + `); + await queryRunner.query(` + DO $$ BEGIN + ALTER TABLE inspection_reports + ADD CONSTRAINT fk_inspection_reports_approved_revision + FOREIGN KEY (approved_revision_id) REFERENCES inspection_report_revisions(id) ON DELETE RESTRICT; + EXCEPTION WHEN duplicate_object THEN NULL; END $$; + `); + + for (const [code, description] of legacyDirectorPermissions) { + await queryRunner.query( + `INSERT INTO permissions (code, description) VALUES ($1, $2) ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description`, + [code, description], + ); + } + await queryRunner.query(` + WITH mapping(role_code, permission_code) AS (VALUES + ('director', 'inspection_reports.revise'), + ('director', 'inspection_reports.review'), + ('director', 'inspection_reports.sign_final') + ) + INSERT INTO role_permissions (role_id, permission_id) + SELECT role.id, permission.id + FROM mapping + JOIN roles role ON role.code = mapping.role_code + JOIN permissions permission ON permission.code = mapping.permission_code + ON CONFLICT (role_id, permission_id) DO NOTHING + `); } }