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.