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