// Retos de gamificación — "estado sin terminar" para retención diaria // Basado en framework Nick Saraev: progreso visible, recompensa clara, urgencia. (function () { const RETOS_CSS = ` @keyframes reto-shine { 0% { background-position: -200% center; } 100% { background-position: 200% center; } } @keyframes reto-ping { 0% { transform: scale(1); opacity: 1; } 70% { transform: scale(2.2); opacity: 0; } 100% { transform: scale(2.2); opacity: 0; } } @keyframes reto-pop { 0% { transform: scale(0.7); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } } .reto-shine-bar { background: linear-gradient(90deg, oklch(0.58 0.12 155) 0%, oklch(0.78 0.18 90) 40%, oklch(0.58 0.12 155) 100% ); background-size: 200% auto; animation: reto-shine 1.8s linear infinite; } .reto-badge-pop { animation: reto-pop 0.45s cubic-bezier(.2,.9,.3,1) both; } .reto-ping { animation: reto-ping 1.4s cubic-bezier(0,.2,.8,1) infinite; } `; if (!document.getElementById('retos-styles')) { const styleEl = document.createElement('style'); styleEl.id = 'retos-styles'; styleEl.textContent = RETOS_CSS; document.head.appendChild(styleEl); } // ── helpers ───────────────────────────────────────────── function mesActual(pedidos) { const now = new Date(); return (pedidos || []).filter( (p) => p.fechaCreacion.getMonth() === now.getMonth() && p.fechaCreacion.getFullYear() === now.getFullYear() ); } function diasRestantesMes() { const now = new Date(); const ultimo = new Date(now.getFullYear(), now.getMonth() + 1, 0); return ultimo.getDate() - now.getDate(); } // ── Barra de progreso ──────────────────────────────────── function BarraProgreso({ pct, completo }) { const T = window.T; return (
= 70 ? T.warn : T.accent, borderRadius: 999, transition: 'width 0.7s cubic-bezier(.2,.9,.3,1)', }} />
); } // ── Badge de completado ────────────────────────────────── function BadgeCompleto({ label }) { const T = window.T; return (
{label}
); } // ── Tarjeta de reto individual ─────────────────────────── function RetoCard({ icono, titulo, descripcion, pct, recompensa, diasRestantes, completo, valorActual, valorMeta, unidad }) { const T = window.T; const pctDisplay = Math.min(100, Math.round(pct)); return (
{/* Ping dot — solo si está incompleto y cerca */} {!completo && pct >= 60 && (
)} {/* Icono + título */}
{titulo}
{descripcion}
{/* Progreso numérico */}
{valorActual} {' '}/ {valorMeta} {unidad} = 70 ? T.warn : T.accent, fontWeight: 600, }} > {pctDisplay}%
{/* Footer: recompensa o badge */}
{completo ? ( ) : ( {recompensa} )} {!completo && ( {diasRestantes}d )}
); } // ── Sección principal ──────────────────────────────────── function RetosSection({ vendedora, pedidos, billing }) { const T = window.T; const { Icon } = window; const pedidosMes = mesActual(pedidos || []); const dias = diasRestantesMes(); const state = billing && billing.state ? billing.state : { facturas: [], pagos: [] }; // ── Reto 1: Inicio Rápido ── // "3 productos en un solo pedido" — mide el pedido con más productos const maxProductosEnPedido = (pedidos || []).reduce( (max, p) => Math.max(max, (p.productos || []).reduce((s, x) => s + (x.cantidad || 1), 0)), 0 ); const META_PRODUCTOS = 3; const pctInicio = (maxProductosEnPedido / META_PRODUCTOS) * 100; // ── Reto 2: Facturación mensual ── const facturasAprobadas = (state.facturas || []).filter( (f) => !f.estado || f.estado === 'procesada' ); const now = new Date(); const facturasEstesMes = facturasAprobadas.filter((f) => { const d = f.fecha ? new Date(f.fecha) : null; return d && d.getMonth() === now.getMonth() && d.getFullYear() === now.getFullYear(); }); const totalFacturado = facturasEstesMes.reduce( (s, f) => s + (f.total || f.monto || 0), 0 ); // fallback: suma de pedidos del mes si no hay facturas const totalFallback = pedidosMes.reduce( (s, p) => s + (p.productos || []).reduce((a, x) => a + x.precio * x.cantidad, 0), 0 ); const totalMes = totalFacturado > 0 ? totalFacturado : totalFallback; const META_FACTURACION = 200; const pctFacturacion = (totalMes / META_FACTURACION) * 100; // ── Reto 3: Diversidad de marcas ── const MARCAS_CONOCIDAS = ['Lbel', 'CyZone', 'Esika', 'lbel', 'cyzone', 'esika']; const marcasUnicas = new Set( (pedidosMes || []).flatMap((p) => (p.productos || []).map((x) => (x.catalogo || x.marca || '').toLowerCase()) ) ).size; const META_MARCAS = 2; const pctMarcas = (marcasUnicas / META_MARCAS) * 100; const retos = [ { id: 'inicio', icono: 'bolt', titulo: 'Arranque fuerte', descripcion: '3+ productos en un pedido', pct: pctInicio, completo: maxProductosEnPedido >= META_PRODUCTOS, valorActual: maxProductosEnPedido, valorMeta: META_PRODUCTOS, unidad: 'prod.', recompensa: '⚡ Vendedora activa', diasRestantes: dias, }, { id: 'facturacion', icono: 'trending', titulo: 'Meta del mes', descripcion: `Factura $${META_FACTURACION} este mes`, pct: pctFacturacion, completo: totalMes >= META_FACTURACION, valorActual: '$' + totalMes.toFixed(0), valorMeta: '$' + META_FACTURACION, unidad: '', recompensa: '🏆 Top vendedora', diasRestantes: dias, }, { id: 'diversidad', icono: 'sparkle', titulo: 'Colección completa', descripcion: 'Vende de 2 marcas distintas', pct: pctMarcas, completo: marcasUnicas >= META_MARCAS, valorActual: marcasUnicas, valorMeta: META_MARCAS, unidad: 'marcas', recompensa: '✨ Catálogo Pro', diasRestantes: dias, }, ]; const completados = retos.filter((r) => r.completo).length; return (
{/* Encabezado de sección */}
Campaña activa
Retos
{completados}/{retos.length} completados
{/* Carrusel de tarjetas */}
{retos.map((r) => ( ))}
{/* Hint de estado incompleto */} {completados < retos.length && (
{dias === 0 ? 'Último día de campaña' : `${dias} ${dias === 1 ? 'día' : 'días'} para cerrar esta campaña`}
)}
); } window.RetosSection = RetosSection; })();