/** * Módulo de gestión del formulario de cotización de vuelos * @version 2.0.0 */ // Variables globales del módulo let itiPhoneInput = null; let currentSubmitLock = false; /** * Inicializa el módulo cuando el DOM está listo */ document.addEventListener("DOMContentLoaded", () => { initPhoneInput(); initFormValidation(); initShareButtons(); initScrollAnimations(); }); /** * Inicializa el input de teléfono internacional */ function initPhoneInput() { const phoneInput = document.querySelector("#telefono"); if (phoneInput && typeof intlTelInput !== "undefined") { itiPhoneInput = window.intlTelInput(phoneInput, { initialCountry: "es", preferredCountries: ["es", "mx", "co", "ar", "pe", "cl"], separateDialCode: true, utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.min.js", autoPlaceholder: "aggressive", formatOnDisplay: true, }); // Limpiar error al escribir phoneInput.addEventListener("input", () => { clearFieldError("telefono"); }); } } /** * Inicializa la validación del formulario */ function initFormValidation() { const form = document.getElementById("cotizacionForm"); if (!form) return; // Agregar listener al formulario form.addEventListener("submit", submitForm); // Validación en tiempo real const inputs = ["nombre", "email", "fecha"]; inputs.forEach((fieldId) => { const input = document.getElementById(fieldId); if (input) { input.addEventListener("blur", () => validateField(fieldId)); input.addEventListener("input", () => { clearFieldError(fieldId); }); } }); } /** * Valida un campo específico * @param {string} fieldId - ID del campo a validar * @returns {boolean} - True si es válido */ function validateField(fieldId) { const input = document.getElementById(fieldId); if (!input) return true; let isValid = true; let errorMessage = ""; switch (fieldId) { case "nombre": const nombre = input.value.trim(); if (!nombre) { isValid = false; errorMessage = "El nombre es requerido"; } else if (nombre.length < 3) { isValid = false; errorMessage = "El nombre debe tener al menos 3 caracteres"; } else if (!/^[a-zA-ZáéíóúñÁÉÍÓÚÑ\s]+$/.test(nombre)) { isValid = false; errorMessage = "El nombre solo puede contener letras"; } break; case "email": const email = input.value.trim(); const emailRegex = /^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,}$/; if (!email) { isValid = false; errorMessage = "El correo electrónico es requerido"; } else if (!emailRegex.test(email)) { isValid = false; errorMessage = "Ingresa un correo electrónico válido"; } break; case "fecha": const fecha = input.value; if (!fecha) { isValid = false; errorMessage = "La fecha es requerida"; } else { const selectedDate = new Date(fecha); const today = new Date(); today.setHours(0, 0, 0, 0); if (selectedDate < today) { isValid = false; errorMessage = "La fecha no puede ser anterior a hoy"; } } break; } if (!isValid) { showFieldError(fieldId, errorMessage); } else { clearFieldError(fieldId); } return isValid; } /** * Muestra error en un campo * @param {string} fieldId - ID del campo * @param {string} message - Mensaje de error */ function showFieldError(fieldId, message) { const input = document.getElementById(fieldId); const errorDiv = document.getElementById(`${fieldId}-error`); if (input) { input.classList.add("error"); } if (errorDiv) { errorDiv.textContent = message; errorDiv.classList.add("show"); } } /** * Limpia el error de un campo * @param {string} fieldId - ID del campo */ function clearFieldError(fieldId) { const input = document.getElementById(fieldId); const errorDiv = document.getElementById(`${fieldId}-error`); if (input) { input.classList.remove("error"); } if (errorDiv) { errorDiv.classList.remove("show"); errorDiv.textContent = ""; } } /** * Valida todo el formulario * @returns {boolean} - True si el formulario es válido */ function validateForm() { let isValid = true; const fields = ["nombre", "email", "fecha"]; for (const field of fields) { if (!validateField(field)) { isValid = false; } } // Validar teléfono const telefono = getFullPhoneNumber(); if (!telefono || telefono.trim() === "") { showFieldError("telefono", "El número de teléfono es requerido"); isValid = false; } else if (telefono.length < 8) { showFieldError("telefono", "Ingresa un número de teléfono válido"); isValid = false; } else { clearFieldError("telefono"); } return isValid; } /** * Obtiene el número de teléfono completo con código de país * @returns {string} - Número completo */ function getFullPhoneNumber() { if (itiPhoneInput) { const number = itiPhoneInput.getNumber(); return number || ""; } const phoneInput = document.getElementById("telefono"); return phoneInput ? phoneInput.value.trim() : ""; } /** * Maneja el envío del formulario * @param {Event} event - Evento del formulario */ async function submitForm(event) { if (event) { event.preventDefault(); } // Evitar envíos múltiples if (currentSubmitLock) { return; } // Validar formulario if (!validateForm()) { const firstError = document.querySelector(".form-control.error"); if (firstError) { firstError.scrollIntoView({ behavior: "smooth", block: "center", }); firstError.focus(); } return; } currentSubmitLock = true; const form = document.getElementById("cotizacionForm"); const submitBtn = document.getElementById("submitCotizacion"); // Deshabilitar botón y mostrar loading const originalText = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.classList.add("btn-loading"); submitBtn.innerHTML = "Enviando..."; try { const telefonoCompleto = getFullPhoneNumber(); const formData = { _token: document.querySelector('input[name="_token"]').value, id_vuelo: document.querySelector('input[name="id_vuelo"]').value, nombre_vuelo: document.querySelector('input[name="nombre_vuelo"]') .value, slug: document.querySelector('input[name="slug"]').value, nombre: document.getElementById("nombre").value.trim(), email: document.getElementById("email").value.trim(), telefono: telefonoCompleto, pasajeros: document.getElementById("pasajeros").value, fecha: document.getElementById("fecha").value, }; const slug = formData.slug; const url = `/cotizar-vuelo/${slug}`; const response = await axios.post(url, formData, { headers: { "Content-Type": "application/json", "X-CSRF-TOKEN": formData._token, }, timeout: 30000, // 30 segundos }); if (response.status === 200) { let mensaje = "Pronto nos pondremos en contacto contigo para confirmar tu cotización."; if (response.data && response.data.mensaje) { mensaje = response.data.mensaje; } else if (response.data && response.data.message) { mensaje = response.data.message; } await Swal.fire({ icon: "success", title: "¡Cotización enviada!", text: mensaje, confirmButtonColor: "#2563eb", confirmButtonText: "Aceptar", timer: 5000, timerProgressBar: true, showClass: { popup: "animate__animated animate__fadeInDown", }, hideClass: { popup: "animate__animated animate__fadeOutUp", }, }); // Resetear formulario form.reset(); if (itiPhoneInput) { itiPhoneInput.setNumber(""); } const fechaInput = document.getElementById("fecha"); if (fechaInput && fechaInput._flatpickr) { fechaInput._flatpickr.clear(); } // Limpiar errores ["nombre", "email", "fecha", "telefono"].forEach(clearFieldError); // Limpiar validación visual document.querySelectorAll(".form-control").forEach((input) => { input.classList.remove("error"); }); } else { throw new Error( response.data?.message || "Error en la respuesta del servidor", ); } } catch (error) { console.error("Error en el envío:", error); let errorMessage = "Ocurrió un error al enviar la cotización. Por favor intenta nuevamente."; if (error.response) { console.log("Respuesta de error:", error.response.data); if (error.response.data && error.response.data.message) { errorMessage = error.response.data.message; } else if (error.response.data && error.response.data.error) { errorMessage = error.response.data.error; } else if (typeof error.response.data === "string") { errorMessage = error.response.data; } else if (error.response.status === 422) { errorMessage = "Por favor verifica los datos ingresados"; } else if (error.response.status === 429) { errorMessage = "Demasiadas solicitudes. Por favor espera un momento."; } else if (error.response.status >= 500) { errorMessage = "Error en el servidor. Por favor intenta más tarde."; } } else if (error.request) { errorMessage = "No se pudo conectar con el servidor. Verifica tu conexión a internet."; } else if (error.message) { errorMessage = error.message; } await Swal.fire({ icon: "error", title: "Error", text: errorMessage, confirmButtonColor: "#2563eb", confirmButtonText: "Intentar de nuevo", }); } finally { currentSubmitLock = false; submitBtn.disabled = false; submitBtn.classList.remove("btn-loading"); submitBtn.innerHTML = originalText; } } /** * Inicializa los botones de compartir */ function initShareButtons() { const shareButtons = document.querySelectorAll(".share-btn"); const currentUrl = encodeURIComponent(window.location.href); const currentTitle = encodeURIComponent(document.title); shareButtons.forEach((btn) => { btn.addEventListener("click", async () => { const type = btn.dataset.share; let shareUrl = ""; switch (type) { case "facebook": shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${currentUrl}`; window.open(shareUrl, "_blank", "width=600,height=400"); break; case "twitter": shareUrl = `https://twitter.com/intent/tweet?text=${currentTitle}&url=${currentUrl}`; window.open(shareUrl, "_blank", "width=600,height=400"); break; case "whatsapp": shareUrl = `https://wa.me/?text=${currentTitle}%20${currentUrl}`; window.open(shareUrl, "_blank"); break; case "copy": try { await navigator.clipboard.writeText( window.location.href, ); Swal.fire({ icon: "success", title: "¡Enlace copiado!", text: "El enlace ha sido copiado al portapapeles", toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, }); } catch (err) { Swal.fire({ icon: "error", title: "Error", text: "No se pudo copiar el enlace", toast: true, position: "top-end", showConfirmButton: false, timer: 3000, }); } break; } }); }); } /** * Inicializa animaciones al hacer scroll */ function initScrollAnimations() { const elements = document.querySelectorAll(".section, .sidebar-card"); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.style.opacity = "1"; entry.target.style.transform = "translateY(0)"; observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: "0px 0px -50px 0px", }, ); elements.forEach((element) => { element.style.opacity = "0"; element.style.transform = "translateY(20px)"; element.style.transition = "all 0.6s ease-out"; observer.observe(element); }); } // Exportar funciones para debugging (opcional) if (typeof window !== "undefined") { window.FlightDetails = { validateForm, submitForm, getFullPhoneNumber, }; }