Implementar Issues #34-43 - Funcionalidades de conversión, UI/UX y SEO avanzadas
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>
This commit is contained in:
343
wp-content/themes/apus-theme/assets/js/footer-contact.js
Normal file
343
wp-content/themes/apus-theme/assets/js/footer-contact.js
Normal file
@@ -0,0 +1,343 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user