1/** 2 * Widget de WhatsApp con flujo de preguntas 3 * Uso: <script src="widget.js"></script> 4 */ 5 6(function () { 7 "use strict"; 8 9 // Configuración 10 // Configuración 11 // Detectar automáticamente la URL de la API basada en la ubicación de este script 12 const scripts = document.getElementsByTagName("script"); 13 const currentScript = scripts[scripts.length - 1]; 14 const scriptPath = currentScript.src; 15 const baseUrl = scriptPath.substring(0, scriptPath.lastIndexOf("/")); 16 const API_URL = `${baseUrl}/api.php`; 17 18 let config = null; 19 let currentStep = 0; 20 let selectedSolution = null; 21 let selectedUserRange = null; 22 let clickStatId = null; 23 24 // Crear estilos 25 const styles = ` 26 .wa-widget-container { 27 position: fixed; 28 bottom: 20px; 29 right: 20px; 30 z-index: 9999; 31 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 32 } 33 34 .wa-widget-button { 35 width: 60px; 36 height: 60px; 37 border-radius: 50%; 38 background: #25D366; 39 box-shadow: 0 4px 12px rgba(37, 211, 102, 0.4); 40 cursor: pointer; 41 display: flex; 42 align-items: center; 43 justify-content: center; 44 transition: all 0.3s ease; 45 border: none; 46 position: relative; 47 } 48 49 .wa-widget-button:hover { 50 transform: scale(1.1); 51 box-shadow: 0 6px 20px rgba(37, 211, 102, 0.6); 52 } 53 54 .wa-widget-button svg { 55 width: 32px; 56 height: 32px; 57 fill: white; 58 } 59 60 .wa-widget-modal { 61 position: absolute; 62 bottom: 80px; 63 right: 0; 64 width: 360px; 65 max-width: calc(100vw - 40px); 66 background: white; 67 border-radius: 12px; 68 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); 69 overflow: hidden; 70 opacity: 0; 71 transform: scale(0.8) translateY(20px); 72 transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); 73 pointer-events: none; 74 } 75 76 .wa-widget-modal.active { 77 opacity: 1; 78 transform: scale(1) translateY(0); 79 pointer-events: all; 80 } 81 82 .wa-widget-header { 83 background: #25D366; 84 color: white; 85 padding: 20px; 86 text-align: center; 87 font-weight: 600; 88 font-size: 16px; 89 position: relative; 90 } 91 92 .wa-widget-close { 93 position: absolute; 94 top: 50%; 95 right: 15px; 96 transform: translateY(-50%); 97 background: rgba(255, 255, 255, 0.2); 98 border: none; 99 color: white; 100 width: 28px; 101 height: 28px; 102 border-radius: 50%; 103 cursor: pointer; 104 display: flex; 105 align-items: center; 106 justify-content: center; 107 transition: background 0.2s; 108 font-size: 20px; 109 line-height: 1; 110 } 111 112 .wa-widget-close:hover { 113 background: rgba(255, 255, 255, 0.3); 114 } 115 116 .wa-widget-body { 117 padding: 24px; 118 max-height: 400px; 119 overflow-y: auto; 120 } 121 122 .wa-widget-title { 123 font-size: 18px; 124 font-weight: 700; 125 margin-bottom: 12px; 126 color: #1a1a1a; 127 } 128 129 .wa-widget-message { 130 font-size: 14px; 131 color: #666; 132 margin-bottom: 20px; 133 line-height: 1.5; 134 } 135 136 .wa-widget-option { 137 background: #25D366; 138 color: white; 139 border: none; 140 padding: 14px 20px; 141 border-radius: 8px; 142 width: 100%; 143 margin-bottom: 10px; 144 cursor: pointer; 145 font-size: 14px; 146 font-weight: 500; 147 transition: all 0.2s; 148 text-align: left; 149 } 150 151 .wa-widget-option:hover { 152 background: #20BD5A; 153 transform: translateX(4px); 154 } 155 156 .wa-widget-option:active { 157 transform: translateX(4px) scale(0.98); 158 } 159 160 .wa-widget-back { 161 background: #f0f0f0; 162 color: #666; 163 border: none; 164 padding: 12px 20px; 165 border-radius: 8px; 166 width: 100%; 167 margin-top: 10px; 168 cursor: pointer; 169 font-size: 14px; 170 font-weight: 500; 171 transition: all 0.2s; 172 } 173 174 .wa-widget-back:hover { 175 background: #e0e0e0; 176 } 177 178 @media (max-width: 480px) { 179 .wa-widget-container { 180 bottom: 10px; 181 right: 10px; 182 } 183 184 .wa-widget-modal { 185 width: calc(100vw - 20px); 186 right: -10px; 187 } 188 } 189 `; 190 191 // Inyectar estilos 192 const styleSheet = document.createElement("style"); 193 styleSheet.textContent = styles; 194 document.head.appendChild(styleSheet); 195 196 // Crear HTML del widget 197 const widgetHTML = ` 198 <div class="wa-widget-container"> 199 <button class="wa-widget-button" id="wa-widget-toggle"> 200 <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> 201 <path d="M16 0C7.164 0 0 7.164 0 16c0 2.825.738 5.488 2.025 7.788L.05 31.95l8.363-2.05A15.93 15.93 0 0 0 16 32c8.836 0 16-7.164 16-16S24.836 0 16 0zm0 29.333c-2.488 0-4.863-.688-6.888-1.988l-.488-.3-5.05 1.238 1.3-4.75-.325-.513A13.267 13.267 0 0 1 2.667 16c0-7.363 5.97-13.333 13.333-13.333S29.333 8.637 29.333 16 23.363 29.333 16 29.333zm7.3-9.95c-.4-.2-2.363-1.163-2.725-1.3-.363-.138-.625-.2-.888.2-.263.4
201-1.013 1.3-1.238 1.563-.225.263-.45.3-.85.1-.4-.2-1.688-.625-3.213-2-.188-1.063-.338-1.838-.5-2.238-.163-.4-.013-.613.175-.813.175-.188.4-.488.6-.738.2-.25.263-.425.4-.7.138-.275.063-.525-.038-.738-.1-.213-.888-2.138-1.213-2.925-.325-.763-.65-.65-.888-.663-.225-.013-.488-.013-.75-.013s-.688.1-1.05.5c-.363.4-1.375 1.338-1.375 3.263s1.413 3.788 1.6 4.05c.188.263 2.65 4.05 6.425 5.675.9.388 1.6.625 2.15.8.9.288 1.725.25 2.375.15.725-.113 2.363-.963 2.7-1.9.338-.938.338-1.738.238-1.9-.1-.163-.363-.263-.763-.463z"/> 202 </svg> 203 </button> 204 <div class="wa-widget-modal" id="wa-widget-modal"> 205 <div class="wa-widget-header"> 206 <span id="wa-widget-company-name">Cargando...</span> 207 <button class="wa-widget-close" id="wa-widget-close">Ã</button> 208 </div> 209 <div class="wa-widget-body" id="wa-widget-content"> 210 <div class="wa-widget-title">Cargando...</div> 211 <div class="wa-widget-message">Por favor espera...</div> 212 </div> 213 </div> 214 </div> 215 `; 216 217 // Inicializar widget cuando el DOM esté listo 218 function init() { 219 // Crear contenedor 220 const container = document.createElement("div"); 221 container.innerHTML = widgetHTML; 222 document.body.appendChild(container.firstElementChild); 223 224 // Obtener elementos 225 const toggleBtn = document.getElementById("wa-widget-toggle"); 226 const modal = document.getElementById("wa-widget-modal"); 227 const closeBtn = document.getElementById("wa-widget-close"); 228 229 // Event listeners 230 toggleBtn.addEventListener("click", toggleModal); 231 closeBtn.addEventListener("click", closeModal); 232 233 // Cargar configuración 234 loadConfig(); 235 } 236 237 // Cargar configuración desde la API 238 async function loadConfig() { 239 try { 240 const response = await fetch(`${API_URL}?action=get_config`); 241 const result = await response.json(); 242 243 if (result.success) { 244 config = result.data; 245 document.getElementById("wa-widget-company-name").textContent = 246 config.company_name; 247 showStep1(); 248 } else { 249 showError("Error al cargar la configuración"); 250 } 251 } catch (error) { 252 console.error("Error:", error); 253 showError("Error de conexión"); 254 } 255 } 256 257 // Mostrar Paso 1: Soluciones 258 function showStep1() { 259 currentStep = 1; 260 selectedSolution = null; 261 selectedUserRange = null; 262 263 const content = document.getElementById("wa-widget-content"); 264 content.innerHTML = ` 265 <div class="wa-widget-title">Soluciones</div> 266 <div class="wa-widget-message">Antes de hablar con un representante, necesitamos saber qué solución te interesa conocer</div> 267 ${config.solutions 268 .map( 269 (solution) => ` 270 <button class="wa-widget-option" data-solution-id="${solution.id}" data-solution-name="${solution.name}"> 271 ${solution.name} 272 </button> 273 `, 274 ) 275 .join("")} 276 `; 277 278 // Event listeners para las opciones 279 content.querySelectorAll(".wa-widget-option").forEach((btn) => { 280 btn.addEventListener("click", function () { 281 const solutionId = this.getAttribute("data-solution-id"); 282 const solutionName = this.getAttribute("data-solution-name"); 283 handleSolutionSelect(solutionId, solutionName); 284 }); 285 }); 286 } 287 288 // Manejar selección de solución 289 function handleSolutionSelect(solutionId, solutionName) { 290 selectedSolution = { id: solutionId, name: solutionName }; 291 292 // Si seleccionó "Otro", ir directo a WhatsApp 293 if (solutionName.toLowerCase() === "otro") { 294 trackAndRedirect(solutionId, null); 295 } else { 296 showStep2(); 297 } 298 } 299 300 // Mostrar Paso 2: Cantidad de usuarios 301 function showStep2() { 302 currentStep = 2; 303 304 const content = document.getElementById("wa-widget-content"); 305 content.innerHTML = ` 306 <div class="wa-widget-title">Cantidad de usuarios</div> 307 <div class="wa-widget-message">Por último, necesitamos saber la cantidad de usuarios concurrentes requerida</div> 308 ${config.user_ranges 309 .map( 310 (range) => ` 311 <button class="wa-widget-option" data-range-id="${range.id}" data-range-text="${range.range_text}"> 312 ${range.range_text} 313 </button> 314 `, 315 ) 316 .join("")} 317 <button class="wa-widget-back" id="wa-widget-back-btn">Volver</button> 318 `; 319 320 // Event listeners para las opciones 321 content.querySelectorAll(".wa-widget-option").forEach((btn) => { 322 btn.addEventListener("click", function () { 323 const rangeId = this.getAttribute("data-range-id"); 324 const rangeText = this.getAttribute("data-range-text"); 325 handleUserRangeSelect(rangeId, rangeText); 326 }); 327 });
328 329 // Event listener para volver 330 document 331 .getElementById("wa-widget-back-btn") 332 .addEventListener("click", showStep1); 333 } 334 335 // Manejar selección de rango de usuarios 336 function handleUserRangeSelect(rangeId, rangeText) { 337 selectedUserRange = { id: rangeId, text: rangeText }; 338 trackAndRedirect(selectedSolution.id, rangeId); 339 } 340 341 // Registrar estadÃstica y redirigir a WhatsApp 342 async function trackAndRedirect(solutionId, userRangeId) { 343 try { 344 // Registrar click 345 const trackResponse = await fetch(`${API_URL}?action=track_click`, { 346 method: "POST", 347 headers: { "Content-Type": "application/json" }, 348 body: JSON.stringify({ 349 page_url: window.location.href, 350 solution_id: solutionId, 351 user_range_id: userRangeId, 352 }), 353 }); 354 355 const trackResult = await trackResponse.json(); 356 console.log('=== Track result ===', trackResult); 357 console.log('Selected Solution:', selectedSolution); 358 console.log('Selected User Range:', selectedUserRange); 359 360 // Enviar evento a Google Tag Manager DataLayer 361 if (selectedSolution) { 362 // Normalizar el nombre de la solución para el evento 363 const solutionName = selectedSolution.name 364 .toUpperCase() 365 .replace(/SOFTLAND\s*/gi, "") 366 .trim(); 367 368 let eventName; 369 let eventLabel; 370 371 if (selectedUserRange) { 372 // Caso: Solución + Rango de usuarios 373 const userRangeNormalized = selectedUserRange.text 374 .replace(/\s*colaboradores?/gi, "") 375 .replace(/\s*usuarios?/gi, "") 376 .replace(/Más\s+de\s+/gi, "MAS_DE_") // Reemplazar "Más de " completo 377 .replace(/Mas\s+de\s+/gi, "MAS_DE_") // Reemplazar "Mas de " completo 378 .replace(/De\s+/gi, "") 379 .replace(/\s+a\s+/gi, "_A_") 380 .replace(/\s+/g, "_") 381 .toUpperCase(); 382 383 eventName = `WHATSAPP_${solutionName}_${userRangeNormalized}`; 384 eventLabel = `${selectedSolution.name} - ${selectedUserRange.text}`; 385 } else { 386 // Caso: Solo solución (ej: "Otro") 387 eventName = `WHATSAPP_${solutionName}`; 388 eventLabel = selectedSolution.name; 389 } 390 391 console.log('=== Sending DataLayer Event ==='); 392 console.log('Event name:', eventName); 393 console.log('Event label:', eventLabel); 394 395 // Enviar al DataLayer 396 window.dataLayer = window.dataLayer || []; 397 window.dataLayer.push({ 398 event: eventName, 399 eventCategory: "WhatsApp Button", 400 eventLabel: eventLabel, 401 solution: selectedSolution.name, 402 userRange: selectedUserRange ? selectedUserRange.text : "N/A", 403 }); 404 405 console.log("â DataLayer event pushed:", eventName); 406 console.log("Current dataLayer:", window.dataLayer); 407 } else { 408 console.warn('â ï¸ No solution selected, event not sent'); 409 } 410 411 // Obtener URL de WhatsApp 412 const params = new URLSearchParams({ 413 action: "get_whatsapp_url", 414 solution: selectedSolution.name, 415 user_range: selectedUserRange ? selectedUserRange.text : "", 416 }); 417 418 const urlResponse = await fetch(`${API_URL}?${params}`); 419 const urlResult = await urlResponse.json(); 420 421 if (urlResult.success) { 422 // Redirigir a WhatsApp 423 window.open(urlResult.url, "_blank"); 424 closeModal(); 425 } 426 } catch (error) { 427 console.error("Error:", error); 428 showError("Error al procesar la solicitud"); 429 } 430 } 431 432 // Toggle modal 433 function toggleModal() { 434 const modal = document.getElementById("wa-widget-modal"); 435 modal.classList.toggle("active"); 436 437 if (modal.classList.contains("active") && currentStep === 0) { 438 showStep1(); 439 } 440 } 441 442 // Cerrar modal 443 function closeModal() { 444 const modal = document.getElementById("wa-widget-modal"); 445 modal.classList.remove("active"); 446 } 447 448 // Mostrar error 449 function showError(message) { 450 const content = document.getElementById("wa-widget-content"); 451 content.innerHTML = ` 452 <div class="wa-widget-title">Error</div> 453 <div class="wa-widget-message">${message}</div> 454 <button class="wa-widget-back" onclick="location.reload()">Reintentar</button> 455 `; 456 } 457 458 // Inicializar cuando el DOM esté listo 459 if (document.readyState === "loading") { 460 document.addEventListener("DOMContentLoaded", init); 461 } else { 462 init(); 463 } 464})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.