Files
ayr2026/scripts/main.js
T
2026-09-20 16:01:31 -03:00

738 lines
25 KiB
JavaScript

/* ============================================
CARGA DE INCLUDES (menu y footer)
============================================ */
async function loadInclude(selector, url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const container = document.querySelector(selector);
if (container) container.innerHTML = html;
} catch (error) {
console.error(`Error cargando ${url}:`, error);
}
}
/* ============================================
SWITCH DE TEMA (claro por defecto)
============================================ */
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
}
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
}
// Ejecutar al cargar la página
document.addEventListener('DOMContentLoaded', () => {
initTheme();
const themeBtn = document.querySelector('.theme-toggle');
if (themeBtn) {
themeBtn.addEventListener('click', toggleTheme);
}
});
/* ============================================
CALCULAR ANCHO DEL SCROLLBAR
============================================ */
function getScrollbarWidth() {
return window.innerWidth - document.documentElement.clientWidth;
}
function applyScrollbarCompensation() {
const scrollbarWidth = getScrollbarWidth();
document.documentElement.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`);
}
/* ============================================
MENÚ FULLSCREEN
============================================ */
function openMenu() {
const menu = document.getElementById('fullscreen-menu');
const overlay = document.getElementById('menu-overlay');
const menuToggle = document.querySelector('.menu-toggle');
if (menu && overlay) {
applyScrollbarCompensation();
menu.classList.add('is-open');
overlay.classList.add('is-open');
document.body.classList.add('menu-open');
menuToggle.setAttribute('aria-expanded', 'true');
}
}
function closeMenu() {
const menu = document.getElementById('fullscreen-menu');
const overlay = document.getElementById('menu-overlay');
const menuToggle = document.querySelector('.menu-toggle');
if (menu && overlay) {
menu.classList.remove('is-open');
overlay.classList.remove('is-open');
document.body.classList.remove('menu-open');
menuToggle.setAttribute('aria-expanded', 'false');
}
}
/* ============================================
ACORDEÓN DE SERVICIOS
============================================ */
function initServiciosAccordion() {
const accordion = document.getElementById('servicios-accordion');
if (!accordion) {
console.log('Acordeón no encontrado');
return;
}
const items = accordion.querySelectorAll('.servicio-item');
console.log('Items encontrados:', items.length);
// Función para activar un item
function activateItem(item) {
if (item.classList.contains('is-active')) return;
accordion.classList.add('has-active');
items.forEach(i => i.classList.remove('is-active'));
item.classList.add('is-active');
}
// Función para cerrar (volver al estado inicial)
function closeAll() {
accordion.classList.remove('has-active');
items.forEach(i => i.classList.remove('is-active'));
}
// Click en cada item para expandirlo
items.forEach((item, index) => {
console.log(`Agregando listener al item ${index}`);
item.addEventListener('click', (e) => {
console.log('Click en item:', index);
// Si el click fue en el botón cerrar o en un botón CTA, no activar
if (e.target.closest('.servicio-close')) {
console.log('Click en cerrar');
return;
}
if (e.target.closest('.btn')) {
console.log('Click en botón CTA');
return;
}
activateItem(item);
});
});
// Click en los botones de cerrar
const closeButtons = accordion.querySelectorAll('.servicio-close');
closeButtons.forEach((btn, index) => {
console.log(`Agregando listener al botón cerrar ${index}`);
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('Botón cerrar clicked');
closeAll();
});
});
}
/* ============================================
INICIALIZACIÓN
============================================ */
document.addEventListener('DOMContentLoaded', async () => {
// Cargar menú y footer
await Promise.all([
loadInclude('#menu-container', 'includes/menu.html'),
loadInclude('#footer-container', 'includes/footer.html')
]);
// Inicializar tema
initTheme();
// Eventos del tema
const themeBtn = document.querySelector('.theme-toggle');
if (themeBtn) themeBtn.addEventListener('click', toggleTheme);
// Eventos del menú
const menuToggle = document.querySelector('.menu-toggle');
const menuClose = document.querySelector('.menu-close');
const menuOverlay = document.getElementById('menu-overlay');
const menuLinks = document.querySelectorAll('.fullscreen-menu a');
if (menuToggle) menuToggle.addEventListener('click', openMenu);
if (menuClose) menuClose.addEventListener('click', closeMenu);
if (menuOverlay) menuOverlay.addEventListener('click', closeMenu);
// Cerrar menú al hacer clic en un enlace
menuLinks.forEach(link => {
link.addEventListener('click', closeMenu);
});
// Cerrar menú con tecla Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeMenu();
});
// Año dinámico en el footer
const yearEl = document.getElementById('current-year');
if (yearEl) yearEl.textContent = new Date().getFullYear();
initServiciosAccordion();
initNavbarAutoHide();
});
/* ============================================
DIAGNÓSTICO INTERACTIVO
============================================ */
function initDiagnostico() {
// Elementos del DOM
const bienvenida = document.getElementById('diagnostico-bienvenida');
const quiz = document.getElementById('diagnostico-quiz');
const resultado = document.getElementById('resultado-contenedor');
const btnComenzar = document.getElementById('btn-comenzar');
const contenedorPreguntas = document.getElementById('cuestionario-contenedor');
const barraCompletado = document.getElementById('progreso-completado');
const textoProgreso = document.getElementById('progreso-texto');
if (!bienvenida || !quiz || !resultado || !btnComenzar) return;
// Banco de preguntas
const preguntas = [
{
id: 1,
titulo: '¿En qué etapa se encuentra hoy tu negocio o proyecto?',
opciones: [
{ texto: 'Creando el proyecto desde cero o formalizando una idea reciente.', valor: 'inicio' },
{ texto: 'Negocio activo, pero con una imagen visual que no refleja la calidad de lo que vendo.', valor: 'identidad' },
{ texto: 'En pleno movimiento comercial, pero desbordados para contestar consultas a tiempo.', valor: 'automatizacion' },
{ texto: 'Establecido y con ventas, pero con un techo de crecimiento que necesitamos romper con publicidad.', valor: 'paid' },
{ texto: 'Empresa consolidada que busca liderazgo de mercado y delegar todo su marketing.', valor: 'corporativo' },
{ texto: 'Emprendimiento personal que gestiono por mi cuenta y necesito resolver piezas puntuales.', valor: 'consultoria' }
]
},
{
id: 2,
titulo: '¿Cuál es tu mayor desafío actual en marketing?',
opciones: [
{ texto: 'No tengo una identidad clara ni coherente en mis canales.', valor: 'identidad' },
{ texto: 'Tengo redes pero no generan consultas ni ventas reales.', valor: 'redes' },
{ texto: 'Mi sitio web está desactualizado o no convierte visitantes en clientes.', valor: 'web' },
{ texto: 'No tengo tiempo ni equipo para producir contenido constante.', valor: 'contenido' },
{ texto: 'Invierto en pauta pero no sé si estoy llegando a la audiencia correcta.', valor: 'paid' },
{ texto: 'No tengo una estrategia clara y voy tomando decisiones sobre la marcha.', valor: 'consultoria' }
]
},
{
id: 3,
titulo: '¿Qué objetivo prioritario tenés para los próximos 6 meses?',
opciones: [
{ texto: 'Lanzar mi marca con una identidad sólida y profesional.', valor: 'identidad' },
{ texto: 'Construir una comunidad activa y fiel en redes sociales.', valor: 'redes' },
{ texto: 'Tener un sitio web que venda por mí las 24 horas.', valor: 'web' },
{ texto: 'Automatizar procesos para ganar tiempo y escalar.', valor: 'automatizacion' },
{ texto: 'Escalar ventas con campañas de publicidad pagada.', valor: 'paid' },
{ texto: 'Tener un partner estratégico que me acompañe en las decisiones.', valor: 'consultoria' }
]
},
{
id: 4,
titulo: '¿Cómo describirías tu inversión actual en marketing?',
opciones: [
{ texto: 'Casi nula o muy básica, recién estoy empezando.', valor: 'inicio' },
{ texto: 'Baja, hago algunas cosas por mi cuenta pero me falta estructura.', valor: 'identidad' },
{ texto: 'Media, invierto en contenido pero no veo retorno claro.', valor: 'contenido' },
{ texto: 'Media-alta, ya tengo equipo o freelancers pero falta dirección.', valor: 'consultoria' },
{ texto: 'Alta, invierto en pauta y necesito maximizar el ROI.', valor: 'paid' },
{ texto: 'Integral, busco una agencia que gestione todo 360°.', valor: 'corporativo' }
]
}
];
// Mapeo de resultados según valor más frecuente
const resultados = {
inicio: {
titulo: 'Plan Esencial',
diagnostico: 'Estás en una etapa fundacional. Tu marca necesita bases sólidas antes de escalar.',
solucion: 'Te recomendamos comenzar con un <strong>Plan Esencial</strong>: identidad visual básica, sitio web institucional y gestión inicial de redes. Es el punto de partida ideal para construir con estrategia desde el día uno.'
},
identidad: {
titulo: 'Construcción de Identidad Estratégica',
diagnostico: 'Tu negocio tiene potencial, pero la percepción visual no está a la altura de lo que ofrecés.',
solucion: 'Necesitás un trabajo de <strong>Branding integral</strong>: definir el ADN de tu marca, su posicionamiento y el camino para conectar con tu audiencia ideal de manera auténtica y memorable.'
},
redes: {
titulo: 'Comunidades que Convierten',
diagnostico: 'Tenés presencia pero no estás generando el impacto ni la conexión que buscás.',
solucion: 'Te recomendamos nuestro servicio de <strong>Gestión de Redes Sociales</strong>: creamos contenido de valor y gestionamos tus canales para construir una comunidad activa, fiel y comprometida con tu marca.'
},
web: {
titulo: 'Experiencias Digitales Impactantes',
diagnostico: 'Tu sitio web no está cumpliendo su rol como herramienta de venta y conversión.',
solucion: 'Necesitás un <strong>rediseño o desarrollo web estratégico</strong>: un sitio que no solo se vea bien, sino que genere resultados tangibles y convierta visitantes en clientes.'
},
contenido: {
titulo: 'Historias que Enamoran',
diagnostico: 'Tenés el negocio andando pero te falta el contenido que conecte emocionalmente con tu audiencia.',
solucion: 'Te recomendamos nuestro servicio de <strong>Producción de Contenido Audiovisual y Gráfico</strong>: contenido de alta calidad que cuente la historia de tu marca y cautive a tu audiencia.'
},
automatizacion: {
titulo: 'Automatización de Procesos',
diagnostico: 'El crecimiento te está desbordando. Necesitás sistemas que trabajen por vos.',
solucion: 'Necesitás <strong>automatizaciones inteligentes</strong>: flujos de WhatsApp, respuestas automáticas y sistemas que liberen tu tiempo para que te enfoques en lo estratégico.'
},
paid: {
titulo: 'Campañas que Escalan Resultados',
diagnostico: 'Tenés producto y demanda, pero necesitás acelerar el crecimiento con inversión publicitaria inteligente.',
solucion: 'Te recomendamos <strong>Paid Media en Meta, Google y LinkedIn</strong>: campañas gestionadas estratégicamente para maximizar tu ROI y alcanzar a los clientes correctos.'
},
consultoria: {
titulo: 'Asesoramiento Estratégico Integral',
diagnostico: 'Tenés recursos y voluntad, pero te falta una brújula estratégica que alinee tus esfuerzos.',
solucion: 'Necesitás una <strong>Consultoría de Marketing</strong>: te acompañamos en la toma de decisiones clave, optimizando tus recursos y alineando tu marketing con los objetivos de tu negocio.'
},
corporativo: {
titulo: 'Plan Corporativo 360°',
diagnostico: 'Tu empresa está en un nivel donde necesitás un equipo dedicado que gestione todo de forma integral.',
solucion: 'Te recomendamos el <strong>Plan Corporativo</strong>: dirección creativa y estratégica, branding integral, pauta multicanal, reportes con KPIs y un equipo dedicado exclusivamente a tu marca.'
}
};
// Estado
let preguntaActual = 0;
const respuestas = [];
// Iniciar cuestionario
btnComenzar.addEventListener('click', () => {
bienvenida.style.display = 'none';
quiz.style.display = 'block';
resultado.style.display = 'none';
preguntaActual = 0;
respuestas.length = 0;
renderizarPregunta();
});
// Renderizar pregunta actual
function renderizarPregunta() {
const pregunta = preguntas[preguntaActual];
const progreso = ((preguntaActual) / preguntas.length) * 100;
barraCompletado.style.width = `${progreso}%`;
textoProgreso.textContent = `Pregunta ${preguntaActual + 1} de ${preguntas.length}`;
let html = `
<div class="pregunta-contenedor">
<h3 class="pregunta-titulo">${pregunta.titulo}</h3>
<div class="opciones-contenedor">
`;
pregunta.opciones.forEach((opcion, index) => {
const seleccionada = respuestas[preguntaActual] === opcion.valor ? 'seleccionada' : '';
html += `
<button class="opcion-tarjeta ${seleccionada}" data-valor="${opcion.valor}" data-index="${index}">
<span class="opcion-numero">${index + 1}</span>
<span class="opcion-texto">${opcion.texto}</span>
</button>
`;
});
html += `</div>`;
// Navegación
html += `
<div class="pregunta-navegacion">
<button class="btn-anterior" ${preguntaActual === 0 ? 'style="visibility: hidden;"' : ''}>
← Anterior
</button>
<button class="btn-siguiente" ${!respuestas[preguntaActual] ? 'disabled' : ''}>
${preguntaActual === preguntas.length - 1 ? 'Ver resultado' : 'Siguiente →'}
</button>
</div>
`;
html += `</div>`;
contenedorPreguntas.innerHTML = html;
// Event listeners de opciones
const opciones = contenedorPreguntas.querySelectorAll('.opcion-tarjeta');
opciones.forEach(opcion => {
opcion.addEventListener('click', () => {
const valor = opcion.dataset.valor;
respuestas[preguntaActual] = valor;
// Actualizar UI
opciones.forEach(o => o.classList.remove('seleccionada'));
opcion.classList.add('seleccionada');
// Habilitar botón siguiente
const btnSiguiente = contenedorPreguntas.querySelector('.btn-siguiente');
if (btnSiguiente) btnSiguiente.disabled = false;
});
});
// Navegación
const btnAnterior = contenedorPreguntas.querySelector('.btn-anterior');
const btnSiguiente = contenedorPreguntas.querySelector('.btn-siguiente');
if (btnAnterior) {
btnAnterior.addEventListener('click', () => {
if (preguntaActual > 0) {
preguntaActual--;
renderizarPregunta();
}
});
}
if (btnSiguiente) {
btnSiguiente.addEventListener('click', () => {
if (respuestas[preguntaActual]) {
if (preguntaActual < preguntas.length - 1) {
preguntaActual++;
renderizarPregunta();
} else {
mostrarResultado();
}
}
});
}
}
// Calcular y mostrar resultado
function mostrarResultado() {
// Contar frecuencia de cada valor
const conteo = {};
respuestas.forEach(valor => {
conteo[valor] = (conteo[valor] || 0) + 1;
});
// Obtener el valor más frecuente
let valorGanador = Object.keys(conteo).reduce((a, b) =>
conteo[a] > conteo[b] ? a : b
);
const resultadoData = resultados[valorGanador] || resultados.consultoria;
// Actualizar barra al 100%
barraCompletado.style.width = '100%';
textoProgreso.textContent = `Completado`;
// Ocultar quiz y mostrar resultado
quiz.style.display = 'none';
resultado.style.display = 'block';
resultado.innerHTML = `
<div class="resultado-encabezado">
<h3 class="resultado-titulo">${resultadoData.titulo}</h3>
<p class="resultado-subtitulo">Según tus respuestas, esto es lo que más te conviene:</p>
</div>
<p class="resultado-diagnostico">${resultadoData.diagnostico}</p>
<div class="resultado-solucion">
${resultadoData.solucion}
</div>
<div class="resultado-acciones">
<a href="contacto.html" class="resultado-cta">
Hablemos sobre esto
<span aria-hidden="true">→</span>
</a>
<button class="btn-reiniciar" id="btn-reiniciar-resultado">
<span aria-hidden="true">↻</span>
Reiniciar diagnóstico
</button>
</div>
`;
// Event listener del botón reiniciar
const btnReiniciar = document.getElementById('btn-reiniciar-resultado');
if (btnReiniciar) {
btnReiniciar.addEventListener('click', () => {
resultado.style.display = 'none';
bienvenida.style.display = 'flex';
preguntaActual = 0;
respuestas.length = 0;
barraCompletado.style.width = '0%';
});
}
}
}
// Inicializar cuando el DOM esté listo
document.addEventListener('DOMContentLoaded', () => {
initDiagnostico();
});
/* ============================================
ANIMACIÓN AUTOMÁTICA DE METODOLOGÍA
============================================ */
function initMetodologiaAnimation() {
const section = document.getElementById('metodologia');
if (!section) return;
const pasos = section.querySelectorAll('.paso-item');
if (pasos.length === 0) return;
let pasoActual = 0;
let intervalo;
const DURACION_PASO = 2500; // 2.5 segundos por paso
// Función para actualizar los estados
function actualizarEstados() {
pasos.forEach((paso, index) => {
// Remover todas las clases de estado
paso.classList.remove('active', 'previous');
// Calcular el índice del paso anterior (con loop)
const pasoAnterior = (pasoActual - 1 + pasos.length) % pasos.length;
// Asignar estados
if (index === pasoActual) {
paso.classList.add('active');
} else if (index === pasoAnterior) {
paso.classList.add('previous');
}
});
}
// Función para avanzar al siguiente paso
function avanzarPaso() {
pasoActual = (pasoActual + 1) % pasos.length;
actualizarEstados();
}
// Función para iniciar la animación
function iniciarAnimacion() {
// Inicializar con el primer paso activo
actualizarEstados();
// Iniciar el intervalo
intervalo = setInterval(avanzarPaso, DURACION_PASO);
}
// Función para detener la animación
function detenerAnimacion() {
clearInterval(intervalo);
}
// IntersectionObserver para detectar cuando la sección es visible
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// La sección está visible: iniciar animación
iniciarAnimacion();
} else {
// La sección no está visible: detener animación
detenerAnimacion();
}
});
}, {
threshold: 0.3 // Se activa cuando el 30% de la sección es visible
});
// Observar la sección
observer.observe(section);
// Inicializar el primer estado (sin animación aún)
actualizarEstados();
}
/* ============================================
EFECTO FOCO EN TARJETAS DE PLANES
============================================ */
function initPlanesFocusEffect() {
const grid = document.getElementById('planes-grid');
if (!grid) return;
const cards = grid.querySelectorAll('.plan-card');
if (cards.length === 0) return;
cards.forEach(card => {
card.addEventListener('mouseenter', () => {
grid.classList.add('is-hovering');
card.classList.add('is-focused');
});
card.addEventListener('mouseleave', () => {
grid.classList.remove('is-hovering');
card.classList.remove('is-focused');
});
});
}
/* ============================================
CARRUSEL AUTOMÁTICO DE PORTFOLIO
============================================ */
function initPortfolioCarousel() {
const slides = document.querySelectorAll('.carousel-slide');
const indicators = document.querySelectorAll('.indicator');
if (slides.length === 0) return;
let currentSlide = 0;
const INTERVAL = 4000; // 4 segundos
let intervalId;
function goToSlide(index) {
// Remover clase active de todos
slides.forEach(slide => slide.classList.remove('active'));
indicators.forEach(indicator => indicator.classList.remove('active'));
// Activar el slide actual
slides[index].classList.add('active');
indicators[index].classList.add('active');
currentSlide = index;
}
function nextSlide() {
const next = (currentSlide + 1) % slides.length;
goToSlide(next);
}
function startCarousel() {
intervalId = setInterval(nextSlide, INTERVAL);
}
function stopCarousel() {
clearInterval(intervalId);
}
// Click en indicadores
indicators.forEach((indicator, index) => {
indicator.addEventListener('click', () => {
stopCarousel();
goToSlide(index);
startCarousel();
});
});
// Iniciar carrusel
startCarousel();
}
/* ============================================
TIRA MARQUEE CON HOVER INTERACTIVO
============================================ */
function initMarqueeHover() {
const track = document.getElementById('marquee-track');
if (!track) return;
const icons = track.querySelectorAll('.marquee-icon');
if (icons.length === 0) return;
icons.forEach(icon => {
icon.addEventListener('mouseenter', () => {
track.classList.add('is-slow');
});
icon.addEventListener('mouseleave', () => {
track.classList.remove('is-slow');
});
});
}
/*==========================================
nav
=============================================*/
function initNavbarAutoHide() {
console.log('🔵 initNavbarAutoHide: iniciando...');
// Reintentar hasta que el header exista (por si se carga async)
let attempts = 0;
const MAX_ATTEMPTS = 50; // 5 segundos
const tryInit = () => {
const header = document.querySelector('.site-header');
console.log('🔵 intento', attempts, '- header:', header);
if (!header) {
attempts++;
if (attempts < MAX_ATTEMPTS) {
setTimeout(tryInit, 100);
} else {
console.error('❌ Navbar: no se encontró .site-header');
}
return;
}
console.log('✅ Navbar encontrado, registrando scroll listener...');
const hero = document.getElementById('hero');
const UMBRAL_HERO = 100; // píxeles mínimos antes de activar el auto-hide
let lastScrollY = window.scrollY;
let ticking = false;
const updateHeader = () => {
const currentScrollY = window.scrollY;
const delta = currentScrollY - lastScrollY;
console.log('📜 scrollY:', currentScrollY, '| delta:', delta);
// 1. Si el menú fullscreen está abierto: siempre visible
if (document.body.classList.contains('menu-open')) {
header.classList.remove('is-hidden');
lastScrollY = currentScrollY;
ticking = false;
return;
}
// 2. Si estamos dentro del hero (primeros 100px): siempre visible
if (currentScrollY <= UMBRAL_HERO) {
header.classList.remove('is-hidden');
lastScrollY = currentScrollY;
ticking = false;
return;
}
// 3. Ignorar micro-movimientos
if (Math.abs(delta) < 5) {
ticking = false;
return;
}
// 4. Scroll hacia abajo → ocultar
if (delta > 0) {
header.classList.add('is-hidden');
console.log(' → ocultando header');
}
// 5. Scroll hacia arriba → mostrar
else {
header.classList.remove('is-hidden');
console.log(' → mostrando header');
}
lastScrollY = currentScrollY;
ticking = false;
};
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(updateHeader);
ticking = true;
}
}, { passive: true });
// Resetear al redimensionar
window.addEventListener('resize', () => {
lastScrollY = window.scrollY;
});
console.log('✅ Navbar auto-hide listo');
};
tryInit();
}
// Inicializar cuestionario al cargar la página
document.addEventListener('DOMContentLoaded', () => {
// Eliminar el bloque del diagnóstico (ya se maneja en initDiagnostico)
initMetodologiaAnimation();
initPlanesFocusEffect();
initPortfolioCarousel();
initMarqueeHover(); // ← además, esta NUNCA se está llamando
});