Commit 0e62dbdbee3d68f508140ae0b1fe85b185429e2d

Authored by Antigravity AI
1 parent 56bd3ec0

Hito: Consolidación Multi-Tenant (Texto Puro y Blindaje SQL)

VERSION.txt
@@ -12,3 +12,4 @@ PROYECTO GIS-GEOSERVER - 2026.04.07.08.18.07 ID DOCKER: d983a409769d. Observacià @@ -12,3 +12,4 @@ PROYECTO GIS-GEOSERVER - 2026.04.07.08.18.07 ID DOCKER: d983a409769d. ObservaciÃ
12 PROYECTO GIS-GEOSERVER - 2026.04.07.20.37.47 ID DOCKER: d983a409769d. Observación: Modernización definitiva del Dashboard completada: Replicación de bloque de bienvenida legado, integración de mapa embebido en modo limpio y reestructuración de la fila inferior de gestión al 100% de ancho. Versión estable validada.Version SIG (Abril 2026) - 2026.04.07.22.30.00 ID DOCKER: d983a409769d. Observación: Éxito en implementación de Login Dinámico (SaaS), integración de logos binarios, eslóganes y responsables desde el servidor .254. 12 PROYECTO GIS-GEOSERVER - 2026.04.07.20.37.47 ID DOCKER: d983a409769d. Observación: Modernización definitiva del Dashboard completada: Replicación de bloque de bienvenida legado, integración de mapa embebido en modo limpio y reestructuración de la fila inferior de gestión al 100% de ancho. Versión estable validada.Version SIG (Abril 2026) - 2026.04.07.22.30.00 ID DOCKER: d983a409769d. Observación: Éxito en implementación de Login Dinámico (SaaS), integración de logos binarios, eslóganes y responsables desde el servidor .254.
13 Version SIG (Abril 2026) - 2026.04.07.23.35.00 ID DOCKER: cb7329596324. Observación: Éxito en la implementación de Estadísticas Reales (formato vertical fdw_X.estadisticas_datos) y actualización forzada de FDW desde el menú Administración. 13 Version SIG (Abril 2026) - 2026.04.07.23.35.00 ID DOCKER: cb7329596324. Observación: Éxito en la implementación de Estadísticas Reales (formato vertical fdw_X.estadisticas_datos) y actualización forzada de FDW desde el menú Administración.
14 Version SIG (Abril 2026) - 2026.04.08.06.45.00 ID DOCKER: cb7329596324. Observación: Inicio de migración a Nomenclatura Alfanumérica Multi-Tenant (e0505_lotes). 14 Version SIG (Abril 2026) - 2026.04.08.06.45.00 ID DOCKER: cb7329596324. Observación: Inicio de migración a Nomenclatura Alfanumérica Multi-Tenant (e0505_lotes).
  15 +Version SIG (Abril 2026) - 2026.04.08.08.15.00 ID DOCKER: cb7329596324. Observación: Consolidación de Arquitectura Multi-Tenant basada en Texto Puro (FDW-TRIM). Blindaje de consultas SQL con filtro de entidad y estandarización alfanumérica de tablas (eXXX_lotes) y vistas (vw_..._XXX). Conexión JOIN optimizada por CCC.
