Commit a3050e01d5f5687036af2b6114d0a38d46be2011

Authored by Antigravity AI
1 parent fc0b710a

Hito: Login Dinámico (.254), Logos Base64 y Responsables en Navbar

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>
... ...
GitLab Appliance - Powered by TurnKey Linux