document.addEventListener("DOMContentLoaded", () => { // Mostrar errores del servidor si existen if (serverErrors.length > 0) mostrarErrores(serverErrors); if (serverSuccess.length > 0) mostrarSuccess(serverSuccess); const form = document.getElementById("form-login"); const usuarioInput = document.querySelector("input[name='usuario']"); const passInput = document.querySelector("input[name='password']"); if (!form || !usuarioInput || !passInput) return; // Protección básica de intentos (localStorage) function verificarIntentos() { const ahora = Date.now(); const intentosData = JSON.parse(localStorage.getItem("login_intentos")) || { c: 0, t: ahora }; if (ahora - intentosData.t > 60000) { intentosData.c = 0; intentosData.t = ahora; } localStorage.setItem("login_intentos", JSON.stringify(intentosData)); return intentosData; } // Funciones utilitarias function isEmpty(value) { return !value || value.trim() === ''; } function hasNoSpaces(value) { return !value.includes(' '); } function isValidUsername(value) { return /^(?=.*[a-zA-Z])(?!.*[._]{2})[a-zA-Z0-9._]{5,30}$/.test(value); } function isValidPassword(value) { return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$/.test(value); } form.addEventListener("submit", (e) => { let errores = []; const usuario = usuarioInput.value.trim(); const password = passInput.value.trim(); const intentos = verificarIntentos(); usuarioInput.classList.remove("is-invalid"); passInput.classList.remove("is-invalid"); // INTENTOS (se verifica primero para cortar rápido) if (intentos.c >= 3) { e.preventDefault(); mostrarErrores(["Demasiados intentos. Espere 1 minuto antes de volver a intentar."]); return; } // USUARIO if (isEmpty(usuario)) { errores.push("El usuario es obligatorio."); usuarioInput.classList.add("is-invalid"); } else if (!isValidUsername(usuario)) { errores.push("El usuario debe tener 5–30 caracteres, incluir al menos una letra y solo usar letras, números, puntos o guiónes bajos no consecutivos."); usuarioInput.classList.add("is-invalid"); } // PASSWORD if (isEmpty(password)) { errores.push("La contraseña es obligatoria."); passInput.classList.add("is-invalid"); } else if (!hasNoSpaces(password)) { errores.push("La contraseña no puede contener espacios."); passInput.classList.add("is-invalid"); } else if (!isValidPassword(password)) { errores.push("La contraseña debe tener al menos una mayúscula, una minúscula y un número, y mínimo 6 caracteres."); passInput.classList.add("is-invalid"); } if (errores.length > 0) { e.preventDefault(); mostrarErrores(errores); intentos.c++; localStorage.setItem("login_intentos", JSON.stringify(intentos)); return; } localStorage.removeItem("login_intentos"); }); // FUNCIÓN PARA MOSTRAR ERRORES function mostrarErrores(errores) { // Buscar si hay un toast previo const viejo = document.getElementById("toastErrores"); // Si existe, se elimina (evita duplicados) if (viejo) viejo.remove(); // Crea el contenedor del toast const toast = document.createElement("div"); // Se le asigna ID para identificarlo toast.id = "toastErrores"; // Clases de estilo (Bootstrap + CSS) toast.className = "toast-flotante alert alert-danger"; // Inserta el contenido dinámico (lista de errores) toast.innerHTML = ` Se encontraron errores: `; // Agrega el toast al body (lo hace visible) document.body.appendChild(toast); // Auto-cierre setTimeout(() => { // Agrega clase para animación de salida (CSS) toast.classList.add("toast-out"); // Elimina el elemento después de la animación setTimeout(() => { toast.remove(); // Elimina el DOM }, 350); }, 5000); // Visible durante 5 segundos } // FUNCIÓN PARA MOSTRAR SUCCESS function mostrarSuccess(mensajes) { // Elimina toast previo de éxito si existe const viejo = document.getElementById("toastSuccess"); if (viejo) viejo.remove(); // Crea el contenedor del toast const toast = document.createElement("div"); // Se le asigna ID único toast.id = "toastSuccess"; // Estilo toast.className = "toast-flotante alert alert-success"; // Inserta mensajes dinámicos toast.innerHTML = ` Mensajes de éxito: `; // Agrega al DOM document.body.appendChild(toast); // Auto-cierre a los 5 segundos setTimeout(() => { // Animación de salida toast.classList.add("toast-out"); // Eliminación final setTimeout(() => { toast.remove(); }, 350); }, 5000); } });