F3.1: agregar merge auditable del catálogo de Hallazgos

This commit is contained in:
2026-09-07 14:41:34 -03:00
parent 0e14157a44
commit aa26dc2227
@@ -0,0 +1,46 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PhaseF31FindingCatalogMerge1789844900000 implements MigrationInterface {
name = 'PhaseF31FindingCatalogMerge1789844900000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE finding_catalog_item_merges (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source_item_id uuid NOT NULL REFERENCES finding_catalog_items(id) ON DELETE RESTRICT,
canonical_item_id uuid NOT NULL REFERENCES finding_catalog_items(id) ON DELETE RESTRICT,
reason text NOT NULL,
merged_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
merged_by uuid NULL REFERENCES users(id) ON DELETE SET NULL,
request_id varchar(128) NULL,
source_snapshot jsonb NOT NULL,
canonical_snapshot jsonb NOT NULL,
CONSTRAINT chk_finding_catalog_item_merges_distinct CHECK (source_item_id <> canonical_item_id),
CONSTRAINT uq_finding_catalog_item_merges_source UNIQUE (source_item_id)
)
`);
await queryRunner.query(`CREATE INDEX idx_finding_catalog_item_merges_canonical ON finding_catalog_item_merges(canonical_item_id)`);
await queryRunner.query(`CREATE INDEX idx_finding_catalog_item_merges_at ON finding_catalog_item_merges(merged_at)`);
await queryRunner.query(`
CREATE OR REPLACE FUNCTION protect_finding_catalog_item_merges_append_only()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
RAISE EXCEPTION 'finding_catalog_item_merges es append-only; una fusión de catálogo no puede editarse ni borrarse';
END;
$$
`);
await queryRunner.query(`
CREATE TRIGGER trg_finding_catalog_item_merges_append_only
BEFORE UPDATE OR DELETE ON finding_catalog_item_merges
FOR EACH ROW EXECUTE FUNCTION protect_finding_catalog_item_merges_append_only()
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP TRIGGER IF EXISTS trg_finding_catalog_item_merges_append_only ON finding_catalog_item_merges');
await queryRunner.query('DROP FUNCTION IF EXISTS protect_finding_catalog_item_merges_append_only()');
await queryRunner.query('DROP TABLE IF EXISTS finding_catalog_item_merges');
}
}