PageSourceSearch

https://www.sanremohoteles.com/list.html?mkt_active=true&Pid=24471&origin=marketplace_homepage

html sanremohoteles.com collected 2026-10-05 09:35:38 UTC 175,037 bytes, 4,158 lines download raw bytes

1
2<!DOCTYPE html>
3<html lang="en">
4
5<head>
6	<!-- CustomHTMLHead (SiteJson) --><link rel="preconnect" href="https://fonts.googleapis.com">
7<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8<link href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap" rel="stylesheet">	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
9	<meta http-equiv="Cache-Control" content="Public" />
10
11
12	
13	<title>HSR TERMAS - The best price, guaranteed!</title>
14
15	<link rel="canonical" href="https://www.sanremohoteles.com/en/list.html" />
16		<link rel="alternate" hreflang="es-AR" href="https://www.sanremohoteles.com/list.html" />
17	<link rel="alternate" hreflang="en-US" href="https://www.sanremohoteles.com/en/list.html" />
18	<link rel="alternate" hreflang="pt-BR" href="https://www.sanremohoteles.com/pt/list.html" />
19	<link rel="alternate" hreflang="fr-FR" href="https://www.sanremohoteles.com/fr/list.html" />
20	<link rel="alternate" hreflang="x-default" href="https://www.sanremohoteles.com/list.html" />
21	<meta property="og:url" content="https://www.sanremohoteles.com/en/list.html" />
22	<meta property="og:type" content="website" />
23	<meta property="og:title" content="HSR TERMAS - The best price, guaranteed!" />
24
25
26	<meta name="description" content="Hotel HSR Termas se encuentra ubicado en pleno centro de la ciudad Termal de Rio Hondo, Santiago del Estero.">
27	<meta property="og:description" content="Hotel HSR Termas se encuentra ubicado en pleno centro de la ciudad Termal de Rio Hondo, Santiago del Estero." />
28			<meta property="og:image" content="https://images-new.pxsol.com/lFRnkg-X1th2dYVtxIxo7d-DEunBB9BSFOoXoGNQVSw/rs:fill:630:430:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F23437%2Fcompany%2Flibrary%2Fuser%2F1602001633576143f09f9529628de298d9cc1c25e5f.jpg@jpg" />
29		
30	<link href="https://fonts.googleapis.com/css?family=Libre+Franklin:200,300,500&display=swap" rel="stylesheet">
31
32		<link data-href="https://cdn.pxsol.com/css_3.css?pos=Sanremohoteles&device=computer&v=1790891789&posv=1788314973_2025-08-29T20:43:48.000Z&filev=1782498034" rel='stylesheet' type="text/css" />
33
34	
35		<link rel="icon" type="image/png" sizes="16x16" href="https://images-new.pxsol.com/i8aTnK9z_I7yMOE6uhTDRjt7u974dallnI0vpd-wgKc/rs:fill:16:16:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
36		<link rel="icon" type="image/png" sizes="32x32" href="https://images-new.pxsol.com/Na6WSYRu9xt0nvBwWoJXCjcZUU0hDjy94UBvLStJTnE/rs:fill:32:32:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
37		<link rel="icon" type="image/png" sizes="48x48" href="https://images-new.pxsol.com/vXQsDGVrobVKfmOIBuWkATPU3ay3i4mI-e7hG6GBTmQ/rs:fill:48:48:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
38		<link rel="icon" type="image/png" sizes="96x96" href="https://images-new.pxsol.com/qnK7mhDfUwm27MqRnhtiKMQtvFhqKxXMmPbKm2rPOkg/rs:fill:96:96:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
39		<link rel="shortcut icon" href="https://files-p.pxsol.com/13307/company/library/user/5126930364ea8a9a40b647af1c2cabae351ef5e772.png" />
40
41		<link rel="apple-touch-icon" sizes="57x57" href="https://images-new.pxsol.com/0Ns7SAkuELF-9IkX4hwCBxt0JYnuWua_OiolB5A94JU/rs:fill:57:57:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
42		<link rel="apple-touch-icon" sizes="72x72" href="https://images-new.pxsol.com/Wf7bwULbfl4jw3AgLGjvEh_CbwL48bqth4VC5a_NJXk/rs:fill:72:72:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
43		<link rel="apple-touch-icon" sizes="76x76" href="https://images-new.pxsol.com/mUynYO5S4DHMv7SRfMIiemu5rchRgy8mvncyJ4HjxQ0/rs:fill:76:76:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
44		<link rel="apple-touch-icon" sizes="114x114" href="https://images-new.pxsol.com/tDzltV8zRUlgI2CqfSvTHm4CvOGkjw1lAtb188TIrqs/rs:fill:114:114:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
45		<link rel="apple-touch-icon" sizes="120x120" href="https://images-new.pxsol.com/DjfKoHijr5ulDWQ1sKqrOkBwJqiyAIKGu2cjFF-c7nw/rs:fill:120:120:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
46		<link rel="apple-touch-icon" sizes="144x144" href="https://images-new.pxsol.com/8Skc2RbdMtUnWpu_vtjiJvyDmIg1DBiaJ9VjO8kPlpo/rs:fill:144:144:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
47		<link rel="apple-touch-icon" sizes="152x152" href="https://images-new.pxsol.com/jOm0xThLFbk9O-i6qVU7-VOzF3PxvnB8Z3nUhCJdChU/rs:fill:152:152:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
48		<link rel="apple-touch-icon" sizes="180x180" href="https://images-new.pxsol.com/FHLyab3zm8ybaGF8mU38Z-T0jcfkqHDCWIp-bzF_RYU/rs:fill:180:180:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png">
49	
50	
51
52	<link data-href="/neo_checkout/responsive.css?filev=1778144039" rel="stylesheet" type="text/css" />
53
54		<link href="/neo_marketplace_lp/css.css?v=1790891789" rel="stylesheet" type="text/css" />
55
56		<style>
57		.neo_bgcolor_primary, .MuiButton-containedSecondary {
58background-color: #b09d21 !important;
59}
60
61.MuiButton-containedSecondary.MuiButton-contained.Mui-disabled {
62    box-shadow: none !important;
63    background-color: rgba(0, 0, 0, 0.12) !important;
64}
65
66.neo_color_primary {
67color: #b09d21;
68}
69
70.cta_webcheckin_px:not(.btn_sesion) {
71background-color: #b09d21 !important;
72color: white !important;
73border: none !important;
74font-weight: bold;
75right: 0;
76display: flex;
77align-items: center;
78font-size: 13px !important;
79padding: 7px 8px !important;
80line-height: 1.2;
81text-align: left;
82}
83
84.cta_webcheckin_px.btn_sesion {
85border: none !important;
86font-weight: bold;
87right: 0;
88display: flex;
89align-items: center;
90font-size: 13px !important;
91padding: 7px 8px !important;
92line-height: 1.2;
93text-align: left;
94}
95
96.cta_webcheckin_px * {
97color: inherit !important;
98}
99
100.cta_webcheckin_px.btn_sesion > i {
101    color: #b09d21 !important;
102}
103
104.cta_webcheckin_px {
105background-color: #c0b14e;
106}
107.cta_webcheckin_px i {
108margin-right: 10px;
109font-size: 15px;
110}
111
112.neo_bgcolor_primary:hover, .MuiButton-containedSecondary:hover {
113background-color: #c0b14e;
114}
115.px-sm-inputcontainer:hover label {
116color: #b09d21 !important;
117}
118div.ui-datepicker#ui-datepicker-div .ui-state-active, div.ui-datepicker#ui-datepicker-div .ui-widget-content .ui-state-active, div.ui-datepicker#ui-datepicker-div .ui-widget-header .ui-state-active, div.ui-datepicker#ui-datepicker-div td:not(.ui-datepicker-today) .ui-state-highlight, div.ui-datepicker#ui-datepicker-div .ui-widget-content .ui-state-highlight, div.ui-datepicker#ui-datepicker-div .ui-widget-header .ui-state-highlight {
119background-color: #b09d21 !important;
120}
121
122.px-mk-iconchangesearch {
123color: #b09d21 !important;
124}
125
126.px-mk-locationdropdown>*:hover .px-mk-locationimg {
127fill: #b09d21 !important;
128}
129
130.neo_mobile_supratop .neo_supratop_selector i {
131color: #b09d21 !important;
132}
133
134.standard_cta {
135padding: 10px 15px;
136box-sizing: border-box;
137border-radius: 5px;
138display: flex;
139justify-content: center;
140text-align: center;
141align-items: center;
142background: #b09d21 !important;
143color: #000000;
144transition: .2s ease-out;
145color: white;
146text-decoration: none;
147font-size: 18px;
148border: none;
149transition: .25s ease-out;
150cursor: pointer;
151}
152
153.standard_cta:hover {
154background-color: #c8bb64;
155}
156.standard_cta:disabled {
157opacity: .5;
158cursor: not-allowed;
159}
160
161.neo_cta_naked {
162font-weight: bold;
163text-decoration: none;
164color: #b09d21;
165display: inline-flex;
166background-color: transparent;
167border: none;
168cursor: pointer;
169transition: .2s ease-out;
170}
171
172.neo_cta_naked.gray {
173color: darkgray;
174}
175
176.neo_cta_naked:hover {
177color: #7c6e18;
178}
179
180.neo_cta_standard {
181border: none;
182border-radius: 10px;
183padding: 12px 20px;
184font-size: 18px;
185display: flex;
186align-items: center;
187justify-content: space-between;
188font-weight: bold;
189cursor: pointer;
190transition: .25s ease-out;
191box-sizing: border-box;
192text-decoration: none;
193}
194
195.neo_cta_standard.smaller {
196font-size: 16px;
197padding: 7px 13px;
198}
199
200.neo_cta_standard.shadow {
201box-shadow: 0px -1px 15px rgba(0,0,0,.3);
202}
203
204.neo_cta_standard.lowpadding {
205padding: 12px;
206}
207.neo_cta_standard.lowpadding > i {
208margin: 0;
209}
210
211.neo_cta_standard:disabled {
212opacity: .5;
213cursor: not-allowed;
214}
215
216.neo_cta_standard.primary {
217background: #b09d21;
218color: #000000;
219}
220.neo_cta_standard.white_cta {
221background: white;
222color: black;
223}
224.neo_cta_standard.primary:hover {
225background-color: #7c6e18;
226color: #FFFFFF;
227}
228.neo_cta_standard.primary:active {
229background-color: #584f11;
230color: #FFFFFF;
231}
232
233.neo_cta_standard > i {
234margin-left: 10px;
235}
236
237.neo_cta_standard:disabled, .neo_cta_standard[disabled] {
238cursor: not-allowed;
239filter: saturate(.3);
240opacity: 0.8;
241}
242
243.neo_cta_standard.v_disabled {
244background-color: #1e1e1e;
245}
246
247.neo_cta_standard .loading-text, .neo_cta_standard .success-text {
248display:none;
249}
250
251.neo_cta_standard.v_loading {
252pointer-events: none;
253}
254.neo_cta_standard.v_loading {
255position: relative;
256}
257.neo_cta_standard .loading-text {
258display:none;
259}
260.neo_cta_standard.v_loading > *:not(i,.loading-text) {
261display:none;
262}
263.neo_cta_standard.v_loading > i {
264position: relative;
265height: 20px;
266width: 20px;
267}
268
269.neo_cta_standard.v_loading > i:before {
270content: none;
271}
272
273.neo_cta_standard.v_loading > i::after {
274content: "";
275position: absolute;
276width: 100%;
277height: 100%;
278box-sizing: border-box;
279top: 0;
280left: 0;
281right: 0;
282bottom: 0;
283margin: auto;
284border: 0.25rem solid transparent;
285border-top-color: #ffffff;
286border-right-color: #ffffff;
287border-radius: 50%;
288animation: spinner .6s linear infinite;
289}
290.neo_cta_standard.v_loading .loading-text {
291display:block;
292}
293
294.neo_cta_standard.v_success {
295background-color: #00a651;
296}
297.neo_cta_standard.v_success > *:not(.success-text) {
298display:none;
299}
300.neo_cta_standard.v_success .success-text {
301display:block;
302}
303@keyframes button-loading-spinner {
304from {
305transform: rotate(0turn);
306}
307
308to {
309transform: rotate(1turn);
310}
311}
312
313@keyframes spinner {
314to {
315transform: rotate(360deg);
316}
317}
318
319#innerLoader, .innerLoader {
320height: 100%;
321display: flex;
322justify-content: center;
323align-items: center;
324min-height: 200px;
325}
326.neo_spin_loader {
327height: 60px;
328width: 60px;
329margin-bottom: 20px;
330}
331.neo_spin_loader:before {
332content: "";
333box-sizing: border-box;
334position: absolute;
335width: 60px;
336height: 60px;
337border-radius: 50%;
338border: 3px solid #ccc;
339border-top-color: #b09d21;
340animation: spinner 1s linear infinite;
341}
342.neo_bgcolor_primary {
343background-color: #b09d21;
344}
345
346.neo_button_partyselector {
347border: 1px solid #b09d21;
348height: 40px;
349width: 40px;
350display: flex;
351justify-content: center;
352align-items: center;
353text-align: center;
354background: transparent;
355color: #b09d21;
356margin: 10px 0;
357transition: .2s ease-out;
358border-radius: 10px;
359font-size: 18px;
360cursor: pointer;
361
362}
363
364.neo_button_partyselector:hover {
365background-color: #e0d8a7;
366}
367
368.neo_button_partyselector:focus {
369box-shadow: 0 0 0 3px #f0ecd3;
370}
371
372.neo_button_partyselector:active {
373color: #000000;
374background: #b09d21;
375}
376
377.neo_partyselector_quantity {
378padding: 15px 10px;
379font-size: 18px;
380}
381
382.ChildrensAgeSelect {
383width: 100%;
384box-sizing: border-box;
385padding: 10px;
386margin-bottom: 10px;
387border-radius: 10px;
388border-color: #ccc;
389}
390
391.PartyTypeSimulator .px-mk-inputicon svg {
392height: 1.2em;
393width: 1.2em;
394fill: #aaa;
395}
396.neo_bgcolor_primary_text {
397color: #000000;
398}
399
400.neo_pxuser_mainbtn {
401height: 3.5rem;
402max-height: 100%;
403overflow: hidden;
404transition: .2s ease-out;
405border-radius: .5rem;
406cursor: pointer;
407padding: 0 0.5rem;
408background-color: white;
409color: black !important;
410}
411
412.neo_pxuser_mainbtn * {
413color: black !important;
414}
415
416.neo_pxuser_mainbtn:hover {
417background-color: #f2f2f2;
418}
419
420.neo_pxuser_avatar_container {
421height: 100%;
422width: 3rem;
423display: flex;
424justify-content: center;
425align-items: center;
426}
427
428.neo_pxuser_avatar_container img {
429height: 80%;
430border-radius: 2rem;
431}
432
433.neo_pxuser_text_block {
434padding: .5rem .5rem .5rem .5rem;
435box-sizing: border-box;
436height: 100%;
437justify-content: center;
438line-height: 1.5;
439}
440
441.neo_pxuser_icon_dd {
442padding: .5rem;
443box-sizing: border-box;
444}
445
446.neo_pxuser_dropdown {
447position: absolute;
448right: 0;
449top: 100% !important;
450background: white;
451border-radius: .25rem;
452min-width: 110%;
453box-shadow: 0 3px 6px rgba(0,0,0,.5);
454text-align: left;
455display: none !important;
456}
457
458.neo_pxuser_dropdown * {
459color: black !important;
460}
461
462.neo_pxuser_dropdown.open {
463display: flex !important;
464top: 100% !important;
465}
466
467.neo_pxuser_dd_button {
468padding: 1rem;
469transition: .2s ease-out;
470cursor: pointer;
471text-decoration: none;
472color: black;
473}
474
475.neo_pxuser_dd_button > .mdi {
476font-size: 1.2rem;
477line-height: 1;
478vertical-align: middle;
479display: inline-flex;
480width: 2rem;
481justify-content: center;
482align-items: center;
483height: 2rem;
484}
485
486.neo_pxuser_dd_button > span {
487padding: .5rem;
488display: inline-flex;
489flex-grow: 1;
490width: calc(100% - 3rem);
491}
492
493.neo_pxuser_dd_button:hover {
494background-color: #f2f2f2;
495border-radius: .25rem;
496}
497
498.neo_pxuser_email {
499    max-width: 8rem;
500    overflow: hidden;
501    text-overflow: ellipsis;
502}
503
504@media only screen and (max-width: 468px) {
505
506    .neo_pxuser_mainbtn {
507        flex-flow: row-reverse;
508        flex-grow: 1;
509    }
510    
511    .neo_pxuser_icon_dd {
512        display: none;
513    }
514    
515    .neo_pxuser_text_block {
516        text-align: right;
517        text-transform: none;
518        letter-spacing: initial;
519        color: black;
520        line-height: 1;
521        max-width: 50%;
522    }
523    
524    .neo_pxuser_email {
525        display: none;
526    }
527    
528    .neo_pxuser_container {
529        text-transform: initial;
530        letter-spacing: initial;
531        font-weight: normal;
532        font-size: .8rem;
533    }
534    
535    .neo_pxuser_name {
536        font-weight: bold;
537        text-align: left;
538        white-space: nowrap;
539        overflow: hidden;
540        text-overflow: ellipsis;
541        display: flex;
542        align-items: center;
543    }
544    
545    .neo_pxuser_name strong {
546        display: inline-block;
547        max-width: 150px;
548        overflow: hidden;
549        white-space: nowrap;
550        text-overflow: ellipsis;
551        vertical-align: middle;
552        line-height: 1.2;
553    }
554    
555    .neo_pxuser_dd_button {
556        padding: .5rem;
557    }
558    
559
560}	</style>
561
562	<!-- Pos->Type: hotel.css -->
563
564<!-- Google tag (gtag.js) GA4 PXSOL TODOS LOS HOTELES -->
565<script>
566  window.dataLayer = window.dataLayer || [];
567  function gtag(){dataLayer.push(arguments);}
568  gtag('js', new Date());
569</script>
vendor: 64 bytes, lines 569-570
569
570<script async src="https://www.googletagmanager.com/gtag/js?id=
570G-SYSQR6EL6E
vendor: 12 bytes, line 570
570"></script>
571<script>
572  let globalSessionGA4Config = {
573    'pos': 'Sanremohoteles',
574    'product_id': '24471',
575    'product_name': 'HSR TERMAS',
576    'product_country': 'Argentina',
577    'product_state': 'Santiago del Estero',
578    'product_plaza': 'Termas de Río Hondo',
579    'product_score': '92.38'
580  };
581
582      gtag('config', 'G-2CG83MMWR8', globalSessionGA4Config);
583  
584      gtag('config', 'G-SYSQR6EL6E', globalSessionGA4Config);
585  
586  
587  // Mirror page_view into dataLayer for GTM debug (guard to avoid duplicates)
588  if (!window.__dl_page_view_pushed) {
589    window.dataLayer.push({ event: 'page_view' });
590    window.__dl_page_view_pushed = true;
591  }
592
593  
594</script>
594
595
596<!-- Global site tag (gtag.js)  -->
vendor: 64 bytes, lines 596-597
596
597<script async src="https://www.googletagmanager.com/gtag/js?id=
597UA-34517567-10
vendor: 12 bytes, line 597
597"></script>
598<script>
599  window.dataLayer = window.dataLayer || [];
600
601  function gtag() {
602    dataLayer.push(arguments);
603    try {
604      var args = Array.prototype.slice.call(arguments);
605      if (args[0] === 'event') {
606        var dlEventName = args[1];
607        var dlParams = (args.length > 2 && typeof args[2] === 'object') ? args[2] : {};
608        var dlObject = {};
609        for (var key in dlParams) {
610          if (Object.prototype.hasOwnProperty.call(dlParams, key)) {
611            dlObject[key] = dlParams[key];
612          }
613        }
614        dlObject.event = dlEventName;
615        window.dataLayer.push(dlObject);
616        
617        // Intercept add_to_cart and fire form_success after it
618        if (dlEventName === 'add_to_cart') {
619          setTimeout(function() {
620            console.log('%c[pxTrack] 🚀 FIRING form_success for add_to_cart', 'color: #4CAF50; font-weight: bold;');
621            window.pxTrack('form_success', {
622              form_id: 'add_to_cart',
623              lead_id: null,
624              transaction_id: null
625            });
626          }, 100);
627        }
628      }
629    } catch (e) {}
630  }
631  gtag('js', new Date());
632
633      gtag('config', 'G-2CG83MMWR8');
634  
635      gtag('config', 'UA-34517567-10');
636  
637  
638  </script>
638
639
640<!-- Pxsol Data Layer utilities and event wiring -->
641<script>
642  (function() {
643    // DEBUG: Confirm script is loaded and updated
644    console.log('%c[pxTrack] Script loaded - Version: 2025-01-15-no-inventory-v3-rate-limit-fix', 'color: #4CAF50; font-weight: bold; font-size: 12px;');
645    
646    // Define manual scan function globally FIRST (before any other code that might fail)
647    window.__px_manual_no_inventory_scan = function() {
648      console.log('[pxTrack] Manual scan triggered');
649      if (window.__px_no_inventory_scan_func) {
650        return window.__px_no_inventory_scan_func();
651      } else {
652        console.warn('[pxTrack] Watcher not initialized yet. Performing basic scan...');
653        // Perform basic scan even if watcher not initialized
654        try {
655          var hasNoInventory = false;
656          var detectionMethod = '';
657          
658          // Check DOM elements
659          var noInventoryContainer = document.querySelector('#no-inventory-container');
660          var noInventoryElement = document.querySelector('#no-inventory');
661          if (noInventoryContainer || noInventoryElement) {
662            hasNoInventory = true;
663            detectionMethod = 'DOM element (ID)';
664            console.log('[pxTrack] ✅ Found #no-inventory-container or #no-inventory');
665          }
666          
667          // Check px_jsonQuery
668          if (!hasNoInventory && typeof window.px_jsonQuery !== 'undefined' && window.px_jsonQuery) {
669            var jsonQuery = window.px_jsonQuery;
670            if (jsonQuery.ProductList && Array.isArray(jsonQuery.ProductList) && jsonQuery.ProductList.length > 0) {
671              var allZero = true;
672              for (var i = 0; i < jsonQuery.ProductList.length; i++) {
673                var product = jsonQuery.ProductList[i];
674                var productAvail = product.Availability || 0;
675                if (product.SkuList && Array.isArray(product.SkuList) && product.SkuList.length > 0) {
676                  for (var j = 0; j < product.SkuList.length; j++) {
677                    if (product.SkuList[j].Availability && product.SkuList[j].Availability > 0) {
678                      allZero = false;
679                      break;
680                    }
681                  }
682                } else if (productAvail > 0) {
683                  allZero = false;
684                }
685                if (!allZero) break;
686              }
687              if (allZero) {
688                hasNoInventory = true;
689                detectionMethod = 'Json_Query';
690                console.log('[pxTrack] ✅ All products have Availability === 0');
691              }
692            }
693          }
694          
695          if (hasNoInventory && !window.__px_no_inventory_pushed) {
696            console.log('[pxTrack] 🚀 FIRING no_inventory event manually - Method:', detectionMethod);
697            window.__px_no_inventory_pushed = true;
698            var params = {};
699            try {
700              // Try to get search params
701              var startRaw = (document.querySelector('#fechaingreso') || {}).value || '';
702              var endRaw = (document.querySelector('#fechaegreso') || {}).value || '';
703              params.start_date = startRaw;
704              params.end_date = endRaw;
705              params.no_inventory = true;
706            } catch(e) {}
707            window.pxTrack('no_inventory', params);
708            
709            // Fire form_success for search form after no_inventory
710            setTimeout(function() {
711              console.log('%c[pxTrack] 🚀 FIRING form_success for search form (after manual no_inventory)', 'color: #4CAF50; font-weight: bold;');
712              window.pxTrack('form_success', {
713                form_id: 'pxmk_searchform',
714                lead_id: null,
715                transaction_id: null
716              });
717            }, 100);
718            
719            return { fired: true, method: detectionMethod, params: params };
720          } else if (hasNoInventory) {
721            return { already_fired: true, method: detectionMethod };
722          } else {
723            return { no_inventory_detected: false, px_jsonQuery_exists: typeof window.px_jsonQuery !== 'undefined' };
724          }
725        } catch(e) {
726          console.error('[pxTrack] Error in manual scan:', e);
727          return { error: 'Scan failed', message: e.message };
728        }
729      }
730    };
731    
732    window.dataLayer = window.dataLayer || [];
733
734    // Helper that tracks to both GA4 and dataLayer
735    if (!window.pxTrack) {
736      window.pxTrack = function(eventName, params) {
737        params = params || {};
738        try { gtag('event', eventName, params); } catch(e) {}
739        try {
740          var dl = {};
741          for (var k in params) if (Object.prototype.hasOwnProperty.call(params, k)) dl[k] = params[k];
742          dl.event = eventName;
743          window.dataLayer.push(dl);
744        } catch(e) {}
745      };
746    }
747
748    // Privacy-safe booleans for PII presence
749    function bool(val) { return !!val; }
750
751    // Read URL param
752    function getUrlParam(name) {
753      try {
754        var params = new URLSearchParams(location.search);
755        return params.get(name) || '';
756      } catch (e) { return ''; }
757    }
758
759    // Normalize common date formats to YYYY-MM-DD (best-effort)
760    function normalizeDate(value) {
761      try {
762        if (!value) return '';
763        var v = String(value).trim();
764        // dd/mm/yyyy -> yyyy-mm-dd
765        if (/^\d{2}\/\d{2}\/\d{4}$/.test(v)) {
766          var p = v.split('/');
767          return [p[2], p[1].padStart(2, '0'), p[0].padStart(2, '0')].join('-');
768        }
769        // Already ISO
770        if (/^\d{4}-\d{2}-\d{2}$/.test(v)) return v;
771        return v;
772      } catch (e) {
773        return String(value || '');
774      }
775    }
776
777    // Session id generator
778    function getPxSessionId() {
779      try {
780        var key = 'px_session_id';
781        var match = document.cookie.match(new RegExp('(^| )' + key + '=([^;]+)'));
782        if (match) return match[2];
783        var id = (crypto && crypto.randomUUID) ? crypto.randomUUID() : String(Date.now()) + '_' + Math.floor(Math.random()*1e9);
784        document.cookie = key + '=' + id + ';path=/;max-age=' + (60*60*24*365).toString();
785        return id;
786      } catch(e) {
787        return 'sid_' + String(Date.now());
788      }
789    }
790
791    // Expose chat helpers (to be called from chat widget)
792    window.pxChatOpen = function(payload) { window.pxTrack('chat_open', payload || {}); };
793    window.pxChatMessage = function(payload) { window.pxTrack('chat_message', payload || {}); };
794    window.pxChatClose = function(payload) { window.pxTrack('chat_close', payload || {}); };
795    window.pxFormSuccess = function(payload) { window.pxTrack('form_success', payload || {}); };
796    window.pxNoInventory = function(payload) { window.pxTrack('no_inventory', payload || { no_inventory: true }); };
797
798    // Context payload from PHP (best-effort)
799    var pxContext = {
800      pos: 'Sanremohoteles',
801      product_id: '24471',
802      product_name: 'HSR TERMAS',
803      product_country: 'Argentina',
804      product_state: 'Santiago del Estero',
805      product_plaza: 'Termas de Río Hondo',
806      search_id: '',
807      start_date: '',
808      end_date: '',
809      transaction_id: '',
810      value: null,
811      currency: '',
812      page_url: location.href,
813      session_id: getPxSessionId()
814    };
815
816    // --- External engine redirect auto event when engine is disabled ---
817    
818    
819    // Adults/children/rooms best-effort totals from PHP when available
820        pxContext.adults = 0;
821    pxContext.children = 0;
822    pxContext.rooms = 0;
823
824    // Push context for GTM usage
825    try { window.dataLayer.push(Object.assign({ event: 'px_context' }, pxContext)); } catch(e) {}
826
827    // Boot event para validar integración en DebugView/GTM
828    if (!window.__px_boot_pushed) {
829      window.__px_boot_pushed = true;
830      window.pxTrack('px_layer_ready', {
831        session_id: pxContext.session_id,
832        page_url: pxContext.page_url,
833        pos: pxContext.pos || '',
834        product_id: pxContext.product_id || '',
835        search_id: pxContext.search_id || '',
836        ts: Date.now()
837      });
838    }
839
840    // ----- Search events wiring (V3/V8) -----
841    function getSearchParamsFromDOM() {
842      var startRaw = (document.querySelector('.neo_marketplace_buscador_v3 #fechaingreso') ||
843                      document.querySelector('#fechaingreso') ||
844                      document.querySelector('[name="Start"]') ||
845                      document.querySelector('#Start') || { }).value || pxContext.start_date || '';
846      var endRaw   = (document.querySelector('.neo_marketplace_buscador_v3 #fechaegreso') ||
847                      document.querySelector('#fechaegreso') ||
848                      document.querySelector('[name="End"]') ||
849                      document.querySelector('#End') || { }).value || pxContext.end_date || '';
850      var sidRaw   = (document.querySelector('#SearchID') || { }).value || getUrlParam('SearchID') || pxContext.search_id || '';
851      var params = {
852        start_date: normalizeDate(startRaw) || '',
853        end_date: normalizeDate(endRaw) || '',
854        pos: pxContext.pos || '',
855        hotel_id: pxContext.product_id || '',
856        page_url: location.href,
857        session_id: pxContext.session_id
858      };
859      if (sidRaw) params.search_id = sidRaw;
860      // Adults/children from UI if present
861      try {
862        var adultsEl = document.querySelector('.neo_partyselector_quantity[party-type="adults"]');
863        var childrenEl = document.querySelector('.neo_partyselector_quantity[party-type="children"]');
864        if (adultsEl) params.adults = Number(adultsEl.getAttribute('val') || adultsEl.textContent) || pxContext.adults || 0;
865        if (childrenEl) params.children = Number(childrenEl.getAttribute('val') || childrenEl.textContent) || pxContext.children || 0;
866        // Fallback from hidden GroupsForm if exists
867        if ((!params.adults || !params.children) && document.querySelector('#GroupsForm')) {
868          var gf = document.querySelector('#GroupsForm').value || '';
869          var rooms = 0, adults = 0, children = 0;
870          if (gf) {
871            gf.split(';').forEach(function(groupStr){
872              if (!groupStr) return;
873              rooms += 1;
874              var parts = groupStr.split(':');
875              if (parts.length < 2) return;
876              var trio = parts[1].split(',');
877              var a = parseInt(trio[0] || '0', 10);
878              adults += isNaN(a) ? 0 : a;
879              var childrenAges = (trio[1] || '0');
880              if (childrenAges && childrenAges !== '0') {
881                children += childrenAges.split('.').filter(Boolean).length;
882              }
883            });
884            if (!params.adults) params.adults = adults;
885            if (!params.children) params.children = children;
886            if (!params.rooms) params.rooms = rooms;
887          }
888        }
889      } catch(e) {}
890      return params;
891    }
892
893    // -- Estado COMPARTIDO del throttle de `search_modify` --------------------------------
894    // Vive FUERA de las funciones de cableado a proposito: `wireSearchEventsWhenReady()` puede
895    // re-ejecutar `wireSearchEvents()` varias veces hasta enganchar el buscador que llega por
896    // AJAX, y con el throttle dentro del cierre cada invocacion tenia su propia ventana de 1 s
897    // => dos cableados equivalentes podian emitir el mismo cambio dos veces.
898    var pxLastModifyMs = 0;
899    var pxLastSnapshot = null;
900
901    function getModifySnapshot() {
902      var p = getSearchParamsFromDOM();
903      return {
904        start_date: p.start_date || '',
905        end_date: p.end_date || '',
906        rooms: (p.rooms*1) || 0,
907        adults: (p.adults*1) || 0,
908        children: (p.children*1) || 0
909      };
910    }
911    function pushModifyFlagsIfChanged(prevSnap, nextSnap) {
912      try {
913        if (!prevSnap) return true;
914        var changes = {};
915        if (nextSnap.start_date !== prevSnap.start_date) changes.start_date = true;
916        if (nextSnap.end_date !== prevSnap.end_date) changes.end_date = true;
917        if ((nextSnap.rooms*1) !== (prevSnap.rooms*1)) changes.rooms = true;
918        if ((nextSnap.adults*1) !== (prevSnap.adults*1)) changes.adults = true;
919        if ((nextSnap.children*1) !== (prevSnap.children*1)) changes.children = true;
920        if (Object.keys(changes).length) return true;
921      } catch (e) {}
922      return false;
923    }
924    function throttledModifyFire() {
925      var now = Date.now();
926      if (now - pxLastModifyMs < 1000) return; // throttle to 1s
927      pxLastModifyMs = now;
928      var current = getModifySnapshot();
929      // Push GA/DataLayer event with current values
930      window.pxTrack('search_modify', getSearchParamsFromDOM());
931      // Also push a probe event with the current values for GTM debug
932      try {
933        window.dataLayer.push({
934          event: 'px_search_modify_probe',
935          search_modify: current,
936          ts: Date.now()
937        });
938      } catch(e) {}
939      // Update snapshot only if something changed
940      if (pushModifyFlagsIfChanged(pxLastSnapshot, current)) {
941        pxLastSnapshot = current;
942      }
943    }
944
945    /**
946     * Respaldos de `search_modify` que NO dependen de que el buscador exista en el DOM:
947     * listeners DELEGADOS en `document` (captura) + un poller de 400 ms sobre
948     * `#fechaingreso`/`#fechaegreso`/`#GroupsForm`.
949     *
950     * OJO: se instalan SIEMPRE en el init, sin pasar por `searchDomPresent()` ni por el tope de
951     * 20 s del watcher -- que es exactamente como se instalaban antes, cuando `wireSearchEvents()`
952     * se llamaba incondicionalmente. Justamente por ser delegados/poll son los que hacen que
953     * `search_modify` funcione sobre un buscador inyectado por AJAX: meterlos detras del
954     * reintento dejaba sin ningun listener a las sesiones donde el buscador tarda mas de 20 s
955     * (telefono con red lenta), que ANTES si los tenian.
956     */
957    function wireSearchFallbacks() {
958      if (window.__px_search_fallbacks_wired) return;
959      window.__px_search_fallbacks_wired = true;
960      pxLastSnapshot = getModifySnapshot();
961      // Debug: signal that search watchers are alive
962      try {
963        window.dataLayer.push({
964          event: 'px_search_watchers_ready',
965          ts: Date.now()
966        });
967      } catch(e) {}
968      // Listeners delegados (capturan aunque el DOM se re-renderice)
969      try {
970        if (!window.__px_global_search_listeners) {
971          window.__px_global_search_listeners = true;
972          var selectorList = ['#fechaingreso','#fechaegreso','.AdultsSelect','.ChildrensSelect','.neo_partyselector_quantity','.ChildrensAgeSelect'];
973          function matchesTracked(el) {
974            try { return selectorList.some(function(sel){ return el && el.matches && el.matches(sel); }); } catch(e) { return false; }
975          }
976          document.addEventListener('change', function(e){ if (matchesTracked(e.target)) throttledModifyFire(); }, true);
977          document.addEventListener('input', function(e){ if (matchesTracked(e.target)) throttledModifyFire(); }, true);
978          document.addEventListener('click', function(e){
979            var t = e.target;
980            if (t && t.classList && t.classList.contains('neo_button_partyselector')) throttledModifyFire();
981          }, true);
982        }
983      } catch(e) {}
984      // Robust watchers for datepicker-driven value changes and hidden GroupsForm updates
985      try {
986        if (!window.__px_modify_watchers_started) {
987          window.__px_modify_watchers_started = true;
988          var lastIn = (document.querySelector('#fechaingreso') || {}).value || '';
989          var lastOut = (document.querySelector('#fechaegreso') || {}).value || '';
990          var lastGF = (document.querySelector('#GroupsForm') || {}).value || '';
991          setInterval(function(){
992            try {
993              var inEl = document.querySelector('#fechaingreso');
994              var outEl = document.querySelector('#fechaegreso');
995              var gfEl = document.querySelector('#GroupsForm');
996              var curIn = inEl ? inEl.value : '';
997              var curOut = outEl ? outEl.value : '';
998              var curGF = gfEl ? gfEl.value : '';
999              if (curIn !== lastIn || curOut !== lastOut || curGF !== lastGF) {
1000                lastIn = curIn; lastOut = curOut; lastGF = curGF;
1001                throttledModifyFire();
1002              }
1003            } catch(e) {}
1004          }, 400);
1005        }
1006      } catch(e) {}
1007    }
1008
1009    /**
1010     * SOLO los cableados que necesitan el nodo del buscador en el DOM: `search_open` (existe el
1011     * contenedor) y `search_submit` (existe el form). Los reintenta `wireSearchEventsWhenReady()`.
1012     *
1013     * OJO: ya NO cuelga listeners de `search_modify` del contenedor. Eran un duplicado EXACTO de
1014     * los delegados de `wireSearchFallbacks()` (la misma lista de 6 selectores y el mismo click
1015     * sobre `.neo_button_partyselector`). Mientras esta funcion corria una sola vez compartian el
1016     * cierre -- y con el el throttle -- asi que no se notaba; con el reintento podian quedar en
1017     * invocaciones distintas y contar el mismo cambio dos veces.
1018     */
1019    function wireSearchEvents() {
1020      var searchForm = document.querySelector('.neo_marketplace_buscador_v3 #pxmk_searchform') || document.querySelector('#search_form');
1021      var searchContainer = document.querySelector('.neo_marketplace_buscador_v3') || document.querySelector('.px-mk-buscador-container') || document.querySelector('#search_form') || document.querySelector('.PartyTypeSimulatorContainer');
1022
1023      if (searchContainer && !window.__px_search_container_bound) {
1024        window.__px_search_container_bound = true;
1025        // fire search_open once
1026        if (!window.__px_search_open_pushed) {
1027          window.__px_search_open_pushed = true;
1028          window.pxTrack('search_open', { page_url: location.href });
1029        }
1030      }
1031
1032      if (searchForm && !window.__px_search_form_bound) {
1033        window.__px_search_form_bound = true;
1034        searchForm.addEventListener('submit', function() {
1035          window.pxTrack('search_submit', getSearchParamsFromDOM());
1036        }, true);
1037        var searchBtn = document.querySelector('.neo_marketplace_buscador_v3 #PxMkAcion_Search') || document.querySelector('#Acion_Search');
1038        if (searchBtn) {
1039          searchBtn.addEventListener('click', function() {
1040            window.pxTrack('search_submit', getSearchParamsFromDOM());
1041          }, true);
1042        }
1043      }
1044    }
1045
1046    /**
1047     * El buscador NO existe al DOMContentLoaded en NINGUNA `lp.html`: lo inyecta por AJAX
1048     * `neo_modules/lp_cart.php` (CargarModulo) cientos de ms despues. `wireSearchEvents()` se
1049     * cableaba UNA sola vez, asi que sus dos gates de DOM evaluaban null y no se emitian nunca
1050     * `search_open` (gate `if (searchContainer)`) ni `search_submit` (gate `if (searchForm)`),
1051     * ni `start_search`/`egreso_search` (`wireDatePickers`). Sólo disparaban en las HOME
1052     * server-rendered, donde el buscador viene en el HTML inicial.
1053     *
1054     * Medido en vivo el 2026-08-21 sobre `fincaogawa.reservadirecto.com/lp.html`: los 6
1055     * selectores del buscador estan en 0 en DOMContentLoaded (1389 ms) y en load (2369 ms), y
1056     * recien aparecen a los ~3010 ms; un click en el submit real (`#Acion_Search`) generaba
1057     * SearchID y `has_inventory`, pero CERO `search_submit`.
1058     *
1059     * `has_inventory`/`no_inventory` nunca sufrieron esto porque cuelgan de una cascada de scans
1060     * + MutationObserver (`wireNoInventoryWatcher` / `observeAvailabilityContainers`), no de un
1061     * cableado one-shot. Esta funcion aplica esa MISMA tecnica a la fam
1061ilia de busqueda.
1062     *
1063     * Se apaga sola en cuanto engancha el contenedor y el form (los dos productores de
1064     * `search_open`/`search_submit`), con tope duro de 20 s. Cada bind es idempotente por su
1065     * propia guarda global, asi que re-correr no duplica ni un listener ni un evento.
1066     */
1067    function searchDomPresent() {
1068      try {
1069        return !!(document.querySelector('.neo_marketplace_buscador_v3')
1070          || document.querySelector('.px-mk-buscador-container')
1071          || document.querySelector('#search_form')
1072          || document.querySelector('.PartyTypeSimulatorContainer')
1073          || document.querySelector('#fechaingreso'));
1074      } catch (e) { return false; }
1075    }
1076    function wireSearchEventsWhenReady() {
1077      if (window.__px_search_ready_watcher) return;
1078      window.__px_search_ready_watcher = true;
1079      var mo = null;
1080      var stopped = false;
1081      function stop() {
1082        if (stopped) return;
1083        stopped = true;
1084        if (mo) { try { mo.disconnect(); } catch (e) {} mo = null; }
1085      }
1086      function attempt() {
1087        if (stopped) return;
1088        // Chequeo barato primero: sin buscador en el DOM no se toca nada. Evita correr
1089        // `wireSearchEvents()` (que lee bastante DOM) en cada mutacion de una pagina sin motor.
1090        if (!searchDomPresent()) return;
1091        if (!window.__px_search_container_bound || !window.__px_search_form_bound) {
1092          try { wireSearchEvents(); } catch (e) {}
1093        }
1094        if (!window.__px_date_pickers_bound) {
1095          try { wireDatePickers(); } catch (e) {}
1096        }
1097        if (window.__px_search_container_bound && window.__px_search_form_bound) stop();
1098      }
1099      attempt();
1100      if (stopped) return;
1101      [200, 500, 1000, 2000, 3000, 5000, 8000, 12000].forEach(function(ms){
1102        setTimeout(attempt, ms);
1103      });
1104      setTimeout(stop, 20000);
1105      if ('MutationObserver' in window && document.body) {
1106        try {
1107          mo = new MutationObserver(function(){
1108            if (stopped || mo.__px_scheduled) return;
1109            mo.__px_scheduled = true;
1110            setTimeout(function(){ if (mo) { mo.__px_scheduled = false; } attempt(); }, 100);
1111          });
1112          mo.observe(document.body, { childList: true, subtree: true });
1113        } catch (e) { mo = null; }
1114      }
1115    }
1116
1117    // ----- Form events wiring (generic) -----
1118    function wireFormEvents() {
1119      var forms = Array.prototype.slice.call(document.querySelectorAll('form:not([data-px-ignore="true"])'));
1120      var observed = new WeakSet();
1121
1122      // form_view when enters viewport first time
1123      if ('IntersectionObserver' in window) {
1124        var io = new IntersectionObserver(function(entries) {
1125          entries.forEach(function(entry) {
1126            if (entry.isIntersecting && !observed.has(entry.target)) {
1127              observed.add(entry.target);
1128              window.pxTrack('form_view', {
1129                form_id: entry.target.id || entry.target.getAttribute('name') || 'form',
1130                page_url: location.href
1131              });
1132            }
1133          });
1134        }, { threshold: 0.2 });
1135        forms.forEach(function(f){ io.observe(f); });
1136      } else {
1137        forms.forEach(function(f){
1138          window.pxTrack('form_view', { form_id: f.id || f.getAttribute('name') || 'form', page_url: location.href });
1139        });
1140      }
1141
1142      // form_interaction on field focus
1143      document.addEventListener('focusin', function(e) {
1144        var field = e.target;
1145        var form = field.form;
1146        if (!form) return;
1147        var fieldName = field.name || field.id || field.type || 'field';
1148        try {
1149          if (field.classList && field.classList.contains('neo_partyselector_quantity')) {
1150            var pt = field.getAttribute('party-type');
1151            if (pt) fieldName = 'party_' + pt;
1152          }
1153        } catch(e) {}
1154        window.pxTrack('form_interaction', {
1155          form_id: form.id || form.getAttribute('name') || 'form',
1156          field_name: fieldName
1157        });
1158      });
1159
1160      // form_submit with privacy-safe flags
1161      forms.forEach(function(form) {
1162        form.addEventListener('submit', function() {
1163          var email = form.querySelector('input[type="email"]');
1164          var phone = form.querySelector('input[type="tel"], input[name*="phone"], input[name*="telefono"]');
1165          var required = Array.prototype.slice.call(form.querySelectorAll('[required]'));
1166          var incomplete = required.some(function(el){ return !el.value; });
1167          window.pxTrack('form_submit', {
1168            form_id: form.id || form.getAttribute('name') || 'form',
1169            page_url: location.href,
1170            fields_status: incomplete ? 'incompletos' : 'completos',
1171            email_provided: bool(email && email.value),
1172            phone_provided: bool(phone && phone.value),
1173            session_id: pxContext.session_id
1174          });
1175        }, true);
1176      });
1177    }
1178    // Extra: map clicks/changes on party selector controls to meaningful field_name
1179    (function wirePartySelectorInteractions(){
1180      try {
1181        var lastMs = 0;
1182        function pushPartyInteraction(partyType) {
1183          var now = Date.now();
1184          if (now - lastMs < 400) return;
1185          lastMs = now;
1186          var form = document.querySelector('#search_form') || document.querySelector('.neo_marketplace_buscador_v3 #pxmk_searchform');
1187          window.pxTrack('form_interaction', {
1188            form_id: form ? (form.id || form.getAttribute('name') || 'form') : 'form',
1189            field_name: partyType ? ('party_' + partyType) : 'party_selector'
1190          });
1191        }
1192        document.addEventListener('click', function(e){
1193          var t = e.target;
1194          if (t && t.classList && t.classList.contains('neo_button_partyselector')) {
1195            pushPartyInteraction(t.getAttribute('party-type') || '');
1196          }
1197        }, true);
1198        document.addEventListener('change', function(e){
1199          var t = e.target;
1200          if (t && t.classList && t.classList.contains('neo_partyselector_quantity')) {
1201            pushPartyInteraction(t.getAttribute('party-type') || '');
1202          }
1203        }, true);
1204        document.addEventListener('input', function(e){
1205          var t = e.target;
1206          if (t && t.classList && t.classList.contains('neo_partyselector_quantity')) {
1207            pushPartyInteraction(t.getAttribute('party-type') || '');
1208          }
1209        }, true);
1210      } catch(e) {}
1211    })();
1212
1213    // Fire specific date interactions (optional)
1214    function wireDatePickers() {
1215      if (window.__px_date_pickers_bound) return;
1216      var inEl = document.querySelector('.neo_marketplace_buscador_v3 #fechaingreso');
1217      var outEl = document.querySelector('.neo_marketplace_buscador_v3 #fechaegreso');
1218      // Todavia no llego el buscador: NO se marca como cableado, lo reintenta
1219      // `wireSearchEventsWhenReady()`.
1220      if (!inEl && !outEl) return;
1221      window.__px_date_pickers_bound = true;
1222      if (inEl) inEl.addEventListener('change', function(){ window.pxTrack('start_search', { start_search: inEl.value || '', page_url: location.href }); });
1223      if (outEl) outEl.addEventListener('change', function(){ window.pxTrack('egreso_search', { egreso_search: outEl.value || '', page_url: location.href }); });
1224    }
1225
1226    // ----- WhatsApp click tracking (GA4/dataLayer + Meta Pixel "Contact") -----
1227    // El cliente pidió medir el clic en el botón de WhatsApp como una conversión más.
1228    // Se replica el patrón del resto de los eventos (window.pxTrack -> gtag + dataLayer) y,
1229    // si el POS tiene el pixel cargado (FacebookPixelID), se dispara el evento estándar "Contact".
1230    function wireWhatsappClick() {
1231      if (window.__px_whatsapp_wired) return;
1232      window.__px_whatsapp_wired = true;
1233
1234      var companyId = '';
1235      var lastWaMs = 0;
1236
1237      function extractPhone(href) {
1238        try {
1239          var m = href.match(/[?&]phone=(\+?\d+)/i);
1240          if (m) return m[1];
1241          m = href.match(/wa\.me\/(\+?\d+)/i);
1242          if (m) return m[1];
1243        } catch (e) {}
1244        return '';
1245      }
1246
1247      // Cualquier <a> hacia WhatsApp (api./web.whatsapp.com o wa.me), incluidos los botones
1248      // flotantes .wsp_floating (neo_social_links / standard_lp_motor) y #checkout_wsp_floating.
1249      function findWhatsappLink(el) {
1250        try {
1251          if (!el || !el.closest) return null;
1252          var a = el.closest('a');
1253          if (!a) return null;
1254          var href = (a.getAttribute('href') || '').toLowerCase();
1255          if (/(?:api\.|web\.)?whatsapp\.com|wa\.me/.test(href)) return a;
1256          if (a.closest('.wsp_floating') || a.closest('#checkout_wsp_floating')) return a;
1257        } catch (e) {}
1258        return null;
1259      }
1260
1261      // Delegado en captura: se dispara aunque el módulo re-renderice el botón (los .wsp_floating
1262      // se re-inyectan/mueven al body). target="_blank" mantiene la página viva para los beacons.
1263      document.addEventListener('click', function (e) {
1264        var link = findWhatsappLink(e.target);
1265        if (!link) return;
1266        var now = Date.now();
1267        if (now - lastWaMs < 800) return; // dedupe de doble-disparo
1268        lastWaMs = now;
1269
1270        var href = link.getAttribute('href') || '';
1271        var phone = extractPhone(href);
1272        var payload = {
1273          phone: phone,
1274          destination_url: href,
1275          pos: pxContext.pos || '',
1276          product_id: pxContext.product_id || '',
1277          product_name: pxContext.product_name || '',
1278          company_id: companyId || '',
1279          search_id: pxContext.search_id || '',
1280          currency: pxContext.currency || '',
1281          page_url: location.href,
1282          session_id: pxContext.session_id
1283        };
1284
1285        // GA4 + dataLayer (Google y GTM)
1286        window.pxTrack('whatsapp_click', payload);
1287
1288        // Meta Pixel: evento estándar "Contact". fbq solo existe si el POS tiene FacebookPixelID
1289        // (se inicializa en Conversion_SearchOK.php / PageView), así que este typeof es el guard.
1290        try {
1291          if (typeof window.fbq === 'function') {
1292            window.fbq('track', 'Contact', { content_name: 'whatsapp', content_category: 'whatsapp_click' });
1293          }
1294        } catch (e) {}
1295      }, true);
1296    }
1297
1298    // Enrich context from hidden inputs present in LP/consultas templates
1299    function enhanceContextFromHiddenInputs() {
1300      var startRaw = (document.querySelector('#Start') ||
1301                      document.querySelector('#fechaingreso') ||
1302                      document.querySelector('[name="Start"]') || { }).value || '';
1303      var endRaw   = (document.querySelector('#End') ||
1304                      document.querySelector('#fechaegreso') ||
1305                      document.querySelector('[name="End"]') || { }).value || '';
1306      var start = normalizeDate(startRaw);
1307      var end = normalizeDate(endRaw);
1308      var sid = (document.querySelector('#SearchID') || {}).value || '';
1309      var gf = (document.querySelector('#GroupsForm') || {}).value || '';
1310      var rooms = 0, adults = 0, children = 0;
1311      if (gf) {
1312        gf.split(';').forEach(function(groupStr){
1313          if (!groupStr) return;
1314          rooms += 1;
1315          var parts = groupStr.split(':');
1316          if (parts.length < 2) return;
1317          var trio = parts[1].split(',');
1318          var a = parseInt(trio[0] || '0', 10);
1319          adults += isNaN(a) ? 0 : a;
1320          var childrenAges = (trio[1] || '0');
1321          if (childrenAges && childrenAges !== '0') {
1322            children += childrenAges.split('.').filter(Boolean).length;
1323          }
1324        });
1325      }
1326      var updated = {};
1327      if (start && start !== pxContext.start_date) { pxContext.start_date = start; updated.start_date = start; }
1328      if (end && end !== pxContext.end_date) { pxContext.end_date = end; updated.end_date = end; }
1329      if (sid && sid !== pxContext.search_id) { pxContext.search_id = sid; updated.search_id = sid; }
1330      if (adults && adults !== pxContext.adults) { pxContext.adults = adults; updated.adults = adults; }
1331      if (children && children !== pxContext.children) { pxContext.children = children; updated.children = children; }
1332      if (rooms && rooms !== pxContext.rooms) { pxContext.rooms = rooms; updated.rooms = rooms; }
1333      if (Object.keys(updated).length) {
1334        try { window.dataLayer.push(Object.assign({ event: 'px_context_update' }, updated)); } catch(e) {}
1335      }
1336    }
1337
1338    // Parse GroupsForm like "1:2,0,0;1:1,5.7,0" into {rooms, adults, children}
1339    function parseGroupsForm(gf) {
1340      var out = { rooms: 0, adults: 0, children: 0 };
1341      try {
1342        if (!gf) return out;
1343        String(gf).split(';').forEach(function(groupStr){
1344          if (!groupStr) return;
1345          out.rooms += 1;
1346          var parts = groupStr.split(':');
1347          if (parts.length < 2) return;
1348          var trio = parts[1].split(',');
1349          var a = parseInt(trio[0] || '0', 10);
1350          out.adults += isNaN(a) ? 0 : a;
1351          var childrenAges = (trio[1] || '0');
1352          if (childrenAges && childrenAges !== '0') {
1353            out.children += childrenAges.split('.').filter(Boolean).length;
1354          }
1355          // Babies are not counted in children
1356        });
1357      } catch (e) {}
1358      return out;
1359    }
1360
1361    // Enrich context from px_jsonQuery (server-rendered Json_Query) when available
1362    function enhanceContextFromPxJson() {
1363      try {
1364        var q = (typeof window !== 'undefined' && window.px_jsonQuery) ? window.px_jsonQuery : (typeof px_jsonQuery !== 'undefined' ? px_jsonQuery : null);
1365        if (!q) return;
1366        var updated = {};
1367        // SearchID
1368        if (q.SearchID && String(q.SearchID) !== String(pxContext.search_id || '')) {
1369          pxContext.search_id = String(q.SearchID);
1370          updated.search_id = pxContext.search_id;
1371        }
1372        // Dates
1373        var start = normalizeDate(q.Start || '');
1374        var end = normalizeDate(q.End || '');
1375        if (start && start !== pxContext.start_date) { pxContext.start_date = start; updated.start_date = start; }
1376        if (end && end !== pxContext.end_date) { pxContext.end_date = end; updated.end_date = end; }
1377        // Pax/rooms from GroupsForm or Group array
1378        var paxParsed = null;
1379        if (q.GroupsForm) {
1380          paxParsed = parseGroupsForm(q.GroupsForm);
1381        } else if (q.Group && Array.isArray(q.Group)) {
1382          paxParsed = { rooms: 0, adults: 0, children: 0 };
1383          q.Group.forEach(function(g){
1384            paxParsed.rooms += 1;
1385            paxParsed.adults += (g.Adults*1) || 0;
1386            paxParsed.children += (g.Childs*1) || 0;
1387          });
1388        }
1389        if (paxParsed) {
1390          if (paxParsed.adults && paxParsed.adults !== pxContext.adults) { pxContext.adults = paxParsed.adults; updated.adults = paxParsed.adults; }
1391          if (paxParsed.children && paxParsed.children !== pxContext.children) { pxContext.children = paxParsed.children; updated.children = paxParsed.children; }
1392          if (paxParsed.rooms && paxParsed.rooms !== pxContext.rooms) { pxContext.rooms = paxParsed.rooms; updated.rooms = paxParsed.rooms; }
1393        }
1394        if (Object.keys(updated).length) {
1395          try { window.dataLayer.push(Object.assign({ event: 'px_context_update' }, updated)); } catch(e) {}
1396        }
1397      } catch (e) {}
1398    }
1399
1400    // Process API payloads to enrich context
1401    function processApiData(url, data) {
1402      try {
1403        if (!data) return;
1404        // avoid reprocessing same SearchID repeatedly
1405        window.__px_processed_sids = window.__px_processed_sids || {};
1406        var updated = {};
1407        var payload = data && data.Json_Query ? data.Json_Query : data;
1408        // SearchID present?
1409        if (typeof payload.SearchID !== 'undefined' && String(payload.SearchID)) {
1410          var sid = String(payload.SearchID);
1411          if (window.__px_processed_sids[sid]) return;
1412          if (sid && sid !== String(pxContext.search_id || '')) { pxContext.search_id = sid; updated.search_id = sid; }
1413          window.__px_processed_sids[sid] = true;
1414        }
1415        // Start / End can exist either top-level or nested
1416        var start = payload.Start || (data && data.Start) || '';
1417        var end = payload.End || (data && data.End) || '';
1418        start = normalizeDate(start);
1419        end = normalizeDate(end);
1420        if (start && start !== pxContext.start_date) { pxContext.start_date = start; updated.start_date = start; }
1421        if (end && end !== pxContext.end_date) { pxContext.end_date = end; updated.end_date = end; }
1422        // GroupsForm could be present
1423        var gf = payload.GroupsForm || (data && data.GroupsForm) || '';
1424        if (gf) {
1425          var parsed = parseGroupsForm(gf);
1426          if (parsed.adults && parsed.adults !== pxContext.adults) { pxContext.adults = parsed.adults; updated.adults = parsed.adults; }
1427          if (parsed.children && parsed.children !== pxContext.children) { pxContext.children = parsed.children; updated.children = parsed.children; }
1428          if (parsed.rooms && parsed.rooms !== pxContext.rooms) { pxContext.rooms = parsed.rooms; updated.rooms = parsed.rooms; }
1429        }
1430        // Some APIs include Group arrays; attempt totals
1431        if (payload.Group && Array.isArray(payload.Group)) {
1432          var aT = 0, cT = 0, rT = 0;
1433          payload.Group.forEach(function(g){
1434            if (!g) return;
1435            rT += 1;
1436            aT += (g.Adults*1) || 0;
1437            cT += (g.Childs*1) || 0;
1438          });
1439          if (aT && aT !== pxContext.adults) { pxContext.adults = aT; updated.adults = aT; }
1440          if (cT && cT !== pxContext.children) { pxContext.children = cT; updated.children = cT; }
1441          if (rT && rT !== pxContext.rooms) { pxContext.rooms = rT; updated.rooms = rT; }
1442        }
1443        if (Object.keys(updated).length) {
1444          try { window.dataLayer.push(Object.assign({ event: 'px_context_update' }, updated)); } catch(e) {}
1445        }
1446      } catch(e) {}
1447    }
1448
1449    // Intercept fetch/XHR to learn from API responses
1450    (function installNetworkTaps(){
1451      try {
1452        var origFetch = window.fetch;
1453        if (origFetch && !window.__px_fetch_hooked) {
1454          window.__px_fetch_hooked = true;
1455          window.fetch = function(input, init) {
1456            var p = origFetch(input, init);
1457            try {
1458              var url = typeof input === 'string' ? input : (input && input.url) || '';
1459              p.then(function(res){
1460                try {
1461                  var ct = (res.headers && res.headers.get && res.headers.get('content-type')) || '';
1462                  if (ct && ct.indexOf('application/json') !== -1 && (/\/query\/list/i.test(url) || /\/search\/info/i.test(url))) {
1463                    res.clone().json().then(function(json){ processApiData(url, json); }).catch(function(){});
1464                  }
1465                } catch(e) {}
1466              }).catch(function(){});
1467            } catch(e) {}
1468            return p;
1469          };
1470        }
1471      } catch(e) {}
1472      try {
1473        if (!window.__px_xhr_hooked && window.XMLHttpRequest) {
1474          window.__px_xhr_hooked = true;
1475          var XHR = window.XMLHttpRequest;
1476          var open = XHR.prototype.open;
1477          var send = XHR.prototype.send;
1478          XHR.prototype.open = function(method, url) {
1479            this.__px_url = url;
1480            return open.apply(this, arguments);
1481          };
1482          XHR.prototype.send = function(body) {
1483            try {
1484              this.addEventListener('load', function() {
1485                try {
1486                  var url = this.__px_url || '';
1487                  var ct = this.getResponseHeader && this.getResponseHeader('content-type') || '';
1488                  if (ct && ct.indexOf('application/json') !== -1 && (/\/query\/list/i.test(url) || /\/search\/info/i.test(url))) {
1489                    var txt = this.responseText;
1490                    try { processApiData(url, JSON.parse(txt)); } catch(e) {}
1491                  }
1492                } catch(e) {}
1493              });
1494            } catch(e) {}
1495            return send.apply(this, arguments);
1496          };
1497        }
1498      } catch(e) {}
1499    })();
1500
1501    // If we have SearchID but missing details, fetch /search/info
1502    function fetchSearchInfoIfNeeded() {
1503      try {
1504        var sid = pxContext.search_id || getUrlParam('SearchID') || (document.querySelector('#SearchID')||{}).value || '';
1505        var needsDates = !(pxContext.start_date && pxContext.end_date);
1506        var needsPaxs = !((pxContext.adults*1) > 0 || (pxContext.children*1) > 0);
1507        if (sid && (needsDates || needsPaxs)) {
1508          fetchSearchInfoForSid(sid);
1509        }
1510      } catch(e) {}
1511    }
1512
1513    // Cached fetch per SearchID
1514    function fetchSearchInfoForSid(sid) {
1515      try {
1516        window.__px_sid_promises = window.__px_sid_promises || {};
1517        if (window.__px_sid_promises[sid]) return window.__px_sid_promises[sid];
1518        var endpoint = 'https://api-1-eb-web.pxsol.io/search/info?SearchID=' + encodeURIComponent(sid);
1519        var pr = fetch(endpoint, { credentials: 'omit' }).then(function(res){
1520          if (!res.ok) return;
1521          var ct = (res.headers && res.headers.get && res.headers.get('content-type')) || '';
1522          if (ct.indexOf('application/json') === -1) return;
1523          return res.json();
1524        }).then(function(json){
1525          if (json) processApiData(endpoint, json);
1526          return true;
1527        }).catch(function(){ return false; });
1528        window.__px_sid_promises[sid] = pr;
1529        return pr;
1530      } catch(e) { return Promise.resolve(false); }
1531    }
1532
1533    // Ensure dates/paxs are ready before firing a callback
1534    function ensureContextReady(cb) {
1535      try {
1536        // Try quick enrichments first
1537        enhanceContextFromHiddenInputs();
1538        enhanceContextFromPxJson();
1539        var sid = pxContext.search_id || getUrlParam('SearchID') || (document.querySelector('#SearchID')||{}).value || '';
1540        var hasDates = !!(pxContext.start_date && pxContext.end_date);
1541        var hasPaxs = ((pxContext.adults*1) > 0) || ((pxContext.children*1) > 0);
1542        if (hasDates && hasPaxs) { cb(); return; }
1543        if (sid) {
1544          fetchSearchInfoForSid(sid).finally(function(){ cb(); });
1545          return;
1546        }
1547        // Last resort: try to enrich from DOM then run
1548        enhanceContextFromHiddenInputs();
1549        cb();
1550      } catch(e) { cb(); }
1551    }
1552
1553    // Detect "No hay resultados" to push no_inventory automatically
1554    function wireNoInventoryWatcher() {
1555      console.log('%c[pxTrack] 🔍 Initializing no_inventory watcher...', 'color: #2196F3; font-weight: bold; font-size: 14px;');
1556      console.log('[pxTrack] wireNoInventoryWatcher() called at:', new Date().toISOString());
1557      var scanCount = 0;
1558      var lastScanResult = null;
1559      var lastSearchID = null; // Track SearchID to detect new searches
1560      var lastScanTime = 0; // For throttling scans
1561      var SCAN_THROTTLE_MS = 1000; // Maximum 1 scan per second
1562      var EVENT_COOLDOWN_MS = 5000; // Minimum 5 seconds between events of the same type
1563      var lastEventTime = {}; // Track last time each event type was fired
1564      var MAX_SCANS_PER_SEARCH = 30; // Maximum scans per search to prevent excessive polling
1565      var __px_has_inventory_pushed = false; // Flag para has_inventory (opuesto de no_inventory)
1566      
1567      // Log immediately to confirm function is executing
1568      console.log('[pxTrack] ✅ wireNoInventoryWatcher() is executing - scan function will be defined');
1569      
1570      // Expose scan function to global manual trigger
1571      window.__px_no_inventory_scan_func = function() {
1572        console.log('[pxTrack] Manual scan triggered via global function');
1573        scan();
1574        return lastScanResult;
1575      };
1576      
1577      function checkJsonQueryAvailability() {
1578        try {
1579          // Check if px_jsonQuery is available
1580          if (typeof window.px_jsonQuery === 'undefined' || !window.px_jsonQuery) {
1581            return false;
1582          }
1583          
1584          var jsonQuery = window.px_jsonQuery;
1585          
1586          // Log full structure on first check (for debugging)
1587          if (!window.__px_jsonQuery_logged) {
1588            window.__px_jsonQuery_logged = true;
1589            try {
1590              console.log('[pxTrack] 📦 px_jsonQuery structure detected:', {
1591                hasProductList: !!(jsonQuery.ProductList),
1592                productListIsArray: Array.isArray(jsonQuery.ProductList),
1593                productListLength: jsonQuery.ProductList ? jsonQuery.ProductList.length : 0,
1594                sampleProduct: jsonQuery.ProductList && jsonQuery.ProductList.length > 0 ? {
1595                  hasAvailability: typeof jsonQuery.ProductList[0].Availability !== 'undefined',
1596                  availability: jsonQuery.ProductList[0].Availability,
1597                  hasSkuList: !!(jsonQuery.ProductList[0].SkuList),
1598                  skuListLength: jsonQuery.ProductList[0].SkuList ? jsonQuery.ProductList[0].SkuList.length : 0
1599                } : null
1600              });
1601              // Log first product details if available
1602              if (jsonQuery.ProductList && jsonQuery.ProductList.length > 0) {
1603                var firstProduct = jsonQuery.ProductList[0];
1604                console.log('[pxTrack] First product sample:', {
1605                  Availability: firstProduct.Availability,
1606                  hasSkuList: !!(firstProduct.SkuList),
1607                  SkuList: firstProduct.SkuList ? firstProduct.SkuList.map(function(sku) {
1608                    return { Availability: sku.Availability };
1609                  }).slice(0, 3) : null
1610                });
1611              }
1612            } catch(e) {
1613              console.error('[pxTrack] Error logging Json_Query structure:', e);
1614            }
1615          }
1616          
1617          // Check if ProductList exists and is an array
1618          if (!jsonQuery.ProductList) {
1619            // ProductList might not be loaded yet, but object exists
1620            return false;
1621          }
1622          
1623          if (!Array.isArray(jsonQuery.ProductList)) {
1624            console.warn('[pxTrack] px_jsonQuery.ProductList exists but is not an array:', typeof jsonQuery.ProductList);
1625            return false;
1626          }
1627          
1628          if (jsonQuery.ProductList.length === 0) {
1629            // Empty ProductList might indicate no inventory, but we need at least one product to check
1630            return false;
1631          }
1632          
1633          // Check if ALL products have Availability === 0
1634          var allZeroAvailability = true;
1635          var availabilityDetails = [];
1636          
1637          for (var i = 0; i < jsonQuery.ProductList.length; i++) {
1638            var product = jsonQuery.ProductList[i];
1639            // Check Availability property (could be at product level or in SkuList)
1640            var productAvailability = product.Availability || 0;
1641            var productHasAvailability = typeof product.Availability !== 'undefined';
1642            
1643            // If product has SkuList, check all SKUs
1644            if (product.SkuList && Array.isArray(product.SkuList) && product.SkuList.length > 0) {
1645              var allSkusZero = true;
1646              var skuAvailabilities = [];
1647              for (var j = 0; j < product.SkuList.length; j++) {
1648                var sku = product.SkuList[j];
1649                var skuAvail = sku.Availability || 0;
1650                skuAvailabilities.push(skuAvail);
1651                if (sku.Availability && sku.Availability > 0) {
1652                  allSkusZero = false;
1653                  break;
1654                }
1655              }
1656              availabilityDetails.push({
1657                productIndex: i,
1658                hasProductAvailability: productHasAvailability,
1659                productAvailability: productAvailability,
1660                hasSkuList: true,
1661                skuAvailabilities: skuAvailabilities,
1662                allSkusZero: allSkusZero
1663              });
1664              if (!allSkusZero) {
1665                allZeroAvailability = false;
1666                break;
1667              }
1668            } else {
1669              // Product-level availability check
1670              availabilityDetails.push({
1671                productIndex: i,
1672                hasProductAvailability: productHasAvailability,
1673                productAvailability: productAvailability,
1674                hasSkuList: false
1675              });
1676              if (productAvailability > 0) {
1677                allZeroAvailability = false;
1678                break;
1679              }
1680            }
1681          }
1682          
1683          // Log availability details when checking (first time or when all zero)
1684          if (allZeroAvailability || !window.__px_availability_details_logged) {
1685            window.__px_availability_details_logged = true;
1686            console.log('[pxTrack] Availability check result:', {
1687              allZeroAvailability: allZeroAvailability,
1688              totalProducts: jsonQuery.ProductList.length,
1689              details: availabilityDetails.slice(0, 3) // Show first 3 products
1690            });
1691          }
1692          
1693          return allZeroAvailability;
1694        } catch(e) {
1695          console.error('[pxTrack] Error checking Json_Query availability:', e);
1696          return false;
1697        }
1698      }
1699      
1700      function checkJsonQueryHasAvailability() {
1701        try {
1702          // Check if px_jsonQuery is available
1703          if (typeof window.px_jsonQuery === 'undefined' || !window.px_jsonQuery) {
1704            return { hasAvailability: false, totalAvailability: 0, availabilityDetails: null };
1705          }
1706          
1707          var jsonQuery = window.px_jsonQuery;
1708          
1709          // Check if ProductList exists and is an array
1710          if (!jsonQuery.ProductList || !Array.isArray(jsonQuery.ProductList) || jsonQuery.ProductList.length === 0) {
1711            return { hasAvailability: false, totalAvailability: 0, availabilityDetails: null };
1712          }
1713          
1714          var totalAvailability = 0;
1715          var hasAnyAvailability = false;
1716          var availabilityDetails = [];
1717          
1718          for (var i = 0; i < jsonQuery.ProductList.length; i++) {
1719            var product = jsonQuery.ProductList[i];
1720            var productAvailability = product.Availability || 0;
1721            var productTotal = 0;
1722            
1723            // If product has SkuList, sum all SKU availabilities
1724            if (product.SkuList && Array.isArray(product.SkuList) && product.SkuList.length > 0) {
1725              for (var j = 0; j < product.SkuList.length; j++) {
1726                var sku = product.SkuList[j];
1727                var skuAvail = sku.Availability || 0;
1728                productTotal += skuAvail;
1729                if (skuAvail > 0) {
1730                  hasAnyAvailability = true;
1731                }
1732              }
1733            } else {
1734              // Product-level availability
1735              productTotal = productAvailability;
1736              if (productAvailability > 0) {
1737                hasAnyAvailability = true;
1738              }
1739            }
1740            
1741            totalAvailability += productTotal;
1742            availabilityDetails.push({
1743              productIndex: i,
1744              productAvailability: productAvailability,
1745              productTotal: productTotal,
1746              hasSkuList: !!(product.SkuList && Array.isArray(product.SkuList) && product.SkuList.length > 0)
1747            });
1748          }
1749          
1750          return {
1751            hasAvailability: hasAnyAvailability,
1752            totalAvailability: totalAvailability,
1753            availabilityDetails: availabilityDetails,
1754            productCount: jsonQuery.ProductList.length
1755          };
1756        } catch(e) {
1757          console.error('[pxTrack] Error checking Json_Query for availability:', e);
1758          return { hasAvailability: false, totalAvailability: 0, availabilityDetails: null };
1759        }
1760      }
1761      
1762      function scan() {
1763        // THROTTLING: Evitar escaneos muy frecuentes
1764        var now = Date.now();
1765        if (now - lastScanTime < SCAN_THROTTLE_MS) {
1766          console.debug('[pxTrack] scan() throttled (too soon after last scan)');
1767          return;
1768        }
1769        lastScanTime = now;
1770        
1771        // Check if SearchID changed (new search) - if so, reset the flag
1772        try {
1773          var currentSearchID = null;
1774          if (typeof window.px_jsonQuery !== 'undefined' && window.px_jsonQuery && window.px_jsonQuery.SearchID) {
1775            currentSearchID = String(window.px_jsonQuery.SearchID);
1776          } else {
1777            var searchIDEl = document.querySelector('#SearchID');
1778            if (searchIDEl && searchIDEl.value) {
1779              currentSearchID = String(searchIDEl.value);
1780            } else {
1781              try {
1782                var urlParams = new URLSearchParams(location.search);
1783                currentSearchID = urlParams.get('SearchID') || null;
1784              } catch(e) {}
1785            }
1786          }
1787          
1788          // If SearchID changed, reset the flags for new search
1789          if (currentSearchID && currentSearchID !== lastSearchID && lastSearchID !== null) {
1790            console.log('[pxTrack] 🔄 New search detected (SearchID: ' + lastSearchID + ' → ' + currentSearchID + '), resetting flags');
1791            window.__px_no_inventory_pushed = false;
1792            __px_has_inventory_pushed = false;
1793            scanCount = 0; // Reset scan count
1794            lastScanTime = 0; // Reset throttling for new search
1795          }
1796          
1797          // Update lastSearchID
1798          if (currentSearchID) {
1799            lastSearchID = currentSearchID;
1800          }
1801        } catch(e) {
1802          console.error('[pxTrack] Error checking SearchID:', e);
1803        }
1804        
1805        // Don't scan if event already fired (unless we just reset it)
1806        if (window.__px_no_inventory_pushed) {
1807          console.debug('[pxTrack] scan() called but event already fired, skipping');
1808          return;
1809        }
1810        
1811        // Límite máximo de escaneos por búsqueda
1812        if (scanCount >= MAX_SCANS_PER_SEARCH) {
1813          console.debug('[pxTrack] scan() skipped - maximum scans per search reached (' + MAX_SCANS_PER_SEARCH + ')');
1814          return;
1815        }
1816        
1817        scanCount++;
1818        // Log first few scans to confirm function is working
1819        if (scanCount <= 5) {
1820          console.log('[pxTrack] scan() executing - Scan #' + scanCount);
1821        }
1822        var shouldFire = false;
1823        var detectionMethod = '';
1824        var debugInfo = {
1825          scanNumber: scanCount,
1826          px_jsonQuery_exists: typeof window.px_jsonQuery !== 'undefined' && !!window.px_jsonQuery,
1827          productList_length: 0,
1828          productList_details: []
1829        };
1830        
1831        // Method 1: Check px_jsonQuery.ProductList for all Availability === 0
1832        try {
1833          if (typeof window.px_jsonQuery !== 'undefined' && window.px_jsonQuery) {
1834            debugInfo.productList_length = (window.px_jsonQuery.ProductList && Array.isArray(window.px_jsonQuery.ProductList)) ? window.px_jsonQuery.ProductList.length : 0;
1835            
1836            if (checkJsonQueryAvailability()) {
1837              shouldFire = true;
1838              detectionMethod = 'Json_Query';
1839              try { 
1840                console.log('%c[pxTrack] ✅ no_inventory DETECTED via Json_Query (all Availability === 0) - Scan #' + scanCount, 'color: #FF5722; font-weight: bold;');
1841              } catch(_e){}
1842            } else {
1843              // Log details about why it didn't fire
1844              try {
1845                if (window.px_jsonQuery.ProductList && Array.isArray(window.px_jsonQuery.ProductList)) {
1846                  var productDetails = [];
1847                  for (var idx = 0; idx < Math.min(window.px_jsonQuery.ProductList.length, 3); idx++) {
1848                    var p = window.px_jsonQuery.ProductList[idx];
1849                    var prodAvail = p.Availability || 0;
1850                    var skuAvail = 'N/A';
1851                    if (p.SkuList && Array.isArray(p.SkuList) && p.SkuList.length > 0) {
1852                      var skuAvails = [];
1853                      for (var skuIdx = 0; skuIdx < p.SkuList.length; skuIdx++) {
1854                        skuAvails.push(p.SkuList[skuIdx].Availability || 0);
1855                      }
1856                      skuAvail = skuAvails.join(',');
1857                    }
1858                    productDetails.push({productAvail: prodAvail, skuAvail: skuAvail});
1859                  }
1860                  debugInfo.productList_details = productDetails;
1861                }
1862              } catch(e) {}
1863            }
1864          }
1865        } catch(e) {
1866          console.error('[pxTrack] Error in Json_Query availability check:', e);
1867        }
1868        
1869        // Method 2: Check for specific DOM elements (most reliable)
1870        if (!shouldFire) {
1871          try {
1872            // Check for #no-inventory-container or #no-inventory (from sindispo.php)
1873            var noInventoryContainer = document.querySelector('#no-inventory-container');
1874            var noInventoryElement = document.querySelector('#no-inventory');
1875            if (noInventoryContainer || noInventoryElement) {
1876              shouldFire = true;
1877              detectionMethod = 'DOM element (ID)';
1878              try { 
1879                console.log('%c[pxTrack] ✅ no_inventory DETECTED via DOM element (#no-inventory-container or #no-inventory) - Scan #' + scanCount, 'color: #FF5722; font-weight: bold;');
1880                console.log('[pxTrack] DOM elements found:', {
1881                  hasContainer: !!noInventoryContainer,
1882                  hasElement: !!noInventoryElement,
1883                  containerVisible: noInventoryContainer ? (noInventoryContainer.offsetParent !== null) : false,
1884                  elementText: noInventoryElement ? (noInventoryElement.textContent || noInventoryElement.innerText || '').substring(0, 50) : ''
1885                });
1886              } catch(_e){}
1887            } else if (scanCount <= 3 || scanCount % 10 === 0) {
1888              // Log when elements are not found (for debugging)
1889              try {
1890                console.debug('[pxTrack] DOM check: #no-inventory-container and #no-inventory not found (scan #' + scanCount + ')');
1891              } catch(_e){}
1892            }
1893          } catch(e) {
1894            console.error('[pxTrack] Error in DOM element check:', e);
1895          }
1896        }
1897        
1898        // Method 3: Fallback - Check DOM text for "No hay resultados" or "No results"
1899        if (!shouldFire) {
1900          try {
1901            var candidates = Array.prototype.slice.call(document.querySelectorAll('body *'));
1902            for (var i=0; i<candidates.length; i++) {
1903              var t = (candidates[i].innerText || '').trim();
1904              if (!t) continue;
1905              if (t.indexOf('No hay resultados') !== -1 || t.indexOf('No results') !== -1) { 
1906                shouldFire = true;
1907                detectionMethod = 'DOM text';
1908                try { 
1909                  console.log('%c[pxTrack] ✅ no_inventory DETECTED via DOM text - Scan #' + scanCount, 'color: #FF5722; font-weight: bold;');
1910                } catch(_e){}
1911                break; 
1912              }
1913            }
1914          } catch(e) {
1915            console.error('[pxTrack] Error in DOM text check:', e);
1916          }
1917        }
1918        
1919        // Store last scan result for debugging
1920        lastScanResult = {
1921          shouldFire: shouldFire,
1922          method: detectionMethod,
1923          scanCount: scanCount,
1924          px_jsonQuery_exists: debugInfo.px_jsonQuery_exists,
1925          productList_length: debugInfo.productList_length,
1926          productList_details: debugInfo.productList_details
1927        };
1928        
1929        // Log scan details every 5th scan or when px_jsonQuery becomes available
1930        if (scanCount % 5 === 0 || (debugInfo.px_jsonQuery_exists && scanCount <= 3)) {
1931          try {
1932            console.log('[pxTrack] no_inventory scan #' + scanCount + ':', {
1933              px_jsonQuery_exists: debugInfo.px_jsonQuery_exists,
1934              productList_length: debugInfo.productList_length,
1935              shouldFire: shouldFire,
1936              method: detectionMethod
1937            });
1938            if (debugInfo.productList_details.length > 0) {
1939              console.log('[pxTrack] Sample product availability:', debugInfo.productList_details);
1940            }
1941          } catch(_e){}
1942        }
1943        
1944        if (shouldFire && !window.__px_no_inventory_pushed) {
1945          // COOLDOWN: Verificar que no se haya disparado recientemente
1946          var eventType = 'no_inventory';
1947          var now = Date.now();
1948          if (lastEventTime[eventType] && (now - lastEventTime[eventType] < EVENT_COOLDOWN_MS)) {
1949            console.log('[pxTrack] ⏸️ Event cooldown active, skipping no_inventory event (last fired ' + Math.round((now - lastEventTime[eventType]) / 1000) + 's ago)');
1950            return;
1951          }
1952          
1953          window.__px_no_inventory_pushed = true;
1954          lastEventTime[eventType] = now; // Registrar tiempo del evento
1955          
1956          // Ensure dates/paxs are enriched before sending
1957          ensureContextReady(function(){
1958            var params = getSearchParamsFromDOM();
1959            params.no_inventory = true;
1960            try { 
1961              console.log('%c[pxTrack] 🚀 FIRING no_inventory event - Detection method: ' + detectionMethod + ' - Scan #' + scanCount, 'color: #4CAF50; font-weight: bold; font-size: 14px;');
1962              console.log('[pxTrack] no_inventory payload:', params);
1963              console.log('[pxTrack] Last scan result:', lastScanResult);
1964            } catch(_e){}
1965            window.pxTrack('no_inventory', params);
1966            
1967            // Fire form_success for search form after no_inventory
1968            setTimeout(function() {
1969              console.log('%c[pxTrack] 🚀 FIRING form_success for search form (after no_inventory)', 'color: #4CAF50; font-weight: bold;');
1970              window.pxTrack('form_success', {
1971                form_id: 'pxmk_searchform',
1972                lead_id: null,
1973                transaction_id: null
1974              });
1975            }, 100);
1976          });
1977        }
1978        
1979        // OPPOSITE: Check if there IS availability (has_inventory)
1980        // Only check if no_inventory was NOT detected and has_inventory hasn't been fired yet
1981        if (!shouldFire && !__px_has_inventory_pushed && !window.__px_no_inventory_pushed) {
1982          try {
1983            // Also verify that no "no inventory" DOM elements are present
1984            var noInventoryContainer = document.querySelector('#no-inventory-container');
1985            var noInventoryElement = document.querySelector('#no-inventory');
1986            var hasNoInventoryDOM = !!(noInventoryContainer || noInventoryElement);
1987            
1988            if (hasNoInventoryDOM) {
1989              console.debug('[pxTrack] has_inventory blocked - no inventory DOM elements present');
1990            } else {
1991              var availabilityInfo = checkJsonQueryHasAvailability();
1992              
1993              if (availabilityInfo.hasAvailability) {
1994                // COOLDOWN: Verificar que no se haya disparado recientemente
1995                var eventType = 'has_inventory';
1996                var now = Date.now();
1997                if (lastEventTime[eventType] && (now - lastEventTime[eventType] < EVENT_COOLDOWN_MS)) {
1998                  console.log('[pxTrack] ⏸️ Event cooldown active, skipping has_inventory event (last fired ' + Math.round((now - lastEventTime[eventType]) / 1000) + 's ago)');
1999                } else {
2000              
2001              __px_has_inventory_pushed = true;
2002              lastEventTime[eventType] = now;
2003              
2004              // Ensure dates/paxs are enriched before sending
2005              ensureContextReady(function(){
2006                var params = getSearchParamsFromDOM();
2007                params.has_availability = true;
2008                params.availability = availabilityInfo.totalAvailability;
2009                params.product_count = availabilityInfo.productC
2009ount || 0;
2010                
2011                // Add SearchID if available
2012                try {
2013                  if (typeof window.px_jsonQuery !== 'undefined' && window.px_jsonQuery && window.px_jsonQuery.SearchID) {
2014                    params.search_id = String(window.px_jsonQuery.SearchID);
2015                  }
2016                } catch(e) {}
2017                
2018                // Add availability details (first 3 products for debugging)
2019                if (availabilityInfo.availabilityDetails && availabilityInfo.availabilityDetails.length > 0) {
2020                  params.availability_details = availabilityInfo.availabilityDetails.slice(0, 3).map(function(detail) {
2021                    return {
2022                      product_index: detail.productIndex,
2023                      availability: detail.productTotal,
2024                      has_skus: detail.hasSkuList
2025                    };
2026                  });
2027                }
2028                
2029                try { 
2030                  console.log('%c[pxTrack] 🚀 FIRING has_inventory event (availability detected) - Scan #' + scanCount, 'color: #4CAF50; font-weight: bold; font-size: 14px;');
2031                  console.log('[pxTrack] has_inventory payload:', params);
2032                  console.log('[pxTrack] Availability info:', {
2033                    totalAvailability: availabilityInfo.totalAvailability,
2034                    productCount: availabilityInfo.productCount,
2035                    hasAvailability: availabilityInfo.hasAvailability
2036                  });
2037                } catch(_e){}
2038                window.pxTrack('has_inventory', params);
2039                
2040                // Fire form_success for search form after has_inventory
2041                setTimeout(function() {
2042                  console.log('%c[pxTrack] 🚀 FIRING form_success for search form (after has_inventory)', 'color: #4CAF50; font-weight: bold;');
2043                  window.pxTrack('form_success', {
2044                    form_id: 'pxmk_searchform',
2045                    lead_id: null,
2046                    transaction_id: null
2047                  });
2048                }, 100);
2049              });
2050                }
2051              }
2052            }
2053          } catch(e) {
2054            console.error('[pxTrack] Error checking for availability (has_inventory):', e);
2055          }
2056        }
2057      }
2058      
2059      // Try to intercept px_jsonQuery assignment (works if px_jsonQuery is undefined)
2060      try {
2061        if (typeof window.px_jsonQuery === 'undefined') {
2062          try {
2063            var _px_jsonQuery_value = undefined;
2064            var _px_jsonQuery_lastTrigger = 0;
2065            Object.defineProperty(window, 'px_jsonQuery', {
2066              get: function() { return _px_jsonQuery_value; },
2067              set: function(newValue) {
2068                _px_jsonQuery_value = newValue;
2069                if (newValue) {
2070                  try {
2071                    // Throttle interceptor triggers (máximo 1 cada 1 segundo)
2072                    var now = Date.now();
2073                    if (now - _px_jsonQuery_lastTrigger < 1000) {
2074                      console.debug('[pxTrack] px_jsonQuery interceptor throttled');
2075                      return;
2076                    }
2077                    _px_jsonQuery_lastTrigger = now;
2078                    console.log('[pxTrack] px_jsonQuery was set via Object.defineProperty, triggering scan');
2079                    setTimeout(function() { scan(); }, 150);
2080                  } catch(e) {
2081                    console.error('[pxTrack] Error in triggerScan:', e);
2082                  }
2083                }
2084              },
2085              configurable: true,
2086              enumerable: true
2087            });
2088          } catch(e) {
2089            console.error('[pxTrack] Object.defineProperty failed for px_jsonQuery:', e);
2090          }
2091        }
2092      } catch(e) {
2093        console.error('[pxTrack] Error setting up px_jsonQuery interception:', e);
2094      }
2095      
2096      // Multiple initial scans with increasing delays (for Chrome compatibility)
2097      function runInitialScans() {
2098        console.log('%c[pxTrack] Starting initial scans for no_inventory detection...', 'color: #FF9800; font-weight: bold;');
2099        console.log('[pxTrack] runInitialScans() called - will execute multiple scans');
2100        // Immediate scan
2101        console.log('[pxTrack] Executing immediate scan...');
2102        scan();
2103        // Delayed scans to catch px_jsonQuery if it loads later AND DOM elements that load dynamically
2104        setTimeout(function() { scan(); }, 200);
2105        setTimeout(function() { scan(); }, 500);
2106        setTimeout(function() { scan(); }, 1000);
2107        setTimeout(function() { scan(); }, 2000);
2108        setTimeout(function() { scan(); }, 3000);
2109        setTimeout(function() { scan(); }, 5000);
2110      }
2111      
2112      // Run initial scans after DOM is ready
2113      if (document.readyState === 'loading') {
2114        console.log('[pxTrack] DOM is loading, will wait for DOMContentLoaded');
2115        document.addEventListener('DOMContentLoaded', function() {
2116          console.log('[pxTrack] DOMContentLoaded fired, scheduling runInitialScans() in 100ms');
2117          setTimeout(function() {
2118            console.log('[pxTrack] Executing runInitialScans() now...');
2119            runInitialScans();
2120          }, 100);
2121        });
2122      } else {
2123        console.log('[pxTrack] DOM already ready, scheduling runInitialScans() in 100ms');
2124        setTimeout(function() {
2125          console.log('[pxTrack] Executing runInitialScans() now...');
2126          runInitialScans();
2127        }, 100);
2128      }
2129      
2130      // Also use requestAnimationFrame for Chrome (ensures DOM is painted)
2131      if (window.requestAnimationFrame) {
2132        requestAnimationFrame(function() {
2133          setTimeout(function() { scan(); }, 500);
2134        });
2135      }
2136      
2137      // Watch for px_jsonQuery availability (reduced polling frequency to prevent rate limiting)
2138      var jsonQueryCheckStart = Date.now();
2139      var jsonQueryCheckTimeout = 20000; // 20 seconds
2140      var jsonQueryCheckInterval = setInterval(function() {
2141        // Stop if timeout reached
2142        if (Date.now() - jsonQueryCheckStart > jsonQueryCheckTimeout) {
2143          clearInterval(jsonQueryCheckInterval);
2144          try { 
2145            console.log('[pxTrack] no_inventory px_jsonQuery polling timed out after', scanCount, 'scans');
2146            console.log('[pxTrack] Last scan result:', lastScanResult);
2147          } catch(_e){}
2148          return;
2149        }
2150        
2151        // Stop if event already fired
2152        if (window.__px_no_inventory_pushed) {
2153          clearInterval(jsonQueryCheckInterval);
2154          return;
2155        }
2156        
2157        // Check if px_jsonQuery is now available and scan
2158        if (typeof window.px_jsonQuery !== 'undefined' && window.px_jsonQuery) {
2159          scan();
2160        }
2161      }, 1000); // Reduced to 1 second to prevent rate limiting
2162      
2163      // Watch DOM changes and specifically look for #no-inventory-container or #no-inventory
2164      try {
2165        var mo = new MutationObserver(function(mutations) { 
2166          if (window.__px_no_inventory_pushed) return;
2167          
2168          // Throttling más agresivo para MutationObserver (2 segundos mínimo)
2169          var now = Date.now();
2170          if (mo.__px_lastScan && (now - mo.__px_lastScan < 2000)) {
2171            return;
2172          }
2173          
2174          // Check if any of the mutations added our target elements
2175          var foundTarget = false;
2176          for (var i = 0; i < mutations.length; i++) {
2177            var mutation = mutations[i];
2178            if (mutation.addedNodes && mutation.addedNodes.length > 0) {
2179              for (var j = 0; j < mutation.addedNodes.length; j++) {
2180                var node = mutation.addedNodes[j];
2181                if (node.nodeType === 1) { // Element node
2182                  // Check if the added node is our target or contains it
2183                  if (node.id === 'no-inventory-container' || node.id === 'no-inventory' ||
2184                      node.querySelector && (node.querySelector('#no-inventory-container') || node.querySelector('#no-inventory'))) {
2185                    foundTarget = true;
2186                    break;
2187                  }
2188                }
2189              }
2190            }
2191            if (foundTarget) break;
2192          }
2193          
2194          // If target found or if enough time passed, scan
2195          if (foundTarget || !mo.__px_lastScan || Date.now() - mo.__px_lastScan > 2000) {
2196            mo.__px_lastScan = Date.now();
2197            if (foundTarget) {
2198              console.log('[pxTrack] MutationObserver detected #no-inventory-container or #no-inventory added to DOM');
2199            }
2200            scan(); 
2201          }
2202        });
2203        mo.observe(document.body, { childList: true, subtree: true });
2204      } catch(e) {
2205        console.error('[pxTrack] MutationObserver failed for no_inventory:', e);
2206      }
2207      
2208      // Additional polling specifically for DOM elements (reduced frequency to prevent rate limiting)
2209      var domPollingStart = Date.now();
2210      var domPollingTimeout = 15000; // 15 seconds
2211      var domPollingInterval = setInterval(function() {
2212        if (Date.now() - domPollingStart > domPollingTimeout) {
2213          clearInterval(domPollingInterval);
2214          return;
2215        }
2216        
2217        if (window.__px_no_inventory_pushed) {
2218          clearInterval(domPollingInterval);
2219          return;
2220        }
2221        
2222        // Quick check for target elements
2223        var noInventoryContainer = document.querySelector('#no-inventory-container');
2224        var noInventoryElement = document.querySelector('#no-inventory');
2225        if (noInventoryContainer || noInventoryElement) {
2226          console.log('[pxTrack] DOM polling detected #no-inventory-container or #no-inventory');
2227          scan();
2228          clearInterval(domPollingInterval);
2229        }
2230      }, 1000); // Reduced to 1 second to prevent rate limiting
2231      
2232      // Confirm watcher is fully initialized
2233      console.log('%c[pxTrack] ✅ no_inventory watcher fully initialized and ready', 'color: #4CAF50; font-weight: bold; font-size: 14px;');
2234      console.log('[pxTrack] wireNoInventoryWatcher() completed at:', new Date().toISOString());
2235      console.log('[pxTrack] All watchers, observers, and polling intervals are now active');
2236    }
2237
2238    // Observe availability containers to re-enrich context after dynamic updates
2239    function observeAvailabilityContainers() {
2240      try {
2241        var targets = [];
2242        var el1 = document.querySelector('#neo_cart_results_list');
2243        var el2 = document.querySelector('#neo_cart_pedido_container');
2244        if (el1) targets.push(el1);
2245        if (el2) targets.push(el2);
2246        if (!targets.length || !('MutationObserver' in window)) return;
2247        var refresh = function() {
2248          enhanceContextFromHiddenInputs();
2249          enhanceContextFromPxJson();
2250        };
2251        var mo = new MutationObserver(function(muts) {
2252          // throttle bursts
2253          if (mo.__px_scheduled) return;
2254          mo.__px_scheduled = true;
2255          setTimeout(function(){ mo.__px_scheduled = false; refresh(); }, 100);
2256        });
2257        targets.forEach(function(t){ mo.observe(t, { childList: true, subtree: true }); });
2258        // initial pass
2259        refresh();
2260      } catch (e) {}
2261    }
2262
2263    // Init after DOM ready - LOG EARLY AND OFTEN
2264    console.log('%c[pxTrack] STEP 1: Checking DOM readyState...', 'color: #9C27B0; font-weight: bold; font-size: 14px;');
2265    console.log('[pxTrack] document.readyState:', document.readyState);
2266    console.log('[pxTrack] wireNoInventoryWatcher function exists:', typeof wireNoInventoryWatcher);
2267    
2268    if (document.readyState === 'loading') {
2269      console.log('%c[pxTrack] STEP 2: DOM is loading, setting up DOMContentLoaded listener', 'color: #9C27B0; font-weight: bold;');
2270      document.addEventListener('DOMContentLoaded', function() {
2271        console.log('%c[pxTrack] STEP 3: DOMContentLoaded fired!', 'color: #9C27B0; font-weight: bold; font-size: 16px;');
2272        console.log('[pxTrack] DOMContentLoaded fired, initializing watchers...');
2273        try {
2274          wireSearchFallbacks();
2275          wireSearchEventsWhenReady();
2276          wireFormEvents();
2277          wireWhatsappClick();
2278          if (typeof wireCheckoutForm === 'function') {
2279            wireCheckoutForm();
2280          } else {
2281            console.log('[pxTrack] wireCheckoutForm() not available (may be conditional)');
2282          }
2283          wireDatePickers();
2284          enhanceContextFromHiddenInputs();
2285          enhanceContextFromPxJson();
2286          fetchSearchInfoIfNeeded();
2287          console.log('%c[pxTrack] STEP 4: About to call wireNoInventoryWatcher()...', 'color: #FF5722; font-weight: bold; font-size: 16px;');
2288          try {
2289            wireNoInventoryWatcher();
2290            console.log('%c[pxTrack] STEP 5: wireNoInventoryWatcher() completed successfully!', 'color: #4CAF50; font-weight: bold; font-size: 16px;');
2291          } catch(e) {
2292            console.error('[pxTrack] ERROR calling wireNoInventoryWatcher():', e);
2293            console.error('[pxTrack] Error stack:', e.stack);
2294          }
2295          observeAvailabilityContainers();
2296          // delayed retries in case modules/scripts load late
2297          setTimeout(function(){ enhanceContextFromHiddenInputs(); enhanceContextFromPxJson(); }, 500);
2298          setTimeout(function(){ enhanceContextFromHiddenInputs(); enhanceContextFromPxJson(); }, 1500);
2299          // checkout success detector (thank-you page) - multiple attempts with delays
2300          if (typeof maybePushFormSuccess === 'function') {
2301            maybePushFormSuccess(); // Immediate
2302            setTimeout(function(){ maybePushFormSuccess(); }, 500); // After 500ms
2303            setTimeout(function(){ maybePushFormSuccess(); }, 1500); // After 1.5s
2304            setTimeout(function(){ maybePushFormSuccess(); }, 3000); // After 3s (for slow loading pages)
2305          }
2306        } catch(e) {
2307          console.error('[pxTrack] Error during initialization:', e);
2308        }
2309      });
2310    } else {
2311      console.log('%c[pxTrack] STEP 2: DOM already ready, initializing immediately', 'color: #9C27B0; font-weight: bold;');
2312      console.log('[pxTrack] DOM already ready, initializing watchers immediately...');
2313      try {
2314        wireSearchFallbacks();
2315        wireSearchEventsWhenReady();
2316        wireFormEvents();
2317        wireWhatsappClick();
2318        if (typeof wireCheckoutForm === 'function') {
2319          wireCheckoutForm();
2320        } else {
2321          console.log('[pxTrack] wireCheckoutForm() not available (may be conditional)');
2322        }
2323        wireDatePickers();
2324        enhanceContextFromHiddenInputs();
2325        enhanceContextFromPxJson();
2326        fetchSearchInfoIfNeeded();
2327        console.log('%c[pxTrack] STEP 4: About to call wireNoInventoryWatcher()...', 'color: #FF5722; font-weight: bold; font-size: 16px;');
2328        try {
2329          wireNoInventoryWatcher();
2330          console.log('%c[pxTrack] STEP 5: wireNoInventoryWatcher() completed successfully!', 'color: #4CAF50; font-weight: bold; font-size: 16px;');
2331        } catch(e) {
2332          console.error('[pxTrack] ERROR calling wireNoInventoryWatcher():', e);
2333          console.error('[pxTrack] Error stack:', e.stack);
2334        }
2335        observeAvailabilityContainers();
2336        setTimeout(function(){ enhanceContextFromHiddenInputs(); enhanceContextFromPxJson(); }, 500);
2337        setTimeout(function(){ enhanceContextFromHiddenInputs(); enhanceContextFromPxJson(); }, 1500);
2338        // checkout success detector (thank-you page) - multiple attempts with delays
2339        if (typeof maybePushFormSuccess === 'function') {
2340          maybePushFormSuccess(); // Immediate
2341          setTimeout(function(){ maybePushFormSuccess(); }, 500); // After 500ms
2342          setTimeout(function(){ maybePushFormSuccess(); }, 1500); // After 1.5s
2343          setTimeout(function(){ maybePushFormSuccess(); }, 3000); // After 3s (for slow loading pages)
2344        }
2345      } catch(e) {
2346        console.error('[pxTrack] Error during initialization:', e);
2347        console.error('[pxTrack] Error stack:', e.stack);
2348      }
2349    }
2350  })();
2351</script>
2351
2352
2353
2354<!-- Google Tag Manager -->
2355<script>
vendor: 436 bytes, lines 2355-2369
2355
2356  (function(w, d, s, l, i) {
2357    w[l] = w[l] || [];
2358    w[l].push({
2359      'gtm.start': new Date().getTime(),
2360      event: 'gtm.js'
2361    });
2362    var f = d.getElementsByTagName(s)[0],
2363      j = d.createElement(s),
2364      dl = l != 'dataLayer' ? '&l=' + l : '';
2365    j.async = true;
2366    j.src =
2367      'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
2368    f.parentNode.insertBefore(j, f);
2369  })(window, document, 'script', 'dataLayer', '
2369GTM-TPTNCNX
vendor: 4 bytes, line 2369
2369');
2370</script>
2370
2371<!-- End Google Tag Manager -->
2372
2373  <!-- Google Tag Manager -->
2374  
2374<script>
2375    (function(w, d, s, l, i) {
2376      w[l] = w[l] || [];
2377      w[l].push({
2378        'gtm.start': new Date().getTime(),
2379        event: 'gtm.js'
2380      });
2381      var f = d.getElementsByTagName(s)[0],
2382        j = d.createElement(s),
2383        dl = l != 'dataLayer' ? '&l=' + l : '';
2384      j.async = true;
2385      j.src =
2386        'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
2387      f.parentNode.insertBefore(j, f);
2388    })(window, document, 'script', 'dataLayer', 'AW-824953249');
2389  </script>
2389
2390  <!-- End Google Tag Manager -->
2391
2392
2393
2394
2395		<!-- Organization Schema for Brand/Logo SEO -->
2396	
2396<script type="application/ld+json">
2397	{
2398		"@context": "https://schema.org",
2399		"@type": "Organization",
2400		"name": "HSR TERMAS",
2401		"url": "https://www.sanremohoteles.com/list.html?mkt_active=true&Pid=24471&origin=marketplace_homepage",
2402		"logo": "https://images-new.pxsol.com/BJEPhNpVdEnVxWSk3IB3nOgtC4ZnU8fWWDVAfHZd6O8/rs:fill:512:512:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png",
2403		"image": "https://images-new.pxsol.com/BJEPhNpVdEnVxWSk3IB3nOgtC4ZnU8fWWDVAfHZd6O8/rs:fill:512:512:1/q:80/plain/https%3A%2F%2Ffiles-p.pxsol.com%2F13307%2Fcompany%2Flibrary%2Fuser%2F5126930364ea8a9a40b647af1c2cabae351ef5e772.png@png"
2404	}
2405	</script>
2405
2406	
2407</head>
2408
2409<body>
2410
2411    
2412    <div class="neo_loader">
2413<style>
2414.linear-activity{overflow:hidden;width:100%;height:4px;background-color:#B3E5FC;margin:0}.determinate{position:relative;max-width:100%;height:100%;-webkit-transition:width .5s ease-out 1s;-moz-transition:width .5s ease-out 1s;-o-transition:width .5s ease-out 1s;transition:width .5s ease-out 1s;background-color:#03A9F4}.indeterminate{position:relative;width:100%;height:100%}.indeterminate:after,.indeterminate:before{content:'';position:absolute;height:100%}.indeterminate:before{background-color:#03A9F4;animation:indeterminate_first 1.5s infinite ease-out}.indeterminate:after{background-color:#4FC3F7;animation:indeterminate_second 1.5s infinite ease-in}@keyframes indeterminate_first{0%{left:-100%;width:100%}100%{left:100%;width:10%}}@keyframes indeterminate_second{0%{left:-150%;width:100%}100%{left:100%;width:10%}}.neo_loader{position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999999999999999999999;background:#fff;height:100vh;width:100vw;display:flex;flex-direction:column;justify-content:center;align-items:center; padding: 40px;box-sizing: border-box;text-align: center;}.neo_loader .loaderline{width:400px;margin:40px;max-width:90vw}.loadername{color:#bbb;font-family:Helvetica,Arial,sans-serif;margin:10px auto 15px}.linear-activity.gray{background-color:#eaeaea}.gray .indeterminate:after,.gray .indeterminate:before{background-color:#bbb}.neo_megacontainer{display:none}</style>
2415
2416				<img style=" height: 250px; width: auto; object-fit: contain; max-width: 100%;" src="https://files-p.pxsol.com/13307/company/library/user/5126930364ea8a9a40b647af1c2cabae351ef5e772.png" alt="San Remo Hoteles">			
2417		    	<div class="loadername">
2418		San Remo Hoteles	</div>
2419	<div class="loaderline">
2420		<div class="linear-activity gray">
2421			<div class="indeterminate"></div>
2422		</div>
2423	</div>
2424</div>
2425
2426
2427    
2428            <div class="main_wraper_top">
2429            
2430
2431<div class="neo_template_container neo_template_ computer">
2432
2433
2434<div id='amerian_supratop'  class="amerian_top_container diplo_top_container flex flex-m-center"  ><div id='diploTopContainer'  class="flex flex-s-center amerian_main_top"  ><a  class="diplo_top_logo padding-5 padding-left-20 fullh"   href="/en/" target=""> <img loading="lazy"  class="diplo_top_logo_img fullh"   lazy-src="https://files-p.pxsol.com/13307/company/library/user/5126930364ea8a9a40b647af1c2cabae351ef5e772.png" alt="HSR TERMAS" > </image></a><div id='diploMenuTop'  class="amerian_menu_container flex-column flex-grow fullh padding-left-20 padding-right-20"  ><div  class="amerian_supramenu flex flex-s-center padding-20"  ><div  class='diplo_idiomas fullh' ><div class='pxModule_include  ModuloWrap_idiomas  '><div id="top_menu">
2435
2436		<div id="contenedor_top">
2437		<div id="menu_div_top">
2438			<div class="nav_top flex flex-s-center">
2439
2440				
2441				<div class="selectidiomaymoneda">
2442					<div class="currency_module_container" >
2443						<input type="hidden" id='ModuloIdioma_DefaultCurrency' value="ARS">
2444						<input type="hidden" id="ModuloIdioma_BookingEngineVersion" value="8">
2445						<div class="texto_idiomaymoneda">
2446							Currency - </div>
2447						<div id="deploymoneda" class='cursornormal';>
2448															<div class="monedaactual hidden" cur="ARS">
2449									ARS $								</div>
2450													</div>
2451											</div>
2452					<div class="texto_idiomaymoneda">
2453						Language - </div>
2454
2455					
2456						<div id="deployidioma" class="deployidioma">
2457															<img alt="flag" style="object-fit: contain;" class="bandera" src="/css/images/bnd_en.png" height="24px" width="24px" />
2458													</div>
2459
2460					
2461					<div id="idiomasshow" class="idiomasshow">
2462						<div class="titlemenujs">
2463							Choose the language of your preference						</div>
2464															<div class="idiomaitem">
2465																				<a class="idiomalink" lng="es" href="/list.html?mkt_active=true&amp;Pid=24471&amp;origin=marketplace_homepage&amp;lng=es" ignorar-vista="true" style="width: 100%;">
2466																							<img class="bandera" style="object-fit: contain;" src="/css/images/bnd_es.png" height="20" width="20" alt="es" />
2467																						<span class="nameidioma">
2468												Español											</span>
2469										</a>
2470									</div>
2471															<div class="idiomaitem">
2472																				<a class="idiomalink" lng="pt" href="/pt/list.html?mkt_active=true&amp;Pid=24471&amp;origin=marketplace_homepage&amp;lng=pt" ignorar-vista="true" style="width: 100%;">
2473																							<img class="bandera" style="object-fit: contain;" src="/css/images/bnd_pt.png" height="20" width="20" alt="pt" />
2474																						<span class="nameidioma">
2475												Português											</span>
2476										</a>
2477									</div>
2478															<div class="idiomaitem">
2479																				<a class="idiomalink" lng="fr" href="/fr/list.html?mkt_active=true&amp;Pid=24471&amp;origin=marketplace_homepage&amp;lng=fr" ignorar-vista="true" style="width: 100%;">
2480																							<img class="bandera" style="object-fit: contain;" src="/css/images/bnd_fr.png" height="20" width="20" alt="fr" />
2481																						<span class="nameidioma">
2482												Français											</span>
2483										</a>
2484									</div>
2485											</div>
2486				</div>
2487
2488				
2489    <a href="https://app.reservadirecto.com/auth_web?return_url=https%3A%2F%2Fwww.sanremohoteles.com%2Fauthorize.php%3Fredirect_url%3Dhttps%253A%252F%252Fwww.sanremohoteles.com%252Flist.html%253Fmkt_active%253Dtrue%2526Pid%253D24471%2526origin%253Dmarketplace_homepage&origin_pos=Sanremohoteles" ignorar-vista="true" class="btn_sesion cta_webcheckin_px margin-sm-right-10" style="text-decoration: none">
2490        <i class="mdi mdi-account" aria-hidden="true"></i>
2491        <span>
2492            Log in        </span>
2493    </a>
2494
2495    
2495<script src="https://accounts.google.com/gsi/intermediate"></script>
2495
2496    
2496<script src="https://accounts.google.com/gsi/intermediatesupport"></script>
2496
2497
2498            <div id="g_id_intermediate_iframe" data-src="https://eva.reservadirecto.com/core/includes/google_one_tap_intermediate_iframe.php?origin=https%3A%2F%2Fwww.sanremohoteles.com&amp;redirect_url=https%3A%2F%2Fwww.sanremohoteles.com%2Flist.html%3Fmkt_active%3Dtrue%26Pid%3D24471%26origin%3Dmarketplace_homepage&amp;pos=Sanremohoteles">
2499    </div>
2500
2501    <style>
2502        .neo_login_incentive {
2503            position: fixed;
2504            top: 10rem;
2505            right: calc(1rem + 10px);
2506            width: 375px;
2507            text-align: left;
2508            z-index: 1;
2509        }
2510
2511        .neo_login_incentive>div {
2512            margin: 0;
2513            border-radius: 10px;
2514        }
2515
2516        iframe#onetap_google_intermediate_iframe,
2517        #credential_picker_container,
2518        #credential_picker_container iframe,
2519        iframe[src*="google_one_tap_intermediate_iframe"] {
2520            top: 18.5rem !important;
2521            right: 1rem !important;
2522        }
2523
2524        @media only screen and (max-width: 468px) {
2525            .neo_login_incentive {
2526                display: none !important;
2527                bottom: 0rem;
2528                top: initial;
2529                left: 0;
2530                right: 0;
2531                position: fixed;
2532                width: 100%;
2533                text-transform: initial;
2534                letter-spacing: normal;
2535                font-weight: initial;
2536            }
2537
2538            iframe#onetap_google_intermediate_iframe,
2539            #credential_picker_container,
2540            #credential_picker_container iframe,
2541            iframe[src*="google_one_tap_intermediate_iframe"] {
2542                top: initial !important;
2543                bottom: 5rem !important;
2544                right: initial !important;
2545                z-index: 1;
2546            }
2547        }
2548    </style>
2549    
2549<script>
2550        (function() {
2551            var repositioned = false;
2552            var attempts = 0;
2553            var timer = setInterval(function() {
2554                if (repositioned || ++attempts > 60) { clearInterval(timer); return; }
2555                var el = document.getElementById('credential_picker_container')
2556                      || document.querySelector('iframe[src*="google_one_tap_intermediate_iframe"]')
2557                      || document.getElementById('onetap_google_intermediate_iframe');
2558                if (el) {
2559                    var target = (el.tagName === 'IFRAME' && el.parentElement && el.parentElement.style.position === 'fixed') ? el.parentElement : el;
2560                    var isMobile = window.innerWidth <= 468;
2561                    if (isMobile) {
2562                        target.style.setProperty('top', 'initial', 'important');
2563                        target.style.setProperty('bottom', '5rem', 'important');
2564                        target.style.setProperty('right', 'initial', 'important');
2565                    } else {
2566                        target.style.setProperty('top', '18.5rem', 'important');
2567                        target.style.setProperty('right', '1rem', 'important');
2568                    }
2569                    repositioned = true;
2570                    clearInterval(timer);
2571                }
2572            }, 100);
2573        })();
2574    </script>
2574
2575
2576			</div>
2577		</div>
2578	</div>
2579
2580</div>
2581</div></div></div></div><div  class="diplo_cta padding-20 toggle_search fullh heightplus1"   style='display: none'><i  class='mdi mdi-room-service-outline margin-right-10 fontsize-20px' ></i><div  class=""  >Reserve now</div></div></div></div> 
2582
2583</div>
2584
2585        </div>
2586    
2587    
2588    
2589    <noscript>This search engine requires Javascript enabled to function. Please update your browser or use the latest version of Chrome or Firefox for a better browsing experience</noscript>
2590
2591    <div class="neo_megacontainer mktplace_contentblock extend padding-sm-top-80">
2592
2593        <div style="display:none" id="loaderText">
2594    Looking for the best prices for your stay</div>
2595
2596<script>
2597    function IncluirModuloViaJS() {
2598
2599        window.apiUrl = window.apiUrl ? window.apiUrl : "https://api-1-eb-web.pxsol.io/";
2600        window.api2Url = window.api2Url ? window.api2Url : "https://api-2-eb.pxsol.io/api/";
2601        window.publicApiUrl = "https://gateway-prod.pxsol.com/";
2602        var loaderText = $("#loaderText").length ? $("#loaderText").text() + "..." : "Cargando...";
2603        var urlParams = new URLSearchParams(window.location.search);
2604        var EmailTk = Email ? Email : "NN";
2605        var userPar = urlParams.get("UserID") ? "&UserID=" + urlParams.get("UserID") : "";
2606        var userTkPar = urlParams.get("Token") ? "&Token=" + urlParams.get("Token") : "";
2607        var tagPar = urlParams.get("tag") ? urlParams.get("tag") : "NN";
2608        var ListIDPar = urlParams.get("ListID") ? "&ListID=" + urlParams.get("ListID") : "";
2609        var LocationPar = urlParams.get("Location") ? "&Location=" + encodeURIComponent(urlParams.get("Location")) : "";
2610        var cartOriginPar = urlParams.get("origin") ? "&origin=" + urlParams.get("origin") : "";
2611        var SearchIDPar = urlParams.get("SearchID") ? "&SearchID=" + urlParams.get("SearchID") : "";
2612        var StartPar = urlParams.get("Start") ? "&Start=" + urlParams.get("Start") : "";
2613        var EndPar = urlParams.get("End") ? "&End=" + urlParams.get("End") : "";
2614        var GroupsPar = urlParams.get("Groups") ? "&Groups=" + urlParams.get("Groups") : "";
2615        var ProductIDPar = urlParams.get("ProductID") ? "&ProductID=" + urlParams.get("ProductID") : "";
2616        const Pid = urlParams.get("Pid") ? "&Pid=" + urlParams.get("Pid") : "";
2617        tagPar = tagPar == "NN" ? urlParams.get("Tag") : tagPar;
2618        tagPar = tagPar ? tagPar : "";
2619        tagPar = tagPar ? "&tag=" + tagPar : '';
2620
2621        var urlCart = '/neo_marketplace_lp/cart_body.php?pos=' + Json_Pos.Pos + SearchIDPar + userPar + userTkPar + tagPar + LocationPar + ListIDPar + cartOriginPar + ProductIDPar + "&cur=" + CurrencyTk + "&lng=" + LngTk + "&Email=" + EmailTk + "&search=OK" + Pid;
2622
2623        if (!SearchIDPar) {
2624            var august_date = new Date('2020-08-01');
2625            var d = new Date();
2626            if (d.getTime() < august_date.getTime()) {
2627                d = august_date;
2628            }
2629            StartPar = !StartPar ? "&Start=" + d.toISOString().split('T')[0] : StartPar;
2630            d.setDate(d.getDate() + 1);
2631            EndPar = !EndPar ? "&End=" + d.toISOString().split('T')[0] : EndPar;
2632            ListIDPar = ListIDPar ? ListIDPar : (Json_Pos.DefaultListID ? "&ListID=" + Json_Pos.DefaultListID : "");
2633            var searchQueryString = '?pos=' + Json_Pos.Pos + StartPar + EndPar + GroupsPar + ListIDPar + LocationPar + tagPar + ProductIDPar + "&cur=" + CurrencyTk + "&lng=" + LngTk + "&Email=" + EmailTk + "&search=NoSearchID" + Pid;
2634            var urlCart = '/neo_marketplace_lp/cart_body.php' + searchQueryString;
2635
2636            history.pushState({
2637                    url: searchQueryString
2638                },
2639                $('title').text().trim(),
2640                searchQueryString
2641            );
2642        }
2643        console.log("URL CART INITIAL LOAD: " + urlCart);
2644        CargarModulo(urlCart, loaderText, ".container_cargarmodulo");
2645    }
2646</script>
2646
2647
2648<div class="neo_full container_cargarmodulo">
2649
2650</div>
2651
2652
2653    </div>
2654
2655    
2655<script>
2656        var deferSlickExecution = true;
2657        var deferFontAwesome = true;
2658        var deferMDI = true;
2659
2660        function runPostJquery() {
2661            var jsMain = document.createElement('script');
2662            jsMain.async = true;
2663            jsMain.src = "https://cdn.pxsol.com/neo_marketplace_lp/main_lp.js?filev=1778144039";
2664            document.body.appendChild(jsMain);
2665            var jsDevice = document.createElement('script');
2666            jsDevice.async = true;
2667            jsDevice.src = "https://cdn.pxsol.com/neo_marketplace_lp/computer.js";
2668            document.body.appendChild(jsDevice);
2669            if (typeof initializeMDI !== "undefined") {
2670                if (typeof initializeMDI === "function") {
2671                    initializeMDI();
2672                }
2673            }
2674            if (typeof FnLoadingOFF !== "undefined") {
2675                if (typeof FnLoadingOFF === "function") {
2676                    FnLoadingOFF();
2677                }
2678            }
2679        }
2680    </script>
2680
2681
2682        
2682<script src='/js_3.js?filev=1778144039&pos=Sanremohoteles&device=computer&posv=1788314973_2025-08-29T20:43:48.000Z' async defer onload="runPostJquery()"></script>
2682
2683
2684    <div class="main_wraper_footer" id="main_wraper_footer">
2685        
2686
2687<div class="neo_template_container neo_template_ computer">
2688
2689
2690<div  class="diplo_footercontainer full flex flex-wrap padding-20 padding-top-60 padding-bottom-40"  ><div  class="xs-100 lg-10"  ></div><div  class="xs-100 lg-50 flex flex-column"  > <img loading="lazy"  class="amerian_footerimg margin-top-10 margin-bottom-30"   lazy-src="https://files-p.pxsol.com/13307/company/library/user/5126930364ea8a9a40b647af1c2cabae351ef5e772.png" alt="San Remo Hoteles" > </image><div  class="fontsize-1em diplo_white padding-bottom-30"  ><div  class="diplo_footerinfo flex-column"  ><div  class="margin-bottom-30 txtUpper txtWhite amerian_heading"  >Reservation center</div><a  class="margin-bottom-5 flex flex-s-center txtUpper amerian_supramenu_item nopadding fontsize-20px"   href="tel:8102208880" target=""><div  class=""  >8102208880</div></a><a  class="margin-bottom-5 flex flex-s-center txtUpper amerian_supramenu_item nopadding fontsize-20px"   href="tel:5491127216237" target=""><div  class=""  >5491127216237</div></a></div></div></div><div  class="xs-100 lg-30"  ><div  class="diplo_social flex flex-m-end flex-s-center padding-bottom-20 padding-right-20"  ><div  class='' >
2691			  <div class="neo_template_block neo_neo_social_links pxModule_include ModuloWrap_neo_social_links" id="neo_social_links" >
2692			  
2693
2694<div class="neo_social_links_container social_block">
2695  <ul class="neo_links_list">
2696          <li class="facebook_page">
2697        <a href="https://www.facebook.com/sanremohoteles" target="_blank">
2698          <i class="mdi mdi-facebook"></i>
2699        </a>
2700      </li>
2701              <li class="instagram_page">
2702        <a href="https://www.instagram.com/hotelessanremo" target="_blank">
2703          <i class="mdi mdi-instagram"></i>
2704        </a>
2705      </li>
2706                              <li class="linkedin_page">
2707        <a href="https://www.linkedin.com/company/101938064" target="_blank">
2708          <i class="mdi mdi-linkedin"></i>
2709        </a>
2710      </li>
2711      </ul>
2712</div>
2713  <div class="wsp_floating">
2714    <a href="https://api.whatsapp.com/send?phone=5491127216237" target="_blank">
2715      <i class="mdi mdi-whatsapp"></i>
2716    </a>
2717  </div>
2718			  </div>
2719			  </div><ul  class='neo_links_list' ><li  class='youtube_page' ><a  class=""   target='_blank' href="https://www.youtube.com/@sanremohoteles5949" target=""><i  class='mdi mdi mdi-youtube' ></i></a></li></ul></div><div  class="diplo_social flex flex-m-end flex-s-center padding-bottom-20 padding-right-20"  ></div></div><div  class="xs-100 lg-10"  ></div></div> 
2720
2721</div>
2722
2723    </div>
2724
2725    <div class="main_wraper_footer" id="main_wraper_footer">
2726        <div id="footer_px" class="noprint footer_px_fix" style="max-width: 100vw; box-sizing: border-box; overflow-x: hidden;">
2727    <div class="wraper_footerpx flex-column flex-center">
2728
2729        
2730        <div class="texto_footerpx">
2731            By using this website, you agree to the use of cookies.            <a target="_blank" rel="nofollow" href="/cookiespolicies.php?pos=Sanremohoteles" class="footer_link">Cookies policy</a> | <a target="_blank" rel="nofollow" href="/terms.php?pos=Sanremohoteles" class="footer_link">Terminos y Condiciones</a> | <a target="_blank" rel="nofollow" href="/policies.php?pos=Sanremohoteles" class="footer_link">Privacy Policy</a> | <span>Cache:
2732                2026-10-05 06:35:37 | </span><a href="" rel="nofollow" target="_blank">Textos</a> | <span class="framework_f">Framework: 3 | <span class="framework_f">Tag: <span class="tag_cookie">..</span> </span> </span>
2733        </div>
2734
2735            <div class="pxsol_link_f">
2736        <a style="text-decoration: underline !important;" target="_blank" href="https://www.pxsol.com/pos_source=Sanremohoteles" class="footer_link">Sistema Hotelero</a>
2737        por        <a href="/" target="_blank" class="footer_link footer_logo_svg" title="Sistema Hotelero">
2738            <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2739<svg
2740   xmlns:dc="http://purl.org/dc/elements/1.1/"
2741   xmlns:cc="http://creativecommons.org/ns#"
2742   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
2743   xmlns:svg="http://www.w3.org/2000/svg"
2744   xmlns="http://www.w3.org/2000/svg"
2745   viewBox="0 0 752.42535 358.37866"
2746   height="50px"
2747   width="23px"
2748   xml:space="preserve"
2749   id="svg2"
2750   version="1.1"><metadata
2751     id="metadata8"><rdf:RDF><cc:Work
2752         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
2753           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
2754     id="defs6"><clipPath
2755       id="clipPath18"
2756       clipPathUnits="userSpaceOnUse"><path
2757         id="path16"
2758         d="M 0,268.784 H 564.319 V 0 H 0 Z" /></clipPath></defs><g
2759     transform="matrix(1.3333333,0,0,-1.3333333,0,358.37867)"
2760     id="g10"><g
2761       id="g12"><g
2762         clip-path="url(#clipPath18)"
2763         id="g14"><g
2764           transform="translate(23.3438,154.8828)"
2765           id="g20"><path
2766             id="path22"
2767             style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
2768             d="m 0,0 h 12.405 c 11.978,0 20.768,1.891 26.369,5.674 5.601,3.781 8.4,9.691 8.4,17.728 0,7.447 -2.507,13.002 -7.521,16.666 -5.013,3.664 -12.828,5.496 -23.44,5.496 H 0 Z m 71.202,24.112 c 0,-13.534 -4.884,-23.905 -14.65,-31.115 -9.768,-7.21 -23.67,-10.814 -41.707,-10.814 H 0 V -66.309 H -23.344 V 63.294 h 41.118 c 17.841,0 31.207,-3.311 40.097,-9.929 8.886,-6.619 13.331,-16.37 13.331,-29.253" /></g><g
2769           transform="translate(325.4336,123.7686)"
2770           id="g24"><path
2771             id="path26"
2772             style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
2773             d="m 0,0 c 0,-11.524 -4.59,-20.565 -13.773,-27.126 -9.179,-6.561 -21.846,-9.84 -37.994,-9.84 -16.149,0 -29.367,2.274 -39.653,6.824 v 20.036 c 6.509,-2.78 13.43,-4.964 20.754,-6.56 7.327,-1.596 14.147,-2.392 20.462,-2.392 9.246,0 16.068,1.594 20.462,4.785 4.396,3.191 6.595,7.475 6.595,12.853 0,4.847 -2.02,8.956 -6.057,12.323 -4.039,3.369 -12.372,7.358 -25.005,11.968 -13.023,4.787 -22.204,10.252 -27.542,16.4 -5.341,6.145 -8.01,13.532 -8.01,22.161 0,10.815 4.233,19.324 12.698,25.53 8.463,6.205 19.827,9.308 34.087,9.308 13.675,0 27.284,-2.719 40.829,-8.156 L -9.572,70.829 c -12.697,4.844 -24.028,7.269 -33.99,7.269 -7.555,0 -13.285,-1.494 -17.19,-4.477 -3.906,-2.985 -5.86,-6.93 -5.86,-11.834 0,-3.369 0.781,-6.25 2.344,-8.644 1.563,-2.393 4.134,-4.653 7.716,-6.782 3.58,-2.127 10.026,-4.935 19.339,-8.421 10.484,-3.959 18.166,-7.654 23.05,-11.08 4.884,-3.429 8.464,-7.3 10.745,-11.613 C -1.14,10.933 0,5.85 0,0" /></g><g
2774           transform="translate(355.8486,153.5532)"
2775           id="g28"><path
2776             id="path30"
2777             style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
2778             d="m 0,0 c 0,-15.956 3.547,-28.057 10.646,-36.299 7.097,-8.245 17.614,-12.369 31.548,-12.369 13.87,0 24.335,4.08 31.401,12.235 7.064,8.155 10.598,20.3 10.598,36.433 0,15.897 -3.501,27.953 -10.5,36.168 C 66.693,44.382 56.26,48.49 42.39,48.49 28.391,48.49 17.826,44.382 10.694,36.168 3.564,27.953 0,15.897 0,0 m 109.001,0 c 0,-21.097 -5.811,-37.498 -17.434,-49.198 C 79.946,-60.901 63.486,-66.75 42.194,-66.75 c -21.553,0 -38.108,5.806 -49.667,17.418 -11.558,11.613 -17.334,28.116 -17.334,49.509 0,21.394 5.81,37.823 17.433,49.288 11.622,11.464 28.21,17.198 49.764,17.198 21.227,0 37.635,-5.822 49.227,-17.464 C 103.205,37.556 109.001,21.156 109.001,0" /></g><g
2779           transform="translate(480.5166,88.5742)"
2780           id="g32"><path
2781             id="path34"
2782             style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
2783             d="M 0,0 V 129.603 H 23.342 V 18.175 H 83.803 V 0 Z" /></g><g
2784           transform="translate(95.5459,218.4404)"
2785           id="g36"><path
2786             id="path38"
2787             style="fill:#ee2735;fill-opacity:1;fill-rule:nonzero;stroke:none"
2788             d="m 0,0 42.967,-58.803 -68.085,-93.3 30.263,-9.074 56.235,77.141 87.961,-120.538 46.28,-13.866 L 79.583,-59.091 159.441,50.344 112.198,36.084 60.982,-33.892 29.65,9.046 Z" /></g></g></g></g></svg>        </a>
2789    </div>
2790
2791    </div>
2792</div>
2793
2794
2795<script>
2796    function loadPxChatWidget() {
2797        const posToGetApiKey = 'Sanremohoteles';
2798        const timestamp = '1788314973_2025-08-29T20:43:48.000Z';
2799        const urlToGetApiKey = `https://px-chat-widget-prod.pxsol.com/company-widget/apiKey/${posToGetApiKey}?v=${timestamp}`;
2800
2801        fetch(urlToGetApiKey, {
2802                method: 'GET',
2803                headers: {
2804                    'Content-Type': 'application/json',
2805                }
2806            })
2807            .then(response => {
2808                if (!response.ok) {
2809                    throw new Error(`HTTP error! status: ${response.status}`);
2810                }
2811                return response.json();
2812            })
2813            .then(data => {
2814                const internalKey = data?.data?.api_key;
2815                if (!internalKey) {
2816                    throw new Error('API key not found in response');
2817                }
2818
2819                const script = document.createElement('script');
2820                script.src = `https://cdn.pxsol.com/dist/chat-widget.min.js?k=${internalKey}&v=${timestamp}`;
2821                script.async = true;
2822                script.onload = () => {
2823                    console.log('PxChat widget loaded successfully');
2824                };
2825                script.onerror = (error) => {
2826                    console.error('Failed to load PxChat widget:', error);
2827                };
2828                document.body.appendChild(script);
2829            })
2830            .catch(error => {
2831                console.error('PxChat widget initialization failed:', error.message);
2832            });
2833    }
2834        document.addEventListener("DOMContentLoaded", loadPxChatWidget);
2835    </script>
2835
2836
2837<style>
2838    /* El botón flotante de WhatsApp (.wsp_floating) y el launcher del widget de chat (pxChat)
2839       se ubican ambos en la esquina inferior derecha y chocan entre sí. Cuando el chat está
2840       activo/montado, ocultamos el botón de WhatsApp para evitar la superposición. */
2841    body.px-chat-widget-active .wsp_floating { display: none !important; }
2842</style>
2843<script>
2844    (function () {
2845        // El widget pxChat monta su launcher/panel bajo #px-widget-root (clase .widget-launcher-button).
2846        function chatWidgetPresent() {
2847            return !!(
2848                document.getElementById('px-widget-root') ||
2849                document.querySelector('.widget-launcher-button, [class*="widget-launcher"]')
2850            );
2851        }
2852        var applied = false;
2853        var observer = null;
2854        var fallbackTimer = null;
2855        function apply() {
2856            if (applied || !document.body || !chatWidgetPresent()) return;
2857            document.body.classList.add('px-chat-widget-active');
2858            applied = true;
2859            if (observer) observer.disconnect();
2860            if (fallbackTimer) clearTimeout(fallbackTimer);
2861        }
2862        function start() {
2863            apply();
2864            if (applied || !('MutationObserver' in window) || !document.body) return;
2865            // El widget carga async (fetch de apiKey + inyección del 
2865<script>), así que observamos
2866            // el DOM hasta que aparezca. Si a los 20s no montó, dejamos de observar.
2867            observer = new MutationObserver(apply);
2868            observer.observe(document.body, { childList: true, subtree: true });
2869            fallbackTimer = setTimeout(function () {
2870                if (observer) observer.disconnect();
2871            }, 20000);
2872        }
2873        if (document.readyState === 'loading') {
2874            document.addEventListener('DOMContentLoaded', start);
2875        } else {
2876            start();
2877        }
2878    })();
2879</script>
2879
2880
2881<script src="https://cdn.pxsol.com/js/domready.js"></script>
vendor: 1 bytes, line 2881
2881
2882<script>
2883    var head = document.getElementsByTagName('head')[0];
2884
2885    function initializeFontAwesome() {
2886        var link = document.createElement('link');
2887        link.id = "fawesome";
2888        link.rel = 'stylesheet';
2889        link.type = 'text/css';
2890        link.href = 'https://use.fontawesome.com/releases/v5.7.1/css/all.css';
2891        head.appendChild(link);
2892    }
2893
2894    function initializeMDI() {
2895        var link = document.createElement('link');
2896        link.id = "fawesome";
2897        link.rel = 'stylesheet';
2898        link.type = 'text/css';
2899        link.href = '/css/materialdesignicons.min.css';
2900        head.appendChild(link);
2901    }
2902
2903    function initializeToast() {
2904        // check if jquery is loaded
2905        if (typeof jQuery == 'function') {
2906            let link = document.createElement('link');
2907            link.id = "jqToastCss";
2908            link.rel = 'stylesheet';
2909            link.type = 'text/css';
2910            link.href = '/includes/toast/jquery.toast.min.css';
2911            head.appendChild(link);
2912            let script = document.createElement('script');
2913            script.id = "jqToastJs";
2914            script.type = 'text/javascript';
2915            script.defer = true;
2916            script.async = true;
2917            script.src = '/includes/toast/jquery.toast.min.js';
2918            head.appendChild(script);
2919        } else {
2920            console.log('jQuery is not loaded. Toast could not be initialized.');
2921        }
2922    }
2923
2924    DomReady.ready(function() {
2925        var link = document.createElement('link');
2926        link.id = "siteCss";
2927        link.rel = 'stylesheet';
2928        link.type = 'text/css';
2929
2930                    link.href =
2931				'/css_3.css?pos=Sanremohoteles&device=computer&posv=1788314973_2025-08-29T20:43:48.000Z_2025-08-29T20:43:48.000Z&filev=1782498034';
2932                            head.appendChild(link);
2933
2934        if (typeof deferFontAwesome !== "undefined") {
2935            if (!deferFontAwesome) {
2936                initializeFontAwesome();
2937            }
2938        } else {
2939            initializeFontAwesome();
2940        }
2941
2942        if (typeof deferMDI !== "undefined") {
2943            if (!deferMDI) {
2944                initializeMDI();
2945            }
2946        } else {
2947            initializeMDI();
2948        }
2949
2950        if (typeof deferToast !== "undefined") {
2951            if (!deferToast) {
2952                initializeToast();
2953            }
2954        } else {
2955            initializeToast();
2956        }
2957
2958    });
2959</script>
vendor: 1 bytes, line 2959
2959
2960<script>
2961	(function(){
2962		console.log('%c[pxChat] 🚀 Chat tracking script loaded (Chromium optimized)', 'color: #4CAF50; font-weight: bold; font-size: 14px;');
2963		window.dataLayer = window.dataLayer || [];
2964		function generateId(prefix){
2965			const rnd = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
2966				(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
2967			);
2968			return (prefix || 'id_') + rnd;
2969		}
2970		function getPxContext(){
2971			try { return window.pxContext || {}; } catch(e){ return {}; }
2972		}
2973		function getSessionId(){
2974			const ctx = getPxContext();
2975			if (ctx.session_id) return ctx.session_id;
2976			let sid = sessionStorage.getItem('px_session_id');
2977			if (!sid){ sid = generateId('s_'); sessionStorage.setItem('px_session_id', sid); }
2978			return sid;
2979		}
2980		function getChatSessionId(){
2981			let cid = sessionStorage.getItem('px_chat_session_id');
2982			if (!cid){ cid = generateId('c_'); sessionStorage.setItem('px_chat_session_id', cid); }
2983			return cid;
2984		}
2985		function basePayload(){
2986			const ctx = getPxContext();
2987			const pos = (ctx.pos || 'Sanremohoteles') || null;
2988			const hotelId = (ctx.product_id || '24471') || null;
2989			return {
2990				session_id: getSessionId(),
2991				search_id: ctx.search_id || ctx.SearchID || null,
2992				pos: pos,
2993				post: pos,
2994				hotel_id: hotelId,
2995				page_url: location.href
2996			};
2997		}
2998		function pushEvent(name, extra){
2999			const params = Object.assign({}, basePayload(), extra || {});
3000			// Dedupe: evita repetidos en ±1s por sesión/mensaje
3001			try {
3002				const keyId = params.chat_session_id || params.chat_id || '';
3003				const key = name + '|' + String(keyId);
3004				const now = Date.now();
3005				window.__px_evt_dedupe = window.__px_evt_dedupe || {};
3006				const last = window.__px_evt_dedupe[key] || 0;
3007				if (now - last < 1000) { 
3008					return; 
3009				}
3010				window.__px_evt_dedupe[key] = now;
3011			} catch(_e){
3012				console.error('[pxChat] Error in pushEvent dedupe:', _e);
3013			}
3014			// Evitar duplicados: si pxTrack existe, delegamos y NO empujamos a dataLayer aquí
3015			if (typeof window.pxTrack === 'function') {
3016				try { 
3017					console.log('[pxChat] 📤 Calling pxTrack for', name, 'with params:', params);
3018					window.pxTrack(name, params); 
3019					console.log('%c[pxChat] ✅ ' + name + ' event dispatched successfully', 'color: #4CAF50; font-weight: bold;');
3020				} catch(e){
3021					console.error('[pxChat] ❌ Error calling window.pxTrack for', name, ':', e);
3022				}
3023				return;
3024			}
3025			const payload = Object.assign({ event: name }, params);
3026			console.log('[pxChat] 📤 Pushing to dataLayer:', payload);
3027			window.dataLayer.push(payload);
3028			console.log('%c[pxChat] ✅ ' + name + ' event pushed to dataLayer', 'color: #4CAF50; font-weight: bold;');
3029		}
3030		let chatOpenTsMs = null;
3031		let chatIsOpen = false;
3032		let lastOpenPushMs = 0;
3033		let closeGuardTimer = null;
3034		let lastStateChangeMs = 0;
3035		let lastCloseCallMs = 0; // Guard para prevenir múltiples llamadas rápidas a handleChatClose
3036		let lastOpenCallMs = 0; // Guard para prevenir múltiples llamadas rápidas a handleChatOpen
3037		let MIN_CHAT_DURATION_MS = 3000; // Mínimo 3 segundos entre chat_open y chat_close (previene falsos positivos en Chrome)
3038		let OPEN_DEBOUNCE_MS = 300; // Debounce para chat_open (300ms - solo para múltiples llamadas rápidas)
3039		let CLOSE_DEBOUNCE_MS = 400; // Debounce para chat_close (400ms - solo para múltiples llamadas rápidas)
3040		let pendingOpenTimer = null; // Timer para debounce de open
3041		let pendingCloseTimer = null; // Timer para debounce de close
3042		let openCallCount = 0; // Contador de llamadas rápidas a open
3043		let closeCallCount = 0; // Contador de llamadas rápidas a close
3044		function syncChatState(){
3045			try {
3046				// Guard mínimo: no sincronizar si acabamos de cambiar estado hace menos de 200ms
3047				const now = Date.now();
3048				if (now - lastStateChangeMs < 200) {
3049					console.log('[pxChat] syncChatState throttled (' + (now - lastStateChangeMs) + 'ms ago)');
3050					return;
3051				}
3052				// Priorizar estado del launcher: clase --open
3053				// Buscar el botón con múltiples selectores
3054				const btn = document.querySelector('.widget-launcher-button') || 
3055				            document.querySelector('[class*="widget-launcher"]') ||
3056				            document.querySelector('[class*="launcher-button"]');
3057				if (btn && btn.classList) {
3058					const isOpenBtn = btn.classList.contains('widget-launcher-button--open') || 
3059					                  btn.classList.toString().indexOf('--open') !== -1;
3060					console.log('[pxChat] syncChatState: button state check:', {
3061						isOpenBtn: isOpenBtn,
3062						chatIsOpen: chatIsOpen,
3063						buttonClasses: btn.className
3064					});
3065					if (isOpenBtn && !chatIsOpen) { 
3066						console.log('[pxChat] syncChatState: button is open but chatIsOpen is false, calling handleChatOpen');
3067						lastStateChangeMs = now; 
3068						return handleChatOpen(); 
3069					}
3070					if (!isOpenBtn && chatIsOpen) { 
3071						// Guard mínimo: no cerrar si el chat se abrió hace menos de 3 segundos
3072						const startMs = chatOpenTsMs || lastOpenPushMs || null;
3073						if (startMs && (now - startMs < 3000)) {
3074							console.log('[pxChat] syncChatState: chat_close blocked - chat opened too recently (' + Math.round((now - startMs) / 1000) + 's ago, minimum 3s)');
3075							return;
3076						}
3077						// CRÍTICO: Esperar y verificar nuevamente que el botón realmente está cerrado
3078						// Esto previene disparos prematuros cuando el DOM aún se está actualizando
3079						setTimeout(function() {
3080							const btnCheck = document.querySelector('.widget-launcher-button') || 
3081							                  document.querySelector('[class*="widget-launcher"]') ||
3082							                  document.querySelector('[class*="launcher-button"]');
3083							const isStillOpen = btnCheck && btnCheck.classList && (
3084								btnCheck.classList.contains('widget-launcher-button--open') || 
3085								btnCheck.classList.toString().indexOf('--open') !== -1
3086							);
3087							const ifrCheck = findChatIframe();
3088							const isIframeVisible = isElementVisible(ifrCheck);
3089							
3090							console.log('[pxChat] syncChatState: delayed verification after button close:', {
3091								isStillOpen: isStillOpen,
3092								isIframeVisible: isIframeVisible,
3093								chatIsOpen: chatIsOpen
3094							});
3095							
3096							if (isStillOpen) {
3097								console.log('[pxChat] syncChatState: chat_close CANCELLED - button still shows as open after delay');
3098								return;
3099							}
3100							if (isIframeVisible) {
3101								console.log('[pxChat] syncChatState: chat_close CANCELLED - iframe still visible after delay');
3102								return;
3103							}
3104							if (!chatIsOpen) {
3105								console.log('[pxChat] syncChatState: chat_close CANCELLED - chatIsOpen is already false');
3106								return;
3107							}
3108							
3109							console.log('[pxChat] syncChatState: button confirmed closed after delay, calling handleChatClose');
3110							lastStateChangeMs = Date.now();
3111							handleChatClose();
3112						}, 600); // Esperar 600ms para confirmar que el DOM se actualizó completamente
3113						return;
3114					}
3115				}
3116				// CRÍTICO: NO usar iframe como fallback si hay un botón disponible
3117				// El iframe puede no ser visible incluso cuando el chat está abierto
3118				// Solo confiar en el botón launcher para determinar el estado
3119				if (!btn) {
3120					// Solo si NO hay botón, intentar buscar el botón de nuevo con un selector más amplio
3121					const btnRetry = document.querySelector('.widget-launcher-button, [class*="widget-launcher"], [class*="launcher-button"]');
3122					if (btnRetry && btnRetry.classList) {
3123						const isOpenBtnRetry = btnRetry.classList.contains('widget-launcher-button--open') || 
3124						                       btnRetry.classList.toString().indexOf('--open') !== -1;
3125						console.log('[pxChat] syncChatState: retry button found:', {
3126							isOpenBtnRetry: isOpenBtnRetry,
3127							chatIsOpen: chatIsOpen,
3128							buttonClasses: btnRetry.className
3129						});
3130						if (isOpenBtnRetry && !chatIsOpen) { 
3131							console.log('[pxChat] syncChatState: retry button is open but chatIsOpen is false, calling handleChatOpen');
3132							lastStateChangeMs = now; 
3133							return handleChatOpen(); 
3134						}
3135						if (!isOpenBtnRetry && chatIsOpen) { 
3136							// Guard mínimo: no cerrar si el chat se abrió hace menos de 3 segundos
3137							const startMs = chatOpenTsMs || lastOpenPushMs || null;
3138							if (startMs && (now - startMs < 3000)) {
3139								console.log('[pxChat] syncChatState: chat_close blocked - chat opened too recently (' + Math.round((now - startMs) / 1000) + 's ago, minimum 3s)');
3140								return;
3141							}
3142							// Verificar nuevamente antes de cerrar
3143							setTimeout(function() {
3144								const btnCheck = document.querySelector('.widget-launcher-button, [class*="widget-launcher"], [class*="launcher-button"]');
3145								const isStillOpen = btnCheck && (btnCheck.classList.contains('widget-launcher-button--open') || 
3146								                                 btnCheck.classList.toString().indexOf('--open') !== -1);
3147								
3148								if (isStillOpen || !chatIsOpen) {
3149									console.log('[pxChat] syncChatState: chat_close CANCELLED - button still open or chatIsOpen is false');
3150									return;
3151								}
3152								
3153								console.log('[pxChat] syncChatState: retry button confirmed closed after delay, calling handleChatClose');
3154								lastStateChangeMs = Date.now();
3155								handleChatClose();
3156							}, 600);
3157							return;
3158						}
3159					}
3160					
3161					// ÚLTIMO RECURSO: Solo usar iframe si NO hay botón Y el chat está abierto
3162					// Y solo para abrir, NUNCA para cerrar (el iframe no es confiable para cerrar)
3163					const ifr = findChatIframe();
3164					const visible = isElementVisible(ifr);
3165					console.log('[pxChat] syncChatState: iframe LAST RESORT check (only for opening):', {
3166						visible: visible,
3167						chatIsOpen: chatIsOpen,
3168						hasIframe: !!ifr
3169					});
3170					// Solo usar iframe para detectar apertura, NUNCA para cerrar
3171					if (visible && !chatIsOpen) { 
3172						console.log('[pxChat] syncChatState: iframe visible but chatIsOpen is false, calling handleChatOpen');
3173						lastStateChangeMs = now; 
3174						return handleChatOpen(); 
3175					}
3176					// NO usar iframe para cerrar - solo confiar en el botón
3177					if (!visible && chatIsOpen) { 
3178						console.log('[pxChat] syncChatState: iframe not visible but chatIsOpen is true - IGNORING (iframe not reliable for closing)');
3179						return; // NO hacer nada - solo el botón puede cerrar
3180					}
3181				}
3182			} catch(_e){
3183				console.error('[pxChat] syncChatState error:', _e);
3184			}
3185		}
3186		function handleChatOpen(meta){
3187			console.log('[pxChat] handleChatOpen called:', { chatIsOpen: chatIsOpen, meta: meta });
3188			
3189			// Si ya está abierto, no hacer nada
3190			if (chatIsOpen) {
3191				console.log('[pxChat] handleChatOpen skipped - chat already open');
3192				return;
3193			}
3194			
3195			const now = Date.now();
3196			
3197			// Guard mínimo: no abrir si se cerró hace menos de 500ms (solo para prevenir toggle extremo)
3198			if (lastCloseCallMs && (now - lastCloseCallMs < 500)) {
3199				console.log('[pxChat] handleChatOpen blocked - chat closed too recently (' + (now - lastCloseCallMs) + 'ms ago)');
3200				return;
3201			}
3202			
3203			// Throttling mínimo: solo prevenir llamadas duplicadas en menos de 200ms
3204			if (now - lastOpenCallMs < 200) {
3205				console.log('[pxChat] handleChatOpen throttled (too soon)');
3206				return;
3207			}
3208			
3209			lastOpenCallMs = now;
3210			
3211			// Cancelar cualquier close pendiente
3212			if (pendingCloseTimer) {
3213				clearTimeout(pendingCloseTimer);
3214				pendingCloseTimer = null;
3215				console.log('[pxChat] handleChatOpen: cancelled pending close');
3216			}
3217			
3218			// Disparar inmediatamente (sin debounce para Chromium)
3219			console.log('[pxChat] handleChatOpen: calling doChatOpen immediately');
3220			doChatOpen(meta);
3221		}
3222		
3223		function doChatOpen(meta) {
3224			// Verificar estado del botón (prioridad máxima para Chromium)
3225			// Buscar el botón con múltiples selectores
3226			const btn = document.querySelector('.widget-launcher-button') || 
3227			            document.querySelector('[class*="widget-launcher"]') ||
3228			            document.querySelector('[class*="launcher-button"]');
3229			const isActuallyOpen = btn && btn.classList && (
3230				btn.classList.contains('widget-launcher-button--open') || 
3231				btn.classList.toString().indexOf('--open') !== -1
3232			);
3233			const ifr = findChatIframe();
3234			const isIframeVisible = isElementVisible(ifr);
3235			
3236			// Log detallado para debugging en Chromium
3237			console.log('[pxChat] doChatOpen called:', {
3238				isActuallyOpen: isActuallyOpen,
3239				isIframeVisible: isIframeVisible,
3240				chatIsOpen: chatIsOpen,
3241				hasButton: !!btn,
3242				buttonClasses: btn ? btn.className : 'no button'
3243			});
3244			
3245			// Si el botón existe y dice que está abierto, proceder
3246			// Si no hay botón pero el iframe es visible, también proceder
3247			if (!isActuallyOpen && !isIframeVisible && btn) {
3248				console.warn('[pxChat] doChatOpen: button exists but not showing as open, proceeding anyway');
3249			}
3250			
3251			// Guardar timestamp ANTES de cambiar el estado (importante para Chrome)
3252			const now = Date.now();
3253			chatOpenTsMs = now;
3254			lastOpenPushMs = now;
3255			chatIsOpen = true;
3256			const chat_session_id = (meta && (meta.chat_session_id || meta.session_id)) || getChatSessionId();
3257			
3258			// Log para debugging
3259			console.log('[pxChat] 🚀 FIRING chat_open event:', {
3260				chatOpenTsMs: chatOpenTsMs,
3261				lastOpenPushMs: lastOpenPushMs,
3262				timestamp: now,
3263				chat_session_id: chat_session_id
3264			});
3265			pushEvent('chat_open', { chat_session_id });
3266		}
3267		function handleChatMessage(meta){
3268			const chat_id = meta && (meta.chat_id || meta.message_id) || null;
3269			const message_type = meta && (meta.message_type || meta.type) || null;
3270			const payload = { chat_id, message_type };
3271			pushEvent('chat_message', payload);
3272		}
3273		function handleChatClose(meta){
3274			console.log('[pxChat] handleChatClose called:', { chatIsOpen: chatIsOpen, meta: meta });
3275			
3276			// Si ya está cerrado, no hacer nada
3277			if (!chatIsOpen) {
3278				console.log('[pxChat] handleChatClose skipped - chat already closed');
3279				return;
3280			}
3281			
3282			const now = Date.now();
3283			
3284			// Guard crítico: prevenir que chat_close se dispare inmediatamente después de chat_open
3285			// Aumentado a 3 segundos para evitar disparos prematuros
3286			const startMs = chatOpenTsMs || lastOpenPushMs || null;
3287			if (startMs && (now - startMs < 3000)) {
3288				console.log('[pxChat] handleChatClose blocked - chat opened too recently (' + Math.round((now - startMs) / 1000) + 's ago, minimum 3s required)');
3289				return;
3290			}
3291			
3292			// Verificación adicional: asegurarse de que el botón realmente está cerrado
3293			// Buscar el botón con múltiples selectores
3294			const btn = document.querySelector('.widget-launcher-button') || 
3295			            document.querySelector('[class*="widget-launcher"]') ||
3296			            document.querySelector('[class*="launcher-button"]');
3297			const isActuallyOpen = btn && btn.classList && (
3298				btn.classList.contains('widget-launcher-button--open') || 
3299				btn.classList.toString().indexOf('--open') !== -1
3300			);
3301			if (isActuallyOpen) {
3302				console.log('[pxChat] handleChatClose blocked - button still shows as open');
3303				return;
3304			}
3305			
3306			// Throttling mínimo: solo prevenir llamadas duplicadas en menos de 200ms
3307			if (now - lastCloseCallMs < 200) {
3308				console.log('[pxChat] handleChatClose throttled (too soon)');
3309				return;
3310			}
3311			
3312			lastCloseCallMs = now;
3313			
3314			// Cancelar cualquier open pendiente
3315			if (pendingOpenTimer) {
3316				clearTimeout(pendingOpenTimer);
3317				pendingOpenTimer = null;
3318				console.log('[pxChat] handleChatClose: cancelled pending open');
3319			}
3320			
3321			// Disparar inmediatamente (sin debounce para Chromium)
3322			console.log('[pxChat] handleChatClose: calling doChatClose immediately');
3323			doChatClose(meta, startMs);
3324		}
3325		
3326		function doChatClose(meta, startMs) {
3327			// Verificar estado del botón (prioridad máxima para Chromium)
3328			// Buscar el botón con múltiples selectores
3329			const btn = document.querySelector('.widget-launcher-button') || 
3330			            document.querySelector('[class*="widget-launcher"]') ||
3331			            document.querySelector('[class*="launcher-button"]');
3332			const isActuallyOpen = btn && btn.classList && (
3333				btn.classList.contains('widget-launcher-button--open') || 
3334				btn.classList.toString().indexOf('--open') !== -1
3335			);
3336			const ifr = findChatIframe();
3337			const isIframeVisible = isElementVisible(ifr);
3338			
3339			// Log detallado para debugging en Chromium
3340			console.log('[pxChat] doChatClose called:', {
3341				isActuallyOpen: isActuallyOpen,
3342				isIframeVisible: isIframeVisible,
3343				chatIsOpen: chatIsOpen,
3344				hasButton: !!btn,
3345				buttonClasses: btn ? btn.className : 'no button',
3346				startMs: startMs
3347			});
3348			
3349			// CRÍTICO: Solo proceder si el chat realmente está cerrado
3350			// Si hay un botón, confiar solo en el botón (no en el iframe)
3351			if (btn) {
3352				if (isActuallyOpen) {
3353					console.warn('[pxChat] doChatClose BLOCKED - button still shows as open');
3354					return;
3355				}
3356				// Si hay botón y no está abierto, proceder (ignorar iframe)
3357				console.log('[pxChat] doChatClose: button exists and is closed, proceeding');
3358			} else {
3359				// Solo si NO hay botón, verificar iframe
3360				if (isIframeVisible) {
3361					console.warn('[pxChat] doChatClose BLOCKED - iframe still visible (no button found)');
3362					return;
3363				}
3364				console.log('[pxChat] doChatClose: no button found, iframe not visible, proceeding');
3365			}
3366			
3367			// Guardar timestamp ANTES de cambiar el estado (importante para Chrome)
3368			const now = Date.now();
3369			const endMs = now;
3370			// Calcular duración antes de resetear variables
3371			const duration_sec = startMs ? Math.max(0, Math.round((endMs - startMs) / 1000)) : null;
3372			// Ahora sí cambiar el estado
3373			chatIsOpen = false;
3374			const chat_session_id = (meta && (meta.chat_session_id || meta.session_id)) || getChatSessionId();
3375			
3376			// Log para debugging
3377			console.log('[pxChat] 🚀 FIRING chat_close event:', {
3378				startMs: startMs,
3379				endMs: endMs,
3380				duration_sec: duration_sec,
3381				chatOpenTsMs: chatOpenTsMs,
3382				lastOpenPushMs: lastOpenPushMs,
3383				calculated: endMs - startMs + 'ms',
3384				chat_session_id: chat_session_id
3385			});
3386			pushEvent('chat_close', { chat_session_id, duration_sec });
3387			// Resetear timestamp inmediatamente (ya calculamos la duración)
3388			chatOpenTsMs = null;
3389			lastOpenPushMs = 0;
3390		}
3391		function isElementVisible(el){
3392			if (!el) return false;
3393			const cs = window.getComputedStyle ? window.getComputedStyle(el) : null;
3394			if (!cs || cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false;
3395			const r = el.getBoundingClientRect();
3396			return !!(r.width && r.height);
3397		}
3398		function findChatIframe(){
3399			try {
3400				const ifrs = document.querySelectorAll('iframe');
3401				for (let i = 0; i < ifrs.length; i++){
3402					const n = ifrs[i];
3403					if (isChatIframe(n)) return n;
3404				}
3405			} catch(_e){}
3406			return null;
3407		}
3408		// Detect launcher button presence (instala watcher; no toggle directo)
3409		document.addEventListener('click', function(ev){
3410			try {
3411				// Detectar click en el botón de cerrar del header
3412				let closeBtn = null;
3413				if (ev.composedPath) {
3414					const path = ev.composedPath();
3415					for (let i = 0; i < path.length; i++) {
3416						const n = path[i];
3417						if (n && n.classList && n.classList.contains && n.classList.contains('widget-header-close')) { 
3418							closeBtn = n; 
3419							break; 
3420						}
3421					}
3422				}
3423				if (!closeBtn && ev.target && ev.target.closest) {
3424					closeBtn = ev.target.closest('.widget-header-close');
3425				}
3426				
3427				// Si se hizo click en el botón de cerrar, esperar a que el DOM se actualice
3428				if (closeBtn && chatIsOpen) {
3429					console.log('[pxChat] Close button clicked, waiting for DOM update...');
3430					// Esperar más tiempo para que el widget actualice el estado del botón
3431					setTimeout(function() {
3432						const launcherBtn = document.querySelector('.widget-launcher-button');
3433						const isStillOpen = launcherBtn && launcherBtn.classList && launcherBtn.classList.contains('widget-launcher-button--open');
3434						console.log('[pxChat] After close button click, launcher state:', {
3435							isStillOpen: isStillOpen,
3436							chatIsOpen: chatIsOpen
3437						});
3438						if (!isStillOpen && chatIsOpen) {
3439							console.log('[pxChat] Close button clicked and launcher is now closed, calling handleChatClose');
3440							handleChatClose();
3441						}
3442					}, 500); // Esperar 500ms para que el DOM se actualice
3443					return;
3444				}
3445				
3446				// Detectar click en el botón launcher
3447				let btn = null;
3448				if (ev.composedPath) {
3449					const path = ev.composedPath();
3450					for (let i = 0; i < path.length; i++) {
3451						const n = path[i];
3452						if (n && n.classList && n.classList.contains && n.classList.contains('widget-launcher-button')) { btn = n; break; }
3453					}
3454				}
3455				if (!btn && ev.target && ev.target.closest) {
3456					btn = ev.target.closest('.widget-launcher-button');
3457				}
3458				if (btn) { 
3459					installButtonListener(btn); 
3460					// Si el botón tiene --open, está abierto; si no, está cerrado
3461					const isOpen = btn.classList.contains('widget-launcher-button--open');
3462					if (isOpen && !chatIsOpen) {
3463						// Chat se está abriendo
3464						console.log('[pxChat] Launcher button clicked (opening), calling handleChatOpen');
3465						setTimeout(function() { handleChatOpen(); }, 300);
3466					} else if (!isOpen && chatIsOpen) {
3467						// Chat se está cerrando
3468						console.log('[pxChat] Launcher button clicked (closing), waiting for DOM update...');
3469						setTimeout(function() {
3470							const launcherBtn = document.querySelector('.widget-launcher-button');
3471							const isStillOpen = launcherBtn && launcherBtn.classList && launcherBtn.classList.contains('widget-launcher-button--open');
3472							if (!isStillOpen && chatIsOpen) {
3473								console.log('[pxChat] Launcher button clicked and confirmed closed, calling handleChatClose');
3474								handleChatClose();
3475							}
3476						}, 500); // Esperar 500ms para confirmar que está cerrado
3477					}
3478				}
3479			} catch(_e){
3480				console.error('[pxChat] Error in click listener:', _e);
3481			}
3482		}, true);
3483		// Keyboard open on launcher
3484		document.addEventListener('keydown', function(ev){
3485			try {
3486				if (ev.key !== 'Enter' && ev.key !== ' ') return;
3487				let btn = null;
3488				if (ev.composedPath) {
3489					const path = ev.composedPath();
3490					for (let i = 0; i < path.length; i++) {
3491						const n = path[i];
3492						if (n && n.classList && n.classList.contains && n.classList.contains('widget-launcher-button')) { btn = n; break; }
3493					}
3494				}
3495				if (!btn && ev.target && ev.target.closest) {
3496					btn = ev.target.closest('.widget-launcher-button');
3497				}
3498				if (btn) { 
3499					installButtonListener(btn); 
3500					// Delay mínimo para dar tiempo al widget a actualizar la clase (Chromium)
3501					setTimeout(syncChatState, 200);
3502				}
3503			} catch(_e){}
3504		}, true);
3505		// Bridge postMessage events from the widget iframe
3506		window.addEventListener('message', function(ev){
3507			const d = ev.data;
3508			try {
3509				if (!d) return;
3510				if (typeof d === 'object') {
3511					const type = d.type || d.event || d.name || d.px_chat_event;
3512					if (!type) return;
3513					const payload = d.payload || d.meta || d;
3514					switch (String(type)) {
3515						case 'chat_opened':
3516						case 'widget_opened':
3517						case 'open':
3518							return handleChatOpen(payload);
3519						case 'message_sent':
3520						case 'chat_message':
3521						case 'message':
3522						case 'message_received':
3523						case 'new_message':
3524							// Extraer message_id del payload
3525							const messageId = payload && (payload.message_id || payload.messageId || payload.id || payload.chat_id);
3526							const messageType = payload && (payload.type || payload.message_type || 'text');
3527							const from = payload && (payload.from || payload.user_id || payload.userId);
3528							if (messageId) {
3529								detectChatMessage(messageId, messageType, from);
3530							} else {
3531								return handleChatMessage(payload);
3532							}
3533							return;
3534						case 'chat_closed':
3535						case 'widget_closed':
3536						case 'close':
3537							return handleChatClose(payload);
3538					}
3539					// También buscar en propiedades del objeto directamente
3540					if (d.message_id || d.messageId || d.id) {
3541						const messageId = d.message_id || d.messageId || d.id;
3542						const messageType = d.type || d.message_type || 'text';
3543						const from = d.from || d.user_id || d.userId;
3544						detectChatMessage(messageId, messageType, from);
3545					}
3546				}
3547				if (typeof d === 'string' && d.indexOf('px-chat:') === 0) {
3548					const parts = d.split(':');
3549					const type = parts[1];
3550					let payload = null;
3551					if (parts[2]) { try { payload = JSON.parse(parts.slice(2).join(':')); } catch(_e){} }
3552					if (type === 'open') return handleChatOpen(payload);
3553					if (type === 'message') {
3554						if (payload && (payload.message_id || payload.messageId || payload.id)) {
3555							detectChatMessage(payload.message_id || payload.messageId || payload.id, payload.type || 'text', payload.from);
3556						} else {
3557							return handleChatMessage(payload);
3558						}
3559						return;
3560					}
3561					if (type === 'close') return handleChatClose(payload);
3562				}
3563			} catch(_e){}
3564		});
3565		// Message detection: interceptar mensajes del widget
3566		let lastMessageIds = new Set(); // Track mensajes ya procesados para evitar duplicados
3567		let lastMessageTime = 0;
3568		const MESSAGE_THROTTLE_MS = 500; // Máximo 1 mensaje cada 500ms
3569		
3570		function detectChatMessage(messageId, messageType, from) {
3571			if (!messageId) {
3572				console.debug('[pxChat] detectChatMessage: no messageId provided');
3573				return;
3574			}
3575			
3576			// Evitar duplicados usando messageId (verificar primero)
3577			if (messageId && lastMessageIds.has(messageId)) {
3578				console.debug('[pxChat] detectChatMessage: message already processed', messageId);
3579				return;
3580			}
3581			
3582			// Throttling: evitar procesar mensajes muy rápidos
3583			const now = Date.now();
3584			const timeSinceLast = now - lastMessageTime;
3585			if (timeSinceLast < MESSAGE_THROTTLE_MS) {
3586				console.debug('[pxChat] detectChatMessage: throttled', timeSinceLast, 'ms since last');
3587				return;
3588			}
3589			
3590			// Permitir trackear mensajes incluso si el chat no está visualmente abierto
3591			// Los mensajes pueden llegar cuando el chat está minimizado pero activo
3592			// En Chromium, el chat puede estar activo sin que chatIsOpen sea true
3593			const hasChatSession = getChatSessionId() !== null;
3594			const shouldProcess = hasChatSession || chatIsOpen;
3595			
3596			if (!shouldProcess) {
3597				// Si no hay sesión y el chat no está abierto, intentar una vez más después de un delay
3598				console.debug('[pxChat] detectChatMessage: no session/open, will retry', { hasChatSession, chatIsOpen });
3599				setTimeout(function() {
3600					const retryHasSession = getChatSessionId() !== null;
3601					const retryIsOpen = chatIsOpen;
3602					if ((retryHasSession || retryIsOpen) && messageId && !lastMessageIds.has(messageId)) {
3603						console.debug('[pxChat] detectChatMessage: retry after delay', { retryHasSession, retryIsOpen });
3604						detectChatMessage(messageId, messageType, from);
3605					} else {
3606						console.debug('[pxChat] detectChatMessage: retry failed - conditions not met');
3607					}
3608				}, 1500); // Aumentado a 1.5s para dar más tiempo en Chromium
3609				return;
3610			}
3611			
3612			// Limpiar Set si tiene más de 100 mensajes (evitar memory leak)
3613			if (lastMessageIds.size > 100) {
3614				lastMessageIds.clear();
3615			}
3616			
3617			if (messageId) {
3618				lastMessageIds.add(messageId);
3619			}
3620			lastMessageTime = now;
3621			
3622			// Disparar evento
3623			try {
3624				const payload = { 
3625					chat_id: messageId, 
3626					message_type: messageType || 'text', 
3627					from: from 
3628				};
3629				console.debug('[pxChat] detectChatMessage: processing message', payload);
3630				handleChatMessage(payload);
3631			} catch(e) {
3632				console.error('[pxChat] ❌ Error processing message:', e);
3633			}
3634		}
3635		
3636		// Listener para eventos personalizados que el widget podría disparar
3637		document.addEventListener('px-chat-message', function(ev) {
3638			try {
3639				const data = ev.detail || ev.data || {};
3640				detectChatMessage(data.message_id || data.messageId || data.id, data.type || data.message_type, data.from);
3641			} catch(_e){}
3642		});
3643		
3644		// Interceptar logs de console.log para detectar mensajes de Pusher
3645		// El widget loguea mensajes con formato específico que podemos interceptar
3646		try {
3647			const originalConsoleLog = console.log;
3648			let isProcessing = false; // Flag para evitar recursión
3649			let processedMessageIds = new Set(); // Track mensajes ya procesados por el interceptor
3650			
3651			console.log = function(...args) {
3652				// Llamar al console.log original primero (siempre)
3653				originalConsoleLog.apply(console, args);
3654				
3655				// Evitar procesamiento recursivo
3656				if (isProcessing) return;
3657				
3658				try {
3659					isProcessing = true;
3660					
3661					let detectedMessageId = null;
3662					let detectedMessageType = 'text';
3663					let detectedFrom = null;
3664					
3665					// Buscar message_id en los argumentos directamente (mejorado para Chromium)
3666					for (let i = 0; i < args.length; i++) {
3667						const arg = args[i];
3668						if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {
3669							// Buscar directamente en el objeto
3670							if (arg.message_id || arg.messageId || arg.id) {
3671								detectedMessageId = arg.message_id || arg.messageId || arg.id;
3672								detectedMessageType = arg.type || arg.message_type || 'text';
3673								detectedFrom = arg.from || arg.user_id || arg.userId || null;
3674								break;
3675							}
3676							// Buscar en rawData (estructura anidada común en Pusher)
3677							if (arg.rawData && typeof arg.rawData === 'object') {
3678								if (arg.rawData.message_id || arg.rawData.messageId || arg.rawData.id) {
3679									detectedMessageId = arg.rawData.message_id || arg.rawData.messageId || arg.rawData.id;
3680									detectedMessageType = arg.rawData.type || arg.rawData.message_type || 'text';
3681									detectedFrom = arg.rawData.from || arg.rawData.user_id || arg.rawData.userId || null;
3682									break;
3683								}
3684							}
3685							// Buscar en propiedades anidadas (para Chromium que puede tener estructura diferente)
3686							try {
3687								for (let key in arg) {
3688									if (arg.hasOwnProperty(key) && typeof arg[key] === 'object' && arg[key] !== null) {
3689										if (arg[key].message_id || arg[key].messageId || arg[key].id) {
3690											detectedMessageId = arg[key].message_id || arg[key].messageId || arg[key].id;
3691											detectedMessageType = arg[key].type || arg[key].message_type || 'text';
3692											detectedFrom = arg[key].from || arg[key].user_id || arg[key].userId || null;
3693											break;
3694										}
3695									}
3696								}
3697								if (detectedMessageId) break;
3698							} catch(_e) {}
3699						}
3700					}
3701					
3702					// Si no encontramos en objetos, buscar en strings (solo si contiene patrones de Pusher)
3703					if (!detectedMessageId) {
3704						const logStr = args.map(arg => {
3705							if (typeof arg === 'string') return arg;
3706							if (typeof arg === 'object' && arg !== null) {
3707								try { return JSON.stringify(arg); } catch(_e) { return String(arg); }
3708							}
3709							return String(arg);
3710						}).join(' ');
3711						
3712						// Solo procesar si contiene patrones específicos de Pusher
3713						if ((logStr.indexOf('[Pusher] 📨 MENSAJE RECIBIDO') !== -1 || 
3714						     logStr.indexOf('[Pusher] 📨 Mensaje recibido') !== -1 ||
3715						     logStr.indexOf('[Pusher] Body completo:') !== -1 ||
3716						     logStr.indexOf('[Pusher]') !== -1) &&
3717						     (logStr.indexOf('message_id') !== -1 || logStr.indexOf('messageId') !== -1)) {
3718							
3719							// Intentar extraer message_id del string (múltiples formatos)
3720							try {
3721								// Formato: "message_id": "xxx"
3722								let messageIdMatch = logStr.match(/["']message_id["']\s*:\s
3722*["']([^"']+)["']/);
3723								if (!messageIdMatch) {
3724									// Formato: messageId: "xxx"
3725									messageIdMatch = logStr.match(/["']messageId["']\s*:\s*["']([^"']+)["']/);
3726								}
3727								if (!messageIdMatch) {
3728									// Formato: message_id: xxx (sin comillas)
3729									messageIdMatch = logStr.match(/message_id["']?\s*:\s*["']?([a-zA-Z0-9\-_]+)/);
3730								}
3731								if (messageIdMatch && messageIdMatch[1]) {
3732									detectedMessageId = messageIdMatch[1];
3733									console.debug('[pxChat] Detected message_id from log string:', detectedMessageId);
3734								}
3735							} catch(_e) {}
3736						}
3737					}
3738					
3739					// Procesar solo si encontramos un messageId y no lo hemos procesado antes
3740					if (detectedMessageId && !processedMessageIds.has(detectedMessageId)) {
3741						processedMessageIds.add(detectedMessageId);
3742						// Limpiar Set si tiene más de 50 mensajes
3743						if (processedMessageIds.size > 50) {
3744							processedMessageIds.clear();
3745						}
3746						
3747						console.debug('[pxChat] Detected message from Pusher log:', {
3748							messageId: detectedMessageId,
3749							messageType: detectedMessageType,
3750							from: detectedFrom
3751						});
3752						
3753						setTimeout(function() {
3754							detectChatMessage(detectedMessageId, detectedMessageType, detectedFrom);
3755						}, 300); // Aumentado a 300ms para dar tiempo a que se establezca la sesión
3756					}
3757				} catch(e) {
3758					// Silenciar errores en el interceptor para no romper otros logs
3759					console.debug('[pxChat] Error in console.log interceptor:', e);
3760				} finally {
3761					isProcessing = false;
3762				}
3763			};
3764			
3765			originalConsoleLog('[pxChat] Console.log interceptor installed for Pusher message detection');
3766		} catch(e) {
3767			console.error('[pxChat] Error setting up console.log interceptor:', e);
3768		}
3769		
3770		// MutationObserver para detectar mensajes en el DOM del chat (dentro del iframe)
3771		// Nota: Esto solo funciona si tenemos acceso al contenido del iframe (mismo origen)
3772		try {
3773			const chatMessageObserver = new MutationObserver(function(mutations) {
3774				if (!chatIsOpen) return;
3775				
3776				for (let i = 0; i < mutations.length; i++) {
3777					const mutation = mutations[i];
3778					if (mutation.addedNodes && mutation.addedNodes.length > 0) {
3779						for (let j = 0; j < mutation.addedNodes.length; j++) {
3780							const node = mutation.addedNodes[j];
3781							if (node.nodeType === 1) { // Element node
3782								// Buscar elementos que parezcan mensajes de chat
3783								// Selectores comunes: .message, .chat-message, [data-message-id], etc.
3784								const messageEl = node.classList && (
3785									node.classList.contains('message') ||
3786									node.classList.contains('chat-message') ||
3787									node.classList.contains('msg') ||
3788									node.getAttribute('data-message-id')
3789								) ? node : (node.querySelector && (
3790									node.querySelector('.message') ||
3791									node.querySelector('.chat-message') ||
3792									node.querySelector('[data-message-id]')
3793								));
3794								
3795								if (messageEl) {
3796									const messageId = messageEl.getAttribute('data-message-id') || 
3797									                  messageEl.getAttribute('data-id') ||
3798									                  messageEl.id ||
3799									                  null;
3800									const messageType = messageEl.getAttribute('data-type') || 
3801									                    messageEl.getAttribute('data-message-type') ||
3802									                    'text';
3803									const from = messageEl.getAttribute('data-from') || null;
3804									
3805									if (messageId) {
3806										detectChatMessage(messageId, messageType, from);
3807									}
3808								}
3809							}
3810						}
3811					}
3812				}
3813			});
3814			
3815			// Observar el iframe del chat cuando esté disponible
3816			function observeChatIframe() {
3817				const ifr = findChatIframe();
3818				if (ifr && ifr.contentDocument && ifr.contentDocument.body) {
3819					try {
3820						chatMessageObserver.observe(ifr.contentDocument.body, {
3821							childList: true,
3822							subtree: true
3823						});
3824						console.debug('[pxChat] Message observer installed on chat iframe');
3825					} catch(e) {
3826						// Cross-origin: no podemos acceder al contenido del iframe
3827						console.debug('[pxChat] Cannot observe iframe content (cross-origin)');
3828					}
3829				}
3830			}
3831			
3832			// Intentar observar cuando el iframe se carga
3833			const ifr = findChatIframe();
3834			if (ifr) {
3835				if (ifr.contentDocument && ifr.contentDocument.body) {
3836					observeChatIframe();
3837				} else {
3838					ifr.addEventListener('load', observeChatIframe);
3839				}
3840			}
3841			
3842			// También intentar cuando se detecta un nuevo iframe
3843			const iframeObserver = new MutationObserver(function() {
3844				const ifr = findChatIframe();
3845				if (ifr && !ifr.__px_chat_message_observed) {
3846					ifr.__px_chat_message_observed = true;
3847					if (ifr.contentDocument && ifr.contentDocument.body) {
3848						observeChatIframe();
3849					} else {
3850						ifr.addEventListener('load', observeChatIframe);
3851					}
3852				}
3853			});
3854			iframeObserver.observe(document.body, { childList: true, subtree: true });
3855		} catch(e) {
3856			console.error('[pxChat] Error setting up message observer:', e);
3857		}
3858		
3859		// Expose helpers for manual triggers if needed
3860		window.pxChatOpen = function(meta){ handleChatOpen(meta || {}); };
3861		window.pxChatMessage = function(meta){ handleChatMessage(meta || {}); };
3862		window.pxChatClose = function(meta){ handleChatClose(meta || {}); };
3863		// Expose message detection function for external use
3864		window.pxChatDetectMessage = function(messageId, messageType, from) {
3865			detectChatMessage(messageId, messageType, from);
3866		};
3867		// Robust DOM observer: button presence and iframe lifecycle
3868		const observedIframes = new Set();
3869		function isChatIframe(node){
3870			if (!node || node.nodeName !== 'IFRAME') return false;
3871			try {
3872				const src = node.getAttribute('src') || '';
3873				return /pxsol|chat|widget/i.test(src);
3874			} catch(_e){ return false; }
3875		}
3876		function installButtonListener(btn){
3877			if (!btn || btn.__px_chat_hooked) return;
3878			btn.__px_chat_hooked = true;
3879			try {
3880				btn.__px_open_state = !!(btn.classList && btn.classList.contains('widget-launcher-button--open'));
3881				// NO sincronizar estado inicial aquí - solo observar cambios futuros
3882				let buttonChangeTimer = null;
3883				const ob = new MutationObserver(function(muts){
3884					for (let i = 0; i < muts.length; i++){
3885						const m = muts[i];
3886						if (m.type === 'attributes' && m.attributeName === 'class') {
3887							const isOpen = btn.classList.contains('widget-launcher-button--open');
3888							// Solo disparar si realmente cambió el estado
3889							if (isOpen !== btn.__px_open_state) {
3890								const now = Date.now();
3891								if (now - lastStateChangeMs < 300) {
3892									console.debug('[pxChat] MutationObserver: change throttled');
3893									return; // Ignorar cambios muy rápidos
3894								}
3895								
3896								// Procesar inmediatamente (sin debounce para Chromium)
3897								if (buttonChangeTimer) {
3898									clearTimeout(buttonChangeTimer);
3899								}
3900								// Delay mínimo solo para asegurar que el DOM se actualizó
3901								buttonChangeTimer = setTimeout(function() {
3902									processButtonStateChange(btn, now);
3903									buttonChangeTimer = null;
3904								}, 50); // Delay mínimo de 50ms para Chromium
3905							}
3906						}
3907					}
3908				});
3909				ob.observe(btn, { attributes: true, attributeFilter: ['class'] });
3910				btn.__px_chat_observer = ob;
3911			} catch(_e){}
3912		}
3913		
3914		function processButtonStateChange(btn, timestamp) {
3915			// Verificar que el estado realmente cambió
3916			const currentIsOpen = btn.classList.contains('widget-launcher-button--open') || 
3917			                      btn.classList.toString().indexOf('--open') !== -1;
3918			console.log('[pxChat] processButtonStateChange:', {
3919				currentIsOpen: currentIsOpen,
3920				previousState: btn.__px_open_state,
3921				chatIsOpen: chatIsOpen
3922			});
3923			
3924			if (currentIsOpen === btn.__px_open_state) {
3925				console.log('[pxChat] MutationObserver: state unchanged, skipping');
3926				return;
3927			}
3928			
3929			btn.__px_open_state = currentIsOpen;
3930			lastStateChangeMs = timestamp || Date.now();
3931			
3932			if (currentIsOpen) { 
3933				console.log('[pxChat] MutationObserver: button opened, calling handleChatOpen');
3934				handleChatOpen(); 
3935			} else { 
3936				// Verificar que chatIsOpen es true antes de intentar cerrar
3937				if (!chatIsOpen) {
3938					console.log('[pxChat] MutationObserver: button closed but chatIsOpen is already false, skipping');
3939					return;
3940				}
3941				// Guard mínimo: no cerrar si el chat se abrió hace menos de 3 segundos
3942				const finalNow = Date.now();
3943				const startMs = chatOpenTsMs || lastOpenPushMs || null;
3944				if (startMs && (finalNow - startMs < 3000)) {
3945					console.log('[pxChat] MutationObserver: chat_close blocked - chat opened too recently (' + Math.round((finalNow - startMs) / 1000) + 's ago, minimum 3s)');
3946					return;
3947				}
3948				
3949				// CRÍTICO: Esperar y verificar nuevamente que el botón realmente perdió la clase --open
3950				// Esto previene disparos prematuros cuando el DOM aún se está actualizando
3951				setTimeout(function() {
3952					const btnCheck = document.querySelector('.widget-launcher-button') || 
3953					                  document.querySelector('[class*="widget-launcher"]') ||
3954					                  document.querySelector('[class*="launcher-button"]');
3955					const isStillOpen = btnCheck && btnCheck.classList && (
3956						btnCheck.classList.contains('widget-launcher-button--open') || 
3957						btnCheck.classList.toString().indexOf('--open') !== -1
3958					);
3959					
3960					console.log('[pxChat] MutationObserver: delayed verification after button close:', {
3961						isStillOpen: isStillOpen,
3962						chatIsOpen: chatIsOpen,
3963						hasButton: !!btnCheck
3964					});
3965					
3966					// Solo proceder si el botón realmente está cerrado
3967					// Si hay botón, confiar solo en el botón (no en el iframe)
3968					if (isStillOpen) {
3969						console.log('[pxChat] MutationObserver: chat_close CANCELLED - button still shows as open after delay');
3970						return;
3971					}
3972					// Solo verificar iframe si NO hay botón
3973					if (!btnCheck) {
3974						const ifr = findChatIframe();
3975						const isIframeVisible = isElementVisible(ifr);
3976						if (isIframeVisible) {
3977							console.log('[pxChat] MutationObserver: chat_close CANCELLED - iframe still visible after delay (no button found)');
3978							return;
3979						}
3980					}
3981					if (!chatIsOpen) {
3982						console.log('[pxChat] MutationObserver: chat_close CANCELLED - chatIsOpen is already false');
3983						return;
3984					}
3985					
3986					console.log('[pxChat] MutationObserver: button confirmed closed after delay, calling handleChatClose');
3987					handleChatClose(); 
3988				}, 600); // Esperar 600ms para confirmar que el DOM se actualizó completamente
3989			}
3990		}
3991		
3992		function scanExisting(){
3993			try {
3994				const btn = document.querySelector('.widget-launcher-button');
3995				if (btn) installButtonListener(btn);
3996				document.querySelectorAll('iframe').forEach(function(ifr){
3997					if (isChatIframe(ifr) && !observedIframes.has(ifr)) {
3998						observedIframes.add(ifr);
3999						// Delay más largo para evitar falsos positivos
4000						setTimeout(syncChatState, 800);
4001					}
4002				});
4003			} catch(_e){}
4004		}
4005		const mo = new MutationObserver(function(mutations){
4006			let anyRemoval = false;
4007			for (const m of mutations) {
4008				if (m.type === 'childList') {
4009					// Added nodes
4010					m.addedNodes && m.addedNodes.forEach(function(n){
4011						try {
4012							if (!(n instanceof Element)) return;
4013							if (n.classList && n.classList.contains('widget-launcher-button')) {
4014								installButtonListener(n);
4015							}
4016							if (isChatIframe(n) && !observedIframes.has(n)) { observedIframes.add(n); setTimeout(syncChatState, 800); }
4017							// Deep scan
4018							n.querySelectorAll && n.querySelectorAll('.widget-launcher-button,iframe').forEach(function(x){
4019								if (x.classList && x.classList.contains('widget-launcher-button')) installButtonListener(x);
4020								if (isChatIframe(x) && !observedIframes.has(x)) { observedIframes.add(x); setTimeout(syncChatState, 800); }
4021							});
4022						} catch(_e){}
4023					});
4024					// Removed nodes
4025					if (m.removedNodes && m.removedNodes.length) {
4026						anyRemoval = true;
4027					}
4028				}
4029			}
4030			if (anyRemoval) {
4031				// Check if any observed iframe is gone
4032				const still = new Set();
4033				observedIframes.forEach(function(ifr){
4034					if (document.body && document.body.contains(ifr)) {
4035						still.add(ifr);
4036					} else {
4037						// Delay más largo cuando se remueve el iframe (chat cerrado)
4038						setTimeout(syncChatState, 500);
4039					}
4040				});
4041				observedIframes.clear();
4042				still.forEach(function(x){ observedIframes.add(x); });
4043			}
4044		});
4045		// Poller de respaldo acotado: solo durante una ventana corta post-load
4046		// para evitar loops eternos si no aparece el botón.
4047		const CHAT_FALLBACK_POLL_INTERVAL_MS = 500;
4048		const CHAT_FALLBACK_POLL_MAX_MS_AFTER_LOAD = 2000;
4049		let chatFallbackPollIntervalId = null;
4050		let chatFallbackPollStartMs = null;
4051		
4052		function stopChatFallbackPoller(reason) {
4053			if (!chatFallbackPollIntervalId) return;
4054			clearInterval(chatFallbackPollIntervalId);
4055			chatFallbackPollIntervalId = null;
4056			console.log('[pxChat] Polling stopped:', reason);
4057		}
4058		
4059		function runChatFallbackPollTick() {
4060			const btn = document.querySelector('.widget-launcher-button') || 
4061			            document.querySelector('[class*="widget-launcher"]') ||
4062			            document.querySelector('[class*="launcher-button"]');
4063			// Solo ejecutar syncChatState si NO hay botón (el botón tiene su propio observer)
4064			if (!btn) {
4065				console.log('[pxChat] Polling syncChatState (no button found)');
4066				syncChatState();
4067				return;
4068			}
4069			
4070			console.debug('[pxChat] Polling skipped - button found, using MutationObserver instead');
4071			stopChatFallbackPoller('button found');
4072		}
4073		
4074		function startChatFallbackPoller() {
4075			if (chatFallbackPollIntervalId) return;
4076			chatFallbackPollStartMs = Date.now();
4077			runChatFallbackPollTick(); // Tick inicial inmediato
4078			chatFallbackPollIntervalId = setInterval(function() {
4079				if (Date.now() - chatFallbackPollStartMs > CHAT_FALLBACK_POLL_MAX_MS_AFTER_LOAD) {
4080					stopChatFallbackPoller('load + ' + CHAT_FALLBACK_POLL_MAX_MS_AFTER_LOAD + 'ms timeout');
4081					return;
4082				}
4083				runChatFallbackPollTick();
4084			}, CHAT_FALLBACK_POLL_INTERVAL_MS);
4085		}
4086		
4087		// Ejecutar solo post-load (o inmediatamente si la página ya terminó de cargar)
4088		if (document.readyState === 'complete') {
4089			startChatFallbackPoller();
4090		} else {
4091			window.addEventListener('load', function onLoadStartChatFallbackPoller() {
4092				window.removeEventListener('load', onLoadStartChatFallbackPoller);
4093				startChatFallbackPoller();
4094			});
4095		}
4096		try {
4097			mo.observe(document.documentElement || document, { childList: true, subtree: true });
4098			// Initial scan after load tick
4099			if (document.readyState === 'loading') {
4100				document.addEventListener('DOMContentLoaded', scanExisting);
4101			} else {
4102				setTimeout(scanExisting, 0);
4103			}
4104		} catch(_e){}
4105	})();
4106</script>
vendor: 1 bytes, line 4106
4106
4107<script>
4108	// Override para evitar espejo de chat_* al dataLayer y evitar doble track
4109	(function(){
4110		if (typeof window.gtag === 'function') {
4111			window.gtag = function(){
4112				try { window.dataLayer.push(arguments); } catch(_e){}
4113			};
4114		}
4115		// Redefinir pxTrack: usa gtag si existe; sino, dataLayer
4116		window.pxTrack = function(eventName, params){
4117			params = params || {};
4118			if (typeof window.gtag === 'function') {
4119				try { window.gtag('event', eventName, params); } catch(_e){}
4120			} else {
4121				try { window.dataLayer.push(Object.assign({ event: eventName }, params)); } catch(_e){}
4122			}
4123		};
4124	})();
4125</script>
4125
4126</script>
4126    </div>
4127    <div id="CortinaCargando" align="center" class="Loading">
4128        <div class="linear-activity">
4129            <div class="indeterminate"></div>
4130        </div>
4131    </div>
4132
4133    <div id="diccionario">
4134            </div>
4135<script>
4136	window.currentContent = "list";
4137	window.currentDevice = "desktop";
4138	console.log('CurrentState', window.currentContent, window.currentDevice);
4139</script>
4139
4140
4141    
4141<script src="/neo_modules/neo_popup/js.js?v=121790891789"></script>
4141
4142<!-- End Popup code -->
4143<div class="EndError left full" style="max-width: 100vw; overflow: auto;">
4144	</div>
4145
4146
4147<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"1a7d7c25cb794fbbb525883f1f41f61e","r":1,"spa":2}' crossorigin="anonymous"></script>
4147
4148</body>
4149<div id="dialogBox" title="..." class="nodisplay">
4150	<p><span class="ui-icon ui-icon-alert alerticon"></span>...</p>
4151</div>
4152
4153
4154
4155
4156</html>
4157
4158

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.