erp-construccion/docs/04-modelado/database-design/schemas/construction-schema-ddl.sql

909 lines
35 KiB
SQL

-- =====================================================
-- SCHEMA: construction
-- PROPOSITO: Obras, proyectos, lotes, prototipos, avances
-- MODULOS: MAI-002, MAI-003, MAI-005, MAI-009, MAI-012
-- FECHA: 2025-11-24
-- ESPECIFICO: 100% construccion
-- =====================================================
-- Crear schema
CREATE SCHEMA IF NOT EXISTS construction;
-- =====================================================
-- TYPES (ENUMs)
-- =====================================================
CREATE TYPE construction.project_status AS ENUM (
'draft', -- Borrador
'planning', -- En planeacion
'in_progress', -- En ejecucion
'paused', -- Pausado
'completed', -- Completado
'cancelled' -- Cancelado
);
CREATE TYPE construction.lot_status AS ENUM (
'available', -- Disponible para venta
'reserved', -- Reservado
'sold', -- Vendido
'under_construction', -- En construccion
'delivered', -- Entregado
'warranty' -- En periodo de garantia
);
CREATE TYPE construction.prototype_type AS ENUM (
'horizontal', -- Casa horizontal
'vertical', -- Departamento en torre
'commercial', -- Local comercial
'mixed' -- Uso mixto
);
CREATE TYPE construction.advance_status AS ENUM (
'pending', -- Pendiente captura
'captured', -- Capturado
'reviewed', -- Revisado
'approved', -- Aprobado
'rejected' -- Rechazado
);
CREATE TYPE construction.quality_status AS ENUM (
'pending', -- Pendiente revision
'in_review', -- En revision
'approved', -- Aprobado
'rejected', -- Rechazado
'rework' -- Requiere retrabajo
);
CREATE TYPE construction.contract_type AS ENUM (
'fixed_price', -- Precio fijo
'unit_price', -- Precio unitario
'cost_plus', -- Costo + porcentaje
'mixed' -- Mixto
);
CREATE TYPE construction.contract_status AS ENUM (
'draft', -- Borrador
'pending_approval', -- Pendiente aprobacion
'active', -- Activo
'suspended', -- Suspendido
'terminated', -- Terminado
'closed' -- Cerrado
);
-- =====================================================
-- TABLES - ESTRUCTURA DE PROYECTO
-- =====================================================
-- Tabla: fraccionamientos (desarrollo inmobiliario)
CREATE TABLE construction.fraccionamientos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
code VARCHAR(20) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
zip_code VARCHAR(10),
location GEOMETRY(POINT, 4326), -- PostGIS
total_area_m2 DECIMAL(12,2),
buildable_area_m2 DECIMAL(12,2),
total_lots INTEGER DEFAULT 0,
status construction.project_status NOT NULL DEFAULT 'draft',
start_date DATE,
expected_end_date DATE,
actual_end_date DATE,
metadata JSONB DEFAULT '{}',
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_fraccionamientos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: etapas (fases del fraccionamiento)
CREATE TABLE construction.etapas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id) ON DELETE CASCADE,
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
sequence INTEGER NOT NULL DEFAULT 1,
total_lots INTEGER DEFAULT 0,
status construction.project_status NOT NULL DEFAULT 'draft',
start_date DATE,
expected_end_date DATE,
actual_end_date DATE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_etapas_code_fracc UNIQUE (fraccionamiento_id, code)
);
-- Tabla: manzanas (agrupacion de lotes)
CREATE TABLE construction.manzanas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
etapa_id UUID NOT NULL REFERENCES construction.etapas(id) ON DELETE CASCADE,
code VARCHAR(20) NOT NULL,
name VARCHAR(100),
total_lots INTEGER DEFAULT 0,
polygon GEOMETRY(POLYGON, 4326), -- PostGIS
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_manzanas_code_etapa UNIQUE (etapa_id, code)
);
-- Tabla: lotes (unidades vendibles)
CREATE TABLE construction.lotes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
manzana_id UUID NOT NULL REFERENCES construction.manzanas(id) ON DELETE CASCADE,
prototipo_id UUID REFERENCES construction.prototipos(id),
code VARCHAR(30) NOT NULL,
official_number VARCHAR(50), -- Numero oficial catastral
area_m2 DECIMAL(10,2),
front_m DECIMAL(8,2),
depth_m DECIMAL(8,2),
status construction.lot_status NOT NULL DEFAULT 'available',
location GEOMETRY(POINT, 4326),
polygon GEOMETRY(POLYGON, 4326),
price_base DECIMAL(14,2),
price_final DECIMAL(14,2),
buyer_id UUID, -- FK a cliente/derechohabiente
sale_date DATE,
delivery_date DATE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_lotes_code_manzana UNIQUE (manzana_id, code)
);
-- =====================================================
-- TABLES - ESTRUCTURA VERTICAL (TORRES)
-- =====================================================
-- Tabla: torres (edificios verticales)
CREATE TABLE construction.torres (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
etapa_id UUID NOT NULL REFERENCES construction.etapas(id) ON DELETE CASCADE,
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
total_floors INTEGER NOT NULL DEFAULT 1,
total_units INTEGER DEFAULT 0,
status construction.project_status NOT NULL DEFAULT 'draft',
location GEOMETRY(POINT, 4326),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_torres_code_etapa UNIQUE (etapa_id, code)
);
-- Tabla: niveles (pisos de torre)
CREATE TABLE construction.niveles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
torre_id UUID NOT NULL REFERENCES construction.torres(id) ON DELETE CASCADE,
floor_number INTEGER NOT NULL,
name VARCHAR(50),
total_units INTEGER DEFAULT 0,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_niveles_floor_torre UNIQUE (torre_id, floor_number)
);
-- Tabla: departamentos (unidades en torre)
CREATE TABLE construction.departamentos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
nivel_id UUID NOT NULL REFERENCES construction.niveles(id) ON DELETE CASCADE,
prototipo_id UUID REFERENCES construction.prototipos(id),
code VARCHAR(30) NOT NULL,
unit_number VARCHAR(20) NOT NULL,
area_m2 DECIMAL(10,2),
status construction.lot_status NOT NULL DEFAULT 'available',
price_base DECIMAL(14,2),
price_final DECIMAL(14,2),
buyer_id UUID,
sale_date DATE,
delivery_date DATE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_departamentos_code_nivel UNIQUE (nivel_id, code)
);
-- =====================================================
-- TABLES - PROTOTIPOS Y PRESUPUESTOS
-- =====================================================
-- Tabla: prototipos (tipos de vivienda)
CREATE TABLE construction.prototipos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
type construction.prototype_type NOT NULL DEFAULT 'horizontal',
area_construction_m2 DECIMAL(10,2),
area_terrain_m2 DECIMAL(10,2),
bedrooms INTEGER DEFAULT 0,
bathrooms DECIMAL(3,1) DEFAULT 0,
parking_spaces INTEGER DEFAULT 0,
floors INTEGER DEFAULT 1,
base_price DECIMAL(14,2),
blueprint_url VARCHAR(500),
render_url VARCHAR(500),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
metadata JSONB DEFAULT '{}', -- Especificaciones tecnicas
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_prototipos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: conceptos (catalogo de conceptos de obra)
CREATE TABLE construction.conceptos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
parent_id UUID REFERENCES construction.conceptos(id),
code VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
unit_id UUID, -- FK a core.uom
unit_price DECIMAL(12,4),
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
level INTEGER NOT NULL DEFAULT 0, -- Nivel en jerarquia
path VARCHAR(500), -- Path materializado para queries
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_conceptos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: presupuestos (presupuesto por prototipo/obra)
CREATE TABLE construction.presupuestos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
fraccionamiento_id UUID REFERENCES construction.fraccionamientos(id),
prototipo_id UUID REFERENCES construction.prototipos(id),
code VARCHAR(30) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
version INTEGER NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
total_amount DECIMAL(16,2) DEFAULT 0,
currency_id UUID, -- FK a core.currencies
approved_at TIMESTAMP,
approved_by UUID REFERENCES auth.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_presupuestos_code_version UNIQUE (tenant_id, code, version)
);
-- Tabla: presupuesto_partidas (lineas del presupuesto)
CREATE TABLE construction.presupuesto_partidas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
presupuesto_id UUID NOT NULL REFERENCES construction.presupuestos(id) ON DELETE CASCADE,
concepto_id UUID NOT NULL REFERENCES construction.conceptos(id),
sequence INTEGER NOT NULL DEFAULT 0,
quantity DECIMAL(12,4) NOT NULL DEFAULT 0,
unit_price DECIMAL(12,4) NOT NULL DEFAULT 0,
total_amount DECIMAL(14,2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_partidas_presupuesto_concepto UNIQUE (presupuesto_id, concepto_id)
);
-- =====================================================
-- TABLES - AVANCES Y CONTROL DE OBRA
-- =====================================================
-- Tabla: programa_obra (programa maestro)
CREATE TABLE construction.programa_obra (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
code VARCHAR(30) NOT NULL,
name VARCHAR(255) NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_programa_code_version UNIQUE (tenant_id, code, version)
);
-- Tabla: programa_actividades (actividades del programa)
CREATE TABLE construction.programa_actividades (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
programa_id UUID NOT NULL REFERENCES construction.programa_obra(id) ON DELETE CASCADE,
concepto_id UUID REFERENCES construction.conceptos(id),
parent_id UUID REFERENCES construction.programa_actividades(id),
name VARCHAR(255) NOT NULL,
sequence INTEGER NOT NULL DEFAULT 0,
planned_start DATE,
planned_end DATE,
planned_quantity DECIMAL(12,4) DEFAULT 0,
planned_weight DECIMAL(8,4) DEFAULT 0, -- Peso ponderado %
wbs_code VARCHAR(50), -- Work Breakdown Structure
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: avances_obra (captura de avances)
CREATE TABLE construction.avances_obra (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
lote_id UUID REFERENCES construction.lotes(id),
departamento_id UUID REFERENCES construction.departamentos(id),
concepto_id UUID NOT NULL REFERENCES construction.conceptos(id),
capture_date DATE NOT NULL,
quantity_executed DECIMAL(12,4) NOT NULL DEFAULT 0,
percentage_executed DECIMAL(5,2) DEFAULT 0,
status construction.advance_status NOT NULL DEFAULT 'pending',
notes TEXT,
captured_by UUID NOT NULL REFERENCES auth.users(id),
reviewed_by UUID REFERENCES auth.users(id),
reviewed_at TIMESTAMP,
approved_by UUID REFERENCES auth.users(id),
approved_at TIMESTAMP,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT chk_avances_lote_or_depto CHECK (
(lote_id IS NOT NULL AND departamento_id IS NULL) OR
(lote_id IS NULL AND departamento_id IS NOT NULL)
)
);
-- Tabla: fotos_avance (evidencia fotografica)
CREATE TABLE construction.fotos_avance (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
avance_id UUID NOT NULL REFERENCES construction.avances_obra(id) ON DELETE CASCADE,
file_url VARCHAR(500) NOT NULL,
file_name VARCHAR(255),
file_size INTEGER,
mime_type VARCHAR(50),
description TEXT,
location GEOMETRY(POINT, 4326), -- GPS donde se tomo
captured_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: bitacora_obra (registro de bitacora)
CREATE TABLE construction.bitacora_obra (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
entry_date DATE NOT NULL,
entry_number INTEGER NOT NULL,
weather VARCHAR(50),
temperature_max DECIMAL(4,1),
temperature_min DECIMAL(4,1),
workers_count INTEGER DEFAULT 0,
description TEXT NOT NULL,
observations TEXT,
incidents TEXT,
registered_by UUID NOT NULL REFERENCES auth.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_bitacora_fracc_number UNIQUE (fraccionamiento_id, entry_number)
);
-- =====================================================
-- TABLES - CALIDAD Y POSTVENTA (MAI-009)
-- =====================================================
-- Tabla: checklists (plantillas de verificacion)
CREATE TABLE construction.checklists (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
code VARCHAR(30) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
prototipo_id UUID REFERENCES construction.prototipos(id),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_checklists_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: checklist_items (items del checklist)
CREATE TABLE construction.checklist_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
checklist_id UUID NOT NULL REFERENCES construction.checklists(id) ON DELETE CASCADE,
sequence INTEGER NOT NULL DEFAULT 0,
name VARCHAR(255) NOT NULL,
description TEXT,
is_required BOOLEAN NOT NULL DEFAULT TRUE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: inspecciones (inspecciones de calidad)
CREATE TABLE construction.inspecciones (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
checklist_id UUID NOT NULL REFERENCES construction.checklists(id),
lote_id UUID REFERENCES construction.lotes(id),
departamento_id UUID REFERENCES construction.departamentos(id),
inspection_date DATE NOT NULL,
status construction.quality_status NOT NULL DEFAULT 'pending',
inspector_id UUID NOT NULL REFERENCES auth.users(id),
notes TEXT,
approved_by UUID REFERENCES auth.users(id),
approved_at TIMESTAMP,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: inspeccion_resultados (resultados por item)
CREATE TABLE construction.inspeccion_resultados (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
inspeccion_id UUID NOT NULL REFERENCES construction.inspecciones(id) ON DELETE CASCADE,
checklist_item_id UUID NOT NULL REFERENCES construction.checklist_items(id),
is_passed BOOLEAN,
notes TEXT,
photo_url VARCHAR(500),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id)
);
-- Tabla: tickets_postventa (tickets de garantia)
CREATE TABLE construction.tickets_postventa (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
lote_id UUID REFERENCES construction.lotes(id),
departamento_id UUID REFERENCES construction.departamentos(id),
ticket_number VARCHAR(30) NOT NULL,
reported_date DATE NOT NULL,
category VARCHAR(50), -- plomeria, electricidad, estructura, etc.
description TEXT NOT NULL,
priority VARCHAR(20) DEFAULT 'medium', -- low, medium, high, urgent
status VARCHAR(20) NOT NULL DEFAULT 'open', -- open, in_progress, resolved, closed
assigned_to UUID REFERENCES auth.users(id),
resolution TEXT,
resolved_at TIMESTAMP,
resolved_by UUID REFERENCES auth.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_tickets_number_tenant UNIQUE (tenant_id, ticket_number)
);
-- =====================================================
-- TABLES - CONTRATOS Y SUBCONTRATOS (MAI-012)
-- =====================================================
-- Tabla: subcontratistas
CREATE TABLE construction.subcontratistas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
partner_id UUID, -- FK a core.partners
code VARCHAR(20) NOT NULL,
name VARCHAR(255) NOT NULL,
legal_name VARCHAR(255),
tax_id VARCHAR(20),
specialty VARCHAR(100), -- Especialidad: alba~nileria, electricidad, etc.
contact_name VARCHAR(100),
contact_phone VARCHAR(20),
contact_email VARCHAR(100),
address TEXT,
rating DECIMAL(3,2), -- Calificacion 0-5
is_active BOOLEAN NOT NULL DEFAULT TRUE,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_subcontratistas_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: contratos (contratos con subcontratistas)
CREATE TABLE construction.contratos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
subcontratista_id UUID NOT NULL REFERENCES construction.subcontratistas(id),
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
contract_number VARCHAR(30) NOT NULL,
contract_type construction.contract_type NOT NULL DEFAULT 'unit_price',
name VARCHAR(255) NOT NULL,
description TEXT,
start_date DATE NOT NULL,
end_date DATE,
total_amount DECIMAL(16,2),
advance_percentage DECIMAL(5,2) DEFAULT 0, -- % anticipo
retention_percentage DECIMAL(5,2) DEFAULT 5, -- % retencion
status construction.contract_status NOT NULL DEFAULT 'draft',
signed_at TIMESTAMP,
signed_by UUID REFERENCES auth.users(id),
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_contratos_number_tenant UNIQUE (tenant_id, contract_number)
);
-- Tabla: contrato_partidas (lineas del contrato)
CREATE TABLE construction.contrato_partidas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
contrato_id UUID NOT NULL REFERENCES construction.contratos(id) ON DELETE CASCADE,
concepto_id UUID NOT NULL REFERENCES construction.conceptos(id),
quantity DECIMAL(12,4) NOT NULL DEFAULT 0,
unit_price DECIMAL(12,4) NOT NULL DEFAULT 0,
total_amount DECIMAL(14,2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
-- Auditoria
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMP,
deleted_by UUID REFERENCES auth.users(id)
);
-- =====================================================
-- INDICES
-- =====================================================
-- Fraccionamientos
CREATE INDEX idx_fraccionamientos_tenant_id ON construction.fraccionamientos(tenant_id);
CREATE INDEX idx_fraccionamientos_status ON construction.fraccionamientos(status);
CREATE INDEX idx_fraccionamientos_code ON construction.fraccionamientos(code);
CREATE INDEX idx_fraccionamientos_active ON construction.fraccionamientos(tenant_id) WHERE deleted_at IS NULL;
-- Etapas
CREATE INDEX idx_etapas_tenant_id ON construction.etapas(tenant_id);
CREATE INDEX idx_etapas_fraccionamiento_id ON construction.etapas(fraccionamiento_id);
CREATE INDEX idx_etapas_status ON construction.etapas(status);
-- Manzanas
CREATE INDEX idx_manzanas_tenant_id ON construction.manzanas(tenant_id);
CREATE INDEX idx_manzanas_etapa_id ON construction.manzanas(etapa_id);
-- Lotes
CREATE INDEX idx_lotes_tenant_id ON construction.lotes(tenant_id);
CREATE INDEX idx_lotes_manzana_id ON construction.lotes(manzana_id);
CREATE INDEX idx_lotes_prototipo_id ON construction.lotes(prototipo_id);
CREATE INDEX idx_lotes_status ON construction.lotes(status);
CREATE INDEX idx_lotes_buyer_id ON construction.lotes(buyer_id) WHERE buyer_id IS NOT NULL;
-- Torres
CREATE INDEX idx_torres_tenant_id ON construction.torres(tenant_id);
CREATE INDEX idx_torres_etapa_id ON construction.torres(etapa_id);
-- Niveles
CREATE INDEX idx_niveles_tenant_id ON construction.niveles(tenant_id);
CREATE INDEX idx_niveles_torre_id ON construction.niveles(torre_id);
-- Departamentos
CREATE INDEX idx_departamentos_tenant_id ON construction.departamentos(tenant_id);
CREATE INDEX idx_departamentos_nivel_id ON construction.departamentos(nivel_id);
CREATE INDEX idx_departamentos_status ON construction.departamentos(status);
-- Prototipos
CREATE INDEX idx_prototipos_tenant_id ON construction.prototipos(tenant_id);
CREATE INDEX idx_prototipos_type ON construction.prototipos(type);
CREATE INDEX idx_prototipos_active ON construction.prototipos(tenant_id) WHERE is_active = TRUE;
-- Conceptos
CREATE INDEX idx_conceptos_tenant_id ON construction.conceptos(tenant_id);
CREATE INDEX idx_conceptos_parent_id ON construction.conceptos(parent_id);
CREATE INDEX idx_conceptos_code ON construction.conceptos(code);
CREATE INDEX idx_conceptos_path ON construction.conceptos(path);
-- Presupuestos
CREATE INDEX idx_presupuestos_tenant_id ON construction.presupuestos(tenant_id);
CREATE INDEX idx_presupuestos_fraccionamiento_id ON construction.presupuestos(fraccionamiento_id);
CREATE INDEX idx_presupuestos_prototipo_id ON construction.presupuestos(prototipo_id);
-- Avances
CREATE INDEX idx_avances_tenant_id ON construction.avances_obra(tenant_id);
CREATE INDEX idx_avances_lote_id ON construction.avances_obra(lote_id);
CREATE INDEX idx_avances_departamento_id ON construction.avances_obra(departamento_id);
CREATE INDEX idx_avances_concepto_id ON construction.avances_obra(concepto_id);
CREATE INDEX idx_avances_capture_date ON construction.avances_obra(capture_date);
CREATE INDEX idx_avances_status ON construction.avances_obra(status);
-- Bitacora
CREATE INDEX idx_bitacora_tenant_id ON construction.bitacora_obra(tenant_id);
CREATE INDEX idx_bitacora_fraccionamiento_id ON construction.bitacora_obra(fraccionamiento_id);
CREATE INDEX idx_bitacora_entry_date ON construction.bitacora_obra(entry_date);
-- Inspecciones
CREATE INDEX idx_inspecciones_tenant_id ON construction.inspecciones(tenant_id);
CREATE INDEX idx_inspecciones_lote_id ON construction.inspecciones(lote_id);
CREATE INDEX idx_inspecciones_status ON construction.inspecciones(status);
-- Tickets
CREATE INDEX idx_tickets_tenant_id ON construction.tickets_postventa(tenant_id);
CREATE INDEX idx_tickets_status ON construction.tickets_postventa(status);
-- Subcontratistas
CREATE INDEX idx_subcontratistas_tenant_id ON construction.subcontratistas(tenant_id);
CREATE INDEX idx_subcontratistas_specialty ON construction.subcontratistas(specialty);
-- Contratos
CREATE INDEX idx_contratos_tenant_id ON construction.contratos(tenant_id);
CREATE INDEX idx_contratos_subcontratista_id ON construction.contratos(subcontratista_id);
CREATE INDEX idx_contratos_fraccionamiento_id ON construction.contratos(fraccionamiento_id);
CREATE INDEX idx_contratos_status ON construction.contratos(status);
-- =====================================================
-- ROW LEVEL SECURITY (RLS)
-- =====================================================
-- Habilitar RLS en todas las tablas
ALTER TABLE construction.fraccionamientos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.etapas ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.manzanas ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.lotes ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.torres ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.niveles ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.departamentos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.prototipos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.conceptos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.presupuestos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.presupuesto_partidas ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.programa_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.programa_actividades ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.avances_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.fotos_avance ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.bitacora_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.checklists ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.checklist_items ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.inspecciones ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.inspeccion_resultados ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.tickets_postventa ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.subcontratistas ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.contratos ENABLE ROW LEVEL SECURITY;
ALTER TABLE construction.contrato_partidas ENABLE ROW LEVEL SECURITY;
-- Policies de tenant isolation
CREATE POLICY tenant_isolation_fraccionamientos ON construction.fraccionamientos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_etapas ON construction.etapas
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_manzanas ON construction.manzanas
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_lotes ON construction.lotes
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_torres ON construction.torres
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_niveles ON construction.niveles
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_departamentos ON construction.departamentos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_prototipos ON construction.prototipos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_conceptos ON construction.conceptos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_presupuestos ON construction.presupuestos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_presupuesto_partidas ON construction.presupuesto_partidas
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_programa_obra ON construction.programa_obra
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_programa_actividades ON construction.programa_actividades
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_avances_obra ON construction.avances_obra
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_fotos_avance ON construction.fotos_avance
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_bitacora_obra ON construction.bitacora_obra
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_checklists ON construction.checklists
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_checklist_items ON construction.checklist_items
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_inspecciones ON construction.inspecciones
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_inspeccion_resultados ON construction.inspeccion_resultados
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_tickets_postventa ON construction.tickets_postventa
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_subcontratistas ON construction.subcontratistas
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_contratos ON construction.contratos
USING (tenant_id = get_current_tenant_id());
CREATE POLICY tenant_isolation_contrato_partidas ON construction.contrato_partidas
USING (tenant_id = get_current_tenant_id());
-- =====================================================
-- COMENTARIOS
-- =====================================================
COMMENT ON SCHEMA construction IS 'Schema especifico de construccion: obras, lotes, avances, calidad';
COMMENT ON TABLE construction.fraccionamientos IS 'Desarrollos inmobiliarios/fraccionamientos';
COMMENT ON TABLE construction.etapas IS 'Etapas/fases de un fraccionamiento';
COMMENT ON TABLE construction.manzanas IS 'Manzanas dentro de una etapa';
COMMENT ON TABLE construction.lotes IS 'Lotes/terrenos vendibles (horizontal)';
COMMENT ON TABLE construction.torres IS 'Torres/edificios (vertical)';
COMMENT ON TABLE construction.niveles IS 'Pisos de una torre';
COMMENT ON TABLE construction.departamentos IS 'Departamentos/unidades en torre (vertical)';
COMMENT ON TABLE construction.prototipos IS 'Tipos de vivienda/prototipos';
COMMENT ON TABLE construction.conceptos IS 'Catalogo de conceptos de obra';
COMMENT ON TABLE construction.presupuestos IS 'Presupuestos por prototipo u obra';
COMMENT ON TABLE construction.avances_obra IS 'Captura de avances fisicos de obra';
COMMENT ON TABLE construction.bitacora_obra IS 'Bitacora diaria de obra';
COMMENT ON TABLE construction.checklists IS 'Plantillas de verificacion de calidad';
COMMENT ON TABLE construction.inspecciones IS 'Inspecciones de calidad';
COMMENT ON TABLE construction.tickets_postventa IS 'Tickets de garantia/postventa';
COMMENT ON TABLE construction.subcontratistas IS 'Catalogo de subcontratistas';
COMMENT ON TABLE construction.contratos IS 'Contratos con subcontratistas';
-- =====================================================
-- FIN DEL SCHEMA CONSTRUCTION
-- =====================================================