{"id":2275,"date":"2025-09-02T14:39:01","date_gmt":"2025-09-02T14:39:01","guid":{"rendered":"https:\/\/www.uaa.mx\/consejo\/?page_id=2275"},"modified":"2026-03-05T19:20:12","modified_gmt":"2026-03-05T19:20:12","slug":"consejeros","status":"publish","type":"page","link":"https:\/\/www.uaa.mx\/consejo\/consejeros\/","title":{"rendered":"Miembros del Honorable Consejo Universitario"},"content":{"rendered":"\n<span id=\"loader\"><\/span>\n<div id=\"contenedor-centros\"><\/div>\n\n<style>\n  span#loader {\n    margin-top: 2.5rem;\n    position: fixed;\n    display: block;\n    width: 8rem;\n    height: 8rem;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    border: solid 0.5rem #0066cc;\n    border-top: solid 0.5rem #0066cc7b;\n    border-radius: 50%;\n    animation: rotate 0.8s infinite linear;\n    z-index: 1000;\n  }\n\n  #contenedor-centros {\n    \/* display: none; *\/\n  }\n\n  @keyframes rotate {\n    100% {\n      transform: translate(-50%, -50%) rotate(360deg);\n    }\n  }\n\n  .centro-section {\n    margin-bottom: 3rem;\n  }\n\n  .centro-title {\n    font-size: 2.6rem;\n    margin-bottom: 1rem;\n    color: #fff;\n    border-radius: 5px 5px 0 0;\n    padding: 0.3rem 0;\n    width: 100% !important;\n    background-color: #073879;\n    text-align: center;\n  }\n\n  .centro-title.oscuro {\n    background-color: #05234a;\n  }\n\n  .cards-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n    gap: 1.5rem;\n  }\n\n  .card {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n\n    border: 1px solid #ddd;\n    border-radius: 5px;\n    padding: 0;\n    background: #fff;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n    transition: box-shadow 0.3s ease;\n    overflow: hidden;\n  }\n\n  .card:hover {\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n  }\n\n  .card img {\n    width: 100%;\n    height: 250px;\n    object-fit: cover;\n    background-color: #0066cc;\n    transition: transform 0.3s ease;\n    -webkit-user-drag: none;\n    \/* Safari, Chrome *\/\n    user-select: none;\n    \/* General *\/\n    \/* box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); *\/\n  }\n\n  .card img:hover {\n    transform: scale(1.1);\n  }\n\n  .card .info {\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n    padding: 1rem;\n    text-align: center;\n    z-index: 5;\n    background-color: #fff;\n    \/* border-top: 2px solid #0066cc; *\/\n    box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.1);\n    max-width: none !important;\n  }\n\n  .card .info h3 {\n    font-size: 1.6rem;\n    margin: 0 0 0.5rem 0;\n    font-weight: bold;\n    color: #222;\n  }\n\n  .card .info p {\n    font-size: 1.4rem;\n    margin: 0.2rem 0;\n    font-weight: bold;\n    color: #073879;\n    line-height: 1.3;\n  }\n\n  .card .info a {\n    font-size: 1.3rem;\n    display: inline-block;\n    margin-top: 0.5rem;\n    color: #0066cc;\n    text-decoration: none;\n    width: 100%;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  .card .info a:hover {\n    text-decoration: underline;\n  }\n\n  \/* Bot\u00f3n flotante *\/\n  .floating-btn {\n    position: fixed;\n    padding: 0 !important;\n    bottom: 30px;\n    right: 30px;\n    width: 60px;\n    height: 60px;\n    background-color: #073879;\n    color: white;\n    border: none;\n    border-radius: 50%;\n    font-size: 24px;\n    cursor: pointer;\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n    transition: all 0.3s ease;\n    z-index: 1000;\n  }\n\n  .floating-btn:hover {\n    background-color: #0066cc;\n    transform: scale(1.1);\n  }\n\n  \/* Estilos para modo colapsado *\/\n  .collapsed .card {\n    height: auto;\n  }\n\n  .collapsed .card img {\n    height: 150px;\n  }\n\n  .collapsed .card .info p {\n    display: none;\n  }\n\n  .collapsed .cards-grid {\n    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n  }\n<\/style>\n\n<script>\n  \/\/ Variable para controlar si mostrar solo una palabra del t\u00edtulo o todo\n  const mostrarTituloCompleto = false; \/\/ Cambiar a true para mostrar t\u00edtulo completo (de los que no pertenecen a consejeros ex oficio)\n\n  \/\/ Variable para controlar el estado colapsado\n  let isCollapsed = false;\n\n  \/\/ Funci\u00f3n para capitalizar texto manteniendo preposiciones en min\u00fasculas\n  function capitalizarConPreposiciones(texto) {\n    const preposiciones = [\n      'a', 'al', 'ante', 'bajo', 'cabe', 'con', 'contra',\n      'de', 'del', 'desde', 'durante', 'en', 'entre',\n      'hacia', 'hasta', 'mediante', 'para', 'por',\n      'seg\u00fan', 'sin', 'so', 'sobre', 'tras', 'versus', 'v\u00eda',\n      'el', 'la', 'los', 'las',\n      'y', 'e', 'o', 'u'\n    ];\n\n\n    return texto.toLowerCase()\n      .split(' ')\n      .map((palabra, index) => {\n        \/\/ La primera palabra siempre se capitaliza\n        if (index === 0) {\n          return palabra.charAt(0).toUpperCase() + palabra.slice(1);\n        }\n        \/\/ Si es preposici\u00f3n, mantener en min\u00fasculas\n        if (preposiciones.includes(palabra)) {\n          return palabra;\n        }\n        \/\/ Otras palabras se capitalizan\n        return palabra.charAt(0).toUpperCase() + palabra.slice(1);\n      })\n      .join(' ');\n  }\n\n  async function cargarDatos() {\n    const url = \"https:\/\/docs.google.com\/spreadsheets\/d\/e\/2PACX-1vS6t13gbqVnBGy_6HZv9OBLjIMl-4RcfjRX74vlgmJ1NbSWAXYPyR0wNenrhcmP3lt0BiE27I6YKN5s\/pub?output=csv\";\n    const response = await fetch(url);\n    const data = await response.text();\n    const filas = data.split(\"\\n\").map(f => f.split(\",\"));\n\n    const headers = filas[0].map(h => h.trim().toLowerCase());\n\n    \/\/ Agrupar registros por centro\n    const centros = {};\n    filas.slice(1).forEach(f => {\n      if (f.length > 1) {\n        const registro = {};\n        headers.forEach((h, i) => registro[h] = f[i] || \"\");\n        const centro = registro[\"centro\"].trim();\n        if (centro && centro.length > 0) {\n          if (!centros[centro]) centros[centro] = [];\n          centros[centro].push(registro);\n        }\n      }\n    });\n\n    \/\/ Ordenar centros seg\u00fan tu regla\n    const ordenPrioritario = [\n      \"CONSEJEROS Y CONSEJERAS EX OFICIO\",\n      ...Object.keys(centros)\n        .filter(c => c !== \"CONSEJEROS Y CONSEJERAS EX OFICIO\" && c !== \"PERSONAL ADMINISTRATIVO\")\n        .sort(), \/\/ orden alfab\u00e9tico\n      \"PERSONAL ADMINISTRATIVO\"\n    ];\n\n    \/\/ Funci\u00f3n para ordenar registros dentro de cada centro\n    function ordenarRegistrosPorCentro(registros, centro) {\n      \/\/ Para centros administrativos y ex oficio, mantener orden original\n      if (centro === \"CONSEJEROS Y CONSEJERAS EX OFICIO\" || centro === \"PERSONAL ADMINISTRATIVO\") {\n        return registros;\n      }\n\n      \/\/ Para centros acad\u00e9micos: separar docentes y estudiantes\n      const docentes = registros.filter(r => (r.carrera || '').toUpperCase().includes('DOCENTE')).sort((a, b) => (a.apellidos || '').localeCompare(b.apellidos || ''));\n      const estudiantes = registros.filter(r => !(r.carrera || '').toUpperCase().includes('DOCENTE')).sort((a, b) => (a.apellidos || '').localeCompare(b.apellidos || ''));\n\n      \/\/ Se pide que los docentes del CEM se ordenen por n\u00famero\n      if (centro === \"CENTRO DE EDUCACI\u00d3N MEDIA\") {\n        docentes.sort((a, b) => {\n          const strA = (a.nombre || '') + ' ' + (a.apellidos || '');\n          const strB = (b.nombre || '') + ' ' + (b.apellidos || '');\n\n          const has1A = strA.includes('[1]');\n          const has1B = strB.includes('[1]');\n          const has2A = strA.includes('[2]');\n          const has2B = strB.includes('[2]');\n\n          if (has1A && !has1B) return -1;\n          if (!has1A && has1B) return 1;\n          if (has2A && !has2B) return -1;\n          if (!has2A && has2B) return 1;\n\n          return 0;\n        });\n\n        \/\/ Opcional: limpiar los tags [1] y [2] para que no se muestren en la vista\n        docentes.forEach(docente => {\n          if (docente.nombre) {\n            docente.nombre = docente.nombre.replace(\/\\[1\\]|\\[2\\]\/g, '').trim();\n          }\n          if (docente.apellidos) {\n            docente.apellidos = docente.apellidos.replace(\/\\[1\\]|\\[2\\]\/g, '').trim();\n          }\n        });\n      }\n      \/\/ Retornar primero docentes, luego estudiantes\n      return [...docentes, ...estudiantes];\n    }\n\n    \/\/ Renderizar secciones\n    let html = \"\";\n    let sectionsAdded = {\n      exOficio: false,\n      centroAcademico: false\n    };\n\n    ordenPrioritario.forEach(centro => {\n      if (centros[centro]) {\n        html += `<div class=\"centro-section\">`;\n\n        \/\/ Agregar encabezado de secci\u00f3n solo una vez\n        if (centro === \"CONSEJEROS Y CONSEJERAS EX OFICIO\" && !sectionsAdded.exOficio) {\n          html += `<div class=\"centro-title oscuro\">CONSEJEROS Y CONSEJERAS EX OFICIO<\/div>`;\n          sectionsAdded.exOficio = true;\n        } else if (centro !== \"CONSEJEROS Y CONSEJERAS EX OFICIO\" && centro !== \"PERSONAL ADMINISTRATIVO\" && !sectionsAdded.centroAcademico) {\n          html += `<div class=\"centro-title oscuro\">CONSEJEROS Y CONSEJERAS POR CENTRO ACAD\u00c9MICO<\/div>`;\n          sectionsAdded.centroAcademico = true;\n        }\n        else if (centro === \"PERSONAL ADMINISTRATIVO\" && !sectionsAdded.admin) {\n          html += `<div class=\"centro-title oscuro\">CONSEJEROS ADMINISTRATIVOS<\/div>`;\n          sectionsAdded.admin = true;\n        }\n\n        \/\/ Solo agregar el t\u00edtulo del centro si no es un centro especial ya manejado arriba\n        if (centro !== \"CONSEJEROS Y CONSEJERAS EX OFICIO\" && centro !== \"PERSONAL ADMINISTRATIVO\") {\n          html += `<div class=\"centro-title\">${centro}<\/div>`;\n        } else if (centro === \"PERSONAL ADMINISTRATIVO\") {\n          html += `<div class=\"centro-title\">PERSONAL ADMINISTRATIVO<\/div>`;\n        }\n        html += `<div class=\"cards-grid\">`;\n\n        \/\/ Ordenar registros del centro\n        const registrosOrdenados = ordenarRegistrosPorCentro(centros[centro], centro);\n\n        registrosOrdenados.forEach(r => {\n          let fotoOriginal = (r['foto'] || 'default.jpg').trim();\n\n          \/\/ Generar versi\u00f3n 768x512 - funciona con y sin -scaled\n          let foto768 = fotoOriginal;\n          if (fotoOriginal.length < 5) {\n            foto768 = 'https:\/\/www.uaa.mx\/consejo\/wp-content\/uploads\/2025\/09\/Portrait_Placeholder.png';\n          } else if (!fotoOriginal.includes('-min')) {\n            foto768 = fotoOriginal.replace(\/(-scaled|-\\d+x\\d+)?(\\.(jpg|jpeg|png|gif))$\/i, '-768x512$2');\n          }\n\n          \/\/ Formatear t\u00edtulo y carrera seg\u00fan el centro\n          let tituloFormateado, carreraFormateada;\n\n          \/\/ Capitalizar nombres para todos los centros con preposiciones en min\u00fasculas\n          let nombreCapitalizado = capitalizarConPreposiciones(r['nombre'] || '');\n          let apellidosCapitalizado = capitalizarConPreposiciones(r['apellidos'] || '');\n\n          if (centro === \"CONSEJEROS Y CONSEJERAS EX OFICIO\") {\n            \/\/ Para consejeros ex oficio: t\u00edtulo tal como viene sin modificar\n            tituloFormateado = r['titulo'] || '';\n            \/\/ Carrera tal como viene\n            carreraFormateada = r['carrera'] || '';\n          } else {\n            \/\/ Para otros centros: controlar si mostrar t\u00edtulo completo o solo primera palabra\n            if (mostrarTituloCompleto) {\n              tituloFormateado = capitalizarConPreposiciones(r['titulo'] || '');\n            } else {\n              \/\/ Solo mostrar la primera palabra del t\u00edtulo y capitalizarla\n              const titulo = r['titulo'] || '';\n              if (titulo.trim()) {\n                tituloFormateado = titulo.split(' ')[0].charAt(0).toUpperCase() + titulo.split(' ')[0].slice(1).toLowerCase();\n              } else {\n                tituloFormateado = '';\n              }\n            }\n\n            \/\/ Formatear carrera seg\u00fan si es docente o estudiante\n            const carrera = r['carrera'] || '';\n            if (carrera.toUpperCase().includes('DOCENTE') || carrera.toUpperCase().includes('ADMINISTRATIVO')) {\n              \/\/ Para docentes, mantener \"DOCENTE\" como est\u00e1\n              carreraFormateada = capitalizarConPreposiciones(carrera);\n            } else {\n              \/\/ Para estudiantes, agregar \"ESTUDIANTE\" antes de la carrera\n              carreraFormateada = 'Estudiante<br>' + capitalizarConPreposiciones(carrera);\n            }\n          }\n\n          html += `\n            <div class=\"card\">\n              <img decoding=\"async\" src=\"${foto768}\" \n                  alt=\"Foto de ${nombreCapitalizado} ${apellidosCapitalizado}\">\n              <div class=\"info\">\n                <h3>${tituloFormateado} ${nombreCapitalizado} ${apellidosCapitalizado}<\/h3>\n                <p>${carreraFormateada}<\/p>\n                <a href=\"mailto:${r['correo'] || ''}\">${r['correo'] || ''}<\/a>\n              <\/div>\n            <\/div>\n          `;\n        });\n\n        html += `<\/div><\/div>`;\n      }\n    });\n\n    document.getElementById(\"loader\").style.display = \"none\";\n\n    document.getElementById(\"contenedor-centros\").innerHTML = html;\n\n    \/\/ Agregar bot\u00f3n flotante si no existe\n    if (!document.getElementById(\"collapse-btn\")) {\n      const button = document.createElement(\"button\");\n      button.id = \"collapse-btn\";\n      button.className = \"floating-btn\";\n      button.innerHTML = \"\u2212\";\n      button.onclick = toggleCollapse;\n      document.body.appendChild(button);\n    }\n  }\n\n  function toggleCollapse() {\n    isCollapsed = !isCollapsed;\n    const contenedor = document.getElementById(\"contenedor-centros\");\n    const button = document.getElementById(\"collapse-btn\");\n\n    if (isCollapsed) {\n      contenedor.classList.add(\"collapsed\");\n      button.innerHTML = \"+\";\n    } else {\n      contenedor.classList.remove(\"collapsed\");\n      button.innerHTML = \"\u2212\";\n    }\n  }\n\n  cargarDatos();\n<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":7,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/template-fullwidth.php","meta":{"footnotes":""},"class_list":["post-2275","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/pages\/2275","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/comments?post=2275"}],"version-history":[{"count":57,"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/pages\/2275\/revisions"}],"predecessor-version":[{"id":2496,"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/pages\/2275\/revisions\/2496"}],"wp:attachment":[{"href":"https:\/\/www.uaa.mx\/consejo\/wp-json\/wp\/v2\/media?parent=2275"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}