src/main/java/com/sigem/gis/security/AuthController.java
@@ -45,12 +45,15 @@ public class AuthController { @@ -45,12 +45,15 @@ public class AuthController {
45 @PostMapping("/login") 45 @PostMapping("/login")
46 public ResponseEntity<?> login(@RequestBody AuthRequest request) { 46 public ResponseEntity<?> login(@RequestBody AuthRequest request) {
47 try { 47 try {
  48 + // Normalización: Únicamente TRIM para respetar el valor de texto original
  49 + String entidadIdDropdown = request.getEntidad() != null ? request.getEntidad().trim() : "";
  50 +
48 // 1. Validar existencia de entidad en directorio maestro (en el .254) 51 // 1. Validar existencia de entidad en directorio maestro (en el .254)
49 String sqlEntidades = "SELECT sigem_site, sigem_dbname, lat, lng, zoom, minzoom, maxzoom, mapa_base, boundno, boundse, nombre, eslogan, entidad_logo, responsable FROM public.entidades WHERE activo= TRUE AND entidad = ?"; 52 String sqlEntidades = "SELECT sigem_site, sigem_dbname, lat, lng, zoom, minzoom, maxzoom, mapa_base, boundno, boundse, nombre, eslogan, entidad_logo, responsable FROM public.entidades WHERE activo= TRUE AND entidad = ?";
50 - List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(request.getEntidad())); 53 + List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(entidadIdDropdown));
51 54
52 if (entidades.isEmpty()) { 55 if (entidades.isEmpty()) {
53 - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "La MUNICIPALIDAD con código " + request.getEntidad() + " no existe o está inactiva.")); 56 + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "La MUNICIPALIDAD con código " + entidadIdDropdown + " no existe o está inactiva."));
54 } 57 }
55 58
56 Map<String, Object> entidadData = entidades.get(0); 59 Map<String, Object> entidadData = entidades.get(0);
@@ -63,11 +66,10 @@ public class AuthController { @@ -63,11 +66,10 @@ public class AuthController {
63 66
64 String responsable = convertObjectToString(entidadData.get("responsable")); 67 String responsable = convertObjectToString(entidadData.get("responsable"));
65 68
66 - // 2. Asegurar Infraestructura FDW (Regla 16: Solo crea si no existe)  
67 - fdwService.setupFdw(request.getEntidad(), false);  
68 - String schemaName = "fdw_" + request.getEntidad(); 69 + // 2. Se asume infraestructura FDW básica (Regla 16)
  70 + String schemaName = "fdw_" + entidadIdDropdown;
69 71
70 - System.out.println("Validando usuario vía FDW local para entidad: " + request.getEntidad()); 72 + System.out.println("Validando usuario vía FDW local para entidad: " + entidadIdDropdown);
71 73
72 // 3. Buscar Usuario en el esquema FDW local (en el .123) 74 // 3. Buscar Usuario en el esquema FDW local (en el .123)
73 String sqlUser = "SELECT usu_nom, usu_ape, activo, entidad, " + 75 String sqlUser = "SELECT usu_nom, usu_ape, activo, entidad, " +
@@ -84,15 +86,22 @@ public class AuthController { @@ -84,15 +86,22 @@ public class AuthController {
84 86
85 // 4. Validación Final y Generación de Token 87 // 4. Validación Final y Generación de Token
86 if (isActivo && request.getPassword().equals(claveDesencriptada)) { 88 if (isActivo && request.getPassword().equals(claveDesencriptada)) {
87 - // Extraer la Entidad oficial desde el FDW ( character varying 8 ) 89 + // Extraer la Entidad oficial desde el FDW respetando el texto original - Aplicar TRIM()
88 String entidadOficial = convertObjectToString(userData.get("entidad")); 90 String entidadOficial = convertObjectToString(userData.get("entidad"));
89 - if (entidadOficial == null || entidadOficial.trim().isEmpty()) {  
90 - entidadOficial = request.getEntidad(); // Fallback si el campo está vacío, aunque no debería 91 + if (entidadOficial != null) {
  92 + entidadOficial = entidadOficial.trim();
  93 + }
  94 +
  95 + if (entidadOficial == null || entidadOficial.isEmpty()) {
  96 + entidadOficial = entidadIdDropdown;
91 } 97 }
92 98
93 String token = jwtUtil.generateToken(request.getUsername(), entidadOficial); 99 String token = jwtUtil.generateToken(request.getUsername(), entidadOficial);
94 String nombreCompleto = convertObjectToString(userData.get("usu_nom")) + " " + convertObjectToString(userData.get("usu_ape")); 100 String nombreCompleto = convertObjectToString(userData.get("usu_nom")) + " " + convertObjectToString(userData.get("usu_ape"));
95 101
  102 + // 3. ASEGURAR INFRAESTRUCTURA CON IDENTIDAD OFICIAL (Alfanumérica)
  103 + fdwService.setupFdw(entidadOficial, false);
  104 +
96 // Metadatos georreferenciados de la entidad 105 // Metadatos georreferenciados de la entidad
97 Double lat = parseDouble(entidadData.get("lat"), -25.456443); 106 Double lat = parseDouble(entidadData.get("lat"), -25.456443);
98 Double lng = parseDouble(entidadData.get("lng"), -56.446949); 107 Double lng = parseDouble(entidadData.get("lng"), -56.446949);
src/main/java/com/sigem/gis/security/AuthResponse.java
@@ -17,6 +17,7 @@ public class AuthResponse { @@ -17,6 +17,7 @@ public class AuthResponse {
17 private String eslogan; 17 private String eslogan;
18 private String entidadLogo; 18 private String entidadLogo;
19 private String responsable; 19 private String responsable;
  20 + private String entidad;
20 21
21 public AuthResponse(String token, String nombre, String message) { 22 public AuthResponse(String token, String nombre, String message) {
22 this.token = token; 23 this.token = token;
@@ -37,12 +38,13 @@ public class AuthResponse { @@ -37,12 +38,13 @@ public class AuthResponse {
37 this.bounds = bounds; 38 this.bounds = bounds;
38 } 39 }
39 40
40 - public AuthResponse(String token, String nombre, String message, Double lat, Double lng, Integer zoom, Integer minZoom, Integer maxZoom, String mapaBase, String bounds, String entidadNombre, String eslogan, String entidadLogo, String responsable) { 41 + public AuthResponse(String token, String nombre, String message, Double lat, Double lng, Integer zoom, Integer minZoom, Integer maxZoom, String mapaBase, String bounds, String entidadNombre, String eslogan, String entidadLogo, String responsable, String entidad) {
41 this(token, nombre, message, lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds); 42 this(token, nombre, message, lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds);
42 this.entidadNombre = entidadNombre; 43 this.entidadNombre = entidadNombre;
43 this.eslogan = eslogan; 44 this.eslogan = eslogan;
44 this.entidadLogo = entidadLogo; 45 this.entidadLogo = entidadLogo;
45 this.responsable = responsable; 46 this.responsable = responsable;
  47 + this.entidad = entidad;
46 } 48 }
47 49
48 // Getters y Setters 50 // Getters y Setters
@@ -60,4 +62,5 @@ public class AuthResponse { @@ -60,4 +62,5 @@ public class AuthResponse {
60 public String getEslogan() { return eslogan; } 62 public String getEslogan() { return eslogan; }
61 public String getEntidadLogo() { return entidadLogo; } 63 public String getEntidadLogo() { return entidadLogo; }
62 public String getResponsable() { return responsable; } 64 public String getResponsable() { return responsable; }
  65 + public String getEntidad() { return entidad; }
63 } 66 }
src/main/java/com/sigem/gis/service/FdwService.java
@@ -57,15 +57,16 @@ public class FdwService { @@ -57,15 +57,16 @@ public class FdwService {
57 // ... 57 // ...
58 // Regla Multi-Tenant: Verificar presencia de las 5 tablas críticas 58 // Regla Multi-Tenant: Verificar presencia de las 5 tablas críticas
59 String checkSql = "SELECT count(*) FROM information_schema.tables " + 59 String checkSql = "SELECT count(*) FROM information_schema.tables " +
60 - "WHERE table_schema = ? AND table_name IN " +  
61 - "('usuarios', 'estadisticas_datos', 'v_liq_entidad_percentiles', 'v_liq_entidad_totalxobjeto', 'ventanas_usuario')";  
62 - 60 + "WHERE table_schema = ? AND table_name IN " +
  61 + "('usuarios', 'estadisticas_datos', 'v_liq_entidad_percentiles', 'v_liq_entidad_totalxobjeto', 'ventanas_usuario')";
  62 +
63 Integer count = (forceUpdate) ? 0 : gisJdbcTemplate.queryForObject(checkSql, Integer.class, schemaName); 63 Integer count = (forceUpdate) ? 0 : gisJdbcTemplate.queryForObject(checkSql, Integer.class, schemaName);
64 - 64 +
65 // Si falta alguna de las 5 tablas o se solicita actualización forzada 65 // Si falta alguna de las 5 tablas o se solicita actualización forzada
66 if (forceUpdate || count == null || count < 5) { 66 if (forceUpdate || count == null || count < 5) {
67 if (count != null && count > 0 && count < 5) { 67 if (count != null && count > 0 && count < 5) {
68 - System.out.println("Infraestructura incompleta para " + entidadId + " (" + count + "/5 tablas). Forzando recreación..."); 68 + System.out.println("Infraestructura incompleta para " + entidadId + " (" + count
  69 + + "/5 tablas). Forzando recreación...");
69 } 70 }
70 // (creación de server, user mapping y esquema igual) 71 // (creación de server, user mapping y esquema igual)
71 gisJdbcTemplate.execute("DROP SERVER IF EXISTS " + serverName + " CASCADE"); 72 gisJdbcTemplate.execute("DROP SERVER IF EXISTS " + serverName + " CASCADE");
@@ -90,7 +91,7 @@ public class FdwService { @@ -90,7 +91,7 @@ public class FdwService {
90 "CREATE OR REPLACE VIEW public.%s AS " + 91 "CREATE OR REPLACE VIEW public.%s AS " +
91 "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + 92 "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " +
92 "FROM %s l " + 93 "FROM %s l " +
93 - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral AND l.entidad::text = m.entidad::text", 94 + "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral",
94 viewLotesName, tableLotes, schemaName)); 95 viewLotesName, tableLotes, schemaName));
95 96
96 // Vista PNG FULL (WMS) - SIN LIMIT 97 // Vista PNG FULL (WMS) - SIN LIMIT
@@ -99,7 +100,7 @@ public class FdwService { @@ -99,7 +100,7 @@ public class FdwService {
99 "CREATE OR REPLACE VIEW public.%s AS " + 100 "CREATE OR REPLACE VIEW public.%s AS " +
100 "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + 101 "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " +
101 "FROM %s l " + 102 "FROM %s l " +
102 - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral AND l.entidad::text = m.entidad::text", 103 + "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral",
103 viewWmsName, tableLotes, schemaName)); 104 viewWmsName, tableLotes, schemaName));
104 105
105 // 4. Sincronización con GeoServer 106 // 4. Sincronización con GeoServer
GitLab Appliance - Powered by TurnKey Linux