# 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*
