F1.2 · Seguimiento administrativo por Acta

Plazo único por Acta, respuestas de empresa con PDF, fecha comprometida, estados administrativos, cola y calendario por Acta.
This commit is contained in:
2026-09-05 16:47:46 -03:00
committed by GitHub
parent cd24736df8
commit 4c5742c548
20 changed files with 473 additions and 11 deletions
@@ -0,0 +1,39 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PhaseF12ActAdministration1789581600000 implements MigrationInterface {
name = 'PhaseF12ActAdministration1789581600000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE inspection_act_deadline_events (
id uuid PRIMARY KEY, act_id uuid NOT NULL REFERENCES inspection_acts(id) ON DELETE RESTRICT,
response_due_on date NOT NULL, reason text NOT NULL, created_by uuid NULL REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
)`);
await queryRunner.query(`CREATE INDEX idx_act_deadline_events_act_created ON inspection_act_deadline_events (act_id, created_at DESC)`);
await queryRunner.query(`CREATE INDEX idx_act_deadline_events_due ON inspection_act_deadline_events (response_due_on)`);
await queryRunner.query(`CREATE TABLE inspection_act_company_responses (
id uuid PRIMARY KEY, act_id uuid NOT NULL REFERENCES inspection_acts(id) ON DELETE RESTRICT,
received_on date NOT NULL, details text NULL, committed_correction_on date NULL,
contact_name varchar(200) NULL, contact_email varchar(320) NULL,
original_name varchar(255) NULL, stored_name varchar(100) NULL, mime_type varchar(120) NULL,
size_bytes bigint NULL, sha256 char(64) NULL, created_by uuid NULL REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_act_company_response_file CHECK ((stored_name IS NULL AND original_name IS NULL AND mime_type IS NULL AND size_bytes IS NULL AND sha256 IS NULL) OR (stored_name IS NOT NULL AND original_name IS NOT NULL AND mime_type = 'application/pdf' AND size_bytes > 0 AND sha256 IS NOT NULL))
)`);
await queryRunner.query(`CREATE INDEX idx_act_company_responses_act_received ON inspection_act_company_responses (act_id, received_on DESC, created_at DESC)`);
await queryRunner.query(`CREATE INDEX idx_act_company_responses_commitment ON inspection_act_company_responses (committed_correction_on)`);
await queryRunner.query(`INSERT INTO inspection_act_deadline_events (id, act_id, response_due_on, reason, created_by, created_at)
SELECT gen_random_uuid(), act_id, MIN(correction_due_on), 'Migrado desde plazo histórico coincidente de hallazgos', NULL, CURRENT_TIMESTAMP
FROM inspection_findings WHERE correction_due_on IS NOT NULL GROUP BY act_id HAVING COUNT(DISTINCT correction_due_on) = 1`);
await queryRunner.query(`CREATE OR REPLACE FUNCTION prevent_f12_append_only_mutation() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'F1.2 append-only: update/delete no permitido'; END; $$ LANGUAGE plpgsql`);
await queryRunner.query(`CREATE TRIGGER trg_act_deadline_events_append_only BEFORE UPDATE OR DELETE ON inspection_act_deadline_events FOR EACH ROW EXECUTE FUNCTION prevent_f12_append_only_mutation()`);
await queryRunner.query(`CREATE TRIGGER trg_act_company_responses_append_only BEFORE UPDATE OR DELETE ON inspection_act_company_responses FOR EACH ROW EXECUTE FUNCTION prevent_f12_append_only_mutation()`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TRIGGER IF EXISTS trg_act_company_responses_append_only ON inspection_act_company_responses`);
await queryRunner.query(`DROP TRIGGER IF EXISTS trg_act_deadline_events_append_only ON inspection_act_deadline_events`);
await queryRunner.query(`DROP FUNCTION IF EXISTS prevent_f12_append_only_mutation()`);
await queryRunner.query(`DROP TABLE inspection_act_company_responses`);
await queryRunner.query(`DROP TABLE inspection_act_deadline_events`);
}
}