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 | 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 | 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 | 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 | 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 | 8 | import org.springframework.web.bind.annotation.*; |
| 9 | 9 | import com.sigem.gis.service.FdwService; |
| 10 | 10 | |
| 11 | +import java.util.Base64; | |
| 11 | 12 | import java.util.List; |
| 12 | 13 | import java.util.Map; |
| 13 | 14 | |
| ... | ... | @@ -29,18 +30,38 @@ public class AuthController { |
| 29 | 30 | @Autowired |
| 30 | 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 | 45 | @PostMapping("/login") |
| 33 | 46 | public ResponseEntity<?> login(@RequestBody AuthRequest request) { |
| 34 | 47 | try { |
| 35 | 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 | 50 | List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(request.getEntidad())); |
| 38 | 51 | |
| 39 | 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 | 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 | 66 | // 2. Asegurar Infraestructura FDW (Regla 16: Solo crea si no existe) |
| 46 | 67 | fdwService.setupFdw(request.getEntidad()); |
| ... | ... | @@ -58,12 +79,12 @@ public class AuthController { |
| 58 | 79 | if (!usuarios.isEmpty()) { |
| 59 | 80 | Map<String, Object> userData = usuarios.get(0); |
| 60 | 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 | 84 | // 4. Validación Final y Generación de Token |
| 64 | 85 | if (isActivo && request.getPassword().equals(claveDesencriptada)) { |
| 65 | 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 | 89 | // Metadatos georreferenciados de la entidad |
| 69 | 90 | Double lat = parseDouble(entidadData.get("lat"), -25.456443); |
| ... | ... | @@ -71,12 +92,13 @@ public class AuthController { |
| 71 | 92 | Integer zoom = parseInteger(entidadData.get("zoom"), 14); |
| 72 | 93 | Integer minZoom = parseInteger(entidadData.get("minzoom"), 5); |
| 73 | 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 | 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 | 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 | 121 | private Double parseDouble(Object val, Double def) { |
| 92 | 122 | if (val == null) return def; |
| 93 | 123 | try { |
| 94 | - return Double.parseDouble(String.valueOf(val).trim()); | |
| 124 | + String str = convertObjectToString(val); | |
| 125 | + return Double.parseDouble(str.trim()); | |
| 95 | 126 | } catch (Exception e) { |
| 96 | 127 | return def; |
| 97 | 128 | } |
| ... | ... | @@ -100,7 +131,8 @@ public class AuthController { |
| 100 | 131 | private Integer parseInteger(Object val, Integer def) { |
| 101 | 132 | if (val == null) return def; |
| 102 | 133 | try { |
| 103 | - return Integer.parseInt(String.valueOf(val)); | |
| 134 | + String str = convertObjectToString(val); | |
| 135 | + return Integer.parseInt(str.trim()); | |
| 104 | 136 | } catch (Exception e) { |
| 105 | 137 | return def; |
| 106 | 138 | } | ... | ... |
src/main/java/com/sigem/gis/security/AuthResponse.java
| ... | ... | @@ -5,7 +5,7 @@ public class AuthResponse { |
| 5 | 5 | private String nombre; |
| 6 | 6 | private String message; |
| 7 | 7 | |
| 8 | - // Metadatos Cartográficos (Nuevos) | |
| 8 | + // Metadatos Cartográficos | |
| 9 | 9 | private Double lat; |
| 10 | 10 | private Double lng; |
| 11 | 11 | private Integer zoom; |
| ... | ... | @@ -13,6 +13,10 @@ public class AuthResponse { |
| 13 | 13 | private Integer maxZoom; |
| 14 | 14 | private String mapaBase; |
| 15 | 15 | private String bounds; |
| 16 | + private String entidadNombre; | |
| 17 | + private String eslogan; | |
| 18 | + private String entidadLogo; | |
| 19 | + private String responsable; | |
| 16 | 20 | |
| 17 | 21 | public AuthResponse(String token, String nombre, String message) { |
| 18 | 22 | this.token = token; |
| ... | ... | @@ -33,6 +37,14 @@ public class AuthResponse { |
| 33 | 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 | 48 | // Getters y Setters |
| 37 | 49 | public String getToken() { return token; } |
| 38 | 50 | public String getNombre() { return nombre; } |
| ... | ... | @@ -44,4 +56,8 @@ public class AuthResponse { |
| 44 | 56 | public Integer getMaxZoom() { return maxZoom; } |
| 45 | 57 | public String getMapaBase() { return mapaBase; } |
| 46 | 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 | 35 | <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> |
| 36 | 36 | </li> |
| 37 | 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 | 51 | </li> |
| 40 | 52 | </ul> |
| 41 | 53 | |
| ... | ... | @@ -163,7 +175,29 @@ |
| 163 | 175 | } |
| 164 | 176 | |
| 165 | 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 | 202 | function notImplemented() { |
| 169 | 203 | alert("OPCIÓN NO IMPLEMENTADA"); | ... | ... |
src/main/resources/static/login.html
| ... | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | 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 | 9 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback"> |
| 10 | 10 | <!-- Font Awesome --> |
| 11 | 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 | 29 | .login-logo a { |
| 30 | 30 | color: #333 !important; |
| 31 | 31 | } |
| 32 | + #municipioSearch::placeholder { | |
| 33 | + font-size: 0.85rem; | |
| 34 | + } | |
| 32 | 35 | </style> |
| 33 | 36 | </head> |
| 34 | 37 | <body class="hold-transition login-page"> |
| 35 | -<div class="login-box"> | |
| 38 | +<div class="login-box" style="width: 400px;"> | |
| 36 | 39 | <div class="login-logo"> |
| 37 | 40 | <a href="#"><b>SIGEM</b>WEB</a> |
| 38 | 41 | </div> |
| 39 | - <!-- /.login-logo --> | |
| 40 | 42 | <div class="card card-outline card-primary shadow-lg"> |
| 41 | 43 | <div class="card-body login-card-body rounded"> |
| 42 | 44 | <p class="login-box-msg font-weight-bold" style="color: #555;">Visor Georreferenciado Multi-Tenant</p> |
| ... | ... | @@ -44,24 +46,25 @@ |
| 44 | 46 | <form id="loginForm"> |
| 45 | 47 | <label for="municipioSearch" class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">1. Entidad (Municipalidad)</label> |
| 46 | 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 | 50 | <div class="input-group-append"> |
| 49 | 51 | <div class="input-group-text"> |
| 50 | - <span class="fas fa-search"></span> | |
| 52 | + <span class="fas fa-university"></span> | |
| 51 | 53 | </div> |
| 52 | 54 | </div> |
| 53 | 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 | 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 | 63 | </div> |
| 61 | 64 | |
| 62 | 65 | <label class="text-xs text-muted mb-1" style="font-size: 0.8rem; text-transform: uppercase;">2. Credenciales</label> |
| 63 | 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 | 68 | <div class="input-group-append"> |
| 66 | 69 | <div class="input-group-text"> |
| 67 | 70 | <span class="fas fa-user"></span> |
| ... | ... | @@ -69,7 +72,7 @@ |
| 69 | 72 | </div> |
| 70 | 73 | </div> |
| 71 | 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 | 76 | <div class="input-group-append"> |
| 74 | 77 | <div class="input-group-text"> |
| 75 | 78 | <span class="fas fa-lock"></span> |
| ... | ... | @@ -89,100 +92,124 @@ |
| 89 | 92 | </div> |
| 90 | 93 | </form> |
| 91 | 94 | </div> |
| 92 | - <!-- /.login-card-body --> | |
| 93 | 95 | </div> |
| 94 | 96 | </div> |
| 95 | -<!-- /.login-box --> | |
| 96 | 97 | |
| 97 | -<!-- jQuery --> | |
| 98 | 98 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> |
| 99 | -<!-- Bootstrap 4 --> | |
| 100 | 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 | 101 | <script> |
| 105 | - // Filtro interactivo de municipios | |
| 106 | 102 | const searchInput = document.getElementById('municipioSearch'); |
| 107 | 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 | 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 | 152 | localStorage.removeItem('jwt'); |
| 125 | - localStorage.removeItem('user_name'); | |
| 126 | - | |
| 153 | + | |
| 127 | 154 | document.getElementById('loginForm').addEventListener('submit', function(e) { |
| 128 | 155 | e.preventDefault(); |
| 129 | 156 | |
| 130 | 157 | const username = document.getElementById('username').value; |
| 131 | 158 | const password = document.getElementById('password').value; |
| 132 | - const entidad = document.getElementById('entidad').value; | |
| 159 | + const searchVal = searchInput.value.trim(); | |
| 133 | 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 | 177 | const btnText = document.getElementById('btn-text'); |
| 135 | 178 | const spinner = document.getElementById('spinner'); |
| 136 | 179 | |
| 137 | - // Bloquear UI | |
| 138 | 180 | errorMsg.style.display = 'none'; |
| 139 | 181 | btnText.style.display = 'none'; |
| 140 | 182 | spinner.style.display = 'block'; |
| 141 | 183 | document.getElementById('btn-login').disabled = true; |
| 142 | 184 | |
| 143 | - const payload = { | |
| 144 | - username: username, | |
| 145 | - password: password, | |
| 146 | - entidad: entidad | |
| 147 | - }; | |
| 148 | - | |
| 149 | 185 | fetch('/gis-geoserver/api/auth/login', { |
| 150 | 186 | method: 'POST', |
| 151 | 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 | 195 | .then(data => { |
| 163 | - // LOGIN EXITOOSO! | |
| 164 | 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 | 204 | localStorage.setItem('map_lat', data.lat); |
| 170 | 205 | localStorage.setItem('map_lng', data.lng); |
| 171 | 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 | 208 | window.location.href = "/gis-geoserver/landing"; |
| 179 | 209 | }) |
| 180 | 210 | .catch(error => { |
| 181 | - // LOGIN ERROR | |
| 182 | 211 | errorMsg.innerText = error.message; |
| 183 | 212 | errorMsg.style.display = 'block'; |
| 184 | - | |
| 185 | - // Restaurar UI | |
| 186 | 213 | btnText.style.display = 'block'; |
| 187 | 214 | spinner.style.display = 'none'; |
| 188 | 215 | document.getElementById('btn-login').disabled = false; | ... | ... |
src/main/resources/static/widgets.html
| ... | ... | @@ -201,9 +201,9 @@ |
| 201 | 201 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> |
| 202 | 202 | <script> |
| 203 | 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 | 208 | </script> |
| 209 | 209 | </body> | ... | ... |