- ESTANDAR-CAMPOS-UBICACION.md: Standards for location fields (GEOMETRY vs DECIMAL) - POLITICA-CASCADAS-FK.md: Foreign key cascade policy guide Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.7 KiB
Política de Cascadas en Foreign Keys - ERP Construcción
Versión: 1.0.0 Fecha: 2026-02-03 Subtarea: ST-P3-003
Resumen
Este documento define la política para configurar acciones de CASCADE, SET NULL, RESTRICT y NO ACTION en foreign keys de la base de datos de ERP Construcción.
Opciones Disponibles
| Acción | ON DELETE | ON UPDATE | Descripción |
|---|---|---|---|
| CASCADE | ✅ | ✅ | Propaga la acción a registros relacionados |
| SET NULL | ✅ | ✅ | Establece NULL en la columna FK |
| SET DEFAULT | ✅ | ✅ | Establece el valor DEFAULT |
| RESTRICT | ✅ | ✅ | Bloquea si hay referencias (antes del commit) |
| NO ACTION | ✅ | ✅ | Bloquea si hay referencias (al commit) |
Estado Actual
Patrón dominante: ON DELETE CASCADE
El DDL actual usa CASCADE en todas las foreign keys. Esto es apropiado para:
- Relaciones padre-hijo donde el hijo no tiene sentido sin el padre
- Multi-tenancy (tenant_id → auth.tenants)
- Tablas de detalle (items de una orden, líneas de una póliza)
Guía de Decisión
Usar ON DELETE CASCADE cuando:
-
Relación de composición - El hijo no existe sin el padre
-- Items de una orden de compra order_id UUID NOT NULL REFERENCES purchase.orders(id) ON DELETE CASCADE -
Multi-tenancy - Todos los datos del tenant se eliminan juntos
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE -
Tablas de detalle/líneas
estimacion_conceptos→estimacionesaccounting_entry_lines→accounting_entriesapproval_steps→approval_instances
-
Historial/Logs asociados
asset_locations→assetsdocument_versions→documents
Usar ON DELETE SET NULL cuando:
-
Relación opcional - El registro puede existir sin la referencia
-- Empleado asignado puede irse pero el ticket persiste assigned_to UUID REFERENCES hr.employees(id) ON DELETE SET NULL -
Referencias a usuarios - El usuario se va pero el registro queda
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL updated_by UUID REFERENCES auth.users(id) ON DELETE SET NULL -
Referencias a catálogos opcionales
category_id UUID REFERENCES catalogs.categories(id) ON DELETE SET NULL
Usar ON DELETE RESTRICT cuando:
-
Proteger datos críticos - No permitir borrar si hay referencias
-- No borrar un proyecto si tiene estimaciones proyecto_id UUID NOT NULL REFERENCES construction.proyectos(id) ON DELETE RESTRICT -
Entidades maestras con dependencias de negocio
-- No borrar proveedor si tiene órdenes de compra supplier_id UUID NOT NULL REFERENCES partners.suppliers(id) ON DELETE RESTRICT -
Datos contables/legales
-- No borrar cuenta contable si tiene movimientos account_id UUID NOT NULL REFERENCES finance.chart_of_accounts(id) ON DELETE RESTRICT
Usar NO ACTION cuando:
Similar a RESTRICT, pero permite verificaciones diferidas dentro de una transacción. Usar cuando se necesita reordenar operaciones dentro de una transacción.
ON UPDATE
Recomendación General: NO especificar
Las primary keys son UUIDs inmutables. No hay necesidad de propagar cambios.
-- UUID nunca cambia, no necesita ON UPDATE
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
Si se usa código natural como PK (no recomendado):
-- Solo si el código puede cambiar
codigo VARCHAR(20) PRIMARY KEY,
-- FK con ON UPDATE CASCADE
producto_codigo VARCHAR(20) REFERENCES productos(codigo) ON UPDATE CASCADE
Tabla de Referencia Rápida
| Tipo de Relación | ON DELETE | Ejemplo |
|---|---|---|
| Tenant → Datos | CASCADE | tenant_id → auth.tenants |
| Padre → Hijos (composición) | CASCADE | order → order_items |
| Referencia opcional | SET NULL | assigned_to → employees |
| Auditoría (created_by) | SET NULL | created_by → users |
| Entidad maestra protegida | RESTRICT | proyecto → estimaciones |
| Catálogo con dependencias | RESTRICT | account → movements |
Aplicación en Schemas Existentes
construction
| FK | Actual | Recomendado | Razón |
|---|---|---|---|
| fraccionamientos.tenant_id | CASCADE | CASCADE ✅ | Multi-tenant |
| etapas.fraccionamiento_id | CASCADE | CASCADE ✅ | Composición |
| avances.responsable_id | CASCADE | SET NULL | Usuario puede irse |
estimates
| FK | Actual | Recomendado | Razón |
|---|---|---|---|
| estimaciones.proyecto_id | CASCADE | RESTRICT | Proteger datos |
| conceptos_estimacion.estimacion_id | CASCADE | CASCADE ✅ | Composición |
finance
| FK | Actual | Recomendado | Razón |
|---|---|---|---|
| accounting_entries.tenant_id | CASCADE | CASCADE ✅ | Multi-tenant |
| entry_lines.entry_id | CASCADE | CASCADE ✅ | Composición |
| entry_lines.account_id | - | RESTRICT | No borrar cuenta con movimientos |
Migración Sugerida
Para FK que deberían usar SET NULL o RESTRICT en lugar de CASCADE:
-- Ejemplo: Cambiar CASCADE a RESTRICT
ALTER TABLE estimates.estimaciones
DROP CONSTRAINT estimaciones_proyecto_id_fkey;
ALTER TABLE estimates.estimaciones
ADD CONSTRAINT estimaciones_proyecto_id_fkey
FOREIGN KEY (proyecto_id) REFERENCES construction.proyectos(id)
ON DELETE RESTRICT;
Nota: Evaluar impacto en lógica de aplicación antes de cambiar.
Referencias
- PostgreSQL Foreign Key Constraints
- ESTANDAR-DATABASE-PROFESIONAL.md
Documentado: 2026-02-03 - ST-P3-003