Commit a3050e01d5f5687036af2b6114d0a38d46be2011
1 parent
fc0b710a
Hito: Login Dinámico (.254), Logos Base64 y Responsables en Navbar
Showing
6 changed files
with
185 additions
and
76 deletions
VERSION.txt
| @@ -9,4 +9,4 @@ PROYECTO GIS-GEOSERVER - 2026.04.06.01.13.00 ID DOCKER: d983a409769d. Observacià | @@ -9,4 +9,4 @@ PROYECTO GIS-GEOSERVER - 2026.04.06.01.13.00 ID DOCKER: d983a409769d. Observacià | ||
| 9 | PROYECTO GIS-GEOSERVER - 2026.04.06.12.44.00 ID DOCKER: d983a409769d. Observación: Backup completo preventivo de la versión con estructura de Git corregida y Landing Page AdminLTE. | 9 | PROYECTO GIS-GEOSERVER - 2026.04.06.12.44.00 ID DOCKER: d983a409769d. Observación: Backup completo preventivo de la versión con estructura de Git corregida y Landing Page AdminLTE. |
| 10 | PROYECTO GIS-GEOSERVER - 2026.04.06.13.31.00 ID DOCKER: d983a409769d. Observación: Prueba de funcionamiento del Manual v1.1 tras estandarización de prefijos y manual de recuperación. | 10 | PROYECTO GIS-GEOSERVER - 2026.04.06.13.31.00 ID DOCKER: d983a409769d. Observación: Prueba de funcionamiento del Manual v1.1 tras estandarización de prefijos y manual de recuperación. |
| 11 | PROYECTO GIS-GEOSERVER - 2026.04.07.08.18.07 ID DOCKER: d983a409769d. Observación: Optimización visual de interfaz completada: unificación de anchos al 100%, limpieza de barra superior, traslado de controles al sidebar y registro legal SIGEM-MIC/DINAPI. | 11 | PROYECTO GIS-GEOSERVER - 2026.04.07.08.18.07 ID DOCKER: d983a409769d. Observación: Optimización visual de interfaz completada: unificación de anchos al 100%, limpieza de barra superior, traslado de controles al sidebar y registro legal SIGEM-MIC/DINAPI. |
| 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. | ||
| 13 | \ No newline at end of file | 12 | \ No newline at end of file |
| 13 | +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. |
src/main/java/com/sigem/gis/security/AuthController.java
| @@ -8,6 +8,7 @@ import org.springframework.jdbc.core.JdbcTemplate; | @@ -8,6 +8,7 @@ import org.springframework.jdbc.core.JdbcTemplate; | ||
| 8 | import org.springframework.web.bind.annotation.*; | 8 | import org.springframework.web.bind.annotation.*; |
| 9 | import com.sigem.gis.service.FdwService; | 9 | import com.sigem.gis.service.FdwService; |
| 10 | 10 | ||
| 11 | +import java.util.Base64; | ||
| 11 | import java.util.List; | 12 | import java.util.List; |
| 12 | import java.util.Map; | 13 | import java.util.Map; |
| 13 | 14 | ||
| @@ -29,18 +30,38 @@ public class AuthController { | @@ -29,18 +30,38 @@ public class AuthController { | ||
| 29 | @Autowired | 30 | @Autowired |
| 30 | private FdwService fdwService; | 31 | private FdwService fdwService; |
| 31 | 32 | ||
| 33 | + @GetMapping("/entidades") | ||
| 34 | + public ResponseEntity<?> getEntidadesActivas() { | ||
| 35 | + try { | ||
| 36 | + String sql = "select entidad, nombre, entidad_logo, responsable, eslogan from public.entidades WHERE activo = TRUE ORDER BY entidad ASC"; | ||
| 37 | + List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sql); | ||
| 38 | + return ResponseEntity.ok(entidades); | ||
| 39 | + } catch (Exception e) { | ||
| 40 | + e.printStackTrace(); | ||
| 41 | + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al cargar entidades"); | ||
| 42 | + } | ||
| 43 | + } | ||
| 44 | + | ||
| 32 | @PostMapping("/login") | 45 | @PostMapping("/login") |
| 33 | public ResponseEntity<?> login(@RequestBody AuthRequest request) { | 46 | public ResponseEntity<?> login(@RequestBody AuthRequest request) { |
| 34 | try { | 47 | try { |
| 35 | // 1. Validar existencia de entidad en directorio maestro (en el .254) | 48 | // 1. Validar existencia de entidad en directorio maestro (en el .254) |
| 36 | - String sqlEntidades = "SELECT sigem_site, sigem_dbname, lat, lng, zoom, minzoom, maxzoom, mapa_base, boundno, boundse FROM public.entidades WHERE activo= TRUE AND entidad = ?"; | 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 = ?"; |
| 37 | List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(request.getEntidad())); | 50 | List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(request.getEntidad())); |
| 38 | 51 | ||
| 39 | if (entidades.isEmpty()) { | 52 | if (entidades.isEmpty()) { |
| 40 | - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "Entidad Inactiva o No Encontrada")); | 53 | + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "La MUNICIPALIDAD con código " + request.getEntidad() + " no existe o está inactiva.")); |
| 41 | } | 54 | } |
| 42 | 55 | ||
| 43 | Map<String, Object> entidadData = entidades.get(0); | 56 | Map<String, Object> entidadData = entidades.get(0); |
| 57 | + String nombreEntidad = convertObjectToString(entidadData.get("nombre")); | ||
| 58 | + String eslogan = convertObjectToString(entidadData.get("eslogan")); | ||
| 59 | + | ||
| 60 | + // Tratamiento especial para el logo (Binario -> Base64) | ||
| 61 | + Object logoObj = entidadData.get("entidad_logo"); | ||
| 62 | + String logo = (logoObj instanceof byte[]) ? Base64.getEncoder().encodeToString((byte[]) logoObj) : convertObjectToString(logoObj); | ||
| 63 | + | ||
| 64 | + String responsable = convertObjectToString(entidadData.get("responsable")); | ||
| 44 | 65 | ||
| 45 | // 2. Asegurar Infraestructura FDW (Regla 16: Solo crea si no existe) | 66 | // 2. Asegurar Infraestructura FDW (Regla 16: Solo crea si no existe) |
| 46 | fdwService.setupFdw(request.getEntidad()); | 67 | fdwService.setupFdw(request.getEntidad()); |
| @@ -58,12 +79,12 @@ public class AuthController { | @@ -58,12 +79,12 @@ public class AuthController { | ||
| 58 | if (!usuarios.isEmpty()) { | 79 | if (!usuarios.isEmpty()) { |
| 59 | Map<String, Object> userData = usuarios.get(0); | 80 | Map<String, Object> userData = usuarios.get(0); |
| 60 | boolean isActivo = (boolean) userData.get("activo"); | 81 | boolean isActivo = (boolean) userData.get("activo"); |
| 61 | - String claveDesencriptada = (String) userData.get("clave_desencriptada"); | 82 | + String claveDesencriptada = convertObjectToString(userData.get("clave_desencriptada")); |
| 62 | 83 | ||
| 63 | // 4. Validación Final y Generación de Token | 84 | // 4. Validación Final y Generación de Token |
| 64 | if (isActivo && request.getPassword().equals(claveDesencriptada)) { | 85 | if (isActivo && request.getPassword().equals(claveDesencriptada)) { |
| 65 | String token = jwtUtil.generateToken(request.getUsername(), request.getEntidad()); | 86 | String token = jwtUtil.generateToken(request.getUsername(), request.getEntidad()); |
| 66 | - String nombreCompleto = userData.get("usu_nom") + " " + userData.get("usu_ape"); | 87 | + String nombreCompleto = convertObjectToString(userData.get("usu_nom")) + " " + convertObjectToString(userData.get("usu_ape")); |
| 67 | 88 | ||
| 68 | // Metadatos georreferenciados de la entidad | 89 | // Metadatos georreferenciados de la entidad |
| 69 | Double lat = parseDouble(entidadData.get("lat"), -25.456443); | 90 | Double lat = parseDouble(entidadData.get("lat"), -25.456443); |
| @@ -71,12 +92,13 @@ public class AuthController { | @@ -71,12 +92,13 @@ public class AuthController { | ||
| 71 | Integer zoom = parseInteger(entidadData.get("zoom"), 14); | 92 | Integer zoom = parseInteger(entidadData.get("zoom"), 14); |
| 72 | Integer minZoom = parseInteger(entidadData.get("minzoom"), 5); | 93 | Integer minZoom = parseInteger(entidadData.get("minzoom"), 5); |
| 73 | Integer maxZoom = parseInteger(entidadData.get("maxzoom"), 20); | 94 | Integer maxZoom = parseInteger(entidadData.get("maxzoom"), 20); |
| 74 | - String mapaBase = String.valueOf(entidadData.getOrDefault("mapa_base", "osm")); | ||
| 75 | - String bounds = String.valueOf(entidadData.getOrDefault("boundno", "")) + "|" + | ||
| 76 | - String.valueOf(entidadData.getOrDefault("boundse", "")); | 95 | + String mapaBase = convertObjectToString(entidadData.getOrDefault("mapa_base", "osm")); |
| 96 | + String bounds = convertObjectToString(entidadData.getOrDefault("boundno", "")) + "|" + | ||
| 97 | + convertObjectToString(entidadData.getOrDefault("boundse", "")); | ||
| 77 | 98 | ||
| 78 | return ResponseEntity.ok(new AuthResponse(token, nombreCompleto, "Login Exitoso", | 99 | return ResponseEntity.ok(new AuthResponse(token, nombreCompleto, "Login Exitoso", |
| 79 | - lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds)); | 100 | + lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds, |
| 101 | + nombreEntidad, eslogan, logo, responsable)); | ||
| 80 | } | 102 | } |
| 81 | } | 103 | } |
| 82 | 104 | ||
| @@ -88,10 +110,19 @@ public class AuthController { | @@ -88,10 +110,19 @@ public class AuthController { | ||
| 88 | } | 110 | } |
| 89 | } | 111 | } |
| 90 | 112 | ||
| 113 | + private String convertObjectToString(Object val) { | ||
| 114 | + if (val == null) return null; | ||
| 115 | + if (val instanceof byte[]) { | ||
| 116 | + return new String((byte[]) val); | ||
| 117 | + } | ||
| 118 | + return String.valueOf(val); | ||
| 119 | + } | ||
| 120 | + | ||
| 91 | private Double parseDouble(Object val, Double def) { | 121 | private Double parseDouble(Object val, Double def) { |
| 92 | if (val == null) return def; | 122 | if (val == null) return def; |
| 93 | try { | 123 | try { |
| 94 | - return Double.parseDouble(String.valueOf(val).trim()); | 124 | + String str = convertObjectToString(val); |
| 125 | + return Double.parseDouble(str.trim()); | ||
| 95 | } catch (Exception e) { | 126 | } catch (Exception e) { |
| 96 | return def; | 127 | return def; |
| 97 | } | 128 | } |
| @@ -100,7 +131,8 @@ public class AuthController { | @@ -100,7 +131,8 @@ public class AuthController { | ||
| 100 | private Integer parseInteger(Object val, Integer def) { | 131 | private Integer parseInteger(Object val, Integer def) { |
| 101 | if (val == null) return def; | 132 | if (val == null) return def; |
| 102 | try { | 133 | try { |
| 103 | - return Integer.parseInt(String.valueOf(val)); | 134 | + String str = convertObjectToString(val); |
| 135 | + return Integer.parseInt(str.trim()); | ||
| 104 | } catch (Exception e) { | 136 | } catch (Exception e) { |
| 105 | return def; | 137 | return def; |
| 106 | } | 138 | } |
src/main/java/com/sigem/gis/security/AuthResponse.java
| @@ -5,7 +5,7 @@ public class AuthResponse { | @@ -5,7 +5,7 @@ public class AuthResponse { | ||
| 5 | private String nombre; | 5 | private String nombre; |
| 6 | private String message; | 6 | private String message; |
| 7 | 7 | ||
| 8 | - // Metadatos Cartográficos (Nuevos) | 8 | + // Metadatos Cartográficos |
| 9 | private Double lat; | 9 | private Double lat; |
| 10 | private Double lng; | 10 | private Double lng; |
| 11 | private Integer zoom; | 11 | private Integer zoom; |
| @@ -13,6 +13,10 @@ public class AuthResponse { | @@ -13,6 +13,10 @@ public class AuthResponse { | ||
| 13 | private Integer maxZoom; | 13 | private Integer maxZoom; |
| 14 | private String mapaBase; | 14 | private String mapaBase; |
| 15 | private String bounds; | 15 | private String bounds; |
| 16 | + private String entidadNombre; | ||
| 17 | + private String eslogan; | ||
| 18 | + private String entidadLogo; | ||
| 19 | + private String responsable; | ||
| 16 | 20 | ||
| 17 | public AuthResponse(String token, String nombre, String message) { | 21 | public AuthResponse(String token, String nombre, String message) { |
| 18 | this.token = token; | 22 | this.token = token; |
| @@ -33,6 +37,14 @@ public class AuthResponse { | @@ -33,6 +37,14 @@ public class AuthResponse { | ||
| 33 | this.bounds = bounds; | 37 | this.bounds = bounds; |
| 34 | } | 38 | } |
| 35 | 39 | ||
| 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 | + this(token, nombre, message, lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds); | ||
| 42 | + this.entidadNombre = entidadNombre; | ||
| 43 | + this.eslogan = eslogan; | ||
| 44 | + this.entidadLogo = entidadLogo; | ||
| 45 | + this.responsable = responsable; | ||
| 46 | + } | ||
| 47 | + | ||
| 36 | // Getters y Setters | 48 | // Getters y Setters |
| 37 | public String getToken() { return token; } | 49 | public String getToken() { return token; } |
| 38 | public String getNombre() { return nombre; } | 50 | public String getNombre() { return nombre; } |
| @@ -44,4 +56,8 @@ public class AuthResponse { | @@ -44,4 +56,8 @@ public class AuthResponse { | ||
| 44 | public Integer getMaxZoom() { return maxZoom; } | 56 | public Integer getMaxZoom() { return maxZoom; } |
| 45 | public String getMapaBase() { return mapaBase; } | 57 | public String getMapaBase() { return mapaBase; } |
| 46 | public String getBounds() { return bounds; } | 58 | public String getBounds() { return bounds; } |
| 59 | + public String getEntidadNombre() { return entidadNombre; } | ||
| 60 | + public String getEslogan() { return eslogan; } | ||
| 61 | + public String getEntidadLogo() { return entidadLogo; } | ||
| 62 | + public String getResponsable() { return responsable; } | ||
| 47 | } | 63 | } |
src/main/resources/static/landing.html
| @@ -35,7 +35,19 @@ | @@ -35,7 +35,19 @@ | ||
| 35 | <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> | 35 | <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> |
| 36 | </li> | 36 | </li> |
| 37 | <li class="nav-item d-none d-sm-inline-block"> | 37 | <li class="nav-item d-none d-sm-inline-block"> |
| 38 | - <span class="nav-link" style="color: white; font-size: 18px; margin-left:10px;">Municipalidad de <span id="nav-entidad-text" style="font-weight: 800;">...</span> <span style="font-size: 14px; font-weight: 300;">¡Emergente y Sostenible!</span></span> | 38 | + <div class="nav-link p-0" style="color: white; margin-left:15px; display: flex; align-items: center; height: 100%;"> |
| 39 | + <!-- Logo Dinámico --> | ||
| 40 | + <img id="nav-entidad-logo" src="" alt="" style="height: 40px; margin-right: 12px; display: none; filter: drop-shadow(0px 0px 1px rgba(0,0,0,0.5));"> | ||
| 41 | + | ||
| 42 | + <div style="display: flex; flex-direction: column; justify-content: center;"> | ||
| 43 | + <div style="line-height: 1.2;"> | ||
| 44 | + <span style="font-size: 18px;">Municipalidad de <span id="nav-entidad-text" style="font-weight: 800;">...</span> <span id="nav-eslogan-text" style="font-weight: 400; margin-left: 5px;"></span></span> | ||
| 45 | + </div> | ||
| 46 | + <div style="line-height: 1.2; font-size: 13px; font-weight: 300; opacity: 0.9;"> | ||
| 47 | + <span id="nav-responsable-text">Administración: ...</span> | ||
| 48 | + </div> | ||
| 49 | + </div> | ||
| 50 | + </div> | ||
| 39 | </li> | 51 | </li> |
| 40 | </ul> | 52 | </ul> |
| 41 | 53 | ||
| @@ -163,7 +175,29 @@ | @@ -163,7 +175,29 @@ | ||
| 163 | } | 175 | } |
| 164 | 176 | ||
| 165 | document.getElementById('nav-user-text').innerText = userName || 'Operador Local'; | 177 | document.getElementById('nav-user-text').innerText = userName || 'Operador Local'; |
| 166 | - document.getElementById('nav-entidad-text').innerText = entidad || 'N/D'; | 178 | + const entNombre = localStorage.getItem('entidad_nombre') || (entidad || 'N/D'); |
| 179 | + const entEslogan = localStorage.getItem('eslogan') || ''; | ||
| 180 | + const entResponsable = localStorage.getItem('responsable') || 'Gestión Municipal'; | ||
| 181 | + const entLogoBase64 = localStorage.getItem('entidad_logo'); | ||
| 182 | + | ||
| 183 | + document.getElementById('nav-entidad-text').innerText = entNombre; | ||
| 184 | + document.getElementById('nav-responsable-text').innerText = `Administración: ${entResponsable}`; | ||
| 185 | + | ||
| 186 | + // Configurar Logo | ||
| 187 | + const logoImg = document.getElementById('nav-entidad-logo'); | ||
| 188 | + if (entLogoBase64 && entLogoBase64 !== 'null' && entLogoBase64.length > 100) { | ||
| 189 | + // Si no tiene el prefijo de data image, se lo agregamos | ||
| 190 | + logoImg.src = entLogoBase64.startsWith('data:') ? entLogoBase64 : `data:image/png;base64,${entLogoBase64}`; | ||
| 191 | + logoImg.style.display = 'block'; | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + // Si el eslogan no está ya contenido en el nombre (para evitar duplicados), mostrarlo | ||
| 195 | + const navEslogan = document.getElementById('nav-eslogan-text'); | ||
| 196 | + if (entEslogan && !entNombre.toLowerCase().includes(entEslogan.toLowerCase())) { | ||
| 197 | + navEslogan.innerText = entEslogan; | ||
| 198 | + } else { | ||
| 199 | + navEslogan.innerText = ''; | ||
| 200 | + } | ||
| 167 | 201 | ||
| 168 | function notImplemented() { | 202 | function notImplemented() { |
| 169 | alert("OPCIÓN NO IMPLEMENTADA"); | 203 | alert("OPCIÓN NO IMPLEMENTADA"); |
src/main/resources/static/login.html
| @@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | <title>Inicie Sesión - Ecosistema SIGEM</title> | 6 | <title>Inicie Sesión - Ecosistema SIGEM</title> |
| 7 | 7 | ||
| 8 | - <!-- Google Font: Source Sans Pro (Estandar de AdminLTE) --> | 8 | + <!-- Google Font: Source Sans Pro --> |
| 9 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback"> | 9 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback"> |
| 10 | <!-- Font Awesome --> | 10 | <!-- Font Awesome --> |
| 11 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> | 11 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> |
| @@ -29,14 +29,16 @@ | @@ -29,14 +29,16 @@ | ||
| 29 | .login-logo a { | 29 | .login-logo a { |
| 30 | color: #333 !important; | 30 | color: #333 !important; |
| 31 | } | 31 | } |
| 32 | + #municipioSearch::placeholder { | ||
| 33 | + font-size: 0.85rem; | ||
| 34 | + } | ||
| 32 | </style> | 35 | </style> |
| 33 | </head> | 36 | </head> |
| 34 | <body class="hold-transition login-page"> | 37 | <body class="hold-transition login-page"> |
| 35 | -<div class="login-box"> | 38 | +<div class="login-box" style="width: 400px;"> |
| 36 | <div class="login-logo"> | 39 | <div class="login-logo"> |
| 37 | <a href="#"><b>SIGEM</b>WEB</a> | 40 | <a href="#"><b>SIGEM</b>WEB</a> |
| 38 | </div> | 41 | </div> |
| 39 | - <!-- /.login-logo --> | ||
| 40 | <div class="card card-outline card-primary shadow-lg"> | 42 | <div class="card card-outline card-primary shadow-lg"> |
| 41 | <div class="card-body login-card-body rounded"> | 43 | <div class="card-body login-card-body rounded"> |
| 42 | <p class="login-box-msg font-weight-bold" style="color: #555;">Visor Georreferenciado Multi-Tenant</p> | 44 | <p class="login-box-msg font-weight-bold" style="color: #555;">Visor Georreferenciado Multi-Tenant</p> |
| @@ -44,24 +46,25 @@ | @@ -44,24 +46,25 @@ | ||
| 44 | <form id="loginForm"> | 46 | <form id="loginForm"> |
| 45 | <label for="municipioSearch" class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">1. Entidad (Municipalidad)</label> | 47 | <label for="municipioSearch" class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">1. Entidad (Municipalidad)</label> |
| 46 | <div class="input-group mb-2"> | 48 | <div class="input-group mb-2"> |
| 47 | - <input type="text" id="municipioSearch" class="form-control" placeholder="Buscar municipio (ej. 505)" autocomplete="off"> | 49 | + <input type="text" id="municipioSearch" class="form-control" placeholder="Digite código o nombre del municipio..." autocomplete="off"> |
| 48 | <div class="input-group-append"> | 50 | <div class="input-group-append"> |
| 49 | <div class="input-group-text"> | 51 | <div class="input-group-text"> |
| 50 | - <span class="fas fa-search"></span> | 52 | + <span class="fas fa-university"></span> |
| 51 | </div> | 53 | </div> |
| 52 | </div> | 54 | </div> |
| 53 | </div> | 55 | </div> |
| 54 | - <div class="form-group mb-3"> | ||
| 55 | - <select id="entidad" class="form-control" required size="3" style="height: 85px; cursor: pointer; font-size: 0.9rem;"> | ||
| 56 | - <option value="505">Entidad 505 (Piloto GIS)</option> | ||
| 57 | - <option value="800">Entidad 800 (Pruebas)</option> | ||
| 58 | - <option value="900">Entidad 900 (Desarrollo)</option> | 56 | + <div class="form-group mb-3 position-relative"> |
| 57 | + <select id="entidad" class="form-control" required size="4" style="height: 110px; cursor: pointer; font-size: 0.9rem;"> | ||
| 58 | + <option value="" disabled>Cargando municipios...</option> | ||
| 59 | </select> | 59 | </select> |
| 60 | + <div id="loader-entidades" class="text-center p-2" style="position: absolute; top:0; left:0; width:100%; height:100%; background:white; display:none;"> | ||
| 61 | + <div class="spinner-border spinner-border-sm text-primary"></div> | ||
| 62 | + </div> | ||
| 60 | </div> | 63 | </div> |
| 61 | 64 | ||
| 62 | <label class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">2. Credenciales</label> | 65 | <label class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">2. Credenciales</label> |
| 63 | <div class="input-group mb-3"> | 66 | <div class="input-group mb-3"> |
| 64 | - <input type="text" id="username" class="form-control" placeholder="Usu_alias (ej. operador)" required autocomplete="username"> | 67 | + <input type="text" id="username" class="form-control" placeholder="Usuario (ej. operador)" required autocomplete="username"> |
| 65 | <div class="input-group-append"> | 68 | <div class="input-group-append"> |
| 66 | <div class="input-group-text"> | 69 | <div class="input-group-text"> |
| 67 | <span class="fas fa-user"></span> | 70 | <span class="fas fa-user"></span> |
| @@ -69,7 +72,7 @@ | @@ -69,7 +72,7 @@ | ||
| 69 | </div> | 72 | </div> |
| 70 | </div> | 73 | </div> |
| 71 | <div class="input-group mb-3"> | 74 | <div class="input-group mb-3"> |
| 72 | - <input type="password" id="password" class="form-control" placeholder="Tu clave alfanumérica" required autocomplete="current-password"> | 75 | + <input type="password" id="password" class="form-control" placeholder="Contraseña SIGEM" required autocomplete="current-password"> |
| 73 | <div class="input-group-append"> | 76 | <div class="input-group-append"> |
| 74 | <div class="input-group-text"> | 77 | <div class="input-group-text"> |
| 75 | <span class="fas fa-lock"></span> | 78 | <span class="fas fa-lock"></span> |
| @@ -89,100 +92,124 @@ | @@ -89,100 +92,124 @@ | ||
| 89 | </div> | 92 | </div> |
| 90 | </form> | 93 | </form> |
| 91 | </div> | 94 | </div> |
| 92 | - <!-- /.login-card-body --> | ||
| 93 | </div> | 95 | </div> |
| 94 | </div> | 96 | </div> |
| 95 | -<!-- /.login-box --> | ||
| 96 | 97 | ||
| 97 | -<!-- jQuery --> | ||
| 98 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> | 98 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> |
| 99 | -<!-- Bootstrap 4 --> | ||
| 100 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> | 99 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> |
| 101 | -<!-- AdminLTE App --> | ||
| 102 | -<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script> | ||
| 103 | 100 | ||
| 104 | <script> | 101 | <script> |
| 105 | - // Filtro interactivo de municipios | ||
| 106 | const searchInput = document.getElementById('municipioSearch'); | 102 | const searchInput = document.getElementById('municipioSearch'); |
| 107 | const selectEntidad = document.getElementById('entidad'); | 103 | const selectEntidad = document.getElementById('entidad'); |
| 108 | - const originalOptions = Array.from(selectEntidad.options); | 104 | + const loader = document.getElementById('loader-entidades'); |
| 105 | + let allEntidades = []; | ||
| 109 | 106 | ||
| 110 | - searchInput.addEventListener('input', (e) => { | ||
| 111 | - const text = e.target.value.toLowerCase(); | 107 | + // Cargar Entidades desde la Base de Datos Remota (.254) |
| 108 | + async function loadEntidades() { | ||
| 109 | + loader.style.display = 'block'; | ||
| 110 | + try { | ||
| 111 | + const response = await fetch('/gis-geoserver/api/auth/entidades'); | ||
| 112 | + if (!response.ok) throw new Error("Error al obtener entidades"); | ||
| 113 | + allEntidades = await response.json(); | ||
| 114 | + renderOptions(allEntidades); | ||
| 115 | + } catch (error) { | ||
| 116 | + console.error(error); | ||
| 117 | + selectEntidad.innerHTML = '<option value="" disabled>Error al cargar municipios</option>'; | ||
| 118 | + } finally { | ||
| 119 | + loader.style.display = 'none'; | ||
| 120 | + } | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + function renderOptions(data) { | ||
| 112 | selectEntidad.innerHTML = ''; | 124 | selectEntidad.innerHTML = ''; |
| 113 | - originalOptions.forEach(opt => { | ||
| 114 | - if (opt.text.toLowerCase().includes(text) || opt.value.includes(text)) { | ||
| 115 | - selectEntidad.appendChild(opt); | ||
| 116 | - } | 125 | + data.forEach(ent => { |
| 126 | + const opt = document.createElement('option'); | ||
| 127 | + opt.value = ent.entidad; | ||
| 128 | + opt.textContent = `${ent.entidad} - ${ent.nombre}`; | ||
| 129 | + selectEntidad.appendChild(opt); | ||
| 117 | }); | 130 | }); |
| 118 | - if (selectEntidad.options.length > 0) { | ||
| 119 | - selectEntidad.options[0].selected = true; | 131 | + if (data.length > 0) selectEntidad.selectedIndex = 0; |
| 132 | + } | ||
| 133 | + | ||
| 134 | + searchInput.addEventListener('input', (e) => { | ||
| 135 | + const text = e.target.value.toLowerCase(); | ||
| 136 | + const filtered = allEntidades.filter(ent => | ||
| 137 | + ent.entidad.toString().includes(text) || | ||
| 138 | + ent.nombre.toLowerCase().includes(text) | ||
| 139 | + ); | ||
| 140 | + renderOptions(filtered); | ||
| 141 | + | ||
| 142 | + // Si el usuario escribió un código exacto que existe, seleccionarlo | ||
| 143 | + const exactMatch = allEntidades.find(ent => ent.entidad.toString() === text); | ||
| 144 | + if (exactMatch) { | ||
| 145 | + selectEntidad.value = exactMatch.entidad; | ||
| 120 | } | 146 | } |
| 121 | }); | 147 | }); |
| 122 | 148 | ||
| 123 | - // Limpiar sesión previa si por error caen al login | 149 | + // Carga inicial |
| 150 | + loadEntidades(); | ||
| 151 | + | ||
| 124 | localStorage.removeItem('jwt'); | 152 | localStorage.removeItem('jwt'); |
| 125 | - localStorage.removeItem('user_name'); | ||
| 126 | - | 153 | + |
| 127 | document.getElementById('loginForm').addEventListener('submit', function(e) { | 154 | document.getElementById('loginForm').addEventListener('submit', function(e) { |
| 128 | e.preventDefault(); | 155 | e.preventDefault(); |
| 129 | 156 | ||
| 130 | const username = document.getElementById('username').value; | 157 | const username = document.getElementById('username').value; |
| 131 | const password = document.getElementById('password').value; | 158 | const password = document.getElementById('password').value; |
| 132 | - const entidad = document.getElementById('entidad').value; | 159 | + const searchVal = searchInput.value.trim(); |
| 133 | const errorMsg = document.getElementById('error-msg'); | 160 | const errorMsg = document.getElementById('error-msg'); |
| 161 | + | ||
| 162 | + // Regla: Validar si la entidad seleccionada coincide con lo digitado o si hay una seleccionada | ||
| 163 | + let entidadId = selectEntidad.value; | ||
| 164 | + | ||
| 165 | + // Si el buscador tiene algo pero no hay selección válida, intentamos buscar el código exacto | ||
| 166 | + if (!entidadId && searchVal) { | ||
| 167 | + const match = allEntidades.find(ent => ent.entidad.toString() === searchVal); | ||
| 168 | + if (match) entidadId = match.entidad; | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + if (!entidadId) { | ||
| 172 | + errorMsg.innerText = "La MUNICIPALIDAD con código " + (searchVal || "seleccionado") + " no existe."; | ||
| 173 | + errorMsg.style.display = 'block'; | ||
| 174 | + return; | ||
| 175 | + } | ||
| 176 | + | ||
| 134 | const btnText = document.getElementById('btn-text'); | 177 | const btnText = document.getElementById('btn-text'); |
| 135 | const spinner = document.getElementById('spinner'); | 178 | const spinner = document.getElementById('spinner'); |
| 136 | 179 | ||
| 137 | - // Bloquear UI | ||
| 138 | errorMsg.style.display = 'none'; | 180 | errorMsg.style.display = 'none'; |
| 139 | btnText.style.display = 'none'; | 181 | btnText.style.display = 'none'; |
| 140 | spinner.style.display = 'block'; | 182 | spinner.style.display = 'block'; |
| 141 | document.getElementById('btn-login').disabled = true; | 183 | document.getElementById('btn-login').disabled = true; |
| 142 | 184 | ||
| 143 | - const payload = { | ||
| 144 | - username: username, | ||
| 145 | - password: password, | ||
| 146 | - entidad: entidad | ||
| 147 | - }; | ||
| 148 | - | ||
| 149 | fetch('/gis-geoserver/api/auth/login', { | 185 | fetch('/gis-geoserver/api/auth/login', { |
| 150 | method: 'POST', | 186 | method: 'POST', |
| 151 | headers: { 'Content-Type': 'application/json' }, | 187 | headers: { 'Content-Type': 'application/json' }, |
| 152 | - body: JSON.stringify(payload) | 188 | + body: JSON.stringify({ username, password, entidad: entidadId }) |
| 153 | }) | 189 | }) |
| 154 | - .then(response => { | ||
| 155 | - if (!response.ok) { | ||
| 156 | - return response.json().then(errData => { | ||
| 157 | - throw new Error(errData.message || "Credenciales incorrectas."); | ||
| 158 | - }); | ||
| 159 | - } | ||
| 160 | - return response.json(); | 190 | + .then(async response => { |
| 191 | + const data = await response.json(); | ||
| 192 | + if (!response.ok) throw new Error(data.message || "Credenciales incorrectas."); | ||
| 193 | + return data; | ||
| 161 | }) | 194 | }) |
| 162 | .then(data => { | 195 | .then(data => { |
| 163 | - // LOGIN EXITOOSO! | ||
| 164 | localStorage.setItem('jwt', data.token); | 196 | localStorage.setItem('jwt', data.token); |
| 165 | - localStorage.setItem('user_name', data.nombre); // Nombre completo del usuario | ||
| 166 | - localStorage.setItem('entidad', entidad); | 197 | + localStorage.setItem('user_name', data.nombre); |
| 198 | + localStorage.setItem('entidad', entidadId); | ||
| 199 | + localStorage.setItem('entidad_nombre', data.entidadNombre); | ||
| 200 | + localStorage.setItem('eslogan', data.eslogan); | ||
| 201 | + localStorage.setItem('responsable', data.responsable); | ||
| 202 | + localStorage.setItem('entidad_logo', data.entidadLogo); | ||
| 167 | 203 | ||
| 168 | - // Persistir metadatos del Visor | ||
| 169 | localStorage.setItem('map_lat', data.lat); | 204 | localStorage.setItem('map_lat', data.lat); |
| 170 | localStorage.setItem('map_lng', data.lng); | 205 | localStorage.setItem('map_lng', data.lng); |
| 171 | localStorage.setItem('map_zoom', data.zoom); | 206 | localStorage.setItem('map_zoom', data.zoom); |
| 172 | - localStorage.setItem('map_min_zoom', data.minZoom); | ||
| 173 | - localStorage.setItem('map_max_zoom', data.maxZoom); | ||
| 174 | - localStorage.setItem('mapa_base_id', data.mapaBase); | ||
| 175 | - localStorage.setItem('map_bounds', data.bounds); | ||
| 176 | 207 | ||
| 177 | - // Redirigir a la URL del Mapas | ||
| 178 | window.location.href = "/gis-geoserver/landing"; | 208 | window.location.href = "/gis-geoserver/landing"; |
| 179 | }) | 209 | }) |
| 180 | .catch(error => { | 210 | .catch(error => { |
| 181 | - // LOGIN ERROR | ||
| 182 | errorMsg.innerText = error.message; | 211 | errorMsg.innerText = error.message; |
| 183 | errorMsg.style.display = 'block'; | 212 | errorMsg.style.display = 'block'; |
| 184 | - | ||
| 185 | - // Restaurar UI | ||
| 186 | btnText.style.display = 'block'; | 213 | btnText.style.display = 'block'; |
| 187 | spinner.style.display = 'none'; | 214 | spinner.style.display = 'none'; |
| 188 | document.getElementById('btn-login').disabled = false; | 215 | document.getElementById('btn-login').disabled = false; |
src/main/resources/static/widgets.html
| @@ -201,9 +201,9 @@ | @@ -201,9 +201,9 @@ | ||
| 201 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> | 201 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> |
| 202 | <script> | 202 | <script> |
| 203 | // Dinamizar el título con el nombre de la entidad si está disponible | 203 | // Dinamizar el título con el nombre de la entidad si está disponible |
| 204 | - const entidadText = localStorage.getItem('entidad_nombre') || ''; | ||
| 205 | - if (entidadText) { | ||
| 206 | - document.getElementById('welcome-title').innerHTML = `<i class="fas fa-map mr-1"></i> Bienvenido al Sistema Integrado de Gestión Municipal de ${entidadText}`; | 204 | + const entidadNombre = localStorage.getItem('entidad_nombre'); |
| 205 | + if (entidadNombre && entidadNombre !== 'null') { | ||
| 206 | + document.getElementById('welcome-title').innerHTML = `<i class="fas fa-map mr-1"></i> Bienvenido al Sistema Integrado de Gestión Municipal de ${entidadNombre}`; | ||
| 207 | } | 207 | } |
| 208 | </script> | 208 | </script> |
| 209 | </body> | 209 | </body> |