erp-construccion-database-v2/schemas/01-construction-schema-ddl.sql
Adrian Flores Cortes 5f009673aa [REMEDIATION] feat: Database schema updates and recreate script enhancement
Major overhaul of drop-and-recreate-database.sh, DDL schema updates,
seed data cleanup. Add utility scripts for auth table fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:18:19 -06:00

1052 lines
47 KiB
SQL

-- ============================================================================
-- CONSTRUCTION Schema DDL - Gestión de Obras (COMPLETO)
-- Modulos: MAI-002, MAI-003, MAI-005, MAI-009, MAI-012
-- Version: 2.0.0
-- Fecha: 2025-12-08
-- ============================================================================
-- POLITICA: CARGA LIMPIA (ver DIRECTIVA-POLITICA-CARGA-LIMPIA.md)
-- Este archivo es parte de la fuente de verdad DDL.
-- ============================================================================
-- Verificar que ERP-Core está instalado
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN
RAISE EXCEPTION 'Schema auth no existe. Ejecutar primero ERP-Core DDL';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'tenants') THEN
RAISE EXCEPTION 'Tabla auth.tenants no existe. ERP-Core debe estar instalado';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users') THEN
RAISE EXCEPTION 'Tabla auth.users no existe. ERP-Core debe estar instalado';
END IF;
END $$;
-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS construction;
-- ============================================================================
-- TYPES (ENUMs)
-- ============================================================================
DO $$ BEGIN
CREATE TYPE construction.project_status AS ENUM (
'draft', 'planning', 'in_progress', 'paused', 'completed', 'cancelled'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.lot_status AS ENUM (
'available', 'reserved', 'sold', 'under_construction', 'delivered', 'warranty'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.prototype_type AS ENUM (
'horizontal', 'vertical', 'commercial', 'mixed'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.advance_status AS ENUM (
'pending', 'captured', 'reviewed', 'approved', 'rejected'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.quality_status AS ENUM (
'pending', 'in_review', 'approved', 'rejected', 'rework'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.contract_type AS ENUM (
'fixed_price', 'unit_price', 'cost_plus', 'mixed'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE construction.contract_status AS ENUM (
'draft', 'pending_approval', 'active', 'suspended', 'terminated', 'closed'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
-- ============================================================================
-- TABLES - ESTRUCTURA DE PROYECTO
-- ============================================================================
-- Tabla: proyectos (proyectos de desarrollo inmobiliario)
CREATE TABLE IF NOT EXISTS construction.proyectos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
codigo VARCHAR(20) NOT NULL,
nombre VARCHAR(200) NOT NULL,
descripcion TEXT,
direccion TEXT,
ciudad VARCHAR(100),
estado VARCHAR(100),
fecha_inicio DATE,
fecha_fin_estimada DATE,
estado_proyecto VARCHAR(20) NOT NULL DEFAULT 'activo',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_proyectos_codigo_tenant UNIQUE (tenant_id, codigo),
CONSTRAINT chk_proyectos_estado CHECK (estado_proyecto IN ('activo', 'pausado', 'completado', 'cancelado'))
);
-- Tabla: fraccionamientos (desarrollo inmobiliario)
CREATE TABLE IF NOT EXISTS construction.fraccionamientos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
proyecto_id UUID NOT NULL REFERENCES construction.proyectos(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),
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 '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_fraccionamientos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: etapas (fases del fraccionamiento)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_etapas_code_fracc UNIQUE (fraccionamiento_id, code)
);
-- Tabla: manzanas (agrupación de lotes)
CREATE TABLE IF NOT EXISTS 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),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_manzanas_code_etapa UNIQUE (etapa_id, code)
);
-- Tabla: prototipos (tipos de vivienda) - definida antes de lotes
CREATE TABLE IF NOT EXISTS 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 '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_prototipos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: lotes (unidades vendibles horizontal)
CREATE TABLE IF NOT EXISTS 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),
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,
sale_date DATE,
delivery_date DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
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 IF NOT EXISTS 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),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_torres_code_etapa UNIQUE (etapa_id, code)
);
-- Tabla: niveles (pisos de torre)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_niveles_floor_torre UNIQUE (torre_id, floor_number)
);
-- Tabla: departamentos (unidades en torre)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_departamentos_code_nivel UNIQUE (nivel_id, code)
);
-- ============================================================================
-- TABLES - CONCEPTOS Y PRESUPUESTOS
-- ============================================================================
-- Tabla: conceptos (catálogo de conceptos de obra)
CREATE TABLE IF NOT EXISTS 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,
unit_price DECIMAL(12,4),
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
level INTEGER NOT NULL DEFAULT 0,
path VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_conceptos_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: presupuestos (presupuesto por prototipo/obra)
CREATE TABLE IF NOT EXISTS 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,
approved_at TIMESTAMPTZ,
approved_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_presupuestos_code_version UNIQUE (tenant_id, code, version)
);
-- Tabla: presupuesto_partidas (líneas del presupuesto)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
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 IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
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 IF NOT EXISTS 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,
wbs_code VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: avances_obra (captura de avances)
CREATE TABLE IF NOT EXISTS 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 TIMESTAMPTZ,
approved_by UUID REFERENCES auth.users(id),
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
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 fotográfica)
CREATE TABLE IF NOT EXISTS 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),
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: bitacora_obra (registro de bitácora)
CREATE TABLE IF NOT EXISTS 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),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
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 verificación)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_checklists_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: checklist_items (items del checklist)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: inspecciones (inspecciones de calidad)
CREATE TABLE IF NOT EXISTS 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 TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: inspeccion_resultados (resultados por item)
CREATE TABLE IF NOT EXISTS 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),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id)
);
-- Tabla: tickets_postventa (tickets de garantía)
CREATE TABLE IF NOT EXISTS 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),
description TEXT NOT NULL,
priority VARCHAR(20) DEFAULT 'medium',
status VARCHAR(20) NOT NULL DEFAULT 'open',
assigned_to UUID REFERENCES auth.users(id),
resolution TEXT,
resolved_at TIMESTAMPTZ,
resolved_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_tickets_number_tenant UNIQUE (tenant_id, ticket_number)
);
-- Tabla: no_conformidades (no conformidades detectadas)
CREATE TABLE IF NOT EXISTS construction.no_conformidades (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
inspeccion_id UUID REFERENCES construction.inspecciones(id),
lote_id UUID REFERENCES construction.lotes(id),
nc_number VARCHAR(50) NOT NULL,
detection_date DATE NOT NULL,
category VARCHAR(100),
severity VARCHAR(20) NOT NULL DEFAULT 'minor',
description TEXT NOT NULL,
root_cause TEXT,
photo_url VARCHAR(500),
contractor_id UUID REFERENCES construction.subcontratistas(id),
status VARCHAR(20) NOT NULL DEFAULT 'open',
due_date DATE,
closed_at TIMESTAMPTZ,
closed_by UUID REFERENCES auth.users(id),
verified_at TIMESTAMPTZ,
verified_by UUID REFERENCES auth.users(id),
closure_photo_url VARCHAR(500),
closure_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_nc_number_tenant UNIQUE (tenant_id, nc_number),
CONSTRAINT chk_nc_severity CHECK (severity IN ('minor', 'major', 'critical')),
CONSTRAINT chk_nc_status CHECK (status IN ('open', 'in_progress', 'closed', 'verified'))
);
-- Tabla: acciones_correctivas (CAPA)
CREATE TABLE IF NOT EXISTS construction.acciones_correctivas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
no_conformidad_id UUID NOT NULL REFERENCES construction.no_conformidades(id) ON DELETE CASCADE,
action_type VARCHAR(20) NOT NULL DEFAULT 'corrective',
description TEXT NOT NULL,
responsible_id UUID NOT NULL REFERENCES auth.users(id),
due_date DATE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
completed_at TIMESTAMPTZ,
completion_notes TEXT,
verified_at TIMESTAMPTZ,
verified_by UUID REFERENCES auth.users(id),
effectiveness_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
CONSTRAINT chk_action_type CHECK (action_type IN ('corrective', 'preventive', 'improvement')),
CONSTRAINT chk_action_status CHECK (status IN ('pending', 'in_progress', 'completed', 'verified'))
);
-- Tabla: ticket_asignaciones (asignaciones de tickets a técnicos)
CREATE TABLE IF NOT EXISTS construction.ticket_asignaciones (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
ticket_id UUID NOT NULL REFERENCES construction.tickets_postventa(id) ON DELETE CASCADE,
technician_id UUID NOT NULL REFERENCES auth.users(id),
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
assigned_by UUID NOT NULL REFERENCES auth.users(id),
status VARCHAR(20) NOT NULL DEFAULT 'assigned',
accepted_at TIMESTAMPTZ,
scheduled_date DATE,
scheduled_time TIME,
completed_at TIMESTAMPTZ,
work_notes TEXT,
reassignment_reason TEXT,
is_current BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ,
CONSTRAINT chk_assignment_status CHECK (status IN ('assigned', 'accepted', 'in_progress', 'completed', 'reassigned'))
);
-- Indices adicionales para calidad
CREATE INDEX IF NOT EXISTS idx_no_conformidades_tenant ON construction.no_conformidades(tenant_id);
CREATE INDEX IF NOT EXISTS idx_no_conformidades_status ON construction.no_conformidades(status);
CREATE INDEX IF NOT EXISTS idx_no_conformidades_inspeccion ON construction.no_conformidades(inspeccion_id);
CREATE INDEX IF NOT EXISTS idx_acciones_correctivas_nc ON construction.acciones_correctivas(no_conformidad_id);
CREATE INDEX IF NOT EXISTS idx_ticket_asignaciones_ticket ON construction.ticket_asignaciones(ticket_id);
CREATE INDEX IF NOT EXISTS idx_ticket_asignaciones_technician ON construction.ticket_asignaciones(technician_id);
-- ============================================================================
-- TABLES - CONTRATOS Y SUBCONTRATOS (MAI-012)
-- ============================================================================
-- Tabla: subcontratistas
CREATE TABLE IF NOT EXISTS 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,
code VARCHAR(20) NOT NULL,
name VARCHAR(255) NOT NULL,
legal_name VARCHAR(255),
tax_id VARCHAR(20),
specialty VARCHAR(100),
contact_name VARCHAR(100),
contact_phone VARCHAR(20),
contact_email VARCHAR(100),
address TEXT,
rating DECIMAL(3,2),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_subcontratistas_code_tenant UNIQUE (tenant_id, code)
);
-- Tabla: contratos (contratos con subcontratistas)
CREATE TABLE IF NOT EXISTS 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,
retention_percentage DECIMAL(5,2) DEFAULT 5,
status construction.contract_status NOT NULL DEFAULT 'draft',
signed_at TIMESTAMPTZ,
signed_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_contratos_number_tenant UNIQUE (tenant_id, contract_number)
);
-- Tabla: contrato_partidas (líneas del contrato)
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id)
);
-- Tabla: contrato_addendas (addendas/modificaciones a contratos)
CREATE TABLE IF NOT EXISTS construction.contrato_addendas (
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,
addendum_number VARCHAR(50) NOT NULL,
addendum_type VARCHAR(30) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
effective_date DATE NOT NULL,
new_end_date DATE,
amount_change DECIMAL(16,2) DEFAULT 0,
new_contract_amount DECIMAL(16,2),
scope_changes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
approved_at TIMESTAMPTZ,
approved_by UUID REFERENCES auth.users(id),
rejection_reason TEXT,
document_url VARCHAR(500),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ,
updated_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_contrato_addendas_number_tenant UNIQUE (tenant_id, addendum_number)
);
-- ============================================================================
-- INDICES
-- ============================================================================
-- Proyectos
CREATE INDEX IF NOT EXISTS idx_proyectos_tenant_id ON construction.proyectos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_proyectos_estado ON construction.proyectos(estado_proyecto);
CREATE INDEX IF NOT EXISTS idx_proyectos_codigo ON construction.proyectos(codigo);
-- Fraccionamientos
CREATE INDEX IF NOT EXISTS idx_fraccionamientos_tenant_id ON construction.fraccionamientos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_fraccionamientos_proyecto_id ON construction.fraccionamientos(proyecto_id);
CREATE INDEX IF NOT EXISTS idx_fraccionamientos_status ON construction.fraccionamientos(status);
CREATE INDEX IF NOT EXISTS idx_fraccionamientos_code ON construction.fraccionamientos(code);
-- Etapas
CREATE INDEX IF NOT EXISTS idx_etapas_tenant_id ON construction.etapas(tenant_id);
CREATE INDEX IF NOT EXISTS idx_etapas_fraccionamiento_id ON construction.etapas(fraccionamiento_id);
-- Manzanas
CREATE INDEX IF NOT EXISTS idx_manzanas_tenant_id ON construction.manzanas(tenant_id);
CREATE INDEX IF NOT EXISTS idx_manzanas_etapa_id ON construction.manzanas(etapa_id);
-- Lotes
CREATE INDEX IF NOT EXISTS idx_lotes_tenant_id ON construction.lotes(tenant_id);
CREATE INDEX IF NOT EXISTS idx_lotes_manzana_id ON construction.lotes(manzana_id);
CREATE INDEX IF NOT EXISTS idx_lotes_prototipo_id ON construction.lotes(prototipo_id);
CREATE INDEX IF NOT EXISTS idx_lotes_status ON construction.lotes(status);
-- Torres
CREATE INDEX IF NOT EXISTS idx_torres_tenant_id ON construction.torres(tenant_id);
CREATE INDEX IF NOT EXISTS idx_torres_etapa_id ON construction.torres(etapa_id);
-- Niveles
CREATE INDEX IF NOT EXISTS idx_niveles_tenant_id ON construction.niveles(tenant_id);
CREATE INDEX IF NOT EXISTS idx_niveles_torre_id ON construction.niveles(torre_id);
-- Departamentos
CREATE INDEX IF NOT EXISTS idx_departamentos_tenant_id ON construction.departamentos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_departamentos_nivel_id ON construction.departamentos(nivel_id);
CREATE INDEX IF NOT EXISTS idx_departamentos_status ON construction.departamentos(status);
-- Prototipos
CREATE INDEX IF NOT EXISTS idx_prototipos_tenant_id ON construction.prototipos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_prototipos_type ON construction.prototipos(type);
-- Conceptos
CREATE INDEX IF NOT EXISTS idx_conceptos_tenant_id ON construction.conceptos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_conceptos_parent_id ON construction.conceptos(parent_id);
CREATE INDEX IF NOT EXISTS idx_conceptos_code ON construction.conceptos(code);
-- Presupuestos
CREATE INDEX IF NOT EXISTS idx_presupuestos_tenant_id ON construction.presupuestos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_presupuestos_fraccionamiento_id ON construction.presupuestos(fraccionamiento_id);
-- Avances
CREATE INDEX IF NOT EXISTS idx_avances_tenant_id ON construction.avances_obra(tenant_id);
CREATE INDEX IF NOT EXISTS idx_avances_lote_id ON construction.avances_obra(lote_id);
CREATE INDEX IF NOT EXISTS idx_avances_concepto_id ON construction.avances_obra(concepto_id);
CREATE INDEX IF NOT EXISTS idx_avances_capture_date ON construction.avances_obra(capture_date);
-- Bitacora
CREATE INDEX IF NOT EXISTS idx_bitacora_tenant_id ON construction.bitacora_obra(tenant_id);
CREATE INDEX IF NOT EXISTS idx_bitacora_fraccionamiento_id ON construction.bitacora_obra(fraccionamiento_id);
-- Inspecciones
CREATE INDEX IF NOT EXISTS idx_inspecciones_tenant_id ON construction.inspecciones(tenant_id);
CREATE INDEX IF NOT EXISTS idx_inspecciones_status ON construction.inspecciones(status);
-- Tickets
CREATE INDEX IF NOT EXISTS idx_tickets_tenant_id ON construction.tickets_postventa(tenant_id);
CREATE INDEX IF NOT EXISTS idx_tickets_status ON construction.tickets_postventa(status);
-- Subcontratistas
CREATE INDEX IF NOT EXISTS idx_subcontratistas_tenant_id ON construction.subcontratistas(tenant_id);
-- Contratos
CREATE INDEX IF NOT EXISTS idx_contratos_tenant_id ON construction.contratos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_contratos_subcontratista_id ON construction.contratos(subcontratista_id);
CREATE INDEX IF NOT EXISTS idx_contratos_fraccionamiento_id ON construction.contratos(fraccionamiento_id);
-- ============================================================================
-- ROW LEVEL SECURITY (RLS)
-- ============================================================================
ALTER TABLE construction.proyectos ENABLE ROW LEVEL SECURITY;
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 usando current_setting
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_proyectos ON construction.proyectos;
CREATE POLICY tenant_isolation_proyectos ON construction.proyectos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_fraccionamientos ON construction.fraccionamientos;
CREATE POLICY tenant_isolation_fraccionamientos ON construction.fraccionamientos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_etapas ON construction.etapas;
CREATE POLICY tenant_isolation_etapas ON construction.etapas
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_manzanas ON construction.manzanas;
CREATE POLICY tenant_isolation_manzanas ON construction.manzanas
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_lotes ON construction.lotes;
CREATE POLICY tenant_isolation_lotes ON construction.lotes
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_torres ON construction.torres;
CREATE POLICY tenant_isolation_torres ON construction.torres
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_niveles ON construction.niveles;
CREATE POLICY tenant_isolation_niveles ON construction.niveles
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_departamentos ON construction.departamentos;
CREATE POLICY tenant_isolation_departamentos ON construction.departamentos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_prototipos ON construction.prototipos;
CREATE POLICY tenant_isolation_prototipos ON construction.prototipos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_conceptos ON construction.conceptos;
CREATE POLICY tenant_isolation_conceptos ON construction.conceptos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_presupuestos ON construction.presupuestos;
CREATE POLICY tenant_isolation_presupuestos ON construction.presupuestos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_presupuesto_partidas ON construction.presupuesto_partidas;
CREATE POLICY tenant_isolation_presupuesto_partidas ON construction.presupuesto_partidas
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_programa_obra ON construction.programa_obra;
CREATE POLICY tenant_isolation_programa_obra ON construction.programa_obra
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_programa_actividades ON construction.programa_actividades;
CREATE POLICY tenant_isolation_programa_actividades ON construction.programa_actividades
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_avances_obra ON construction.avances_obra;
CREATE POLICY tenant_isolation_avances_obra ON construction.avances_obra
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_fotos_avance ON construction.fotos_avance;
CREATE POLICY tenant_isolation_fotos_avance ON construction.fotos_avance
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_bitacora_obra ON construction.bitacora_obra;
CREATE POLICY tenant_isolation_bitacora_obra ON construction.bitacora_obra
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_checklists ON construction.checklists;
CREATE POLICY tenant_isolation_checklists ON construction.checklists
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_checklist_items ON construction.checklist_items;
CREATE POLICY tenant_isolation_checklist_items ON construction.checklist_items
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_inspecciones ON construction.inspecciones;
CREATE POLICY tenant_isolation_inspecciones ON construction.inspecciones
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_inspeccion_resultados ON construction.inspeccion_resultados;
CREATE POLICY tenant_isolation_inspeccion_resultados ON construction.inspeccion_resultados
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_tickets_postventa ON construction.tickets_postventa;
CREATE POLICY tenant_isolation_tickets_postventa ON construction.tickets_postventa
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_subcontratistas ON construction.subcontratistas;
CREATE POLICY tenant_isolation_subcontratistas ON construction.subcontratistas
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_contratos ON construction.contratos;
CREATE POLICY tenant_isolation_contratos ON construction.contratos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_contrato_partidas ON construction.contrato_partidas;
CREATE POLICY tenant_isolation_contrato_partidas ON construction.contrato_partidas
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
-- ============================================================================
-- COMENTARIOS
-- ============================================================================
COMMENT ON SCHEMA construction IS 'Schema de construcción: obras, lotes, avances, calidad, contratos';
COMMENT ON TABLE construction.proyectos IS 'Proyectos de desarrollo inmobiliario';
COMMENT ON TABLE construction.fraccionamientos IS 'Fraccionamientos/obras dentro de un proyecto';
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';
COMMENT ON TABLE construction.prototipos IS 'Tipos de vivienda/prototipos';
COMMENT ON TABLE construction.conceptos IS 'Catálogo 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 físicos';
COMMENT ON TABLE construction.bitacora_obra IS 'Bitácora diaria de obra';
COMMENT ON TABLE construction.checklists IS 'Plantillas de verificación';
COMMENT ON TABLE construction.inspecciones IS 'Inspecciones de calidad';
COMMENT ON TABLE construction.tickets_postventa IS 'Tickets de garantía';
COMMENT ON TABLE construction.subcontratistas IS 'Catálogo de subcontratistas';
COMMENT ON TABLE construction.contratos IS 'Contratos con subcontratistas';
-- ============================================================================
-- FIN DEL SCHEMA CONSTRUCTION
-- Total tablas: 25 (agregada proyectos)
-- ============================================================================