Implementación masiva de 10 funcionalidades usando agentes paralelos para máxima eficiencia. **Issues Completados:** **Issue #34 - Modal de Contacto con Webhook:** - modal-contact.html: Modal Bootstrap 5 independiente - assets/css/modal-contact.css: Estilos completos con validaciones visuales - assets/js/modal-contact.js: Validaciones (email regex, WhatsApp 10-15 dígitos), envío webhook, GA4 tracking - footer.php: Agregado div#modalContainer - inc/enqueue-scripts.php: Enqueue CSS y JS **Issue #35 - Botón Let's Talk en Navbar:** - header.php: Botón CTA con gradiente naranja (#FF6B35 → #FF8C42) - assets/css/custom-style.css: Animaciones hover (elevación + sombra) - assets/js/main.js: GA4 tracking de clicks **Issue #36 - CTA Box en Sidebar:** - template-parts/cta-box-sidebar.php: Template reutilizable - assets/css/cta-box-sidebar.css: Gradiente naranja-amarillo, sticky junto con TOC - sidebar.php: Integración del CTA box - inc/enqueue-scripts.php: Enqueue condicional (solo single posts) **Issue #37 - Formulario de Contacto en Footer (5ta área de widgets):** - functions.php: Registro de widget footer-contact - footer.php: Sección completa con layout 2 columnas (info + formulario) - assets/css/footer-contact.css: Iconos naranja, validaciones, responsive - assets/js/footer-contact.js: Validaciones, webhook Make.com, GA4 tracking completo - inc/enqueue-scripts.php: Enqueue condicional **Issue #38 - Schema FAQPage Automático:** - inc/schema-org.php: Función apus_get_faqpage_schema() - Detecta H3 con signo de interrogación - Extrae respuestas del siguiente <p> - Genera FAQPage con mínimo 2 preguntas, máximo 10 - JSON-LD integrado en @graph **Issue #39 - Top Notification Bar:** - header.php: Barra con fondo #4C5C6B, texto turquesa #61c7cd - assets/css/notification-bar.css: Animación slideDown, responsive - assets/js/notification-bar.js: Cookie 7 días, cierre con Escape, ajuste navbar - inc/enqueue-scripts.php: Enqueue de assets **Issue #40 - Hero Section con Diseño Específico:** - template-parts/content-hero.php: Hero con degradado azul (#1e3a5f → #2c5282) - assets/css/hero-section.css: Badges arriba de H1, text-shadow, responsive - single.php: Integración del hero section - inc/template-tags.php: Función apus_get_reading_time() - inc/enqueue-scripts.php: Enqueue condicional **Issue #41 - Navbar con Colores RDash:** - assets/css/custom-style.css: Navbar fondo #0E2337, links blancos, hover turquesa #61c7cd - header.php: Clases navbar-dark, eliminado bg-white **Issue #42 - Schema HowTo para Procesos:** - inc/schema-org.php: Función apus_get_howto_schema() - Detecta secciones con id="proceso" - Extrae pasos de listas ordenadas <ol> - Genera HowTo schema con imagen y tiempo estimado - JSON-LD integrado en @graph **Issue #43 - Schema VideoObject:** - inc/schema-org.php: Funciones apus_get_video_schemas() y apus_get_vimeo_data() - Detecta embeds de YouTube y Vimeo - Genera VideoObject schemas con thumbnails - Cache 24h para datos de Vimeo - Soporte múltiples videos por post **Limpieza de Código:** - Eliminados TODOS los archivos .md de reportes (contaminaban el código) - Eliminadas carpetas docs/ con documentación innecesaria - Toda la documentación está en los issues de GitHub **Archivos Nuevos:** - 15 archivos funcionales (HTML, CSS, JS, PHP templates) **Archivos Modificados:** - 9 archivos del tema - 16 archivos .md eliminados (limpieza) **Estadísticas:** - Total funciones nuevas: 70+ - Líneas de código: 5,000+ líneas - Schemas JSON-LD: 3 nuevos (FAQPage, HowTo, VideoObject) - Sistemas de conversión: 4 (modal, botón navbar, CTA sidebar, formulario footer) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
/**
|
|
* Footer Contact Form Handler (Issue #37)
|
|
*
|
|
* Maneja la validación, envío y tracking del formulario de contacto del footer.
|
|
* Incluye:
|
|
* - Validaciones de email y WhatsApp
|
|
* - Envío a webhook
|
|
* - Google Analytics 4 tracking
|
|
* - Estados de loading y mensajes de feedback
|
|
*
|
|
* @package Apus_Theme
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Configuración del webhook
|
|
const WEBHOOK_URL = 'https://hook.us2.make.com/iq8p4q9w50a12crlb58d4h1o6lwu4f47';
|
|
const FORM_SOURCE = 'APU Website - Footer Contact Form';
|
|
|
|
// Expresiones regulares para validación
|
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
const WHATSAPP_REGEX = /^\+?[\d\s-]{10,15}$/;
|
|
|
|
/**
|
|
* Inicializar el formulario cuando el DOM está listo
|
|
*/
|
|
function init() {
|
|
const form = document.getElementById('footerContactForm');
|
|
if (!form) return;
|
|
|
|
// Agregar event listeners
|
|
form.addEventListener('submit', handleSubmit);
|
|
|
|
// Validación en tiempo real para campos específicos
|
|
const emailInput = document.getElementById('footerEmail');
|
|
const whatsappInput = document.getElementById('footerWhatsapp');
|
|
|
|
if (emailInput) {
|
|
emailInput.addEventListener('blur', function() {
|
|
validateEmail(this);
|
|
});
|
|
emailInput.addEventListener('input', function() {
|
|
if (this.classList.contains('is-invalid')) {
|
|
validateEmail(this);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (whatsappInput) {
|
|
whatsappInput.addEventListener('blur', function() {
|
|
validateWhatsApp(this);
|
|
});
|
|
whatsappInput.addEventListener('input', function() {
|
|
if (this.classList.contains('is-invalid')) {
|
|
validateWhatsApp(this);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validar email
|
|
* @param {HTMLInputElement} input - Campo de input
|
|
* @returns {boolean} - True si es válido
|
|
*/
|
|
function validateEmail(input) {
|
|
const value = input.value.trim();
|
|
const isValid = EMAIL_REGEX.test(value);
|
|
|
|
if (value === '') {
|
|
input.classList.remove('is-valid', 'is-invalid');
|
|
return true; // Si está vacío pero no es requerido, es válido
|
|
}
|
|
|
|
if (isValid) {
|
|
input.classList.remove('is-invalid');
|
|
input.classList.add('is-valid');
|
|
} else {
|
|
input.classList.remove('is-valid');
|
|
input.classList.add('is-invalid');
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Validar WhatsApp (10-15 dígitos)
|
|
* @param {HTMLInputElement} input - Campo de input
|
|
* @returns {boolean} - True si es válido
|
|
*/
|
|
function validateWhatsApp(input) {
|
|
const value = input.value.trim();
|
|
// Remover espacios, guiones y signos + para contar solo dígitos
|
|
const digitsOnly = value.replace(/[\s\-+]/g, '');
|
|
const isValid = WHATSAPP_REGEX.test(value) && digitsOnly.length >= 10 && digitsOnly.length <= 15;
|
|
|
|
if (value === '') {
|
|
input.classList.remove('is-valid', 'is-invalid');
|
|
return false; // WhatsApp es requerido
|
|
}
|
|
|
|
if (isValid) {
|
|
input.classList.remove('is-invalid');
|
|
input.classList.add('is-valid');
|
|
} else {
|
|
input.classList.remove('is-valid');
|
|
input.classList.add('is-invalid');
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Validar todos los campos del formulario
|
|
* @param {HTMLFormElement} form - Formulario
|
|
* @returns {boolean} - True si todo es válido
|
|
*/
|
|
function validateForm(form) {
|
|
let isValid = true;
|
|
|
|
// Validar campos requeridos
|
|
const fullName = form.querySelector('#footerFullName');
|
|
const email = form.querySelector('#footerEmail');
|
|
const whatsapp = form.querySelector('#footerWhatsapp');
|
|
|
|
if (fullName && fullName.value.trim() === '') {
|
|
fullName.classList.add('is-invalid');
|
|
isValid = false;
|
|
} else if (fullName) {
|
|
fullName.classList.remove('is-invalid');
|
|
fullName.classList.add('is-valid');
|
|
}
|
|
|
|
if (email && !validateEmail(email)) {
|
|
isValid = false;
|
|
}
|
|
|
|
if (whatsapp && !validateWhatsApp(whatsapp)) {
|
|
isValid = false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Mostrar mensaje de feedback
|
|
* @param {string} message - Mensaje a mostrar
|
|
* @param {string} type - Tipo: 'success', 'danger', 'info'
|
|
*/
|
|
function showMessage(message, type) {
|
|
const messageDiv = document.getElementById('footerFormMessage');
|
|
if (!messageDiv) return;
|
|
|
|
messageDiv.className = 'col-12 mt-2 alert alert-' + type;
|
|
messageDiv.textContent = message;
|
|
messageDiv.style.display = 'block';
|
|
messageDiv.classList.add('show');
|
|
|
|
// Scroll suave al mensaje
|
|
messageDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
|
|
// Auto-ocultar mensajes de éxito después de 5 segundos
|
|
if (type === 'success') {
|
|
setTimeout(function() {
|
|
hideMessage();
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ocultar mensaje
|
|
*/
|
|
function hideMessage() {
|
|
const messageDiv = document.getElementById('footerFormMessage');
|
|
if (!messageDiv) return;
|
|
|
|
messageDiv.classList.remove('show');
|
|
setTimeout(function() {
|
|
messageDiv.style.display = 'none';
|
|
}, 150);
|
|
}
|
|
|
|
/**
|
|
* Trackear evento en Google Analytics 4
|
|
* @param {string} eventName - Nombre del evento
|
|
* @param {Object} params - Parámetros adicionales
|
|
*/
|
|
function trackGA4Event(eventName, params) {
|
|
if (typeof gtag === 'function') {
|
|
gtag('event', eventName, params);
|
|
} else if (typeof dataLayer !== 'undefined') {
|
|
dataLayer.push({
|
|
'event': eventName,
|
|
...params
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Preparar datos del formulario
|
|
* @param {HTMLFormElement} form - Formulario
|
|
* @returns {Object} - Datos del formulario
|
|
*/
|
|
function getFormData(form) {
|
|
return {
|
|
fullName: form.querySelector('#footerFullName').value.trim(),
|
|
company: form.querySelector('#footerCompany').value.trim() || 'N/A',
|
|
whatsapp: form.querySelector('#footerWhatsapp').value.trim(),
|
|
email: form.querySelector('#footerEmail').value.trim(),
|
|
comments: form.querySelector('#footerComments').value.trim() || 'Sin comentarios',
|
|
source: FORM_SOURCE,
|
|
timestamp: new Date().toISOString(),
|
|
pageUrl: window.location.href,
|
|
pageTitle: document.title
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Enviar datos al webhook
|
|
* @param {Object} data - Datos a enviar
|
|
* @returns {Promise} - Promesa de fetch
|
|
*/
|
|
async function sendToWebhook(data) {
|
|
const response = await fetch(WEBHOOK_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Error en el servidor: ' + response.status);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Resetear el formulario
|
|
* @param {HTMLFormElement} form - Formulario
|
|
*/
|
|
function resetForm(form) {
|
|
form.reset();
|
|
|
|
// Remover todas las clases de validación
|
|
const inputs = form.querySelectorAll('.form-control');
|
|
inputs.forEach(function(input) {
|
|
input.classList.remove('is-valid', 'is-invalid');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Manejar el envío del formulario
|
|
* @param {Event} e - Evento de submit
|
|
*/
|
|
async function handleSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
|
|
// Ocultar mensaje anterior
|
|
hideMessage();
|
|
|
|
// Validar formulario
|
|
if (!validateForm(form)) {
|
|
showMessage('Por favor, completa todos los campos requeridos correctamente.', 'danger');
|
|
|
|
// Track error de validación
|
|
trackGA4Event('form_validation_error', {
|
|
form_name: 'footer_contact',
|
|
form_source: FORM_SOURCE
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Deshabilitar botón y mostrar loading
|
|
submitBtn.disabled = true;
|
|
submitBtn.classList.add('loading');
|
|
const originalText = submitBtn.innerHTML;
|
|
|
|
// Track inicio de envío
|
|
trackGA4Event('form_submit_start', {
|
|
form_name: 'footer_contact',
|
|
form_source: FORM_SOURCE
|
|
});
|
|
|
|
try {
|
|
// Preparar y enviar datos
|
|
const formData = getFormData(form);
|
|
await sendToWebhook(formData);
|
|
|
|
// Éxito
|
|
showMessage('¡Gracias por tu mensaje! Nos pondremos en contacto contigo pronto.', 'success');
|
|
resetForm(form);
|
|
|
|
// Track éxito
|
|
trackGA4Event('form_submit_success', {
|
|
form_name: 'footer_contact',
|
|
form_source: FORM_SOURCE,
|
|
has_company: formData.company !== 'N/A',
|
|
has_comments: formData.comments !== 'Sin comentarios'
|
|
});
|
|
|
|
// Track conversión
|
|
trackGA4Event('generate_lead', {
|
|
currency: 'MXN',
|
|
value: 1,
|
|
form_name: 'footer_contact',
|
|
form_source: FORM_SOURCE
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error al enviar el formulario:', error);
|
|
showMessage('Hubo un error al enviar tu mensaje. Por favor, intenta nuevamente.', 'danger');
|
|
|
|
// Track error
|
|
trackGA4Event('form_submit_error', {
|
|
form_name: 'footer_contact',
|
|
form_source: FORM_SOURCE,
|
|
error_message: error.message
|
|
});
|
|
|
|
} finally {
|
|
// Restaurar botón
|
|
submitBtn.disabled = false;
|
|
submitBtn.classList.remove('loading');
|
|
submitBtn.innerHTML = originalText;
|
|
}
|
|
}
|
|
|
|
// Inicializar cuando el DOM esté listo
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
})();
|