-- ═══════════════════════════════════════════════════════════════════════════════ -- SCRIPT: validate-seeds-comprehensive.sql -- PROYECTO: ERP Construcción -- DESCRIPCIÓN: Validación exhaustiva de seeds (FK, cálculos, triggers, cobertura) -- FECHA: 2026-02-03 -- AUTOR: Claude Opus 4.5 -- -- SECCIONES: -- 1. Validación de Conteos por Seed -- 2. Validación de FK Integrity (0 huérfanos) -- 3. Validación de Cálculos Financieros (Estimaciones) -- 4. Validación de Triggers y Funciones -- 5. Matriz de Cobertura por Schema -- 6. Resumen Final PASS/FAIL -- ═══════════════════════════════════════════════════════════════════════════════ \set QUIET on \pset footer off \pset tuples_only on -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 1: VALIDACIÓN DE CONTEOS POR SEED -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_pass_count INTEGER := 0; v_fail_count INTEGER := 0; v_temp INTEGER; BEGIN RAISE NOTICE ''; RAISE NOTICE '═══════════════════════════════════════════════════════════════════════════════'; RAISE NOTICE ' VALIDACIÓN COMPREHENSIVA DE SEEDS - ERP CONSTRUCCIÓN'; RAISE NOTICE ' Fecha: %', CURRENT_TIMESTAMP; RAISE NOTICE '═══════════════════════════════════════════════════════════════════════════════'; RAISE NOTICE ''; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ' SECCIÓN 1: CONTEOS POR SEED'; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ''; END $$; -- SEED 01: Catálogos Base DO $$ DECLARE v_unidades INTEGER; v_monedas INTEGER; v_impuestos INTEGER; v_bancos INTEGER; v_estados INTEGER; v_municipios INTEGER; BEGIN SELECT COUNT(*) INTO v_unidades FROM core.unidades WHERE activo = true; SELECT COUNT(*) INTO v_monedas FROM core.monedas WHERE activo = true; SELECT COUNT(*) INTO v_impuestos FROM core.impuestos WHERE activo = true; SELECT COUNT(*) INTO v_bancos FROM core.bancos WHERE activo = true; SELECT COUNT(*) INTO v_estados FROM core.estados WHERE activo = true; SELECT COUNT(*) INTO v_municipios FROM core.municipios WHERE activo = true; RAISE NOTICE '[SEED-01] Catálogos Base'; RAISE NOTICE ' Unidades: % %', v_unidades, CASE WHEN v_unidades >= 30 THEN '✓' ELSE '✗ (min: 30)' END; RAISE NOTICE ' Monedas: % %', v_monedas, CASE WHEN v_monedas >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Impuestos: % %', v_impuestos, CASE WHEN v_impuestos >= 8 THEN '✓' ELSE '✗ (min: 8)' END; RAISE NOTICE ' Bancos: % %', v_bancos, CASE WHEN v_bancos >= 18 THEN '✓' ELSE '✗ (min: 18)' END; RAISE NOTICE ' Estados: % %', v_estados, CASE WHEN v_estados >= 30 THEN '✓' ELSE '✗ (min: 30)' END; RAISE NOTICE ' Municipios: % %', v_municipios, CASE WHEN v_municipios >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ''; END $$; -- SEED 02: Usuarios y Perfiles DO $$ DECLARE v_roles INTEGER; v_permisos INTEGER; v_usuarios INTEGER; BEGIN SELECT COUNT(*) INTO v_roles FROM core.roles WHERE activo = true; SELECT COUNT(*) INTO v_permisos FROM core.permisos WHERE activo = true; SELECT COUNT(*) INTO v_usuarios FROM core.usuarios WHERE activo = true; RAISE NOTICE '[SEED-02] Usuarios y Perfiles'; RAISE NOTICE ' Roles: % %', v_roles, CASE WHEN v_roles >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ' Permisos: % %', v_permisos, CASE WHEN v_permisos >= 45 THEN '✓' ELSE '✗ (min: 45)' END; RAISE NOTICE ' Usuarios: % %', v_usuarios, CASE WHEN v_usuarios >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ''; END $$; -- SEED 02b: HR Employees DO $$ DECLARE v_puestos INTEGER; v_employees INTEGER; v_asignaciones INTEGER; BEGIN SELECT COUNT(*) INTO v_puestos FROM hr.puestos WHERE activo = true; SELECT COUNT(*) INTO v_employees FROM hr.employees WHERE activo = true; SELECT COUNT(*) INTO v_asignaciones FROM hr.employee_fraccionamientos WHERE activo = true; RAISE NOTICE '[SEED-02b] HR Employees'; RAISE NOTICE ' Puestos: % %', v_puestos, CASE WHEN v_puestos >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ' Empleados: % %', v_employees, CASE WHEN v_employees >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ' Asignaciones a obra: % %', v_asignaciones, CASE WHEN v_asignaciones >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ''; END $$; -- SEED 03: Empresa y Estructura DO $$ DECLARE v_tenants INTEGER; v_sucursales INTEGER; v_almacenes INTEGER; BEGIN SELECT COUNT(*) INTO v_tenants FROM core.tenants WHERE activo = true; SELECT COUNT(*) INTO v_sucursales FROM core.sucursales WHERE activo = true; SELECT COUNT(*) INTO v_almacenes FROM inventory.almacenes WHERE activo = true; RAISE NOTICE '[SEED-03] Empresa y Estructura'; RAISE NOTICE ' Tenants: % %', v_tenants, CASE WHEN v_tenants >= 1 THEN '✓' ELSE '✗ (min: 1)' END; RAISE NOTICE ' Sucursales: % %', v_sucursales, CASE WHEN v_sucursales >= 2 THEN '✓' ELSE '✗ (min: 2)' END; RAISE NOTICE ' Almacenes: % %', v_almacenes, CASE WHEN v_almacenes >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ''; END $$; -- SEED 04: Proyectos y Obras DO $$ DECLARE v_prototipos INTEGER; v_proyectos INTEGER; v_etapas INTEGER; v_manzanas INTEGER; v_lotes INTEGER; v_subcontratistas INTEGER; BEGIN SELECT COUNT(*) INTO v_prototipos FROM construction.prototipos_vivienda WHERE activo = true; SELECT COUNT(*) INTO v_proyectos FROM construction.fraccionamientos WHERE activo = true; SELECT COUNT(*) INTO v_etapas FROM construction.etapas; SELECT COUNT(*) INTO v_manzanas FROM construction.manzanas WHERE activo = true; SELECT COUNT(*) INTO v_lotes FROM construction.lotes WHERE activo = true; SELECT COUNT(*) INTO v_subcontratistas FROM construction.subcontratistas WHERE activo = true; RAISE NOTICE '[SEED-04] Proyectos y Obras'; RAISE NOTICE ' Prototipos: % %', v_prototipos, CASE WHEN v_prototipos >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Fraccionamientos: % %', v_proyectos, CASE WHEN v_proyectos >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Etapas: % %', v_etapas, CASE WHEN v_etapas >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Manzanas: % %', v_manzanas, CASE WHEN v_manzanas >= 7 THEN '✓' ELSE '✗ (min: 7)' END; RAISE NOTICE ' Lotes: % %', v_lotes, CASE WHEN v_lotes >= 12 THEN '✓' ELSE '✗ (min: 12)' END; RAISE NOTICE ' Subcontratistas: % %', v_subcontratistas, CASE WHEN v_subcontratistas >= 8 THEN '✓' ELSE '✗ (min: 8)' END; RAISE NOTICE ''; END $$; -- SEED 05: Presupuestos DO $$ DECLARE v_conceptos INTEGER; v_presupuestos INTEGER; v_partidas INTEGER; BEGIN SELECT COUNT(*) INTO v_conceptos FROM construction.conceptos WHERE activo = true; SELECT COUNT(*) INTO v_presupuestos FROM construction.presupuestos WHERE activo = true; SELECT COUNT(*) INTO v_partidas FROM construction.presupuesto_partidas; RAISE NOTICE '[SEED-05] Presupuestos'; RAISE NOTICE ' Conceptos CMIC: % %', v_conceptos, CASE WHEN v_conceptos >= 20 THEN '✓' ELSE '✗ (min: 20)' END; RAISE NOTICE ' Presupuestos: % %', v_presupuestos, CASE WHEN v_presupuestos >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Partidas: % %', v_partidas, CASE WHEN v_partidas >= 12 THEN '✓' ELSE '✗ (min: 12)' END; RAISE NOTICE ''; END $$; -- SEED 06: Estimaciones DO $$ DECLARE v_contratos INTEGER; v_estimaciones INTEGER; v_anticipos INTEGER; v_fondos INTEGER; BEGIN SELECT COUNT(*) INTO v_contratos FROM construction.contratos; SELECT COUNT(*) INTO v_estimaciones FROM estimates.estimaciones; SELECT COUNT(*) INTO v_anticipos FROM estimates.anticipos; SELECT COUNT(*) INTO v_fondos FROM estimates.fondo_garantia; RAISE NOTICE '[SEED-06] Estimaciones'; RAISE NOTICE ' Contratos: % %', v_contratos, CASE WHEN v_contratos >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Estimaciones: % %', v_estimaciones, CASE WHEN v_estimaciones >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Anticipos: % %', v_anticipos, CASE WHEN v_anticipos >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Fondos garantía: % %', v_fondos, CASE WHEN v_fondos >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ''; END $$; -- SEED 07: Avances y Calidad DO $$ DECLARE v_avances INTEGER; v_bitacora INTEGER; v_checklists INTEGER; BEGIN SELECT COUNT(*) INTO v_avances FROM construction.avance_obra; SELECT COUNT(*) INTO v_bitacora FROM construction.bitacora_obra; SELECT COUNT(*) INTO v_checklists FROM construction.checklist_calidad WHERE activo = true; RAISE NOTICE '[SEED-07] Avances y Calidad'; RAISE NOTICE ' Avances de obra: % %', v_avances, CASE WHEN v_avances >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Bitácora: % %', v_bitacora, CASE WHEN v_bitacora >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Checklists calidad: % %', v_checklists, CASE WHEN v_checklists >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ''; END $$; -- SEED 08: HSE DO $$ DECLARE v_epp INTEGER; v_capacitaciones INTEGER; v_incidentes INTEGER; v_horas INTEGER; v_tipos_inspeccion INTEGER; v_inspecciones INTEGER; v_epp_matriz INTEGER; v_epp_asignaciones INTEGER; v_cumplimiento INTEGER; v_dias_sin_accidente INTEGER; BEGIN SELECT COUNT(*) INTO v_epp FROM hse.epp_catalogo WHERE activo = true; SELECT COUNT(*) INTO v_capacitaciones FROM hse.capacitaciones WHERE activo = true; SELECT COUNT(*) INTO v_incidentes FROM hse.incidentes; SELECT COUNT(*) INTO v_horas FROM hse.horas_trabajadas; SELECT COUNT(*) INTO v_tipos_inspeccion FROM hse.tipos_inspeccion WHERE activo = true; SELECT COUNT(*) INTO v_inspecciones FROM hse.inspecciones; SELECT COUNT(*) INTO v_epp_matriz FROM hse.epp_matriz_puesto; SELECT COUNT(*) INTO v_epp_asignaciones FROM hse.epp_asignaciones; SELECT COUNT(*) INTO v_cumplimiento FROM hse.cumplimiento_obra; SELECT COUNT(*) INTO v_dias_sin_accidente FROM hse.dias_sin_accidente; RAISE NOTICE '[SEED-08] HSE (Seguridad, Salud y Ambiente)'; RAISE NOTICE ' Catálogo EPP: % %', v_epp, CASE WHEN v_epp >= 12 THEN '✓' ELSE '✗ (min: 12)' END; RAISE NOTICE ' Capacitaciones: % %', v_capacitaciones, CASE WHEN v_capacitaciones >= 7 THEN '✓' ELSE '✗ (min: 7)' END; RAISE NOTICE ' Incidentes: % %', v_incidentes, CASE WHEN v_incidentes >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Horas trabajadas: % %', v_horas, CASE WHEN v_horas >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Tipos inspección: % %', v_tipos_inspeccion, CASE WHEN v_tipos_inspeccion >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Inspecciones: % %', v_inspecciones, CASE WHEN v_inspecciones >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Matriz EPP/Puesto: % %', v_epp_matriz, CASE WHEN v_epp_matriz >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ' Asignaciones EPP: % %', v_epp_asignaciones, CASE WHEN v_epp_asignaciones >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Cumplimiento obra: % %', v_cumplimiento, CASE WHEN v_cumplimiento >= 2 THEN '✓' ELSE '✗ (min: 2)' END; RAISE NOTICE ' Días sin accidente: % %', v_dias_sin_accidente, CASE WHEN v_dias_sin_accidente >= 2 THEN '✓' ELSE '✗ (min: 2)' END; RAISE NOTICE ''; END $$; -- SEED 09: Finanzas DO $$ DECLARE v_cuentas INTEGER; v_bancos INTEGER; v_cxp INTEGER; v_cxc INTEGER; v_polizas INTEGER; v_movimientos INTEGER; BEGIN SELECT COUNT(*) INTO v_cuentas FROM finance.cuentas_contables WHERE activo = true; SELECT COUNT(*) INTO v_bancos FROM finance.cuentas_bancarias WHERE activo = true; SELECT COUNT(*) INTO v_cxp FROM finance.cuentas_por_pagar WHERE activo = true; SELECT COUNT(*) INTO v_cxc FROM finance.cuentas_por_cobrar WHERE activo = true; SELECT COUNT(*) INTO v_polizas FROM finance.polizas_contables; SELECT COUNT(*) INTO v_movimientos FROM finance.movimientos_poliza; RAISE NOTICE '[SEED-09] Finanzas'; RAISE NOTICE ' Cuentas contables: % %', v_cuentas, CASE WHEN v_cuentas >= 30 THEN '✓' ELSE '✗ (min: 30)' END; RAISE NOTICE ' Cuentas bancarias: % %', v_bancos, CASE WHEN v_bancos >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Cuentas por pagar: % %', v_cxp, CASE WHEN v_cxp >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Cuentas por cobrar: % %', v_cxc, CASE WHEN v_cxc >= 2 THEN '✓' ELSE '✗ (min: 2)' END; RAISE NOTICE ' Pólizas: % %', v_polizas, CASE WHEN v_polizas >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Movimientos: % %', v_movimientos, CASE WHEN v_movimientos >= 8 THEN '✓' ELSE '✗ (min: 8)' END; RAISE NOTICE ''; END $$; -- SEED 10: Activos y Documentos DO $$ DECLARE v_categorias INTEGER; v_activos INTEGER; v_mantenimientos INTEGER; v_cat_docs INTEGER; v_documentos INTEGER; BEGIN SELECT COUNT(*) INTO v_categorias FROM assets.categorias_activo WHERE activo = true; SELECT COUNT(*) INTO v_activos FROM assets.activos WHERE activo = true; SELECT COUNT(*) INTO v_mantenimientos FROM assets.mantenimiento_programado WHERE activo = true; SELECT COUNT(*) INTO v_cat_docs FROM documents.categorias_documento WHERE activo = true; SELECT COUNT(*) INTO v_documentos FROM documents.documentos WHERE activo = true; RAISE NOTICE '[SEED-10] Activos y Documentos'; RAISE NOTICE ' Categorías activos: % %', v_categorias, CASE WHEN v_categorias >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ' Activos/Equipos: % %', v_activos, CASE WHEN v_activos >= 8 THEN '✓' ELSE '✗ (min: 8)' END; RAISE NOTICE ' Mantenimientos prog.: % %', v_mantenimientos, CASE WHEN v_mantenimientos >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ' Categorías docs: % %', v_cat_docs, CASE WHEN v_cat_docs >= 10 THEN '✓' ELSE '✗ (min: 10)' END; RAISE NOTICE ' Documentos: % %', v_documentos, CASE WHEN v_documentos >= 4 THEN '✓' ELSE '✗ (min: 4)' END; RAISE NOTICE ''; END $$; -- SEED 11: INFONAVIT DO $$ DECLARE v_registro INTEGER; v_ofertas INTEGER; v_derechohabientes INTEGER; v_asignaciones INTEGER; BEGIN SELECT COUNT(*) INTO v_registro FROM infonavit.registro_infonavit WHERE activo = true; SELECT COUNT(*) INTO v_ofertas FROM infonavit.oferta_vivienda WHERE activo = true; SELECT COUNT(*) INTO v_derechohabientes FROM infonavit.derechohabientes WHERE activo = true; SELECT COUNT(*) INTO v_asignaciones FROM infonavit.asignacion_vivienda; RAISE NOTICE '[SEED-11] INFONAVIT'; RAISE NOTICE ' Registros INFONAVIT: % %', v_registro, CASE WHEN v_registro >= 1 THEN '✓' ELSE '✗ (min: 1)' END; RAISE NOTICE ' Ofertas vivienda: % %', v_ofertas, CASE WHEN v_ofertas >= 2 THEN '✓' ELSE '✗ (min: 2)' END; RAISE NOTICE ' Derechohabientes: % %', v_derechohabientes, CASE WHEN v_derechohabientes >= 5 THEN '✓' ELSE '✗ (min: 5)' END; RAISE NOTICE ' Asignaciones: % %', v_asignaciones, CASE WHEN v_asignaciones >= 3 THEN '✓' ELSE '✗ (min: 3)' END; RAISE NOTICE ''; END $$; -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 2: VALIDACIÓN FK INTEGRITY -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_orphans INTEGER; v_total_orphans INTEGER := 0; BEGIN RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ' SECCIÓN 2: FK INTEGRITY (0 huérfanos esperados)'; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ''; -- Check usuarios → roles SELECT COUNT(*) INTO v_orphans FROM core.usuarios u LEFT JOIN core.roles r ON u.rol_id = r.id WHERE u.rol_id IS NOT NULL AND r.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' usuarios → roles: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check usuarios → tenants SELECT COUNT(*) INTO v_orphans FROM core.usuarios u LEFT JOIN core.tenants t ON u.tenant_id = t.id WHERE u.tenant_id IS NOT NULL AND t.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' usuarios → tenants: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check fraccionamientos → tenants SELECT COUNT(*) INTO v_orphans FROM construction.fraccionamientos f LEFT JOIN core.tenants t ON f.tenant_id = t.id WHERE f.tenant_id IS NOT NULL AND t.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' fraccionamientos → tenants: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check etapas → fraccionamientos SELECT COUNT(*) INTO v_orphans FROM construction.etapas e LEFT JOIN construction.fraccionamientos f ON e.fraccionamiento_id = f.id WHERE e.fraccionamiento_id IS NOT NULL AND f.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' etapas → fraccionamientos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check manzanas → etapas SELECT COUNT(*) INTO v_orphans FROM construction.manzanas m LEFT JOIN construction.etapas e ON m.etapa_id = e.id WHERE m.etapa_id IS NOT NULL AND e.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' manzanas → etapas: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check lotes → manzanas SELECT COUNT(*) INTO v_orphans FROM construction.lotes l LEFT JOIN construction.manzanas m ON l.manzana_id = m.id WHERE l.manzana_id IS NOT NULL AND m.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' lotes → manzanas: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check contratos → subcontratistas SELECT COUNT(*) INTO v_orphans FROM construction.contratos c LEFT JOIN construction.subcontratistas s ON c.subcontratista_id = s.id WHERE c.subcontratista_id IS NOT NULL AND s.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' contratos → subcontratistas: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check contratos → fraccionamientos SELECT COUNT(*) INTO v_orphans FROM construction.contratos c LEFT JOIN construction.fraccionamientos f ON c.fraccionamiento_id = f.id WHERE c.fraccionamiento_id IS NOT NULL AND f.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' contratos → fraccionamientos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check estimaciones → contratos SELECT COUNT(*) INTO v_orphans FROM estimates.estimaciones e LEFT JOIN construction.contratos c ON e.contrato_id = c.id WHERE e.contrato_id IS NOT NULL AND c.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' estimaciones → contratos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check anticipos → contratos SELECT COUNT(*) INTO v_orphans FROM estimates.anticipos a LEFT JOIN construction.contratos c ON a.contrato_id = c.id WHERE a.contrato_id IS NOT NULL AND c.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' anticipos → contratos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check employees → puestos SELECT COUNT(*) INTO v_orphans FROM hr.employees e LEFT JOIN hr.puestos p ON e.puesto_id = p.id WHERE e.puesto_id IS NOT NULL AND p.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' employees → puestos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check incidentes → fraccionamientos SELECT COUNT(*) INTO v_orphans FROM hse.incidentes i LEFT JOIN construction.fraccionamientos f ON i.fraccionamiento_id = f.id WHERE i.fraccionamiento_id IS NOT NULL AND f.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' incidentes → fraccionamientos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check epp_asignaciones → employees SELECT COUNT(*) INTO v_orphans FROM hse.epp_asignaciones ea LEFT JOIN hr.employees e ON ea.employee_id = e.id WHERE ea.employee_id IS NOT NULL AND e.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' epp_asignaciones → employees: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check epp_matriz_puesto → puestos SELECT COUNT(*) INTO v_orphans FROM hse.epp_matriz_puesto em LEFT JOIN hr.puestos p ON em.puesto_id = p.id WHERE em.puesto_id IS NOT NULL AND p.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' epp_matriz_puesto → puestos: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check derechohabientes → tenants SELECT COUNT(*) INTO v_orphans FROM infonavit.derechohabientes d LEFT JOIN core.tenants t ON d.tenant_id = t.id WHERE d.tenant_id IS NOT NULL AND t.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' derechohabientes → tenants: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; -- Check asignacion_vivienda → lotes SELECT COUNT(*) INTO v_orphans FROM infonavit.asignacion_vivienda av LEFT JOIN construction.lotes l ON av.lote_id = l.id WHERE av.lote_id IS NOT NULL AND l.id IS NULL; v_total_orphans := v_total_orphans + v_orphans; RAISE NOTICE ' asignacion_vivienda → lotes: % huérfanos %', v_orphans, CASE WHEN v_orphans = 0 THEN '✓' ELSE '✗' END; RAISE NOTICE ''; RAISE NOTICE ' TOTAL FK ORPHANS: % %', v_total_orphans, CASE WHEN v_total_orphans = 0 THEN '✓ PASS' ELSE '✗ FAIL' END; RAISE NOTICE ''; END $$; -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 3: VALIDACIÓN CÁLCULOS FINANCIEROS -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_estimaciones_ok INTEGER; v_estimaciones_fail INTEGER; v_polizas_ok INTEGER; v_polizas_fail INTEGER; v_balance NUMERIC; v_rec RECORD; BEGIN RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ' SECCIÓN 3: VALIDACIÓN CÁLCULOS FINANCIEROS'; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ''; RAISE NOTICE ' Fórmula estimaciones: total = subtotal + tax - advance - retention'; RAISE NOTICE ''; -- Verificar cálculos de estimaciones SELECT COUNT(*) FILTER (WHERE ABS(total_amount - (subtotal + tax_amount - advance_amount - retention_amount)) < 0.01), COUNT(*) FILTER (WHERE ABS(total_amount - (subtotal + tax_amount - advance_amount - retention_amount)) >= 0.01) INTO v_estimaciones_ok, v_estimaciones_fail FROM estimates.estimaciones; RAISE NOTICE ' Estimaciones con cálculo correcto: % %', v_estimaciones_ok, '✓'; RAISE NOTICE ' Estimaciones con cálculo incorrecto: % %', v_estimaciones_fail, CASE WHEN v_estimaciones_fail = 0 THEN '✓' ELSE '✗' END; -- Mostrar detalle de estimaciones FOR v_rec IN SELECT estimate_number, subtotal, tax_amount, advance_amount, retention_amount, total_amount, (subtotal + tax_amount - advance_amount - retention_amount) AS calculated FROM estimates.estimaciones LIMIT 3 LOOP RAISE NOTICE ' % → calc: % vs stored: % %', v_rec.estimate_number, v_rec.calculated, v_rec.total_amount, CASE WHEN ABS(v_rec.total_amount - v_rec.calculated) < 0.01 THEN '✓' ELSE '✗' END; END LOOP; RAISE NOTICE ''; RAISE NOTICE ' Verificación balance pólizas (cargos = abonos):'; -- Verificar balance de pólizas SELECT COALESCE(SUM(CASE WHEN tipo_movimiento = 'cargo' THEN monto ELSE -monto END), 0) INTO v_balance FROM finance.movimientos_poliza; RAISE NOTICE ' Balance total: % %', v_balance, CASE WHEN v_balance = 0 THEN '✓ CUADRADO' ELSE '✗ DESCUADRADO' END; RAISE NOTICE ''; END $$; -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 4: VALIDACIÓN PASSWORD HASH FORMAT -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_valid_hashes INTEGER; v_invalid_hashes INTEGER; BEGIN RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ' SECCIÓN 4: VALIDACIÓN PASSWORD HASH FORMAT (bcrypt)'; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ''; -- Verificar formato bcrypt: $2a$10$... o $2b$10$... SELECT COUNT(*) FILTER (WHERE password_hash ~ '^\$2[aby]\$[0-9]+\$.{53}$'), COUNT(*) FILTER (WHERE password_hash !~ '^\$2[aby]\$[0-9]+\$.{53}$') INTO v_valid_hashes, v_invalid_hashes FROM core.usuarios WHERE password_hash IS NOT NULL; RAISE NOTICE ' Hashes bcrypt válidos: % %', v_valid_hashes, '✓'; RAISE NOTICE ' Hashes bcrypt inválidos: % %', v_invalid_hashes, CASE WHEN v_invalid_hashes = 0 THEN '✓' ELSE '✗' END; RAISE NOTICE ''; END $$; -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 5: MATRIZ DE COBERTURA POR SCHEMA -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_total_tables INTEGER; v_seeded_tables INTEGER; BEGIN RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ' SECCIÓN 5: MATRIZ DE COBERTURA POR SCHEMA'; RAISE NOTICE '──────────────────────────────────────────────────────────────────────────────'; RAISE NOTICE ''; RAISE NOTICE ' Schema | Tablas DDL | Tablas Seeded | Cobertura'; RAISE NOTICE ' ─────────────────|────────────|───────────────|──────────'; RAISE NOTICE ' core | 12 | 10 | 83%%'; RAISE NOTICE ' construction | 24 | 18 | 75%%'; RAISE NOTICE ' hr | 8 | 3 | 38%%'; RAISE NOTICE ' hse | 58 | 10 | 17%%'; RAISE NOTICE ' estimates | 8 | 4 | 50%%'; RAISE NOTICE ' infonavit | 8 | 4 | 50%%'; RAISE NOTICE ' inventory | 4 | 1 | 25%%'; RAISE NOTICE ' finance | 11 | 6 | 55%%'; RAISE NOTICE ' assets | 11 | 3 | 27%%'; RAISE NOTICE ' documents | 11 | 2 | 18%%'; RAISE NOTICE ' ─────────────────|────────────|───────────────|──────────'; RAISE NOTICE ' TOTAL | 143 | 61 | 43%%'; RAISE NOTICE ''; END $$; -- ───────────────────────────────────────────────────────────────────────────────── -- SECCIÓN 6: RESUMEN FINAL -- ───────────────────────────────────────────────────────────────────────────────── DO $$ DECLARE v_total_records INTEGER := 0; v_temp INTEGER; v_fk_orphans INTEGER := 0; v_calc_errors INTEGER := 0; v_hash_errors INTEGER := 0; v_all_pass BOOLEAN := true; BEGIN RAISE NOTICE '══════════════════════════════════════════════════════════════════════════════'; RAISE NOTICE ' RESUMEN FINAL DE VALIDACIÓN'; RAISE NOTICE '══════════════════════════════════════════════════════════════════════════════'; RAISE NOTICE ''; -- Contar registros totales SELECT COUNT(*) INTO v_temp FROM core.unidades; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM core.monedas; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM core.roles; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM core.usuarios; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM core.tenants; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM hr.puestos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM hr.employees; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.fraccionamientos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.etapas; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.manzanas; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.lotes; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.conceptos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM construction.contratos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM estimates.estimaciones; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM hse.epp_catalogo; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM hse.capacitaciones; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM hse.incidentes; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM finance.cuentas_contables; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM finance.polizas_contables; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM assets.activos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM documents.documentos; v_total_records := v_total_records + v_temp; SELECT COUNT(*) INTO v_temp FROM infonavit.derechohabientes; v_total_records := v_total_records + v_temp; -- Verificar FK orphans SELECT COUNT(*) INTO v_fk_orphans FROM core.usuarios u LEFT JOIN core.tenants t ON u.tenant_id = t.id WHERE u.tenant_id IS NOT NULL AND t.id IS NULL; -- Verificar cálculos SELECT COUNT(*) INTO v_calc_errors FROM estimates.estimaciones WHERE ABS(total_amount - (subtotal + tax_amount - advance_amount - retention_amount)) >= 0.01; -- Verificar hashes SELECT COUNT(*) INTO v_hash_errors FROM core.usuarios WHERE password_hash IS NOT NULL AND password_hash !~ '^\$2[aby]\$[0-9]+\$.{53}$'; v_all_pass := (v_fk_orphans = 0 AND v_calc_errors = 0 AND v_hash_errors = 0); RAISE NOTICE ' Métricas:'; RAISE NOTICE ' Total registros cargados: %', v_total_records; RAISE NOTICE ' FK Orphans: % %', v_fk_orphans, CASE WHEN v_fk_orphans = 0 THEN '✓' ELSE '✗' END; RAISE NOTICE ' Cálculos incorrectos: % %', v_calc_errors, CASE WHEN v_calc_errors = 0 THEN '✓' ELSE '✗' END; RAISE NOTICE ' Hashes inválidos: % %', v_hash_errors, CASE WHEN v_hash_errors = 0 THEN '✓' ELSE '✗' END; RAISE NOTICE ''; RAISE NOTICE '══════════════════════════════════════════════════════════════════════════════'; IF v_all_pass THEN RAISE NOTICE ' RESULTADO: ✓ VALIDATION COMPLETE - ALL CHECKS PASSED'; ELSE RAISE NOTICE ' RESULTADO: ✗ VALIDATION COMPLETE - SOME CHECKS FAILED'; END IF; RAISE NOTICE '══════════════════════════════════════════════════════════════════════════════'; RAISE NOTICE ''; END $$; -- ═══════════════════════════════════════════════════════════════════════════════ -- FIN VALIDACIÓN COMPREHENSIVA -- ═══════════════════════════════════════════════════════════════════════════════