-- ============================================================================ -- 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 -- ============================================================================