/**
 * Botón que muestra/oculta contraseña
 * MARK: mostrarContrasena()
 */
function mostrarContrasena() {
  var tipo = document.getElementById("contraseña");
  var ojo = document.getElementById("mostrarcontra");
  if (tipo.type == "password") {
    tipo.type = "text";
    ojo.innerHTML = '<span id="iconojo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M288 80c-65.2 0-118.8 29.6-159.9 67.7C89.6 183.5 63 226 49.4 256c13.6 30 40.2 72.5 78.6 108.3C169.2 402.4 222.8 432 288 432s118.8-29.6 159.9-67.7C486.4 328.5 513 286 526.6 256c-13.6-30-40.2-72.5-78.6-108.3C406.8 109.6 353.2 80 288 80zM95.4 112.6C142.5 68.8 207.2 32 288 32s145.5 36.8 192.6 80.6c46.8 43.5 78.1 95.4 93 131.1c3.3 7.9 3.3 16.7 0 24.6c-14.9 35.7-46.2 87.7-93 131.1C433.5 443.2 368.8 480 288 480s-145.5-36.8-192.6-80.6C48.6 356 17.3 304 2.5 268.3c-3.3-7.9-3.3-16.7 0-24.6C17.3 208 48.6 156 95.4 112.6zM288 336c44.2 0 80-35.8 80-80s-35.8-80-80-80c-.7 0-1.3 0-2 0c1.3 5.1 2 10.5 2 16c0 35.3-28.7 64-64 64c-5.5 0-10.9-.7-16-2c0 .7 0 1.3 0 2c0 44.2 35.8 80 80 80zm0-208a128 128 0 1 1 0 256 128 128 0 1 1 0-256z"/></svg></span>';
    ojo.classList.remove('nomostrar');
    ojo.classList.add("mostrar");
  } else {
    tipo.type = "password";
    ojo.innerHTML = '<span id="iconojo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c8.4-19.3 10.6-41.4 4.8-63.3c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zM373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5L373 389.9z"/></svg></span>';
    ojo.classList.remove('mostrar');
    ojo.classList.add("nomostrar");

  }
}
/*******************************************************************************
 *                                    [dialog]                                 *
 *******************************************************************************/
/**
 * @param {*} sing Nombre de la página
 * MARK: cargar_eliminar()
 */
function cargar_eliminar(sing) {
  // Un único botón por ID
  const elemento = document.getElementById('eliminar');
  if (!elemento) return;

  elemento.addEventListener('click', function (event) {
    event.preventDefault();
    // Id desde value o data-id (como lo tuvieses)
    const id = elemento.value || elemento.dataset.id || '';
    let data = 'id=' + encodeURIComponent(id);
    let ocultar = document.getElementById('no_mostrar_aprobaciones');
    if (sing == 'registro' && ocultar && ocultar.checked) {
      data += '&no_mostrar_aprobaciones=true';
    }
    ajax('../../app/ajax/delete/delete_' + sing, 'notificaciones', data, ['cargar_ajax_table()', 'animacion_y_cierre_dialog()']);
  });
}



/**
 * Cierra los dialog
 * MARK: cerrar_dialog()
 */
function cerrar_dialog() {
  let cancelar = document.getElementById("cancelar");
  if (cancelar) {
    cancelar.addEventListener("click", function () {
      animacion_y_cierre_dialog();
    });
  }
  let cerrar = document.getElementById("cerrar");
  if (cerrar) {
    cerrar.addEventListener("click", function () {
      animacion_y_cierre_dialog();
    });
  }
}
/**
 * Animación de cierre dialog y cierra
 * MARK: animacion_y_cierre_dialog()
 */
function animacion_y_cierre_dialog() {
  let dialog = document.getElementById("dialog");
  let showdialog = document.getElementById("showdialog");
  dialog.classList.add('desaparecer-abajo');
  showdialog.classList.add('desaparecer');
  setTimeout(() => {
    dialog.remove();
    showdialog.classList.remove("desaparecer");
    showdialog.style.display = "none";
  }, 200);
}

/**
 * Carga dialog
 * MARK: cargar_dialog()
 */
