No es nuestro, pero la tapa
La página de precios del tabaco del Ministerio de Hacienda está programada para que, cada vez que su programa quita el velo de cargando, busque otro velo que, según el propio fichero, otro programa del mismo portal deja puesto encima de la página entera, y lo esconda a la fuerza. El fichero lo cuenta él mismo, en castellano, y a esos velos ajenos los llama por su nombre: rogue, canallas.
- Dominio
- www.hacienda.gob.es
- Guardado
- 09 SEP 2026
- Citas
- 6 en 2 ficheros guardados
- Lectura
- unos 3 min

Un velo de cargando es ese círculo que da vueltas sobre una página gris mientras el servidor, el ordenador del ministerio, contesta. Se pone al pulsar un botón y se quita cuando llega la respuesta. Este fichero tiene el suyo, con su nombre propio, y lo maneja con cuidado: solo lo enseña al pulsar los dos botones del filtro, Restaurar valores y Consultar precios, y lo quita en cuanto aparece la tabla de precios. Hasta aquí, trabajo bien hecho.

- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 52,9 kB
- sha256
- 623ef574d0ba…
| 550 | <input form="aspnetForm" id="filtrarSin" type="submit" name="filtrarSin" value="Consultar precios" onclick="javascript: _spFormOnSubmitCalled = false;"> |
| 551 | </div> |
| 552 | </div> |
| 553 | <p class="exportPDF"> |
| 554 | <input form="aspnetForm" id="exportToPdf" type="submit" name="exportToPdf" value="Exportar a PDF" onclick="javascript: _spFormOnSubmitCalled = false;"> |
| 555 | <input form="aspnetForm" id="exportToExcel" type="submit" name="exportToExcel" value="Exportar a Excel" onclick="javascript: _spFormOnSubmitCalled = false;"> |
| 556 | <input form="aspnetForm" id="exportToCSV" type="submit" value="Exportar a CSV" name="exportToCsv" onclick="javascript: _spFormOnSubmitCalled = false;"> |
| 557 | </p> |
| 558 | |
| 559 | <script src="/ |
| 560 | <script src="/ |
| 561 | …src="/ |
| 562 | |
| 563 | </div><div class=" |
| 564 | </div><div class=" |
| 565 | </div><div id=" |
| 566 | <div class="ms-webpart-chrome ms-webpart-chrome-vertical ms-webpart-chrome-fullWidth "> |
| 567 | <div WebPartID=" |
| 568 | |
| 569 | </div><div class=" |
| 570 | </div><div class=" |
| 571 | </div><div id=" |
| 572 | <div class="ms-webpart-chrome ms-webpart-chrome-vertical ms-webpart-chrome-fullWidth "> |
Lo raro está en la cabecera del fichero, donde el autor resume lo que hace su programa. El tercer punto de la lista dice que neutraliza el velo genérico de otro programa, el paquete de Suscripciones, que se queda huérfano en la cabecera del portal y tapa toda la página. El fichero no dice cuál es. En la página hay uno de suscripciones: es el que pinta el botón Suscribirse de la columna de la derecha, y su ventana de suscripción pone, mientras carga, un recuadro llamado preloader con otro dentro llamado loader.
- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 20,8 kB
- sha256
- 0ea13a51c07c…
| 1 | /* ============================================================================ |
| 2 | * MINHAC.SP. |
| 3 | * |
| 4 | * Lógica de cliente de la página "Precios de Labores": |
| 5 | * - Overlay #preloaderTabacos SOLO al pulsar #reset o #filtrarSin. |
| 6 | * - Ocultado garantizado en TODOS los escenarios SharePoint: |
| 7 | * a) Recarga completa de página -> window.load / pageshow |
| 8 | * b) Postback parcial (UpdatePanel) -> Page |
| 9 | * - Neutraliza el overlay genérico #preloader del bundle React de |
| 10 | * Suscripciones (loader.jsx) que se queda huérfano en la cabecera |
| 11 | * del portal y tapa toda la página. |
| 12 | * - Subtítulo dinámico en <h1 class="titulo">. |
| 13 | * - Notificación SP.UI.Notify al marcar "Sólo modificadas". |
| 14 | * - Accesibilidad: Enter/Espacio sobre <label class="choice">. |
| 15 | * |
| 16 | * Sin dependencia de jQuery (la versión que carga SharePoint no tiene .on()). |
| 17 | * ========================================================================== */ |
| 18 | (function () { |
| 19 | 'use strict'; |
| 20 | |
| 21 | var MODULE = '[TabacosFiltroPrecios]'; |
Unas líneas más abajo está la lista de lo que hay que esconder, con un comentario que no necesita traducción. Dice que el velo lo inyecta el paquete de Suscripciones, que no es nuestro, pero se queda colgado en esta página y la tapa por completo, así que lo forzamos a desaparecer. La lista se llama ROGUE_PRELOADER_IDS. Rogue, en inglés, es canalla, rebelde, el que va por libre. Dos nombres dentro: preloader y loader, los mismos del programa de suscripciones.
- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 20,8 kB
- sha256
- 0ea13a51c07c…
| 17 | * ========================================================================== */ |
| 18 | (function () { |
| 19 | 'use strict'; |
| 20 | |
| 21 | var MODULE = '[TabacosFiltroPrecios]'; |
| 22 | var PRELOADER_ID = 'preloaderTabacos'; |
| 23 | var LOADER_ID = 'loaderTabacos'; |
| 24 | |
| 25 | // ID del overlay genérico inyectado por el bundle React de Suscripciones |
| 26 | // (loader.jsx). No es nuestro, pero se queda colgado en esta página y la |
| 27 | // tapa por completo, así que lo forzamos a display:none. |
| 28 | var ROGUE_PRELOADER_IDS = ['preloader', 'loader']; |
| 29 | |
| 30 | // El usuario indica que la página tarda como mucho 3 s. Usamos 5 s de |
| 31 | // margen como salvavidas: si por cualquier razón ni endRequest ni load |
| 32 | // se disparan, el overlay se quitará igualmente. |
| 33 | var SAFETY_MS = 5000; |
| 34 | |
| 35 | // SOLO los dos botones del formulario de filtro. |
| 36 | // 'input[name="filtrar"]' cubre el caso tras renombrar #filtrarSin. |
| 37 | var TRIGGERS_SELECTOR = '#reset, #filtrarSin, input[name="filtrar"]'; |
| 38 | |
| 39 | // Selectores de los nodos que el XSLT siempre renderiza al actualizar |
Y la función que lo hace se llama esconder los velos canallas. Recorre la lista, busca cada uno en la página y, si está, le ordena no mostrarse, con la marca de importante para que ninguna hoja de estilo pueda contradecirla. Se llama cada vez que el programa quita su propio velo. Además, durante los quince segundos siguientes a la carga, el programa vigila cada cambio de la página y la llama otra vez, porque, dice su comentario, el paquete puede inyectar su velo más tarde.
- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 20,8 kB
- sha256
- 0ea13a51c07c…
| 85 | var inner = document. |
| 86 | inner.id = LOADER_ID; |
| 87 | p.appendChild(inner); |
| 88 | (document.body || document. |
| 89 | } |
| 90 | return p; |
| 91 | } |
| 92 | |
| 93 | // Oculta cualquier overlay genérico #preloader que algún otro componente |
| 94 | // (ej. bundle React de Suscripciones) haya dejado huérfano y esté |
| 95 | // tapando la página. |
| 96 | function hideRoguePreloaders() { |
| 97 | for (var i = 0; i < ROGUE_PRELOADER_IDS.length; i++) { |
| 98 | var el = document. |
| 99 | if (el) { |
| 100 | el. |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | function showPreloader() { |
| 106 | var p = ensurePreloader(); |
| 107 | p.style.display = 'block'; |
Hay un segundo aviso de la misma clase. El fichero tiene un salvavidas de cinco segundos: si por cualquier razón nadie quita el velo, se quita solo. El comentario explica de dónde sale el número: el usuario indica que la página tarda como mucho tres segundos, y se dejan cinco de margen. El salvavidas se arma cada vez que alguien pulsa uno de los dos botones del filtro, y al saltar quita el velo propio y, de paso, los ajenos.
- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 20,8 kB
- sha256
- 0ea13a51c07c…
| 22 | var PRELOADER_ID = 'preloaderTabacos'; |
| 23 | var LOADER_ID = 'loaderTabacos'; |
| 24 | |
| 25 | // ID del overlay genérico inyectado por el bundle React de Suscripciones |
| 26 | // (loader.jsx). No es nuestro, pero se queda colgado en esta página y la |
| 27 | // tapa por completo, así que lo forzamos a display:none. |
| 28 | var ROGUE_PRELOADER_IDS = ['preloader', 'loader']; |
| 29 | |
| 30 | // El usuario indica que la página tarda como mucho 3 s. Usamos 5 s de |
| 31 | // margen como salvavidas: si por cualquier razón ni endRequest ni load |
| 32 | // se disparan, el overlay se quitará igualmente. |
| 33 | var SAFETY_MS = 5000; |
| 34 | |
| 35 | // SOLO los dos botones del formulario de filtro. |
| 36 | // 'input[name="filtrar"]' cubre el caso tras renombrar #filtrarSin. |
| 37 | var TRIGGERS_SELECTOR = '#reset, #filtrarSin, input[name="filtrar"]'; |
| 38 | |
| 39 | // Selectores de los nodos que el XSLT siempre renderiza al actualizar |
| 40 | // la tabla de resultados. En cuanto aparezcan en el DOM podemos ocultar |
| 41 | // el spinner, sin esperar a que otros UpdatePanels de la página terminen |
| 42 | // (componentes laterales, etc.). |
| 43 | var RESULTS_SELECTOR = '.tablaResultadosTabacos, .paginacionTabacos'; |
Y un tercero, más pequeño. La cabecera avisa de que el programa no depende de jQuery, una biblioteca de funciones para manejar páginas web, porque «la versión que carga SharePoint no tiene .on()», la función que asocia acciones a los clics. SharePoint es el programa de Microsoft sobre el que está hecho el portal. En la página, dos líneas antes del programa del filtro, se carga jQuery 1.4.2, de 2010 según su cabecera, con un aviso de error a nombre del propio filtro. La 1.4.2 no tiene .on(): llegó en la 1.7. Más arriba, la página ya había cargado dos veces la 3.6.0.
- Copia
- archivo
- Guardado
- 9 sep 2026
- Peso
- 20,8 kB
- sha256
- 0ea13a51c07c…
| 5 | * - Overlay #preloaderTabacos SOLO al pulsar #reset o #filtrarSin. |
| 6 | * - Ocultado garantizado en TODOS los escenarios SharePoint: |
| 7 | * a) Recarga completa de página -> window.load / pageshow |
| 8 | * b) Postback parcial (UpdatePanel) -> Page |
| 9 | * - Neutraliza el overlay genérico #preloader del bundle React de |
| 10 | * Suscripciones (loader.jsx) que se queda huérfano en la cabecera |
| 11 | * del portal y tapa toda la página. |
| 12 | * - Subtítulo dinámico en <h1 class="titulo">. |
| 13 | * - Notificación SP.UI.Notify al marcar "Sólo modificadas". |
| 14 | * - Accesibilidad: Enter/Espacio sobre <label class="choice">. |
| 15 | * |
| 16 | * Sin dependencia de jQuery (la versión que carga SharePoint no tiene .on()). |
| 17 | * ========================================================================== */ |
| 18 | (function () { |
| 19 | 'use strict'; |
| 20 | |
| 21 | var MODULE = '[TabacosFiltroPrecios]'; |
| 22 | var PRELOADER_ID = 'preloaderTabacos'; |
| 23 | var LOADER_ID = 'loaderTabacos'; |
| 24 | |
| 25 | // ID del overlay genérico inyectado por el bundle React de Suscripciones |
| 26 | // (loader.jsx). No es nuestro, pero se queda colgado en esta página y la |
| 27 | // tapa por completo, así que lo forzamos a display:none. |
jQuery, porque la que trae SharePoint es demasiado vieja para lo que hacía falta.Nada de esto se ve en la página. El 28 de septiembre la abrimos en nuestro navegador sin pulsar nada, primero tal cual y después con el programa del filtro bloqueado, y en las dos ocasiones cargó sin velo: no había ningún recuadro llamado preloader. Cuándo aparece, el fichero no lo dice; dice que se queda colgado. Lo que queda escrito es la solución que se le dio en esta página: un programa que lo busca y lo esconde, con su lista de nombres y su etiqueta de canalla.
El 28 de septiembre el fichero seguía igual, byte a byte, y su lista seguía llamando canallas a dos velos que ese día, en nuestro navegador, no aparecieron.
- El fichero MINHAC.SP.Portal.Tabacos.FiltroPrecios.js de www.hacienda.gob.es, guardado el 9 de septiembre de 2026 con 20.808 bytes y servido igual, con la misma huella, el 28 de septiembre, declara en su cabecera que neutraliza el overlay genérico #preloader del bundle React de Suscripciones (loader.jsx).
- Define la lista ROGUE_PRELOADER_IDS con los valores 'preloader' y 'loader' bajo un comentario que dice “No es nuestro, pero se queda colgado en esta página y la tapa por completo, así que lo forzamos a display:none”.
- La función
hideRoguePreloadersles aplica display none con !important, se llama desdehidePreloadery, durante 15 segundos tras la carga, desde un vigilante de cambios de la página. - SAFETY_MS vale 5000, se arma al pulsar los botones del filtro y lleva el comentario de que el usuario indica que la página tarda como mucho 3 s.
- La cabecera declara que no depende de
jQueryporque la versión que carga SharePoint no tiene .on(). - La página, guardada el 9 de septiembre y pedida el 28, carga dos líneas antes de ese programa el fichero js/libs/jquery.min.js, que es
jQuery1.4.2 (2010, según su cabecera), con un aviso de error firmado [TabacosFiltroPrecios], y antes jquery-3.6.0.js y jquery-3.6.0.min.js. - La página carga también minhac-app-subscriptionsbell.js, que pinta el botón Suscribirse en el hueco
subscribeButtony cuya ventana de suscripción pinta, mientras carga, un div preloader con un div loader dentro (el fichero, el 28 de septiembre, igual que el guardado). - El 28 de septiembre, en nuestro navegador, sin pulsar nada y esperando 16 segundos, la página no tenía ningún elemento preloader, ni con el programa del filtro ni con él bloqueado.
- Las cuatro versiones de idioma de precioslabores.aspx cargan el programa del filtro el 9 y el 28 de septiembre.
- No decimos que el velo ajeno llegue a verse en esa página: el 28 de septiembre, sin pulsar nada, no apareció, y no hemos abierto la ventana de suscripción para ver si al cerrarla se queda.
- No decimos quién escribió el fichero ni quién es el usuario que indicó los tres segundos, ni por qué la página carga
jQuery1.4.2 ni a qué versión se refiere el comentario. - No decimos que el paquete de Suscripciones funcione mal en el resto del portal: solo que este fichero dice que deja su velo en esta página.
De dónde sale cada cita
Cada trozo de código de esta ficha está en un fichero guardado tal como lo entregó el servidor, con su huella (sha256) y su hora. Las citas entre «» se comprueban por programa contra ese fichero antes de publicar.
- https://www.hacienda.gob.es/es-es/areas%20tematicas/cmtabacos/paginas/precioslabores.aspx9 de septiembre de 2026, 02:09archivosha256 623ef574d0ba21ae56b05fcc7188fd657270f111fff4817bfa57d9e2b94dea7a
- https://www.hacienda.gob.es/Style%20Library/MINHAC.SP.Portal/Tabacos/js/MINHAC.SP.Portal.Tabacos.FiltroPrecios.js?v=59 de septiembre de 2026, 02:09archivosha256 0ea13a51c07c972d21cc6992a0b89d87e300d98678593eee29f0638400abe6dd