Files
roi-theme/wp-content/themes/apus-theme/assets/js/modal-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

465 lines
16 KiB
JavaScript

/**
* Modal de Contacto - JavaScript
*
* Carga dinámica del modal, validaciones y envío a webhook
* Compatible con Bootstrap 5.3.2
*
* @package Apus_Theme
* @since 1.0.0
*/
(function() {
'use strict';
// =========================================================================
// CONFIGURACIÓN
// =========================================================================
/**
* URL del webhook para envío de formulario
* IMPORTANTE: Reemplaza con tu URL real de webhook
*
* Servicios recomendados:
* - Make (Integromat): https://www.make.com
* - Zapier: https://zapier.com
* - Webhook.site (testing): https://webhook.site
* - n8n (self-hosted): https://n8n.io
* - Pipedream: https://pipedream.com
*/
const WEBHOOK_URL = 'https://webhook.site/tu-url-aqui';
/**
* Expresión regular para validación de email
*/
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
/**
* Expresión regular para validación de WhatsApp
* Acepta 10-15 dígitos con o sin espacios/guiones
*/
const WHATSAPP_REGEX = /^[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,4}[-\s\.]?[0-9]{1,5}$/;
// =========================================================================
// INICIALIZACIÓN
// =========================================================================
/**
* Inicializa el modal cuando el DOM está listo
*/
document.addEventListener('DOMContentLoaded', function() {
loadContactModal();
});
// =========================================================================
// CARGA DINÁMICA DEL MODAL
// =========================================================================
/**
* Carga el HTML del modal dinámicamente desde archivo externo
*/
function loadContactModal() {
// Verificar si ya existe el contenedor del modal
let modalContainer = document.getElementById('modalContainer');
if (!modalContainer) {
// Crear contenedor si no existe
modalContainer = document.createElement('div');
modalContainer.id = 'modalContainer';
document.body.appendChild(modalContainer);
}
// Obtener la URL del tema desde WordPress
const themeUrl = typeof apusTheme !== 'undefined' && apusTheme.themeUrl
? apusTheme.themeUrl
: '/wp-content/themes/apus-theme';
// Cargar el HTML del modal
fetch(themeUrl + '/modal-contact.html')
.then(response => {
if (!response.ok) {
throw new Error('Error al cargar el modal: ' + response.status);
}
return response.text();
})
.then(html => {
modalContainer.innerHTML = html;
// Inicializar el formulario después de cargar el HTML
initContactForm();
console.log('Modal de contacto cargado exitosamente');
})
.catch(error => {
console.error('Error cargando el modal:', error);
});
}
// =========================================================================
// VALIDACIONES
// =========================================================================
/**
* Valida el campo de nombre completo
* @param {string} fullName - Nombre a validar
* @returns {Object} - {valid: boolean, message: string}
*/
function validateFullName(fullName) {
if (!fullName || fullName.trim().length === 0) {
return {
valid: false,
message: 'Por favor ingresa tu nombre completo'
};
}
if (fullName.trim().length < 3) {
return {
valid: false,
message: 'El nombre debe tener al menos 3 caracteres'
};
}
return { valid: true, message: '' };
}
/**
* Valida el campo de email
* @param {string} email - Email a validar
* @returns {Object} - {valid: boolean, message: string}
*/
function validateEmail(email) {
if (!email || email.trim().length === 0) {
return {
valid: false,
message: 'Por favor ingresa tu correo electrónico'
};
}
if (!EMAIL_REGEX.test(email.trim())) {
return {
valid: false,
message: 'Por favor ingresa un correo electrónico válido'
};
}
return { valid: true, message: '' };
}
/**
* Valida el campo de WhatsApp
* @param {string} whatsapp - Número de WhatsApp a validar
* @returns {Object} - {valid: boolean, message: string}
*/
function validateWhatsApp(whatsapp) {
if (!whatsapp || whatsapp.trim().length === 0) {
return {
valid: false,
message: 'Por favor ingresa tu número de WhatsApp'
};
}
// Remover todos los caracteres no numéricos excepto el +
const cleanNumber = whatsapp.replace(/[^\d+]/g, '');
const digitsOnly = cleanNumber.replace(/\+/g, '');
if (digitsOnly.length < 10 || digitsOnly.length > 15) {
return {
valid: false,
message: 'El número debe tener entre 10 y 15 dígitos'
};
}
if (!WHATSAPP_REGEX.test(whatsapp)) {
return {
valid: false,
message: 'Por favor ingresa un número de WhatsApp válido'
};
}
return { valid: true, message: '' };
}
/**
* Marca un campo como válido o inválido visualmente
* @param {HTMLElement} input - Elemento input
* @param {boolean} isValid - Si es válido o no
* @param {string} message - Mensaje de error (opcional)
*/
function setFieldValidation(input, isValid, message = '') {
if (isValid) {
input.classList.remove('is-invalid');
input.classList.add('is-valid');
} else {
input.classList.remove('is-valid');
input.classList.add('is-invalid');
// Actualizar mensaje de error
const feedback = input.nextElementSibling;
if (feedback && feedback.classList.contains('invalid-feedback')) {
feedback.textContent = message;
}
}
}
/**
* Limpia las validaciones visuales de un campo
* @param {HTMLElement} input - Elemento input
*/
function clearFieldValidation(input) {
input.classList.remove('is-valid', 'is-invalid');
}
// =========================================================================
// FORMULARIO
// =========================================================================
/**
* 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;
}
// Validación en tiempo real
const fullNameInput = document.getElementById('fullName');
const emailInput = document.getElementById('email');
const whatsappInput = document.getElementById('whatsapp');
if (fullNameInput) {
fullNameInput.addEventListener('blur', function() {
const validation = validateFullName(this.value);
setFieldValidation(this, validation.valid, validation.message);
});
fullNameInput.addEventListener('input', function() {
if (this.classList.contains('is-invalid')) {
const validation = validateFullName(this.value);
if (validation.valid) {
setFieldValidation(this, true);
}
}
});
}
if (emailInput) {
emailInput.addEventListener('blur', function() {
const validation = validateEmail(this.value);
setFieldValidation(this, validation.valid, validation.message);
});
emailInput.addEventListener('input', function() {
if (this.classList.contains('is-invalid')) {
const validation = validateEmail(this.value);
if (validation.valid) {
setFieldValidation(this, true);
}
}
});
}
if (whatsappInput) {
whatsappInput.addEventListener('blur', function() {
const validation = validateWhatsApp(this.value);
setFieldValidation(this, validation.valid, validation.message);
});
whatsappInput.addEventListener('input', function() {
if (this.classList.contains('is-invalid')) {
const validation = validateWhatsApp(this.value);
if (validation.valid) {
setFieldValidation(this, true);
}
}
});
}
// Manejo del envío del formulario
contactForm.addEventListener('submit', handleFormSubmit);
}
/**
* Maneja el envío del formulario
* @param {Event} e - Evento de submit
*/
async function handleFormSubmit(e) {
e.preventDefault();
// Obtener elementos del formulario
const fullNameInput = document.getElementById('fullName');
const companyInput = document.getElementById('company');
const whatsappInput = document.getElementById('whatsapp');
const emailInput = document.getElementById('email');
const commentsInput = document.getElementById('comments');
const submitButton = e.target.querySelector('button[type="submit"]');
// Validar todos los campos
const fullNameValidation = validateFullName(fullNameInput.value);
const emailValidation = validateEmail(emailInput.value);
const whatsappValidation = validateWhatsApp(whatsappInput.value);
// Marcar campos inválidos
setFieldValidation(fullNameInput, fullNameValidation.valid, fullNameValidation.message);
setFieldValidation(emailInput, emailValidation.valid, emailValidation.message);
setFieldValidation(whatsappInput, whatsappValidation.valid, whatsappValidation.message);
// Si algún campo es inválido, detener el envío
if (!fullNameValidation.valid || !emailValidation.valid || !whatsappValidation.valid) {
showFormMessage('Por favor corrige los errores en el formulario', 'danger');
// Hacer foco en el primer campo inválido
if (!fullNameValidation.valid) {
fullNameInput.focus();
} else if (!emailValidation.valid) {
emailInput.focus();
} else if (!whatsappValidation.valid) {
whatsappInput.focus();
}
return;
}
// Preparar datos del formulario
const formData = {
fullName: fullNameInput.value.trim(),
company: companyInput.value.trim(),
whatsapp: whatsappInput.value.trim(),
email: emailInput.value.trim(),
comments: commentsInput.value.trim(),
timestamp: new Date().toISOString(),
source: 'APU Website - Modal Contact Form'
};
// Deshabilitar botón y mostrar spinner
const originalButtonText = submitButton.innerHTML;
submitButton.disabled = true;
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Enviando...';
try {
// Enviar datos al webhook
const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
mode: 'no-cors' // Permite envío sin CORS (no podremos leer la respuesta)
});
// Como usamos no-cors, asumimos que el envío fue exitoso si no hay error
showFormMessage('¡Mensaje enviado exitosamente! Nos pondremos en contacto pronto.', 'success');
// Resetear formulario
e.target.reset();
// Limpiar validaciones visuales
clearFieldValidation(fullNameInput);
clearFieldValidation(emailInput);
clearFieldValidation(whatsappInput);
// Tracking de Google Analytics 4
if (typeof gtag !== 'undefined') {
gtag('event', 'form_submission', {
event_category: 'Contact Form',
event_label: 'Modal Contact Form Submitted',
value: 1
});
}
// Cerrar modal después de 2 segundos
setTimeout(() => {
const modalElement = document.getElementById('contactModal');
if (modalElement && typeof bootstrap !== 'undefined') {
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) {
modal.hide();
}
}
}, 2000);
} catch (error) {
console.error('Error al enviar el formulario:', 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 un mensaje de feedback en el formulario
* @param {string} message - Mensaje a mostrar
* @param {string} type - Tipo de alerta (success, danger, warning, info)
*/
function showFormMessage(message, type) {
const messageDiv = document.getElementById('formMessage');
if (!messageDiv) {
console.error('Contenedor de mensajes no encontrado');
return;
}
messageDiv.className = `mt-3 alert alert-${type}`;
messageDiv.textContent = message;
messageDiv.style.display = 'block';
messageDiv.setAttribute('role', 'alert');
// Anunciar mensaje a lectores de pantalla
messageDiv.setAttribute('aria-live', 'polite');
// Ocultar mensaje después de 5 segundos (excepto mensajes de éxito)
if (type !== 'success') {
setTimeout(() => {
messageDiv.style.display = 'none';
}, 5000);
}
}
// =========================================================================
// EVENTOS DEL MODAL
// =========================================================================
/**
* Limpia el formulario cuando se cierra el modal
*/
document.addEventListener('hidden.bs.modal', function (event) {
if (event.target.id === 'contactModal') {
const contactForm = document.getElementById('contactForm');
const messageDiv = document.getElementById('formMessage');
if (contactForm) {
contactForm.reset();
// Limpiar validaciones visuales
const inputs = contactForm.querySelectorAll('.form-control');
inputs.forEach(input => clearFieldValidation(input));
}
if (messageDiv) {
messageDiv.style.display = 'none';
}
}
});
/**
* Tracking cuando se abre el modal
*/
document.addEventListener('shown.bs.modal', function (event) {
if (event.target.id === 'contactModal') {
// Hacer foco en el primer campo
const fullNameInput = document.getElementById('fullName');
if (fullNameInput) {
setTimeout(() => fullNameInput.focus(), 100);
}
// Google Analytics 4 tracking
if (typeof gtag !== 'undefined') {
gtag('event', 'modal_open', {
event_category: 'Contact Form',
event_label: 'Contact Modal Opened',
});
}
}
});
})();