function cargar_dialog() {
  let showdialog = document.getElementById('showdialog');
  showdialog.style.display = "flex";
  cerrar_dialog();
}
/*******************************************************************************
 *                                     [ajax]                                  *
 *******************************************************************************/
/**
 * Hace una llamada ajax a un archivo V2
 * MARK: ajax()
 * @param string nombrearchivo Ruta del archivo
 * @param string idresultado id donde irá el resultado ajax
 * @param string parametros parametros para recoger el archivo donde llama
 * @param array funcionesArray Array de nombres de funciones a ejecutar después de la llamada AJAX
 * @return void
 */
function ajax(nombrearchivo, idresultado, parametros, funcionesArray) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', SITIO_CARPETA + '/app/ajax/' + nombrearchivo, true);
  xhr.onload = function () {
    if (xhr.status >= 200 && xhr.status < 300) {
      if (idresultado !== '') {
        const el = document.getElementById(idresultado);
        el.innerHTML = xhr.responseText;
        // Evaluar scripts estáticos devueltos por PHP
        Array.from(el.querySelectorAll("script")).forEach(script => {
          const newScript = document.createElement("script");
          Array.from(script.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value));
          newScript.textContent = script.textContent;
          document.head.appendChild(newScript).parentNode.removeChild(newScript);
        });
      }
      // Itera sobre el array de cadenas de llamadas a funciones y las ejecuta
      if (Array.isArray(funcionesArray)) {
        funcionesArray.forEach(funcionCadena => {
          // Extraer el nombre de la función y los parámetros de la cadena
          var match = funcionCadena.match(/^(\w+)\((.*)\)$/);
          if (match) {
            var funcionNombre = match[1];
            var parametros = match[2].split(',').map(param => param.trim().replace(/['"]/g, ''));

            if (typeof window[funcionNombre] === "function") {
              window[funcionNombre](...parametros);
            } else {
              console.error(`La función ${funcionNombre} no existe.`);
            }
          } else {
            console.error(`Formato de cadena de función inválido: ${funcionCadena}`);
          }
        });
      }
    } else if (xhr.status === 401 || xhr.status === 440) {
      window.location.href = SITIO_CARPETA + '/iniciar_sesion?tipo=error&mensaje=Sesión caducada';
    } else {
      console.error('Error al cargar el archivo: ' + xhr.status);
    }
  };
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  const csrfMeta = document.querySelector('meta[name="csrf-token"]');
  if (csrfMeta) {
    xhr.setRequestHeader("X-CSRF-Token", csrfMeta.content);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    
    let csrfParam = 'csrf_token=' + encodeURIComponent(csrfMeta.content);
    if (parametros) {
        parametros += '&' + csrfParam;
    } else {
        parametros = csrfParam;
    }
  }
  if (parametros !== '') {
    var data = parametros;
    xhr.send(data);
  } else {
    xhr.send();
  }
}
/**
 * Quita  cargando
 * MARK: quitar_cargando()
 */
function quitar_cargando() {
  var cargando = document.getElementById('cargando');
  cargando.innerHTML = "";
}
/**
 * Pone el icono de cargando
 * MARK: poner_cargando()
 */
function poner_cargando() {
  var cargando = document.getElementById('cargando');
  cargando.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="2rem" height="2rem" viewBox="0 0 512 512"><path d="M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z"/></svg>';
}

/**
 * Crear notificación
 * MARK: crear_notificacion()
 */
function crear_notificacion(tipo, mensaje) {
  const contenedor = document.getElementById('notificaciones');
  if (!contenedor) return;

  // Si ya existe una notificación con ese id, la quitamos para no duplicar IDs
  const previa = contenedor.querySelector('#notificacion');
  if (previa) previa.remove();

  // Contenedor <aside>
  const aside = document.createElement('aside');
  aside.id = 'notificacion';

  // Icono
  const ico = document.createElement('span');
  ico.id = 'noti-ico';

  // Mensaje
  const texto = document.createElement('span');
  texto.id = 'noti-mensaje';
  texto.textContent = mensaje;

  // SVGs por tipo (reutilizo los tuyos)
  const SVGs = {
    correcto: `
      <svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512">
        <path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 
        9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 
        9.4 24.6 9.4 33.9 0L369 209z"></path>
      </svg>
    `,
    error: `
      <svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512">
        <path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 
        0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 
        0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 
        0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
      </svg>
    `,
    incidencia: `
      <svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 192 512">
        <path d="M48 80a48 48 0 1 1 96 0A48 48 0 1 1 48 80zM0 224c0-17.7 14.3-32 
        32-32H96c17.7 0 32 14.3 32 32V448h32c17.7 0 32 14.3 32 32s-14.3 32-32 
        32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H64V256H32c-17.7 0-32-14.3-32-32z"></path>
      </svg>
    `
  };

  // Normalizamos 'fallo' como 'error' y 'incidencia1' como 'incidencia'
  if (tipo === 'fallo') tipo = 'error';
  if (tipo === 'incidencia1') tipo = 'incidencia';

  // Clase del icono según tipo (ajústalas a tu CSS)
  const clasePorTipo = {
    correcto: 'noti-ok',
    error: 'noti-error',
    incidencia: 'noti-info'
  };

  ico.className = clasePorTipo[tipo] || 'noti-info';
  ico.innerHTML = SVGs[tipo] || SVGs['incidencia'];

  // Montaje
  aside.appendChild(ico);
  aside.appendChild(texto);
  contenedor.appendChild(aside);
}

/**
 * buscador()
 * MARK:buscador()
 * @requires buscador - id del buscador
 * @requires lista_empleados - id de la lista de empleados
 * @requires id_empleado - id de donde se pondrá el id más cercano
 */
function buscador() {
  // === Enlaza input ↔ datalist al cargar, no en el click ===
  // ====== AUTOCOMPLETADO INTELIGENTE ======
  (function () {
    const input = document.getElementById('buscador');
    const lista = document.getElementById('lista_empleados');
    const hidden = document.getElementById('id_empleado');

    if (!input || !lista || !hidden) return;

    // Cachea las opciones (value = nombre completo que ve el usuario; data-id = id)
    const opciones = Array.from(lista.options).map((o, idx) => ({
      idx,
      value: o.value,
      id: o.dataset.id || '',
      norm: normaliza(o.value)
    }));

    function normaliza(s) {
      return (s || '')
        .toLowerCase()
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // quita acentos
        .replace(/\s+/g, ' ') // espacios únicos
        .trim();
    }

    // Scoring: mayor es mejor
    function puntua(queryNorm, candidato) {
      if (!queryNorm) return 0;
      const hay = candidato.norm;
      if (hay === queryNorm) return 1000; // match exacto
      if (hay.startsWith(queryNorm)) return 900; // empieza por
      // empieza por palabra
      if (new RegExp(`(^|\\s)${escapeReg(queryNorm)}`).test(hay)) return 800;
      if (hay.includes(queryNorm)) return 600; // contiene
      // fuzzy (subsecuencia)
      const fz = subsecuencia(queryNorm, hay);
      if (fz) return 400 + Math.max(0, 50 - fz); // penaliza distancia
      return 0;
    }

    function escapeReg(s) {
      return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }

    // Devuelve “costo” de subsecuencia (0 = muy pegado, >0 peor). false si no es subsecuencia
    function subsecuencia(needle, hay) {
      let i = 0,
        j = 0,
        saltos = 0;
      while (i < needle.length && j < hay.length) {
        if (needle[i] === hay[j]) {
          i++;
          j++;
        } else {
          j++;
          saltos++;
        }
      }
      return i === needle.length ? saltos : false;
    }

    function mejorCoincidencia(q) {
      const qn = normaliza(q);
      if (!qn) return null;
      let mejor = null;
      let mejorScore = 0;
      for (const op of opciones) {
        const s = puntua(qn, op);
        if (s > mejorScore || (s === mejorScore && mejor && op.value.length < mejor.value.length)) {
          mejor = op;
          mejorScore = s;
        }
      }
      return mejorScore > 0 ? mejor : null;
    }

    function actualizar() {
      const q = input.value;
      const m = mejorCoincidencia(q);
      if (m) {
        hidden.value = m.id;
        // (opcional) muestra el elegido como tooltip
        input.title = m.value;
        input.dataset.bestId = m.id;
      } else {
        hidden.value = '';
        input.title = '';
        delete input.dataset.bestId;
      }
    }

    // Cambia dinámicamente según tecleas
    input.addEventListener('input', actualizar);
    input.addEventListener('change', () => {
      actualizar();
      if (typeof window.enfocar_pin === 'function') {
        window.enfocar_pin();
      } else if (typeof enfocar_pin === 'function') {
        enfocar_pin();
      }
    });


    // Si el usuario pulsa Enter y hay “mejor”, fija el valor exacto
    input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !hidden.value && input.dataset.bestId) {
        const m = mejorCoincidencia(input.value);
        if (m) {
          input.value = m.value; // autocompleta al mejor
          hidden.value = m.id;
        }
      }
    });

    // Inicial
    actualizar();
  })();
}



