PageSourceSearch

https://sportingclubhuelva.com/js/wf_ventana.js?v=1778290966

js sportingclubhuelva.com collected 2026-10-02 13:36:42 UTC 46,004 bytes, 1,381 lines download raw bytes

1
2/**
3 * Se encarga de gestionar ventanas WF desde una única clase JavaScript.
4 * 
5 * FUNCIONALIDAD EXTENDIDA
6 * ----------------------------------------------------------------------------------------------------
7 * Esta clase centraliza la apertura, creación, carga, inserción y visualización
8 * de ventanas del sistema Webforever.
9 * 
10 * Nace como evolución natural de la antigua función `ventana_wf()`, que sigue
11 * pudiendo existir como wrapper de compatibilidad, pero cuya lógica real debe
12 * terminar delegando en esta clase.
13 * 
14 * La clase permite abrir ventanas en tres escenarios principales:
15 * 
16 * 1. Ventanas ya existentes en el DOM.
17 * 2. Ventanas creadas dinámicamente para pintar HTML bruto recibido por AJAX.
18 * 3. Ventanas completas generadas por PHP y recibidas dentro de una respuesta
19 *    JSON tipo R de Webforever.
20 * 
21 * La idea importante es esta:
22 * 
23 *     wf_ventana no solo abre ventanas.
24 *     wf_ventana decide qué tipo de respuesta ha recibido y actúa en consecuencia.
25 * 
26 * ----------------------------------------------------------------------------------------------------
27 * PROBLEMA QUE RESUELVE
28 * ----------------------------------------------------------------------------------------------------
29 * Antes, el sistema esperaba casi siempre HTML bruto.
30 * 
31 * Ejemplo:
32 * 
33 *     <p class='abrir_ventana_wf'
34 *        data-proceso='vista_previa_r'
35 *        data-m='ec'
36 *        data-id='1'
37 *        data-url='/ajax.php'>
38 *         Abrir
39 *     </p>
40 * 
41 * Ese caso devuelve HTML normal y la clase puede:
42 * 
43 * 1. Crear una ventana genérica.
44 * 2. Insertar ese HTML dentro de `.contenido`.
45 * 3. Mostrar la ventana.
46 * 
47 * Pero hay otro caso más potente:
48 * 
49 *     <p class='abrir_ventana_wf'
50 *        data-proceso='productos_carac_load'
51 *        data-id_padre='1'
52 *        data-url='/enrutador.php'>
53 *         Abrir
54 *     </p>
55 * 
56 * En este segundo caso, el servidor puede devolver una matriz R de Webforever:
57 * 
58 *     {
59 *         "id"   : 0,
60 *         "code" : 1,
61 *         "str"  : "Datos cargados.",
62 *         "html" : "<div id='productos_carac_load' class='... ventana_wf'>...</div>"
63 *     }
64 * 
65 * Aquí `data.html` no es contenido suelto.
66 * Aquí `data.html` ya trae una ventana completa generada por PHP.
67 * 
68 * Por tanto, no se debe crear una ventana JS adicional para meter dentro esa
69 * ventana PHP, porque acabaríamos con una ventana dentro de otra.
70 * 
71 * Esta clase resuelve ese caso detectando si el HTML recibido contiene
72 * `.ventana_wf`.
73 * 
74 * Si lo contiene:
75 * 
76 *     Inserta la ventana completa en el DOM y la muestra.
77 * 
78 * Si no lo contiene:
79 * 
80 *     Crea una ventana genérica, mete el HTML en `.contenido` y la muestra.
81 * 
82 * ----------------------------------------------------------------------------------------------------
83 * IDEA GENERAL
84 * ----------------------------------------------------------------------------------------------------
85 * La clase distingue varios conceptos que conviene no mezclar:
86 * 
87 * - `id`
88 *   Identificador o selector de una ventana ya existente o que queremos crear
89 *   con un ID concreto.
90 * 
91 * - `id_artilugio`
92 *   Identificador de un artilugio que debe solicitarse al servidor mediante el
93 *   proceso clásico:
94 * 
95 *       /ajax.php?proceso=artilugio_ventana
96 * 
97 * - `url`
98 *   URL completa o base construida desde el oyente `abrir_ventana_wf`.
99 *   Este es el modo más flexible, porque permite llamar a cualquier proceso
100 *   antiguo o nuevo del sistema.
101 * 
102 * - `params`
103 *   Cadena de parámetros tipo querystring. Puede venir escrita manualmente en
104 *   `data-params` o construirse automáticamente desde los atributos `data-*`
105 *   del botón pulsado.
106 * 
107 * - `html`
108 *   HTML final que se va a pintar. Puede venir como respuesta directa o dentro
109 *   de `data.html` si la respuesta es JSON tipo R.
110 * 
111 * ----------------------------------------------------------------------------------------------------
112 * MODOS DE TRABAJO
113 * ----------------------------------------------------------------------------------------------------
114 * 
115 * MODO 1: APERTURA POR URL
116 * ----------------------------------------------------------------------------------------------------
117 * Es el modo más importante y el más versátil.
118 * 
119 * Se activa cuando en `abrir()` viene informado el parámetro `url`.
120 * 
121 * Ejemplo:
122 * 
123 *     v.abrir({
124 *         url             : '/enrutador.php?proceso=productos_carac_load&id_padre=1',
125 *         clase_ancho     : 'w1200',
126 *         titulo_ventana  : 'Características',
127 *         pintar_cabecera : true
128 *     });
129 * 
130 * Flujo interno:
131 * 
132 * 1. Se llama a `_abrir_url()`.
133 * 2. Se hace `fetch()` por GET.
134 * 3. La respuesta se lee como texto.
135 * 4. Se intenta convertir ese texto a JSON.
136 * 5. Si es JSON, se trata como matriz R de Webforever.
137 * 6. Si no es JSON, se trata como HTML bruto.
138 * 7. Se comprueba si el HTML contiene una ventana completa `.ventana_wf`.
139 * 8. Si la contiene, se inserta tal cual en el DOM.
140 * 9. Si no la contiene, se crea una ventana genérica y se mete dentro.
141 * 
142 * Este modo permite usar botones como:
143 * 
144 *     <p class='abrir_ventana_wf'
145 *        data-proceso='productos_carac_load'
146 *        data-id_padre='1'
147 *        data-url='/enrutador.php'>
148 *         Abrir características
149 *     </p>
150 * 
151 * Y también:
152 * 
153 *     <p class='abrir_ventana_wf'
154 *        data-proceso='vista_previa_r'
155 *        data-m='ec'
156 *        data-id='1'
157 *        data-url='/ajax.php'>
158 *         Abrir vista previa
159 *     </p>
160 * 
161 * Ambos casos pasan por el mismo método, pero la clase decide qué hacer según
162 * la respuesta real del servidor.
163 * 
164 * ----------------------------------------------------------------------------------------------------
165 * MODO 2: RESPUESTA HTML BRUTA
166 * ----------------------------------------------------------------------------------------------------
167 * Se produce cuando la URL devuelve directamente HTML.
168 * 
169 * Ejemplo de respuesta:
170 * 
171 *     <div class='p-3'>
172 *         <h2>Vista previa</h2>
173 *         <p>Contenido recibido desde AJAX.</p>
174 *     </div>
175 * 
176 * En este caso la clase:
177 * 
178 * 1. Crea una ventana genérica con ID `wf_ventana_url`.
179 * 2. Elimina una ventana anterior con ese mismo ID si existiera.
180 * 3. Inserta el HTML dentro de:
181 * 
182 *        #wf_ventana_url .contenido
183 * 
184 * 4. Muestra la ventana.
185 * 
186 * Este es el comportamiento clásico.
187 * 
188 * ----------------------------------------------------------------------------------------------------
189 * MODO 3: RESPUESTA JSON R DE WEBFOREVER
190 * ----------------------------------------------------------------------------------------------------
191 * Se produce cuando la URL devuelve una matriz R.
192 * 
193 * Formato esperado:
194 * 
195 *     {
196 *         "id"   : 0,
197 *         "code" : 1,
198 *         "str"  : "Datos cargados.",
199 *         "html" : "<div>...</div>"
200 *     }
201 * 
202 * Reglas aplicadas:
203 * 
204 * - Si `code` no es 1:
205 * 
206 *       Se muestra error con `swal.fire()`.
207 * 
208 * - Si `code` es 1 pero `html` viene vacío:
209 * 
210 *       Se muestra error indicando que no hay HTML.
211 * 
212 * - Si `code` es 1 y `html` trae contenido:
213 * 
214 *       Se analiza el HTML recibido.
215 * 
216 * Aquí aparece la gran mejora de esta versión:
217 * 
218 *     El HTML puede ser contenido suelto o una ventana WF completa.
219 * 
220 * ----------------------------------------------------------------------------------------------------
221 * MODO 4: JSON R CON HTML SUELTO
222 * ----------------------------------------------------------------------------------------------------
223 * Ejemplo:
224 * 
225 *     {
226 *         "code" : 1,
227 *         "str"  : "Correcto.",
228 *         "html" : "<h2>Contenido</h2><p>Texto recibido.</p>"
229 *     }
230 * 
231 * En este caso `data.html` no contiene `.ventana_wf`.
232 * 
233 * La clase actúa igual que con HTML bruto:
234 * 
235 * 1. Crea `wf_ventana_url`.
236 * 2. Inserta el HTML en `.contenido`.
237 * 3. Muestra la ventana.
238 * 
239 * ----------------------------------------------------------------------------------------------------
240 * MODO 5: JSON R CON VENTANA WF COMPLETA
241 * ----------------------------------------------------------------------------------------------------
242 * Ejemplo:
243 * 
244 *     {
245 *         "code" : 1,
246 *         "str"  : "Datos cargados.",
247 *         "html" : "
248 *             <div id='productos_carac_load'
249 *                  class='h-100 position-fixed w-100 zi_1050 ventana_wf'>
250 *                 <div class='contenedor_principal w992 bg-white p-0 shadow-lg mt-1 rounded'>
251 *                     ...
252 *                 </div>
253 *             </div>
254 *         "
255 *     }
256 * 
257 * En este caso `data.html` ya trae una ventana completa.
258 * 
259 * La clase:
260 * 
261 * 1. Detecta `.ventana_wf` mediante `_html_trae_ventana_wf()`.
262 * 2. Extrae esa ventana desde un `<template>` temporal.
263 * 3. Lee su `id`.
264 * 4. Elimina del DOM una ventana anterior con el mismo ID, si existe.
265 * 5. Inserta la ventana completa en `document.body`.
266 * 6. Llama a `_toggle(id_html)` para mostrarla.
267 * 
268 * Este flujo evita duplicar estructura y permite que PHP siga teniendo control
269 * total cuando genera ventanas completas con `ventana()`.
270 * 
271 * ----------------------------------------------------------------------------------------------------
272 * MODO 6: APERTURA POR ARTILUGIO
273 * ----------------------------------------------------------------------------------------------------
274 * Se activa cuando viene informado `id_artilugio`.
275 * 
276 * Ejemplo:
277 * 
278 *     v.abrir({
279 *         id_artilugio    : 176,
280 *         params          : 'id=345&nombre=Juego',
281 *         clase_ancho     : 'w1200',
282 *         titulo_ventana  : 'Información',
283 *         pintar_cabecera : true
284 *     });
285 * 
286 * Flujo:
287 * 
288 * 1. Se llama a `_abrir_ajax()`.
289 * 2. Se hace POST a:
290 * 
291 *        /ajax.php?proceso=artilugio_ventana
292 * 
293 * 3. Se envía:
294 * 
295 *        id_artilugio
296 *        params
297 * 
298 * 4. El servidor devuelve JSON.
299 * 5. Si `code` es 1 y trae `html`, se crea:
300 * 
301 *        wf_ventana_{id_artilugio}
302 * 
303 * 6. Se inserta el HTML.
304 * 7. Se muestra la ventana.
305 * 
306 * Este modo queda mantenido por compatibilidad con el sistema de artilugios.
307 * 
308 * ----------------------------------------------------------------------------------------------------
309 * MODO 7: APERTURA POR DOM
310 * ----------------------------------------------------------------------------------------------------
311 * Se activa cuando viene informado `id` y no viene `url` ni `id_artilugio`.
312 * 
313 * Ejemplo:
314 * 
315 *     v.abrir({
316 *         id : '#mi_ventana'
317 *     });
318 * 
319 * Flujo:
320 * 
321 * 1. Se limpia el selector eliminando `#` si viene.
322 * 2. Se busca el elemento en el DOM.
323 * 3. Si no existe, se crea una ventana base con ese ID.
324 * 4. Se llama a `_toggle()` para abrirla o cerrarla.
325 * 
326 * Es el modo más sencillo.
327 * 
328 * ----------------------------------------------------------------------------------------------------
329 * ORDEN DE PRIORIDAD EN `abrir()`
330 * ----------------------------------------------------------------------------------------------------
331 * El método `abrir()` decide el camino con este orden:
332 * 
333 * 1. Si viene `url`, manda `url`.
334 * 2. Si no viene `url` pero viene `id_artilugio`, manda `id_artilugio`.
335 * 3. Si no viene lo anterior pero viene `id`, manda `id`.
336 * 4. Si no viene nada, no hace nada y deja aviso en consola.
337 * 
338 * Esto es importante:
339 * 
340 *     `url` tiene prioridad sobre todo.
341 * 
342 * Por tanto, si un botón trae `data-url`, el sistema usará `_abrir_url()`.
343 * 
344 * ----------------------------------------------------------------------------------------------------
345 * OYENTE RECOMENDADO: `.abrir_ventana_wf`
346 * ----------------------------------------------------------------------------------------------------
347 * El oyente del sistema puede recoger cualquier botón/enlace con la clase:
348 * 
349 *     abrir_ventana_wf
350 * 
351 * Ejemplo:
352 * 
353 *     <p class='abrir_ventana_wf'
354 *        data-proceso='productos_carac_load'
355 *        data-id_padre='1'
356 *        data-url='/enrutador.php'>
357 *         Abrir 1
358 *     </p>
359 * 
360 * El oyente debe:
361 * 
362 * 1. Detectar el click mediante delegación de eventos.
363 * 2. Leer los `data-*`.
364 * 3. Construir automáticamente los parámetros.
365 * 4. Montar la URL final.
366 * 5. Llamar a `ventana_wf()` o directamente a `new wf_ventana().abrir()`.
367 * 
368 * Los atributos `data-*` que no sean configuración interna pueden convertirse
369 * automáticamente en querystring.
370 * 
371 * Por ejemplo:
372 * 
373 *     data-proceso='productos_carac_load'
374 *     data-id_padre='1'
375 *     data-url='/enrutador.php'
376 * 
377 * puede terminar generando:
378 * 
379 *     /enrutador.php?proceso=productos_carac_load&id_padre=1
380 * 
381 * ----------------------------------------------------------------------------------------------------
382 * WRAPPER DE COMPATIBILIDAD: `ventana_wf()`
383 * ----------------------------------------------------------------------------------------------------
384 * Durante la migración puede mantenerse la función antigua:
385 * 
386 *     function ventana_wf(
387 *         id,
388 *         eliminar = false,
389 *         contenido_b64 = null,
390 *         clase_ancho = `w1200`,
391 *         titulo_ventana = '',
392 *         pintar_cabecera = true,
393 *         style_contenedor_principal = ``,
394 *         params = '',
395 *         id_artilugio = 0,
396 *         url = ''
397 *     )
398 *     {
399 *         const v = new wf_ventana();
400 * 
401 *         return v.abrir({
402 *             id                         : id || '',
403 *             eliminar                   : eliminar,
404 *             contenido_b64              : contenido_b64,
405 *             clase_ancho                : clase_ancho,
406 *             titulo_ventana             : titulo_ventana,
407 *             pintar_cabecera            : pintar_cabecera,
408 *             style_contenedor_principal : style_contenedor_principal,
409 *             params                     : params || '',
410 *             id_artilugio               : parseInt(id_artilugio, 10) || 0,
411 *             url                        : url || ''
412 *         });
413 *     }
414 * 
415 * Este wrapper permite no romper llamadas antiguas.
416 * 
417 * La dirección recomendada es:
418 * 
419 *     Código nuevo  -> usar `new wf_ventana().abrir({...})`.
420 *     Código viejo  -> puede seguir usando `ventana_wf(...)`.
421 * 
422 * ----------------------------------------------------------------------------------------------------
423 * MÉTODOS INTERNOS
424 * ----------------------------------------------------------------------------------------------------
425 * 
426 * abrir()
427 * ----------------------------------------------------------------------------------------------------
428 * Método principal.
429 * Decide qué flujo usar según los parámetros recibidos.
430 * 
431 * Prioridad:
432 * 
433 * 1. `_abrir_url()`
434 * 2. `_abrir_ajax()`
435 * 3. `_abrir_dom()`
436 * 
437 * También abre un grupo de depuración con `console.groupCollapsed()`.
438 * 
439 * ----------------------------------------------------------------------------------------------------
440 * _abrir_url()
441 * ----------------------------------------------------------------------------------------------------
442 * Carga contenido desde una URL por GET.
443 * 
444 * Es el método más inteligente de la clase.
445 * 
446 * Hace estas comprobaciones:
447 * 
448 * 1. Si la respuesta HTTP es correcta.
449 * 2. Si la respuesta es JSON o HTML.
450 * 3. Si el JSON cumple el formato R.
451 * 4. Si `code` es 1.
452 * 5. Si existe HTML.
453 * 6. Si el HTML recibido contiene `.ventana_wf`.
454 * 
455 * Según el resultado:
456 * 
457 * - Inserta una ventana completa.
458 * - O crea una ventana genérica e inserta contenido.
459 * 
460 * ----------------------------------------------------------------------------------------------------
461 * _html_trae_ventana_wf()
462 * ----------------------------------------------------------------------------------------------------
463 * Recibe HTML como texto y comprueba si contiene una ventana WF completa.
464 * 
465 * Internamente usa:
466 * 
467 *     const tpl = document.createElement(`template`);
468 * 
469 * Esto permite analizar el HTML sin pintarlo todavía en pantalla.
470 * 
471 * Devuelve:
472 * 
473 *     true  -> si encuentra `.ventana_wf`
474 *     false -> si no la encuentra
475 * 
476 * ----------------------------------------------------------------------------------------------------
477 * _abrir_html_ventana_completa()
478 * ----------------------------------------------------------------------------------------------------
479 * Recibe HTML que ya contiene una ventana WF completa.
480 * 
481 * Pasos:
482 * 
483 * 1. Crea un `<template>` temporal.
484 * 2. Busca `.ventana_wf`.
485 * 3. Comprueba que la ventana tenga `id`.
486 * 4. Elimina una ventana previa con el mismo ID.
487 * 5. Inserta la nueva ventana en `document.body`.
488 * 6. Llama a `_toggle(id_html)`.
489 * 
490 * Este método es fundamental para ventanas generadas por PHP.
491 * 
492 * ----------------------------------------------------------------------------------------------------
493 * _abrir_ajax()
494 * ----------------------------------------------------------------------------------------------------
495 * Modo clásico de artilugios.
496 * 
497 * Hace POST a:
498 * 
499 *     /ajax.php?proceso=artilugio_ventana
500 * 
501 * y espera un JSON con:
502 * 
503 *     code
504 *     html
505 * 
506 * Si todo es correcto, crea una ventana con ID:
507 * 
508 *     wf_ventana_{id_artilugio}
509 * 
510 * ----------------------------------------------------------------------------------------------------
511 * _abrir_dom()
512 * ----------------------------------------------------------------------------------------------------
513 * Modo básico por ID.
514 * 
515 * Abre o crea una ventana usando un identificador conocido.
516 * 
517 * ----------------------------------------------------------------------------------------------------
518 * _crear()
519 * ----------------------------------------------------------------------------------------------------
520 * Crea la estructura base de una ventana WF:
521 * 
522 *     <div id='...' class='h-100 position-fixed w-100 zi_1050 ventana_wf'>
523 *         <div class='contenedor_principal ...'>
524 *             <div class='cabecera ...'>...</div>
525 *             <div class='contenido p-2'></div>
526 *         </div>
527 *     </div>
528 * 
529 * Si `pintar_cabecera` es falso, no genera la cabecera.
530 * 
531 * ----------------------------------------------------------------------------------------------------
532 * _set_html()
533 * ----------------------------------------------------------------------------------------------------
534 * Inserta HTML dentro de:
535 * 
536 *     #{id} .contenido
537 * 
538 * Solo se usa cuando el HTML recibido no es una ventana completa.
539 * 
540 * ----------------------------------------------------------------------------------------------------
541 * _toggle()
542 * ----------------------------------------------------------------------------------------------------
543 * Muestra u oculta una ventana.
544 * 
545 * Si la ventana no tiene la clase `visible`:
546 * 
547 *     La añade y bloquea el scroll del body.
548 * 
549 * Si ya tiene la clase `visible`:
550 * 
551 *     La quita y restaura el scroll del body.
552 * 
553 * ----------------------------------------------------------------------------------------------------
554 * DEPURACIÓN
555 * ----------------------------------------------------------------------------------------------------
556 * La clase usa grupos de consola plegados:
557 * 
558 *     console.groupCollapsed()
559 *     console.groupEnd()
560 * 
561 * Esto permite ver claramente cada flujo:
562 * 
563 * - `wf_ventana.abrir()`
564 * - `wf_ventana._abrir_url()`
565 * - `wf_ventana._html_trae_ventana_wf()`
566 * - `wf_ventana._abrir_html_ventana_completa()`
567 * - `wf_ventana._abrir_ajax()`
568 * - `wf_ventana._abrir_dom()`
569 * - `wf_ventana._crear()`
570 * - `wf_ventana._set_html()`
571 * - `wf_ventana._toggle()`
572 * 
573 * La depuración está pensada para que dentro de tres meses se pueda seguir el
574 * rastro completo sin tener que adivinar si la respuesta era HTML, JSON R,
575 * contenido suelto o ventana completa.
576 * 
577 * ----------------------------------------------------------------------------------------------------
578 * REGLAS PRÁCTICAS
579 * ----------------------------------------------------------------------------------------------------
580 * 
581 * 1. Si el servidor devuelve HTML bruto, la clase crea una ventana genérica.
582 * 
583 * 2. Si el servidor devuelve JSON R con `code != 1`, se muestra error.
584 * 
585 * 3. Si el servidor devuelve JSON R con `code == 1` pero sin `html`, se muestra
586 *    error.
587 * 
588 * 4. Si el servidor devuelve JSON R con `html` normal, la clase crea una ventana
589 *    genérica y lo mete dentro.
590 * 
591 * 5. Si el servidor devuelve JSON R con `html` que ya contiene `.ventana_wf`,
592 *    la clase inserta esa ventana completa y la abre.
593 * 
594 * 6. Una ventana completa recibida desde PHP debe traer siempre atributo `id`.
595 * 
596 * 7. Si ya existe una ventana con el mismo ID, se elimina antes de insertar la
597 *    nueva.
598 * 
599 * 8. `url` tiene prioridad sobre `id_artilugio` e `id`.
600 * 
601 * 9. El contenido debe resolverse en PHP. JavaScript no debe sustituir tokens
602 *    de artilugios ni preparar HTML de negocio.
603 * 
604 * 10. El wrapper `ventana_wf()` puede seguir existiendo, pero la lógica viva
605 *     debe estar en esta clase.
606 * 
607 * ----------------------------------------------------------------------------------------------------
608 * EJEMPLOS RÁPIDOS
609 * ----------------------------------------------------------------------------------------------------
610 * 
611 * HTML bruto:
612 * 
613 *     <p class='abrir_ventana_wf'
614 *        data-proceso='vista_previa_r'
615 *        data-m='ec'
616 *        data-id='1'
617 *        data-url='/ajax.php'>
618 *         Abrir vista previa
619 *     </p>
620 * 
621 * JSON R con ventana PHP completa:
622 * 
623 *     <p class='abrir_ventana_wf'
624 *        data-proceso='productos_carac_load'
625 *        data-id_padre='1'
626 *        data-url='/enrutador.php'>
627 *         Abrir características
628 *     </p>
629 * 
630 * Apertura directa desde JavaScript:
631 * 
632 *     const v = new wf_ventana();
633 * 
634 *     v.abrir({
635 *         url             : '/enrutador.php?proceso=productos_carac_load&id_padre=1',
636 *         clase_ancho     : 'w1200',
637 *         titulo_ventana  : 'Características',
638 *         pintar_cabecera : true
639 *     });
640 * 
641 * Apertura por artilugio:
642 * 
643 *     const v = new wf_ventana();
644 * 
645 *     v.abrir({
646 *         id_artilugio : 176,
647 *         params       : 'id=345&nombre=Juego'
648 *     });
649 * 
650 * Apertura por DOM:
651 * 
652 *     const v = new wf_ventana();
653 * 
654 *     v.abrir({
655 *         id : '#mi_ventana'
656 *     });
657 * 
658 * ----------------------------------------------------------------------------------------------------
659 * FILOSOFÍA DE DISEÑO
660 * ----------------------------------------------------------------------------------------------------
661 * Esta clase no intenta ser un framework de modales universal.
662 * 
663 * Su objetivo es ser una pieza pequeña, clara y mantenible para ventanas WF:
664 * 
665 * - compatible con código antiguo;
666 * - útil para procesos nuevos;
667 * - capaz de entender respuestas modernas tipo R;
668 * - respetuosa con ventanas completas generadas por PHP;
669 * - fácil de depurar desde consola.
670 * 
671 * La regla mental es sencilla:
672 * 
673 *     PHP decide el contenido.
674 *     JavaScript decide cómo abrirlo sin duplicar estructura.
675 * 
676 * ----------------------------------------------------------------------------------------------------
677 * REGISTRO DE CAMBIOS. PUEDE INDICAR PÁGINA Y TOMO:
678 * ----------------------------------------------------------------------------------------------------
679 * 18-04-2026  :  Se documenta el paso conceptual desde la función procedural
680 *                `ventana_wf()` hacia una clase `wf_ventana`.
681 * 
682 * 09-05-2026  :  Se actualiza la documentación para reflejar el nuevo flujo
683 *                inteligente de `_abrir_url()`, capaz de distinguir entre HTML
684 *                bruto, JSON R de Webforever, HTML suelto y ventanas WF completas
685 *                generadas desde PHP.
686 * 
687 * REFERENCIAS
688 * ----------------------------------------------------------------------------------------------------
689 * t0p0
690 * Sistema WF ventanas.
691 * AJAX clásico: /ajax.php?proceso=artilugio_ventana
692 * Enrutador WF: /enrutador.php
693 * 
694 * DERECHOS
695 * ----------------------------------------------------------------------------------------------------
696 * @author Pablo Manuel Burrero Sánchez <[email protected]>
697 * @copyright Copyright (c) 2005-2026
698 */
699class wf_ventana
700{
701	constructor()
702	{
703		this.selector = null;
704		this.element  = null;
705	}
706
707	// Método principal de entrada para abrir una ventana WF.
708	abrir({
709		id = '',
710		eliminar = false,
711		contenido_b64 = null,
712		clase_ancho = 'w1200',
713		titulo_ventana = '',
714		pintar_cabecera = true,
715		style_contenedor_principal = '',
716		params = '',
717		id_artilugio = 0,
718		url = ''
719	} = {})
720	{
721		// Inicia un grupo de depuración plegado para seguir cada apertura de ventana.
722		console.groupCollapsed(`🪟 wf_ventana.abrir()`);
723
724		// Muestra la configuración completa recibida por la clase.
725		console.log(`Configuración recibida:`, {
726			id,
727			eliminar,
728			contenido_b64,
729			clase_ancho,
730			titulo_ventana,
731			pintar_cabecera,
732			style_contenedor_principal,
733			params,
734			id_artilugio,
735			url
736		});
737
738		// Si viene URL, se entiende que la ventana debe cargar contenido remoto.
739		if (url)
740		{
741			// Informa del modo de apertura seleccionado.
742			console.log(`Modo seleccionado: _abrir_url`);
743
744			// Muestra la URL final que se va a solicitar.
745			console.log(`URL:`, url);
746
747			this._abrir_url(url, clase_ancho, titulo_ventana, pintar_cabecera);
748
749			// Cierra el grupo de depuración antes de salir del método.
750			console.groupEnd();
751
752			return;
753		}
754
755		// Si viene id_artilugio, se usa el modo AJAX clásico de artilugios.
756		if (id_artilugio)
757		{
758			// Informa del modo de apertura seleccionado.
759			console.log(`Modo seleccionado: _abrir_ajax`);
760
761			// Muestra los datos principales del artilugio.
762			console.log(`ID artilugio:`, id_artilugio);
763			console.log(`Params:`, params);
764
765			this._abrir_ajax(id_artilugio, params, clase_ancho, titulo_ventana, pintar_cabecera);
766
767			// Cierra el grupo de depuración antes de salir del método.
768			console.groupEnd();
769
770			return;
771		}
772
773		// Si viene id, se trabaja contra una ventana ya existente o creada por ID.
774		if (id)
775		{
776			// Informa del modo de apertura seleccionado.
777			console.log(`Modo seleccionado: _abrir_dom`);
778
779			// Muestra el ID de ventana usado.
780			console.log(`ID:`, id);
781
782			this._abrir_dom(id, clase_ancho, titulo_ventana, pintar_cabecera);
783
784			// Cierra el grupo de depuración antes de salir del método.
785			console.groupEnd();
786
787			return;
788		}
789
790		// Si no viene ningún modo válido, no se abre nada.
791		console.warn(`No se recibió url, id_artilugio ni id. No se abre ventana.`);
792
793		// Cierra el grupo de depuración.
794		console.groupEnd();
795	}
796
797	// Abre una ventana WF cargando contenido remoto desde una URL.
798	_abrir_url(url, clase_ancho, titulo_ventana, pintar_cabecera)
799	{
800		// Inicia un grupo de depuración plegado para esta carga por URL.
801		console.groupCollapsed(`🪟 wf_ventana._abrir_url()`);
802
803		// Muestra los datos principales recibidos por el método.
804		console.log(`URL recibida:`, url);
805		console.log(`Clase ancho:`, clase_ancho);
806		console.log(`Título ventana:`, titulo_ventana);
807		console.log(`Pintar cabecera:`, pintar_cabecera);
808
809		// Muestra el loader del sistema mientras se realiza la petición.
810		mostrar_espera();
811
812		// Lanza la petición GET contra la URL indicada.
813		fetch(url, {
814			method: `GET`,
815			cache: `no-store`,
816			headers: {
817				'X-Requested-With': 'XMLHttpRequest'
818			}
819		})
820		.then(r =>
821		{
822			// Muestra información básica de la respuesta HTTP.
823			console.log(`Respuesta HTTP:`, r.status, r.statusText);
824
825			// Si la respuesta HTTP no es correcta, se lanza error.
826			if (!r.ok) throw new Error(`HTTP ${r.status}`);
827
828			// Devuelve la respuesta como texto para poder decidir si es JSON o HTML bruto.
829			return r.text();
830		})
831		.then(texto =>
832		{
833			// Guarda el HTML final que se terminará pintando.
834			let html = ``;
835
836			// Guarda el JSON parseado si la respuesta viene en formato R de Webforever.
837			let data = null;
838
839			// Muestra una vista rápida de la respuesta recibida.
840			console.log(`Longitud respuesta:`, texto.length);
841			console.log(`Primeros caracteres:`, texto.substring(0, 250));
842
843			// Intentamos averiguar si la respuesta es JSON tipo R de Webforever.
844			try
845			{
846				data = JSON.parse(texto);
847
848				// Informa de que la respuesta se ha podido interpretar como JSON.
849				console.log(`Respuesta detectada como JSON R:`, data);
850			}
851			catch (e)
852			{
853				// Si falla el parseo, se considera HTML bruto.
854				console.log(`Respuesta no JSON. Se tratará como HTML bruto.`);
855
856				data = null;
857			}
858
859			// Si es JSON, aplicamos reglas R.
860			if (data)
861			{
862				// Muestra el código y mensaje recibidos desde Webforever.
863				console.log(`JSON code:`, data.code);
864				console.log(`JSON str:`, data.str || ``);
865
866				if (parseInt(data.code, 10) !== 1)
867				{
868					// Informa del motivo por el que no se continuará.
869					console.warn(`JSON R con code distinto de 1. Se cancela apertura.`);
870
871					swal.fire(`¡Error!`, data.str || `No se pudo abrir la ventana.`, `error`);
872					return;
873				}
874
875				// Toma el HTML desde la matriz R.
876				html = data.html || ``;
877
878				// Muestra la longitud del HTML recibido dentro del JSON.
879				console.log(`Longitud data.html:`, html.length);
880
881				if (!html.trim())
882				{
883					// Informa de que el JSON era correcto pero no traía contenido pintable.
884					console.warn(`JSON R correcto, pero data.html viene vacío.`);
885
886					swal.fire(`¡Error!`, `La respuesta JSON no contiene HTML.`, `error`);
887					return;
888				}
889			}
890			else
891			{
892				// Si no es JSON, lo tratamos como HTML bruto.
893				html = texto || ``;
894
895				// Muestra la longitud del HTML bruto.
896				console.log(`Longitud HTML bruto:`, html.length);
897
898				if (!html.trim())
899				{
900					// Informa de que no hay HTML útil que pintar.
901					console.warn(`Respuesta HTML vacía.`);
902
903					swal.fire(`¡Error!`, `La respuesta no contiene HTML.`, `error`);
904					return;
905				}
906			}
907
908			// Si el HTML ya trae una ventana WF completa, no creamos otra.
909			if (this._html_trae_ventana_wf(html))
910			{
911				// Informa de que se ha detectado una ventana completa generada por PHP.
912				console.log(`HTML con .ventana_wf detectado. Se insertará la ventana completa sin envolver.`);
913
914				this._abrir_html_ventana_completa(html);
915				return;
916			}
917
918			// Si no trae ventana, usamos el comportamiento actual:
919			// crear ventana genérica, meter HTML dentro y abrir.
920			const id_html = `wf_ventana_url`;
921
922			// Muestra el ID de la ventana genérica que se va a usar.
923			console.log(`HTML sin ventana WF. Se usará ventana genérica:`, id_html);
924
925			// Busca si ya existe una ventana genérica anterior.
926			const existente = document.getElementById(id_html);
927
928			// Si ya existía una ventana genérica, se elimina para evitar arrastrar contenido anterior.
929			if (existente) existente.remove();
930
931			// Crea la estructura base de la ventana.
932			this._crear(id_html, clase_ancho, titulo_ventana, pintar_cabecera);
933
934			// Inserta el HTML remoto dentro del contenedor .contenido.
935			this._set_html(id_html, html);
936
937			// Muestra la ventana.
938			this._toggle(id_html);
939
940			// Informa de que el flujo terminó correctamente.
941			console.log(`Ventana abierta correctamente desde URL.`);
942		})
943		.catch(error =>
944		{
945			// Muestra el error técnico en consola.
946			console.error(error);
947
948			// Muestra aviso visual al usuario.
949			swal.fire(`¡Error!`, `No se pudo abrir la ventana.`, `error`);
950		})
951		.finally(() =>
952		{
953			// Oculta el loader del sistema.
954			eliminar_espera();
955
956			// Cierra el grupo de depuración.
957			console.groupEnd();
958		});
959	}
960
961	// Comprueba si un bloque HTML recibido ya contiene una ventana WF completa.
962	_html_trae_ventana_wf(html)
963	{
964		// Inicia un grupo de depuración plegado para esta comprobación.
965		console.groupCollapsed(`🪟 wf_ventana._html_trae_ventana_wf()`);
966
967		// Muestra la longitud del HTML recibido.
968		console.log(`Longitud HTML:`, html ? html.length : 0);
969
970		// Crea un template temporal para poder analizar el HTML sin pintarlo todavía en el DOM.
971		const tpl = document.createElement(`template`);
972
973		// Inserta el HTML recibido dentro del template, eliminando espacios exteriores.
974		tpl.innerHTML = html.trim();
975
976		// Busca dentro del HTML recibido un elemento con la clase principal de ventana WF.
977		const ventana = tpl.content.querySelector(`.ventana_wf`);
978
979		// Muestra si se ha encontrado o no una ventana WF.
980		console.log(`¿Trae .ventana_wf?:`, !!ventana);
981
982		// Si existe ventana, muestra su ID para facilitar depuración.
983		if (ventana) console.log(`ID ventana detectada:`, ventana.id || `(sin id)`);
984
985		// Cierra el grupo de depuración.
986		console.groupEnd();
987
988		return !!ventana;
989	}
990
991	// Inserta y abre una ventana WF completa recibida ya construida desde PHP.
992	_abrir_html_ventana_completa(html)
993	{
994		// Inicia un grupo de depuración plegado para la apertura de ventana completa.
995		console.groupCollapsed(`🪟 wf_ventana._abrir_html_ventana_completa()`);
996
997		// Muestra la longitud del HTML recibido.
998		console.log(`Longitud HTML:`, html ? html.length : 0);
999
1000		// Crea un template temporal para analizar el HTML sin insertarlo directamente en el DOM.
1001		const tpl = document.createElement(`template`);
1002
1003		// Inserta el HTML recibido dentro del template, eliminando espacios exteriores.
1004		tpl.innerHTML = html.trim();
1005
1006		// Busca dentro del HTML recibido la ventana WF completa.
1007		const ventana = tpl.content.querySelector(`.ventana_wf`);
1008
1009		// Muestra si se ha localizado la ventana.
1010		console.log(`Ventana encontrada:`, !!ventana);
1011
1012		if (!ventana)
1013		{
1014			// Informa en consola de que el HTML no contiene una ventana válida.
1015			console.warn(`No se encontró ningún elemento con clase .ventana_wf.`);
1016
1017			swal.fire(`¡Error!`, `No se encontró la ventana WF en el HTML recibido.`, `error`);
1018
1019			// Cierra el grupo de depuración antes de salir.
1020			console.groupEnd();
1021
1022			return;
1023		}
1024
1025		// Toma el ID HTML real de la ventana recibida.
1026		const id_html = (ventana.id || ``).trim();
1027
1028		// Muestra el ID detectado.
1029		console.log(`ID ventana:`, id_html || `(sin id)`);
1030
1031		if (!id_html)
1032		{
1033			// Informa en consola de que la ventana no trae ID.
1034			console.warn(`La ventana WF recibida no tiene atributo id.`);
1035
1036			swal.fire(`¡Error!`, `La ventana recibida no tiene ID.`, `error`);
1037
1038			// Cierra el grupo de depuración antes de salir.
1039			console.groupEnd();
1040
1041			return;
1042		}
1043
1044		// Busca si ya existe en el DOM una ventana con el mismo ID.
1045		const existente = document.getElementById(id_html);
1046
1047		// Informa si había una ventana previa con el mismo ID.
1048		console.log(`Ventana previa existente:`, !!existente);
1049
1050		// Si ya existe, se elimina para evitar duplicados o contenido antiguo.
1051		if (existente) existente.remove();
1052
1053		// Inserta en el body la ventana completa recibida desde PHP.
1054		document.body.appendChild(ventana);
1055
1056		// Muestra en consola la ventana ya insertada.
1057		console.log(`Ventana insertada en document.body:`, ventana);
1058
1059		// Abre la ventana usando el mecanismo normal de la clase.
1060		this._toggle(id_html);
1061
1062		// Informa de que el proceso ha terminado correctamente.
1063		console.log(`Ventana completa abierta correctamente.`);
1064
1065		// Cierra el grupo de depuración.
1066		console.groupEnd();
1067	}
1068
1069	// Abre una ventana WF dinámica pidiendo el contenido de un artilugio por AJAX.
1070	_abrir_ajax(id_artilugio, params, clase_ancho, titulo_ventana, pintar_cabecera)
1071	{
1072		// Inicia un grupo de depuración plegado para este flujo AJAX.
1073		console.groupCollapsed(`🪟 wf_ventana._abrir_ajax()`);
1074
1075		// Informa del modo de trabajo seleccionado.
1076		console.log(`→ modo AJAX`);
1077
1078		// Muestra los parámetros principales recibidos.
1079		console.log(`ID artilugio:`, id_artilugio);
1080		console.log(`Params recibidos:`, params);
1081		console.log(`Clase ancho:`, clase_ancho);
1082		console.log(`Título ventana:`, titulo_ventana);
1083		console.log(`Pintar cabecera:`, pintar_cabecera);
1084
1085		// Define la URL fija encargada de devolver el HTML del artilugio en ventana.
1086		const url  = '/ajax.php?proceso=artilugio_ventana';
1087
1088		// Prepara el cuerpo POST con el ID del artilugio y sus parámetros.
1089		const body = new URLSearchParams({ id_artilugio, params });
1090
1091		// Muestra la URL final del proceso AJAX.
1092		console.log(`   - URL:`, url);
1093
1094		// Muestra el cuerpo POST codificado.
1095		console.log(`   - BODY:`, body.toString());
1096
1097		// Lanza la petición AJAX por POST.
1098		fetch(url, {
1099			method  : 'POST',
1100			headers : { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
1101			body    : body.toString()
1102		})
1103		.then(r =>
1104		{
1105			// Muestra el estado HTTP recibido.
1106			console.log(`   - HTTP:`, r.status, r.statusText);
1107
1108			// Convierte la respuesta a JSON.
1109			return r.json();
1110		})
1111		.then(data =>
1112		{
1113			// Muestra la respuesta JSON completa.
1114			console.log(`   - RESPUESTA AJAX:`, data);
1115
1116			// 🔴 CONTROL DURO
1117			if (!data || Number(data.code) !== 1 || !data.html)
1118			{
1119				// Avisa de respuesta inválida o sin HTML.
1120				console.warn(`wf_ventana: respuesta inválida o sin HTML`);
1121
1122				// Cierra el grupo de depuración antes de salir.
1123				console.groupEnd();
1124
1125				return;
1126			}
1127
1128			// Construye el ID interno de la ventana dinámica del artilugio.
1129			const id_html = `wf_ventana_${id_artilugio}`;
1130
1131			// Toma el HTML devuelto por el servidor.
1132			const html    = data.html;
1133
1134			// Muestra el ID HTML calculado para la ventana.
1135			console.log(`   - id_html:`, id_html);
1136
1137			// Muestra la longitud del HTML recibido.
1138			console.log(`   - longitud html:`, html.length);
1139
1140			// eliminamos si existe
1141			const existente = document.getElementById(id_html);
1142
1143			// Muestra si había una ventana previa con ese mismo ID.
1144			console.log(`   - ventana previa existente:`, !!existente);
1145
1146			if (existente)
1147			{
1148				// Informa de que se elimina la ventana anterior.
1149				console.log(`   - eliminando ventana previa`);
1150
1151				existente.remove();
1152			}
1153
1154			// Crea la estructura visual base de la ventana.
1155			this._crear(id_html, clase_ancho, titulo_ventana, pintar_cabecera);
1156
1157			// Informa de que se va a insertar el HTML.
1158			console.log(`   - insertando HTML`);
1159
1160			// Inserta el HTML recibido dentro de la ventana.
1161			this._set_html(id_html, html);
1162
1163			// Informa de que se va a mostrar la ventana.
1164			console.log(`   - mostrando ventana`);
1165
1166			// Muestra la ventana.
1167			this._toggle(id_html);
1168
1169			// Informa de que el flujo AJAX ha terminado correctamente.
1170			console.log(`   - ventana AJAX abierta correctamente`);
1171
1172			// Cierra el grupo de depuración.
1173			console.groupEnd();
1174		})
1175		.catch(err =>
1176		{
1177			// Muestra el error técnico en consola.
1178			console.error(`   - ERROR AJAX:`, err);
1179
1180			// Cierra el grupo de depuración también en caso de error.
1181			console.groupEnd();
1182		});
1183	}
1184
1185	// ==============================
1186	// DOM
1187	// ==============================
1188
1189	// Abre una ventana WF existente en el DOM o la crea si todavía no existe.
1190	_abrir_dom(id, clase_ancho, titulo_ventana, pintar_cabecera)
1191	{
1192		// Inicia un grupo de depuración plegado para este flujo DOM.
1193		console.groupCollapsed(`🪟 wf_ventana._abrir_dom()`);
1194
1195		// Informa del modo de trabajo seleccionado.
1196		console.log(`→ modo DOM`);
1197
1198		// Muestra los parámetros principales recibidos.
1199		console.log(`ID recibido:`, id);
1200		console.log(`Clase ancho:`, clase_ancho);
1201		console.log(`Título ventana:`, titulo_ventana);
1202		console.log(`Pintar cabecera:`, pintar_cabecera);
1203
1204		// Limpia la almohadilla inicial si el ID viene como selector CSS.
1205		const id_limpio = id.replace(/^#/, '');
1206
1207		// Muestra el ID final que se usará para buscar o crear la ventana.
1208		console.log(`ID limpio:`, id_limpio);
1209
1210		// Busca la ventana en el DOM.
1211		let el = document.getElementById(id_limpio);
1212
1213		// Muestra si la ventana ya existía.
1214		console.log(`Ventana existente:`, !!el);
1215
1216		if (!el)
1217		{
1218			// Informa de que no existía y se va a crear.
1219			console.log(`No existe la ventana. Se crea nueva estructura.`);
1220
1221			this._crear(id_limpio, clase_ancho, titulo_ventana, pintar_cabecera);
1222		}
1223
1224		// Muestra u oculta la ventana según su estado actual.
1225		this._toggle(id_limpio);
1226
1227		// Informa de que el flujo DOM ha terminado.
1228		console.log(`Ventana DOM procesada correctamente.`);
1229
1230		// Cierra el grupo de depuración.
1231		console.groupEnd();
1232	}
1233
1234	// ==============================
1235	// Crear ventana
1236	// ==============================
1237
1238	// Crea la estructura HTML base de una ventana WF y la añade al body.
1239	_crear(id, clase_ancho, titulo_ventana, pintar_cabecera)
1240	{
1241		// Inicia un grupo de depuración plegado para la creación de ventana.
1242		console.groupCollapsed(`🪟 wf_ventana._crear()`);
1243
1244		// Muestra el ID de la ventana que se va a crear.
1245		console.log(`crear ventana:`, id);
1246
1247		// Muestra la configuración visual recibida.
1248		console.log(`Clase ancho:`, clase_ancho);
1249		console.log(`Título ventana:`, titulo_ventana);
1250		console.log(`Pintar cabecera:`, pintar_cabecera);
1251
1252		// Crea el contenedor principal de la ventana.
1253		const el = document.createElement('div');
1254
1255		// Asigna el ID recibido al contenedor principal.
1256		el.id = id;
1257
1258		// Asigna las clases base de ventana WF.
1259		el.className = 'h-100 position-fixed w-100 zi_1050 ventana_wf';
1260
1261		// Prepara la cabecera si se ha indicado que debe pintarse.
1262		const cabecera = (!pintar_cabecera) ? '' : 
1263		`
1264			<div class="cabecera bg-primary d-flex justify-content-between p-2 text-white">
1265				<span>${titulo_ventana}</span>
1266				<i class="fa fa-window-close cerrar_ventana_wf puntero"></i>
1267			</div>
1268		`;
1269
1270		// Muestra si finalmente se ha generado cabecera.
1271		console.log(`Cabecera generada:`, cabecera.trim() !== ``);
1272
1273		// Inserta la estructura interna de la ventana.
1274		el.innerHTML = `
1275			<div class="contenedor_principal ${clase_ancho} bg-white shadow-lg mt-1 rounded">
1276				${cabecera}
1277				<div class="contenido p-2"></div>
1278			</div>
1279		`;
1280
1281		// Añade la ventana completa al body.
1282		document.body.appendChild(el);
1283
1284		// Muestra el elemento creado para inspección en consola.
1285		console.log(`Elemento creado:`, el);
1286
1287		// Informa de que la ventana se ha añadido correctamente.
1288		console.log(`Ventana añadida a document.body.`);
1289
1290		// Cierra el grupo de depuración.
1291		console.groupEnd();
1292	}
1293
1294	// Inserta HTML dentro del contenedor .contenido de una ventana WF.
1295	_set_html(id, html)
1296	{
1297		// Inicia un grupo de depuración plegado para la inserción de HTML.
1298		console.groupCollapsed(`🪟 wf_ventana._set_html()`);
1299
1300		// Muestra el ID de la ventana donde se va a insertar contenido.
1301		console.log(`ID ventana:`, id);
1302
1303		// Muestra la longitud del HTML recibido.
1304		console.log(`Longitud HTML:`, html ? html.length : 0);
1305
1306		// Busca el contenedor interno .contenido de la ventana indicada.
1307		const cont = document.querySelector(`#${id} .contenido`);
1308
1309		// Muestra si se ha encontrado el contenedor.
1310		console.log(`Contenedor .contenido encontrado:`, !!cont);
1311
1312		// Si existe el contenedor, inserta el HTML recibido.
1313		if (cont) cont.innerHTML = html;
1314
1315		// Informa del resultado de la operación.
1316		if (cont) console.log(`HTML insertado correctamente.`);
1317		else console.warn(`No se encontró #${id} .contenido. No se pudo insertar HTML.`);
1318
1319		// Cierra el grupo de depuración.
1320		console.groupEnd();
1321	}
1322
1323	// Muestra u oculta una ventana WF según su estado actual.
1324	_toggle(id)
1325	{
1326		// Inicia un grupo de depuración plegado para el cambio de visibilidad.
1327		console.groupCollapsed(`🪟 wf_ventana._toggle()`);
1328
1329		// Muestra el ID de la ventana que se va a alternar.
1330		console.log(`ID ventana:`, id);
1331
1332		// Busca la ventana en el DOM.
1333		const el = document.getElementById(id);
1334
1335		// Muestra si se ha encontrado la ventana.
1336		console.log(`Ventana encontrada:`, !!el);
1337
1338		if (!el)
1339		{
1340			// Informa de que no hay ventana sobre la que actuar.
1341			console.warn(`No existe ninguna ventana con ID:`, id);
1342
1343			// Cierra el grupo de depuración antes de salir.
1344			console.groupEnd();
1345
1346			return;
1347		}
1348
1349		// Comprueba si la ventana está actualmente visible.
1350		console.log(`Estaba visible:`, el.classList.contains('visible'));
1351
1352		if (!el.classList.contains('visible'))
1353		{
1354			// Añade la clase visible para mostrar la ventana.
1355			el.classList.add('visible');
1356
1357			// Bloquea el scroll del body mientras la ventana está abierta.
1358			document.body.style.overflow = 'hidden';
1359
1360			// Informa de que la ventana se ha abierto.
1361			console.log(`Ventana abierta.`);
1362		}
1363		else
1364		{
1365			// Quita la clase visible para ocultar la ventana.
1366			el.classList.remove('visible');
1367
1368			// Restaura el scroll del body.
1369			document.body.style.overflow = '';
1370
1371			// Informa de que la ventana se ha cerrado.
1372			console.log(`Ventana cerrada.`);
1373		}
1374
1375		// Muestra el estado final de la ventana.
1376		console.log(`Visible ahora:`, el.classList.contains('visible'));
1377
1378		// Cierra el grupo de depuración.
1379		console.groupEnd();
1380	}	
1381}

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.