workspace/projects/erp-suite/apps/verticales/construccion/database/schemas/05-infonavit-schema-ddl.sql
rckrdmrd 789d1ab46b
Some checks failed
CI Pipeline / changes (push) Has been cancelled
CI Pipeline / core (push) Has been cancelled
CI Pipeline / trading-backend (push) Has been cancelled
CI Pipeline / trading-data-service (push) Has been cancelled
CI Pipeline / trading-frontend (push) Has been cancelled
CI Pipeline / erp-core (push) Has been cancelled
CI Pipeline / erp-mecanicas (push) Has been cancelled
CI Pipeline / gamilit-backend (push) Has been cancelled
CI Pipeline / gamilit-frontend (push) Has been cancelled
changes on workspace
2025-12-09 14:46:20 -06:00

414 lines
19 KiB
SQL

-- ============================================================================
-- INFONAVIT Schema DDL - Cumplimiento INFONAVIT y Derechohabientes
-- Modulos: MAI-010 (CRM Derechohabientes), MAI-011 (Integración INFONAVIT)
-- Version: 1.0.0
-- Fecha: 2025-12-08
-- ============================================================================
-- PREREQUISITOS:
-- 1. ERP-Core instalado (auth.tenants, auth.users, auth.companies)
-- 2. Schema construction instalado (fraccionamientos, lotes, departamentos)
-- ============================================================================
-- Verificar prerequisitos
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_namespace WHERE nspname = 'construction') THEN
RAISE EXCEPTION 'Schema construction no existe. Ejecutar primero construction DDL';
END IF;
END $$;
-- Crear schema
CREATE SCHEMA IF NOT EXISTS infonavit;
-- ============================================================================
-- TYPES (ENUMs)
-- ============================================================================
DO $$ BEGIN
CREATE TYPE infonavit.derechohabiente_status AS ENUM (
'prospect', 'pre_qualified', 'qualified', 'assigned', 'in_process', 'owner', 'cancelled'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE infonavit.credit_type AS ENUM (
'infonavit_tradicional', 'infonavit_total', 'cofinavit', 'mejoravit',
'fovissste', 'fovissste_infonavit', 'bank_credit', 'cash'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE infonavit.acta_type AS ENUM (
'inicio_obra', 'verificacion_avance', 'entrega_recepcion', 'conclusion_obra', 'liberacion_vivienda'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE infonavit.acta_status AS ENUM (
'draft', 'pending', 'signed', 'submitted', 'approved', 'rejected', 'cancelled'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE infonavit.report_type AS ENUM (
'avance_fisico', 'avance_financiero', 'inventario_viviendas', 'asignaciones', 'escrituraciones', 'cartera_vencida'
);
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
-- ============================================================================
-- TABLES - REGISTRO INFONAVIT
-- ============================================================================
-- Tabla: registro_infonavit (registro del constructor ante INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.registro_infonavit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
company_id UUID NOT NULL,
registro_number VARCHAR(50) NOT NULL,
registro_date DATE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active',
vigencia_start DATE,
vigencia_end DATE,
responsable_tecnico VARCHAR(255),
cedula_profesional VARCHAR(50),
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_registro_infonavit_tenant UNIQUE (tenant_id, registro_number)
);
-- Tabla: oferta_vivienda (oferta de viviendas ante INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.oferta_vivienda (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
registro_id UUID NOT NULL REFERENCES infonavit.registro_infonavit(id),
fraccionamiento_id UUID NOT NULL REFERENCES construction.fraccionamientos(id),
oferta_number VARCHAR(50) NOT NULL,
submission_date DATE NOT NULL,
approval_date DATE,
total_units INTEGER NOT NULL DEFAULT 0,
approved_units INTEGER DEFAULT 0,
price_range_min DECIMAL(14,2),
price_range_max DECIMAL(14,2),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
rejection_reason TEXT,
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_oferta_vivienda_tenant UNIQUE (tenant_id, oferta_number)
);
-- ============================================================================
-- TABLES - DERECHOHABIENTES
-- ============================================================================
-- Tabla: derechohabientes (compradores con crédito INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.derechohabientes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
partner_id UUID,
nss VARCHAR(15) NOT NULL,
curp VARCHAR(18),
rfc VARCHAR(13),
full_name VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
second_last_name VARCHAR(100),
birth_date DATE,
gender VARCHAR(10),
marital_status VARCHAR(20),
nationality VARCHAR(50) DEFAULT 'Mexicana',
email VARCHAR(255),
phone VARCHAR(20),
mobile VARCHAR(20),
address TEXT,
city VARCHAR(100),
state VARCHAR(100),
zip_code VARCHAR(10),
employer_name VARCHAR(255),
employer_rfc VARCHAR(13),
employment_start_date DATE,
salary DECIMAL(12,2),
cotization_weeks INTEGER,
credit_type infonavit.credit_type,
credit_number VARCHAR(50),
credit_amount DECIMAL(14,2),
puntos_infonavit DECIMAL(10,2),
subcuenta_vivienda DECIMAL(14,2),
precalificacion_date DATE,
precalificacion_amount DECIMAL(14,2),
status infonavit.derechohabiente_status NOT NULL DEFAULT 'prospect',
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_derechohabientes_nss_tenant UNIQUE (tenant_id, nss)
);
-- Tabla: asignacion_vivienda (asignación de vivienda a derechohabiente)
CREATE TABLE IF NOT EXISTS infonavit.asignacion_vivienda (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
derechohabiente_id UUID NOT NULL REFERENCES infonavit.derechohabientes(id),
lote_id UUID REFERENCES construction.lotes(id),
departamento_id UUID REFERENCES construction.departamentos(id),
oferta_id UUID REFERENCES infonavit.oferta_vivienda(id),
assignment_date DATE NOT NULL,
assignment_number VARCHAR(50),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
sale_price DECIMAL(14,2) NOT NULL,
credit_amount DECIMAL(14,2),
down_payment DECIMAL(14,2),
subsidy_amount DECIMAL(14,2),
notary_name VARCHAR(255),
notary_number VARCHAR(50),
deed_date DATE,
deed_number VARCHAR(50),
public_registry_number VARCHAR(50),
public_registry_date DATE,
scheduled_delivery_date DATE,
actual_delivery_date DATE,
delivery_act_id UUID,
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),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT chk_asignacion_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)
)
);
-- ============================================================================
-- TABLES - ACTAS
-- ============================================================================
-- Tabla: actas (actas INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.actas (
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),
acta_type infonavit.acta_type NOT NULL,
acta_number VARCHAR(50) NOT NULL,
acta_date DATE NOT NULL,
status infonavit.acta_status NOT NULL DEFAULT 'draft',
infonavit_representative VARCHAR(255),
constructor_representative VARCHAR(255),
perito_name VARCHAR(255),
perito_cedula VARCHAR(50),
description TEXT,
observations TEXT,
agreements TEXT,
physical_advance_percentage DECIMAL(5,2),
financial_advance_percentage DECIMAL(5,2),
signed_at TIMESTAMPTZ,
submitted_to_infonavit_at TIMESTAMPTZ,
infonavit_response_at TIMESTAMPTZ,
infonavit_folio VARCHAR(50),
document_url VARCHAR(500),
signed_document_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),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_actas_number_tenant UNIQUE (tenant_id, acta_number)
);
-- Tabla: acta_viviendas (viviendas incluidas en acta)
CREATE TABLE IF NOT EXISTS infonavit.acta_viviendas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
acta_id UUID NOT NULL REFERENCES infonavit.actas(id) ON DELETE CASCADE,
lote_id UUID REFERENCES construction.lotes(id),
departamento_id UUID REFERENCES construction.departamentos(id),
advance_percentage DECIMAL(5,2),
observations TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id)
);
-- ============================================================================
-- TABLES - REPORTES INFONAVIT
-- ============================================================================
-- Tabla: reportes_infonavit (reportes enviados a INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.reportes_infonavit (
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),
report_type infonavit.report_type NOT NULL,
report_number VARCHAR(50) NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
submission_date DATE,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
infonavit_folio VARCHAR(50),
total_units INTEGER,
units_in_progress INTEGER,
units_completed INTEGER,
units_delivered INTEGER,
physical_advance_percentage DECIMAL(5,2),
financial_advance_percentage DECIMAL(5,2),
document_url VARCHAR(500),
acknowledgment_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),
deleted_at TIMESTAMPTZ,
deleted_by UUID REFERENCES auth.users(id),
CONSTRAINT uq_reportes_number_tenant UNIQUE (tenant_id, report_number)
);
-- Tabla: historico_puntos (histórico de puntos INFONAVIT)
CREATE TABLE IF NOT EXISTS infonavit.historico_puntos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
derechohabiente_id UUID NOT NULL REFERENCES infonavit.derechohabientes(id),
query_date DATE NOT NULL,
puntos DECIMAL(10,2),
subcuenta_vivienda DECIMAL(14,2),
cotization_weeks INTEGER,
credit_capacity DECIMAL(14,2),
source VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id)
);
-- ============================================================================
-- INDICES
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_registro_infonavit_tenant_id ON infonavit.registro_infonavit(tenant_id);
CREATE INDEX IF NOT EXISTS idx_registro_infonavit_company_id ON infonavit.registro_infonavit(company_id);
CREATE INDEX IF NOT EXISTS idx_oferta_vivienda_tenant_id ON infonavit.oferta_vivienda(tenant_id);
CREATE INDEX IF NOT EXISTS idx_oferta_vivienda_registro_id ON infonavit.oferta_vivienda(registro_id);
CREATE INDEX IF NOT EXISTS idx_oferta_vivienda_fraccionamiento_id ON infonavit.oferta_vivienda(fraccionamiento_id);
CREATE INDEX IF NOT EXISTS idx_oferta_vivienda_status ON infonavit.oferta_vivienda(status);
CREATE INDEX IF NOT EXISTS idx_derechohabientes_tenant_id ON infonavit.derechohabientes(tenant_id);
CREATE INDEX IF NOT EXISTS idx_derechohabientes_nss ON infonavit.derechohabientes(nss);
CREATE INDEX IF NOT EXISTS idx_derechohabientes_curp ON infonavit.derechohabientes(curp);
CREATE INDEX IF NOT EXISTS idx_derechohabientes_status ON infonavit.derechohabientes(status);
CREATE INDEX IF NOT EXISTS idx_derechohabientes_credit_type ON infonavit.derechohabientes(credit_type);
CREATE INDEX IF NOT EXISTS idx_asignacion_tenant_id ON infonavit.asignacion_vivienda(tenant_id);
CREATE INDEX IF NOT EXISTS idx_asignacion_derechohabiente_id ON infonavit.asignacion_vivienda(derechohabiente_id);
CREATE INDEX IF NOT EXISTS idx_asignacion_lote_id ON infonavit.asignacion_vivienda(lote_id);
CREATE INDEX IF NOT EXISTS idx_asignacion_status ON infonavit.asignacion_vivienda(status);
CREATE INDEX IF NOT EXISTS idx_actas_tenant_id ON infonavit.actas(tenant_id);
CREATE INDEX IF NOT EXISTS idx_actas_fraccionamiento_id ON infonavit.actas(fraccionamiento_id);
CREATE INDEX IF NOT EXISTS idx_actas_type ON infonavit.actas(acta_type);
CREATE INDEX IF NOT EXISTS idx_actas_status ON infonavit.actas(status);
CREATE INDEX IF NOT EXISTS idx_acta_viviendas_tenant_id ON infonavit.acta_viviendas(tenant_id);
CREATE INDEX IF NOT EXISTS idx_acta_viviendas_acta_id ON infonavit.acta_viviendas(acta_id);
CREATE INDEX IF NOT EXISTS idx_reportes_tenant_id ON infonavit.reportes_infonavit(tenant_id);
CREATE INDEX IF NOT EXISTS idx_reportes_fraccionamiento_id ON infonavit.reportes_infonavit(fraccionamiento_id);
CREATE INDEX IF NOT EXISTS idx_reportes_type ON infonavit.reportes_infonavit(report_type);
CREATE INDEX IF NOT EXISTS idx_historico_puntos_tenant_id ON infonavit.historico_puntos(tenant_id);
CREATE INDEX IF NOT EXISTS idx_historico_puntos_derechohabiente_id ON infonavit.historico_puntos(derechohabiente_id);
-- ============================================================================
-- ROW LEVEL SECURITY (RLS)
-- ============================================================================
ALTER TABLE infonavit.registro_infonavit ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.oferta_vivienda ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.derechohabientes ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.asignacion_vivienda ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.actas ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.acta_viviendas ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.reportes_infonavit ENABLE ROW LEVEL SECURITY;
ALTER TABLE infonavit.historico_puntos ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
DROP POLICY IF EXISTS tenant_isolation_registro_infonavit ON infonavit.registro_infonavit;
CREATE POLICY tenant_isolation_registro_infonavit ON infonavit.registro_infonavit
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_oferta_vivienda ON infonavit.oferta_vivienda;
CREATE POLICY tenant_isolation_oferta_vivienda ON infonavit.oferta_vivienda
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_derechohabientes ON infonavit.derechohabientes;
CREATE POLICY tenant_isolation_derechohabientes ON infonavit.derechohabientes
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_asignacion_vivienda ON infonavit.asignacion_vivienda;
CREATE POLICY tenant_isolation_asignacion_vivienda ON infonavit.asignacion_vivienda
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_actas ON infonavit.actas;
CREATE POLICY tenant_isolation_actas ON infonavit.actas
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_acta_viviendas ON infonavit.acta_viviendas;
CREATE POLICY tenant_isolation_acta_viviendas ON infonavit.acta_viviendas
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_reportes_infonavit ON infonavit.reportes_infonavit;
CREATE POLICY tenant_isolation_reportes_infonavit ON infonavit.reportes_infonavit
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_historico_puntos ON infonavit.historico_puntos;
CREATE POLICY tenant_isolation_historico_puntos ON infonavit.historico_puntos
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
EXCEPTION WHEN undefined_object THEN NULL; END $$;
-- ============================================================================
-- COMENTARIOS
-- ============================================================================
COMMENT ON SCHEMA infonavit IS 'Schema de cumplimiento INFONAVIT y gestión de derechohabientes';
COMMENT ON TABLE infonavit.registro_infonavit IS 'Registro del constructor ante INFONAVIT';
COMMENT ON TABLE infonavit.oferta_vivienda IS 'Oferta de viviendas registrada ante INFONAVIT';
COMMENT ON TABLE infonavit.derechohabientes IS 'Derechohabientes INFONAVIT/compradores';
COMMENT ON TABLE infonavit.asignacion_vivienda IS 'Asignación de vivienda a derechohabiente';
COMMENT ON TABLE infonavit.actas IS 'Actas oficiales INFONAVIT';
COMMENT ON TABLE infonavit.acta_viviendas IS 'Viviendas incluidas en cada acta';
COMMENT ON TABLE infonavit.reportes_infonavit IS 'Reportes periódicos enviados a INFONAVIT';
COMMENT ON TABLE infonavit.historico_puntos IS 'Histórico de consulta de puntos INFONAVIT';
-- ============================================================================
-- FIN DEL SCHEMA INFONAVIT
-- Total tablas: 8
-- ============================================================================