//CIERRE DE SESIONES
// Gestión de sesión e inactividad (versión revisada)
(function () {
  'use strict';

  // --- Configuración ---
  const BASE = String(window.SITIO_CARPETA || '').replace(/\/+$/, '');
  const URLS = {
    login: joinPath(BASE, '/iniciar_sesion'),
    logout: joinPath(BASE, '/app/ajax/comprobar_sesion_activa?cerrar_sesion=true'),
    status: joinPath(BASE, '/app/ajax/comprobar_sesion_activa'),
    keepalive: joinPath(BASE, '/app/ajax/keepalive'),
  };

  const DEFAULT_INACTIVITY_MS = 15 * 60 * 1000; // 15 min por defecto
  const INACTIVITY_MS = normalizeTimeout(window.T_INACTIVIDAD, DEFAULT_INACTIVITY_MS);

  // Pon >0 para aviso previo (en ms). 0 = sin aviso.
  const WARN_BEFORE_MS = 0; // p. ej., 30_000 para avisar 30 s antes
  const KEEPALIVE_EVERY_MS = 60 * 1000; // no hacer keepalive más de 1 vez/min

  // Páginas donde no tiene sentido vigilar sesión
  const EXCLUDE = new Set(['', 'index', 'index.php', 'iniciar_sesion', 'iniciar_sesion.php']);

  if (isExcluded()) return;

  let inactivityTimer = null;
  let warnTimer = null;
  let lastKeepAlive = 0;

  // --- Sincronización multi‑pestaña ---
  const bc = ('BroadcastChannel' in window) ? new BroadcastChannel('app-session') : null;
  if (bc) {
    bc.addEventListener('message', (ev) => {
      if (ev?.data?.type === 'logout') goLogin('Sesión cerrada');
      if (ev?.data?.type === 'reset-timer') resetInactivityTimer();
    });
  } else {
    window.addEventListener('storage', (e) => {
      if (e.key === 'app:logout' && e.newValue) goLogin('Sesión cerrada');
      if (e.key === 'app:activity' && e.newValue) resetInactivityTimer();
    });
  }

  // --- Escucha de actividad del usuario ---
  const onActivity = throttle(() => {
    resetInactivityTimer();
    maybeKeepAlive();
    if (bc) bc.postMessage({ type: 'reset-timer' });
    else try { localStorage.setItem('app:activity', String(Date.now())); } catch {}
  }, 500);

  ['pointerdown', 'mousemove', 'keydown', 'scroll', 'touchstart', 'click'].forEach(evt =>
    window.addEventListener(evt, onActivity, { passive: true })
  );

  // Al volver al foco/visibilidad, validamos estado con el servidor
  window.addEventListener('focus', checkSessionStatus, { passive: true });
  document.addEventListener('visibilitychange', () => {
    if (!document.hidden) checkSessionStatus();
  }, { passive: true });

  // Arranque
  resetInactivityTimer();
  maybeKeepAlive();

  // --- Funciones principales ---
  function resetInactivityTimer() {
    clearTimeout(inactivityTimer);
    clearTimeout(warnTimer);

    if (WARN_BEFORE_MS > 0 && INACTIVITY_MS > WARN_BEFORE_MS) {
      warnTimer = setTimeout(() => {
        // Aquí se puede advertir console.log
      }, INACTIVITY_MS - WARN_BEFORE_MS);
    }

    inactivityTimer = setTimeout(handleInactivityTimeout, INACTIVIDAD_MS_SAFE());
  }

  function INACTIVIDAD_MS_SAFE() {
    return Math.max(5_000, INACTIVITY_MS);
  }

  function handleInactivityTimeout() {
    doServerLogout('Sesión cerrada por inactividad').finally(() => {
      if (bc) bc.postMessage({ type: 'logout' });
      else try { localStorage.setItem('app:logout', String(Date.now())); } catch {}
      // Pase lo que pase con el backend, redirigimos
      goLogin('Sesión cerrada por inactividad');
    });
  }

  async function doServerLogout() {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 3000);
    const csrf = getCSRFToken();

    try {
      // Preferimos POST + CSRF; si no hay token, caemos a GET (mejor que nada).
      const res = await fetch(URLS.logout, {
        method: csrf ? 'POST' : 'GET',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
          ...(csrf ? { 'X-CSRF-Token': csrf, 'Content-Type': 'application/json' } : {})
        },
        credentials: 'same-origin',
        body: csrf ? JSON.stringify({ cerrar_sesion: true }) : undefined,
        signal: controller.signal,
        keepalive: true
      });
      return res.ok;
    } catch {
      return false;
    } finally {
      clearTimeout(timeoutId);
    }
  }

  async function checkSessionStatus() {
    try {
      const res = await fetch(`${URLS.status}?t=${Date.now()}`, {
        method: 'GET',
        headers: { 'X-Requested-With': 'XMLHttpRequest' },
        credentials: 'same-origin',
        cache: 'no-store'
      });

      // Si el servidor devuelve 401/440, redirigimos.
      if (res.status === 401 || res.status === 440) {
        goLogin('La sesión ha caducado');
        return;
      }

      // Si responde JSON con un flag, lo respetamos.
      if (res.ok && (res.headers.get('content-type') || '').includes('application/json')) {
        const data = await res.json().catch(() => null);
        if (data && data.activa === false) {
          goLogin('La sesión ha caducado');
        }
      }
    } catch {
      // Sin red: no hacemos nada aquí.
    }
  }

  function maybeKeepAlive() {
    const now = Date.now();
    if (now - lastKeepAlive < KEEPALIVE_EVERY_MS) return;
    lastKeepAlive = now;

    try {
      if ('sendBeacon' in navigator) {
        const blob = new Blob([JSON.stringify({ ts: now })], { type: 'application/json' });
        navigator.sendBeacon(URLS.keepalive, blob);
      } else {
        fetch(URLS.keepalive, {
          method: 'POST',
          headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify({ ts: now })
        }).catch(() => {});
      }
    } catch {}
  }

  function goLogin(msg) {
    if (window.__redirectingToLogin) return;
    window.__redirectingToLogin = true;

    const u = new URL(URLS.login, location.origin);
    u.searchParams.set('tipo', 'error');
    if (msg) u.searchParams.set('mensaje', msg);
    // Solo ruta relativa para evitar redirecciones abiertas
    u.searchParams.set('returnTo', location.pathname + location.search);
    location.replace(u.toString());
  }

  // --- Utilidades ---
  function joinPath(base, path) {
    if (!base) return path;
    const b = base.endsWith('/') ? base.slice(0, -1) : base;
    const p = path.startsWith('/') ? path : '/' + path;
    return b + p;
  }

  function normalizeTimeout(value, fallback) {
    const n = Number(value);
    return Number.isFinite(n) && n > 0 ? n : fallback;
  }

  function throttle(fn, wait) {
    let last = 0, t;
    return function (...args) {
      const now = Date.now();
      if (now - last >= wait) {
        last = now;
        fn.apply(this, args);
      } else {
        clearTimeout(t);
        t = setTimeout(() => {
          last = Date.now();
          fn.apply(this, args);
        }, wait - (now - last));
      }
    };
  }

  function isExcluded() {
    const base = String(window.SITIO_CARPETA || '').replace(/\/+$/, '');
    let path = location.pathname;
    if (base && path.startsWith(base)) path = path.slice(base.length) || '/';
    if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1);
    const basename = path.split('/').pop().toLowerCase();
    return EXCLUDE.has(basename);
  }

  function getCSRFToken() {
    // <meta name="csrf-token" content="..."> o cookie habitual
    const m = document.querySelector('meta[name="csrf-token"]') || document.querySelector('meta[name="csrf"]');
    if (m?.content) return m.content;
    const c = document.cookie.split(';').map(s => s.trim());
    const kv = c.find(s => s.startsWith('csrf=') || s.startsWith('XSRF-TOKEN='));
    if (kv) return decodeURIComponent(kv.split('=').slice(1).join('='));
    return null;
  }
})();

