{"id":5640,"date":"2026-01-12T18:32:19","date_gmt":"2026-01-12T18:32:19","guid":{"rendered":"https:\/\/utelvt.edu.ec\/biblioteca\/?page_id=5640"},"modified":"2026-01-26T18:57:13","modified_gmt":"2026-01-26T18:57:13","slug":"noticias-biblioteca","status":"publish","type":"page","link":"https:\/\/utelvt.edu.ec\/biblioteca\/noticias-biblioteca\/","title":{"rendered":"Noticias Biblioteca"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Noticias de la Biblioteca<\/title>\n <style>\n\/* Bot\u00f3n principal del dropdown (Servicios) \u2192 igual que los dem\u00e1s *\/\n.dropbtn {\n  background: none;\n  color: #fff;\n  border: none;\n  font-weight: bold;\n  font-size: 0.95rem;\n  padding: 0.6rem 1rem;\n  cursor: pointer;\n  position: relative;\n  z-index: 1;\n  transition: color 0.3s ease;\n}\n\n\/* Franja blanca SOLO para el bot\u00f3n de Servicios *\/\n.dropbtn::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0.2rem;\n  bottom: -1rem;\n  background: #fff;\n  z-index: -1;\n  transform: scaleY(0);\n  transform-origin: top;\n  transition: transform 0.3s ease;\n}\n\n.dropbtn:hover { color: #008000; }\n.dropbtn:hover::after { transform: scaleY(1); }\n\n\/* Contenedor del men\u00fa desplegable *\/\n.dropdown-content {\n  display: none;\n  position: absolute;\n  background-color: #fff;          \/* fondo blanco *\/\n  min-width: 220px;\n  box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n  border-radius: 6px;\n  z-index: 1000;\n}\n\n\/* Links dentro del men\u00fa desplegable \u2192 SIN franja blanca *\/\n.dropdown-content a {\n  color: #333;\n  padding: 10px 16px;\n  text-decoration: none;\n  display: block;\n  transition: background 0.2s ease; \/* solo cambio de fondo *\/\n}\n\n.dropdown-content a:hover {\n  background-color: #f0f0f0;       \/* gris claro *\/\n}\n\n\/* Mostrar men\u00fa al pasar el mouse *\/\n.dropdown:hover .dropdown-content {\n  display: block;\n}\n\n\/* Animaci\u00f3n estilo administraci\u00f3n SOLO para los botones del header *\/\nnav a {\n  background: none;\n  color: #fff;\n  border: none;\n  font-size: 0.95rem;\n  font-weight: bold;\n  cursor: pointer;\n  padding: 0.6rem 1rem;\n  position: relative;\n  z-index: 1;\n  transition: color 0.3s ease;\n}\n\nnav a::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0.2rem;\n  bottom: -1rem;\n  background: #fff;\n  z-index: -1;\n  transform: scaleY(0);\n  transform-origin: top;\n  transition: transform 0.3s ease;\n}\n\nnav a:hover { color: #008000; }\nnav a:hover::after { transform: scaleY(1); }\n\nnav a.active {\n  color: #008000;\n}\nnav a.active::after {\n  background: #fff;\n  transform: scaleY(1);\n}\n\n\n    * { margin: 0; padding: 0; box-sizing: border-box; }\n    body { font-family: 'Segoe UI', Arial, sans-serif; }\n\n\/* Barra de administrador *\/\n#admin-bar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  text-align: center;\n  padding: 0.5rem;\n  font-weight: bold;\n  font-size: 0.95rem;\n  z-index: 10;   \/* mucho m\u00e1s bajo que los modales *\/\n}\n\n\/* Estilo de los enlaces dentro de la barra *\/\n#admin-bar a {\n  color: #fff;\n  text-decoration: underline;\n}\n\n\/* Variantes de color seg\u00fan condici\u00f3n *\/\n#admin-bar.red { background-color: #cc0000; color: #fff; }\n#admin-bar.blue { background-color: #0066cc; color: #fff; }\n#admin-bar.yellow { background-color: #e6b800; color: #000; }\n\n\n\n    header {\n      width: 100vw;\n      background-color: #008000;\n      color: #fff;\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      padding: 1rem 2rem;\n      position: fixed;\n      top: 40px; \/* espacio para la barra roja *\/\n      left: 0;\n      z-index: 1000;\n    }\n\n    .titulo { font-size: 1.6rem; font-weight: bold; letter-spacing: 1px; }\n\n    nav {\n      display: flex;\n      gap: 1rem;\n      margin-left: auto;\n      align-items: center;\n    }\n\n\n    #login-btn { order: 2; }\n    #user-info { order: 4; display: none; position: relative; }\n    #search-wrapper { order: 3; }\n\n    .logged-in #user-info { order: 3; display: flex; align-items: center; gap: 0.6rem; }\n    .logged-in #search-wrapper { order: 2; }\n    .logged-in #login-btn { display: none; }\n\n    .busqueda {\n      display: flex;\n      align-items: center;\n      background: #fff;\n      border-radius: 25px;\n      padding: 0.2rem 0.6rem;\n      width: 150px;\n      box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);\n    }\n    .busqueda input {\n      border: none;\n      outline: none;\n      font-size: 0.85rem;\n      flex: 1;\n      min-width: 0;\n      color: #333;\n      padding-right: 0.5rem;\n    }\n    .busqueda img {\n      width: 16px;\n      height: 16px;\n      flex-shrink: 0;\n      cursor: pointer;\n    }\n\nmain {\n  padding-top: 20px;\n  padding-left: 2rem;\n  padding-right: 2rem;\n}\n\n    .login-btn {\n      background: #fff;\n      color: #008000;\n      font-weight: bold;\n      padding: 0.5rem 1rem;\n      border-radius: 20px;\n      display: flex;\n      align-items: center;\n      gap: 0.4rem;\n      transition: background 0.3s ease, color 0.3s ease;\n      border: none;\n      cursor: pointer;\n    }\n    .login-btn:hover { background: rgba(255,255,255,0.25); color: #fff; }\n\n    #user-info .user-name {\n      font-weight: bold;\n      color: #fff;\n    }\n\n    .user-avatar {\n      width: 40px;\n      height: 40px;\n      border-radius: 50%;\n      cursor: pointer;\n      transition: transform 0.3s ease, box-shadow 0.3s ease;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-weight: bold;\n      color: #fff;\n      overflow: hidden;\n    }\n    .user-avatar:hover {\n      transform: scale(1.1);\n      box-shadow: 0 0 8px rgba(255,255,255,0.6);\n    }\n\n    .logout-btn {\n      display: none;\n      background: #fff;\n      color: #008000;\n      font-weight: bold;\n      padding: 0.3rem 0.8rem;\n      border-radius: 20px;\n      border: none;\n      cursor: pointer;\n    }\n    .logout-btn:hover {\n      background: rgba(255,255,255,0.25);\n      color: #fff;\n    }\n\n    .user-name {\n      font-weight: bold;\n      color: #fff;\n      opacity: 1;\n      transition: opacity 0.3s ease;\n    }\n    .user-name.hidden { display: none; opacity: 0; }\n    .user-name.fade-in { display: inline; opacity: 1; }\n\n    \/* Estilos de logos *\/\n.logos-wrapper {\n  position: relative;\n  width: 100%;\n  max-width: 700px;\n  margin: 40px auto -65px auto;   \/* &#x1f50e; sube el bloque de noticias\/calendario m\u00e1s arriba *\/\n  height: 260px;\n}\n\n    .logo-container {\n      position: absolute;\n      top: 50%;\n      left: 50%;\n      transform: translate(-50%, -50%);\n      overflow: hidden;\n    }\n    .logo-container img {\n      display: block;\n      max-width: 100%;\n      height: auto;\n    }\n    .logo-ut {\n      max-width: 300px;\n      z-index: 2;\n      opacity: 1;\n      animation: ut-slide-left 700ms ease-out forwards;\n      animation-delay: 1.5s;\n    }\n    .logo-acred {\n      max-width: 110px;\n      opacity: 0;\n      z-index: 1;\n      animation: acred-reveal-right 700ms ease-out forwards;\n      animation-delay: 1.5s;\n      animation-fill-mode: forwards;\n    }\n    .logo-container::after {\n      content: \"\";\n      position: absolute;\n      top: 0;\n      left: -100%;\n      width: 100%;\n      height: 100%;\n      background: linear-gradient(\n        120deg,\n        rgba(255,255,255,0) 0%,\n        rgba(255,255,255,0.6) 50%,\n        rgba(255,255,255,0) 100%\n      );\n      animation: shine 1s ease-out forwards;\n      animation-delay: 2.5s;\n    }\n    @keyframes shine {\n      0% { left: -100%; }\n      100% { left: 100%; }\n    }\n    @keyframes ut-slide-left {\n      0% { transform: translate(-50%, -50%); }\n      100% { transform: translate(calc(-50% - 80px), -50%); }\n    }\n    @keyframes acred-reveal-right {\n      0% { opacity: 0; transform: translate(-50%, -50%) scale(0.98); }\n      100% { opacity: 1; transform: translate(calc(-50% + 160px), -50%) scale(1); }\n    }\n.lista-eventos {\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n}\n\n.evento-item {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 12px;\n  background: #fafafa;\n}\n\n.evento-item h3 {\n  margin: 0 0 6px;\n  color: #0077cc;\n}\n\n.evento-item img.miniatura {\n  max-width: 120px;\n  border-radius: 6px;\n  margin-top: 8px;\n}\n\n  <\/style>\n<\/head>\n<body>\n  <!-- Barra de administrador visible para todos -->\n  <div id=\"admin-bar\">\n    Bienvenido administrador \u2014\n    <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/administracion-de-noticias\/\">\n      Pulsa aqu\u00ed para administrar esta p\u00e1gina\n    <\/a>\n  <\/div>\n\n <header id=\"site-header\">\n  <div class=\"titulo\">Noticias de la biblioteca<\/div>\n  <nav>\n    <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/\">Inicio<\/a>\n<a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/base-de-datos-suscritas\/#seccion-todas\">Cat\u00e1logo<\/a>\n\n    <a href=\"#\" class=\"active\">Noticias<\/a>\n    \n    <!-- Men\u00fa desplegable de Servicios -->\n    <div class=\"dropdown\">\n      <button class=\"dropbtn\">Servicios<\/button>\n      <div class=\"dropdown-content\">\n        <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/capacitaciones-2\/\">Capacitaciones<\/a>\n        <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/salas-de-lectura-2\/\">Salas de Lectura<\/a>\n        <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/estanterias-abiertas\/\">Estanter\u00eda Abierta<\/a>\n        <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/area-infantil-2\/\">Sala de Lectura Infantil<\/a>\n      <\/div>\n    <\/div>\n\n    <a href=\"#\">Eventos<\/a>\n\n    <button id=\"login-btn\" class=\"login-btn\">Iniciar sesi\u00f3n<\/button>\n\n    <div id=\"user-info\">\n      <div class=\"user-avatar\" title=\"Cerrar sesi\u00f3n\"><\/div>\n      <span class=\"user-name\"><\/span>\n      <button id=\"logout-btn\" class=\"logout-btn\">Cerrar sesi\u00f3n<\/button>\n    <\/div>\n\n    <div id=\"search-wrapper\">\n      <div class=\"busqueda\">\n        <input type=\"text\" placeholder=\"Buscar...\">\n        <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/Lupa-negra2.png\" alt=\"Buscar\">\n      <\/div>\n    <\/div>\n  <\/nav>\n<\/header>\n\n\n<main style=\"padding-top: 20px;\">\n  <div class=\"logos-wrapper\">\n    <!-- Contenedor Logo Universidad -->\n    <div class=\"logo-container logo-ut\">\n      <img decoding=\"async\"\n        src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2025\/11\/UTELVTE-LOGO-scaled.png\"\n        alt=\"Logo UTELVT\">\n    <\/div>\n\n    <!-- Contenedor Logo Acreditaci\u00f3n -->\n    <div class=\"logo-container logo-acred\">\n      <img decoding=\"async\"\n        src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2025\/11\/ACREDITACION.png\"\n        alt=\"Logo Acreditaci\u00f3n UTELVT\">\n    <\/div>\n  <\/div>\n<\/main>\n\n<div id=\"modal-lista-eventos\" class=\"modal-evento\"> <div class=\"modal-contenido\" style=\"max-width:90%; width:90%;\"> <span id=\"cerrar-lista-eventos\" class=\"modal-cerrar\">&times;<\/span> <h2 id=\"titulo-lista\" style=\"text-align:center; color:#008000;\">Eventos agendados<\/h2> <div id=\"contenedor-eventos\" class=\"lista-eventos\"><\/div> <\/div> <\/div>\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", async () => {\n  const anio = new Date().getFullYear();\n\n  \/\/ Conexi\u00f3n a Supabase\n  const supabase = window.supabase.createClient(\n    \"https:\/\/mokvdfuarrxnjmuzvsfj.supabase.co\",\n    \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1va3ZkZnVhcnJ4bmptdXp2c2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMTkzNzYsImV4cCI6MjA4Mzg5NTM3Nn0.TQbSQNDgzwAxr2tAUQyBtIOVKuqN1d29to4BrdRjI-s\"\n  );\n\n  \/\/ Traer todos los eventos del a\u00f1o actual\n  let { data: eventos, error } = await supabase\n    .from(\"eventos\")\n    .select(\"*\")\n    .gte(\"fecha\", `${anio}-01-01`)\n    .lte(\"fecha\", `${anio}-12-31`);\n\n  if (error) {\n    console.error(\"Error Supabase:\", error);\n    eventos = [];\n  }\n\n  \/\/ Seleccionar el enlace \"Eventos\" del encabezado\n  const enlaceEventos = document.querySelector(\"nav a[href='#']:last-of-type\");\n  const modalLista = document.getElementById(\"modal-lista-eventos\");\n  const cerrarLista = document.getElementById(\"cerrar-lista-eventos\");\n\n  \/\/ Abrir modal al hacer clic en \"Eventos\"\n  enlaceEventos.addEventListener(\"click\", (e) => {\n    e.preventDefault();\n    renderizarListaEventos();\n    modalLista.classList.add(\"abierto\");\n  });\n\n  \/\/ Cerrar modal\n  cerrarLista.addEventListener(\"click\", () => {\n    modalLista.classList.remove(\"abierto\");\n  });\n\n  window.addEventListener(\"click\", (e) => {\n    if (e.target.id === \"modal-lista-eventos\") {\n      modalLista.classList.remove(\"abierto\");\n    }\n  });\n\n  \/\/ Renderizar lista de eventos (solo t\u00edtulo, fecha y hora)\n  function renderizarListaEventos() {\n    const contenedor = document.getElementById(\"contenedor-eventos\");\n    contenedor.innerHTML = \"\";\n\n    if (!eventos || eventos.length === 0) {\n      contenedor.innerHTML = \"<p>No hay eventos agendados.<\/p>\";\n      return;\n    }\n\n    \/\/ Ordenar cronol\u00f3gicamente\n    eventos.sort((a, b) => new Date(a.fecha) - new Date(b.fecha));\n\n    eventos.forEach(ev => {\n      const item = document.createElement(\"div\");\n      item.classList.add(\"evento-item\");\n      item.innerHTML = `\n        <h3>${ev.titulo}<\/h3>\n        <p><strong>Fecha:<\/strong> ${ev.fecha}<\/p>\n        <p><strong>Hora:<\/strong> ${ev.hora || \"No especificada\"}<\/p>\n        ${ev.imagen ? `<img decoding=\"async\" src=\"${ev.imagen}\" alt=\"Imagen evento\" class=\"miniatura\">` : \"\"}\n      `;\n\n      \/\/ Al hacer clic en el evento, abrir el modal individual con detalles\n      item.addEventListener(\"click\", () => mostrarModalEvento(ev));\n\n      contenedor.appendChild(item);\n    });\n  }\n\n  \/\/ Funci\u00f3n para mostrar el modal individual de un evento\n  function mostrarModalEvento(ev) {\n    document.getElementById(\"modal-descripcion\").textContent = ev.descripcion || \"\";\n    document.getElementById(\"modal-fecha\").textContent = ev.fecha;\n    document.getElementById(\"modal-hora\").textContent = ev.hora || \"\";\n    document.getElementById(\"modal-imagen\").src = ev.imagen || \"\";\n    document.getElementById(\"modal-evento\").classList.add(\"abierto\");\n  }\n\n  \/\/ Cerrar modal individual\n  document.getElementById(\"modal-cerrar\").addEventListener(\"click\", () => {\n    document.getElementById(\"modal-evento\").classList.remove(\"abierto\");\n  });\n});\n<\/script>\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\n<script>\n  const supabaseUrl = \"https:\/\/mokvdfuarrxnjmuzvsfj.supabase.co\";\n  const supabaseAnonKey = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1va3ZkZnVhcnJ4bmptdXp2c2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMTkzNzYsImV4cCI6MjA4Mzg5NTM3Nn0.TQbSQNDgzwAxr2tAUQyBtIOVKuqN1d29to4BrdRjI-s\";\n\n  const sb = supabase.createClient(supabaseUrl, supabaseAnonKey, {\n    auth: {\n      persistSession: true,\n      autoRefreshToken: true,\n      detectSessionInUrl: true\n    }\n  });\n\n  async function loginWithGoogle() {\n    const { error } = await sb.auth.signInWithOAuth({\n      provider: \"google\",\n      options: { redirectTo: \"https:\/\/utelvt.edu.ec\/biblioteca\/noticias-biblioteca\/\" }\n    });\n    if (error) console.error(\"Error en login:\", error);\n  }\n\n  function normalizeWord(w) {\n    const lower = w.toLowerCase();\n    return lower.charAt(0).toUpperCase() + lower.slice(1);\n  }\n  function formatShortName(fullName, email) {\n    let base = (fullName || \"\").trim();\n    if (!base) {\n      const local = (email || \"\").split(\"@\")[0];\n      return normalizeWord(local || \"Usuario\");\n    }\n    const parts = base.split(\/\\s+\/).filter(Boolean);\n    const norm = parts.map(normalizeWord);\n    if (norm.length === 1) return norm[0];\n    if (norm.length === 2) return `${norm[0]} ${norm[1]}`;\n    if (norm.length === 3) return `${norm[0]} ${norm[1]}`;\n    return `${norm[0]} ${norm[norm.length - 2]}`;\n  }\n  function getInitials(name) {\n    const parts = name.trim().split(\" \").filter(Boolean);\n    if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n    return parts[0][0].toUpperCase();\n  }\n  function getRandomColor() {\n    const colors = [\"#008000\",\"#0066cc\",\"#cc3300\",\"#9933cc\",\"#ff6600\"];\n    return colors[Math.floor(Math.random() * colors.length)];\n  }\n\n  async function renderSession() {\n    const { data: { user } } = await sb.auth.getUser();\n    const header = document.getElementById(\"site-header\");\n    const loginBtn = document.getElementById(\"login-btn\");\n    const userInfo = document.getElementById(\"user-info\");\n    const avatar = userInfo.querySelector(\".user-avatar\");\n    const nameEl = userInfo.querySelector(\".user-name\");\n    const logoutBtn = document.getElementById(\"logout-btn\");\n\n    if (user) {\n      header.classList.add(\"logged-in\");\n      const fullName = user.user_metadata?.full_name || \"\";\n      const email = user.email || \"\";\n      const shortName = formatShortName(fullName, email);\n\n      if (user.user_metadata?.avatar_url) {\n        avatar.innerHTML = `<img decoding=\"async\" src=\"${user.user_metadata.avatar_url}\" style=\"width:100%;height:100%;object-fit:cover;\">`;\n        avatar.style.background = \"transparent\";\n      } else {\n        avatar.textContent = getInitials(fullName || email);\n        avatar.style.background = getRandomColor();\n      }\n\n      nameEl.textContent = shortName;\n      userInfo.style.display = \"flex\";\n      loginBtn.style.display = \"none\";\n    } else {\n      header.classList.remove(\"logged-in\");\n      userInfo.style.display = \"none\";\n      loginBtn.style.display = \"flex\";\n    }\n  }\n\n  \/\/ Barra din\u00e1mica con prioridad para administradores\n  async function updateAdminBar() {\n    const bar = document.getElementById(\"admin-bar\");\n    const { data: { user } } = await sb.auth.getUser();\n\n    if (!user) {\n      bar.className = \"yellow\";\n      bar.textContent = \"Inicia sesi\u00f3n para ver todas las funciones de la p\u00e1gina\";\n      return;\n    }\n\n    const email = user.email || \"\";\n\n    \/\/ 1. Administrador (tiene prioridad sobre cualquier otro caso)\n    const { data, error } = await sb.from(\"administradores\").select(\"email\").eq(\"email\", email);\n    if (!error && data && data.length > 0) {\n      bar.className = \"red\";\n      bar.innerHTML = `Bienvenido administrador \u2014 \n        <a href=\"https:\/\/utelvt.edu.ec\/biblioteca\/administracion-de-noticias\/\">\n          Pulsa aqu\u00ed para administrar esta p\u00e1gina\n        <\/a>`;\n      return;\n    }\n\n    \/\/ 2. Gmail normal (no administrador)\n    if (email.endsWith(\"@gmail.com\")) {\n      bar.className = \"blue\";\n      bar.textContent = \"Bienvenido a las noticias de la biblioteca UTLVTE\";\n      return;\n    }\n\n    \/\/ 3. Institucional (no administrador)\n    if (email.endsWith(\"@utelvt.edu.ec\")) {\n      bar.className = \"blue\";\n      bar.textContent = \"Bienvenido estudiante de la UTLVTE\";\n      return;\n    }\n\n    \/\/ 4. Otros\n    bar.className = \"yellow\";\n    bar.textContent = \"Inicia sesi\u00f3n para ver todas las funciones de la p\u00e1gina\";\n  }\n\n  document.addEventListener(\"DOMContentLoaded\", () => {\n    document.getElementById(\"login-btn\").addEventListener(\"click\", loginWithGoogle);\n\n    sb.auth.onAuthStateChange((_event, _session) => {\n      renderSession();\n      updateAdminBar();\n    });\n\n    renderSession();\n    updateAdminBar();\n\n    const userInfo = document.getElementById(\"user-info\");\n    const nameEl = userInfo.querySelector(\".user-name\");\n    const avatar = userInfo.querySelector(\".user-avatar\");\n\n    document.addEventListener(\"click\", (e) => {\n      if (e.target.closest(\".user-avatar\")) {\n        const logoutBtn = document.getElementById(\"logout-btn\");\n        if (logoutBtn.style.display === \"inline-block\") {\n          logoutBtn.style.display = \"none\";\n          nameEl.style.display = \"inline\";\n          nameEl.style.opacity = 0;\n          nameEl.style.transition = \"opacity 0.25s ease\";\n          requestAnimationFrame(() => { nameEl.style.opacity = 1; });\n        } else {\n          nameEl.style.display = \"none\";\n          logoutBtn.style.display = \"inline-block\";\n          logoutBtn.style.pointerEvents = \"auto\";\n        }\n      }\n      if (!userInfo.contains(e.target)) {\n        const logoutBtn = document.getElementById(\"logout-btn\");\n        nameEl.style.display = \"inline\";\n        logoutBtn.style.display = \"none\";\n      }\n    });\n\n    document.addEventListener(\"click\", async (e) => {\n      const btn = e.target.closest(\"#logout-btn\");\n      if (!btn) return;\n      btn.disabled = true;\n      try {\n        const { error } = await sb.auth.signOut({ scope: 'global' });\n        if (error) {\n          console.error(\"Error al cerrar sesi\u00f3n:\", error);\n          btn.disabled = false;\n          return;\n        }\n        window.location.reload();\n      } catch (err) {\n        console.error(\"Excepci\u00f3n al cerrar sesi\u00f3n:\", err);\n        btn.disabled = false;\n      }\n    });\n  });\n<\/script>\n\n\n\n<\/body>\n<\/html>\n\n\n<!-- Bloque de publicaciones y calendario -->\n<section class=\"contenedor-principal\">\n  <!-- Columna izquierda: publicaciones -->\n  <div class=\"columna-izquierda\">\n    <!-- Portada principal -->\n    <div id=\"portada\" class=\"portada\">\n      <div class=\"overlay\">\n        <div class=\"texto-portada\">\n          <h2 id=\"portada-titulo\"><\/h2>\n          <small id=\"portada-fecha\"><\/small>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <!-- Publicaciones en tarjetas -->\n    <div id=\"publicaciones\" class=\"grid-publicaciones\">\n      <!-- Ejemplo de tarjeta de publicaci\u00f3n -->\n      <div class=\"publicacion\" data-id=\"ejemplo\">\n        <div class=\"imagenes-tarjeta\">\n          <!-- Imagen principal -->\n          <img decoding=\"async\" src=\"ejemplo.jpg\" alt=\"Imagen publicaci\u00f3n\" class=\"active\">\n        <\/div>\n\n        <!-- Informaci\u00f3n de la publicaci\u00f3n -->\n        <h2>T\u00edtulo de la publicaci\u00f3n<\/h2>\n        <small>Fecha de la publicaci\u00f3n<\/small>\n        <p>Descripci\u00f3n breve de la publicaci\u00f3n&#8230;<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n\n<!-- Columna derecha: calendario -->\n<aside class=\"columna-derecha\">\n  <div class=\"calendario-minimalista\">\n    <!-- El t\u00edtulo ahora tiene id para abrir el modal anual -->\n    <h3 id=\"abrir-calendario\">Calendario de eventos<\/h3>\n    <h2 id=\"mes-actual\"><\/h2>\n    <div class=\"dias-semana\">\n      <div class=\"domingo\">D<\/div>\n      <div>L<\/div>\n      <div>M<\/div>\n      <div>X<\/div>\n      <div>J<\/div>\n      <div>V<\/div>\n      <div class=\"sabado\">S<\/div>\n    <\/div>\n    <div class=\"fechas\" id=\"fechas\"><\/div>\n  <\/div>\n\n  <!-- Modal de evento individual -->\n  <div id=\"modal-evento\" class=\"modal-evento\">\n    <div class=\"modal-contenido\">\n      <span id=\"modal-cerrar\" class=\"modal-cerrar\">&times;<\/span>\n<h2 id=\"titulo-anual\" style=\"text-align:center; color:#008000;\">Calendario de eventos<\/h2>\n      <p id=\"modal-descripcion\"><\/p>\n      <p><strong>Fecha:<\/strong> <span id=\"modal-fecha\"><\/span><\/p>\n      <p><strong>Hora:<\/strong> <span id=\"modal-hora\"><\/span><\/p>\n<div class=\"imagen-wrapper\">\n  <img decoding=\"async\" id=\"modal-imagen\" src=\"\" alt=\"Imagen evento\">\n<\/div>\n\n<\/div>\n   \n  <\/div>\n\n <!-- Modal anual con todos los meses -->\n<div id=\"modal-calendario\" class=\"modal-evento\">\n  <div class=\"modal-contenido\" style=\"max-width:90%; width:90%;\">\n    <span id=\"cerrar-calendario\" class=\"modal-cerrar\">&times;<\/span>\n    <h2 id=\"titulo-anual\" style=\"text-align:center; color:#008000;\">Calendario de eventos<\/h2>\n    \n    <!-- Grilla de meses -->\n    <div class=\"grilla-meses\">\n \n        <!-- Enero a Junio -->\n        <div class=\"fila-meses\">\n          <div class=\"calendario-minimalista\" id=\"mes-1\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-2\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-3\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-4\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-5\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-6\"><\/div>\n        <\/div>\n        <!-- Julio a Diciembre -->\n        <div class=\"fila-meses\">\n          <div class=\"calendario-minimalista\" id=\"mes-7\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-8\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-9\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-10\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-11\"><\/div>\n          <div class=\"calendario-minimalista\" id=\"mes-12\"><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/aside>\n\n\n<style>\n\/* Cabecera de d\u00edas de semana *\/\n.dias-semana .domingo,\n.dias-semana .sabado {\n  color: #c00;       \/* rojo *\/\n  font-weight: 600;\n}\n\n\/* D\u00edas reales del calendario *\/\n.fechas .domingo,\n.fechas .sabado {\n  color: #c00;       \/* rojo *\/\n  font-weight: 600;\n}\n\n\/* Animaci\u00f3n hover en el t\u00edtulo del calendario *\/\n#abrir-calendario {\n  cursor: pointer;              \/* indica que se puede hacer clic *\/\n  display: inline-block;\n  position: relative;\n  transition: color 0.3s ease;  \/* transici\u00f3n suave del color *\/\n}\n\n#abrir-calendario::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  bottom: -4px;\n  width: 100%;\n  height: 2px;\n  background: #008000;          \/* color verde institucional *\/\n  transform: scaleX(0);         \/* l\u00ednea oculta *\/\n  transform-origin: right;\n  transition: transform 0.3s ease;\n}\n\n#abrir-calendario:hover {\n  color: #008000;               \/* cambia el color del texto *\/\n}\n\n#abrir-calendario:hover::after {\n  transform: scaleX(1);         \/* l\u00ednea aparece *\/\n  transform-origin: left;\n}\n\n\/* Contenedor general de los meses dentro del modal *\/\n.grilla-meses {\n  display: flex;\n  flex-direction: column;\n  gap: 12px; \/* menos espacio entre filas *\/\n}\n\n\/* Cada fila con 6 meses m\u00e1s anchos y menos separaci\u00f3n *\/\n.fila-meses {\n  display: grid;\n  grid-template-columns: repeat(6, minmax(160px, 1fr));\n  gap: 4px;\n}\n\n\/* Mini-calendarios dentro del modal *\/\n.grilla-meses .calendario-minimalista {\n  border: 1px solid #ddd;\n  border-radius: 6px;\n  padding: 6px;\n  background: #fafafa;\n  font-size: 12px;\n  min-width: 160px;\n}\n\n\/* T\u00edtulos de cada mes *\/\n.grilla-meses .calendario-minimalista h4 {\n  font-size: 14px;\n  margin: 4px 0;\n  color: #0077cc;\n  text-transform: capitalize;\n  text-align: center;\n}\n\n\/* D\u00edas de semana dentro de mini\u2011calendarios *\/\n.grilla-meses .dias-semana {\n  font-size: 11px;\n  margin-bottom: 4px;\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n}\n\n\/* Fechas dentro de mini\u2011calendarios *\/\n.grilla-meses .fechas {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  gap: 1px;\n}\n\n.grilla-meses .fechas div {\n  font-size: 11px;\n  padding: 2px 0;\n  text-align: center;\n}\n\n\/* D\u00eda actual *\/\n.hoy {\n  border: 2px solid #0077cc;\n  color: #0077cc !important;\n  border-radius: 50%;\n  font-weight: bold;\n  background: transparent;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 25px;\n  height: 25px;\n  margin: 0 auto;\n}\n\n\/* D\u00eda con evento *\/\n.dia-evento {\n  background: #008000;\n  color: #fff !important;\n  border-radius: 50%;\n  cursor: pointer;\n  font-weight: bold;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 25px;\n  height: 25px;\n  margin: 0 auto;\n}\n\n\/* Modal general *\/\n.modal-evento {\n  display: none;\n  position: fixed;\n  z-index: 2000;\n  inset: 0;\n  width: 100%; height: 100%;\n  background: rgba(0,0,0,0.6);\n  align-items: center;\n  justify-content: center;\n}\n\n.modal-evento.abierto {\n  display: flex;\n}\n\n\/* Contenido del modal *\/\n.modal-contenido {\n  background: #fff;\n  padding: 25px 30px;\n  border-radius: 12px;\n  max-width: 500px;\n  width: 90%;\n  box-shadow: 0 8px 20px rgba(0,0,0,0.3);\n  text-align: left;\n  animation: aparecer 0.25s ease-out;\n}\n\n.modal-contenido h2 {\n  margin-top: 0;\n  font-size: 22px;\n  color: #008000;\n}\n\n.modal-contenido p {\n  margin: 8px 0;\n  font-size: 15px;\n  color: #333;\n}\n\n#modal-imagen {\n  width: 100%;\n  aspect-ratio: 16 \/ 9;   \/* relaci\u00f3n 16:9 *\/\n  object-fit: cover;      \/* recorta sin deformar *\/\n  border-radius: 8px;\n  margin-top: 12px;\n  display: block;\n}\n\n\n\/* Bot\u00f3n cerrar *\/\n.modal-cerrar {\n  float: right;\n  font-size: 22px;\n  font-weight: bold;\n  color: #555;\n  cursor: pointer;\n  transition: color 0.2s;\n}\n\n.modal-cerrar:hover {\n  color: #c00;\n}\n\n\/* Animaci\u00f3n de aparici\u00f3n *\/\n@keyframes aparecer {\n  from { transform: scale(0.96); opacity: 0; }\n  to   { transform: scale(1);    opacity: 1; }\n}\n\n\/* Calendario minimalista *\/\n.calendario-minimalista {\n  font-family: 'Segoe UI', sans-serif;\n  text-align: center;\n  max-width: 100%;\n  margin: 0 auto;\n  padding: 10px;\n  color: #333;\n}\n\n.calendario-minimalista h3 {\n  font-size: 20px;\n  margin-bottom: 4px;\n  font-weight: bold;\n  color: #222;\n}\n\n.calendario-minimalista h2 {\n  font-size: 22px;\n  margin-bottom: 10px;\n  font-weight: 600;\n  color: #333;\n  text-transform: lowercase;\n}\n\n.dias-semana {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  font-weight: 600;\n  font-size: 14px;\n  margin-bottom: 6px;\n  color: #555;\n}\n\n.fechas {\n  display: grid;\n  grid-template-columns: repeat(7, 1fr);\n  font-size: 14px;\n  gap: 4px;\n}\n\n.fechas div {\n  padding: 4px 0;\n  color: #333;\n}\n\n.domingo { \n  color: #c00; \n  font-weight: 600; \n}\n\n.sabado { \n  color: #c00; \n  font-weight: 600; \n}\n\n\/* Texto de eventos dentro de los d\u00edas *\/\n.evento {\n  display: block;\n  font-size: 12px;\n  margin-top: 2px;\n  color: #0077cc;\n}\n\n\/* Z-index para diferenciar modales *\/\n#modal-calendario { \n  z-index: 1000; \n}\n\n#modal-evento { \n  z-index: 2000; \n}\n<\/style>\n\n\n<\/style>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\n<script>\ndocument.addEventListener('DOMContentLoaded', async function() {\n  const meses = [\"enero\",\"febrero\",\"marzo\",\"abril\",\"mayo\",\"junio\",\n                 \"julio\",\"agosto\",\"septiembre\",\"octubre\",\"noviembre\",\"diciembre\"];\n\n  const hoy = new Date();\n  const mes = hoy.getMonth(); \/\/ 0 = enero\n  const anio = hoy.getFullYear();\n\n  document.getElementById(\"mes-actual\").textContent = meses[mes];\n\n  const primerDia = new Date(anio, mes, 1).getDay(); \/\/ 0=domingo\n  const diasEnMes = new Date(anio, mes+1, 0).getDate();\n\n  const fechasDiv = document.getElementById(\"fechas\");\n\n  \/\/ Conexi\u00f3n a Supabase\n  const supabase = window.supabase.createClient(\n    \"https:\/\/mokvdfuarrxnjmuzvsfj.supabase.co\",\n    \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1va3ZkZnVhcnJ4bmptdXp2c2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMTkzNzYsImV4cCI6MjA4Mzg5NTM3Nn0.TQbSQNDgzwAxr2tAUQyBtIOVKuqN1d29to4BrdRjI-s\"\n  );\n\n  \/\/ Traer eventos del mes actual\n  let { data, error } = await supabase\n    .from('eventos')\n    .select('*')\n    .gte('fecha', `${anio}-${String(mes+1).padStart(2,'0')}-01`)\n    .lte('fecha', `${anio}-${String(mes+1).padStart(2,'0')}-${diasEnMes}`);\n\n  if (error) {\n    console.error(\"Error Supabase:\", error);\n  }\n\n  \/\/ Rellenar espacios vac\u00edos antes del primer d\u00eda\n  for (let i=0; i<primerDia; i++) {\n    fechasDiv.appendChild(document.createElement(\"div\"));\n  }\n\n  \/\/ Rellenar d\u00edas del mes\n  for (let dia=1; dia<=diasEnMes; dia++) {\n    const fecha = new Date(anio, mes, dia);\n    const diaSemana = fecha.getDay();\n    const cell = document.createElement(\"div\");\n\n    if (diaSemana === 0) cell.classList.add(\"domingo\");\n    if (diaSemana === 6) cell.classList.add(\"sabado\");\n    if (dia === hoy.getDate()) cell.classList.add(\"hoy\");\n\n    cell.textContent = dia;\n\n    \/\/ Buscar si hay eventos en Supabase para este d\u00eda\nconst eventosDia = data?.filter(ev => {\n  \/\/ Supabase devuelve fecha como 'YYYY-MM-DD' o 'YYYY-MM-DDT00:00:00Z'\n  const fechaStr = ev.fecha.split(\"T\")[0]; \/\/ nos quedamos solo con 'YYYY-MM-DD'\n  const fechaComparar = `${anio}-${String(mes+1).padStart(2,'0')}-${String(dia).padStart(2,'0')}`;\n  return fechaStr === fechaComparar;\n});\n\n\n    if (eventosDia && eventosDia.length > 0) {\n      cell.classList.add(\"dia-evento\"); \/\/ marcar el d\u00eda en verde\n\n      \/\/ Al hacer clic, mostrar modal con el primer evento\n      cell.addEventListener(\"click\", () => {\n        const ev = eventosDia[0];\n        document.getElementById(\"modal-titulo\").textContent = ev.titulo;\n        document.getElementById(\"modal-descripcion\").textContent = ev.descripcion || \"\";\n        document.getElementById(\"modal-fecha\").textContent = ev.fecha;\n        document.getElementById(\"modal-hora\").textContent = ev.hora || \"\";\n        document.getElementById(\"modal-imagen\").src = ev.imagen || \"\";\n\ndocument.getElementById(\"modal-evento\").classList.add(\"abierto\");\n\n      });\n    }\n\n    fechasDiv.appendChild(cell);\n  }\n\n\/\/ Cerrar modal con la X\ndocument.getElementById(\"modal-cerrar\").addEventListener(\"click\", () => {\n  document.getElementById(\"modal-evento\").classList.remove(\"abierto\");\n});\n\n\/\/ Cerrar al hacer clic fuera\nwindow.addEventListener(\"click\", (e) => {\n  if (e.target.id === \"modal-evento\") {\n    document.getElementById(\"modal-evento\").classList.remove(\"abierto\");\n  }\n});\n\n});\n<\/script>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@supabase\/supabase-js@2\"><\/script>\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", async () => {\n  const meses = [\"enero\",\"febrero\",\"marzo\",\"abril\",\"mayo\",\"junio\",\n                 \"julio\",\"agosto\",\"septiembre\",\"octubre\",\"noviembre\",\"diciembre\"];\n  const anio = new Date().getFullYear();   \/\/ a\u00f1o din\u00e1mico\n  const hoy = new Date();\n\n  const abrirTitulo = document.getElementById(\"abrir-calendario\");\n  const modalCal = document.getElementById(\"modal-calendario\");\n  const cerrarCal = document.getElementById(\"cerrar-calendario\");\n\n  \/\/ Actualizar t\u00edtulo din\u00e1mico en el modal anual\n  const tituloAnual = document.getElementById(\"titulo-anual\");\n  if (tituloAnual) {\n    tituloAnual.textContent = `Calendario de eventos ${anio}`;\n  }\n\n  \/\/ Conexi\u00f3n a Supabase\n  const supabase = window.supabase.createClient(\n    \"https:\/\/mokvdfuarrxnjmuzvsfj.supabase.co\",\n    \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1va3ZkZnVhcnJ4bmptdXp2c2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMTkzNzYsImV4cCI6MjA4Mzg5NTM3Nn0.TQbSQNDgzwAxr2tAUQyBtIOVKuqN1d29to4BrdRjI-s\"\n  );\n\n  \/\/ Traer todos los eventos del a\u00f1o actual\n  let { data: eventos, error } = await supabase\n    .from('eventos')\n    .select('*')\n    .gte('fecha', `${anio}-01-01`)\n    .lte('fecha', `${anio}-12-31`);\n\n  if (error) {\n    console.error(\"Error Supabase:\", error);\n    eventos = [];\n  }\n\n  abrirTitulo.addEventListener(\"click\", () => {\n    modalCal.classList.add(\"abierto\");\n    renderizarMiniCalendarios();\n  });\n\n  cerrarCal.addEventListener(\"click\", () => {\n    modalCal.classList.remove(\"abierto\");\n  });\n\n  window.addEventListener(\"click\", (e) => {\n    if (e.target.id === \"modal-calendario\") {\n      modalCal.classList.remove(\"abierto\");\n    }\n  });\n\n  function renderizarMiniCalendarios() {\n    for (let m=0; m<12; m++) {\n      const diasEnMes = new Date(anio, m+1, 0).getDate();\n      const primerDia = new Date(anio, m, 1).getDay();\n      const contenedor = document.getElementById(\"mes-\"+(m+1));\n      if (!contenedor) continue;\n\n      contenedor.innerHTML = `\n        <h4 style=\"margin:4px 0; color:#0077cc;\">${meses[m]}<\/h4>\n        <div class=\"dias-semana\">\n          <div class=\"domingo\">D<\/div><div>L<\/div><div>M<\/div><div>X<\/div>\n          <div>J<\/div><div>V<\/div><div class=\"sabado\">S<\/div>\n        <\/div>\n        <div class=\"fechas\"><\/div>\n      `;\n\n      const fechasDiv = contenedor.querySelector(\".fechas\");\n\n      \/\/ Espacios vac\u00edos antes del primer d\u00eda\n      for (let i=0; i<primerDia; i++) {\n        fechasDiv.appendChild(document.createElement(\"div\"));\n      }\n\n      \/\/ D\u00edas del mes\n      for (let d=1; d<=diasEnMes; d++) {\n        const fecha = new Date(anio, m, d);\n        const diaSemana = fecha.getDay();\n        const cell = document.createElement(\"div\");\n        cell.textContent = d;\n\n        \/\/ Marcar domingos y s\u00e1bados\n        if (diaSemana === 0) cell.classList.add(\"domingo\");\n        if (diaSemana === 6) cell.classList.add(\"sabado\");\n\n        \/\/ Marcar el d\u00eda actual\n        if (m === hoy.getMonth()) {\n          if (d === hoy.getDate()) {\n            cell.classList.add(\"hoy\");\n          }\n        }\n\n        \/\/ Buscar eventos en Supabase\n        const fechaComparar = `${anio}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;\n        const eventosDia = eventos.filter(ev => {\n          const fechaStr = ev.fecha.length > 10 ? ev.fecha.split(\"T\")[0] : ev.fecha;\n          return fechaStr === fechaComparar;\n        });\n\n        if (eventosDia.length > 0) {\n          cell.classList.add(\"dia-evento\");\n          cell.addEventListener(\"click\", () => mostrarModalEvento(eventosDia[0]));\n        }\n\n        fechasDiv.appendChild(cell);\n      }\n    }\n  }\n\n  \/\/ Modal de evento individual\n  function mostrarModalEvento(ev) {\n    document.getElementById(\"modal-titulo\").textContent = ev.titulo;\n    document.getElementById(\"modal-descripcion\").textContent = ev.descripcion || \"\";\n    document.getElementById(\"modal-fecha\").textContent = ev.fecha;\n    document.getElementById(\"modal-hora\").textContent = ev.hora || \"\";\n    document.getElementById(\"modal-imagen\").src = ev.imagen || \"\";\n    document.getElementById(\"modal-evento\").classList.add(\"abierto\");\n  }\n\n  document.getElementById(\"modal-cerrar\").addEventListener(\"click\", () => {\n    document.getElementById(\"modal-evento\").classList.remove(\"abierto\");\n  });\n});\n<\/script>\n\n\n\n\n\n<!-- Modal de publicaci\u00f3n -->\n<div id=\"modal-publicacion\" class=\"modal\" style=\"display:none;\">\n  <div class=\"modal-contenido\">\n    <div class=\"modal-header\">\n      <span class=\"cerrar\" aria-label=\"Cerrar\">&times;<\/span>\n      <!-- Men\u00fa de opciones (3 puntitos) -->\n      <div class=\"menu-publicacion\">\n        <button class=\"menu-toggle\" aria-label=\"Opciones\">\u22ee<\/button>\n        <div class=\"menu-opciones\">\n          <button class=\"editar\">Editar<\/button>\n          <button class=\"eliminar\">Eliminar<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"modal-body\">\n      <!-- Carrusel a la izquierda -->\n      <div class=\"carrusel\">\n        <button class=\"flecha izquierda\" aria-label=\"Anterior\">\n          <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-IZQUIERDA-BLANCA.png\" alt=\"izquierda\">\n        <\/button>\n        <div class=\"imagenes-carrusel\"><\/div>\n        <button class=\"flecha derecha\" aria-label=\"Siguiente\">\n          <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\" alt=\"derecha\">\n        <\/button>\n      <\/div>\n\n      <!-- Informaci\u00f3n a la derecha con scrollbar propio -->\n      <div class=\"info-publicacion\">\n        <div class=\"scroll-content\" id=\"scrollContent\">\n          <h2 id=\"modal-titulo\"><\/h2>\n          <small id=\"modal-fecha\"><\/small>\n          <p id=\"modal-descripcion\"><\/p>\n        <\/div>\n        <div class=\"custom-scrollbar\" id=\"customScrollbar\" aria-hidden=\"true\">\n          <div class=\"custom-thumb\" id=\"customThumb\"><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\nimport express from \"express\";\nimport fetch from \"node-fetch\";\nimport { createClient } from \"@supabase\/supabase-js\";\n\nconst app = express();\n\n\/\/ Conexi\u00f3n a Supabase\nconst supabase = createClient(\n  \"https:\/\/mokvdfuarrxnjmuzvsfj.supabase.co\",\n  \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1va3ZkZnVhcnJ4bmptdXp2c2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzMTkzNzYsImV4cCI6MjA4Mzg5NTM3Nn0.TQbSQNDgzwAxr2tAUQyBtIOVKuqN1d29to4BrdRjI-s\"\n);\n\n\/\/ Endpoint de callback OAuth2\napp.get(\"\/oauth2callback\", async (req, res) => {\n  const code = req.query.code;\n\n  \/\/ Intercambiar el code por tokens\n  const tokenRes = await fetch(\"https:\/\/oauth2.googleapis.com\/token\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\/x-www-form-urlencoded\" },\n    body: new URLSearchParams({\n      code,\n      client_id: \"530531166533-ja979dbnvq437vi1o17l6706s2v7ffpn.apps.googleusercontent.com\",\n      client_secret: \"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\",\n      redirect_uri: \"https:\/\/utelvt.edu.ec\/biblioteca\/noticias-biblioteca\/oauth2callback\",\n      grant_type: \"authorization_code\"\n    })\n  });\n\n  const tokens = await tokenRes.json();\n  const accessToken = tokens.access_token;\n\n  if (!accessToken) {\n    return res.status(400).send(\"No se pudo obtener el access_token\");\n  }\n\n  \/\/ Leer correos de Gmail (solo remitentes deseados)\n  const mailsRes = await fetch(\n    \"https:\/\/gmail.googleapis.com\/gmail\/v1\/users\/me\/messages?q=from:elibro.com OR from:magisterioeditorial.com\",\n    { headers: { Authorization: `Bearer ${accessToken}` } }\n  );\n\n  const mails = await mailsRes.json();\n\n  if (!mails.messages) {\n    return res.send(\"No se encontraron correos de esos remitentes.\");\n  }\n\n  for (const msg of mails.messages) {\n    const detalleRes = await fetch(\n      `https:\/\/gmail.googleapis.com\/gmail\/v1\/users\/me\/messages\/${msg.id}`,\n      { headers: { Authorization: `Bearer ${accessToken}` } }\n    );\n    const detalle = await detalleRes.json();\n\n    const asunto = detalle.payload.headers.find(h => h.name === \"Subject\")?.value || \"Sin asunto\";\n    const fecha = new Date(parseInt(detalle.internalDate));\n    const cuerpo = detalle.snippet;\n\n    \/\/ Insertar en Supabase\n    const { error } = await supabase.from(\"calendario\").insert({\n      fecha: fecha.toISOString().split(\"T\")[0],\n      titulo: asunto,\n      descripcion: cuerpo,\n      imagen: null\n    });\n\n    if (error) {\n      console.error(\"Error insertando en Supabase:\", error);\n    }\n  }\n\n  res.send(\"Correos importados al calendario\");\n});\n\napp.listen(3000, () => console.log(\"Servidor corriendo en puerto 3000\"));\n\n<\/script>\n\n<style>\n\/* Header fijo arriba *\/\n.modal-header {\n  position: absolute;\n  top: 10px;\n  right: 10px;   \/* separado del borde derecho *\/\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  z-index: 20;\n}\n\n\/* Bot\u00f3n cerrar (X) *\/\n.cerrar {\n  color: #008000;\n  font-size: 28px;\n  font-weight: bold;\n  cursor: pointer;\n  line-height: 1;\n  margin: 0;\n  padding: 0;\n}\n.cerrar:hover { color: #cc0000; }\n\n\/* Men\u00fa de tres puntitos *\/\n.menu-publicacion { position: relative; margin-top: 2px; }\n.menu-toggle {\n  background: none;\n  color: #333;\n  border: none;\n  font-size: 22px;\n  cursor: pointer;\n  transition: transform 0.2s ease, color 0.2s ease;\n}\n.menu-toggle:hover { color: #008000; transform: rotate(90deg); }\n\n.menu-opciones {\n  display: none;\n  position: absolute;\n  top: 28px;\n  right: 0;\n  background: #fff;\n  border: 1px solid #ddd;\n  box-shadow: 0 6px 12px rgba(0,0,0,0.15);\n  border-radius: 6px;\n  z-index: 200;\n  overflow: hidden;\n  animation: fadeIn 0.2s ease;\n}\n.menu-opciones button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 500;\n  transition: background 0.2s ease, color 0.2s ease;\n}\n.menu-opciones .editar { color: #007bff; }\n.menu-opciones .editar:hover { background: #e6f0ff; color: #0056b3; }\n.menu-opciones .eliminar { color: #dc3545; }\n.menu-opciones .eliminar:hover { background: #ffe6e6; color: #a71d2a; }\n\n@keyframes fadeIn { from {opacity:0; transform:translateY(-5px);} to {opacity:1; transform:translateY(0);} }\n\n\/* Modal *\/\n.modal {\n  display: none;\n  position: fixed;\n  z-index: 3000;\n  left: 0; top: 0;\n  width: 100%; height: 100%;\n  background: rgba(0,0,0,0.85);\n  backdrop-filter: blur(4px);\n}\n\n\/* Modal contenido *\/\n.modal-contenido {\n  position: relative;\n  background: linear-gradient(135deg, #ffffff, #f4f4f4);\n  margin: 2% auto;\n  padding: 30px 20px 20px;\n  border-radius: 0;\n  width: 78%;\n  max-width: 965px;\n  max-height: 88vh;\n  overflow: hidden; \/* sin scroll global *\/\n  box-shadow: 0 12px 30px rgba(0,0,0,0.5);\n  animation: aparecer 0.4s ease-out;\n  border: 2px solid #008000;\n}\n\n@keyframes aparecer { from {transform:translateY(-20px); opacity:0;} to {transform:translateY(0); opacity:1;} }\n\n\/* Cuerpo del modal *\/\n.modal-body {\n  display: flex;\n  gap: 30px;\n  align-items: flex-start;\n  justify-content: flex-start;\npadding-top: 8px; \/* antes 40px *\/\n  padding-bottom: 40px;\n  height: calc(88vh - 30px);\n  overflow: hidden;   \/* quitamos scroll nativo *\/\n}\n\n\/* Contenido que scrollea (ocultamos barra nativa) *\/\n.scroll-content {\n  max-height: calc(88vh - 140px);\n  overflow-y: scroll;\n  scrollbar-width: none;              \/* Firefox *\/\n}\n.scroll-content::-webkit-scrollbar {  \/* Chrome\/Edge\/Safari *\/\n  display: none;\n}\n\n.custom-scrollbar {\n  position: absolute;\n  right: 25px;        \/* separado del borde derecho *\/\n  top: 50px;          \/* &#x1f448; aire arriba *\/\n  bottom: 50px;       \/* &#x1f448; aire abajo *\/\n  width: 8px;\n  background: transparent;\n}\n\n\n.custom-thumb {\n  position: absolute;\n  left: 0;\n  width: 100%;\n  height: 40px; \/* se recalcula en JS *\/\n  background: #008000;\n  border-radius: 4px;\n  cursor: grab;\n  transition: background 0.2s ease;\n}\n.custom-thumb:active {\n  cursor: grabbing;\n  background: #006600;\n}\n\n\/* Carrusel fijo con proporci\u00f3n 4:5 *\/\n.carrusel {\n  flex: 0 0 auto;\n  width: 380px;\n  aspect-ratio: 4 \/ 5;\n  background: #000;\n  border: 2px solid #008000;\n  box-shadow: inset 0 0 20px rgba(0,128,0,0.6);\n  position: sticky;\n  top: 200;\n  align-self: flex-start;\n\n  \/* &#x1f447; centrado absoluto *\/\n  display: flex;\n  align-items: center;   \/* centra verticalmente *\/\n  justify-content: center; \/* centra horizontalmente *\/\n}\n\n.carrusel img {\n  max-width: 100%;\n  max-height: 100%;\n  object-fit: contain;\n  display: none;\n}\n\n.carrusel img.active {\n  display: block;\n}\n\n\n\n\n\/* Informaci\u00f3n de la publicaci\u00f3n *\/\n.info-publicacion {\n  flex: 1;\n  min-width: 400px;\n  max-width: 600px;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-start;\n  border-left: 3px solid #008000;\n  padding-left: 25px;\n  padding-right: 25px;\n  box-sizing: border-box;\n  overflow: visible; \/* sin scroll propio *\/\n}\n\n\/* T\u00edtulo dentro de info-publicacion *\/\n.info-publicacion h2 {\n  font-size: 2rem;\n  font-weight: bold;\n  color: #008000;\n  margin-bottom: 15px;\n  border-bottom: 2px solid #008000;\n  padding-bottom: 8px;\n  text-transform: uppercase;\n  letter-spacing: 1px;\n  line-height: 1.4;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\n\/* Descripci\u00f3n *\/\n#modal-descripcion {\n  font-size: 1rem;\n  color: #333;\n  line-height: 1.6;\n  margin-top: 15px;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n#modal-descripcion::after {\n  content: \"\";\n  display: block;\n  height: 30px; \/* aire extra al final *\/\n}\n\n  .contenedor-principal {\n    display: flex;\n    gap: 20px;\n    max-width: 1200px;\n    margin: 20px auto;\n    padding: 0 1rem;\n    padding-bottom: 60px;\n  }\n\n  .columna-izquierda { flex: 0 0 80%; }\n  .columna-derecha {\n    flex: 0 0 20%;\n    background: #f9f9f9;\n    border-radius: 12px;\n    padding: 15px;\n    box-shadow: 0 2px 6px rgba(0,0,0,0.1);\n  }\n\n\/* Portada principal *\/\n.portada {\n  width: 100%;\n  aspect-ratio: 16 \/ 9;\n  border-radius: 0;\n  overflow: hidden;\n  margin-bottom: 30px;\n  box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n  position: relative;\n  cursor: pointer;\n}\n\n\/* &#x1f447; Afecta solo a las im\u00e1genes directas (slides) de la portada *\/\n.portada > img {\n  width: 100%; height: 100%; object-fit: cover;\n  position: absolute; top: 0; left: 0;\n  opacity: 0; transition: opacity 1s ease-in-out;\n  z-index: 0;\n}\n.portada > img.active { opacity: 1; }\n\n\/* Overlay y texto *\/\n.portada .overlay {\n  position: absolute; bottom: 0; left: 0;\n  width: 100%; height: 100%;\n  background: linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0));\n  z-index: 1;\n}\n\n.texto-portada {\n  position: absolute;\n  bottom: 20px; \n  left: 20px;\n  text-align: left; \n  z-index: 2;\n  display: flex;\n  flex-direction: column; \/* apila t\u00edtulo, fecha y ver m\u00e1s *\/\n  gap: 6px;\n}\n\n.texto-portada h2 {\n  color: #fff; \n  font-size: 2rem; \n  font-weight: bold; \n  margin: 0;\n}\n\n.texto-portada small {\n  color: #fff; \n  font-size: 0.9rem; \n  font-weight: bold;\n}\n\n.texto-portada .ver-mas {\n  font-size: 1rem;\n  color: #ffffff;\n  font-weight: bold;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: color 0.3s ease;\n}\n\n.texto-portada .ver-mas img {\n  width: 20px;\n  height: 20px;\n  transition: transform 0.3s ease;\n}\n\n.texto-portada .ver-mas:hover {\n  color: #cccccc;\n  text-decoration: underline;\n}\n\n.texto-portada .ver-mas:hover img {\n  transform: translateX(5px);\n}\n\n\n\/* Indicadores en portada *\/\n.portada .indicadores {\n  position: absolute;\n  bottom: 12px;\n  left: 50%;\n  transform: translateX(-50%);\n  display: flex;\n  gap: 6px;\n  z-index: 3;\n}\n.portada .indicadores span {\n  width: 12px;\n  height: 12px;\n  background: rgba(255,255,255,0.5);\n  border-radius: 50%;\n  transition: background 0.3s ease;\n}\n.portada .indicadores span.active { background: #fff; }\n\n\/* Grid de publicaciones debajo de la portada *\/\n.grid-publicaciones {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr); \/* &#x1f448; siempre 3 columnas *\/\n  gap: 25px;\n  margin-top: 20px;\n}\n\n\/* Alternancia de colores institucionales *\/\n.grid-publicaciones .publicacion:nth-child(odd) {\n  background: #004003; \/* verde oscuro *\/\n  border: 2px solid #007504;\n  border-radius: 0;\n  color: #fff;\n}\n.grid-publicaciones .publicacion:nth-child(even) {\n  background: #007504; \/* verde claro *\/\n  border: 2px solid #004003;\n  border-radius: 0;\n  color: #fff;\n}\n\n.publicacion {\n  overflow: hidden;\n  box-shadow: 0 6px 16px rgba(0,0,0,0.35);\n  cursor: pointer;\n  transition: box-shadow 0.3s ease; \/* &#x1f448; solo sombra *\/\n  display: flex;\n  flex-direction: column;\n}\n\n.publicacion:hover {\n  \/* &#x1f447; ya no hay movimiento, solo aumenta la sombra *\/\n  box-shadow: 0 12px 28px rgba(0,0,0,0.5);\n}\n\n\n\/* Imagen superior *\/\n.imagenes-tarjeta {\n  position: relative;\n  width: 100%;\n  height: 200px;\n  overflow: hidden;\n  border-bottom: 3px solid rgba(255,255,255,0.3);\n}\n.imagenes-tarjeta img {\n  position: absolute;\n  top: 0; left: 0;\n  width: 100%; height: 100%;\n  object-fit: cover;\n  opacity: 0;\n  transition: opacity 1s ease-in-out;\n}\n.imagenes-tarjeta img.active { opacity: 1; }\n\n\/* Indicadores *\/\n.indicadores {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  transform: translateX(-50%);\n  display: flex;\n  gap: 6px;\n}\n.indicadores span {\n  width: 10px;\n  height: 10px;\n  background: rgba(255,255,255,0.6);\n  border-radius: 50%;\n  transition: background 0.3s ease;\n}\n.indicadores span.active { background: #fff; }\n\n\/* T\u00edtulo *\/\n.publicacion h2 {\n  font-size: 1.3rem;\n  font-weight: bold;\n  color: #ffffff;\n  margin: 15px;\n  border-bottom: 2px solid rgba(255,255,255,0.3);\n  padding-bottom: 6px;\n}\n\n\/* Fecha *\/\n.publicacion small {\n  font-size: 0.9rem;\n  color: #f0f0f0;\n  margin: 0 15px 10px;\n  display: block;\n  font-weight: bold;\n}\n\n\/* &#x2705; Descripci\u00f3n sencilla (vista previa limitada a 2 l\u00edneas) *\/\n.publicacion p {\n  font-size: 0.95rem;\n  color: #ffffff;\n  margin: 0 15px 15px;\n  line-height: 1.5;\n\n  \/* &#x1f447; Limitar a 2 l\u00edneas con puntos suspensivos *\/\n  display: -webkit-box;\n  -webkit-line-clamp: 2;       \/* m\u00e1ximo 2 l\u00edneas *\/\n  -webkit-box-orient: vertical;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  \/* &#x1f447; asegurar que el texto se rompa en varias l\u00edneas *\/\n  white-space: normal;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\n\/* Bot\u00f3n ver m\u00e1s *\/\n.publicacion .ver-mas {\n  font-size: 0.95rem;\n  color: #ffffff;\n  font-weight: bold;\n  margin: 0 15px 15px;\n  align-self: flex-start;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: color 0.3s ease;\n}\n.publicacion .ver-mas img {\n  width: 18px;\n  height: 18px;\n  transition: transform 0.3s ease;\n}\n.publicacion .ver-mas:hover {\n  color: #cccccc;\n  text-decoration: underline;\n}\n.publicacion .ver-mas:hover img {\n  transform: translateX(5px);\n}\n\n  .columna-derecha h3 { margin-bottom: 10px; color: #008000; }\n\n\n\/* &#x2705; Flechas restauradas como antes *\/\n.flecha {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n  background: rgba(0,0,0,0.6); \/* fondo oscuro semitransparente *\/\n  border: none;\n  border-radius: 50%; \/* redondeadas *\/\n  padding: 10px;\n  cursor: pointer;\n  opacity: 0.85;\n  transition: transform 0.3s ease, opacity 0.3s ease, box-shadow 0.3s ease;\n  box-shadow: 0 2px 6px rgba(0,0,0,0.4);\n}\n.flecha img {\n  width: 26px;\n  height: 26px;\n  display: block;\n}\n.flecha:hover {\n  transform: translateY(-50%) scale(1.15);\n  opacity: 1;\n  box-shadow: 0 4px 10px rgba(0,0,0,0.6);\n}\n.flecha.izquierda { left: 12px; }\n.flecha.derecha { right: 12px; }\n\n\n.info-publicacion small {\n  font-size: 1rem;\n  color: #555;\n  margin-bottom: 15px;\n  border-bottom: 1px dashed #aaa;\n  padding-bottom: 6px;\n}\n\n.info-publicacion p {\n  font-size: 1rem;\n  color: #333;\n  line-height: 1.6;\n  margin-top: 15px;\n\n  \/* &#x1f447; reglas solo para que el texto se ajuste bien *\/\n  white-space: normal;\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n}\n\n\n\/* Portada secundaria alineada en altura con las tarjetas *\/\n.portada-pequena {\n  width: 100%;              \/* ocupa sus 2 columnas *\/\n  height: 360px;            \/* &#x1f448; misma altura aproximada que una tarjeta completa *\/\n  margin: 0;\n  position: relative;\n  overflow: hidden;\n  box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n  cursor: pointer;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-end; \/* texto al fondo como en portada principal *\/\n}\n\n.portada-pequena > img {\n  position: absolute;\n  top: 0; left: 0;\n  width: 100%; height: 100%;\n  object-fit: cover;\n  opacity: 0;\n  transition: opacity 1s ease-in-out;\n}\n.portada-pequena > img.active { opacity: 1; }\n\n.portada-pequena .overlay {\n  position: absolute;\n  bottom: 0; left: 0;\n  width: 100%; height: 100%;\n  background: linear-gradient(to top, rgba(0,0,0,0.6), rgba(0,0,0,0));\n}\n\n.portada-pequena .texto-portada {\n  position: relative;\n  z-index: 2;\n  padding: 12px 16px;\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n}\n\n.portada-pequena .texto-portada h2 {\n  color: #fff;\n  font-size: 1.2rem;\n  font-weight: bold;\n  margin: 0;\n}\n.portada-pequena .texto-portada small {\n  color: #fff;\n  font-size: 0.85rem;\n  font-weight: bold;\n}\n.portada-pequena .texto-portada .ver-mas {\n  font-size: 0.95rem;\n  color: #fff;\n  font-weight: bold;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  cursor: pointer;\n  transition: color 0.3s ease;\n}\n.portada-pequena .texto-portada .ver-mas img {\n  width: 18px;\n  height: 18px;\n  transition: transform 0.3s ease;\n}\n.portada-pequena .texto-portada .ver-mas:hover {\n  color: #ccc;\n  text-decoration: underline;\n}\n.portada-pequena .texto-portada .ver-mas:hover img {\n  transform: translateX(5px);\n}\n\n\/* Indicadores *\/\n.portada-pequena .indicadores {\n  position: absolute;\n  bottom: 12px;\n  left: 50%;\n  transform: translateX(-50%);\n  display: flex;\n  gap: 6px;\n  z-index: 3;\n}\n.portada-pequena .indicadores span {\n  width: 10px;\n  height: 10px;\n  background: rgba(255,255,255,0.5);\n  border-radius: 50%;\n  transition: background 0.3s ease;\n}\n.portada-pequena .indicadores span.active { background: #fff; }\n\/* === Fila 3: control real de ancho y alto de la portada secundaria === *\/\n.grid-publicaciones.fila3 {\n  display: grid;\n  \/* &#x1f447; portada m\u00e1s ancha, tarjeta m\u00e1s fina (ajusta si quieres 72\/28, 75\/25, etc.) *\/\n  grid-template-columns: 75% 25%;\n  gap: 25px;\n  align-items: start;\n}\n\n\/* &#x1f447; Aumenta altura de la portada secundaria sin mover su contenido interno *\/\n.grid-publicaciones.fila3 .portada-pequena {\n  height: 380px;            \/* antes 360px \u2014ajusta a 400\/420\/440 *\/\n  width: 100%;              \/* ocupa su columna completa *\/\n  box-sizing: border-box;   \/* evita que bordes\/sombras alteren el c\u00e1lculo *\/\n  position: relative;\n  overflow: hidden;\n  box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n  cursor: pointer;\n}\n\n\/* &#x1f447; Mant\u00e9n overlay y texto anclados sin moverse al cambiar altura *\/\n.grid-publicaciones.fila3 .portada-pequena .overlay {\n  position: absolute;\n  inset: 0; \/* top\/right\/bottom\/left: 0 *\/\n  background: linear-gradient(to top, rgba(0,0,0,0.6), rgba(0,0,0,0));\n}\n\n.grid-publicaciones.fila3 .portada-pequena .texto-portada {\n  position: absolute;\n  left: 16px;\n  right: 16px;\n  bottom: 16px; \/* siempre pegado al fondo *\/\n  z-index: 2;\n  padding: 0;   \/* evita que el padding altere la percepci\u00f3n de altura *\/\n}\n\n\/* &#x1f447; Asegura que las im\u00e1genes llenen el nuevo alto sin deformarse *\/\n.grid-publicaciones.fila3 .portada-pequena > img {\n  position: absolute;\n  top: 0; left: 0;\n  width: 100%; height: 100%;\n  object-fit: cover;\n  opacity: 0;\n  transition: opacity 1s ease-in-out;\n}\n.grid-publicaciones.fila3 .portada-pequena > img.active { opacity: 1; }\n\n\n\n<\/style>\n\n<script>\nasync function cargarPublicaciones() {\n  const portadaDiv = document.getElementById(\"portada\");\n  const publicacionesDiv = document.getElementById(\"publicaciones\");\n  const tituloEl = document.getElementById(\"portada-titulo\");\n  const fechaEl = document.getElementById(\"portada-fecha\");\n  const textoPortada = portadaDiv.querySelector(\".texto-portada\");\n\n  const { data, error } = await sb\n    .from(\"imagenes_portada\")\n    .select(\"*\")\n    .order(\"created_at\", { ascending: false });\n\n  if (error) {\n    publicacionesDiv.innerHTML = `<p style=\"color:red;\">&#x274c; Error al cargar publicaciones: ${error.message}<\/p>`;\n    return;\n  }\n  if (!data || data.length === 0) {\n    publicacionesDiv.innerHTML = \"<p>No hay publicaciones disponibles.<\/p>\";\n    return;\n  }\n\n  \/\/ === Portada principal con prioridad ===\n  let portada;\n\n  \/\/ Consultar si hay noticia prioritaria\n  const { data: prioridad } = await sb\n    .from(\"noticias_prioritarias\")\n    .select(\"noticia_id\")\n    .order(\"id\", { ascending: false })\n    .limit(1);\n\n  if (prioridad && prioridad.length > 0) {\n    \/\/ Buscar la publicaci\u00f3n correspondiente en imagenes_portada\n    portada = data.find(pub => pub.id === prioridad[0].noticia_id);\n  }\n\n  \/\/ Si no hay prioritaria o no se encuentra, usar la \u00faltima creada\n  if (!portada) {\n    portada = data[0];\n  }\n\n  \/\/ === Renderizar portada principal ===\n  tituloEl.textContent = portada.titulo;\n  fechaEl.textContent = new Date(portada.created_at).toLocaleDateString();\n  portadaDiv.querySelectorAll(\":scope > img\").forEach(el => el.remove());\n  portada.imagenes.forEach((url, i) => {\n    const img = document.createElement(\"img\");\n    img.src = url;\n    if (i === 0) img.classList.add(\"active\");\n    portadaDiv.appendChild(img);\n  });\n\n  \/\/ Indicadores portada\n  let portadaIndicadores = portadaDiv.querySelector(\".indicadores\");\n  if (!portadaIndicadores) {\n    portadaIndicadores = document.createElement(\"div\");\n    portadaIndicadores.classList.add(\"indicadores\");\n    portadaDiv.appendChild(portadaIndicadores);\n  }\n  portadaIndicadores.innerHTML = \"\";\n  if (portada.imagenes.length > 1) {\n    portada.imagenes.forEach((_, j) => {\n      const span = document.createElement(\"span\");\n      if (j === 0) span.classList.add(\"active\");\n      portadaIndicadores.appendChild(span);\n    });\n  }\n\n  \/\/ Bot\u00f3n ver m\u00e1s\n  let portadaVerMas = textoPortada.querySelector(\".ver-mas\");\n  if (!portadaVerMas) {\n    portadaVerMas = document.createElement(\"span\");\n    portadaVerMas.classList.add(\"ver-mas\");\n    portadaVerMas.innerHTML = `ver m\u00e1s <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">`;\n    fechaEl.insertAdjacentElement(\"afterend\", portadaVerMas);\n  }\n  portadaVerMas.onclick = (e) => { e.stopPropagation(); abrirModal(portada); };\n\n  \/\/ Men\u00fa de opciones (\u22ee) en portada principal\n  let menuPortada = portadaDiv.querySelector(\".menu-publicacion\");\n  if (!menuPortada) {\n    menuPortada = document.createElement(\"div\");\n    menuPortada.classList.add(\"menu-publicacion\");\n    menuPortada.innerHTML = `\n      <button class=\"menu-toggle\">\u22ee<\/button>\n      <div class=\"menu-opciones\">\n        <button class=\"editar\">Editar<\/button>\n        <button class=\"eliminar\">Eliminar<\/button>\n      <\/div>\n    `;\n    portadaDiv.appendChild(menuPortada);\n  }\n\n  \/\/ L\u00f3gica de men\u00fa\n  const toggleBtn = menuPortada.querySelector(\".menu-toggle\");\n  const opcionesMenu = menuPortada.querySelector(\".menu-opciones\");\n\n  toggleBtn.addEventListener(\"click\", (e) => {\n    e.stopPropagation();\n    opcionesMenu.style.display = opcionesMenu.style.display === \"block\" ? \"none\" : \"block\";\n  });\n\n  \/\/ Editar publicaci\u00f3n\n  opcionesMenu.querySelector(\".editar\").addEventListener(\"click\", (e) => {\n    e.stopPropagation();\n    fetch(`\/api\/publicaciones\/${portada.id}`)\n      .then(res => res.json())\n      .then(data => {\n        abrirModal(data);\n      });\n  });\n\n  \/\/ Eliminar publicaci\u00f3n\n  opcionesMenu.querySelector(\".eliminar\").addEventListener(\"click\", (e) => {\n    e.stopPropagation();\n    if (confirm(\"\u00bfSeguro que quieres eliminar esta publicaci\u00f3n?\")) {\n      fetch(`\/api\/publicaciones\/${portada.id}`, { method: \"DELETE\" })\n        .then(() => {\n          portadaDiv.remove();\n        });\n    }\n  });\n\n  \/\/ Rotaci\u00f3n autom\u00e1tica portada\n  if (portada.imagenes.length > 1) {\n    let current = 0;\n    setInterval(() => {\n      const imgs = portadaDiv.querySelectorAll(\":scope > img\");\n      const dots = portadaIndicadores.querySelectorAll(\"span\");\n      imgs[current].classList.remove(\"active\");\n      if (dots[current]) dots[current].classList.remove(\"active\");\n      current = (current + 1) % imgs.length;\n      imgs[current].classList.add(\"active\");\n      if (dots[current]) dots[current].classList.add(\"active\");\n    }, 5000);\n  }\n\n  \/\/ === Resto de publicaciones ===\n  const restantes = data.filter(pub => pub.id !== portada.id);\n  publicacionesDiv.innerHTML = \"\";\n\n\n  \/\/ === Fila 2: tres tarjetas ===\n  publicacionesDiv.innerHTML = restantes.slice(0, 3).map((pub, idx) => {\n    const descripcionCorta = pub.descripcion.length > 120 \n      ? pub.descripcion.substring(0, 120) + \"...\" \n      : pub.descripcion;\n    const imagenesHTML = pub.imagenes.map((url, i) => `\n      <img decoding=\"async\" src=\"${url}\" ${i === 0 ? 'class=\"active\"' : ''}>\n    `).join(\"\");\n    return `\n      <div class=\"publicacion\" data-index=\"${idx}\">\n        <div class=\"imagenes-tarjeta\">\n          ${imagenesHTML}\n          <div class=\"indicadores\"><\/div>\n        <\/div>\n        <h2>${pub.titulo}<\/h2>\n        <small>${new Date(pub.created_at).toLocaleDateString()}<\/small>\n        <p>${descripcionCorta}<\/p>\n        <span class=\"ver-mas\">\n          ver m\u00e1s\n          <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">\n        <\/span>\n      <\/div>\n    `;\n  }).join(\"\");\n\n  const tarjetasFila2 = publicacionesDiv.querySelectorAll(\".publicacion\");\ntarjetasFila2.forEach((card) => {\n  const idx = parseInt(card.getAttribute(\"data-index\"), 10);\n  const pub = restantes[idx];\n  if (!pub) return;\n\n  \/\/ Abrir modal al hacer click\n  card.addEventListener(\"click\", () => abrirModal(pub));\n  const verMas = card.querySelector(\".ver-mas\");\n  if (verMas) {\n    verMas.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      abrirModal(pub);\n    });\n  }\n\n  \/\/ Indicadores + rotaci\u00f3n al pasar el mouse\n  const indicadores = card.querySelector(\".indicadores\");\n  if (pub.imagenes.length > 1) {\n    indicadores.innerHTML = \"\";\n    pub.imagenes.forEach((_, j) => {\n      const dot = document.createElement(\"span\");\n      if (j === 0) dot.classList.add(\"active\");\n      indicadores.appendChild(dot);\n    });\n    let current = 0;\n    let interval;\n    card.addEventListener(\"mouseenter\", () => {\n      const imgs = card.querySelectorAll(\".imagenes-tarjeta img\");\n      const dots = indicadores.querySelectorAll(\"span\");\n      interval = setInterval(() => {\n        imgs[current].classList.remove(\"active\");\n        dots[current].classList.remove(\"active\");\n        current = (current + 1) % imgs.length;\n        imgs[current].classList.add(\"active\");\n        dots[current].classList.add(\"active\");\n      }, 3000);\n    });\n    card.addEventListener(\"mouseleave\", () => clearInterval(interval));\n  }\n});\n  \/\/ === Fila 3: portada secundaria + tarjeta derecha ===\n  if (restantes[3]) {\n    const pubSmall = restantes[3];\n    const fila3 = document.createElement(\"div\");\n    fila3.classList.add(\"grid-publicaciones\", \"fila3\");\n    fila3.style.gridTemplateColumns = \"2fr 1fr\";\n\n    \/\/ Portada secundaria\n    const portadaPequena = document.createElement(\"div\");\n    portadaPequena.className = \"portada portada-pequena\";\n    pubSmall.imagenes.forEach((url, i) => {\n      const img = document.createElement(\"img\");\n      img.src = url;\n      if (i === 0) img.classList.add(\"active\");\n      portadaPequena.appendChild(img);\n    });\n    const overlay = document.createElement(\"div\");\n    overlay.className = \"overlay\";\n    const texto = document.createElement(\"div\");\n    texto.className = \"texto-portada\";\n    texto.innerHTML = `\n      <h2>${pubSmall.titulo}<\/h2>\n      <small>${new Date(pubSmall.created_at).toLocaleDateString()}<\/small>\n      <span class=\"ver-mas\">ver m\u00e1s <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\"><\/span>\n    `;\n    portadaPequena.appendChild(overlay);\n    portadaPequena.appendChild(texto);\n    fila3.appendChild(portadaPequena);\n\n    \/\/ Abrir modal desde portada secundaria\n    portadaPequena.addEventListener(\"click\", () => abrirModal(pubSmall));\n    const verMasSmall = texto.querySelector(\".ver-mas\");\n    if (verMasSmall) {\n      verMasSmall.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        abrirModal(pubSmall);\n      });\n    }\n\n    \/\/ Indicadores portada secundaria + rotaci\u00f3n autom\u00e1tica\n    if (pubSmall.imagenes.length > 1) {\n      const indicadores = document.createElement(\"div\");\n      indicadores.className = \"indicadores\";\n      pubSmall.imagenes.forEach((_, j) => {\n        const dot = document.createElement(\"span\");\n        if (j === 0) dot.classList.add(\"active\");\n        indicadores.appendChild(dot);\n      });\n      portadaPequena.appendChild(indicadores);\n\n      let currentSmall = 0;\n      setInterval(() => {\n        const imgs = portadaPequena.querySelectorAll(\":scope > img\");\n        const dots = indicadores.querySelectorAll(\"span\");\n        imgs[currentSmall].classList.remove(\"active\");\n        dots[currentSmall].classList.remove(\"active\");\n        currentSmall = (currentSmall + 1) % imgs.length;\n        imgs[currentSmall].classList.add(\"active\");\n        dots[currentSmall].classList.add(\"active\");\n      }, 5000);\n    }\n\n    \/\/ Tarjeta secundaria\n    if (restantes[4]) {\n      const pubCard = restantes[4];\n      const descripcionCorta = pubCard.descripcion.length > 120 \n        ? pubCard.descripcion.substring(0, 120) + \"...\" \n        : pubCard.descripcion;\n      const imagenesHTML = pubCard.imagenes.map((url, i) => `\n        <img decoding=\"async\" src=\"${url}\" ${i === 0 ? 'class=\"active\"' : ''}>\n      `).join(\"\");\n      const cardHTML = `\n        <div class=\"publicacion\">\n          <div class=\"imagenes-tarjeta\">\n            ${imagenesHTML}\n            <div class=\"indicadores\"><\/div>\n          <\/div>\n          <h2>${pubCard.titulo}<\/h2>\n          <small>${new Date(pubCard.created_at).toLocaleDateString()}<\/small>\n          <p>${descripcionCorta}<\/p>\n          <span class=\"ver-mas\">\n            ver m\u00e1s\n            <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">\n          <\/span>\n        <\/div>\n      `;\n      const tarjetaWrapper = document.createElement(\"div\");\n      tarjetaWrapper.innerHTML = cardHTML;\n      const tarjetaEl = tarjetaWrapper.firstElementChild;\n      fila3.appendChild(tarjetaEl);\n\n      \/\/ Abrir modal desde tarjeta secundaria\n      tarjetaEl.addEventListener(\"click\", () => abrirModal(pubCard));\n      const verMasCard = tarjetaEl.querySelector(\".ver-mas\");\n      if (verMasCard) {\n        verMasCard.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          abrirModal(pubCard);\n        });\n      }\n\n      \/\/ Indicadores + rotaci\u00f3n al pasar el mouse en tarjeta secundaria\n      const indicadoresCard = tarjetaEl.querySelector(\".indicadores\");\n      if (pubCard.imagenes.length > 1) {\n        indicadoresCard.innerHTML = \"\";\n        pubCard.imagenes.forEach((_, j) => {\n          const dot = document.createElement(\"span\");\n          if (j === 0) dot.classList.add(\"active\");\n          indicadoresCard.appendChild(dot);\n        });\n        let currentCard = 0;\n        let intervalCard;\n        tarjetaEl.addEventListener(\"mouseenter\", () => {\n          const imgs = tarjetaEl.querySelectorAll(\".imagenes-tarjeta img\");\n          const dots = indicadoresCard.querySelectorAll(\"span\");\n          intervalCard = setInterval(() => {\n            imgs[currentCard].classList.remove(\"active\");\n            dots[currentCard].classList.remove(\"active\");\n            currentCard = (currentCard + 1) % imgs.length;\n            imgs[currentCard].classList.add(\"active\");\n            dots[currentCard].classList.add(\"active\");\n          }, 3000);\n        });\n        tarjetaEl.addEventListener(\"mouseleave\", () => clearInterval(intervalCard));\n      }\n    }\n\n    publicacionesDiv.insertAdjacentElement(\"afterend\", fila3);\n  \n      \/\/ === Fila 4: tres tarjetas ===\n  if (restantes.length >= 8) {\n    const fila4 = document.createElement(\"div\");\n    fila4.classList.add(\"grid-publicaciones\", \"fila4\");\n\n    for (let i = 5; i <= 7; i++) {\n      const pub = restantes[i];\n      if (!pub) continue;\n\n      const descripcionCorta = pub.descripcion.length > 120 \n        ? pub.descripcion.substring(0, 120) + \"...\" \n        : pub.descripcion;\n      const imagenesHTML = pub.imagenes.map((url, j) => `\n        <img decoding=\"async\" src=\"${url}\" ${j === 0 ? 'class=\"active\"' : ''}>\n      `).join(\"\");\n      const cardHTML = `\n        <div class=\"publicacion\">\n          <div class=\"imagenes-tarjeta\">\n            ${imagenesHTML}\n            <div class=\"indicadores\"><\/div>\n          <\/div>\n          <h2>${pub.titulo}<\/h2>\n          <small>${new Date(pub.created_at).toLocaleDateString()}<\/small>\n          <p>${descripcionCorta}<\/p>\n          <span class=\"ver-mas\">\n            ver m\u00e1s\n            <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">\n          <\/span>\n        <\/div>\n      `;\n      const tarjetaWrapper = document.createElement(\"div\");\n      tarjetaWrapper.innerHTML = cardHTML;\n      const tarjetaEl = tarjetaWrapper.firstElementChild;\n      fila4.appendChild(tarjetaEl);\n\n      \/\/ Abrir modal desde tarjeta\n      tarjetaEl.addEventListener(\"click\", () => abrirModal(pub));\n      const verMas = tarjetaEl.querySelector(\".ver-mas\");\n      if (verMas) {\n        verMas.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          abrirModal(pub);\n        });\n      }\n\n      \/\/ Indicadores + rotaci\u00f3n al pasar el mouse\n      const indicadores = tarjetaEl.querySelector(\".indicadores\");\n      if (pub.imagenes.length > 1) {\n        indicadores.innerHTML = \"\";\n        pub.imagenes.forEach((_, j) => {\n          const dot = document.createElement(\"span\");\n          if (j === 0) dot.classList.add(\"active\");\n          indicadores.appendChild(dot);\n        });\n        let currentCard = 0;\n        let intervalCard;\n        tarjetaEl.addEventListener(\"mouseenter\", () => {\n          const imgs = tarjetaEl.querySelectorAll(\".imagenes-tarjeta img\");\n          const dots = indicadores.querySelectorAll(\"span\");\n          intervalCard = setInterval(() => {\n            imgs[currentCard].classList.remove(\"active\");\n            dots[currentCard].classList.remove(\"active\");\n            currentCard = (currentCard + 1) % imgs.length;\n            imgs[currentCard].classList.add(\"active\");\n            dots[currentCard].classList.add(\"active\");\n          }, 3000);\n        });\n        tarjetaEl.addEventListener(\"mouseleave\", () => clearInterval(intervalCard));\n      }\n    }\n\n    \/\/ Insertar fila 4 justo despu\u00e9s de fila3\n    fila3.insertAdjacentElement(\"afterend\", fila4);\n  \n      \/\/ === Bucle infinito para repetir patr\u00f3n 1\u21922\u21923\u21924 ===\n      let start = 8;                 \/\/ ya consumiste restantes[0..7] en filas 2,3,4\n      let ultimaFilaLocal = fila4;   \/\/ referencia a la \u00faltima fila creada\n      let alternaFila3 = 0;          \/\/ alterna izquierda\/derecha en fila 3\n\n      while (start < restantes.length) {\n        \/\/ --- Fila tipo 1: portada principal ---\n        const pubPortada = restantes[start];\n        if (!pubPortada) break;\n\n        const filaPortada = document.createElement(\"div\");\n        filaPortada.classList.add(\"portada\", \"fila-portada\");\n\n        \/\/ im\u00e1genes\n        pubPortada.imagenes.forEach((url, i) => {\n          const img = document.createElement(\"img\");\n          img.src = url;\n          if (i === 0) img.classList.add(\"active\");\n          filaPortada.appendChild(img);\n        });\n\n        \/\/ overlay + texto\n        const overlayP = document.createElement(\"div\");\n        overlayP.className = \"overlay\";\n        const textoP = document.createElement(\"div\");\n        textoP.className = \"texto-portada\";\n        textoP.innerHTML = `\n          <h2>${pubPortada.titulo}<\/h2>\n          <small>${new Date(pubPortada.created_at).toLocaleDateString()}<\/small>\n          <span class=\"ver-mas\">ver m\u00e1s <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\"><\/span>\n        `;\n        filaPortada.appendChild(overlayP);\n        filaPortada.appendChild(textoP);\n\n        \/\/ abrir modal\n        filaPortada.addEventListener(\"click\", () => abrirModal(pubPortada));\n        const verMasP = textoP.querySelector(\".ver-mas\");\n        if (verMasP) {\n          verMasP.addEventListener(\"click\", (e) => { e.stopPropagation(); abrirModal(pubPortada); });\n        }\n\n        \/\/ indicadores + rotaci\u00f3n autom\u00e1tica\n        if (pubPortada.imagenes.length > 1) {\n          const indicadoresP = document.createElement(\"div\");\n          indicadoresP.className = \"indicadores\";\n          pubPortada.imagenes.forEach((_, j) => {\n            const dot = document.createElement(\"span\");\n            if (j === 0) dot.classList.add(\"active\");\n            indicadoresP.appendChild(dot);\n          });\n          filaPortada.appendChild(indicadoresP);\n\n          let currentP = 0;\n          setInterval(() => {\n            const imgs = filaPortada.querySelectorAll(\":scope > img\");\n            const dots = indicadoresP.querySelectorAll(\"span\");\n            imgs[currentP].classList.remove(\"active\");\n            dots[currentP].classList.remove(\"active\");\n            currentP = (currentP + 1) % imgs.length;\n            imgs[currentP].classList.add(\"active\");\n            dots[currentP].classList.add(\"active\");\n          }, 5000);\n        }\n\n        ultimaFilaLocal.insertAdjacentElement(\"afterend\", filaPortada);\n        ultimaFilaLocal = filaPortada;\n        start++;\n\n        \/\/ --- Fila tipo 2: tres tarjetas ---\n        const fila2Loop = document.createElement(\"div\");\n        fila2Loop.classList.add(\"grid-publicaciones\", \"fila2\");\n\n        for (let i = start; i < start + 3; i++) {\n          const pub = restantes[i];\n          if (!pub) continue;\n\n          const descripcionCorta = pub.descripcion.length > 120 \n            ? pub.descripcion.substring(0, 120) + \"...\" \n            : pub.descripcion;\n\n          const imagenesHTML = pub.imagenes.map((url, j) => `\n            <img decoding=\"async\" src=\"${url}\" ${j === 0 ? 'class=\"active\"' : ''}>\n          `).join(\"\");\n\n          const cardHTML = `\n            <div class=\"publicacion\">\n              <div class=\"imagenes-tarjeta\">\n                ${imagenesHTML}\n                <div class=\"indicadores\"><\/div>\n              <\/div>\n              <h2>${pub.titulo}<\/h2>\n              <small>${new Date(pub.created_at).toLocaleDateString()}<\/small>\n              <p>${descripcionCorta}<\/p>\n              <span class=\"ver-mas\">ver m\u00e1s <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\"><\/span>\n            <\/div>\n          `;\n\n          const tarjetaWrapper = document.createElement(\"div\");\n          tarjetaWrapper.innerHTML = cardHTML;\n          const tarjetaEl = tarjetaWrapper.firstElementChild;\n          fila2Loop.appendChild(tarjetaEl);\n\n          \/\/ abrir modal\n          tarjetaEl.addEventListener(\"click\", () => abrirModal(pub));\n          const verMas = tarjetaEl.querySelector(\".ver-mas\");\n          if (verMas) {\n            verMas.addEventListener(\"click\", (e) => { e.stopPropagation(); abrirModal(pub); });\n          }\n\n          \/\/ indicadores + rotaci\u00f3n al hover\n          const indicadores = tarjetaEl.querySelector(\".indicadores\");\n          if (pub.imagenes.length > 1) {\n            indicadores.innerHTML = \"\";\n            pub.imagenes.forEach((_, j) => {\n              const dot = document.createElement(\"span\");\n              if (j === 0) dot.classList.add(\"active\");\n              indicadores.appendChild(dot);\n            });\n            let currentCard = 0;\n            let intervalCard;\n            tarjetaEl.addEventListener(\"mouseenter\", () => {\n              const imgs = tarjetaEl.querySelectorAll(\".imagenes-tarjeta img\");\n              const dots = indicadores.querySelectorAll(\"span\");\n              intervalCard = setInterval(() => {\n                imgs[currentCard].classList.remove(\"active\");\n                dots[currentCard].classList.remove(\"active\");\n                currentCard = (currentCard + 1) % imgs.length;\n                imgs[currentCard].classList.add(\"active\");\n                dots[currentCard].classList.add(\"active\");\n              }, 3000);\n            });\n            tarjetaEl.addEventListener(\"mouseleave\", () => clearInterval(intervalCard));\n          }\n        }\n\n        ultimaFilaLocal.insertAdjacentElement(\"afterend\", fila2Loop);\n        ultimaFilaLocal = fila2Loop;\n        start += 3;\n\n        \/\/ --- Fila tipo 3: portada secundaria + tarjeta alternando (mismo dise\u00f1o, solo orden) ---\n        const pubSmallLoop = restantes[start];\n        const pubCardLoop = restantes[start + 1];\n        if (!pubSmallLoop || !pubCardLoop) break;\n\n        const fila3Loop = document.createElement(\"div\");\n        fila3Loop.classList.add(\"grid-publicaciones\", \"fila3\");\n        fila3Loop.style.gridTemplateColumns = \"2fr 1fr\"; \/\/ mismo dise\u00f1o\n\n        \/\/ Portada secundaria\n        const portadaPequenaLoop = document.createElement(\"div\");\n        portadaPequenaLoop.className = \"portada portada-pequena\";\n        pubSmallLoop.imagenes.forEach((url, i) => {\n          const img = document.createElement(\"img\");\n          img.src = url;\n          if (i === 0) img.classList.add(\"active\");\n          portadaPequenaLoop.appendChild(img);\n        });\n        const overlayS = document.createElement(\"div\");\n        overlayS.className = \"overlay\";\n        const textoS = document.createElement(\"div\");\n        textoS.className = \"texto-portada\";\n        textoS.innerHTML = `\n          <h2>${pubSmallLoop.titulo}<\/h2>\n          <small>${new Date(pubSmallLoop.created_at).toLocaleDateString()}<\/small>\n          <span class=\"ver-mas\">ver m\u00e1s <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\"><\/span>\n        `;\n        portadaPequenaLoop.appendChild(overlayS);\n        portadaPequenaLoop.appendChild(textoS);\n\n        \/\/ abrir modal portada secundaria\n        portadaPequenaLoop.addEventListener(\"click\", () => abrirModal(pubSmallLoop));\n        const verMasSmallLoop = textoS.querySelector(\".ver-mas\");\n        if (verMasSmallLoop) {\n          verMasSmallLoop.addEventListener(\"click\", (e) => { e.stopPropagation(); abrirModal(pubSmallLoop); });\n        }\n\n        \/\/ indicadores + rotaci\u00f3n autom\u00e1tica portada secundaria\n        if (pubSmallLoop.imagenes.length > 1) {\n          const indicadoresS = document.createElement(\"div\");\n          indicadoresS.className = \"indicadores\";\n          pubSmallLoop.imagenes.forEach((_, j) => {\n            const dot = document.createElement(\"span\");\n            if (j === 0) dot.classList.add(\"active\");\n            indicadoresS.appendChild(dot);\n          });\n          portadaPequenaLoop.appendChild(indicadoresS);\n\n          let currentSmallLoop = 0;\n          setInterval(() => {\n            const imgs = portadaPequenaLoop.querySelectorAll(\":scope > img\");\n            const dots = indicadoresS.querySelectorAll(\"span\");\n            imgs[currentSmallLoop].classList.remove(\"active\");\n            dots[currentSmallLoop].classList.remove(\"active\");\n            currentSmallLoop = (currentSmallLoop + 1) % imgs.length;\n            imgs[currentSmallLoop].classList.add(\"active\");\n            dots[currentSmallLoop].classList.add(\"active\");\n          }, 5000);\n        }\n\n        \/\/ Tarjeta secundaria\n        const tarjetaSecLoop = document.createElement(\"div\");\n        const descripcionCortaCard = pubCardLoop.descripcion.length > 120\n          ? pubCardLoop.descripcion.substring(0, 120) + \"...\"\n          : pubCardLoop.descripcion;\n        const imagenesHTMLCard = pubCardLoop.imagenes.map((url, i) => `\n          <img decoding=\"async\" src=\"${url}\" ${i === 0 ? 'class=\"active\"' : ''}>\n        `).join(\"\");\n        tarjetaSecLoop.innerHTML = `\n          <div class=\"publicacion\">\n            <div class=\"imagenes-tarjeta\">\n              ${imagenesHTMLCard}\n              <div class=\"indicadores\"><\/div>\n            <\/div>\n            <h2>${pubCardLoop.titulo}<\/h2>\n            <small>${new Date(pubCardLoop.created_at).toLocaleDateString()}<\/small>\n            <p>${descripcionCortaCard}<\/p>\n            <span class=\"ver-mas\">\n              ver m\u00e1s\n              <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">\n            <\/span>\n          <\/div>\n        `;\n        const tarjetaSecEl = tarjetaSecLoop.firstElementChild;\n\n        \/\/ abrir modal tarjeta secundaria\n        tarjetaSecEl.addEventListener(\"click\", () => abrirModal(pubCardLoop));\n        const verMasCard = tarjetaSecEl.querySelector(\".ver-mas\");\n        if (verMasCard) {\n          verMasCard.addEventListener(\"click\", (e) => { e.stopPropagation(); abrirModal(pubCardLoop); });\n        }\n\n        \/\/ indicadores + rotaci\u00f3n al hover tarjeta secundaria\n        const indicadoresCardLoop = tarjetaSecEl.querySelector(\".indicadores\");\n        if (pubCardLoop.imagenes.length > 1) {\n          indicadoresCardLoop.innerHTML = \"\";\n          pubCardLoop.imagenes.forEach((_, j) => {\n            const dot = document.createElement(\"span\");\n            if (j === 0) dot.classList.add(\"active\");\n            indicadoresCardLoop.appendChild(dot);\n          });\n          let currentCardLoop = 0;\n          let intervalCardLoop;\n          tarjetaSecEl.addEventListener(\"mouseenter\", () => {\n            const imgs = tarjetaSecEl.querySelectorAll(\".imagenes-tarjeta img\");\n            const dots = indicadoresCardLoop.querySelectorAll(\"span\");\n            intervalCardLoop = setInterval(() => {\n              imgs[currentCardLoop].classList.remove(\"active\");\n              dots[currentCardLoop].classList.remove(\"active\");\n              currentCardLoop = (currentCardLoop + 1) % imgs.length;\n              imgs[currentCardLoop].classList.add(\"active\");\n              dots[currentCardLoop].classList.add(\"active\");\n            }, 3000);\n          });\n          tarjetaSecEl.addEventListener(\"mouseleave\", () => clearInterval(intervalCardLoop));\n        }\n\n        \/\/ Alternar SOLO el orden, manteniendo el mismo dise\u00f1o\n        if (alternaFila3 === 0) {\n          \/\/ Igual que fila 3 original: portada izquierda, tarjeta derecha\n          fila3Loop.appendChild(portadaPequenaLoop);\n          fila3Loop.appendChild(tarjetaSecEl);\n        } else {\n          \/\/ Invertido: tarjeta izquierda, portada derecha\n          fila3Loop.appendChild(tarjetaSecEl);\n          fila3Loop.appendChild(portadaPequenaLoop);\n        }\n        alternaFila3 = (alternaFila3 + 1) % 2;\n\n        ultimaFilaLocal.insertAdjacentElement(\"afterend\", fila3Loop);\n        ultimaFilaLocal = fila3Loop;\n        start += 2;\n\n        \/\/ --- Fila tipo 4: tres tarjetas ---\n        const fila4Loop = document.createElement(\"div\");\n        fila4Loop.classList.add(\"grid-publicaciones\", \"fila4\");\n\n        for (let i = start; i < start + 3; i++) {\n          const pub = restantes[i];\n          if (!pub) continue;\n\n          const descripcionCorta = pub.descripcion.length > 120 \n            ? pub.descripcion.substring(0, 120) + \"...\" \n            : pub.descripcion;\n\n          const imagenesHTML = pub.imagenes.map((url, j) => `\n            <img decoding=\"async\" src=\"${url}\" ${j === 0 ? 'class=\"active\"' : ''}>\n          `).join(\"\");\n\n          const cardHTML = `\n            <div class=\"publicacion\">\n              <div class=\"imagenes-tarjeta\">\n                ${imagenesHTML}\n                <div class=\"indicadores\"><\/div>\n              <\/div>\n              <h2>${pub.titulo}<\/h2>\n              <small>${new Date(pub.created_at).toLocaleDateString()}<\/small>\n              <p>${descripcionCorta}<\/p>\n              <span class=\"ver-mas\">\n                ver m\u00e1s\n                <img decoding=\"async\" src=\"https:\/\/utelvt.edu.ec\/biblioteca\/wp-content\/uploads\/2026\/01\/FLECHA-TARJETAS-DERECHA-BLANCA.png\">\n              <\/span>\n            <\/div>\n          `;\n\n          const tarjetaWrapper = document.createElement(\"div\");\n          tarjetaWrapper.innerHTML = cardHTML;\n          const tarjetaEl = tarjetaWrapper.firstElementChild;\n          fila4Loop.appendChild(tarjetaEl);\n\n          \/\/ abrir modal\n          tarjetaEl.addEventListener(\"click\", () => abrirModal(pub));\n          const verMas = tarjetaEl.querySelector(\".ver-mas\");\n          if (verMas) {\n            verMas.addEventListener(\"click\", (e) => { e.stopPropagation(); abrirModal(pub); });\n          }\n\n          \/\/ indicadores + rotaci\u00f3n al hover\n          const indicadores = tarjetaEl.querySelector(\".indicadores\");\n          if (pub.imagenes.length > 1) {\n            indicadores.innerHTML = \"\";\n            pub.imagenes.forEach((_, j) => {\n              const dot = document.createElement(\"span\");\n              if (j === 0) dot.classList.add(\"active\");\n              indicadores.appendChild(dot);\n            });\n            let currentCard = 0;\n            let intervalCard;\n            tarjetaEl.addEventListener(\"mouseenter\", () => {\n              const imgs = tarjetaEl.querySelectorAll(\".imagenes-tarjeta img\");\n              const dots = indicadores.querySelectorAll(\"span\");\n              intervalCard = setInterval(() => {\n                imgs[currentCard].classList.remove(\"active\");\n                dots[currentCard].classList.remove(\"active\");\n                currentCard = (currentCard + 1) % imgs.length;\n                imgs[currentCard].classList.add(\"active\");\n                dots[currentCard].classList.add(\"active\");\n              }, 3000);\n            });\n            tarjetaEl.addEventListener(\"mouseleave\", () => clearInterval(intervalCard));\n          }\n        }\n\n        ultimaFilaLocal.insertAdjacentElement(\"afterend\", fila4Loop);\n        ultimaFilaLocal = fila4Loop;\n        start += 3;\n      } \/\/ while\n\n    } \/\/ if restantes.length >= 8\n  } \/\/ if restantes[3]\n\n  \/\/ Click en portada principal abre modal\n  portadaDiv.addEventListener(\"click\", () => abrirModal(portada));\n}\n\nlet esAdmin = false;\nasync function verificarAdmin() {\n  const { data: { user } } = await sb.auth.getUser();\n  if (user) {\n    const { data, error } = await sb.from(\"administradores\").select(\"email\").eq(\"email\", user.email);\n    esAdmin = (!error && data && data.length > 0);\n  } else {\n    esAdmin = false;\n  }\n}\nsb.auth.onAuthStateChange((_event, _session) => {\n  renderSession();\n  updateAdminBar();\n  verificarAdmin();\n});\n\nfunction abrirModal(publicacion) {\n  const modal = document.getElementById(\"modal-publicacion\");\n  const tituloEl = modal.querySelector(\"#modal-titulo\");\n  const fechaEl = modal.querySelector(\"#modal-fecha\");\n  const descEl = modal.querySelector(\"#modal-descripcion\");\n  const carruselDiv = modal.querySelector(\".imagenes-carrusel\");\n  const flechaIzq = modal.querySelector(\".flecha.izquierda\");\n  const flechaDer = modal.querySelector(\".flecha.derecha\");\n\n  \/\/ Cargar datos de la publicaci\u00f3n\n  tituloEl.textContent = publicacion.titulo;\n  fechaEl.textContent = new Date(publicacion.created_at).toLocaleString();\n  descEl.textContent = publicacion.descripcion;\n\n  \/\/ Cargar im\u00e1genes en carrusel\n  carruselDiv.innerHTML = \"\";\n  publicacion.imagenes.forEach((url, i) => {\n    const img = document.createElement(\"img\");\n    img.src = url;\n    if (i === 0) img.classList.add(\"active\");\n    carruselDiv.appendChild(img);\n  });\n\n  \/\/ Mostrar modal\n  modal.style.display = \"block\";\n\n  \/\/ Mostrar\/ocultar flechas seg\u00fan cantidad de im\u00e1genes\n  if (publicacion.imagenes.length > 1) {\n    flechaIzq.style.display = \"block\";\n    flechaDer.style.display = \"block\";\n  } else {\n    flechaIzq.style.display = \"none\";\n    flechaDer.style.display = \"none\";\n  }\n\n  \/\/ L\u00f3gica de carrusel\n  let current = 0;\n  const imgs = carruselDiv.querySelectorAll(\"img\");\n  flechaIzq.onclick = () => {\n    imgs[current].classList.remove(\"active\");\n    current = (current - 1 + imgs.length) % imgs.length;\n    imgs[current].classList.add(\"active\");\n  };\n  flechaDer.onclick = () => {\n    imgs[current].classList.remove(\"active\");\n    current = (current + 1) % imgs.length;\n    imgs[current].classList.add(\"active\");\n  };\n\n  \/\/ Bot\u00f3n cerrar\n  modal.querySelector(\".cerrar\").onclick = () => {\n    modal.style.display = \"none\";\n  };\n\n  \/\/ Cerrar modal al hacer clic fuera\n  window.onclick = (e) => {\n    if (e.target === modal) modal.style.display = \"none\";\n  };\n\n  \/\/ Men\u00fa de tres puntitos dentro del modal\n  const toggleBtn = modal.querySelector(\".menu-toggle\");\n  const opcionesMenu = modal.querySelector(\".menu-opciones\");\n\n  if (toggleBtn && opcionesMenu) {\n    \/\/ Mostrar men\u00fa solo si es administrador\n    if (!esAdmin) {\n      toggleBtn.style.display = \"none\";\n      opcionesMenu.style.display = \"none\";\n    } else {\n      toggleBtn.onclick = (e) => {\n        e.stopPropagation();\n        opcionesMenu.style.display = opcionesMenu.style.display === \"block\" ? \"none\" : \"block\";\n      };\n\n      const cerrarMenuFuera = (e) => {\n        if (!toggleBtn.contains(e.target) && !opcionesMenu.contains(e.target)) {\n          opcionesMenu.style.display = \"none\";\n        }\n      };\n      window.removeEventListener(\"click\", cerrarMenuFuera);\n      window.addEventListener(\"click\", cerrarMenuFuera);\n\n      const btnEditar = opcionesMenu.querySelector(\".editar\");\n      const btnEliminar = opcionesMenu.querySelector(\".eliminar\");\n\n      if (btnEditar) {\n        btnEditar.onclick = (e) => {\n          e.stopPropagation();\n          opcionesMenu.style.display = \"none\";\n          window.location.href = `https:\/\/utelvt.edu.ec\/biblioteca\/administracion-de-noticias\/?id=${publicacion.id}`;\n        };\n      }\n\n      if (btnEliminar) {\n        btnEliminar.onclick = async (e) => {\n          e.stopPropagation();\n          if (confirm(\"\u00bfSeguro que quieres eliminar esta publicaci\u00f3n?\")) {\n            const { error } = await sb\n              .from(\"imagenes_portada\")\n              .delete()\n              .eq(\"id\", parseInt(publicacion.id, 10));\n\n            if (error) {\n              console.error(\"Error al eliminar:\", error.message);\n            } else {\n              console.log(\"Publicaci\u00f3n eliminada con id:\", publicacion.id);\n              modal.style.display = \"none\";\n              window.location.reload();\n            }\n          }\n          opcionesMenu.style.display = \"none\";\n        };\n      }\n    }\n  }\n\n  \/\/ Barra personalizada dentro del modal\n  const content = modal.querySelector(\"#scrollContent\");\n  const scrollbar = modal.querySelector(\"#customScrollbar\");\n  const thumb = modal.querySelector(\"#customThumb\");\n\n  if (content && scrollbar && thumb) {\n    function updateThumb() {\n      const view = content.clientHeight;\n      const total = content.scrollHeight;\n      const track = scrollbar.clientHeight;\n\n      const thumbHeight = Math.max(24, (view \/ total) * track);\n      thumb.style.height = thumbHeight + \"px\";\n\n      const maxTop = track - thumbHeight;\n      const ratio = content.scrollTop \/ (total - view || 1);\n      thumb.style.top = ratio * maxTop + \"px\";\n    }\n\n    content.addEventListener(\"scroll\", updateThumb);\n    window.addEventListener(\"resize\", updateThumb);\n\n    let dragging = false, startY = 0, startTop = 0;\n    thumb.onmousedown = (e) => {\n      dragging = true;\n      startY = e.clientY;\n      startTop = parseInt(window.getComputedStyle(thumb).top, 10) || 0;\n      document.body.style.userSelect = \"none\";\n    };\n    document.onmousemove = (e) => {\n      if (!dragging) return;\n      const delta = e.clientY - startY;\n      const newTop = startTop + delta;\n      const maxTop = scrollbar.clientHeight - thumb.clientHeight;\n      const clamped = Math.max(0, Math.min(newTop, maxTop));\n      thumb.style.top = clamped + \"px\";\n\n      const ratio = clamped \/ maxTop;\n      content.scrollTop = ratio * (content.scrollHeight - content.clientHeight);\n    };\n    document.onmouseup = () => {\n      if (!dragging) return;\n      dragging = false;\n      document.body.style.userSelect = \"\";\n    };\n\n    scrollbar.onclick = (e) => {\n      if (e.target === thumb) return;\n      const rect = scrollbar.getBoundingClientRect();\n      const clickY = e.clientY - rect.top;\n      const thumbHalf = thumb.clientHeight \/ 2;\n      const newTop = clickY - thumbHalf;\n      const maxTop = scrollbar.clientHeight - thumb.clientHeight;\n      const clamped = Math.max(0, Math.min(newTop, maxTop));\n      thumb.style.top = clamped + \"px\";\n      const ratio = clamped \/ maxTop;\n      content.scrollTop = ratio * (content.scrollHeight - content.clientHeight);\n    };\n\n    updateThumb();\n  }\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", cargarPublicaciones);\n\n\n\n<\/script>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Noticias de la Biblioteca Bienvenido administrador \u2014 Pulsa aqu\u00ed para administrar esta p\u00e1gina Noticias de la biblioteca Inicio Cat\u00e1logo Noticias Servicios Capacitaciones Salas de Lectura Estanter\u00eda Abierta Sala de Lectura Infantil Eventos Iniciar sesi\u00f3n Cerrar sesi\u00f3n &times; Eventos agendados T\u00edtulo de la publicaci\u00f3n Fecha de la publicaci\u00f3n Descripci\u00f3n breve de la publicaci\u00f3n&#8230; Calendario de eventos [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-5640","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/pages\/5640","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/comments?post=5640"}],"version-history":[{"count":581,"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/pages\/5640\/revisions"}],"predecessor-version":[{"id":6484,"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/pages\/5640\/revisions\/6484"}],"wp:attachment":[{"href":"https:\/\/utelvt.edu.ec\/biblioteca\/wp-json\/wp\/v2\/media?parent=5640"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}