erp-construccion/docs/04-modelado/database-design/schemas/DDL-SPEC-construction.md

1020 lines
29 KiB
Markdown

# DDL SPECIFICATION: Schema Construction
**Version:** 1.0.0
**Fecha:** 2025-12-05
**Schema:** `construction`
**Modulos:** MAI-002 (Proyectos), MAI-003 (Presupuestos), MAI-005 (Control Obra)
---
## Resumen
| Metrica | Valor |
|---------|-------|
| Total Tablas | 25 |
| ENUMs | 8 |
| Funciones | 6 |
| Triggers | 4 |
| Indices | 35+ |
---
## 1. ENUMs
```sql
-- Estados de proyecto
CREATE TYPE construction.project_status AS ENUM (
'planning', -- En planeacion
'in_progress', -- En ejecucion
'paused', -- Pausado
'completed', -- Completado
'cancelled' -- Cancelado
);
-- Estados de vivienda
CREATE TYPE construction.housing_unit_status AS ENUM (
'land', -- Terreno
'foundation', -- Cimentacion
'structure', -- Estructura
'finishing', -- Acabados
'completed', -- Terminada
'delivered' -- Entregada
);
-- Tipos de concepto de presupuesto
CREATE TYPE construction.concept_type AS ENUM (
'material', -- Material
'labor', -- Mano de obra
'equipment', -- Equipo
'subcontract', -- Subcontrato
'indirect' -- Indirecto
);
-- Estados de presupuesto
CREATE TYPE construction.budget_status AS ENUM (
'draft', -- Borrador
'approved', -- Aprobado
'active', -- Activo (en uso)
'closed' -- Cerrado
);
-- Tipos de avance
CREATE TYPE construction.progress_type AS ENUM (
'quantity', -- Por cantidad
'percentage' -- Por porcentaje
);
-- Estados de estimacion
CREATE TYPE construction.estimation_status AS ENUM (
'draft', -- Borrador
'submitted', -- Enviada
'approved', -- Aprobada
'rejected', -- Rechazada
'paid' -- Pagada
);
-- Tipos de incidencia
CREATE TYPE construction.incident_type AS ENUM (
'delay', -- Retraso
'quality', -- Calidad
'safety', -- Seguridad
'material', -- Material
'weather', -- Clima
'other' -- Otro
);
-- Tipos de entrada de bitacora
CREATE TYPE construction.logbook_entry_type AS ENUM (
'daily_report', -- Reporte diario
'incident', -- Incidencia
'instruction', -- Instruccion
'observation', -- Observacion
'visit' -- Visita
);
```
---
## 2. Tablas de Proyectos (MAI-002)
### 2.1 projects (Proyectos)
```sql
CREATE TABLE construction.projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
-- Ubicacion
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
postal_code VARCHAR(10),
location GEOGRAPHY(POINT, 4326), -- PostGIS
-- Fechas
start_date DATE,
end_date DATE,
actual_start_date DATE,
actual_end_date DATE,
-- Estado
status construction.project_status NOT NULL DEFAULT 'planning',
progress_percentage DECIMAL(5,2) DEFAULT 0,
-- Responsables
project_manager_id UUID REFERENCES core.users(id),
-- Financiero
total_budget DECIMAL(18,2),
total_spent DECIMAL(18,2) DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_projects_code UNIQUE (tenant_id, code)
);
-- Indices
CREATE INDEX idx_projects_tenant ON construction.projects(tenant_id);
CREATE INDEX idx_projects_status ON construction.projects(tenant_id, status);
CREATE INDEX idx_projects_location ON construction.projects USING GIST(location);
CREATE INDEX idx_projects_active ON construction.projects(tenant_id) WHERE deleted_at IS NULL;
```
### 2.2 developments (Fraccionamientos/Desarrollos)
```sql
CREATE TABLE construction.developments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
-- Ubicacion
address TEXT,
location GEOGRAPHY(POINT, 4326),
area_m2 DECIMAL(12,2),
-- Totales
total_sections INTEGER DEFAULT 0,
total_units INTEGER DEFAULT 0,
completed_units INTEGER DEFAULT 0,
-- Estado
status construction.project_status NOT NULL DEFAULT 'planning',
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_developments_code UNIQUE (tenant_id, project_id, code)
);
CREATE INDEX idx_developments_project ON construction.developments(project_id);
CREATE INDEX idx_developments_tenant ON construction.developments(tenant_id);
```
### 2.3 sections (Secciones/Manzanas)
```sql
CREATE TABLE construction.sections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
development_id UUID NOT NULL REFERENCES construction.developments(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
-- Geometria
boundary GEOGRAPHY(POLYGON, 4326),
area_m2 DECIMAL(12,2),
-- Totales
total_units INTEGER DEFAULT 0,
completed_units INTEGER DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_sections_code UNIQUE (tenant_id, development_id, code)
);
CREATE INDEX idx_sections_development ON construction.sections(development_id);
```
### 2.4 prototypes (Prototipos de Vivienda)
```sql
CREATE TABLE construction.prototypes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Especificaciones
bedrooms INTEGER,
bathrooms DECIMAL(3,1),
floors INTEGER DEFAULT 1,
construction_area_m2 DECIMAL(10,2),
land_area_m2 DECIMAL(10,2),
-- Precios
base_price DECIMAL(18,2),
construction_cost DECIMAL(18,2),
-- Imagen
image_url TEXT,
blueprint_url TEXT,
-- Estado
is_active BOOLEAN DEFAULT true,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_prototypes_code UNIQUE (tenant_id, code)
);
CREATE INDEX idx_prototypes_tenant ON construction.prototypes(tenant_id);
CREATE INDEX idx_prototypes_active ON construction.prototypes(tenant_id) WHERE is_active = true;
```
### 2.5 housing_units (Viviendas)
```sql
CREATE TABLE construction.housing_units (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
section_id UUID NOT NULL REFERENCES construction.sections(id),
prototype_id UUID REFERENCES construction.prototypes(id),
-- Identificacion
unit_number VARCHAR(20) NOT NULL,
lot_number VARCHAR(20),
block VARCHAR(20),
-- Ubicacion
location GEOGRAPHY(POINT, 4326),
address TEXT,
-- Estado de construccion
status construction.housing_unit_status NOT NULL DEFAULT 'land',
progress_percentage DECIMAL(5,2) DEFAULT 0,
-- Fechas
construction_start_date DATE,
construction_end_date DATE,
delivery_date DATE,
-- Cliente (si vendida)
buyer_name VARCHAR(200),
buyer_contact TEXT,
sale_price DECIMAL(18,2),
sale_date DATE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_housing_units_number UNIQUE (tenant_id, section_id, unit_number)
);
CREATE INDEX idx_housing_units_section ON construction.housing_units(section_id);
CREATE INDEX idx_housing_units_status ON construction.housing_units(tenant_id, status);
CREATE INDEX idx_housing_units_prototype ON construction.housing_units(prototype_id);
CREATE INDEX idx_housing_units_location ON construction.housing_units USING GIST(location);
```
---
## 3. Tablas de Presupuestos (MAI-003)
### 3.1 budgets (Presupuestos)
```sql
CREATE TABLE construction.budgets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
-- Vinculacion
project_id UUID REFERENCES construction.projects(id),
prototype_id UUID REFERENCES construction.prototypes(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
version INTEGER DEFAULT 1,
-- Tipo
is_base BOOLEAN DEFAULT false, -- true = presupuesto base (por prototipo)
-- Totales calculados
total_direct_cost DECIMAL(18,2) DEFAULT 0,
total_indirect_cost DECIMAL(18,2) DEFAULT 0,
total_cost DECIMAL(18,2) DEFAULT 0,
-- Indirectos
indirect_percentage DECIMAL(5,2) DEFAULT 0,
profit_percentage DECIMAL(5,2) DEFAULT 0,
-- Estado
status construction.budget_status NOT NULL DEFAULT 'draft',
approved_at TIMESTAMP,
approved_by UUID REFERENCES core.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_budgets_code UNIQUE (tenant_id, code, version)
);
CREATE INDEX idx_budgets_project ON construction.budgets(project_id);
CREATE INDEX idx_budgets_prototype ON construction.budgets(prototype_id);
CREATE INDEX idx_budgets_status ON construction.budgets(tenant_id, status);
```
### 3.2 budget_partidas (Partidas)
```sql
CREATE TABLE construction.budget_partidas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
budget_id UUID NOT NULL REFERENCES construction.budgets(id),
parent_id UUID REFERENCES construction.budget_partidas(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
-- Orden
sort_order INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
-- Totales (calculados)
total_cost DECIMAL(18,2) DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
CONSTRAINT uq_partidas_code UNIQUE (budget_id, code)
);
CREATE INDEX idx_partidas_budget ON construction.budget_partidas(budget_id);
CREATE INDEX idx_partidas_parent ON construction.budget_partidas(parent_id);
```
### 3.3 budget_concepts (Conceptos)
```sql
CREATE TABLE construction.budget_concepts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
partida_id UUID NOT NULL REFERENCES construction.budget_partidas(id),
-- Identificacion
code VARCHAR(30) NOT NULL,
name VARCHAR(300) NOT NULL,
description TEXT,
-- Unidad y cantidad
unit_id UUID REFERENCES core.units_of_measure(id),
unit_code VARCHAR(10),
quantity DECIMAL(14,4) NOT NULL DEFAULT 0,
-- Precio unitario
unit_price DECIMAL(14,4) NOT NULL DEFAULT 0,
total_price DECIMAL(18,2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
-- Tipo
concept_type construction.concept_type DEFAULT 'material',
-- APU vinculado
has_apu BOOLEAN DEFAULT false,
-- Orden
sort_order INTEGER DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_concepts_partida ON construction.budget_concepts(partida_id);
CREATE INDEX idx_concepts_type ON construction.budget_concepts(concept_type);
```
### 3.4 apu_items (Analisis de Precios Unitarios)
```sql
CREATE TABLE construction.apu_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
concept_id UUID NOT NULL REFERENCES construction.budget_concepts(id),
-- Tipo de insumo
item_type construction.concept_type NOT NULL,
-- Identificacion del insumo
product_id UUID REFERENCES core.products(id),
product_code VARCHAR(50),
product_name VARCHAR(200) NOT NULL,
-- Unidad y cantidad
unit_code VARCHAR(10),
quantity DECIMAL(14,6) NOT NULL DEFAULT 0,
-- Costo
unit_cost DECIMAL(14,4) NOT NULL DEFAULT 0,
total_cost DECIMAL(18,2) GENERATED ALWAYS AS (quantity * unit_cost) STORED,
-- Rendimiento (para mano de obra)
yield_factor DECIMAL(10,4) DEFAULT 1,
-- Orden
sort_order INTEGER DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_apu_concept ON construction.apu_items(concept_id);
CREATE INDEX idx_apu_product ON construction.apu_items(product_id);
CREATE INDEX idx_apu_type ON construction.apu_items(item_type);
```
### 3.5 materials_explosion (Explosion de Materiales)
```sql
CREATE TABLE construction.materials_explosion (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
budget_id UUID NOT NULL REFERENCES construction.budgets(id),
-- Producto
product_id UUID REFERENCES core.products(id),
product_code VARCHAR(50) NOT NULL,
product_name VARCHAR(200) NOT NULL,
-- Unidad
unit_code VARCHAR(10),
-- Cantidades
total_quantity DECIMAL(14,4) NOT NULL DEFAULT 0,
-- Costo
unit_cost DECIMAL(14,4),
total_cost DECIMAL(18,2),
-- Fecha de calculo
calculated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_explosion_budget ON construction.materials_explosion(budget_id);
CREATE INDEX idx_explosion_product ON construction.materials_explosion(product_id);
```
---
## 4. Tablas de Control de Obra (MAI-005)
### 4.1 schedules (Programas de Obra)
```sql
CREATE TABLE construction.schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Identificacion
name VARCHAR(200) NOT NULL,
description TEXT,
version INTEGER DEFAULT 1,
-- Fechas
start_date DATE NOT NULL,
end_date DATE NOT NULL,
-- Estado
is_active BOOLEAN DEFAULT true,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP
);
CREATE INDEX idx_schedules_project ON construction.schedules(project_id);
```
### 4.2 schedule_items (Actividades del Programa)
```sql
CREATE TABLE construction.schedule_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
schedule_id UUID NOT NULL REFERENCES construction.schedules(id),
parent_id UUID REFERENCES construction.schedule_items(id),
concept_id UUID REFERENCES construction.budget_concepts(id),
-- Identificacion
code VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
-- Fechas programadas
planned_start DATE NOT NULL,
planned_end DATE NOT NULL,
duration_days INTEGER,
-- Fechas reales
actual_start DATE,
actual_end DATE,
-- Avance
planned_progress DECIMAL(5,2) DEFAULT 0,
actual_progress DECIMAL(5,2) DEFAULT 0,
-- Dependencias (WBS)
predecessors TEXT[], -- Array de IDs
-- Orden
sort_order INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_schedule_items_schedule ON construction.schedule_items(schedule_id);
CREATE INDEX idx_schedule_items_parent ON construction.schedule_items(parent_id);
CREATE INDEX idx_schedule_items_concept ON construction.schedule_items(concept_id);
```
### 4.3 progress_records (Registros de Avance)
```sql
CREATE TABLE construction.progress_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Vinculacion
housing_unit_id UUID REFERENCES construction.housing_units(id),
concept_id UUID REFERENCES construction.budget_concepts(id),
schedule_item_id UUID REFERENCES construction.schedule_items(id),
-- Fecha del avance
record_date DATE NOT NULL,
-- Tipo de avance
progress_type construction.progress_type NOT NULL DEFAULT 'percentage',
-- Valores
previous_progress DECIMAL(14,4) DEFAULT 0,
current_progress DECIMAL(14,4) NOT NULL,
progress_increment DECIMAL(14,4),
-- Para tipo cantidad
quantity_executed DECIMAL(14,4),
unit_code VARCHAR(10),
-- Notas
notes TEXT,
-- Aprobacion
approved_at TIMESTAMP,
approved_by UUID REFERENCES core.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_progress_project ON construction.progress_records(project_id);
CREATE INDEX idx_progress_unit ON construction.progress_records(housing_unit_id);
CREATE INDEX idx_progress_concept ON construction.progress_records(concept_id);
CREATE INDEX idx_progress_date ON construction.progress_records(record_date);
```
### 4.4 logbook_entries (Bitacora de Obra)
```sql
CREATE TABLE construction.logbook_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Fecha y tipo
entry_date DATE NOT NULL,
entry_type construction.logbook_entry_type NOT NULL,
-- Contenido
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
-- Clima (para reporte diario)
weather VARCHAR(50),
temperature_min DECIMAL(4,1),
temperature_max DECIMAL(4,1),
-- Personal (para reporte diario)
workers_count INTEGER,
-- Vinculacion opcional
housing_unit_id UUID REFERENCES construction.housing_units(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_logbook_project ON construction.logbook_entries(project_id);
CREATE INDEX idx_logbook_date ON construction.logbook_entries(entry_date);
CREATE INDEX idx_logbook_type ON construction.logbook_entries(entry_type);
```
### 4.5 progress_photos (Fotos de Avance)
```sql
CREATE TABLE construction.progress_photos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
-- Vinculacion
progress_record_id UUID REFERENCES construction.progress_records(id),
logbook_entry_id UUID REFERENCES construction.logbook_entries(id),
housing_unit_id UUID REFERENCES construction.housing_units(id),
-- Archivo
file_url TEXT NOT NULL,
thumbnail_url TEXT,
file_name VARCHAR(255),
file_size INTEGER,
mime_type VARCHAR(50),
-- Metadata
caption TEXT,
taken_at TIMESTAMP,
location GEOGRAPHY(POINT, 4326),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_photos_progress ON construction.progress_photos(progress_record_id);
CREATE INDEX idx_photos_logbook ON construction.progress_photos(logbook_entry_id);
CREATE INDEX idx_photos_unit ON construction.progress_photos(housing_unit_id);
```
### 4.6 estimations (Estimaciones)
```sql
CREATE TABLE construction.estimations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Identificacion
estimation_number VARCHAR(20) NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
-- Montos
previous_accumulated DECIMAL(18,2) DEFAULT 0,
current_period DECIMAL(18,2) DEFAULT 0,
total_accumulated DECIMAL(18,2) DEFAULT 0,
-- Deducciones
advance_amortization DECIMAL(18,2) DEFAULT 0,
retentions DECIMAL(18,2) DEFAULT 0,
other_deductions DECIMAL(18,2) DEFAULT 0,
-- Neto
net_amount DECIMAL(18,2) DEFAULT 0,
-- Estado
status construction.estimation_status NOT NULL DEFAULT 'draft',
-- Aprobacion
submitted_at TIMESTAMP,
submitted_by UUID REFERENCES core.users(id),
approved_at TIMESTAMP,
approved_by UUID REFERENCES core.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
deleted_at TIMESTAMP,
CONSTRAINT uq_estimations_number UNIQUE (tenant_id, project_id, estimation_number)
);
CREATE INDEX idx_estimations_project ON construction.estimations(project_id);
CREATE INDEX idx_estimations_status ON construction.estimations(status);
CREATE INDEX idx_estimations_period ON construction.estimations(period_start, period_end);
```
### 4.7 estimation_lines (Lineas de Estimacion)
```sql
CREATE TABLE construction.estimation_lines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
estimation_id UUID NOT NULL REFERENCES construction.estimations(id),
concept_id UUID NOT NULL REFERENCES construction.budget_concepts(id),
-- Cantidades
budget_quantity DECIMAL(14,4),
previous_quantity DECIMAL(14,4) DEFAULT 0,
current_quantity DECIMAL(14,4) NOT NULL,
accumulated_quantity DECIMAL(14,4),
-- Precio
unit_price DECIMAL(14,4) NOT NULL,
-- Montos
previous_amount DECIMAL(18,2) DEFAULT 0,
current_amount DECIMAL(18,2) NOT NULL,
accumulated_amount DECIMAL(18,2),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id)
);
CREATE INDEX idx_estimation_lines_estimation ON construction.estimation_lines(estimation_id);
CREATE INDEX idx_estimation_lines_concept ON construction.estimation_lines(concept_id);
```
### 4.8 incidents (Incidencias)
```sql
CREATE TABLE construction.incidents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES core.tenants(id),
project_id UUID NOT NULL REFERENCES construction.projects(id),
-- Identificacion
incident_number VARCHAR(20) NOT NULL,
incident_type construction.incident_type NOT NULL,
-- Descripcion
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
-- Impacto
severity VARCHAR(20), -- low, medium, high, critical
estimated_delay_days INTEGER,
cost_impact DECIMAL(18,2),
-- Vinculacion
housing_unit_id UUID REFERENCES construction.housing_units(id),
-- Resolucion
resolution TEXT,
resolved_at TIMESTAMP,
resolved_by UUID REFERENCES core.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES core.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES core.users(id),
CONSTRAINT uq_incidents_number UNIQUE (tenant_id, project_id, incident_number)
);
CREATE INDEX idx_incidents_project ON construction.incidents(project_id);
CREATE INDEX idx_incidents_type ON construction.incidents(incident_type);
CREATE INDEX idx_incidents_unresolved ON construction.incidents(project_id) WHERE resolved_at IS NULL;
```
---
## 5. Funciones
### 5.1 Calcular Totales de Presupuesto
```sql
CREATE OR REPLACE FUNCTION construction.calculate_budget_totals(p_budget_id UUID)
RETURNS VOID AS $$
BEGIN
-- Actualizar totales de partidas
UPDATE construction.budget_partidas bp
SET total_cost = (
SELECT COALESCE(SUM(total_price), 0)
FROM construction.budget_concepts bc
WHERE bc.partida_id = bp.id
)
WHERE bp.budget_id = p_budget_id;
-- Actualizar total del presupuesto
UPDATE construction.budgets b
SET
total_direct_cost = (
SELECT COALESCE(SUM(total_cost), 0)
FROM construction.budget_partidas
WHERE budget_id = p_budget_id AND level = 1
),
total_cost = total_direct_cost * (1 + indirect_percentage/100) * (1 + profit_percentage/100),
updated_at = CURRENT_TIMESTAMP
WHERE id = p_budget_id;
END;
$$ LANGUAGE plpgsql;
```
### 5.2 Explosionar Materiales
```sql
CREATE OR REPLACE FUNCTION construction.explode_materials(p_budget_id UUID)
RETURNS INTEGER AS $$
DECLARE
v_count INTEGER := 0;
BEGIN
-- Limpiar explosion anterior
DELETE FROM construction.materials_explosion WHERE budget_id = p_budget_id;
-- Insertar materiales agrupados
INSERT INTO construction.materials_explosion (
tenant_id, budget_id, product_id, product_code, product_name,
unit_code, total_quantity, unit_cost, total_cost, calculated_at
)
SELECT
b.tenant_id,
b.id,
ai.product_id,
ai.product_code,
ai.product_name,
ai.unit_code,
SUM(ai.quantity * bc.quantity) as total_quantity,
AVG(ai.unit_cost) as unit_cost,
SUM(ai.quantity * bc.quantity * ai.unit_cost) as total_cost,
CURRENT_TIMESTAMP
FROM construction.budgets b
JOIN construction.budget_partidas bp ON bp.budget_id = b.id
JOIN construction.budget_concepts bc ON bc.partida_id = bp.id
JOIN construction.apu_items ai ON ai.concept_id = bc.id
WHERE b.id = p_budget_id
AND ai.item_type = 'material'
GROUP BY b.tenant_id, b.id, ai.product_id, ai.product_code, ai.product_name, ai.unit_code;
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
END;
$$ LANGUAGE plpgsql;
```
### 5.3 Actualizar Progreso de Vivienda
```sql
CREATE OR REPLACE FUNCTION construction.update_housing_progress()
RETURNS TRIGGER AS $$
BEGIN
-- Actualizar porcentaje de avance de la vivienda
UPDATE construction.housing_units hu
SET
progress_percentage = (
SELECT COALESCE(AVG(current_progress), 0)
FROM construction.progress_records pr
WHERE pr.housing_unit_id = hu.id
),
updated_at = CURRENT_TIMESTAMP
WHERE id = NEW.housing_unit_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_update_housing_progress
AFTER INSERT OR UPDATE ON construction.progress_records
FOR EACH ROW
WHEN (NEW.housing_unit_id IS NOT NULL)
EXECUTE FUNCTION construction.update_housing_progress();
```
---
## 6. Row Level Security
```sql
-- Habilitar RLS en todas las tablas
ALTER TABLE construction.projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.developments ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.sections ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.prototypes ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.housing_units ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.budgets ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.budget_partidas ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.budget_concepts ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.apu_items ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.schedules ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.schedule_items ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.progress_records ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.logbook_entries ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.progress_photos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.estimations ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.estimation_lines ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.incidents ENABLE ROW LEVEL SECURITY;
-- Crear politicas de aislamiento por tenant
DO $$
DECLARE
t TEXT;
BEGIN
FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'construction'
LOOP
EXECUTE format('
CREATE POLICY tenant_isolation ON construction.%I
USING (tenant_id = current_setting(''app.current_tenant_id'')::uuid)
', t);
END LOOP;
END $$;
```
---
## 7. Seeds Iniciales
```sql
-- Prototipos de ejemplo
INSERT INTO construction.prototypes (tenant_id, code, name, bedrooms, bathrooms, construction_area_m2)
VALUES
('{{TENANT_ID}}', 'CASA-2R', 'Casa 2 Recamaras', 2, 1, 55.00),
('{{TENANT_ID}}', 'CASA-3R', 'Casa 3 Recamaras', 3, 1.5, 75.00),
('{{TENANT_ID}}', 'CASA-4R', 'Casa 4 Recamaras', 4, 2.5, 120.00);
```
---
## Referencias
- [MAI-002: Proyectos y Estructura](../../02-definicion-modulos/MAI-002-proyectos-estructura/)
- [MAI-003: Presupuestos y Costos](../../02-definicion-modulos/MAI-003-presupuestos-costos/)
- [MAI-005: Control de Obra](../../02-definicion-modulos/MAI-005-control-obra-avances/)
- [ADR-007: Database Design](../../97-adr/ADR-007-database-design.md)
---
*Ultima actualizacion: 2025-12-05*