{"id":11252,"date":"2026-06-25T10:25:48","date_gmt":"2026-06-25T09:25:48","guid":{"rendered":"https:\/\/footballfun.es\/ffcomprobacioninterna\/"},"modified":"2026-06-29T16:15:14","modified_gmt":"2026-06-29T15:15:14","slug":"ffcomprobacioninterna","status":"publish","type":"page","link":"https:\/\/footballfun.es\/eus\/ffcomprobacioninterna\/","title":{"rendered":"F&#038;F Comprobaci\u00f3n Interna"},"content":{"rendered":"<meta name=\"robots\" content=\"noindex,nofollow\">\n<style>\n  .ff-internal {\n    --ff-ink: #141414;\n    --ff-muted: #667085;\n    --ff-line: #d8dde7;\n    --ff-page: #f3f5f8;\n    --ff-panel: #ffffff;\n    --ff-yellow: #ffd71b;\n    --ff-orange: #ff8a00;\n    --ff-green: #1aa85b;\n    --ff-blue: #153b8a;\n    --ff-shadow: 0 12px 28px rgba(20, 20, 20, .12);\n    min-height: 100vh;\n    background: var(--ff-page);\n    color: var(--ff-ink);\n    font-family: Montserrat, Arial, sans-serif;\n    font-size: 14px;\n  }\n  .ff-internal * { box-sizing: border-box; }\n  .ff-internal [hidden] { display: none !important; }\n  .ff-shell { max-width: 1120px; margin: 0 auto; padding: 14px 12px 96px; }\n  .ff-header {\n    position: sticky; top: 0; z-index: 20;\n    background: #111; color: #fff; border-bottom: 5px solid var(--ff-yellow);\n    box-shadow: 0 4px 18px rgba(0,0,0,.18);\n  }\n  .ff-top { max-width: 1120px; margin: 0 auto; padding: 13px 12px 10px; display: flex; justify-content: space-between; gap: 10px; align-items: center; }\n  .ff-brand strong { display:block; font-size: 19px; font-weight: 950; text-transform: uppercase; letter-spacing: .02em; }\n  .ff-brand span { color: rgba(255,255,255,.72); font-size: 11px; font-weight: 800; text-transform: uppercase; }\n  .ff-badge { display:inline-flex; align-items:center; justify-content:center; min-height: 30px; border-radius: 999px; padding: 5px 10px; background: var(--ff-yellow); color:#111; font-weight: 950; font-size: 11px; text-transform: uppercase; }\n  .ff-panel, .ff-card { background: var(--ff-panel); border: 1px solid var(--ff-line); border-radius: 10px; box-shadow: 0 2px 0 rgba(20,20,20,.04); }\n  .ff-panel { padding: 12px; margin-bottom: 12px; }\n  .ff-login { min-height: 70vh; display:grid; place-items:center; padding: 20px; }\n  .ff-login-box { width:min(440px,100%); background:#fff; border:1px solid var(--ff-line); border-radius:12px; box-shadow:var(--ff-shadow); padding:18px; display:grid; gap:12px; }\n  .ff-login h2 { margin:0; font-size:24px; text-transform:uppercase; }\n  .ff-login p { margin:0; color:var(--ff-muted); font-weight:700; }\n  .ff-input, .ff-select {\n    width:100%; min-height:46px; border:1px solid #cbd5e1; border-radius:8px; background:#fff; color:var(--ff-ink);\n    padding:9px 10px; font:inherit; font-weight:850; outline:none;\n  }\n  .ff-input:focus, .ff-select:focus { border-color: var(--ff-yellow); box-shadow:0 0 0 3px rgba(255,215,27,.28); }\n  .ff-button {\n    min-height:44px; border:1px solid #111; border-radius:8px; background:#111; color:#fff; font:inherit; font-weight:950;\n    padding:9px 12px; text-transform:uppercase; cursor:pointer;\n  }\n  .ff-button.alt { background:#fff; color:#111; border-color:#cbd5e1; }\n  .ff-button.warn { background:var(--ff-orange); border-color:var(--ff-orange); color:#111; }\n  .ff-button:disabled { opacity:.45; cursor:not-allowed; }\n  .ff-filters { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap:8px; }\n  .ff-field { display:grid; gap:4px; }\n  .ff-field label { color:var(--ff-muted); font-size:10px; font-weight:950; text-transform:uppercase; }\n  .ff-search-row { display:grid; grid-template-columns: minmax(0,1fr) auto; gap:8px; margin-top:8px; }\n  .ff-search { min-height:50px; border-width:2px; font-size:17px; }\n  .ff-status { color:var(--ff-muted); font-size:12px; font-weight:850; text-transform:uppercase; }\n  .ff-grid { display:grid; gap:10px; }\n  .ff-card { padding:12px; display:grid; gap:9px; }\n  .ff-card-top { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:10px; align-items:start; }\n  .ff-name { margin:0; font-size:20px; line-height:1.1; font-weight:950; text-transform:uppercase; }\n  .ff-chips { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }\n  .ff-chip { display:inline-flex; align-items:center; min-height:26px; border-radius:7px; padding:4px 8px; font-weight:950; font-size:11px; text-transform:uppercase; background:#eef2f7; color:#26344a; }\n  .ff-chip.mini { background:#e8fff3; color:#05603a; }\n  .ff-chip.pro { background:#dbeafe; color:#173b7a; }\n  .ff-chip.porteros { background:#fff0d9; color:#9a4b00; }\n  .ff-info-box { border:1px solid #cfd8e6; border-left:5px solid var(--ff-yellow); border-radius:9px; padding:9px; background:#fbfcff; display:grid; gap:6px; }\n  .ff-info-box strong { font-size:15px; text-transform:uppercase; }\n  .ff-vest { display:inline-flex; min-height:30px; align-items:center; border-radius:7px; padding:5px 9px; background:#dcfce7; color:#14532d; font-weight:950; text-transform:uppercase; }\n  .ff-vest.empty { background:#fee2e2; color:#991b1b; }\n  .ff-empty { padding:18px; text-align:center; color:var(--ff-muted); font-weight:850; }\n  .ff-admin-bar { display:none; gap:8px; margin-top:8px; }\n  .ff-admin .ff-admin-bar { display:flex; }\n  .ff-sheet {\n    position:fixed; inset:0; z-index:50; display:none; background:rgba(17,17,17,.42); padding:18px 10px; align-items:flex-end;\n  }\n  .ff-sheet.open { display:flex; }\n  .ff-sheet.open[data-data-sheet] { align-items:stretch; padding:0; }\n  .ff-sheet-panel { width:min(720px,100%); max-height:94vh; overflow:auto; margin:0 auto; background:#fff; border-radius:14px 14px 0 0; box-shadow:var(--ff-shadow); padding:14px; display:grid; gap:10px; }\n  [data-data-sheet] .ff-sheet-panel { width:100vw; height:100dvh; max-height:none; overflow:hidden; padding:7px; border-radius:0; grid-template-rows:auto minmax(0,1fr); gap:5px; }\n  .ff-sheet-head { display:flex; justify-content:space-between; gap:8px; align-items:start; position:sticky; top:0; background:#fff; padding-bottom:6px; }\n  [data-data-sheet] .ff-sheet-head { position:static; padding-bottom:0; align-items:center; }\n  .ff-sheet-title { margin:0; font-size:20px; text-transform:uppercase; font-weight:950; }\n  [data-data-sheet] .ff-sheet-title { font-size:clamp(14px,4.2vw,19px); line-height:.95; }\n  [data-data-body] { min-height:0; overflow:hidden; display:grid; grid-template-rows:auto minmax(0,1fr); gap:5px; }\n  .ff-data-grid { display:grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap:8px; }\n  [data-data-sheet] .ff-data-grid { height:100%; min-height:0; grid-auto-rows:minmax(0,1fr); gap:3px; }\n  .ff-data { border:1px solid #e1e7ef; border-radius:9px; padding:8px; display:grid; gap:3px; min-height:58px; }\n  [data-data-sheet] .ff-data { min-height:0; padding:3px 5px; overflow:hidden; }\n  .ff-data span { color:var(--ff-muted); font-size:10px; font-weight:950; text-transform:uppercase; }\n  .ff-data strong, .ff-data button { color:var(--ff-ink); font-size:14px; font-weight:900; text-align:left; }\n  [data-data-sheet] .ff-data span { font-size:clamp(7px,2vw,9px); line-height:1; }\n  [data-data-sheet] .ff-data strong, [data-data-sheet] .ff-data button { font-size:clamp(8px,2.35vw,11px); line-height:1.05; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }\n  .ff-data button { border:0; background:transparent; padding:0; cursor:pointer; text-decoration:underline; }\n  .ff-contact-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }\n  .ff-contact-actions a { min-height:42px; border-radius:8px; background:#111; color:#fff; display:grid; place-items:center; text-decoration:none; font-weight:950; text-transform:uppercase; }\n  [data-data-sheet] .ff-contact-actions { gap:6px; }\n  [data-data-sheet] .ff-contact-actions { gap:5px; }\n  [data-data-sheet] .ff-contact-actions a { min-height:28px; font-size:clamp(9px,2.35vw,11px); }\n  .ff-data-hero { min-height:0; height:100%; display:grid; grid-template-rows:auto auto minmax(0,1fr); gap:6px; }\n  .ff-priority-grid, .ff-secondary-grid { min-height:0; display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:5px; }\n  .ff-secondary-grid { grid-auto-rows:minmax(0,1fr); }\n  .ff-data.primary { border-color:#111; background:#fffdf0; }\n  .ff-data.wide { grid-column:1\/-1; }\n  [data-data-sheet] .ff-data.primary span { color:#111; }\n  [data-data-sheet] .ff-data.primary strong, [data-data-sheet] .ff-data.primary button { font-size:clamp(10px,2.65vw,13px); }\n  .ff-toast { position:fixed; left:50%; bottom:20px; z-index:80; transform:translateX(-50%); background:#111; color:#fff; border-radius:999px; padding:9px 14px; font-weight:950; display:none; }\n  .ff-toast.show { display:block; }\n  .ff-vest-list { display:grid; gap:8px; }\n  .ff-vest-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(120px,220px) auto; gap:8px; align-items:center; border:1px solid #e1e7ef; border-radius:9px; padding:8px; }\n  .ff-vest-row small { color:var(--ff-muted); font-weight:850; }\n  @media (max-width: 700px) {\n    .ff-shell { padding: 10px 8px 84px; }\n    .ff-top { padding: 11px 10px 8px; }\n    .ff-brand strong { font-size:17px; }\n    .ff-filters { grid-template-columns: repeat(2,minmax(0,1fr)); }\n    .ff-search-row { grid-template-columns:1fr; }\n    .ff-name { font-size:18px; }\n    .ff-data-grid { grid-template-columns:1fr; }\n    [data-data-sheet] .ff-data-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }\n    .ff-vest-row { grid-template-columns:1fr; }\n    .ff-sheet { padding:0; }\n    .ff-sheet-panel { border-radius:16px 16px 0 0; max-height:92vh; }\n    [data-data-sheet] .ff-sheet-panel { height:100dvh; max-height:none; border-radius:0; }\n  }\n<\/style>\n\n<div class=\"ff-internal\" id=\"ffComprobacionApp\">\n  <div class=\"ff-login\" data-login>\n    <div class=\"ff-login-box\">\n      <h2>F&#038;F Comprobaci\u00f3n<\/h2>\n      <p>Introduce la contrase\u00f1a de acceso.<\/p>\n      <input class=\"ff-input\" type=\"password\" data-password placeholder=\"Contrase\u00f1a\">\n      <button class=\"ff-button\" data-login-btn>Entrar<\/button>\n      <div class=\"ff-status\" data-login-status><\/div>\n    <\/div>\n  <\/div>\n\n  <div data-app hidden>\n    <header class=\"ff-header\">\n      <div class=\"ff-top\">\n        <div class=\"ff-brand\">\n          <strong>F&#038;F Comprobaci\u00f3n<\/strong>\n          <span>Buscador interno de grupos y vestuarios<\/span>\n        <\/div>\n        <div class=\"ff-badge\" data-role>Vista<\/div>\n      <\/div>\n    <\/header>\n    <main class=\"ff-shell\">\n      <section class=\"ff-panel\">\n        <div class=\"ff-filters\">\n          <div class=\"ff-field\"><label>Sede<\/label><select class=\"ff-select\" data-sede><\/select><\/div>\n          <div class=\"ff-field\"><label>Turno<\/label><select class=\"ff-select\" data-turno><\/select><\/div>\n          <div class=\"ff-field\"><label>Modalidad<\/label><select class=\"ff-select\" data-modalidad><\/select><\/div>\n          <div class=\"ff-field\"><label>Vestuario<\/label><select class=\"ff-select\" data-vestuario><\/select><\/div>\n        <\/div>\n        <div class=\"ff-search-row\">\n          <input class=\"ff-input ff-search\" data-search placeholder=\"Buscar nombre, tutor, tel\u00e9fono, club...\">\n          <button class=\"ff-button alt\" data-refresh>Actualizar JSON<\/button>\n        <\/div>\n        <div class=\"ff-admin-bar\">\n          <button class=\"ff-button warn\" data-open-vestuarios>Vestuarios<\/button>\n        <\/div>\n        <div class=\"ff-status\" data-status>Cargando&#8230;<\/div>\n      <\/section>\n      <section class=\"ff-grid\" data-results><\/section>\n    <\/main>\n  <\/div>\n\n  <div class=\"ff-sheet\" data-data-sheet>\n    <div class=\"ff-sheet-panel\">\n      <div class=\"ff-sheet-head\">\n        <h3 class=\"ff-sheet-title\" data-data-title>Datos<\/h3>\n        <button class=\"ff-button alt\" data-close-sheet>Cerrar<\/button>\n      <\/div>\n      <div data-data-body><\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"ff-sheet\" data-vest-sheet>\n    <div class=\"ff-sheet-panel\">\n      <div class=\"ff-sheet-head\">\n        <h3 class=\"ff-sheet-title\">Vestuarios<\/h3>\n        <button class=\"ff-button alt\" data-close-vest>Cerrar<\/button>\n      <\/div>\n      <div class=\"ff-status\">Solo se guarda con contrase\u00f1a Admin.<\/div>\n      <div class=\"ff-vest-list\" data-vest-list><\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"ff-toast\" data-toast><\/div>\n<\/div>\n\n<script>\n(function(){\n  const API_URL = \"https:\/\/script.google.com\/macros\/s\/AKfycbwqrXH9BQgIXeqdzcwY6qD0tpwRqANyGY4QSs5hck-1zRmp1LrXmI70wv1HSenaUa13Lg\/exec\" || \"https:\/\/script.google.com\/macros\/s\/AKfycbwqrXH9BQgIXeqdzcwY6qD0tpwRqANyGY4QSs5hck-1zRmp1LrXmI70wv1HSenaUa13Lg\/exec\";\n  const DATA_PREFIX = \"ff-comprobacion-data\";\n  const VIEW_PASS = \"Comprobacion\";\n  const ADMIN_PASS = \"Admin\";\n  const state = { role: \"\", password: \"\", manifest: null, dataset: null, sede: \"\", turno: \"\", modalidad: \"TODAS\", vestuario: \"TODOS\", query: \"\" };\n  const el = (s, root=document) => root.querySelector(s);\n  const els = (s, root=document) => Array.from(root.querySelectorAll(s));\n  const root = el(\"#ffComprobacionApp\");\n\n  function norm(v){ return String(v||\"\").normalize(\"NFD\").replace(\/[\\u0300-\\u036f]\/g,\"\").toUpperCase(); }\n  function esc(v){ return String(v??\"\").replace(\/[&<>\"']\/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[ch])); }\n  function slugFor(sede, turno){ return `${DATA_PREFIX}-${String(sede).toLowerCase()}-${String(turno).toLowerCase()}`; }\n  function toast(msg){ const t=el(\"[data-toast]\"); t.textContent=msg; t.classList.add(\"show\"); setTimeout(()=>t.classList.remove(\"show\"),1600); }\n  async function copyText(text, label){\n    if(!text) return;\n    try { await navigator.clipboard.writeText(text); }\n    catch(e){ const ta=document.createElement(\"textarea\"); ta.value=text; document.body.appendChild(ta); ta.select(); document.execCommand(\"copy\"); ta.remove(); }\n    toast(label || \"Copiado\");\n  }\n  function decodeHtml(text){ const d=document.createElement(\"textarea\"); d.innerHTML=text || \"\"; return d.value; }\n  function decodeBase64Json(value){\n    const binary = atob(String(value || \"\").trim());\n    const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));\n    const text = new TextDecoder(\"utf-8\").decode(bytes);\n    return JSON.parse(text);\n  }\n  async function loadJsonPage(slug){\n    const url = `\/wp-json\/wp\/v2\/pages?slug=${encodeURIComponent(slug)}&_fields=content.rendered,modified,slug`;\n    const res = await fetch(url, { cache:\"no-store\" });\n    if(!res.ok) throw new Error(`No se puede cargar ${slug}`);\n    const pages = await res.json();\n    if(!pages.length) throw new Error(`No existe el JSON ${slug}`);\n    const html = decodeHtml(pages[0].content.rendered || \"\");\n    const doc = new DOMParser().parseFromString(html, \"text\/html\");\n    const pre = doc.querySelector(\"[data-ff-json]\");\n    if(!pre) throw new Error(`JSON inv\u00e1lido: ${slug}`);\n    return decodeBase64Json(pre.textContent);\n  }\n  async function postApi(action, payload){\n    if(!API_URL || API_URL.indexOf(\"__\") === 0) throw new Error(\"API de Apps Script no configurada\");\n    const res = await fetch(API_URL, { method:\"POST\", headers:{ \"Content-Type\":\"text\/plain;charset=utf-8\" }, body: JSON.stringify({ action, payload }) });\n    const data = await res.json();\n    if(!data.ok) throw new Error(data.error || \"Error\");\n    return data;\n  }\n  function fillSelect(select, items, value){\n    select.innerHTML = items.map(item => `<option value=\"${esc(item.value)}\">${esc(item.label)}<\/option>`).join(\"\");\n    if(value) select.value = value;\n  }\n  async function init(){\n    bind();\n    setInterval(refreshLiveVestuariosInBackground, 30000);\n    const saved = sessionStorage.getItem(\"ff-comprobacion-role\");\n    const savedPass = sessionStorage.getItem(\"ff-comprobacion-pass\");\n    if(saved && savedPass) enter(savedPass);\n  }\n  function bind(){\n    el(\"[data-login-btn]\").addEventListener(\"click\", () => enter(el(\"[data-password]\").value.trim()));\n    el(\"[data-password]\").addEventListener(\"keydown\", e => { if(e.key === \"Enter\") enter(e.currentTarget.value.trim()); });\n    [\"sede\",\"turno\",\"modalidad\",\"vestuario\"].forEach(name => el(`[data-${name}]`).addEventListener(\"change\", onFilter));\n    el(\"[data-search]\").addEventListener(\"input\", e => { state.query = e.target.value; render(); });\n    el(\"[data-refresh]\").addEventListener(\"click\", loadCurrent);\n    el(\"[data-open-vestuarios]\").addEventListener(\"click\", openVestuarios);\n    el(\"[data-close-sheet]\").addEventListener(\"click\", closeSheets);\n    el(\"[data-data-sheet]\").addEventListener(\"click\", e => { if(e.target.matches(\"[data-data-sheet]\")) closeSheets(); });\n    el(\"[data-close-vest]\").addEventListener(\"click\", closeSheets);\n    el(\"[data-vest-sheet]\").addEventListener(\"click\", e => { if(e.target.matches(\"[data-vest-sheet]\")) closeSheets(); });\n    document.addEventListener(\"click\", e => {\n      const btn = e.target.closest(\"[data-copy]\");\n      if(btn) copyText(btn.dataset.copy, btn.dataset.copyLabel || \"Copiado\");\n    });\n  }\n  async function enter(password){\n    if(password !== VIEW_PASS && password !== ADMIN_PASS){ el(\"[data-login-status]\").textContent = \"Contrase\u00f1a incorrecta\"; return; }\n    state.password = password;\n    state.role = password === ADMIN_PASS ? \"admin\" : \"viewer\";\n    sessionStorage.setItem(\"ff-comprobacion-pass\", password);\n    sessionStorage.setItem(\"ff-comprobacion-role\", state.role);\n    root.classList.toggle(\"ff-admin\", state.role === \"admin\");\n    el(\"[data-role]\").textContent = state.role === \"admin\" ? \"Admin\" : \"Vista\";\n    el(\"[data-login]\").hidden = true;\n    el(\"[data-app]\").hidden = false;\n    await loadManifest();\n  }\n  async function loadManifest(){\n    setStatus(\"Cargando datos r\u00e1pidos...\");\n    try{\n      state.manifest = await loadJsonPage(`${DATA_PREFIX}-manifest`);\n      const sedes = (state.manifest.sedes || []).map(s => ({ value:s.key, label:s.sede }));\n      state.sede = state.sede || (sedes[0] && sedes[0].value) || \"\";\n      fillSelect(el(\"[data-sede]\"), sedes, state.sede);\n      fillTurns();\n      await loadCurrent();\n    }catch(err){ setStatus(`Error: ${err.message}`); }\n  }\n  function currentSede(){ return (state.manifest.sedes || []).find(s => s.key === state.sede) || { turns:[] }; }\n  function currentTurn(){ return (currentSede().turns || []).find(t => t.turno === state.turno); }\n  function fillTurns(){\n    const turns = (currentSede().turns || []).map(t => ({ value:t.turno, label:t.turno }));\n    state.turno = turns.some(t => t.value === state.turno) ? state.turno : (turns[0] && turns[0].value) || \"\";\n    fillSelect(el(\"[data-turno]\"), turns, state.turno);\n  }\n  async function onFilter(e){\n    state[e.target.dataset.sede !== undefined ? \"sede\" : e.target.dataset.turno !== undefined ? \"turno\" : e.target.dataset.modalidad !== undefined ? \"modalidad\" : \"vestuario\"] = e.target.value;\n    if(e.target.matches(\"[data-sede]\")) fillTurns();\n    if(e.target.matches(\"[data-sede], [data-turno]\")) await loadCurrent();\n    else render();\n  }\n  async function loadCurrent(){\n    if(!state.sede || !state.turno) return;\n    setStatus(\"Cargando turno...\");\n    try{\n      state.dataset = await loadJsonPage(slugFor(state.sede, state.turno));\n      await refreshLiveVestuarios();\n      const modalidades = [\"TODAS\"].concat(state.dataset.modalidades || []);\n      state.modalidad = modalidades.includes(state.modalidad) ? state.modalidad : \"TODAS\";\n      fillSelect(el(\"[data-modalidad]\"), modalidades.map(m => ({ value:m, label:m === \"TODAS\" ? \"Todas\" : m })), state.modalidad);\n      const vestuarios = currentVestuarioOptions();\n      state.vestuario = vestuarios.includes(state.vestuario) ? state.vestuario : \"TODOS\";\n      fillSelect(el(\"[data-vestuario]\"), vestuarios.map(v => ({ value:v, label:v === \"TODOS\" ? \"Todos\" : v })), state.vestuario);\n      render();\n    }catch(err){ setStatus(`Error: ${err.message}`); }\n  }\n  async function refreshLiveVestuarios(){\n    if(!state.dataset || !API_URL || API_URL.indexOf(\"__\") === 0) return;\n    try{\n      const live = await postApi(\"getVestuarios\", { sede: state.sede, turno: state.turno });\n      const liveKey = `${String(state.sede).toLowerCase()}-${String(state.turno).toLowerCase()}`;\n      const slugKey = slugFor(state.sede, state.turno);\n      const payload = live.datasets && (live.datasets[liveKey] || live.datasets[slugKey]);\n      if(!payload) return;\n      state.dataset.vestuarioAssignments = payload.assignments || [];\n      applyAssignmentsToPlayers(state.dataset.vestuarioAssignments);\n      state.dataset.vestuarios = currentVestuarioOptions();\n    }catch(err){\n      console.warn(\"No se pudieron cargar vestuarios en vivo\", err);\n    }\n  }\n  function applyAssignmentsToPlayers(assignments){\n    const sorted = (assignments || []).slice().sort((a,b) => Number(a.priority || 3) - Number(b.priority || 3));\n    (state.dataset.players || []).forEach(p => {\n      const match = sorted.find(r => assignmentApplies(p, r));\n      p.vestuario = match ? (match.vestuario || \"\") : \"\";\n      p.vestuarioLabel = p.vestuario || \"SIN VESTUARIO\";\n      p.vestuarioTipo = match ? (match.tipo || (match.pestana === \"REGLA\" ? \"REGLA\" : \"GRUPO\")) : \"\";\n      p.vestuarioCriterio = match ? (match.groupNumber || \"GRUPO\") : \"\";\n    });\n  }\n  function currentVestuarioOptions(){\n    const values = new Set();\n    ((state.dataset && state.dataset.players) || []).forEach(p => {\n      const label = p.vestuarioLabel || \"SIN VESTUARIO\";\n      if(label && label !== \"SIN VESTUARIO\") values.add(label);\n    });\n    return [\"TODOS\", \"SIN VESTUARIO\"].concat(Array.from(values).sort((a,b) => a.localeCompare(b, \"es\", { numeric:true })));\n  }\n  async function refreshLiveVestuariosInBackground(){\n    if(!state.dataset || !state.sede || !state.turno || el(\"[data-login]\").hidden === false) return;\n    if(el(\"[data-data-sheet]\").classList.contains(\"open\") || el(\"[data-vest-sheet]\").classList.contains(\"open\")) return;\n    await refreshLiveVestuarios();\n    const vestuarios = currentVestuarioOptions();\n    state.vestuario = vestuarios.includes(state.vestuario) ? state.vestuario : \"TODOS\";\n    fillSelect(el(\"[data-vestuario]\"), vestuarios.map(v => ({ value:v, label:v === \"TODOS\" ? \"Todos\" : v })), state.vestuario);\n    render();\n  }\n  function filtered(){\n    const players = (state.dataset && state.dataset.players) || [];\n    const q = norm(state.query);\n    return players.filter(p => {\n      if(state.modalidad !== \"TODAS\" && p.modalidad !== state.modalidad) return false;\n      const vest = p.vestuarioLabel || \"SIN VESTUARIO\";\n      if(state.vestuario !== \"TODOS\" && vest !== state.vestuario) return false;\n      if(q && !(p.search || norm(Object.values(p).join(\" \"))).includes(q)) return false;\n      return true;\n    });\n  }\n  function isYes(value){ const n = norm(value); return n === \"SI\" || n === \"S\" || n === \"YES\"; }\n  function isFemale(value){ const n = norm(value); return n === \"CHICA\" || n === \"FEMENINO\" || n === \"F\" || n === \"MUJER\" || n === \"NINA\" || n.indexOf(\"CHICA\") === 0; }\n  function assignmentApplies(player, assignment){\n    if(player.turno !== assignment.turno) return false;\n    if(assignment.pestana === \"REGLA\" && assignment.groupNumber === \"LANZADERA\") return isYes(player.lanzadera);\n    if(assignment.pestana === \"REGLA\" && assignment.groupNumber === \"CHICAS\") return !isYes(player.lanzadera) && isFemale(player.sexo);\n    return !isYes(player.lanzadera) && !isFemale(player.sexo) && player.pestana === assignment.pestana && String(player.groupNumber) === String(assignment.groupNumber);\n  }\n  function render(){\n    const rows = filtered();\n    const out = el(\"[data-results]\");\n    setStatus(`${rows.length} jugador\/a${rows.length===1?\"\":\"s\"} \u00b7 ${state.sede.toUpperCase()} ${state.turno}`);\n    if(!rows.length){ out.innerHTML = `<div class=\"ff-empty ff-panel\">No hay resultados.<\/div>`; return; }\n    out.innerHTML = rows.map(cardHtml).join(\"\");\n    els(\"[data-open-data]\", out).forEach(btn => btn.addEventListener(\"click\", () => openData(rows[Number(btn.dataset.index)])));\n  }\n  function cardHtml(p, idx){\n    const modClass = norm(p.modalidad).toLowerCase();\n    const vestEmpty = !p.vestuario || p.vestuarioLabel === \"SIN VESTUARIO\";\n    return `<article class=\"ff-card\">\n      <div class=\"ff-card-top\">\n        <h3 class=\"ff-name\">${esc(p.jugador)}<\/h3>\n        <button class=\"ff-button alt\" data-open-data data-index=\"${idx}\">Datos<\/button>\n      <\/div>\n      <div class=\"ff-chips\">\n        <span class=\"ff-chip ${esc(modClass)}\">${esc(p.modalidad)}<\/span>\n        ${p.desde ? `<span class=\"ff-chip\">Lanzadera: ${esc(p.desde)}<\/span>` : \"\"}\n        ${p.talla ? `<span class=\"ff-chip\">${esc(p.talla)}<\/span>` : \"\"}\n      <\/div>\n      <div class=\"ff-info-box\">\n        <strong>${esc(p.groupLabel || \"SIN GRUPO\")}<\/strong>\n        <span>${esc(p.pestana || \"\")}<\/span>\n        <span class=\"ff-vest ${vestEmpty ? \"empty\" : \"\"}\">${esc(p.vestuarioLabel || \"SIN VESTUARIO\")}<\/span>\n      <\/div>\n    <\/article>`;\n  }\n  function openData(p){\n    el(\"[data-data-title]\").textContent = p.jugador;\n    const phone = String(p.telefono || \"\").replace(\/\\s+\/g,\"\");\n    const wa = phone ? `https:\/\/wa.me\/34${phone.replace(\/^(\\+?34)\/,\"\")}` : \"\";\n    el(\"[data-data-body]\").innerHTML = `\n      <div class=\"ff-data-hero\">\n        <div class=\"ff-contact-actions\">\n          <a ${phone ? `href=\"tel:${esc(phone)}\"` : `aria-disabled=\"true\"`}>Llamar<\/a>\n          <a ${wa ? `href=\"${esc(wa)}\" target=\"_blank\" rel=\"noopener\"` : `aria-disabled=\"true\"`}>WhatsApp<\/a>\n        <\/div>\n        <div class=\"ff-priority-grid\">\n          ${dataItem(\"Tel\u00e9fono\", p.telefono, \"primary\")}\n          ${dataItem(\"Talla\", p.talla || p.tallaSudadera, \"primary\")}\n          ${copyItem(\"Email\", p.email, \"Email copiado\", \"primary wide\")}\n          ${copyItem(\"A\u00f1o nacimiento\", p.birthYear, \"A\u00f1o copiado\", \"primary\")}\n          ${dataItem(\"Turnos\", (p.turnosInscritos || []).join(\", \"), \"primary\")}\n        <\/div>\n        <div class=\"ff-secondary-grid\">\n          ${dataItem(\"Tutor\/a\", p.tutor)}\n          ${dataItem(\"Sexo\", p.sexo)}\n          ${dataItem(\"Club\", p.club)}\n          ${dataItem(\"Sudadera\", p.tallaSudadera)}\n          ${dataItem(\"Lanzadera\", p.lanzadera)}\n          ${dataItem(\"Parada\", p.desde)}\n          ${dataItem(\"Observaciones\", p.observaciones, \"wide\")}\n        <\/div>\n      <\/div>`;\n    el(\"[data-data-sheet]\").classList.add(\"open\");\n  }\n  function dataItem(label, value, extraClass){ return `<div class=\"ff-data ${esc(extraClass || \"\")}\"><span>${esc(label)}<\/span><strong>${esc(value || \"\u2014\")}<\/strong><\/div>`; }\n  function copyItem(label, value, msg, extraClass){\n    return `<div class=\"ff-data ${esc(extraClass || \"\")}\"><span>${esc(label)}<\/span>${value ? `<button data-copy=\"${esc(value)}\" data-copy-label=\"${esc(msg)}\">${esc(value)}<\/button>` : `<strong>\u2014<\/strong>`}<\/div>`;\n  }\n  function openVestuarios(){\n    if(state.role !== \"admin\") return;\n    const rows = (state.dataset && state.dataset.vestuarioAssignments) || [];\n    el(\"[data-vest-list]\").innerHTML = rows.map((r,i) => `<div class=\"ff-vest-row\">\n      <div><strong>${esc(r.turno)} \u00b7 ${esc(r.modalidad)} \u00b7 ${esc(r.groupLabel)}<\/strong><br><small>${esc(r.pestana)}<\/small><\/div>\n      <input class=\"ff-input\" value=\"${esc(r.vestuario || \"\")}\" data-vest-input=\"${i}\" placeholder=\"Vestuario\">\n      <button class=\"ff-button\" data-save-vest=\"${i}\">Guardar<\/button>\n    <\/div>`).join(\"\") || `<div class=\"ff-empty\">No hay grupos.<\/div>`;\n    els(\"[data-save-vest]\", el(\"[data-vest-list]\")).forEach(btn => btn.addEventListener(\"click\", async () => {\n      const i = Number(btn.dataset.saveVest);\n      const r = rows[i];\n      const vestuario = el(`[data-vest-input=\"${i}\"]`).value.trim();\n      btn.disabled = true;\n      try{\n        await postApi(\"setVestuario\", { password: state.password, sede:r.sede, turno:r.turno, pestana:r.pestana, modalidad:r.modalidad, groupNumber:r.groupNumber, groupLabel:r.groupLabel, vestuario, user:\"web\" });\n        r.vestuario = vestuario;\n        (state.dataset.players || []).forEach(p => {\n          if(assignmentApplies(p, r)){\n            p.vestuario = vestuario; p.vestuarioLabel = vestuario || \"SIN VESTUARIO\";\n            p.vestuarioTipo = r.pestana === \"REGLA\" ? \"REGLA\" : \"GRUPO\";\n            p.vestuarioCriterio = r.groupNumber;\n          }\n        });\n        state.dataset.vestuarios = currentVestuarioOptions();\n        fillSelect(el(\"[data-vestuario]\"), state.dataset.vestuarios.map(v => ({ value:v, label:v === \"TODOS\" ? \"Todos\" : v })), state.vestuario);\n        toast(\"Vestuario guardado\");\n        render();\n      }catch(err){ toast(`Error: ${err.message}`); }\n      finally{ btn.disabled = false; }\n    }));\n    el(\"[data-vest-sheet]\").classList.add(\"open\");\n  }\n  function closeSheets(){ els(\".ff-sheet\", root).forEach(s => s.classList.remove(\"open\")); }\n  function setStatus(msg){ el(\"[data-status]\").textContent = msg; }\n  init();\n})();\n<\/script>","protected":false},"excerpt":{"rendered":"<p>F&#038;F Comprobaci\u00f3n Introduce la contrase\u00f1a de acceso. Entrar F&#038;F Comprobaci\u00f3n Buscador interno de grupos y vestuarios Vista Sede Turno Modalidad Vestuario Actualizar JSON Vestuarios Cargando&#8230; Datos Cerrar Vestuarios Cerrar Solo se guarda con contrase\u00f1a Admin.<\/p>","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-11252","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>F&amp;F Comprobaci\u00f3n Interna - Football &amp; Fun<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/footballfun.es\/eus\/ffcomprobacioninterna\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"F&amp;F Comprobaci\u00f3n Interna - Football &amp; Fun\" \/>\n<meta property=\"og:description\" content=\"F&#038;F Comprobaci\u00f3n Introduce la contrase\u00f1a de acceso. Entrar F&#038;F Comprobaci\u00f3n Buscador interno de grupos y vestuarios Vista Sede Turno Modalidad Vestuario Actualizar JSON Vestuarios Cargando&#8230; Datos Cerrar Vestuarios Cerrar Solo se guarda con contrase\u00f1a Admin.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/footballfun.es\/eus\/ffcomprobacioninterna\/\" \/>\n<meta property=\"og:site_name\" content=\"Football &amp; Fun\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-29T15:15:14+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/footballfun.es\/ffcomprobacioninterna\/\",\"url\":\"https:\/\/footballfun.es\/ffcomprobacioninterna\/\",\"name\":\"F&F Comprobaci\u00f3n Interna - Football &amp; Fun\",\"isPartOf\":{\"@id\":\"https:\/\/footballfun.es\/#website\"},\"datePublished\":\"2026-06-25T09:25:48+00:00\",\"dateModified\":\"2026-06-29T15:15:14+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/footballfun.es\/ffcomprobacioninterna\/#breadcrumb\"},\"inLanguage\":\"eu\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/footballfun.es\/ffcomprobacioninterna\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/footballfun.es\/ffcomprobacioninterna\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\/\/footballfun.es\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"F&#038;F Comprobaci\u00f3n Interna\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/footballfun.es\/#website\",\"url\":\"https:\/\/footballfun.es\/\",\"name\":\"Football &amp; Fun\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/footballfun.es\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/footballfun.es\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"eu\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/footballfun.es\/#organization\",\"name\":\"Football &amp; Fun\",\"url\":\"https:\/\/footballfun.es\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"eu\",\"@id\":\"https:\/\/footballfun.es\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/footballfun.es\/wp-content\/uploads\/2021\/04\/logo-web-1.png\",\"contentUrl\":\"https:\/\/footballfun.es\/wp-content\/uploads\/2021\/04\/logo-web-1.png\",\"width\":1865,\"height\":299,\"caption\":\"Football &amp; Fun\"},\"image\":{\"@id\":\"https:\/\/footballfun.es\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"F&F Comprobaci\u00f3n Interna - Football &amp; Fun","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/footballfun.es\/eus\/ffcomprobacioninterna\/","og_locale":"en_US","og_type":"article","og_title":"F&F Comprobaci\u00f3n Interna - Football &amp; Fun","og_description":"F&#038;F Comprobaci\u00f3n Introduce la contrase\u00f1a de acceso. Entrar F&#038;F Comprobaci\u00f3n Buscador interno de grupos y vestuarios Vista Sede Turno Modalidad Vestuario Actualizar JSON Vestuarios Cargando&#8230; Datos Cerrar Vestuarios Cerrar Solo se guarda con contrase\u00f1a Admin.","og_url":"https:\/\/footballfun.es\/eus\/ffcomprobacioninterna\/","og_site_name":"Football &amp; Fun","article_modified_time":"2026-06-29T15:15:14+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/footballfun.es\/ffcomprobacioninterna\/","url":"https:\/\/footballfun.es\/ffcomprobacioninterna\/","name":"F&F Comprobaci\u00f3n Interna - Football &amp; Fun","isPartOf":{"@id":"https:\/\/footballfun.es\/#website"},"datePublished":"2026-06-25T09:25:48+00:00","dateModified":"2026-06-29T15:15:14+00:00","breadcrumb":{"@id":"https:\/\/footballfun.es\/ffcomprobacioninterna\/#breadcrumb"},"inLanguage":"eu","potentialAction":[{"@type":"ReadAction","target":["https:\/\/footballfun.es\/ffcomprobacioninterna\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/footballfun.es\/ffcomprobacioninterna\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/footballfun.es\/"},{"@type":"ListItem","position":2,"name":"F&#038;F Comprobaci\u00f3n Interna"}]},{"@type":"WebSite","@id":"https:\/\/footballfun.es\/#website","url":"https:\/\/footballfun.es\/","name":"Football &amp; Fun","description":"","publisher":{"@id":"https:\/\/footballfun.es\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/footballfun.es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"eu"},{"@type":"Organization","@id":"https:\/\/footballfun.es\/#organization","name":"Football &amp; Fun","url":"https:\/\/footballfun.es\/","logo":{"@type":"ImageObject","inLanguage":"eu","@id":"https:\/\/footballfun.es\/#\/schema\/logo\/image\/","url":"https:\/\/footballfun.es\/wp-content\/uploads\/2021\/04\/logo-web-1.png","contentUrl":"https:\/\/footballfun.es\/wp-content\/uploads\/2021\/04\/logo-web-1.png","width":1865,"height":299,"caption":"Football &amp; Fun"},"image":{"@id":"https:\/\/footballfun.es\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages\/11252","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/comments?post=11252"}],"version-history":[{"count":8,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages\/11252\/revisions"}],"predecessor-version":[{"id":11336,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages\/11252\/revisions\/11336"}],"wp:attachment":[{"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/media?parent=11252"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}