Files
roi-theme/wp-content/themes/apus-theme/assets/js/footer-contact.js
FrankZamora 2cc274d6e2 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>
2025-11-04 18:22:37 -06:00

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();
}
})();