// Navbar scroll effect window.addEventListener('scroll', function() { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } }); // Smooth scroll para TOC links document.querySelectorAll('.toc-container a').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const targetId = this.getAttribute('href'); const targetElement = document.querySelector(targetId); if (targetElement) { // Altura del navbar sticky + margen adicional const navbarHeight = document.querySelector('.navbar').offsetHeight; const offsetTop = targetElement.offsetTop - navbarHeight - 40; window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } }); }); // ScrollSpy mejorado para TOC function updateActiveSection() { const tocLinks = document.querySelectorAll('.toc-container a'); const navbar = document.querySelector('.navbar'); const navbarHeight = navbar ? navbar.offsetHeight : 0; // Obtener todas las secciones referenciadas en el TOC const sectionIds = Array.from(tocLinks).map(link => { const href = link.getAttribute('href'); return href ? href.substring(1) : null; }).filter(id => id !== null); const sections = sectionIds.map(id => document.getElementById(id)).filter(el => el !== null); // Calcular qué sección está visible // Una sección está "activa" cuando su parte superior está por encima del punto de activación const scrollPosition = window.scrollY + navbarHeight + 100; // Punto de activación let activeSection = null; // Recorrer las secciones de arriba hacia abajo for (let i = 0; i < sections.length; i++) { const section = sections[i]; const sectionTop = section.offsetTop; // Si hemos pasado esta sección, es la activa if (scrollPosition >= sectionTop) { activeSection = section.getAttribute('id'); } else { // Ya no hay más secciones visibles break; } } // Si no hay sección activa y estamos al inicio, activar la primera if (!activeSection && sections.length > 0 && window.scrollY < sections[0].offsetTop) { activeSection = sections[0].getAttribute('id'); } // Actualizar clases active en los links del TOC tocLinks.forEach(link => { link.classList.remove('active'); const href = link.getAttribute('href'); if (href === '#' + activeSection) { link.classList.add('active'); } }); } // Ejecutar al hacer scroll window.addEventListener('scroll', updateActiveSection); // Ejecutar al cargar la página document.addEventListener('DOMContentLoaded', updateActiveSection); // A/B Test para CTA - Rotación en cada carga de página document.addEventListener('DOMContentLoaded', function() { // Selección aleatoria 50/50 entre A y B en cada carga const ctaVariant = Math.random() < 0.5 ? 'A' : 'B'; // Mostrar la variante seleccionada if (ctaVariant === 'A') { document.querySelector('.cta-variant-a').style.display = 'block'; } else { document.querySelector('.cta-variant-b').style.display = 'block'; } // Tracking de clicks en el CTA (opcional, para Google Analytics) document.querySelectorAll('.cta-button').forEach(button => { button.addEventListener('click', function(e) { const variant = this.getAttribute('data-cta-variant'); // Si tienes Google Analytics instalado: if (typeof gtag !== 'undefined') { gtag('event', 'cta_click', { 'event_category': 'CTA', 'event_label': 'Variant_' + variant, 'value': variant }); } // Log en consola para verificar console.log('CTA clicked - Variant: ' + variant); }); }); // ========================================== // CARGAR MODAL DE CONTACTO DINÁMICAMENTE // ========================================== loadContactModal(); // ========================================== // INICIALIZAR FORMULARIO DE FOOTER // ========================================== initFooterContactForm(); }); /** * Carga el modal de contacto desde un archivo externo */ function loadContactModal() { fetch('modal-contact.html') .then(response => { if (!response.ok) { throw new Error('No se pudo cargar el modal de contacto'); } return response.text(); }) .then(html => { // Insertar el HTML del modal en el contenedor document.getElementById('modalContainer').innerHTML = html; // Inicializar el formulario de contacto initContactForm(); }) .catch(error => { console.error('Error cargando el modal:', error); }); } /** * Inicializa el formulario de contacto con validación y envío a webhook */ function initContactForm() { const contactForm = document.getElementById('contactForm'); if (!contactForm) { console.error('Formulario de contacto no encontrado'); return; } contactForm.addEventListener('submit', async function(e) { e.preventDefault(); // Obtener datos del formulario const formData = { fullName: document.getElementById('fullName').value.trim(), company: document.getElementById('company').value.trim(), whatsapp: document.getElementById('whatsapp').value.trim(), email: document.getElementById('email').value.trim(), comments: document.getElementById('comments').value.trim(), timestamp: new Date().toISOString(), source: 'APU Website - Let\'s Talk Button' }; // Validación básica if (!formData.fullName || !formData.whatsapp || !formData.email) { showFormMessage('Por favor completa todos los campos obligatorios', 'danger'); return; } // Validar email const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { showFormMessage('Por favor ingresa un correo electrónico válido', 'danger'); return; } // Validar WhatsApp (básico) const whatsappRegex = /^\+?[0-9]{10,15}$/; if (!whatsappRegex.test(formData.whatsapp.replace(/\s/g, ''))) { showFormMessage('Por favor ingresa un número de WhatsApp válido', 'danger'); return; } // Deshabilitar botón mientras se envía const submitButton = contactForm.querySelector('button[type="submit"]'); const originalButtonText = submitButton.innerHTML; submitButton.disabled = true; submitButton.innerHTML = 'Enviando...'; try { // IMPORTANTE: Reemplaza esta URL con tu webhook real const WEBHOOK_URL = 'https://tu-webhook.com/contacto'; const response = await fetch(WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); if (response.ok) { showFormMessage('¡Mensaje enviado exitosamente! Nos pondremos en contacto pronto.', 'success'); contactForm.reset(); // Cerrar modal después de 2 segundos setTimeout(() => { const modalElement = document.getElementById('contactModal'); const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.hide(); } }, 2000); // Tracking de conversión (si tienes Google Analytics) if (typeof gtag !== 'undefined') { gtag('event', 'form_submission', { 'event_category': 'Contact Form', 'event_label': 'Contact Form Submitted', 'value': 1 }); } } else { throw new Error('Error al enviar el formulario'); } } catch (error) { console.error('Error:', error); showFormMessage('Hubo un error al enviar el mensaje. Por favor intenta nuevamente.', 'danger'); } finally { // Rehabilitar botón submitButton.disabled = false; submitButton.innerHTML = originalButtonText; } }); } /** * Muestra mensajes de éxito o error en el formulario */ function showFormMessage(message, type) { const messageDiv = document.getElementById('formMessage'); messageDiv.className = `mt-3 alert alert-${type}`; messageDiv.textContent = message; messageDiv.style.display = 'block'; // Ocultar mensaje después de 5 segundos setTimeout(() => { messageDiv.style.display = 'none'; }, 5000); } /** * Inicializa el formulario de contacto del footer */ function initFooterContactForm() { const footerForm = document.getElementById('footerContactForm'); if (!footerForm) { console.error('Formulario de footer no encontrado'); return; } footerForm.addEventListener('submit', async function(e) { e.preventDefault(); // Obtener datos del formulario const formData = { fullName: document.getElementById('footerFullName').value.trim(), company: document.getElementById('footerCompany').value.trim(), whatsapp: document.getElementById('footerWhatsapp').value.trim(), email: document.getElementById('footerEmail').value.trim(), comments: document.getElementById('footerComments').value.trim(), timestamp: new Date().toISOString(), source: 'APU Website - Footer Contact Form' }; // Validación básica if (!formData.fullName || !formData.whatsapp || !formData.email) { showFooterFormMessage('Por favor completa todos los campos obligatorios', 'danger'); return; } // Validar email const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { showFooterFormMessage('Por favor ingresa un correo electrónico válido', 'danger'); return; } // Validar WhatsApp (básico) const whatsappRegex = /^\+?[0-9]{10,15}$/; if (!whatsappRegex.test(formData.whatsapp.replace(/\s/g, ''))) { showFooterFormMessage('Por favor ingresa un número de WhatsApp válido', 'danger'); return; } // Deshabilitar botón mientras se envía const submitButton = footerForm.querySelector('button[type="submit"]'); const originalButtonText = submitButton.innerHTML; submitButton.disabled = true; submitButton.innerHTML = 'Enviando...'; try { // IMPORTANTE: Reemplaza esta URL con tu webhook real const WEBHOOK_URL = 'https://tu-webhook.com/contacto'; const response = await fetch(WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); if (response.ok) { showFooterFormMessage('¡Mensaje enviado exitosamente! Nos pondremos en contacto pronto.', 'success'); footerForm.reset(); // Tracking de conversión (si tienes Google Analytics) if (typeof gtag !== 'undefined') { gtag('event', 'form_submission', { 'event_category': 'Footer Contact Form', 'event_label': 'Footer Form Submitted', 'value': 1 }); } } else { throw new Error('Error al enviar el formulario'); } } catch (error) { console.error('Error:', error); showFooterFormMessage('Hubo un error al enviar el mensaje. Por favor intenta nuevamente.', 'danger'); } finally { // Rehabilitar botón submitButton.disabled = false; submitButton.innerHTML = originalButtonText; } }); } /** * Muestra mensajes de éxito o error en el formulario del footer */ function showFooterFormMessage(message, type) { const messageDiv = document.getElementById('footerFormMessage'); messageDiv.className = `mt-2 alert alert-${type}`; messageDiv.textContent = message; messageDiv.style.display = 'block'; // Ocultar mensaje después de 5 segundos setTimeout(() => { messageDiv.style.display = 'none'; }, 5000); }