/*******************************************************************************
 *                    MARK: Tooltips menú lateral plegado                      *
 *******************************************************************************/
(function() {
  'use strict';
  
  // Esperar a que el DOM esté listo
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initMenuTooltips);
  } else {
    initMenuTooltips();
  }
  
  function initMenuTooltips() {
    const menu = document.getElementById('encabezado-lateral');
    if (!menu) return;
    
    // Crear contenedor de tooltip (fuera del menú para evitar overflow)
    const tooltip = document.createElement('div');
    tooltip.id = 'menu-tooltip';
    tooltip.style.cssText = `
      position: fixed;
      background-color: #1a1a2e;
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 0.4rem;
      font-size: 0.9rem;
      white-space: nowrap;
      opacity: 0;
      visibility: hidden;
      transition: opacity 0.2s ease, visibility 0.2s ease;
      z-index: 9999;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
      pointer-events: none;
    `;
    document.body.appendChild(tooltip);
    
    // Crear flecha del tooltip
    const arrow = document.createElement('div');
    arrow.style.cssText = `
      position: fixed;
      width: 0;
      height: 0;
      border: 6px solid transparent;
      border-right-color: #1a1a2e;
      opacity: 0;
      visibility: hidden;
      transition: opacity 0.2s ease, visibility 0.2s ease;
      z-index: 9999;
      pointer-events: none;
    `;
    document.body.appendChild(arrow);
    
    // Mover title a data-tooltip para evitar tooltip nativo
    const links = menu.querySelectorAll('a[title]');
    links.forEach(link => {
      const title = link.getAttribute('title');
      if (title) {
        link.setAttribute('data-tooltip', title);
        link.removeAttribute('title');
      }
    });
    
    // Obtener todos los enlaces con tooltip
    const tooltipLinks = menu.querySelectorAll('a[data-tooltip]');
    
    tooltipLinks.forEach(link => {
      link.addEventListener('mouseenter', function() {
        if (menu.classList.contains('activo')) return;
        showTooltip(this);
      });
      
      link.addEventListener('mouseleave', function() {
        hideTooltip();
      });
    });
    
    function showTooltip(link) {
      const text = link.getAttribute('data-tooltip');
      if (!text) return;
      
      tooltip.textContent = text;
      
      // Posicionar tooltip - usar el borde visible del menú
      const menuRect = menu.getBoundingClientRect();
      const linkRect = link.getBoundingClientRect();
      const menuVisibleRight = menuRect.right;
      const tooltipLeft = menuVisibleRight + 10;
      const tooltipTop = linkRect.top + (linkRect.height / 2);
      
      tooltip.style.left = tooltipLeft + 'px';
      tooltip.style.top = tooltipTop + 'px';
      tooltip.style.transform = 'translateY(-50%)';
      tooltip.style.opacity = '1';
      tooltip.style.visibility = 'visible';
      
      // Posicionar flecha
      arrow.style.left = (menuVisibleRight - 1) + 'px';
      arrow.style.top = tooltipTop + 'px';
      arrow.style.transform = 'translateY(-50%)';
      arrow.style.opacity = '1';
      arrow.style.visibility = 'visible';
    }
    
    function hideTooltip() {
      tooltip.style.opacity = '0';
      tooltip.style.visibility = 'hidden';
      arrow.style.opacity = '0';
      arrow.style.visibility = 'hidden';
    }
    
    // Ocultar cuando el menú se expande
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.attributeName === 'class') {
          if (menu.classList.contains('activo')) {
            hideTooltip();
          }
        }
      });
    });
    observer.observe(menu, { attributes: true });
  }
})();

/**
 * Silenciar error nativo de Chromium con View Transitions MPA
 * Evita que la consola se llene de "AbortError" o "InvalidStateError" al navegar rápido
 */
window.addEventListener('unhandledrejection', event => {
  if (event.reason) {
    const msg = event.reason.message || event.reason.toString() || '';
    if (msg.includes('Transition')) {
      event.preventDefault();
    }
  }
});