From 2b69098fd47768ad4b1dddb2e5efecc907147773 Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Wed, 4 Feb 2026 00:00:23 -0600 Subject: [PATCH] [EPIC-001-FK-DEBILES] feat(ddl): Add conditional FK constraints to fix weak references Add 19 conditional FK constraints to 4 schema files: - 06-inventory-ext: 7 FKs (warehouses, products, uom, stock_moves) - 07-purchase-ext: 5 FKs (purchase_orders, partners, products) - 03-hse: 3 FKs (almacen_temporal for EPP) - 10-documents: 4 FKs (documents, users) Features: - Constraints are conditional (only created if target tables exist) - Idempotent (can be re-run without errors) - Added 6 new indexes for FK fields Co-Authored-By: Claude Opus 4.5 --- schemas/03-hse-schema-ddl.sql | 62 +++++++++++- schemas/06-inventory-ext-schema-ddl.sql | 125 ++++++++++++++++++++++++ schemas/07-purchase-ext-schema-ddl.sql | 93 ++++++++++++++++++ schemas/10-documents-schema-ddl.sql | 80 ++++++++++++++- 4 files changed, 358 insertions(+), 2 deletions(-) diff --git a/schemas/03-hse-schema-ddl.sql b/schemas/03-hse-schema-ddl.sql index 4fe65cf..dfaad31 100644 --- a/schemas/03-hse-schema-ddl.sql +++ b/schemas/03-hse-schema-ddl.sql @@ -1264,5 +1264,65 @@ COMMENT ON TABLE hse.permisos_trabajo IS 'Permisos para trabajos de alto riesgo' COMMENT ON TABLE hse.indicadores_valores IS 'Valores calculados de indicadores HSE'; -- ============================================================================ --- FIN DEL DDL +-- FK CONSTRAINTS (Condicionales) +-- NOTA: Los campos almacen_* pueden referenciar hse.almacen_temporal o +-- inventory.warehouses del ERP-Core. +-- ============================================================================ + +-- FK: epp_inventario.almacen_id → hse.almacen_temporal (preferido para HSE) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_epp_inventario_almacen' + ) THEN + ALTER TABLE hse.epp_inventario + ADD CONSTRAINT fk_epp_inventario_almacen + FOREIGN KEY (almacen_id) REFERENCES hse.almacen_temporal(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: epp_inventario.almacen_id → hse.almacen_temporal'; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK epp_inventario.almacen_id no creada: %', SQLERRM; +END $$; + +-- FK: epp_movimientos.almacen_origen_id → hse.almacen_temporal +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_epp_movimientos_almacen_origen' + ) THEN + ALTER TABLE hse.epp_movimientos + ADD CONSTRAINT fk_epp_movimientos_almacen_origen + FOREIGN KEY (almacen_origen_id) REFERENCES hse.almacen_temporal(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: epp_movimientos.almacen_origen_id → hse.almacen_temporal'; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK epp_movimientos.almacen_origen_id no creada: %', SQLERRM; +END $$; + +-- FK: epp_movimientos.almacen_destino_id → hse.almacen_temporal +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_epp_movimientos_almacen_destino' + ) THEN + ALTER TABLE hse.epp_movimientos + ADD CONSTRAINT fk_epp_movimientos_almacen_destino + FOREIGN KEY (almacen_destino_id) REFERENCES hse.almacen_temporal(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: epp_movimientos.almacen_destino_id → hse.almacen_temporal'; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK epp_movimientos.almacen_destino_id no creada: %', SQLERRM; +END $$; + +-- Índices para campos de almacén +CREATE INDEX IF NOT EXISTS idx_epp_inventario_almacen_id ON hse.epp_inventario(almacen_id); +CREATE INDEX IF NOT EXISTS idx_epp_movimientos_almacen_origen ON hse.epp_movimientos(almacen_origen_id); +CREATE INDEX IF NOT EXISTS idx_epp_movimientos_almacen_destino ON hse.epp_movimientos(almacen_destino_id); + +-- ============================================================================ +-- FIN DEL DDL +-- FK constraints condicionales: 3 -- ============================================================================ diff --git a/schemas/06-inventory-ext-schema-ddl.sql b/schemas/06-inventory-ext-schema-ddl.sql index ce643ab..0ebeb4b 100644 --- a/schemas/06-inventory-ext-schema-ddl.sql +++ b/schemas/06-inventory-ext-schema-ddl.sql @@ -207,7 +207,132 @@ COMMENT ON TABLE inventory.requisiciones_obra IS 'Extensión: requisiciones de m COMMENT ON TABLE inventory.requisicion_lineas IS 'Extensión: líneas de requisición de obra'; COMMENT ON TABLE inventory.consumos_obra IS 'Extensión: consumos de materiales por obra/lote'; +-- ============================================================================ +-- FK CONSTRAINTS A TABLAS ERP-CORE (Condicionales) +-- NOTA: Estos constraints solo se crean si las tablas del ERP-Core existen. +-- Si ERP-Core no está instalado, los campos quedan sin FK formal. +-- ============================================================================ + +-- FK: almacenes_proyecto.warehouse_id → inventory.warehouses +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'inventory' AND tablename = 'warehouses') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_almacenes_proyecto_warehouse' + ) THEN + ALTER TABLE inventory.almacenes_proyecto + ADD CONSTRAINT fk_almacenes_proyecto_warehouse + FOREIGN KEY (warehouse_id) REFERENCES inventory.warehouses(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: almacenes_proyecto.warehouse_id → inventory.warehouses'; + END IF; + ELSE + RAISE NOTICE 'AVISO: inventory.warehouses no existe. FK warehouse_id pendiente.'; + END IF; +END $$; + +-- FK: requisicion_lineas.product_id → products.products +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'products' AND tablename = 'products') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_requisicion_lineas_product' + ) THEN + ALTER TABLE inventory.requisicion_lineas + ADD CONSTRAINT fk_requisicion_lineas_product + FOREIGN KEY (product_id) REFERENCES products.products(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: requisicion_lineas.product_id → products.products'; + END IF; + ELSE + RAISE NOTICE 'AVISO: products.products no existe. FK product_id pendiente.'; + END IF; +END $$; + +-- FK: requisicion_lineas.unit_id → core.uom (opcional porque es NULLABLE) +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'core' AND tablename = 'uom') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_requisicion_lineas_unit' + ) THEN + ALTER TABLE inventory.requisicion_lineas + ADD CONSTRAINT fk_requisicion_lineas_unit + FOREIGN KEY (unit_id) REFERENCES core.uom(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: requisicion_lineas.unit_id → core.uom'; + END IF; + ELSE + RAISE NOTICE 'AVISO: core.uom no existe. FK unit_id pendiente.'; + END IF; +END $$; + +-- FK: requisiciones_obra.destination_warehouse_id → inventory.warehouses +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'inventory' AND tablename = 'warehouses') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_requisiciones_obra_dest_warehouse' + ) THEN + ALTER TABLE inventory.requisiciones_obra + ADD CONSTRAINT fk_requisiciones_obra_dest_warehouse + FOREIGN KEY (destination_warehouse_id) REFERENCES inventory.warehouses(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: requisiciones_obra.destination_warehouse_id → inventory.warehouses'; + END IF; + END IF; +END $$; + +-- FK: requisiciones_obra.purchase_order_id → purchases.purchase_orders +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'purchases' AND tablename = 'purchase_orders') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_requisiciones_obra_purchase_order' + ) THEN + ALTER TABLE inventory.requisiciones_obra + ADD CONSTRAINT fk_requisiciones_obra_purchase_order + FOREIGN KEY (purchase_order_id) REFERENCES purchases.purchase_orders(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: requisiciones_obra.purchase_order_id → purchases.purchase_orders'; + END IF; + END IF; +END $$; + +-- FK: consumos_obra.product_id → products.products +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'products' AND tablename = 'products') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_consumos_obra_product' + ) THEN + ALTER TABLE inventory.consumos_obra + ADD CONSTRAINT fk_consumos_obra_product + FOREIGN KEY (product_id) REFERENCES products.products(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: consumos_obra.product_id → products.products'; + END IF; + END IF; +END $$; + +-- FK: consumos_obra.stock_move_id → inventory.stock_moves +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'inventory' AND tablename = 'stock_moves') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_consumos_obra_stock_move' + ) THEN + ALTER TABLE inventory.consumos_obra + ADD CONSTRAINT fk_consumos_obra_stock_move + FOREIGN KEY (stock_move_id) REFERENCES inventory.stock_moves(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: consumos_obra.stock_move_id → inventory.stock_moves'; + END IF; + END IF; +END $$; + -- ============================================================================ -- FIN DE EXTENSIONES INVENTORY -- Total tablas: 4 +-- FK constraints condicionales: 7 -- ============================================================================ diff --git a/schemas/07-purchase-ext-schema-ddl.sql b/schemas/07-purchase-ext-schema-ddl.sql index 03d6a43..3bd7581 100644 --- a/schemas/07-purchase-ext-schema-ddl.sql +++ b/schemas/07-purchase-ext-schema-ddl.sql @@ -221,7 +221,100 @@ COMMENT ON TABLE purchase.comparativo_cotizaciones IS 'Extensión: cuadro compar COMMENT ON TABLE purchase.comparativo_proveedores IS 'Extensión: proveedores participantes en comparativo'; COMMENT ON TABLE purchase.comparativo_productos IS 'Extensión: productos cotizados por proveedor'; +-- ============================================================================ +-- FK CONSTRAINTS A TABLAS ERP-CORE (Condicionales) +-- NOTA: Estos constraints solo se crean si las tablas del ERP-Core existen. +-- ============================================================================ + +-- FK: purchase_order_construction.purchase_order_id → purchases.purchase_orders +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'purchases' AND tablename = 'purchase_orders') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_po_construction_purchase_order' + ) THEN + ALTER TABLE purchase.purchase_order_construction + ADD CONSTRAINT fk_po_construction_purchase_order + FOREIGN KEY (purchase_order_id) REFERENCES purchases.purchase_orders(id) + ON DELETE CASCADE; + RAISE NOTICE 'FK creada: purchase_order_construction.purchase_order_id → purchases.purchase_orders'; + END IF; + ELSE + RAISE NOTICE 'AVISO: purchases.purchase_orders no existe. FK purchase_order_id pendiente.'; + END IF; +END $$; + +-- FK: supplier_construction.supplier_id → partners.partners +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'partners' AND tablename = 'partners') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_supplier_construction_partner' + ) THEN + ALTER TABLE purchase.supplier_construction + ADD CONSTRAINT fk_supplier_construction_partner + FOREIGN KEY (supplier_id) REFERENCES partners.partners(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: supplier_construction.supplier_id → partners.partners'; + END IF; + ELSE + RAISE NOTICE 'AVISO: partners.partners no existe. FK supplier_id pendiente.'; + END IF; +END $$; + +-- FK: comparativo_cotizaciones.winner_supplier_id → partners.partners +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'partners' AND tablename = 'partners') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_comparativo_winner_supplier' + ) THEN + ALTER TABLE purchase.comparativo_cotizaciones + ADD CONSTRAINT fk_comparativo_winner_supplier + FOREIGN KEY (winner_supplier_id) REFERENCES partners.partners(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: comparativo_cotizaciones.winner_supplier_id → partners.partners'; + END IF; + END IF; +END $$; + +-- FK: comparativo_proveedores.supplier_id → partners.partners +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'partners' AND tablename = 'partners') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_comparativo_prov_supplier' + ) THEN + ALTER TABLE purchase.comparativo_proveedores + ADD CONSTRAINT fk_comparativo_prov_supplier + FOREIGN KEY (supplier_id) REFERENCES partners.partners(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: comparativo_proveedores.supplier_id → partners.partners'; + END IF; + END IF; +END $$; + +-- FK: comparativo_productos.product_id → products.products +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'products' AND tablename = 'products') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_comparativo_prod_product' + ) THEN + ALTER TABLE purchase.comparativo_productos + ADD CONSTRAINT fk_comparativo_prod_product + FOREIGN KEY (product_id) REFERENCES products.products(id) + ON DELETE RESTRICT; + RAISE NOTICE 'FK creada: comparativo_productos.product_id → products.products'; + END IF; + END IF; +END $$; + +-- Índice adicional para product_id (faltaba) +CREATE INDEX IF NOT EXISTS idx_comparativo_prod_product_id ON purchase.comparativo_productos(product_id); + -- ============================================================================ -- FIN DE EXTENSIONES PURCHASE -- Total tablas: 5 +-- FK constraints condicionales: 5 -- ============================================================================ diff --git a/schemas/10-documents-schema-ddl.sql b/schemas/10-documents-schema-ddl.sql index 1e75f33..4cf6303 100644 --- a/schemas/10-documents-schema-ddl.sql +++ b/schemas/10-documents-schema-ddl.sql @@ -828,5 +828,83 @@ COMMENT ON TABLE documents.access_logs IS 'Historial de acceso a documentos'; COMMENT ON TABLE documents.document_shares IS 'Links de compartido externo'; -- ============================================================================ --- FIN DEL SCRIPT +-- FK CONSTRAINTS ADICIONALES +-- NOTA: Fortalecer referencias internas y externas del schema documents. +-- ============================================================================ + +-- FK: documents.parent_document_id → documents.documents (auto-referencia) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_documents_parent' + ) THEN + ALTER TABLE documents.documents + ADD CONSTRAINT fk_documents_parent + FOREIGN KEY (parent_document_id) REFERENCES documents.documents(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: documents.parent_document_id → documents.documents'; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK documents.parent_document_id no creada: %', SQLERRM; +END $$; + +-- FK: access_logs.document_id → documents.documents (para mantener logs aunque doc se elimine) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_access_logs_document' + ) THEN + ALTER TABLE documents.access_logs + ADD CONSTRAINT fk_access_logs_document + FOREIGN KEY (document_id) REFERENCES documents.documents(id) + ON DELETE CASCADE; + RAISE NOTICE 'FK creada: access_logs.document_id → documents.documents'; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK access_logs.document_id no creada: %', SQLERRM; +END $$; + +-- FK: access_logs.user_id → auth.users +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_access_logs_user' + ) THEN + ALTER TABLE documents.access_logs + ADD CONSTRAINT fk_access_logs_user + FOREIGN KEY (user_id) REFERENCES auth.users(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: access_logs.user_id → auth.users'; + END IF; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK access_logs.user_id no creada: %', SQLERRM; +END $$; + +-- FK: document_shares.shared_by_id → auth.users +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'fk_document_shares_shared_by' + ) THEN + ALTER TABLE documents.document_shares + ADD CONSTRAINT fk_document_shares_shared_by + FOREIGN KEY (shared_by_id) REFERENCES auth.users(id) + ON DELETE SET NULL; + RAISE NOTICE 'FK creada: document_shares.shared_by_id → auth.users'; + END IF; + END IF; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'FK document_shares.shared_by_id no creada: %', SQLERRM; +END $$; + +-- Índices adicionales para campos sin índice +CREATE INDEX IF NOT EXISTS idx_documents_parent ON documents.documents(parent_document_id); +CREATE INDEX IF NOT EXISTS idx_document_shares_shared_by ON documents.document_shares(shared_by_id); + +-- ============================================================================ +-- FIN DEL SCRIPT +-- FK constraints adicionales: 4 -- ============================================================================