From bdf28159085c449cdf17c49cefe5e6159cc97cba Mon Sep 17 00:00:00 2001 From: enlineawork Date: Wed, 9 Sep 2026 08:03:48 -0300 Subject: [PATCH] =?UTF-8?q?F5.1:=20retirar=20migraci=C3=B3n=20con=20timest?= =?UTF-8?q?amp=20duplicado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...90087300000-f5-1-clean-manual-inventory.ts | 154 ------------------ 1 file changed, 154 deletions(-) delete mode 100644 api-v3/src/database/migrations/1790087300000-f5-1-clean-manual-inventory.ts diff --git a/api-v3/src/database/migrations/1790087300000-f5-1-clean-manual-inventory.ts b/api-v3/src/database/migrations/1790087300000-f5-1-clean-manual-inventory.ts deleted file mode 100644 index 3b2689b..0000000 --- a/api-v3/src/database/migrations/1790087300000-f5-1-clean-manual-inventory.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class F51CleanManualInventory1790087300000 implements MigrationInterface { - name = 'F51CleanManualInventory1790087300000'; - - public async up(queryRunner: QueryRunner): Promise { - // F5.1 is an intentional clean-start cut. The deployment process creates a - // full database backup before migrations, so old domain data is recovered - // from that backup rather than by pretending a destructive migration can - // reconstruct historical rows. - await queryRunner.query('DROP TRIGGER IF EXISTS trg_f5_canonical_asset_hierarchy ON assets'); - - // Remove all operational/domain instances and every table that depends on - // them (visits, acts, findings, reports, versions, media, relations, etc.). - // Users/roles/permissions and technical configuration are deliberately not - // part of this TRUNCATE. - await queryRunner.query('TRUNCATE TABLE assets CASCADE'); - - // Imported territorial/source material must not silently repopulate or - // influence the new manually curated structure. - await queryRunner.query('TRUNCATE TABLE administrative_departments CASCADE'); - await queryRunner.query('TRUNCATE TABLE source_documents CASCADE'); - - // Start the classification ↔ finding applicability review from zero while - // preserving both master catalogs themselves. - await queryRunner.query('TRUNCATE TABLE finding_catalog_item_inventory_families'); - - // Explicitly clear audit history, including authentication/admin events - // accumulated during development. New events continue to be recorded after - // this migration. - await queryRunner.query('TRUNCATE TABLE audit_events'); - - // Import/reconciliation tables can contain rows not connected to a current - // Asset. Clear every asset_import_* data table without coupling this cut to - // one historical import implementation. - await queryRunner.query(` - DO $$ - DECLARE table_name text; - BEGIN - FOR table_name IN - SELECT tablename - FROM pg_tables - WHERE schemaname = current_schema() - AND tablename LIKE 'asset_import_%' - LOOP - EXECUTE format('TRUNCATE TABLE %I CASCADE', table_name); - END LOOP; - END $$; - `); - - // Departamento becomes the real root of the physical Inventory tree. - await queryRunner.query(` - INSERT INTO asset_types(code,name,description,can_be_root,is_active,operational_role) - SELECT 'departamento','Departamento','Departamento administrativo que contiene Áreas.',true,true,'GENERIC' - WHERE NOT EXISTS ( - SELECT 1 FROM asset_types WHERE lower(code)='departamento' - ) - `); - await queryRunner.query(` - UPDATE asset_types - SET name='Departamento', - description='Departamento administrativo que contiene Áreas.', - can_be_root=true, - is_active=true, - operational_role='GENERIC', - updated_at=CURRENT_TIMESTAMP - WHERE lower(code)='departamento' - `); - await queryRunner.query(` - UPDATE asset_types - SET can_be_root=false,updated_at=CURRENT_TIMESTAMP - WHERE lower(code)='area' - `); - - // Area has exactly one canonical structural parent kind: Departamento. - await queryRunner.query(` - DELETE FROM asset_type_parent_rules rule - USING asset_types child - WHERE rule.child_type_id=child.id AND lower(child.code)='area' - `); - await queryRunner.query(` - INSERT INTO asset_type_parent_rules(child_type_id,parent_type_id) - SELECT child.id,parent.id - FROM asset_types child CROSS JOIN asset_types parent - WHERE lower(child.code)='area' AND lower(parent.code)='departamento' - ON CONFLICT (child_type_id,parent_type_id) DO NOTHING - `); - - // Database-level guard: UI/API bugs cannot create an invalid physical tree. - await queryRunner.query(` - CREATE OR REPLACE FUNCTION enforce_f5_canonical_asset_hierarchy() - RETURNS trigger LANGUAGE plpgsql AS $$ - DECLARE child_code text; parent_code text; - BEGIN - SELECT lower(code) INTO child_code FROM asset_types WHERE id=NEW.asset_type_id; - - IF child_code IN ('empresa','organizacion','departamento') THEN - IF NEW.parent_id IS NOT NULL THEN - RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='Empresa y Departamento son maestros raíz independientes'; - END IF; - RETURN NEW; - END IF; - - IF child_code NOT IN ('area','yacimiento','instalacion','subinstalacion') THEN - RETURN NEW; - END IF; - - IF NEW.parent_id IS NULL THEN - RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='La estructura requiere Departamento → Área → Yacimiento → Instalación → Subinstalación'; - END IF; - - SELECT lower(type.code) INTO parent_code - FROM assets parent - JOIN asset_types type ON type.id=parent.asset_type_id - WHERE parent.id=NEW.parent_id; - - IF (child_code='area' AND parent_code<>'departamento') - OR (child_code='yacimiento' AND parent_code<>'area') - OR (child_code='instalacion' AND parent_code<>'yacimiento') - OR (child_code='subinstalacion' AND parent_code<>'instalacion') THEN - RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='Jerarquía inválida: Departamento → Área → Yacimiento → Instalación → Subinstalación'; - END IF; - RETURN NEW; - END $$; - `); - await queryRunner.query(` - CREATE TRIGGER trg_f5_canonical_asset_hierarchy - BEFORE INSERT OR UPDATE OF asset_type_id,parent_id ON assets - FOR EACH ROW EXECUTE FUNCTION enforce_f5_canonical_asset_hierarchy() - `); - - const rows = (await queryRunner.query(` - SELECT - (SELECT COUNT(*)::integer FROM assets) AS assets, - (SELECT COUNT(*)::integer FROM audit_events) AS audits, - (SELECT COUNT(*)::integer FROM finding_catalog_item_inventory_families) AS applicability, - (SELECT COUNT(*)::integer FROM asset_types WHERE lower(code)='departamento' AND can_be_root=true AND is_active=true) AS departments, - (SELECT COUNT(*)::integer - FROM asset_type_parent_rules rule - JOIN asset_types child ON child.id=rule.child_type_id - JOIN asset_types parent ON parent.id=rule.parent_type_id - WHERE lower(child.code)='area' AND lower(parent.code)='departamento') AS area_rules - `)) as Array<{ assets: number; audits: number; applicability: number; departments: number; area_rules: number }>; - const check = rows[0]; - if (!check || Number(check.assets) !== 0 || Number(check.audits) !== 0 || Number(check.applicability) !== 0 - || Number(check.departments) !== 1 || Number(check.area_rules) !== 1) { - throw new Error(`F5.1 clean-start verification failed: ${JSON.stringify(check ?? {})}`); - } - } - - public async down(): Promise { - throw new Error('F5.1 is an intentional destructive clean-start migration. Restore the pre-deploy database backup to recover previous data.'); - } -}