hbox-docs/docs/js/incidencias.js

194 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Problemas del sitio (27-oct-2026) — al pie de TODAS las páginas (las de MkDocs por extra_javascript y las
sueltas: actas, Mi cuenta, administración, expediente, con <script src="/js/incidencias.js" defer>).
- Sección «Problemas del sitio»: reportar lo que falla en esta página (POST /api/incidencias) y ver cuántas hay
abiertas (GET /api/incidencias/resumen) con liga al tablero /tableros/incidencias/.
- Errores de JavaScript y llamadas a la API que fallan (5xx) se mandan solos a /api/incidencias/error (máximo 5
por página); el vigía del servidor los junta y abre la incidencia si se repiten.
- Si la sesión terminó (una API responde 401), franja arriba con la liga a la página de entrada.
Sin servidor, la página queda igual. Estilos aquí mismo para que sirva también en las páginas sueltas. */
(function () {
if (window.__hbIncidencias) return;
window.__hbIncidencias = true;
var errores = [], llamadas = [], enviados = 0, MAX_ENVIOS = 5, avisoSesion = false;
var fetchOriginal = window.fetch ? window.fetch.bind(window) : null;
function ruta() { return location.pathname + location.search; }
function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }
function ahoraLocal() { try { return new Date().toLocaleString('es-MX'); } catch (e) { return String(new Date()); } }
function mandarError(reg) {
if (!fetchOriginal || enviados >= MAX_ENVIOS) return;
enviados++;
reg.pagina = ruta();
fetchOriginal('/api/incidencias/error', {
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(reg)
}).catch(function () {});
}
// ---- errores de JavaScript
window.addEventListener('error', function (e) {
if (!e || !e.message) return;
var r = { tipo: 'js', mensaje: String(e.message).slice(0, 300), fuente: (e.filename || '').replace(location.origin, ''),
linea: e.lineno || 0, pila: e.error && e.error.stack ? String(e.error.stack).slice(0, 1000) : '' };
errores.push({ mensaje: r.mensaje, fuente: r.fuente + ':' + r.linea, cuando: ahoraLocal() });
if (errores.length > 10) errores.shift();
mandarError(r);
});
window.addEventListener('unhandledrejection', function (e) {
var m = e && e.reason ? (e.reason.message || String(e.reason)) : 'promesa rechazada';
if (/Failed to fetch|NetworkError|Load failed|AbortError/i.test(m)) return; // sin red: no es un error del sitio
errores.push({ mensaje: String(m).slice(0, 300), fuente: 'promesa', cuando: ahoraLocal() });
if (errores.length > 10) errores.shift();
mandarError({ tipo: 'promesa', mensaje: String(m).slice(0, 300), pila: e.reason && e.reason.stack ? String(e.reason.stack).slice(0, 1000) : '' });
});
// ---- llamadas a la API que fallan (y sesión terminada)
if (fetchOriginal) {
window.fetch = function (entrada, opciones) {
var url = typeof entrada === 'string' ? entrada : (entrada && entrada.url) || '';
var metodo = (opciones && opciones.method) || (entrada && entrada.method) || 'GET';
return fetchOriginal(entrada, opciones).then(function (r) {
try {
var u = new URL(url, location.href);
if (u.origin === location.origin && u.pathname.indexOf('/api/') === 0 && r.status >= 400
&& u.pathname !== '/api/incidencias/error') {
llamadas.push({ url: u.pathname, metodo: metodo, estado: String(r.status), cuando: ahoraLocal() });
if (llamadas.length > 10) llamadas.shift();
if (r.status === 401) sesionTerminada();
else if (r.status >= 500) mandarError({ tipo: 'api', mensaje: metodo + ' ' + u.pathname + ' → ' + r.status, url_api: u.pathname, estado_http: r.status });
}
} catch (e) {}
return r;
});
};
}
function sesionTerminada() {
if (avisoSesion) return;
avisoSesion = true;
var d = document.createElement('div');
d.className = 'hg-sesion-fin'; d.setAttribute('role', 'alert');
d.innerHTML = '<b>Tu sesión terminó</b> (cambió tu contraseña o pasaron 30 días). '
+ '<a href="/entrar.html#' + encodeURI(ruta()) + '">Entrar de nuevo</a> — lo que no hayas guardado sigue en esta pestaña.';
document.body.insertBefore(d, document.body.firstChild);
}
// ---- estilos (tokens de stylesheets/helgen.css, que cargan todas las páginas)
var css = ''
+ '.hg-inc{margin:40px 0 8px;padding:16px 18px;border:1px solid var(--hg-linea);border-radius:12px;background:var(--hg-plano-2);'
+ 'font-family:var(--hg-fuente);font-size:14.5px;line-height:1.45;color:var(--hg-texto)}'
+ '.hg-inc *{box-sizing:border-box}'
+ '.hg-inc__fila{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;justify-content:space-between}'
+ '.hg-inc h2.hg-inc__t{margin:0!important;font-family:var(--hg-fuente-titulo);font-weight:600;font-size:17px!important;line-height:1.2;color:var(--hg-texto-fuerte);border:0;padding:0}'
+ '.hg-inc p.hg-inc__s{margin:3px 0 0!important;color:var(--hg-texto-2);font-size:13.5px}'
+ '.hg-inc__acc{display:flex;flex-wrap:wrap;gap:8px;align-items:center}'
+ '.hg-inc button,.hg-inc a.hg-inc__b{display:inline-flex;align-items:center;gap:6px;font:inherit;font-size:14px;font-weight:600;padding:8px 13px;'
+ 'border-radius:9px;border:1px solid var(--hg-linea-2);background:transparent;color:var(--hg-liga);cursor:pointer;text-decoration:none;line-height:1.2}'
+ '.hg-inc button:hover,.hg-inc a.hg-inc__b:hover{background:var(--hg-elevado)}'
+ '.hg-inc button.hg-inc__p{background:var(--hg-acento);border-color:var(--hg-acento);color:var(--hg-sobre-acento)}'
+ '.hg-inc button.hg-inc__p:hover{filter:brightness(1.06);background:var(--hg-acento)}'
+ '.hg-inc__n{display:inline-block;min-width:20px;padding:0 6px;border-radius:10px;background:var(--hg-aviso-bg);color:var(--hg-aviso);font-size:12px;text-align:center}'
+ '.hg-inc__ult{margin:10px 0 0;padding:0;list-style:none;font-size:13.5px;color:var(--hg-texto-2)}'
+ '.hg-inc__ult li{margin:3px 0!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}'
+ '.hg-inc__ult a{color:var(--hg-liga)}'
+ '.hg-inc form{margin:14px 0 0;border-top:1px solid var(--hg-linea);padding-top:14px}'
+ '.hg-inc label{display:block;font-weight:600;font-size:13px;margin:10px 0 5px;color:var(--hg-texto-2)}'
+ '.hg-inc__tipos{display:flex;flex-wrap:wrap;gap:6px}'
+ '.hg-inc__tipos label{display:inline-flex;align-items:center;gap:6px;margin:0;font-weight:500;font-size:13.5px;color:var(--hg-texto);'
+ 'padding:6px 11px;border:1px solid var(--hg-linea-2);border-radius:20px;cursor:pointer}'
+ '.hg-inc__tipos input{accent-color:var(--hg-acento);margin:0}'
+ '.hg-inc__tipos label:has(input:checked){border-color:var(--hg-acento);background:var(--hg-acento-suave)}'
+ '.hg-inc textarea{width:100%;min-height:90px;padding:10px 12px;border:1px solid var(--hg-linea-2);border-radius:9px;font:inherit;font-size:15px;'
+ 'background:var(--hg-plano);color:var(--hg-texto);resize:vertical}'
+ '.hg-inc textarea:focus{outline:2px solid var(--hg-foco);outline-offset:1px;border-color:transparent}'
+ '.hg-inc__tec{display:flex!important;align-items:center;gap:8px;font-weight:500!important;color:var(--hg-texto-2)!important}'
+ '.hg-inc__tec input{accent-color:var(--hg-acento);margin:0}'
+ '.hg-inc__fin{display:flex;gap:8px;margin-top:12px}'
+ '.hg-inc__msg{display:none;margin:10px 0 0;padding:9px 12px;border-radius:9px;font-size:14px}'
+ '.hg-inc__msg.ok{display:block;background:var(--hg-hecho-bg);border:1px solid color-mix(in srgb,var(--hg-hecho) 40%,transparent)}'
+ '.hg-inc__msg.err{display:block;background:var(--hg-bloqueado-bg);border:1px solid color-mix(in srgb,var(--hg-bloqueado) 45%,transparent)}'
+ '.hg-sesion-fin{background:var(--hg-bloqueado-bg);color:var(--hg-texto-fuerte);font-size:14px;padding:9px 16px;text-align:center;'
+ 'border-bottom:1px solid color-mix(in srgb,var(--hg-bloqueado) 45%,transparent);position:sticky;top:0;z-index:10}'
+ '.hg-sesion-fin a{color:var(--hg-bloqueado);font-weight:700}'
+ '@media print{.hg-inc,.hg-sesion-fin{display:none}}';
var TIPOS = [['error', 'Error o pantalla rota'], ['datos', 'Datos incorrectos'], ['lento', 'Lento'],
['acceso', 'Permisos o acceso'], ['sugerencia', 'Sugerencia'], ['otro', 'Otro']];
function montar() {
if (document.querySelector('.hg-inc')) return;
var st = document.createElement('style'); st.textContent = css; document.head.appendChild(st);
var s = document.createElement('section');
s.className = 'hg-inc'; s.id = 'problemas-del-sitio'; s.setAttribute('aria-labelledby', 'hg-inc-t');
s.innerHTML = ''
+ '<div class="hg-inc__fila"><div><h2 class="hg-inc__t" id="hg-inc-t">Problemas del sitio</h2>'
+ '<p class="hg-inc__s">¿Algo no funciona en esta página? Repórtalo: se revisa todos los días y te contestamos ahí mismo.</p></div>'
+ '<div class="hg-inc__acc"><button type="button" class="hg-inc__p" id="hg-inc-abrir">Reportar un problema</button>'
+ '<a class="hg-inc__b" href="/tableros/incidencias/">Incidencias <span class="hg-inc__n" id="hg-inc-n">…</span></a></div></div>'
+ '<ul class="hg-inc__ult" id="hg-inc-ult"></ul>'
+ '<form id="hg-inc-f" hidden novalidate>'
+ '<label>Qué pasa</label><div class="hg-inc__tipos">'
+ TIPOS.map(function (t, i) { return '<label><input type="radio" name="hg-inc-tipo" value="' + t[0] + '"' + (i === 0 ? ' checked' : '') + '> ' + t[1] + '</label>'; }).join('')
+ '</div><label for="hg-inc-d">Cuéntanos con tus palabras: qué hiciste, qué esperabas y qué pasó</label>'
+ '<textarea id="hg-inc-d" maxlength="4000"></textarea>'
+ '<label class="hg-inc__tec"><input type="checkbox" id="hg-inc-c" checked> Incluir los datos técnicos de esta página (dirección, navegador y errores recientes)</label>'
+ '<div class="hg-inc__fin"><button type="submit" class="hg-inc__p" id="hg-inc-e">Enviar reporte</button>'
+ '<button type="button" id="hg-inc-x">Cancelar</button></div>'
+ '<div class="hg-inc__msg" id="hg-inc-m" role="status" aria-live="polite"></div></form>';
var donde = document.querySelector('.md-content__inner') || document.querySelector('.wrap') || document.querySelector('main') || document.body;
donde.appendChild(s);
var f = s.querySelector('#hg-inc-f'), msg = s.querySelector('#hg-inc-m');
s.querySelector('#hg-inc-abrir').addEventListener('click', function () {
f.hidden = !f.hidden; if (!f.hidden) s.querySelector('#hg-inc-d').focus();
});
s.querySelector('#hg-inc-x').addEventListener('click', function () { f.hidden = true; });
f.addEventListener('submit', function (ev) {
ev.preventDefault();
var d = s.querySelector('#hg-inc-d').value.trim();
if (d.length < 5) { msg.className = 'hg-inc__msg err'; msg.textContent = 'Cuéntanos qué pasó (aunque sea una línea).'; return; }
var tipo = (s.querySelector('input[name="hg-inc-tipo"]:checked') || {}).value || 'otro';
var cuerpo = { descripcion: d, tipo: tipo, pagina: ruta() };
if (s.querySelector('#hg-inc-c').checked) {
cuerpo.contexto = { pantalla: innerWidth + '×' + innerHeight, idioma: navigator.language, referer: document.referrer,
hora_local: ahoraLocal(), errores: errores.slice(-10), llamadas: llamadas.slice(-10) };
}
var b = s.querySelector('#hg-inc-e'); b.disabled = true; b.textContent = 'Enviando…';
(fetchOriginal || fetch)('/api/incidencias', {
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cuerpo)
}).then(function (r) { return r.json().catch(function () { return { ok: false, error: 'HTTP ' + r.status }; }); })
.then(function (j) {
if (j && j.ok) {
msg.className = 'hg-inc__msg ok';
msg.innerHTML = '<b>Recibido: <a href="/tableros/incidencias/?id=' + esc(j.id) + '">' + esc(j.id) + '</a>.</b> '
+ 'Ahí ves cómo va y te contestamos. Gracias.';
s.querySelector('#hg-inc-d').value = '';
resumen();
} else { msg.className = 'hg-inc__msg err'; msg.textContent = (j && j.error) || 'No se pudo enviar.'; }
})
.catch(function () { msg.className = 'hg-inc__msg err'; msg.textContent = 'No pude contactar al servidor. Intenta otra vez en un momento.'; })
.then(function () { b.disabled = false; b.textContent = 'Enviar reporte'; });
});
resumen();
}
function resumen() {
(fetchOriginal || fetch)('/api/incidencias/resumen', { cache: 'no-store', credentials: 'same-origin' })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (j) {
var n = document.getElementById('hg-inc-n'), ul = document.getElementById('hg-inc-ult');
if (!n) return;
if (!j || !j.ok) { n.textContent = '—'; return; }
n.textContent = j.abiertas;
n.title = j.abiertas + ' abiertas' + (j.mias ? ' · ' + j.mias + ' tuyas' : '');
ul.innerHTML = (j.ultimas || []).map(function (x) {
return '<li><a href="/tableros/incidencias/?id=' + esc(x.id) + '">' + esc(x.id) + '</a> · ' + esc(x.titulo) + '</li>';
}).join('');
})
.catch(function () {});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', montar);
else montar();
})();