From f16256b406950c0904141289a982e11227734a6b Mon Sep 17 00:00:00 2001 From: Antigravity AI Date: Sun, 19 Apr 2026 08:00:07 -0300 Subject: [PATCH] Emergencia: Restaurar trabajo de los ultimos 4 dias --- GIS-GEOSERVER-REGLAS.md | 34 +++++++++++++++++++++++++++------- PERFIL-TECNOLOGICO.md | 42 ++++++++++++++++++++++++++++++++++++++++++ PUNTO_DE_CONTROL_OVIEDO.md | 20 ++++++++++++++++++++ apply_master_join_505.sql | 18 ++++++++++++++++++ check_505.sql | 1 + check_bounds_505.sql | 3 +++ check_cols_505.sql | 4 ++++ check_count_505.sql | 1 + check_map_colors_505.sql | 5 +++++ check_master_integrity.sql | 10 ++++++++++ check_migration_progress.sql | 1 + check_oviedo_local_load.sql | 1 + check_oviedo_mapping.sql | 1 + check_view_def.sql | 1 + check_vinculacion_505.sql | 6 ++++++ count_505.sql | 2 ++ create_master_snc_table.sql | 41 +++++++++++++++++++++++++++++++++++++++++ debug_join_precision.sql | 19 +++++++++++++++++++ deduplicate_master_table.sql | 10 ++++++++++ diagnostic_split.sql | 27 +++++++++++++++++++++++++++ fdw_inm_audit.sql | 28 ++++++++++++++++++++++++++++ fdw_inm_dash_sample.sql | 10 ++++++++++ fdw_quality_audit.sql | 26 ++++++++++++++++++++++++++ final_audit_505.sql | 5 +++++ final_binary_audit.sql | 8 ++++++++ final_integrity_check.sql | 1 + final_logic_verification.sql | 13 +++++++++++++ find_dist_21.sql | 2 ++ find_oviedo_real_codes.sql | 5 +++++ fix_ccc_505.sql | 12 ++++++++++++ fix_master_table_columns.sql | 3 +++ fix_regla26_perfected.sql | 15 +++++++++++++++ full_record_audit.sql | 10 ++++++++++ full_record_dump.sql | 2 ++ get_bounds_master.sql | 3 +++ national_sovereign_fix.sql | 25 +++++++++++++++++++++++++ nature_of_lots_audit.sql | 11 +++++++++++ optimize_master_table.sql | 3 +++ peek_fdw_keys.sql | 4 ++++ peek_success_matches.sql | 10 ++++++++++ query_raw_districts.sql | 3 +++ recalculate_snc_cuenta.sql | 20 ++++++++++++++++++++ show_discrepancies.sql | 10 ++++++++++ show_mapping_505.sql | 1 + src/main/java/com/sigem/gis/WebConfig.java | 2 ++ src/main/java/com/sigem/gis/controller/SncImportController.java | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------- src/main/java/com/sigem/gis/security/SecurityConfig.java | 1 + src/main/java/com/sigem/gis/service/FdwService.java | 19 ++++++++++++------- src/main/resources/static/login.html | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------- src/main/resources/static/mapas.html | 605 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------ test_dash_matches.sql | 9 +++++++++ test_morosidad_505.sql | 4 ++++ truncate_505.sql | 2 ++ update_morosidad_505.sql | 18 +++++++++++------- urban_record_dump.sql | 2 ++ verify_oviedo_master.sql | 9 +++++++++ verify_prefix_21.sql | 18 ++++++++++++++++++ 57 files changed, 1440 insertions(+), 213 deletions(-) create mode 100644 PERFIL-TECNOLOGICO.md create mode 100644 PUNTO_DE_CONTROL_OVIEDO.md create mode 100644 apply_master_join_505.sql create mode 100644 check_505.sql create mode 100644 check_bounds_505.sql create mode 100644 check_cols_505.sql create mode 100644 check_count_505.sql create mode 100644 check_map_colors_505.sql create mode 100644 check_master_integrity.sql create mode 100644 check_migration_progress.sql create mode 100644 check_oviedo_local_load.sql create mode 100644 check_oviedo_mapping.sql create mode 100644 check_view_def.sql create mode 100644 check_vinculacion_505.sql create mode 100644 count_505.sql create mode 100644 create_master_snc_table.sql create mode 100644 debug_join_precision.sql create mode 100644 deduplicate_master_table.sql create mode 100644 diagnostic_split.sql create mode 100644 fdw_inm_audit.sql create mode 100644 fdw_inm_dash_sample.sql create mode 100644 fdw_quality_audit.sql create mode 100644 final_audit_505.sql create mode 100644 final_binary_audit.sql create mode 100644 final_integrity_check.sql create mode 100644 final_logic_verification.sql create mode 100644 find_dist_21.sql create mode 100644 find_oviedo_real_codes.sql create mode 100644 fix_ccc_505.sql create mode 100644 fix_master_table_columns.sql create mode 100644 fix_regla26_perfected.sql create mode 100644 full_record_audit.sql create mode 100644 full_record_dump.sql create mode 100644 get_bounds_master.sql create mode 100644 national_sovereign_fix.sql create mode 100644 nature_of_lots_audit.sql create mode 100644 optimize_master_table.sql create mode 100644 peek_fdw_keys.sql create mode 100644 peek_success_matches.sql create mode 100644 query_raw_districts.sql create mode 100644 recalculate_snc_cuenta.sql create mode 100644 show_discrepancies.sql create mode 100644 show_mapping_505.sql create mode 100644 test_dash_matches.sql create mode 100644 test_morosidad_505.sql create mode 100644 truncate_505.sql create mode 100644 urban_record_dump.sql create mode 100644 verify_oviedo_master.sql create mode 100644 verify_prefix_21.sql diff --git a/GIS-GEOSERVER-REGLAS.md b/GIS-GEOSERVER-REGLAS.md index fffc345..70d3080 100644 --- a/GIS-GEOSERVER-REGLAS.md +++ b/GIS-GEOSERVER-REGLAS.md @@ -2,6 +2,7 @@ Regla 0. Eres un senior fullstack developer con experiencia reconocida en Base de Datos PostgreSQL, amplia experiencia en proyectos corporativos con geoserver con uso de plataformas basadas en Java, javascript, HTML, XML, git, jenkins, maven, springboot, swagger, proxy de apache y nginx. El ecosistema principal es Java 21 con Spring Boot y deberá usarse de forma preferencial todas las veces que se pueda. +El frontend usa el framework corporativo AdminLTE/Bootstrap. Regla 1. El ambiente de desarrollo y compilación se encuentra en el 192.168.1.123. El JENKINS a usar está en este servidor. Los comandos del jenkins se ejecutan en el servidor 192.168.1.123. @@ -86,6 +87,7 @@ Regla 23. Columnas de Unión (Joins). Standard SNC. Para toda vista de unión con el FDW, se debe utilizar la columna `snc_cuenta` (limpia) contra `REPLACE(liq.inm_ctacatastral, '-', '')`. La definición del SQL de JOIN entre los datos SNC (datos georreferenciados) y la base de datos del municipio (datos alfanuméricos) genera la vista `public.vw_lotes_morosidad_XXX` a usar para desplegar mapa georreferenciado coloreado y es la siguiente: ```sql +DROP VIEW IF EXISTS public.vw_lotes_morosidad_XXX CASCADE; CREATE OR REPLACE VIEW public.vw_lotes_morosidad_XXX AS SELECT lot.*, @@ -114,13 +116,21 @@ Almacenamiento en carpeta cronológica dentro de /publico/. Solo bajo autorización del usuario. -Regla 26. Normalización Universal de Cartografía SNC. -El motor de importación debe aplicar una limpieza universal al campo snc_cuenta para asegurar el match tributario. -La identificación de la zona se realiza mediante la variable **tipo_cuenta**: -1. **Zona Urbana (tipo_cuenta = 0)**: snc_cuenta = Substring(ccatastral, 4) eliminando ceros a la izquierda y caracteres especiales. -2. **Zona Rural (tipo_cuenta = 1)**: snc_cuenta = padron::text (sin modificaciones). - -Integridad: Se debe aplicar ST_MakeValid(geom) en la inserción para prevenir errores de renderizado en GeoServer. +Regla 26. El motor de importación debe aplicar una limpieza universal al campo snc_cuenta para asegurar el match tributario. +La identificación de la zona se realiza mediante la variable tipo_cuenta: +1. Zona Urbana (tipo_cuenta = 1): snc_cuenta = Substring(ccatastral, 4) eliminando ceros a la izquierda y caracteres especiales. +Algoritmo Java para Zona Urbana: +substring(3) (4ª posición). +replaceAll("[^a-zA-Z0-9]", "") (Eliminar guiones, puntos, etc.). +replaceFirst("^0+", "") (Eliminar ceros a la izquierda resultantes). +Algoritmo SQL para Zona Urbana: +SUBSTRING(ccatastral FROM 4) (Substring desde la 4ª posición). +REGEXP_REPLACE(..., '[^a-zA-Z0-9]', '', 'g') (Limpieza de caracteres especiales). +LTRIM(..., '0') (Eliminación de ceros a la izquierda). + +2. Zona Rural (tipo_cuenta = 0): snc_cuenta = padron::text (sin modificaciones). +Esta regla es mandatoria para toda municipalidad. +Se debe aplicar ST_MakeValid(geom) en la inserción para prevenir errores de renderizado en GeoServer. Para la relación entre código SNC y código de municipio se debe utilizar la tabla ENTIDADES que se registra en public.snc_catalog_mapping Regla 27. Optimización y Cache GeoWebCache (GWC): @@ -134,3 +144,13 @@ La inserción en la base de datos se realizará mediante el uso directo de ST_Ge Implementar un TRUNCATE automático al inicio del proceso de importación. Si al importar los números son UTM, se deben transformar a 4326 antes de guardarlos. Las columnas de las tablas eXXX_lotes_activos deberá tener todas las columnas del SNC. + +Regla 29. +Para la construcción en la compilación, se usa JAVA21 del 192.168.1.123, en sincronía con la Regla 1. + +Regla 30. Arquitectura de Visualización en Dos Niveles. +El visor GIS utiliza una estrategia de superposición para optimizar el rendimiento y la claridad visual. Utiliza dos tipos de capas para mostrar los lotes: Una Capa Base (Lotes Grises) y múltiples Capas Temáticas (Lotes Coloreados). +1. Capa Base (Esqueleto): Utiliza la tabla física soberana `eXXX_lotes_activos`. Se publica en GeoServer con un estilo neutro (gris/transparente). Representa la totalidad del territorio y siempre es local y persistente. +2. Capa Temática (Inteligencia): Utiliza la vista lógica `vw_lotes_morosidad_XXX`. Es la capa dinámica que aplica el JOIN con el FDW municipal. +El Efecto Visual: La capa temática se superpone a la base. Aquellos lotes con coincidencia tributaria se "pintan" con el estilo de morosidad (semáforo), mientras que los lotes sin vínculo o sin deuda permanecen en gris (dejando ver la capa base), garantizando que la ciudad nunca se vea "incompleta". + diff --git a/PERFIL-TECNOLOGICO.md b/PERFIL-TECNOLOGICO.md new file mode 100644 index 0000000..a178732 --- /dev/null +++ b/PERFIL-TECNOLOGICO.md @@ -0,0 +1,42 @@ +# Perfil Tecnológico: Plataforma GIS-GEOSERVER (SNC + SIGEM) + +## 1. Visión Estratégica +La plataforma **GIS-GEOSERVER** representa la capa de inteligencia geográfica para la gestión municipal moderna. A diferencia de sistemas GIS aislados, esta arquitectura garantiza la **soberanía de los datos** al vincular el parcelario oficial de la Dirección Nacional de Catastro (SNC) con la realidad tributaria y administrativa de cada municipio en tiempo real. + +## 2. Núcleo Tecnológico (The Core) + +### Procesamiento Geoespacial Avanzado +* **Engine:** PostgreSQL 16 con extensión **PostGIS**. +* **Estándar de Geometría:** Procesamiento universal en **SRID 4326**. El sistema realiza transformaciones de coordenadas en tiempo real para asegurar que los datos del SNC (a menudo en UTM) se visualicen correctamente en cualquier visor web moderno. +* **Integridad Topológica:** Uso de funciones espaciales como `ST_MakeValid` y `ST_GeomFromGeoJSON` para garantizar que las capas se rendericen sin errores en el motor de mapas. + +### Orquestación de Mapas (Map Serving) +* **GeoServer:** Implementación de alto rendimiento sobre Java 21. +* **Protocolos Soportados:** WMS (Visualización), WFS (Intercambio de datos vectoriales) y REST API para administración automatizada de capas. +* **Optimización de Cache:** Uso intensivo de **GeoWebCache (GWC)** para reducir la carga del servidor y mejorar el tiempo de respuesta al usuario final mediante el pre-cacheo de teselas. + +## 3. Arquitectura de Integración (Interoperabilidad) + +### Virtualización de Datos mediante FDW +El sistema utiliza **Foreign Data Wrappers (FDW)** para "leer" las bases de datos municipales sin necesidad de duplicar la información. +* **Beneficio:** Si un contribuyente paga su impuesto en el sistema de gestión, el mapa se "pinta" automáticamente de un color diferente en el siguiente refresco de pantalla, sin intervención manual. + +### Normalización Dinámica (Regla 26) +Implementación de un motor de limpieza de cuentas catastrales que elimina la fricción entre los diferentes formatos de códigos de cuenta: +* **Limpieza Universal:** Algoritmos en Java y SQL que normalizan cuentas urbanas y rurales para asegurar que el `snc_cuenta` siempre encuentre su par tributario en el sistema de gestión. + +## 4. Gestión de Infraestructura y Resiliencia + +### Desarrollo y Despliegue (DevOps) +* **Stack:** Java 21 / Spring Boot 3.x / Maven. +* **CI/CD:** Automatización mediante **Jenkins**, permitiendo actualizaciones continuas con mínimo tiempo de inactividad. +* **Contenerización:** Despliegue basado en **Docker**, facilitando la escalabilidad y la portabilidad del entorno entre servidores. + +### Seguridad y Aislamiento Multi-Tenant +La arquitectura está diseñada para manejar múltiples municipios (Entidades) de forma aislada: +* **Aislamiento de Datos:** Cada municipio tiene su propio esquema y sus propias vistas de morosidad (`vw_lotes_morosidad_XXX`), garantizando la privacidad y seguridad de la información. +* **Soberanía Local:** El sistema diferencia entre la "Capa Base" (lotes físicos locales) y "Capas Temáticas" (vistas inteligentes), permitiendo que el municipio siempre tenga acceso a su inventario de tierras, incluso si los servicios externos están temporalmente fuera de línea. + +--- +**Documento de Perfil Tecnológico v1.1** +*Preparado para revisión y expansión de hitos técnicos.* diff --git a/PUNTO_DE_CONTROL_OVIEDO.md b/PUNTO_DE_CONTROL_OVIEDO.md new file mode 100644 index 0000000..6d0312a --- /dev/null +++ b/PUNTO_DE_CONTROL_OVIEDO.md @@ -0,0 +1,20 @@ +# Punto de Control - SIGEM-GIS (Coronel Oviedo 505) +**Fecha**: 16 de Abril de 2026 + +## 1. Logros Alcanzados en la Sesión +* **Corrección de la Regla 26**: Se identificó y corrigió la lógica en el backend y en el documento de reglas. Oficialmente: `tipo_cuenta = 1` (Urbano) y `tipo_cuenta = 0` (Rural). +* **Saneamiento Nacional Masivo**: Se actualizó la tabla maestra soberana (`public.snc_raw_lotes_activos`) en el servidor **.123**. Se recalcularon los campos `snc_cuenta` para más de 3 millones de registros bajo la lógica correcta. +* **Impacto en Oviedo (505)**: + * De 26,882 lotes totales, pasamos a tener **18,804 cuentas catastrales correctamente pobladas** (14,719 Urbanos y 12,163 Rurales). + * Los lotes vinculados con éxito (en color) subieron a **10,474**. +* **Integridad Visual**: La capa base de lotes grises (`e505_lotes_activos`) se mantiene intacta mostrando toda la cartografía territorial, lista para la Landing Page. + +## 2. Estado Actual y Motivo de Pausa +La vinculación (`JOIN`) no alcanza al 100% de los lotes urbanos debido a una discrepancia de formatos detectada: +* **El SNC (Regla 26)** extrae la base de la cuenta. Ejemplo: **`101`**. +* **El FDW (Municipalidad)** tiene registrado el inmueble con guiones. Ejemplo: `21-0001-01`. Al limpiarlo con `REPLACE(..., '-', '')`, queda: **`21000101`**. +* **El Fallo**: Como `101` no es igual a `21000101`, el vínculo no se produce para miles de lotes, quedando en gris a pesar de existir la deuda. + +## 3. Próximos Pasos (Próxima Sesión) +* Queda pendiente investigar y definir cómo abordar el prefijo (ej. `"21"`) y los ceros de relleno que la municipalidad (FDW) usa, sin violar la restricción de no usar columnas fuera de las reglas establecidas. +* El usuario retomará el análisis de por qué falta este "pegamento" en la vinculación para proponer la solución conceptual definitiva. diff --git a/apply_master_join_505.sql b/apply_master_join_505.sql new file mode 100644 index 0000000..39b6b97 --- /dev/null +++ b/apply_master_join_505.sql @@ -0,0 +1,18 @@ +-- Reconstrucción de la Vista 505 según el SQL Maestro Definido +DROP VIEW IF EXISTS public.vw_lotes_morosidad_505 CASCADE; + +CREATE OR REPLACE VIEW public.vw_lotes_morosidad_505 AS +SELECT + lot.*, + liq.inm_ficha, + liq.inm_ctacatastral, + liq.trb_tributo, + liq.trb_total_deuda, + liq.trb_total_pago, + liq.ultimo_pago +FROM public.e505_lotes_activos lot +LEFT JOIN fdw_505.v_liq_entidad_totalxobjeto liq ON lot.snc_cuenta = REPLACE(liq.inm_ctacatastral, '-', ''); + +-- Auditoría de Éxito de Coloreado (Con la nueva lógica de REPLACE) +SELECT count(*) as total_lotes, count(inm_ficha) as lotes_con_deuda_coloreados +FROM public.vw_lotes_morosidad_505; diff --git a/check_505.sql b/check_505.sql new file mode 100644 index 0000000..4f5f106 --- /dev/null +++ b/check_505.sql @@ -0,0 +1 @@ +SELECT * FROM public.snc_catalog_mapping WHERE entidad_id = '505'; diff --git a/check_bounds_505.sql b/check_bounds_505.sql new file mode 100644 index 0000000..854ea89 --- /dev/null +++ b/check_bounds_505.sql @@ -0,0 +1,3 @@ +SELECT entidad, boundno, boundse, latlong, zoom +FROM public.entidades +WHERE entidad = 505; diff --git a/check_cols_505.sql b/check_cols_505.sql new file mode 100644 index 0000000..ccd6bb9 --- /dev/null +++ b/check_cols_505.sql @@ -0,0 +1,4 @@ +SELECT column_name, data_type +FROM information_schema.columns +WHERE table_name = 'e505_lotes_activos' +AND table_schema = 'public'; diff --git a/check_count_505.sql b/check_count_505.sql new file mode 100644 index 0000000..14c11db --- /dev/null +++ b/check_count_505.sql @@ -0,0 +1 @@ +SELECT tipo_cuenta, count(*) FROM public.e505_lotes_activos GROUP BY tipo_cuenta; diff --git a/check_map_colors_505.sql b/check_map_colors_505.sql new file mode 100644 index 0000000..9667171 --- /dev/null +++ b/check_map_colors_505.sql @@ -0,0 +1,5 @@ +SELECT + count(*) as total_geometrias, + count(inm_ficha) as lotes_con_deuda_coloreados, + round(count(inm_ficha)::numeric / count(*)::numeric * 100, 2) as cobertura_mapa +FROM public.vw_lotes_morosidad_505; diff --git a/check_master_integrity.sql b/check_master_integrity.sql new file mode 100644 index 0000000..478af17 --- /dev/null +++ b/check_master_integrity.sql @@ -0,0 +1,10 @@ +-- Verificación de atributos en la tabla maestra +SELECT + dpto, + dist, + count(*) as total, + count(NULLIF(snc_cuenta, '')) as con_cuenta_real, + count(NULLIF(ccatastral, '')) as con_catastro +FROM public.snc_raw_lotes_activos +WHERE dpto = 'F' AND dist = 1 +GROUP BY dpto, dist; diff --git a/check_migration_progress.sql b/check_migration_progress.sql new file mode 100644 index 0000000..688f977 --- /dev/null +++ b/check_migration_progress.sql @@ -0,0 +1 @@ +SELECT count(*) as total_lotes_locales FROM public.snc_raw_lotes_activos; diff --git a/check_oviedo_local_load.sql b/check_oviedo_local_load.sql new file mode 100644 index 0000000..a86df34 --- /dev/null +++ b/check_oviedo_local_load.sql @@ -0,0 +1 @@ +SELECT count(*) as total_lotes_oviedo FROM public.e505_lotes_activos; diff --git a/check_oviedo_mapping.sql b/check_oviedo_mapping.sql new file mode 100644 index 0000000..4f5f106 --- /dev/null +++ b/check_oviedo_mapping.sql @@ -0,0 +1 @@ +SELECT * FROM public.snc_catalog_mapping WHERE entidad_id = '505'; diff --git a/check_view_def.sql b/check_view_def.sql new file mode 100644 index 0000000..4798b99 --- /dev/null +++ b/check_view_def.sql @@ -0,0 +1 @@ +SELECT pg_get_viewdef('public.vw_lotes_morosidad_505', true); diff --git a/check_vinculacion_505.sql b/check_vinculacion_505.sql new file mode 100644 index 0000000..6a61296 --- /dev/null +++ b/check_vinculacion_505.sql @@ -0,0 +1,6 @@ +-- Diagnóstico de Vinculación (Entidad 505) +SELECT + count(*) as total_registros, + count(inm_ficha) as vinculados, + round(count(inm_ficha)::numeric / count(*)::numeric * 100, 2) as porcentaje_exito +FROM public.vw_lotes_morosidad_505; diff --git a/count_505.sql b/count_505.sql new file mode 100644 index 0000000..2ed4875 --- /dev/null +++ b/count_505.sql @@ -0,0 +1,2 @@ +SELECT count(*) as total_lotes +FROM public.e505_lotes_activos; diff --git a/create_master_snc_table.sql b/create_master_snc_table.sql new file mode 100644 index 0000000..cb904f7 --- /dev/null +++ b/create_master_snc_table.sql @@ -0,0 +1,41 @@ +-- Creación de Tabla Maestra Nacional de Lotes SNC +DROP TABLE IF EXISTS public.snc_raw_lotes_activos CASCADE; + +CREATE TABLE public.snc_raw_lotes_activos ( + sigem_id SERIAL PRIMARY KEY, + id integer, + objectid numeric, + id_parcela numeric, + dpto text, + dist smallint, + padron integer, + zona integer, + mz integer, + lote integer, + finca integer, + nro_matricula text, + ccatastral text, + obs text, + mz_agr text, + lote_agr text, + tipo_pavim text, + tipo_cuenta smallint, + hectareas numeric(20,6), + superficie_tierra numeric(20,6), + superficie_edificado numeric(20,6), + valor_tierra numeric(20,2), + valor_edificado numeric(20,2), + tipo smallint, + referencia smallint, + clave_comparacion text, + snc_cuenta text, + ccc text, + geom geometry(MultiPolygon, 4326), + fecha_ingesta timestamp default now() +); + +-- Índices de Performance +CREATE INDEX idx_snc_raw_geom ON public.snc_raw_lotes_activos USING GIST(geom); +CREATE INDEX idx_snc_raw_ccatastral ON public.snc_raw_lotes_activos(ccatastral); +CREATE INDEX idx_snc_raw_ubica ON public.snc_raw_lotes_activos(dpto, dist); +CREATE INDEX idx_snc_raw_ccc ON public.snc_raw_lotes_activos(ccc); diff --git a/debug_join_precision.sql b/debug_join_precision.sql new file mode 100644 index 0000000..d1c70b1 --- /dev/null +++ b/debug_join_precision.sql @@ -0,0 +1,19 @@ +-- Comparativa técnica de claves para asegurar el JOIN +SELECT + 'LOCAL' as origen, + mz, + lote, + snc_cuenta, + ccatastral, + ('21-' || LPAD(mz::text, 4, '0') || '-' || LPAD(lote::text, 2, '0')) as clave_generada +FROM public.e505_lotes_activos +WHERE mz IS NOT NULL AND lote IS NOT NULL +LIMIT 5; + +SELECT + 'REMOTO (FDW)' as origen, + inm_ctacatastral, + REPLACE(inm_ctacatastral, '-', '') as limpia_fdw, + inm_ficha +FROM fdw_505.v_liq_entidad_totalxobjeto +LIMIT 5; diff --git a/deduplicate_master_table.sql b/deduplicate_master_table.sql new file mode 100644 index 0000000..6299ec7 --- /dev/null +++ b/deduplicate_master_table.sql @@ -0,0 +1,10 @@ +-- Limpieza de Duplicados en Tabla Maestra Nacional +DELETE FROM public.snc_raw_lotes_activos a +USING public.snc_raw_lotes_activos b +WHERE a.sigem_id > b.sigem_id + AND a.id_snc = b.id_snc + AND a.ccatastral = b.ccatastral; + +-- Verificación Final tras limpieza +SELECT count(*) as total_lotes_unicos FROM public.snc_raw_lotes_activos; +SELECT count(*) as total_oviedo_unico FROM public.snc_raw_lotes_activos WHERE dpto = 'F' AND dist = 1; diff --git a/diagnostic_split.sql b/diagnostic_split.sql new file mode 100644 index 0000000..6d6f6cf --- /dev/null +++ b/diagnostic_split.sql @@ -0,0 +1,27 @@ +-- 1. MUESTRA URBANA (tipo_cuenta = 0) +-- Buscamos 5 lotes sin vínculo para ver por qué no cruzan +SELECT + 'URBANO (SNC)' as status, + id_snc, tipo_cuenta, snc_cuenta, ccatastral, mz, lote +FROM public.e505_lotes_activos +WHERE tipo_cuenta = 0 AND (snc_cuenta IS NOT NULL AND snc_cuenta != '') +LIMIT 5; + +-- 2. MUESTRA RURAL (tipo_cuenta = 1) +-- Buscamos 5 lotes sin vínculo +SELECT + 'RURAL (SNC)' as status, + id_snc, tipo_cuenta, snc_cuenta, padron, finca +FROM public.e505_lotes_activos +WHERE tipo_cuenta = 1 AND (snc_cuenta IS NOT NULL AND snc_cuenta != '') +LIMIT 5; + +-- 3. MUESTRA FDW (Para comparar formatos) +SELECT + 'FDW (Tributario)' as status, + inm_ctacatastral, + REPLACE(inm_ctacatastral, '-', '') as limpio, + inm_ficha +FROM fdw_505.v_liq_entidad_totalxobjeto +ORDER BY inm_ctacatastral +LIMIT 10; diff --git a/fdw_inm_audit.sql b/fdw_inm_audit.sql new file mode 100644 index 0000000..40b2718 --- /dev/null +++ b/fdw_inm_audit.sql @@ -0,0 +1,28 @@ +-- 1. ESTADÍSTICA DE POBLACIÓN (Solo 'INM') +SELECT + count(*) as total_inm, + count(NULLIF(inm_ctacatastral, '')) as con_cuenta, + count(CASE WHEN inm_ctacatastral LIKE '%-%' THEN 1 END) as con_guiones, + count(CASE WHEN inm_ctacatastral NOT LIKE '%-%' AND inm_ctacatastral != '' THEN 1 END) as sin_guiones +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE trb_tributo = 'INM'; + +-- 2. MUESTRA DE FORMATOS (Solo 'INM') +SELECT + 'INM - CON GUIONES' as categoria, + inm_ctacatastral, + REPLACE(inm_ctacatastral, '-', '') as limpia, + inm_ ficha, + trb_total_deuda +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE trb_tributo = 'INM' AND inm_ctacatastral LIKE '%-%' +LIMIT 5; + +SELECT + 'INM - SIN GUIONES' as categoria, + inm_ctacatastral, + inm_ficha, + trb_total_deuda +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE trb_tributo = 'INM' AND inm_ctacatastral NOT LIKE '%-%' AND inm_ctacatastral != '' +LIMIT 5; diff --git a/fdw_inm_dash_sample.sql b/fdw_inm_dash_sample.sql new file mode 100644 index 0000000..bcae81f --- /dev/null +++ b/fdw_inm_dash_sample.sql @@ -0,0 +1,10 @@ +-- MUESTRA DE FORMATOS CON GUIONES (Solo 'INM') +SELECT + 'INM - CON GUIONES' as categoria, + inm_ctacatastral, + REPLACE(inm_ctacatastral, '-', '') as limpia, + inm_ficha, + trb_total_deuda +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE trb_tributo = 'INM' AND inm_ctacatastral LIKE '%-%' +LIMIT 10; diff --git a/fdw_quality_audit.sql b/fdw_quality_audit.sql new file mode 100644 index 0000000..062fbb4 --- /dev/null +++ b/fdw_quality_audit.sql @@ -0,0 +1,26 @@ +-- 1. ESTADÍSTICA DE POBLACIÓN DE CUENTAS EN FDW 505 +SELECT + count(*) as total_registros_fdw, + count(NULLIF(inm_ctacatastral, '')) as con_cuenta_poblada, + (count(*) - count(NULLIF(inm_ctacatastral, ''))) as vacios_o_nulos +FROM fdw_505.v_liq_entidad_totalxobjeto; + +-- 2. MUESTRA DE FORMATOS EXISTENTES EN FDW +-- Registros con guiones +SELECT + 'CON GUIONES' as categoria, + inm_ctacatastral, + REPLACE(inm_ctacatastral, '-', '') as limpia, + inm_ficha +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE inm_ctacatastral LIKE '%-%' +LIMIT 5; + +-- Registros sin guiones (números puros) +SELECT + 'SIN GUIONES' as categoria, + inm_ctacatastral, + inm_ficha +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE inm_ctacatastral NOT LIKE '%-%' AND inm_ctacatastral != '' +LIMIT 5; diff --git a/final_audit_505.sql b/final_audit_505.sql new file mode 100644 index 0000000..67a43db --- /dev/null +++ b/final_audit_505.sql @@ -0,0 +1,5 @@ +-- Validación Final de Integridad (Regla 23 + Regla 26) +SELECT ccc, inm_ficha, trb_total_deuda +FROM public.vw_lotes_morosidad_505 +WHERE trb_total_deuda > 0 +LIMIT 10; diff --git a/final_binary_audit.sql b/final_binary_audit.sql new file mode 100644 index 0000000..af386d9 --- /dev/null +++ b/final_binary_audit.sql @@ -0,0 +1,8 @@ +-- Auditoría de "Match" Real (Regla 23 vs Regla 26) +SELECT 'LOCAL (SNC)' as fuente, snc_cuenta, ccatastral +FROM public.e505_lotes_activos +LIMIT 5; + +SELECT 'REMOTO (MUNICIPIO)' as fuente, inm_ctacatastral, REPLACE(inm_ctacatastral, '-', '') as normalizada_remota +FROM fdw_505.v_liq_entidad_totalxobjeto +LIMIT 5; diff --git a/final_integrity_check.sql b/final_integrity_check.sql new file mode 100644 index 0000000..949a8f6 --- /dev/null +++ b/final_integrity_check.sql @@ -0,0 +1 @@ +SELECT count(*) as total, count(NULLIF(snc_cuenta, '')) as con_cuenta FROM public.e505_lotes_activos; diff --git a/final_logic_verification.sql b/final_logic_verification.sql new file mode 100644 index 0000000..cd37925 --- /dev/null +++ b/final_logic_verification.sql @@ -0,0 +1,13 @@ +SELECT + 'e505_lotes_activos' as tabla, + count(*) as total, + count(NULLIF(snc_cuenta, '')) as con_cuenta, + count(CASE WHEN tipo_cuenta = 1 THEN 1 END) as urbanos, + count(CASE WHEN tipo_cuenta = 0 THEN 1 END) as rurales +FROM public.e505_lotes_activos; + +-- Comprobar si ahora el JOIN tiene más éxito +SELECT + count(*) as total_vinculados_con_color +FROM public.e505_lotes_activos lot +JOIN fdw_505.v_liq_entidad_totalxobjeto liq ON lot.snc_cuenta = REPLACE(liq.inm_ctacatastral, '-', ''); diff --git a/find_dist_21.sql b/find_dist_21.sql new file mode 100644 index 0000000..e4e89e2 --- /dev/null +++ b/find_dist_21.sql @@ -0,0 +1,2 @@ +-- Buscar el distrito 21 en todo el SNC +SELECT * FROM public.snc_catalog_mapping WHERE dist_snc = '21'; diff --git a/find_oviedo_real_codes.sql b/find_oviedo_real_codes.sql new file mode 100644 index 0000000..95d3f90 --- /dev/null +++ b/find_oviedo_real_codes.sql @@ -0,0 +1,5 @@ +-- Búsqueda de códigos reales para Coronel Oviedo +SELECT * FROM public.snc_catalog_mapping +WHERE snc_nom_dist ILIKE '%OVIEDO%' + OR dist_snc = '21' + OR (dpto_snc = '5' AND dist_snc = '1'); diff --git a/fix_ccc_505.sql b/fix_ccc_505.sql new file mode 100644 index 0000000..f95e6fc --- /dev/null +++ b/fix_ccc_505.sql @@ -0,0 +1,12 @@ +-- Saneamiento Masivo de Claves (Regla 26) para Coronel Oviedo (505) +UPDATE public.e505_lotes_activos +SET ccc = CASE + WHEN tipo_cuenta = 0 THEN LTRIM(REPLACE(SUBSTRING(ccatastral FROM 4), '-', ''), '0') + WHEN tipo_cuenta = 1 THEN padron + ELSE snc_cuenta +END; + +-- Verificación de Calidad (Comparativa de match) +SELECT 'AUDITORIA' as status, ccc, ccatastral, snc_cuenta +FROM public.e505_lotes_activos +LIMIT 5; diff --git a/fix_master_table_columns.sql b/fix_master_table_columns.sql new file mode 100644 index 0000000..1048b35 --- /dev/null +++ b/fix_master_table_columns.sql @@ -0,0 +1,3 @@ +-- Ajuste de nombres de columnas para compatibilidad con el motor de ingesta masiva +ALTER TABLE public.snc_raw_lotes_activos RENAME COLUMN id TO id_snc; +ALTER TABLE public.snc_raw_lotes_activos RENAME COLUMN tipo TO tipo_parcela; diff --git a/fix_regla26_perfected.sql b/fix_regla26_perfected.sql new file mode 100644 index 0000000..2e6a7ed --- /dev/null +++ b/fix_regla26_perfected.sql @@ -0,0 +1,15 @@ +-- Saneamiento Definitivo según Regla 26 (Perfected) +UPDATE public.e505_lotes_activos +SET snc_cuenta = LTRIM(REGEXP_REPLACE(SUBSTRING(ccatastral FROM 4), '[^a-zA-Z0-9]', '', 'g'), '0'), + ccc = LTRIM(REGEXP_REPLACE(SUBSTRING(ccatastral FROM 4), '[^a-zA-Z0-9]', '', 'g'), '0') +WHERE tipo_cuenta = 0; + +UPDATE public.e505_lotes_activos +SET snc_cuenta = padron, + ccc = padron +WHERE tipo_cuenta = 1; + +-- Auditoría de Coincidencia +SELECT ccatastral, snc_cuenta as cuenta_normalizada, ccc as join_key +FROM public.e505_lotes_activos +LIMIT 10; diff --git a/full_record_audit.sql b/full_record_audit.sql new file mode 100644 index 0000000..0339c44 --- /dev/null +++ b/full_record_audit.sql @@ -0,0 +1,10 @@ +-- Extracción de Ficha Completa (Entidad 505) +( + SELECT 'URBANA' as origen_audit, *, ST_AsText(ST_Centroid(geom)) as centroide_text + FROM public.e505_lotes_activos WHERE tipo_cuenta = 0 LIMIT 5 +) +UNION ALL +( + SELECT 'RURAL' as origen_audit, *, ST_AsText(ST_Centroid(geom)) as centroide_text + FROM public.e505_lotes_activos WHERE tipo_cuenta = 1 LIMIT 5 +); diff --git a/full_record_dump.sql b/full_record_dump.sql new file mode 100644 index 0000000..cd42751 --- /dev/null +++ b/full_record_dump.sql @@ -0,0 +1,2 @@ +-- Volcado vertical completo de los 10 registros de prueba +SELECT * FROM public.e505_lotes_activos ORDER BY tipo_cuenta ASC, id ASC; diff --git a/get_bounds_master.sql b/get_bounds_master.sql new file mode 100644 index 0000000..841a872 --- /dev/null +++ b/get_bounds_master.sql @@ -0,0 +1,3 @@ +SELECT * FROM dblink('host=192.168.1.254 user=postgres password=x25yvaga2017 dbname=sigemweb', + 'SELECT entidad, boundno, boundse, latlong, zoom FROM public.entidades WHERE entidad = 505') +AS t(entidad int, boundno text, boundse text, latlong text, zoom text); diff --git a/national_sovereign_fix.sql b/national_sovereign_fix.sql new file mode 100644 index 0000000..3ad1aa6 --- /dev/null +++ b/national_sovereign_fix.sql @@ -0,0 +1,25 @@ +-- RECALCULO MASIVO NACIONAL (REGLA 26 ACTUALIZADA) +-- 1 = URBANO (Catastro Substring 4) +-- 0 = RURAL (Padron Puro) + +UPDATE public.snc_raw_lotes_activos +SET + snc_cuenta = CASE + WHEN tipo_cuenta = 1 THEN LTRIM(REGEXP_REPLACE(SUBSTRING(ccatastral, 4), '[^a-zA-Z0-9]', '', 'g'), '0') + WHEN tipo_cuenta = 0 THEN padron::text + ELSE snc_cuenta + END, + ccc = CASE + WHEN tipo_cuenta = 1 THEN LTRIM(REGEXP_REPLACE(SUBSTRING(ccatastral, 4), '[^a-zA-Z0-9]', '', 'g'), '0') + WHEN tipo_cuenta = 0 THEN padron::text + ELSE ccc + END +WHERE tipo_cuenta IN (0, 1); + +-- Auditoria de Poblacion Nacional Post-Saneamiento +SELECT + tipo_cuenta, + count(*) as total, + count(NULLIF(snc_cuenta, '')) as con_cuenta +FROM public.snc_raw_lotes_activos +GROUP BY tipo_cuenta; diff --git a/nature_of_lots_audit.sql b/nature_of_lots_audit.sql new file mode 100644 index 0000000..949f9cb --- /dev/null +++ b/nature_of_lots_audit.sql @@ -0,0 +1,11 @@ +-- AUDITORIA DE NATURALEZA DE LOTE (SNC) +SELECT + tipo_cuenta, + count(*) as total_registros, + count(NULLIF(padron::text, '')) as con_padron, + count(NULLIF(ccatastral, '')) as con_catastro, + count(CASE WHEN (padron IS NULL OR padron::text = '') AND (ccatastral IS NOT NULL AND ccatastral != '') THEN 1 END) as solo_catastro_prob_urbano, + count(CASE WHEN (padron IS NOT NULL AND padron::text != '') AND (ccatastral IS NULL OR ccatastral = '') THEN 1 END) as solo_padron_prob_rural +FROM public.snc_raw_lotes_activos +WHERE dpto = 'F' AND dist = 1 +GROUP BY tipo_cuenta; diff --git a/optimize_master_table.sql b/optimize_master_table.sql new file mode 100644 index 0000000..fd9d4c4 --- /dev/null +++ b/optimize_master_table.sql @@ -0,0 +1,3 @@ +-- Optimización Post-Ingesta Nacional +CREATE INDEX IF NOT EXISTS idx_snc_raw_snc_cuenta ON public.snc_raw_lotes_activos(snc_cuenta); +VACUUM ANALYZE public.snc_raw_lotes_activos; diff --git a/peek_fdw_keys.sql b/peek_fdw_keys.sql new file mode 100644 index 0000000..1d1d271 --- /dev/null +++ b/peek_fdw_keys.sql @@ -0,0 +1,4 @@ +-- Muestra de claves catastrales en la base de datos de deudas (FDW) +SELECT inm_ctacatastral, inm_ficha +FROM fdw_505.v_liq_entidad_totalxobjeto +LIMIT 10; diff --git a/peek_success_matches.sql b/peek_success_matches.sql new file mode 100644 index 0000000..ba25df0 --- /dev/null +++ b/peek_success_matches.sql @@ -0,0 +1,10 @@ +-- Casos donde el JOIN del usuario SI funciona +SELECT + lot.snc_cuenta, + liq.inm_ctacatastral, + REPLACE(liq.inm_ctacatastral, '-', '') as match_db, + lot.mz, + lot.lote +FROM public.e505_lotes_activos lot +JOIN fdw_505.v_liq_entidad_totalxobjeto liq ON lot.snc_cuenta = REPLACE(liq.inm_ctacatastral, '-', '') +LIMIT 5; diff --git a/query_raw_districts.sql b/query_raw_districts.sql new file mode 100644 index 0000000..07d1265 --- /dev/null +++ b/query_raw_districts.sql @@ -0,0 +1,3 @@ +-- Consulta del Registro Crudo en el Departamento F +SELECT * FROM public.snc_raw_distritos WHERE cod_dpto = 'E' AND codigo = 'E01'; +SELECT * FROM public.snc_raw_distritos WHERE cod_dpto = 'F' AND codigo = 'F01'; diff --git a/recalculate_snc_cuenta.sql b/recalculate_snc_cuenta.sql new file mode 100644 index 0000000..9cf7aeb --- /dev/null +++ b/recalculate_snc_cuenta.sql @@ -0,0 +1,20 @@ +-- Simulación de Recálculo snc_cuenta (Regla 26) para Zona Urbana y Rural +( + SELECT + 'URBANA (0)' as zona, + ccatastral as entrada_cruda, + LTRIM(REGEXP_REPLACE(SUBSTRING(ccatastral FROM 4), '[^a-zA-Z0-9]', '', 'g'), '0') as snc_cuenta_recalculada + FROM public.e505_lotes_activos + WHERE tipo_cuenta = 0 + LIMIT 5 +) +UNION ALL +( + SELECT + 'RURAL (1)' as zona, + padron as entrada_cruda, + padron::text as snc_cuenta_recalculada + FROM public.e505_lotes_activos + WHERE tipo_cuenta = 1 + LIMIT 5 +); diff --git a/show_discrepancies.sql b/show_discrepancies.sql new file mode 100644 index 0000000..7d1c887 --- /dev/null +++ b/show_discrepancies.sql @@ -0,0 +1,10 @@ +-- 10 Registros de la Cartografía Local (SNC) +SELECT 'LOCAL_SNC' as fuente, ccatastral, snc_cuenta, ccc +FROM public.e505_lotes_activos +LIMIT 10; + +-- 10 Registros del Sistema Municipal (SIGEM) +SELECT 'REMOTO_SIGEM' as fuente, inm_ctacatastral, REPLACE(inm_ctacatastral, '-', '') as join_esperado, trb_total_deuda +FROM fdw_505.v_liq_entidad_totalxobjeto +WHERE trb_total_deuda > 0 +LIMIT 10; diff --git a/show_mapping_505.sql b/show_mapping_505.sql new file mode 100644 index 0000000..4f5f106 --- /dev/null +++ b/show_mapping_505.sql @@ -0,0 +1 @@ +SELECT * FROM public.snc_catalog_mapping WHERE entidad_id = '505'; diff --git a/src/main/java/com/sigem/gis/WebConfig.java b/src/main/java/com/sigem/gis/WebConfig.java index 7861ef1..4c3341d 100644 --- a/src/main/java/com/sigem/gis/WebConfig.java +++ b/src/main/java/com/sigem/gis/WebConfig.java @@ -14,6 +14,8 @@ public class WebConfig implements WebMvcConfigurer { // Enrutador amigable (Friendly URLs sin ".html") para el Frontend registry.addViewController("/login").setViewName("forward:/login.html"); registry.addViewController("/mapas").setViewName("forward:/mapas.html"); + registry.addViewController("/landing").setViewName("forward:/landing.html"); + registry.addViewController("/widgets").setViewName("forward:/widgets.html"); registry.addViewController("/").setViewName("forward:/login.html"); } diff --git a/src/main/java/com/sigem/gis/controller/SncImportController.java b/src/main/java/com/sigem/gis/controller/SncImportController.java index b8daaeb..4662181 100644 --- a/src/main/java/com/sigem/gis/controller/SncImportController.java +++ b/src/main/java/com/sigem/gis/controller/SncImportController.java @@ -3,6 +3,7 @@ package com.sigem.gis.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.util.*; @@ -511,7 +512,7 @@ public class SncImportController { System.out.println(String.format("[%d/%d] PROCESANDO ENTIDAD %s (SNC: %s-%s)", current, total, eid, dpto, dist)); try { - importDistrict(eid, dpto, dist, false); + importDistrict(eid, dpto, dist, 0, false, false); } catch (Exception e) { System.err.println("!!! FALLO CRÍTICO EN ENTIDAD " + eid + ": " + e.getMessage()); } @@ -526,20 +527,36 @@ public class SncImportController { } @GetMapping("/snc/{entityId}/{dpto}/{dist}") - public String importDistrict( + public ResponseEntity importDistrict( @PathVariable String entityId, @PathVariable String dpto, @PathVariable String dist, - @RequestParam(defaultValue = "true") boolean processFdw) { + @RequestParam(defaultValue = "0") int limit, + @RequestParam(defaultValue = "true") boolean processFdw, + @RequestParam(defaultValue = "false") boolean forceWfs) { + + String tableName = "public.e" + entityId + "_lotes_activos"; + createSncTableIfNotExists(tableName); + + int batchSize = 0; + if (forceWfs) { + // RECUPERACIÓN: Importación desde WFS directamente a la Maestra y luego al municipio + batchSize = importFromWfs(tableName, dpto, dist, limit); + } else { + // OPERACIÓN NORMAL: Transferencia local soberana + batchSize = importDistrictInternal(tableName, dpto, dist, limit); + } + + if (batchSize > 0 && processFdw) { + processFdwAndViews(entityId); + } + + return ResponseEntity.ok("OK: Importación " + entityId + " finalizada. Registros: " + batchSize); + } + private int importFromWfs(String tableName, String dpto, String dist, int limit) { try { - String tableName = "public.e" + entityId + "_lotes_activos"; - - // 1. Garantizar existencia de la tabla e Iniciar Limpieza - createSncTableIfNotExists(tableName); - gisJdbcTemplate.execute("TRUNCATE TABLE " + tableName + " CASCADE"); - - // 2. Construcción Robusta de URL (Regla 28) + System.out.println(">>> RECUPERACIÓN WFS: " + dpto + "-" + dist + " -> Activos y Maestra"); String url = org.springframework.web.util.UriComponentsBuilder .fromHttpUrl("https://www.catastro.gov.py/geoserver/ows") .queryParam("service", "WFS") @@ -553,90 +570,137 @@ public class SncImportController { .toUriString(); Map response = restTemplate.getForObject(url, Map.class); - if (response == null) { - System.err.println("!!! Respuesta NULL para Entidad " + entityId); - return "ERR: Respuesta NULL del SNC"; - } + if (response == null) return 0; List> features = (List>) response.get("features"); - if (features == null || features.isEmpty()) { - System.out.println("--- CERO CARGAS para " + entityId + " (Filtro: " + dpto + "-" + dist + ")"); - return "WARN: No se encontraron registros"; - } + if (features == null || features.isEmpty()) return 0; com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); List batchArgs = new ArrayList<>(); for (Map feature : features) { Map props = (Map) feature.get("properties"); - - // Intento resiliente de captura de geometría (Regla 28) - Object shapeObj = props.get("shape"); - if (shapeObj == null) { - shapeObj = feature.get("geometry"); - } - - String ccatastral = (String) props.get("ccatastral"); Integer tc = (Integer) props.get("tipo_cuenta"); + Object shapeObj = props.get("shape") != null ? props.get("shape") : feature.get("geometry"); + String ccatastral = (String) props.get("ccatastral"); Object padronObj = props.get("padron"); String padronStr = padronObj != null ? String.valueOf(padronObj) : ""; - // REGLA 26: Normalización Universal de Cartografía SNC (Actualizada) String snc_cuenta = ""; - if (tc != null && tc == 0) { - // 1. Zona Urbana (tipo_cuenta = 0): Substring(ccatastral, 4) eliminando ceros + if (tc != null && tc == 1) { + // URBANO (tipo_cuenta = 1): Substring(ccatastral, 4) if (ccatastral != null && ccatastral.length() >= 4) { - snc_cuenta = ccatastral.substring(3).replaceAll("^0+", "").replaceAll("[^a-zA-Z0-9]", ""); - } else if (!padronStr.isEmpty()) { - snc_cuenta = padronStr.replaceAll("^0+", ""); + String cleaned = ccatastral.substring(3).replaceAll("[^a-zA-Z0-9]", ""); + snc_cuenta = cleaned.replaceFirst("^0+", ""); } - } else if (tc != null && tc == 1) { - // 2. Zona Rural (tipo_cuenta = 1): padron::text (sin modificaciones) + } else if (tc != null && tc == 0) { + // RURAL (tipo_cuenta = 0): Padron snc_cuenta = padronStr; } try { - String geomJson = mapper.writeValueAsString(shapeObj); - if (shapeObj == null) - continue; - batchArgs.add(new Object[] { - props.get("id"), props.get("objectid"), props.get("id_parcela"), - props.get("dpto"), props.get("dist"), props.get("padron"), - props.get("zona"), props.get("mz"), props.get("lote"), - props.get("finca"), props.get("nro_matricula"), - ccatastral, props.get("obs"), - props.get("mz_agr"), props.get("lote_agr"), - props.get("tipo_pavim"), tc, - props.get("hectareas"), props.get("superficie_tierra"), - props.get("superficie_edificado"), props.get("valor_tierra"), - props.get("valor_edificado"), props.get("tipo"), - props.get("referencia"), props.get("clave_comparacion"), - geomJson, snc_cuenta, ccatastral + props.get("id"), props.get("objectid"), props.get("id_parcela"), + props.get("dpto"), props.get("dist"), props.get("padron"), + props.get("zona"), props.get("mz"), props.get("lote"), + props.get("finca"), props.get("nro_matricula"), + ccatastral, props.get("obs"), + props.get("mz_agr"), props.get("lote_agr"), + props.get("tipo_pavim"), tc, + props.get("hectareas"), props.get("superficie_tierra"), + props.get("superficie_edificado"), props.get("valor_tierra"), + props.get("valor_edificado"), props.get("tipo"), + props.get("referencia"), props.get("clave_comparacion"), + mapper.writeValueAsString(shapeObj), snc_cuenta, snc_cuenta }); - } catch (Exception e) { - } + } catch (Exception e) {} } - String insertSql = "INSERT INTO " + tableName + " (" + - "id_snc, objectid, id_parcela, dpto, dist, padron, zona, mz, lote, " + - "finca, nro_matricula, ccatastral, obs, mz_agr, lote_agr, tipo_pavim, " + - "tipo_cuenta, hectareas, superficie_tierra, superficie_edificado, " + - "valor_tierra, valor_edificado, tipo_parcela, referencia, clave_comparacion, " + - "geom, snc_cuenta, ccc) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_SetSRID(ST_GeomFromGeoJSON(?), 4326)), 3)), ?, ?)"; + String columns = "id_snc, objectid, id_parcela, dpto, dist, padron, zona, mz, lote, finca, nro_matricula, ccatastral, obs, mz_agr, lote_agr, tipo_pavim, tipo_cuenta, hectareas, superficie_tierra, superficie_edificado, valor_tierra, valor_edificado, tipo_parcela, referencia, clave_comparacion, geom, snc_cuenta, ccc"; + String values = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ST_Multi(ST_CollectionExtract(ST_MakeValid(ST_SetSRID(ST_GeomFromGeoJSON(?), 4326)), 3)), ?, ?"; + + gisJdbcTemplate.execute("DELETE FROM public.snc_raw_lotes_activos WHERE dpto = '" + dpto + "' AND dist = " + dist); + gisJdbcTemplate.batchUpdate("INSERT INTO public.snc_raw_lotes_activos (" + columns + ") VALUES (" + values + ")", batchArgs); + + gisJdbcTemplate.execute("TRUNCATE TABLE " + tableName + " CASCADE"); + gisJdbcTemplate.batchUpdate("INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")", batchArgs); + + return batchArgs.size(); + } catch (Exception e) { e.printStackTrace(); return 0; } + } + + @GetMapping("/snc/import-nacional") + public String importNacional() { + new Thread(() -> { + try { + System.out.println(">>> INICIANDO INGESTA NACIONAL SNC..."); + String sql = "SELECT cod_dpto, cod_dist, nom_dist FROM public.snc_raw_distritos ORDER BY cod_dpto, cod_dist"; + List> distritos = gisJdbcTemplate.queryForList(sql); + + int total = distritos.size(); + int current = 0; + long startAll = System.currentTimeMillis(); - gisJdbcTemplate.batchUpdate(insertSql, batchArgs); - System.out.println("+++ EXITO: " + entityId + " -> Inyectados " + batchArgs.size() + " registros."); + for (Map d : distritos) { + current++; + String dpto = String.valueOf(d.get("cod_dpto")); + String dist = String.valueOf(d.get("cod_dist")); + String nombre = (String) d.get("nom_dist"); - if (processFdw) { - processFdwAndViews(entityId); + System.out.println(String.format("### [%d/%d] PROCESANDO DISTRITO: %s (%s-%s)", + current, total, nombre, dpto, dist)); + + try { + importDistrictInternal("public.snc_raw_lotes_activos", dpto, dist, 0); + } catch (Exception e) { + System.err.println("!!! Error en distrito " + nombre + ": " + e.getMessage()); + } + } + + long duration = (System.currentTimeMillis() - startAll) / 1000 / 60; + System.out.println(">>> INGESTA NACIONAL COMPLETADA EN " + duration + " MINUTOS."); + } catch (Exception e) { + e.printStackTrace(); } + }).start(); + return "OK: Proceso de Ingesta Nacional iniciado en segundo plano. Verifique logs."; + } - return "OK: " + entityId + " (" + features.size() + " recs)"; + private int importDistrictInternal(String tableName, String dpto, String dist, int limit) { + try { + System.out.println(">>> IMPORTACIÓN LOCAL: " + dpto + "-" + dist + " -> " + tableName); + + // 1. Limpiar tabla destino + gisJdbcTemplate.execute("TRUNCATE TABLE " + tableName + " CASCADE"); + + // 2. Transferencia Interna de Alta Velocidad (Regla 26 ya aplicada en Master) + String columns = "id_snc, objectid, id_parcela, dpto, dist, padron, zona, mz, lote, finca, nro_matricula, ccatastral, obs, mz_agr, lote_agr, tipo_pavim, tipo_cuenta, hectareas, superficie_tierra, superficie_edificado, valor_tierra, valor_edificado, tipo_parcela, referencia, clave_comparacion, geom, snc_cuenta, ccc"; + + String sql = "INSERT INTO " + tableName + " (" + columns + ") " + + "SELECT id_snc, objectid, id_parcela, dpto, dist, padron, zona, mz, lote, finca, nro_matricula, ccatastral, obs, mz_agr, lote_agr, tipo_pavim, tipo_cuenta, hectareas, superficie_tierra, superficie_edificado, valor_tierra, valor_edificado, tipo_parcela, referencia, clave_comparacion, geom, snc_cuenta, ccc " + + "FROM public.snc_raw_lotes_activos " + + "WHERE dpto = ? AND dist = ?::smallint"; + + int rows; + if (limit > 0) { + // Modo Calidad Balanceado (5 Urbanos / 5 Rurales) + String limitSql = "INSERT INTO " + tableName + " (" + columns + ") " + + "(SELECT " + columns + " FROM public.snc_raw_lotes_activos WHERE dpto = ? AND dist = ?::smallint AND tipo_cuenta = 0 LIMIT ?) " + + "UNION ALL " + + "(SELECT " + columns + " FROM public.snc_raw_lotes_activos WHERE dpto = ? AND dist = ?::smallint AND tipo_cuenta = 1 LIMIT ?)"; + + int half = limit / 2; + rows = gisJdbcTemplate.update(limitSql, dpto, dist, half, dpto, dist, half); + } else { + rows = gisJdbcTemplate.update(sql, dpto, dist); + } + + System.out.println("+++ EXITO LOCAL: Ubicados " + rows + " registros para " + tableName); + return rows; } catch (Exception e) { - System.err.println("!!! FALLO en Importación de " + entityId + ": " + e.getMessage()); - return "ERR: " + entityId + " -> " + e.getMessage(); + System.err.println("!!! FALLO en Transferencia Local: " + e.getMessage()); + e.printStackTrace(); + return 0; } } @@ -730,7 +794,7 @@ public class SncImportController { entityId, fdwSchema)); gisJdbcTemplate.execute("CREATE OR REPLACE VIEW public.vw_lotes_morosidad_" + entityId + " AS " + - "SELECT lot.*, liq.inm_ficha, liq.inm_ctacatastral, liq.trb_total_deuda, liq.trb_total_pago, liq.ultimo_pago " + "SELECT lot.*, liq.inm_ficha, liq.inm_ctacatastral, liq.trb_tributo, liq.trb_total_deuda, liq.trb_total_pago, liq.ultimo_pago " + "FROM public.e" + entityId + "_lotes_activos lot " + "LEFT JOIN " + fdwSchema diff --git a/src/main/java/com/sigem/gis/security/SecurityConfig.java b/src/main/java/com/sigem/gis/security/SecurityConfig.java index 1a1ea0c..5caf607 100644 --- a/src/main/java/com/sigem/gis/security/SecurityConfig.java +++ b/src/main/java/com/sigem/gis/security/SecurityConfig.java @@ -30,6 +30,7 @@ public class SecurityConfig { .requestMatchers("/api/admin/**").permitAll() // Admin FDW .requestMatchers("/api/gis/**").permitAll() // API Datos GIS (Estadísticas) .requestMatchers("/api/import/**").permitAll() // Importador SNC + .requestMatchers("/api/fdw/**").permitAll() // Orquestación FDW .requestMatchers("/login.html", "/", "/mapas/**", "/mapas.html", "/login", "/error", "/landing", "/landing.html", "/widgets", "/widgets.html").permitAll() .requestMatchers("/mapas_institucional.html").permitAll() .requestMatchers("/css/**", "/js/**", "/img/**", "/vendor/**").permitAll() // Recursos diff --git a/src/main/java/com/sigem/gis/service/FdwService.java b/src/main/java/com/sigem/gis/service/FdwService.java index cd0fbe9..487beeb 100644 --- a/src/main/java/com/sigem/gis/service/FdwService.java +++ b/src/main/java/com/sigem/gis/service/FdwService.java @@ -51,7 +51,8 @@ public class FdwService { String serverName = "srv_mun_" + entidadId; String schemaName = "fdw_" + entidadId; - // 2. Ejecutar comandos DDL en el servidor PostGIS (.123) - RECREACIÓN OBLIGATORIA + // 2. Ejecutar comandos DDL en el servidor PostGIS (.123) - RECREACIÓN + // OBLIGATORIA try { // Recreación del Servidor y Mapeo gisJdbcTemplate.execute("DROP SERVER IF EXISTS " + serverName + " CASCADE"); @@ -74,20 +75,24 @@ public class FdwService { // Vista de Auditoría (MVT) - REGLA 23 String viewLotesName = "vw_lotes_morosidad_" + entidadId; + gisJdbcTemplate.execute("DROP VIEW IF EXISTS public." + viewLotesName + " CASCADE"); gisJdbcTemplate.execute(String.format( "CREATE OR REPLACE VIEW public.%s AS " + - "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + - "FROM %s l " + - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.snc_cuenta = REPLACE(m.inm_ctacatastral, '-', '')", + "SELECT lot.*, liq.inm_ficha, liq.inm_ctacatastral, liq.trb_tributo, liq.trb_total_deuda, liq.trb_total_pago, liq.ultimo_pago " + + + "FROM %s lot " + + "LEFT JOIN %s.v_liq_entidad_totalxobjeto liq ON lot.snc_cuenta = REPLACE(liq.inm_ctacatastral, '-', '')", viewLotesName, tableLotes, schemaName)); // Vista PNG FULL (WMS) - REGLA 23 String viewWmsName = "vw_lotes_wms_" + entidadId; + gisJdbcTemplate.execute("DROP VIEW IF EXISTS public." + viewWmsName + " CASCADE"); gisJdbcTemplate.execute(String.format( "CREATE OR REPLACE VIEW public.%s AS " + - "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + - "FROM %s l " + - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.snc_cuenta = REPLACE(m.inm_ctacatastral, '-', '')", + "SELECT lot.*, liq.inm_ficha, liq.inm_ctacatastral, liq.trb_tributo, liq.trb_total_deuda, liq.trb_total_pago, liq.ultimo_pago " + + + "FROM %s lot " + + "LEFT JOIN %s.v_liq_entidad_totalxobjeto liq ON lot.snc_cuenta = REPLACE(liq.inm_ctacatastral, '-', '')", viewWmsName, tableLotes, schemaName)); // 4. Sincronización con GeoServer diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html index c8b5cfe..6a05e72 100644 --- a/src/main/resources/static/login.html +++ b/src/main/resources/static/login.html @@ -1,97 +1,220 @@ - - - SIGEM GIS - Login - + + + Inicie Sesión - Ecosistema SIGEM + + + + + + + + - -