{"id":11274,"date":"2026-06-25T10:28:05","date_gmt":"2026-06-25T09:28:05","guid":{"rendered":"https:\/\/footballfun.es\/fflanzaderainterna\/"},"modified":"2026-07-02T09:49:19","modified_gmt":"2026-07-02T08:49:19","slug":"fflanzaderainterna","status":"publish","type":"page","link":"https:\/\/footballfun.es\/eus\/fflanzaderainterna\/","title":{"rendered":"F&#038;F Lanzadera Interna"},"content":{"rendered":"<meta name=\"robots\" content=\"noindex,nofollow\">\n<style>\n  .ff-shuttle {\n    --ff-ink:#141414; --ff-muted:#667085; --ff-line:#d8dde7; --ff-page:#f3f5f8; --ff-panel:#fff;\n    --ff-yellow:#ffd71b; --ff-green:#16a34a; --ff-red:#dc2626; --ff-blue:#3b6f91; --ff-orange:#ffb020;\n    --ff-shadow:0 12px 28px rgba(20,20,20,.12);\n    min-height:100vh; background:var(--ff-page); color:var(--ff-ink); font-family:Montserrat,Arial,sans-serif; font-size:13px;\n  }\n  .ff-shuttle *{box-sizing:border-box}\n  .ff-shuttle [hidden]{display:none!important}\n  .ffs-header{position:sticky;top:0;z-index:20;background:#111;color:#fff;border-bottom:5px solid var(--ff-yellow);box-shadow:0 4px 18px rgba(0,0,0,.18)}\n  .ffs-top{max-width:1160px;margin:0 auto;padding:11px 10px 8px;display:flex;justify-content:space-between;gap:8px;align-items:center}\n  .ffs-brand strong{display:block;font-size:18px;font-weight:950;text-transform:uppercase;line-height:1.05}\n  .ffs-brand span{color:rgba(255,255,255,.72);font-size:10px;font-weight:850;text-transform:uppercase}\n  .ffs-state{display:inline-flex;align-items:center;justify-content:center;min-height:28px;border-radius:999px;background:var(--ff-yellow);color:#111;padding:4px 10px;font-weight:950;font-size:10px;text-transform:uppercase}\n  .ffs-shell{max-width:1160px;margin:0 auto;padding:10px 8px 92px}\n  .ffs-panel,.ffs-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  .ffs-panel{padding:10px;margin-bottom:10px}\n  .ffs-login{min-height:70vh;display:grid;place-items:center;padding:20px}\n  .ffs-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  .ffs-login h2{margin:0;font-size:24px;text-transform:uppercase}\n  .ffs-login p{margin:0;color:var(--ff-muted);font-weight:750}\n  .ffs-input,.ffs-select{width:100%;min-height:44px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:var(--ff-ink);padding:9px 10px;font:inherit;font-weight:850;outline:none}\n  .ffs-input:focus,.ffs-select:focus{border-color:var(--ff-yellow);box-shadow:0 0 0 3px rgba(255,215,27,.28)}\n  .ffs-button{min-height:42px;border:1px solid #111;border-radius:8px;background:#111;color:#fff;font:inherit;font-weight:950;padding:8px 10px;text-transform:uppercase;cursor:pointer}\n  .ffs-button.alt{background:#fff;color:#111;border-color:#cbd5e1}\n  .ffs-button:disabled{opacity:.45;cursor:not-allowed}\n  .ffs-filters{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:7px}\n  .ffs-field{display:grid;gap:3px}\n  .ffs-field label{color:var(--ff-muted);font-size:9px;font-weight:950;text-transform:uppercase}\n  .ffs-search-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px;margin-top:8px}\n  .ffs-search{min-height:48px;border-width:2px;font-size:16px}\n  .ffs-status{color:var(--ff-muted);font-size:11px;font-weight:850;text-transform:uppercase;margin-top:7px}\n  .ffs-legend{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}\n  .ffs-pill{display:inline-flex;align-items:center;justify-content:center;min-height:24px;border-radius:999px;padding:3px 8px;font-weight:950;font-size:10px;text-transform:uppercase;background:#eef2f7;color:#26344a}\n  .ffs-pill.si{background:#dcfce7;color:#14532d}.ffs-pill.no{background:#fee2e2;color:#991b1b}.ffs-pill.familia{background:#dbeafe;color:#1e3a8a}.ffs-pill.tarde{background:#fef3c7;color:#92400e}.ffs-pill.blank{background:#f1f5f9;color:#475569}\n  .ffs-list{display:grid;gap:8px}\n  .ffs-section-title{position:sticky;top:64px;z-index:10;border-radius:8px;background:#111;color:#fff;padding:8px 10px;font-weight:950;text-transform:uppercase;border-left:5px solid var(--ff-yellow)}\n  .ffs-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(58px,.48fr) minmax(58px,.48fr);gap:6px;align-items:stretch;background:#fff;border:1px solid var(--ff-line);border-radius:10px;padding:7px}\n  .ffs-row.one{grid-template-columns:minmax(0,1fr) minmax(90px,.8fr)}\n  .ffs-player{min-width:0;display:grid;gap:4px}\n  .ffs-name{font-size:15px;font-weight:950;text-transform:uppercase;line-height:1.08;white-space:normal}\n  .ffs-meta{display:flex;flex-wrap:wrap;gap:4px;align-items:center}\n  .ffs-chip{display:inline-flex;align-items:center;min-height:21px;border-radius:6px;padding:3px 6px;font-size:9px;font-weight:950;text-transform:uppercase;background:#eef2f7;color:#26344a}\n  .ffs-chip.mini{background:#e8fff3;color:#05603a}.ffs-chip.pro{background:#dbeafe;color:#173b7a}.ffs-chip.porteros{background:#fff0d9;color:#9a4b00}\n  .ffs-cell{min-height:58px;border:1px solid #cbd5e1;border-radius:8px;background:#f1f5f9;color:#334155;display:grid;place-items:center;font-weight:950;font-size:12px;text-transform:uppercase;position:relative}\n  .ffs-cell.si{background:#dcfce7;color:#14532d;border-color:#86efac}.ffs-cell.no{background:#fee2e2;color:#991b1b;border-color:#fecaca}.ffs-cell.familia{background:#dbeafe;color:#1e3a8a;border-color:#bfdbfe}.ffs-cell.tarde{background:#fef3c7;color:#92400e;border-color:#fde68a}\n  .ffs-cell.saving:after{content:\"\";position:absolute;right:5px;top:5px;width:8px;height:8px;border-radius:50%;background:#111;animation:ffsPulse .8s infinite alternate}\n  @keyframes ffsPulse{from{opacity:.2}to{opacity:1}}\n  .ffs-wide{overflow:auto;border:1px solid var(--ff-line);border-radius:10px;background:#fff}\n  .ffs-table{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%}\n  .ffs-table th,.ffs-table td{border-bottom:1px solid #e5eaf2;border-right:1px solid #eef2f7;padding:6px;vertical-align:middle;background:#fff}\n  .ffs-table th{position:sticky;top:0;background:#111;color:#fff;font-size:10px;text-transform:uppercase;z-index:2}\n  .ffs-table .player-col{position:sticky;left:0;z-index:3;min-width:210px;max-width:230px}\n  .ffs-table td.player-col{z-index:1;background:#fff}\n  .ffs-day-start{border-left:4px solid var(--ff-yellow)!important}\n  .ffs-table .ffs-cell{min-width:70px;min-height:38px}\n  .ffs-sheet{position:fixed;inset:0;z-index:50;display:none;background:rgba(17,17,17,.42);padding:18px 10px;align-items:flex-end}\n  .ffs-sheet.open{display:flex}\n  .ffs-sheet.open[data-data-sheet]{align-items:stretch;padding:0}\n  .ffs-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] .ffs-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  .ffs-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] .ffs-sheet-head{position:static;padding-bottom:0;align-items:center}\n  .ffs-sheet-title{margin:0;font-size:20px;text-transform:uppercase;font-weight:950}\n  [data-data-sheet] .ffs-sheet-title{font-size:clamp(14px,4.2vw,19px);line-height:.95}\n  .ffs-options{display:grid;gap:8px}.ffs-option{min-height:48px;border:1px solid #cbd5e1;border-radius:9px;font-weight:950;text-transform:uppercase}.ffs-option.si{background:#dcfce7;color:#14532d}.ffs-option.no{background:#fee2e2;color:#991b1b}.ffs-option.familia{background:#dbeafe;color:#1e3a8a}.ffs-option.tarde{background:#fef3c7;color:#92400e}.ffs-option.blank{background:#f8fafc;color:#475569}\n  [data-data-body]{min-height:0;overflow:hidden;display:grid;grid-template-rows:auto minmax(0,1fr);gap:5px}\n  .ffs-data-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n  [data-data-sheet] .ffs-data-grid{height:100%;min-height:0;grid-auto-rows:minmax(0,1fr);gap:3px}\n  .ffs-data{border:1px solid #e1e7ef;border-radius:9px;padding:8px;display:grid;gap:3px;min-height:56px}.ffs-data span{color:var(--ff-muted);font-size:10px;font-weight:950;text-transform:uppercase}.ffs-data strong,.ffs-data button{font-size:13px;font-weight:900;text-align:left;color:var(--ff-ink)}.ffs-data button{border:0;background:transparent;padding:0;text-decoration:underline}\n  [data-data-sheet] .ffs-data{min-height:0;padding:3px 5px;overflow:hidden}\n  [data-data-sheet] .ffs-data span{font-size:clamp(7px,2vw,9px);line-height:1}\n  [data-data-sheet] .ffs-data strong,[data-data-sheet] .ffs-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  .ffs-contact{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.ffs-contact 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] .ffs-contact{gap:5px}\n  [data-data-sheet] .ffs-contact a{min-height:28px;font-size:clamp(9px,2.35vw,11px)}\n  .ffs-data-hero{min-height:0;height:100%;display:grid;grid-template-rows:auto auto minmax(0,1fr);gap:6px}\n  .ffs-priority-grid,.ffs-secondary-grid{min-height:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px}\n  .ffs-secondary-grid{grid-auto-rows:minmax(0,1fr)}\n  .ffs-data.primary{border-color:#111;background:#fffdf0}.ffs-data.wide{grid-column:1\/-1}\n  [data-data-sheet] .ffs-data.primary span{color:#111}\n  [data-data-sheet] .ffs-data.primary strong,[data-data-sheet] .ffs-data.primary button{font-size:clamp(10px,2.65vw,13px)}\n  .ffs-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}.ffs-toast.show{display:block}\n  @media(max-width:760px){.ffs-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.ffs-search-row{grid-template-columns:1fr}.ffs-shell{padding:8px 6px 86px}.ffs-section-title{top:56px}.ffs-sheet{padding:0}.ffs-sheet-panel{max-height:92vh;border-radius:16px 16px 0 0}[data-data-sheet] .ffs-sheet-panel{height:100dvh;max-height:none;border-radius:0}.ffs-data-grid{grid-template-columns:1fr}[data-data-sheet] .ffs-data-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ffs-cell{min-height:54px}.ffs-name{font-size:14px}}\n<\/style>\n\n<div class=\"ff-shuttle\" id=\"ffLanzaderaApp\">\n  <div class=\"ffs-login\" data-login>\n    <div class=\"ffs-login-box\">\n      <h2>F&#038;F Lanzadera<\/h2>\n      <p>Introduce la contrase\u00f1a de acceso. Comprobacion y Admin permiten guardar asistencia.<\/p>\n      <input class=\"ffs-input\" type=\"password\" data-password placeholder=\"Contrase\u00f1a\">\n      <button class=\"ffs-button\" data-login-btn>Entrar<\/button>\n      <div class=\"ffs-status\" data-login-status><\/div>\n    <\/div>\n  <\/div>\n\n  <div data-app hidden>\n    <header class=\"ffs-header\">\n      <div class=\"ffs-top\">\n        <div class=\"ffs-brand\"><strong>F&#038;F Lanzadera<\/strong><span>Registro interno de asistencia<\/span><\/div>\n        <div class=\"ffs-state\" data-role>Vista<\/div>\n      <\/div>\n    <\/header>\n    <main class=\"ffs-shell\">\n      <section class=\"ffs-panel\">\n        <div class=\"ffs-filters\">\n          <div class=\"ffs-field\"><label>Sede<\/label><select class=\"ffs-select\" data-sede><\/select><\/div>\n          <div class=\"ffs-field\"><label>Turno<\/label><select class=\"ffs-select\" data-turno><\/select><\/div>\n          <div class=\"ffs-field\"><label>Parada<\/label><select class=\"ffs-select\" data-parada><\/select><\/div>\n          <div class=\"ffs-field\"><label>D\u00eda<\/label><select class=\"ffs-select\" data-dia><\/select><\/div>\n          <div class=\"ffs-field\"><label>Modo<\/label><select class=\"ffs-select\" data-mode><option value=\"REGISTRO\">Registro<\/option><option value=\"TODOS\">Todos los d\u00edas<\/option><\/select><\/div>\n        <\/div>\n        <div class=\"ffs-search-row\">\n          <input class=\"ffs-input ffs-search\" data-search placeholder=\"Buscar nombre, parada, tutor, tel\u00e9fono...\">\n          <button class=\"ffs-button alt\" data-refresh>Actualizar<\/button>\n        <\/div>\n        <div class=\"ffs-legend\">\n          <span class=\"ffs-pill si\">SI<\/span><span class=\"ffs-pill no\">NO<\/span><span class=\"ffs-pill familia\">FAMILIA<\/span><span class=\"ffs-pill tarde\">TARDE<\/span><span class=\"ffs-pill blank\">&#8212;<\/span>\n        <\/div>\n        <div class=\"ffs-status\" data-status>Cargando&#8230;<\/div>\n      <\/section>\n      <section data-results><\/section>\n    <\/main>\n  <\/div>\n\n  <div class=\"ffs-sheet\" data-attendance-sheet>\n    <div class=\"ffs-sheet-panel\">\n      <div class=\"ffs-sheet-head\">\n        <h3 class=\"ffs-sheet-title\" data-att-title>Asistencia<\/h3>\n        <button class=\"ffs-button alt\" data-close-att>Cerrar<\/button>\n      <\/div>\n      <div class=\"ffs-status\" data-att-subtitle><\/div>\n      <div class=\"ffs-options\">\n        <button class=\"ffs-option si\" data-state=\"SI\">SI<\/button>\n        <button class=\"ffs-option no\" data-state=\"NO\">NO<\/button>\n        <button class=\"ffs-option familia\" data-state=\"FAMILIA\">FAMILIA<\/button>\n        <button class=\"ffs-option tarde\" data-state=\"TARDE\">TARDE<\/button>\n        <button class=\"ffs-option blank\" data-state=\"\">BORRAR<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"ffs-sheet\" data-data-sheet>\n    <div class=\"ffs-sheet-panel\">\n      <div class=\"ffs-sheet-head\">\n        <h3 class=\"ffs-sheet-title\" data-data-title>Datos<\/h3>\n        <button class=\"ffs-button alt\" data-close-data>Cerrar<\/button>\n      <\/div>\n      <div data-data-body><\/div>\n    <\/div>\n  <\/div>\n  <div class=\"ffs-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-lanzadera-data\";\n  const VIEW_PASS = \"Comprobacion\";\n  const ADMIN_PASS = \"Admin\";\n  const state = { role:\"\", password:\"\", manifest:null, datasets:null, dataset:null, sede:\"\", turno:\"\", parada:\"TODAS\", dia:\"\", mode:\"REGISTRO\", query:\"\", active:null };\n  const el=(s,r=document)=>r.querySelector(s), els=(s,r=document)=>Array.from(r.querySelectorAll(s));\n  const root=el(\"#ffLanzaderaApp\");\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  function decodeHtml(text){const d=document.createElement(\"textarea\");d.innerHTML=text||\"\";return d.value;}\n  function decodeBase64Json(value){const binary=atob(String(value||\"\").trim());const bytes=Uint8Array.from(binary,ch=>ch.charCodeAt(0));const text=new TextDecoder(\"utf-8\").decode(bytes);return JSON.parse(text);}\n  async function copyText(text,label){if(!text)return;try{await navigator.clipboard.writeText(text);}catch(e){const ta=document.createElement(\"textarea\");ta.value=text;document.body.appendChild(ta);ta.select();document.execCommand(\"copy\");ta.remove();}toast(label||\"Copiado\");}\n  async function loadJsonPage(slug){\n    const res=await fetch(`\/wp-json\/wp\/v2\/pages?slug=${encodeURIComponent(slug)}&_fields=content.rendered,modified,slug`,{cache:\"no-store\"});\n    if(!res.ok)throw new Error(`No se puede cargar ${slug}`);\n    const pages=await res.json(); 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]\"); 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(); if(!data.ok)throw new Error(data.error||\"Error\"); return data;\n  }\n  function fillSelect(select,items,value){select.innerHTML=items.map(i=>`<option value=\"${esc(i.value)}\">${esc(i.label)}<\/option>`).join(\"\"); if(value)select.value=value;}\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\",\"parada\",\"dia\",\"mode\"].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\",loadManifest);\n    el(\"[data-close-att]\").addEventListener(\"click\",closeSheets);\n    el(\"[data-close-data]\").addEventListener(\"click\",closeSheets);\n    el(\"[data-attendance-sheet]\").addEventListener(\"click\",e=>{if(e.target.matches(\"[data-attendance-sheet]\"))closeSheets();});\n    el(\"[data-data-sheet]\").addEventListener(\"click\",e=>{if(e.target.matches(\"[data-data-sheet]\"))closeSheets();});\n    els(\"[data-state]\",el(\"[data-attendance-sheet]\")).forEach(b=>b.addEventListener(\"click\",()=>saveActive(b.dataset.state)));\n    document.addEventListener(\"click\",e=>{\n      const cell=e.target.closest(\"[data-cell]\"); if(cell)openAttendance(cell.dataset.player,cell.dataset.cell);\n      const data=e.target.closest(\"[data-open-data]\"); if(data)openData(Number(data.dataset.player));\n      const copy=e.target.closest(\"[data-copy]\"); if(copy)copyText(copy.dataset.copy,copy.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; state.role=password===ADMIN_PASS?\"admin\":\"editor\";\n    sessionStorage.setItem(\"ff-lanzadera-pass\",password); sessionStorage.setItem(\"ff-lanzadera-role\",state.role);\n    el(\"[data-role]\").textContent=state.role===\"admin\"?\"Admin\":\"Edici\u00f3n\";\n    el(\"[data-login]\").hidden=true; el(\"[data-app]\").hidden=false;\n    await loadManifest();\n  }\n  async function loadManifest(){\n    setStatus(\"Cargando datos vivos...\");\n    try{\n      const live=await postApi(\"exportLanzadera\",{});\n      state.manifest=live.manifest;\n      state.datasets=live.datasets||{};\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){\n      console.warn(\"No se pudieron cargar datos vivos, usando \u00faltima copia publicada\",err);\n      try{\n        state.manifest=await loadJsonPage(`${DATA_PREFIX}-manifest`);\n        state.datasets=null;\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(fallbackErr){setStatus(`Error: ${fallbackErr.message}`);}\n    }\n  }\n  function currentSede(){return (state.manifest.sedes||[]).find(s=>s.key===state.sede)||{turns:[]};}\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    const map={sede:\"sede\",turno:\"turno\",parada:\"parada\",dia:\"dia\",mode:\"mode\"};\n    const key=Object.keys(map).find(k=>e.target.matches(`[data-${k}]`));\n    state[map[key]]=e.target.value;\n    if(key===\"sede\")fillTurns();\n    if(key===\"sede\"||key===\"turno\")await loadCurrent(); else render();\n  }\n  async function loadCurrent(){\n    if(!state.sede||!state.turno)return;\n    setStatus(\"Cargando turno...\");\n    try{\n      const liveKey=`${String(state.sede).toLowerCase()}-${String(state.turno).toLowerCase()}`;\n      state.dataset=state.datasets&&state.datasets[liveKey]?state.datasets[liveKey]:await loadJsonPage(slugFor(state.sede,state.turno));\n      await refreshSnapshot();\n      const stops=[\"TODAS\"].concat(state.dataset.stops||[]);\n      state.parada=stops.includes(state.parada)?state.parada:\"TODAS\";\n      fillSelect(el(\"[data-parada]\"),stops.map(s=>({value:s,label:s===\"TODAS\"?\"Todas\":s})),state.parada);\n      const days=unique((state.dataset.attendanceColumns||[]).map(c=>c.day));\n      state.dia=days.includes(state.dia)?state.dia:(days[0]||\"\");\n      fillSelect(el(\"[data-dia]\"),days.map(d=>({value:d,label:dayLabel(d)})),state.dia);\n      render();\n    }catch(err){setStatus(`Error: ${err.message}`);}\n  }\n  async function refreshSnapshot(){\n    if(!API_URL || API_URL.indexOf(\"__\")===0)return;\n    try{\n      const snap=await postApi(\"snapshotLanzadera\",{sede:state.sede,turno:state.turno});\n      const map=snap.states||{};\n      (state.dataset.players||[]).forEach(p=>Object.values(p.cells||{}).forEach(c=>{if(Object.prototype.hasOwnProperty.call(map,c.recordId))c.value=map[c.recordId]||\"\";}));\n    }catch(err){console.warn(err);}\n  }\n  function filtered(){\n    const q=norm(state.query);\n    return ((state.dataset&&state.dataset.players)||[]).filter(p=>{\n      if(state.parada!==\"TODAS\" && p.parada!==state.parada)return false;\n      if(q && !(p.search||norm(Object.values(p).join(\" \"))).includes(q))return false;\n      return true;\n    });\n  }\n  function visibleColumns(){\n    const cols=state.dataset.attendanceColumns||[];\n    if(state.mode===\"TODOS\")return cols;\n    return cols.filter(c=>c.day===state.dia);\n  }\n  function render(){\n    const rows=filtered(), cols=visibleColumns();\n    const marked=rows.reduce((sum,p)=>sum+cols.filter(c=>p.cells&&p.cells[c.key]&&p.cells[c.key].value).length,0);\n    const total=rows.length*cols.length;\n    setStatus(`${rows.length} jugador\/a${rows.length===1?\"\":\"s\"} \u00b7 ${marked}\/${total} marcadas`);\n    const out=el(\"[data-results]\");\n    if(!rows.length){out.innerHTML=`<div class=\"ffs-panel\">No hay resultados.<\/div>`;return;}\n    out.innerHTML=state.mode===\"TODOS\"?wideHtml(rows,cols):registerHtml(rows,cols);\n  }\n  function groupRows(rows){\n    const groups=[]; let current=\"\";\n    rows.forEach((p,i)=>{ if(p.parada!==current){current=p.parada;groups.push({stop:current,items:[]});} groups[groups.length-1].items.push({p,i});});\n    return groups;\n  }\n  function registerHtml(rows,cols){\n    const one=cols.length===1;\n    return `<div class=\"ffs-list\">${groupRows(rows).map(g=>`\n      <div class=\"ffs-section-title\">${esc(g.stop||\"SIN PARADA\")}<\/div>\n      ${g.items.map(({p,i})=>`<article class=\"ffs-row ${one?\"one\":\"\"}\">\n        ${playerHtml(p,i)}\n        ${cols.map(c=>cellHtml(p,c,i)).join(\"\")}\n      <\/article>`).join(\"\")}\n    `).join(\"\")}<\/div>`;\n  }\n  function wideHtml(rows,cols){\n    return `<div class=\"ffs-wide\"><table class=\"ffs-table\"><thead><tr>\n      <th class=\"player-col\">Jugador\/a<\/th>${cols.map((c,i)=>`<th class=\"${isDayStart(cols,i)?\"ffs-day-start\":\"\"}\">${esc(dayShort(c.day))}<br>${esc(c.route)}<\/th>`).join(\"\")}\n    <\/tr><\/thead><tbody>${rows.map((p,i)=>`<tr><td class=\"player-col\">${playerHtml(p,i)}<\/td>${cols.map((c,j)=>`<td class=\"${isDayStart(cols,j)?\"ffs-day-start\":\"\"}\">${cellHtml(p,c,i)}<\/td>`).join(\"\")}<\/tr>`).join(\"\")}<\/tbody><\/table><\/div>`;\n  }\n  function playerHtml(p,i){\n    const cls=norm(p.modalidad).toLowerCase();\n    return `<div class=\"ffs-player\"><div class=\"ffs-name\">${esc(p.jugador)}<\/div><div class=\"ffs-meta\">\n      <span class=\"ffs-chip\">${esc(p.parada||\"\")}<\/span><span class=\"ffs-chip ${esc(cls)}\">${esc(p.modalidad||\"\")}<\/span>${p.club?`<span class=\"ffs-chip\">${esc(p.club)}<\/span>`:\"\"}<button class=\"ffs-chip\" data-open-data data-player=\"${i}\">Datos<\/button>\n    <\/div><\/div>`;\n  }\n  function cellHtml(p,c,i){\n    const cell=(p.cells&&p.cells[c.key])||{}; const value=cell.value||\"--\"; const cls=norm(cell.value).toLowerCase()||\"blank\";\n    return `<button class=\"ffs-cell ${esc(cls)}\" data-player=\"${i}\" data-cell=\"${esc(c.key)}\">${esc(value)}<\/button>`;\n  }\n  function isDayStart(cols,i){return i===0 || cols[i].day!==cols[i-1].day;}\n  function dayLabel(d){return ({LUNES:\"Lunes\",MARTES:\"Martes\",MIERCOLES:\"Mi\u00e9rcoles\",JUEVES:\"Jueves\",VIERNES:\"Viernes\"}[d]||d);}\n  function dayShort(d){return ({LUNES:\"Lun\",MARTES:\"Mar\",MIERCOLES:\"Mi\u00e9\",JUEVES:\"Jue\",VIERNES:\"Vie\"}[d]||d);}\n  function unique(items){return Array.from(new Set(items.filter(Boolean)));}\n  function openAttendance(playerIndex,cellKey){\n    const p=filtered()[Number(playerIndex)], cell=p&&p.cells&&p.cells[cellKey]; if(!p||!cell)return;\n    state.active={player:p,cell};\n    el(\"[data-att-title]\").textContent=p.jugador;\n    el(\"[data-att-subtitle]\").textContent=`${p.parada} \u00b7 ${dayLabel(cell.day)} \u00b7 ${cell.route} \u00b7 Actual: ${cell.value||\"--\"}`;\n    el(\"[data-attendance-sheet]\").classList.add(\"open\");\n  }\n  async function saveActive(value){\n    if(!state.active)return;\n    if(state.role!==\"admin\"&&state.role!==\"editor\"){toast(\"No tienes permiso para guardar\");return;}\n    const {cell}=state.active, previous=cell.value||\"\";\n    cell.value=value; closeSheets(); render();\n    try{await postApi(\"setAttendance\",{recordId:cell.recordId,estado:value,user:\"web\"}); toast(value?\"Guardado\":\"Borrado\");}\n    catch(err){cell.value=previous; render(); toast(`Error: ${err.message}`);}\n  }\n  function openData(index){\n    const p=filtered()[index]; if(!p)return;\n    el(\"[data-data-title]\").textContent=p.jugador;\n    const phone=String(p.telefono||\"\").replace(\/\\s+\/g,\"\"); const wa=phone?`https:\/\/wa.me\/34${phone.replace(\/^(\\+?34)\/,\"\")}`:\"\";\n    el(\"[data-data-body]\").innerHTML=`<div class=\"ffs-data-hero\">\n      <div class=\"ffs-contact\"><a ${phone?`href=\"tel:${esc(phone)}\"`:`aria-disabled=\"true\"`}>Llamar<\/a><a ${wa?`href=\"${esc(wa)}\" target=\"_blank\" rel=\"noopener\"`:`aria-disabled=\"true\"`}>WhatsApp<\/a><\/div>\n      <div class=\"ffs-priority-grid\">${dataItem(\"Tel\u00e9fono\",p.telefono,\"primary\")}${dataItem(\"Talla\",p.talla||p.tallaSudadera,\"primary\")}${copyItem(\"Email\",p.email,\"Email copiado\",\"primary wide\")}${copyItem(\"A\u00f1o nacimiento\",p.birthYear,\"A\u00f1o copiado\",\"primary\")}${dataItem(\"Parada\",p.parada,\"primary\")}<\/div>\n      <div class=\"ffs-secondary-grid\">${dataItem(\"Tutor\/a\",p.tutor)}${dataItem(\"Sexo\",p.sexo)}${dataItem(\"Club\",p.club)}${dataItem(\"Sudadera\",p.tallaSudadera)}${dataItem(\"Desde\",p.desde)}${dataItem(\"Modalidad\",p.modalidad)}${dataItem(\"Observaciones\",p.observaciones,\"wide\")}<\/div>\n      <\/div>`;\n    el(\"[data-data-sheet]\").classList.add(\"open\");\n  }\n  function dataItem(label,value,extraClass){return `<div class=\"ffs-data ${esc(extraClass||\"\")}\"><span>${esc(label)}<\/span><strong>${esc(value||\"\u2014\")}<\/strong><\/div>`;}\n  function copyItem(label,value,msg,extraClass){return `<div class=\"ffs-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  function closeSheets(){els(\".ffs-sheet\",root).forEach(s=>s.classList.remove(\"open\")); state.active=null;}\n  function setStatus(msg){el(\"[data-status]\").textContent=msg;}\n  bind();\n  const saved=sessionStorage.getItem(\"ff-lanzadera-pass\"); if(saved)enter(saved);\n})();\n<\/script>","protected":false},"excerpt":{"rendered":"<p>F&#038;F Lanzadera Introduce la contrase\u00f1a de acceso. Comprobacion y Admin permiten guardar asistencia. Entrar F&#038;F LanzaderaRegistro interno de asistencia Vista Sede Turno Parada D\u00eda ModoRegistroTodos los d\u00edas Actualizar SINOFAMILIATARDE&#8212; Cargando&#8230; Asistencia Cerrar SI NO FAMILIA TARDE BORRAR Datos Cerrar<\/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-11274","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 Lanzadera 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\/fflanzaderainterna\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"F&amp;F Lanzadera Interna - Football &amp; Fun\" \/>\n<meta property=\"og:description\" content=\"F&#038;F Lanzadera Introduce la contrase\u00f1a de acceso. Comprobacion y Admin permiten guardar asistencia. Entrar F&#038;F LanzaderaRegistro interno de asistencia Vista Sede Turno Parada D\u00eda ModoRegistroTodos los d\u00edas Actualizar SINOFAMILIATARDE&#8212; Cargando&#8230; Asistencia Cerrar SI NO FAMILIA TARDE BORRAR Datos Cerrar\" \/>\n<meta property=\"og:url\" content=\"https:\/\/footballfun.es\/eus\/fflanzaderainterna\/\" \/>\n<meta property=\"og:site_name\" content=\"Football &amp; Fun\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-02T08:49:19+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\/fflanzaderainterna\/\",\"url\":\"https:\/\/footballfun.es\/fflanzaderainterna\/\",\"name\":\"F&F Lanzadera Interna - Football &amp; Fun\",\"isPartOf\":{\"@id\":\"https:\/\/footballfun.es\/#website\"},\"datePublished\":\"2026-06-25T09:28:05+00:00\",\"dateModified\":\"2026-07-02T08:49:19+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/footballfun.es\/fflanzaderainterna\/#breadcrumb\"},\"inLanguage\":\"eu\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/footballfun.es\/fflanzaderainterna\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/footballfun.es\/fflanzaderainterna\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\/\/footballfun.es\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"F&#038;F Lanzadera 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 Lanzadera 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\/fflanzaderainterna\/","og_locale":"en_US","og_type":"article","og_title":"F&F Lanzadera Interna - Football &amp; Fun","og_description":"F&#038;F Lanzadera Introduce la contrase\u00f1a de acceso. Comprobacion y Admin permiten guardar asistencia. Entrar F&#038;F LanzaderaRegistro interno de asistencia Vista Sede Turno Parada D\u00eda ModoRegistroTodos los d\u00edas Actualizar SINOFAMILIATARDE&#8212; Cargando&#8230; Asistencia Cerrar SI NO FAMILIA TARDE BORRAR Datos Cerrar","og_url":"https:\/\/footballfun.es\/eus\/fflanzaderainterna\/","og_site_name":"Football &amp; Fun","article_modified_time":"2026-07-02T08:49:19+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/footballfun.es\/fflanzaderainterna\/","url":"https:\/\/footballfun.es\/fflanzaderainterna\/","name":"F&F Lanzadera Interna - Football &amp; Fun","isPartOf":{"@id":"https:\/\/footballfun.es\/#website"},"datePublished":"2026-06-25T09:28:05+00:00","dateModified":"2026-07-02T08:49:19+00:00","breadcrumb":{"@id":"https:\/\/footballfun.es\/fflanzaderainterna\/#breadcrumb"},"inLanguage":"eu","potentialAction":[{"@type":"ReadAction","target":["https:\/\/footballfun.es\/fflanzaderainterna\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/footballfun.es\/fflanzaderainterna\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/footballfun.es\/"},{"@type":"ListItem","position":2,"name":"F&#038;F Lanzadera 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\/11274","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=11274"}],"version-history":[{"count":8,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages\/11274\/revisions"}],"predecessor-version":[{"id":11359,"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/pages\/11274\/revisions\/11359"}],"wp:attachment":[{"href":"https:\/\/footballfun.es\/eus\/wp-json\/wp\/v2\/media?parent=11274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}