taiage/js/inap-watch.js

107 lines
3.7 KiB
JavaScript

'use strict';
/**
* inap-watch.js
* Comprueba si la página de la convocatoria TAI del INAP ha cambiado
* desde la última visita. Si detecta cambios, muestra un banner en la
* página de inicio.
*
* Estrategia:
* 1. Descarga la página via un proxy CORS (allorigins.win)
* 2. Extrae solo el texto visible (sin scripts/estilos) y normaliza espacios
* 3. Calcula un hash djb2 del texto
* 4. Compara con el hash almacenado en localStorage
* 5. Si difiere → muestra el banner y guarda el nuevo hash
* (el usuario puede silenciarlo con "Lo he visto")
*/
const INAP_URL = 'https://sede.inap.gob.es/es/procedimientos-y-servicios/seleccion/procesos-selectivos-de-cuerpos-y-escalas-generales/cuerpo-de-tecnicos-auxiliares-de-informatica-de-la-administracion-del-estado-ingreso-libre-convocatoria-2025';
const PROXY = 'https://api.allorigins.win/get?url=';
const STORE_KEY = 'inap_tai_hash';
const SEEN_KEY = 'inap_tai_hash_seen';
function djb2(str) {
let h = 5381;
for (let i = 0; i < str.length; i++) {
h = ((h << 5) + h) ^ str.charCodeAt(i);
h = h >>> 0; // mantener 32 bits sin signo
}
return h.toString(16);
}
function extraerTexto(html) {
// Eliminar scripts, estilos, comentarios y atributos dinámicos
return html
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/<[^>]+>/g, ' ') // quitar etiquetas
.replace(/\s+/g, ' ') // normalizar espacios
.trim();
}
function mostrarBanner(urlPagina) {
const banner = document.getElementById('inap-cambio-banner');
if (!banner) return;
banner.style.display = 'flex';
banner.querySelector('.inap-banner-link').href = urlPagina;
}
function ocultarBanner() {
const banner = document.getElementById('inap-cambio-banner');
if (!banner) return;
banner.style.display = 'none';
// Marcar como visto para esta versión del hash
const hash = localStorage.getItem(STORE_KEY);
if (hash) localStorage.setItem(SEEN_KEY, hash);
}
async function comprobarCambios() {
// Si el navegador no tiene fetch o localStorage, salir silenciosamente
if (typeof fetch === 'undefined' || typeof localStorage === 'undefined') return;
const hashPrevio = localStorage.getItem(STORE_KEY);
const hashVisto = localStorage.getItem(SEEN_KEY);
// Si ya hay cambio pendiente que el usuario aún no ha marcado como visto → mostrar banner
if (hashPrevio && hashPrevio !== hashVisto) {
mostrarBanner(INAP_URL);
// Seguir para comprobar si hay cambios adicionales desde entonces
}
try {
const resp = await fetch(PROXY + encodeURIComponent(INAP_URL), { cache: 'no-store' });
if (!resp.ok) return;
const data = await resp.json();
const texto = extraerTexto(data.contents || '');
if (!texto) return;
const hashNuevo = djb2(texto);
if (!hashPrevio) {
// Primera visita: guardar referencia silenciosamente
localStorage.setItem(STORE_KEY, hashNuevo);
localStorage.setItem(SEEN_KEY, hashNuevo);
return;
}
if (hashNuevo !== hashPrevio) {
// La página cambió
localStorage.setItem(STORE_KEY, hashNuevo);
mostrarBanner(INAP_URL);
}
} catch (_) {
// Fallo de red o proxy caído: no hacer nada, sin ruido
}
}
// ── Arranque ───────────────────────────────────────────────────
// Exponer función de cierre al botón del banner
window.cerrarBannerInap = ocultarBanner;
// Ejecutar cuando la página esté lista (sin bloquear el render)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', comprobarCambios);
} else {
comprobarCambios();
}