[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 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-04 00:00:23 -06:00
parent 2cd5a1cce2
commit 2b69098fd4
4 changed files with 358 additions and 2 deletions

View File

@ -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
-- ============================================================================

View File

@ -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
-- ============================================================================

View File

@ -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
-- ============================================================================

View File

@ -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
-- ============================================================================