/* PANEL SAAS · Orgullo Millonario — React + Tailwind · Liquid Glass
   Conectado a OM-DB: lo que se edita aquí se refleja EN VIVO en la página principal (index.html). */
const {useState,useEffect,useRef,useMemo,useSyncExternalStore,createContext,useContext}=React;
const useDB=()=>useSyncExternalStore(OMDB.subscribe,OMDB.get);

/* ============ ICONS ============ */
const I={
  dash:<path d="M3 3h7v9H3zM14 3h7v5h-7zM14 12h7v9h-7zM3 16h7v5H3z"/>,
  card:<><rect x="4" y="3" width="12" height="16" rx="2"/><path d="M8 21h12V8"/></>,
  squad:<><circle cx="9" cy="8" r="3"/><circle cx="17" cy="10" r="2.5"/><path d="M3 20a6 6 0 0 1 12 0M13 20a5 5 0 0 1 8-4"/></>,
  spark:<path d="m13 2-8 12h6l-2 8 8-12h-6l2-8Z"/>,
  gift:<><path d="M4 9h16v10H4z"/><path d="M4 9l2-5 6 3 6-3 2 5M12 9v10"/></>,
  heart:<path d="M12 21C7 17 3 13.5 3 9.5A4.5 4.5 0 0 1 12 7a4.5 4.5 0 0 1 9 2.5c0 4-4 7.5-9 11.5Z"/>,
  play:<><rect x="2" y="4" width="20" height="16" rx="3"/><path d="m10 8 6 4-6 4V8Z"/></>,
  chat:<path d="M21 12a8 8 0 0 1-8 8H4l2-3a8 8 0 1 1 15-5Z"/>,
  grid:<><rect x="3" y="3" width="8" height="8" rx="2"/><rect x="13" y="3" width="8" height="8" rx="2"/><rect x="3" y="13" width="8" height="8" rx="2"/><rect x="13" y="13" width="8" height="8" rx="2"/></>,
  bell:<path d="M6 9a6 6 0 1 1 12 0c0 5 2 6 2 6H4s2-1 2-6M10 19a2 2 0 0 0 4 0"/>,
  search:<><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></>,
  x:<path d="M6 6l12 12M18 6 6 18"/>,
  down:<path d="M4 4v12h12M8 10l4 4 6-8"/>,
  up:<path d="M4 20V8h12M8 14l4-4 6 8"/>,
  eye:<><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6Z"/><circle cx="12" cy="12" r="2.5"/></>,
  drag:<><circle cx="9" cy="6" r="1.4"/><circle cx="15" cy="6" r="1.4"/><circle cx="9" cy="12" r="1.4"/><circle cx="15" cy="12" r="1.4"/><circle cx="9" cy="18" r="1.4"/><circle cx="15" cy="18" r="1.4"/></>,
  users:<><circle cx="9" cy="8" r="3.2"/><path d="M3 20a6 6 0 0 1 12 0"/><path d="M16 5.5a3 3 0 0 1 0 5.8M21 20a5.5 5.5 0 0 0-4-5.3"/></>,
  trophy:<><path d="M7 4h10v4a5 5 0 0 1-10 0V4Z"/><path d="M7 6H4v1a3 3 0 0 0 3 3M17 6h3v1a3 3 0 0 1-3 3M9 20h6M12 14v6"/></>,
  video:<><rect x="2" y="5" width="14" height="14" rx="3"/><path d="m16 10 6-3v10l-6-3"/></>,
  plus:<path d="M12 5v14M5 12h14"/>,
  trash:<><path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13h10l1-13"/></>,
  sun:<><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></>,
  moon:<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/>,
};
const Ic=({n,s=18,c=""})=><svg viewBox="0 0 24 24" width={s} height={s} className={c} fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">{I[n]}</svg>;

/* ===== Inputs estables a nivel de módulo (NO redefinir dentro del render:
   eso remonta el <input> en cada tecla y hace perder el foco) ===== */
const F=({label,val,onChange,mono})=>(
  <label className="block"><span className="font-mono text-[8.5px] tracking-[1.5px] text-mut block mb-1.5">{label}</span>
    <input value={val||''} onChange={e=>onChange(e.target.value)} className={`w-full bg-white/[.06] border border-white/10 rounded-xl px-3.5 py-2.5 text-[12px] outline-none focus:border-river/60 transition-colors ${mono?'font-mono':''}`}/></label>);
const Inp=({v,set,ph,type='text'})=><input value={v||''} onChange={e=>set(e.target.value)} placeholder={ph} type={type} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[12px] outline-none focus:border-river/60 transition-colors"/>;
const Fld=({icon,...p})=><label className="flex items-center gap-3 bg-white/[.06] border border-white/12 rounded-2xl px-4 py-3.5 focus-within:border-river/60 transition-colors"><span className="text-mut flex-none"><Ic n={icon} s={16}/></span><input {...p} className="flex-1 bg-transparent text-[14px] outline-none placeholder:text-dim min-w-0"/></label>;

/* ============ SWITCH (estilo coming soon) ============ */
function Switch({on,onChange,size=18}){
  const w=size*2, h=size+4, k=size;
  return <button onClick={e=>{e.stopPropagation();onChange(!on);}} className="press flex-none rounded-full relative transition-all duration-300" style={{width:w,height:h,background:on?'linear-gradient(90deg,#ff2e3e,#8d0917)':'rgba(255,255,255,.14)',boxShadow:on?'0 0 12px rgba(224,30,47,.5), inset 0 1px 2px rgba(0,0,0,.3)':'inset 0 1px 3px rgba(0,0,0,.4)',border:'1px solid rgba(255,255,255,.12)'}}>
    <span className="absolute rounded-full bg-white transition-all duration-300" style={{width:k-2,height:k-2,top:2,left:on?w-k:2,boxShadow:'0 1px 4px rgba(0,0,0,.45)'}}/>
  </button>;
}

/* ============ SHIRT 360 ============ */
function Shirt360({front,back,s=110}){
  const [ang,setAng]=useState(0);const drag=useRef(null);const auto=useRef(true);const resume=useRef();
  useEffect(()=>{let last=performance.now(),raf;const loop=t=>{const dt=t-last;last=t;if(auto.current)setAng(a=>(a+dt*0.05)%360);raf=requestAnimationFrame(loop);};raf=requestAnimationFrame(loop);return()=>cancelAnimationFrame(raf);},[]);
  const down=e=>{auto.current=false;clearTimeout(resume.current);drag.current={x:e.clientX,a:ang};e.currentTarget.setPointerCapture&&e.currentTarget.setPointerCapture(e.pointerId);};
  const move=e=>{if(drag.current)setAng(drag.current.a+(e.clientX-drag.current.x)*0.8);};
  const up=()=>{if(drag.current){drag.current=null;resume.current=setTimeout(()=>auto.current=true,2000);}};
  return <div className="flex flex-col items-center gap-1.5 select-none">
    <div onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up} className="relative cursor-grab active:cursor-grabbing touch-none" style={{width:s,height:s*1.25,perspective:'600px'}}>
      <div className="absolute inset-0" style={{transformStyle:'preserve-3d',transform:`rotateY(${ang}deg)`}}>
        <img src={front} draggable="false" className="absolute inset-0 w-full h-full object-contain drop-shadow-[0_10px_20px_rgba(0,0,0,.6)]" style={{backfaceVisibility:'hidden'}}/>
        <img src={back||front} draggable="false" className="absolute inset-0 w-full h-full object-contain drop-shadow-[0_10px_20px_rgba(0,0,0,.6)]" style={{backfaceVisibility:'hidden',transform:'rotateY(180deg)'}}/>
      </div>
    </div>
    <span className="font-mono text-[7px] tracking-[1px] text-mut">↺ GIRA 360°</span>
  </div>;
}

/* ============ TOAST ============ */
const ToastCtx=createContext(()=>{});
function ToastHost({children}){
  const [msg,setMsg]=useState(null);const t=useRef();
  const toast=m=>{setMsg(m);clearTimeout(t.current);t.current=setTimeout(()=>setMsg(null),2400);};
  return <ToastCtx.Provider value={toast}>{children}
    <div className={`fixed left-1/2 -translate-x-1/2 bottom-24 md:bottom-8 z-[90] transition-all duration-300 ${msg?'opacity-100 translate-y-0':'opacity-0 translate-y-4 pointer-events-none'}`}>
      <div className="glass rounded-2xl px-5 py-3 text-[13px] font-semibold border-river/40" style={{borderColor:'rgba(224,30,47,.45)'}}>{msg}</div>
    </div>
  </ToastCtx.Provider>;
}

/* ============ SHARED ============ */
const Sec=({label,children,right})=>(
  <div className="flex items-center justify-between mb-3">
    <span className="font-mono text-[9px] tracking-[2px] text-mut">{label}</span>{right}
  </div>
);
const Bar=({pct,cls})=>(
  <div className="h-[7px] rounded-full bg-white/10 overflow-hidden">
    <i className={`block h-full rounded-full ${cls}`} style={{width:pct+'%',background:cls?undefined:'linear-gradient(90deg,#8d0917,#ff2e3e)'}}/>
  </div>
);

/* ============ MODULES ============ */
function KpiModule(){
  const db=useDB();const k=db.kpis;
  const kpis=[[k.viajes,"VIAJES PUBLICADOS","#2f7df6","🚌"],[k.reservas,"RESERVAS","#22c55e","📅"],[k.socios,"SOCIOS","#ffcf3e","👤"],[k.visitas,"VISITAS TOTALES","#ff2e3e","👁"]];
  return <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
    {kpis.map(([n,l,c,e],i)=>(
      <div key={i} className="glass rounded-3xl p-4 press cursor-default overflow-hidden">
        <div className="flex items-start justify-between"><b className="font-disp text-[30px] leading-none pr-6" style={{color:c}}>{n}</b><span className="text-[15px] opacity-90">{e}</span></div>
        <small className="font-mono text-[8px] tracking-[1.5px] text-mut block mt-2 leading-relaxed">{l}</small>
        <div className="absolute inset-x-0 bottom-0 h-[3px]" style={{background:`linear-gradient(90deg,${c},transparent)`}}/>
      </div>))}
  </div>;
}

/* ============ SITIO & HERO — controla la página principal EN VIVO ============ */
function SiteModule(){
  const db=useDB();const toast=useContext(ToastCtx);const c=db.config;
  const heroRef=useRef();
  const [drag,setDrag]=useState(null);
  const secs=c.sections||[];
  const modular=!!c.modular;
  const [ak,setAk]=useState('');const [al,setAl]=useState('128');const [at,setAt]=useState('435');const [asn,setAsn]=useState('2025');
  const [live,setLive]=useState(()=>!!(window.OMDB&&OMDB.isLive&&OMDB.isLive()));
  const conectarAPI=async()=>{if(!ak.trim()){toast('Pegá tu API key de API-Football');return;}toast('Conectando con la API…');const ok=await OMDB.setAPI({key:ak.trim(),league:+al,season:+asn,team:+at});setLive(ok);toast(ok?'✔ Datos EN VIVO desde la API':'No se pudo conectar · sigue en DEMO');};
  const onHero=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{OMDB.set('config.hero_img',r.result);toast('Imagen del hero actualizada en el sitio');};r.readAsDataURL(f);};
  const move=(from,to)=>{if(from===to||to<0||to>=secs.length)return;OMDB.update(s=>{const a=s.config.sections;const [it]=a.splice(from,1);a.splice(to,0,it);});};
  const toggleSec=i=>OMDB.update(s=>{s.config.sections[i].on=!s.config.sections[i].on;});
  const toggleBlk=(i,j)=>OMDB.update(s=>{s.config.sections[i].blocks[j].on=!s.config.sections[i].blocks[j].on;});
  const allOn=v=>OMDB.update(s=>{s.config.sections.forEach(x=>{x.on=v;(x.blocks||[]).forEach(b=>b.on=v);});});
  return <>
    <Sec label="TODO ESTO SE REFLEJA EN VIVO EN LA PÁGINA" right={<a href={(c.site_url||'index.html')} target="_blank" rel="noopener" className="press font-mono text-[8.5px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-3 py-1.5 hover:bg-cyanx/10">ABRIR SITIO ↗</a>}/>
    <div className="flex flex-col gap-3">
      <F label="TÍTULO DEL HERO" val={c.hero_titulo} onChange={v=>OMDB.set('config.hero_titulo',v)}/>
      <F label="SUBTÍTULO" val={c.hero_subtitulo} onChange={v=>OMDB.set('config.hero_subtitulo',v)}/>
      <div className="grid grid-cols-2 gap-3">
        <F label="WHATSAPP" val={c.whatsapp} onChange={v=>OMDB.set('config.whatsapp',v)} mono/>
        <F label="LINK MERCADO PAGO" val={c.mp_link} onChange={v=>OMDB.set('config.mp_link',v)} mono/>
      </div>
      <div>
        <div className="flex items-center justify-between mb-1.5">
          <span className="font-mono text-[8.5px] tracking-[1.5px] text-mut">IMAGEN DEL HERO</span>
          {c.hero_img&&<button onClick={()=>{OMDB.set('config.hero_img','');toast('Imagen del hero quitada · queda solo el video/fondo');}} className="press font-mono text-[8px] tracking-[1px] text-river2 flex items-center gap-1 px-2 py-1 rounded-lg" style={{background:'rgba(224,30,47,.12)',border:'1px solid rgba(224,30,47,.3)'}}><Ic n="trash" s={11}/> QUITAR IMAGEN</button>}
        </div>
        <input ref={heroRef} type="file" accept="image/*" onChange={onHero} className="hidden"/>
        <div onDragOver={e=>e.preventDefault()} onDrop={e=>{e.preventDefault();const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f&&f.size<=4.5e6){const r=new FileReader();r.onload=()=>{OMDB.set('config.hero_img',r.result);toast('Imagen del hero actualizada');};r.readAsDataURL(f);}else{const u=e.dataTransfer.getData('text/uri-list')||e.dataTransfer.getData('text/plain');if(u&&/^https?:/.test(u)){OMDB.set('config.hero_img',u);toast('Imagen del hero actualizada');}}}}
          onClick={()=>heroRef.current.click()} className="press cursor-pointer relative rounded-2xl overflow-hidden border-2 border-dashed border-white/12 hover:border-cyanx/60 transition-colors" style={{aspectRatio:'16/7',background:'linear-gradient(160deg,#1a1a20,#0e0e12)'}}>
          {c.hero_img?<img src={c.hero_img} alt="" className="absolute inset-0 w-full h-full object-cover"/>:<div className="absolute inset-0 grid place-items-center text-center"><span className="font-mono text-[9px] tracking-[1px] text-dim">SIN IMAGEN · el hero usa el video de fondo</span></div>}
          <div className="absolute inset-0" style={{background:'linear-gradient(transparent 55%,rgba(0,0,0,.75))'}}/>
          <span className="absolute bottom-2 left-1/2 -translate-x-1/2 flex items-center gap-2 font-mono text-[9px] tracking-[1px] text-white px-3 py-1.5 rounded-full" style={{background:'rgba(47,125,246,.85)'}}>⬆ Arrastrá o clic para {c.hero_img?'cambiar':'subir'}</span>
        </div>
      </div>
      <div>
        <span className="font-mono text-[8px] tracking-[1.5px] text-mut block mb-1.5">TEMA DEL SITIO</span>
        <div className="grid grid-cols-2 gap-2">
          {[['dark','Oscuro'],['light','Claro']].map(([t,l])=>(
            <button key={t} onClick={()=>{OMDB.set('config.tema',t);toast('Tema: '+l);}} className={`press rounded-xl py-2.5 text-[12px] font-bold border transition-all ${c.tema===t?'text-white border-river':'text-mut border-white/10'}`} style={c.tema===t?{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}:{}}>{l}</button>))}
        </div>
      </div>

      {/* ===== PROMO / BANNER DEL ÁLBUM (Mi Cuenta) ===== */}
      <div>
        <span className="font-mono text-[8px] tracking-[1.5px] text-mut block mb-1.5">BANNER / PUBLICIDAD DEL ÁLBUM (MI CUENTA)</span>
        <div onDragOver={e=>e.preventDefault()} onDrop={e=>{e.preventDefault();const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f&&f.size<=4.5e6){const r=new FileReader();r.onload=()=>{OMDB.set('config.album_promo.img',r.result);toast('Banner del álbum actualizado');};r.readAsDataURL(f);}else{const u=e.dataTransfer.getData('text/uri-list')||e.dataTransfer.getData('text/plain');if(u&&/^https?:/.test(u)){OMDB.set('config.album_promo.img',u);toast('Banner del álbum actualizado');}}}}
          onClick={()=>{const i=document.createElement('input');i.type='file';i.accept='image/*';i.onchange=e=>{const f=e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Máx 4.5MB');return;}const r=new FileReader();r.onload=()=>{OMDB.set('config.album_promo.img',r.result);toast('Banner del álbum actualizado');};r.readAsDataURL(f);};i.click();}}
          className="press cursor-pointer relative rounded-2xl overflow-hidden border-2 border-dashed border-white/12 hover:border-cyanx/60 transition-colors grid place-items-center" style={{aspectRatio:'16/9',background:'linear-gradient(160deg,#1a1a20,#0e0e12)'}}>
          {(c.album_promo&&c.album_promo.img)?<img src={c.album_promo.img} alt="" className="absolute inset-0 w-full h-full object-cover"/>:<span className="font-mono text-[9px] tracking-[1px] text-dim">⬆ Arrastrá imagen/animación o clic</span>}
        </div>
        <div className="grid grid-cols-1 gap-2 mt-2">
          <input value={(c.album_promo&&c.album_promo.link)||''} onChange={e=>OMDB.set('config.album_promo.link',e.target.value)} placeholder="Link de destino (https://…)" className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11px] font-mono outline-none focus:border-cyanx/60"/>
          <input value={(c.album_promo&&c.album_promo.titulo)||''} onChange={e=>OMDB.set('config.album_promo.titulo',e.target.value)} placeholder="Título / texto del espacio" className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11px] outline-none focus:border-cyanx/60"/>
          {(c.album_promo&&c.album_promo.img)&&<button onClick={()=>{OMDB.set('config.album_promo.img','');toast('Banner quitado');}} className="press rounded-xl py-2 text-[11px] font-bold text-river2 border border-river/40">Quitar imagen</button>}
        </div>
      </div>

      {/* modularización y constructor viven en sus propias tarjetas */}

      {/* ===== DATOS REALES · API-FOOTBALL ===== */}
      <div className="pt-1">
        <Sec label="DATOS REALES · API-FOOTBALL (api-sports.io)" right={<span className={`font-mono text-[8px] tracking-[1px] px-2 py-1 rounded-lg ${live?'text-green-400 border border-green-400/40':'text-dim border border-white/15'}`}>{live?'● ON':'○ OFF'}</span>}/>
        <F label="API KEY" val={ak} onChange={setAk} mono/>
        <div className="grid grid-cols-3 gap-2 mt-2">
          <label className="block"><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">LIGA</span><input value={al} onChange={e=>setAl(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-2 py-2 text-[11px] font-mono text-center outline-none focus:border-river/60"/></label>
          <label className="block"><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">EQUIPO</span><input value={at} onChange={e=>setAt(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-2 py-2 text-[11px] font-mono text-center outline-none focus:border-river/60"/></label>
          <label className="block"><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">TEMPORADA</span><input value={asn} onChange={e=>setAsn(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-2 py-2 text-[11px] font-mono text-center outline-none focus:border-river/60"/></label>
        </div>
        <button onClick={conectarAPI} className="press rounded-xl py-2.5 mt-2 w-full text-[12px] font-bold text-white" style={{background:'linear-gradient(160deg,#2f7df6,#1b4fb0)',boxShadow:'0 8px 22px -8px rgba(47,125,246,.6)'}}>Conectar datos en vivo</button>
        <p className="font-mono text-[7.5px] text-dim tracking-[.5px] mt-1.5">Liga 128 = Liga Profesional Argentina · Equipo 435 = River Plate · sin key el indicador queda en OFF</p>
      </div>

      {/* constructor movido a su propia tarjeta */}

      <button onClick={()=>{OMDB.reset();toast('Datos restablecidos a los valores de fábrica');}} className="press glass rounded-xl py-2.5 text-[11px] font-bold text-mut hover:text-ink mt-1">↺ Restablecer datos del sitio</button>
    </div>
  </>;
}

/* ============ MODULARIZACIÓN (tarjeta separada) ============ */
function ModularModule(){
  const db=useDB();const toast=useContext(ToastCtx);const modular=!!db.config.modular;
  return <>
    <Sec label="ACTIVÁ O DESACTIVÁ LA MODULARIZACIÓN DEL SITIO"/>
    <button onClick={()=>{OMDB.set('config.modular',!modular);toast(modular?'Modularización DESACTIVADA · el sitio muestra todo':'Modularización ACTIVADA · ya podés ocultar secciones');}}
      className="press w-full flex items-center gap-3 rounded-2xl px-4 py-4 border transition-all" style={{borderColor:modular?'rgba(224,30,47,.55)':'rgba(255,255,255,.1)',background:modular?'linear-gradient(160deg,rgba(255,46,62,.16),rgba(255,255,255,.02))':'rgba(255,255,255,.04)'}}>
      <span className={modular?'text-river2':'text-dim'}><Ic n="grid" s={20}/></span>
      <div className="flex-1 text-left"><b className="text-[14px] block">MODULARIZACIÓN {modular?'ACTIVA':'DESACTIVADA'}</b><small className="font-mono text-[8px] text-mut">{modular?'Lo que apagues en el Constructor se oculta del sitio':'El sitio muestra todas las secciones'}</small></div>
      <span className={`w-12 h-7 rounded-full p-[3px] transition-colors flex-none ${modular?'bg-river':'bg-white/15'}`}><i className={`block w-[22px] h-[22px] rounded-full bg-white shadow transition-transform ${modular?'translate-x-5':''}`}/></span>
    </button>
    <div className="grid grid-cols-2 gap-2 mt-3 flex-1 items-stretch">
      <div className="rounded-2xl border border-white/[.08] bg-white/[.04] p-3 flex flex-col items-center justify-center"><b className="font-disp text-[24px] block leading-none" style={{color:modular?'#22c55e':'#8a8a97'}}>{modular?'ON':'OFF'}</b><small className="font-mono text-[7px] tracking-[1px] text-mut mt-1">ESTADO</small></div>
      <a href={(db.config.site_url||'index.html')} target="_blank" rel="noopener" className="press rounded-2xl border border-cyanx/40 text-cyanx grid place-items-center font-mono text-[10px] tracking-[1px]">ABRIR SITIO ↗</a>
    </div>
  </>;
}

/* ============ CONSTRUCTOR DEL SITIO (3 tarjetas) ============ */
function ConstructorModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const secs=db.config.sections||[];const modular=!!db.config.modular;
  const [drag,setDrag]=useState(null);
  const move=(from,to)=>{if(from===to||to<0||to>=secs.length)return;OMDB.update(s=>{const a=s.config.sections;const [it]=a.splice(from,1);a.splice(to,0,it);});};
  const toggleSec=i=>OMDB.update(s=>{s.config.sections[i].on=!s.config.sections[i].on;});
  const toggleBlk=(i,j)=>OMDB.update(s=>{s.config.sections[i].blocks[j].on=!s.config.sections[i].blocks[j].on;});
  const allOn=v=>OMDB.update(s=>{s.config.sections.forEach(x=>{x.on=v;(x.blocks||[]).forEach(b=>b.on=v);});});
  const per=Math.max(1,Math.ceil(secs.length/3));
  const groups=[[0,secs.slice(0,per)],[per,secs.slice(per,per*2)],[per*2,secs.slice(per*2)]];
  const row=(sc,i)=>(
    <div key={sc.id} className={`rounded-2xl border transition-all ${drag===i?'border-river bg-river/10':'border-white/[.08] bg-white/[.05]'} ${sc.on?'':'opacity-50'}`}>
      <div draggable onDragStart={()=>setDrag(i)} onDragEnd={()=>setDrag(null)} onDragOver={e=>{e.preventDefault();if(drag!==null&&drag!==i){move(drag,i);setDrag(i);}}} className="flex items-center gap-2 px-2.5 py-2 cursor-grab active:cursor-grabbing">
        <span className="text-dim flex-none"><Ic n="drag" s={15}/></span>
        <b className="text-[11.5px] flex-1 min-w-0 truncate">{sc.label}</b>
        {sc.blocks&&<span className="font-mono text-[7px] text-cyanx tracking-[.5px] hidden xl:block">{sc.blocks.filter(b=>b.on).length}/{sc.blocks.length}</span>}
        <button onClick={()=>toggleSec(i)} className={`press w-7 h-7 rounded-lg grid place-items-center flex-none ${sc.on?'text-cyanx':'text-dim'}`}><Ic n="eye" s={14}/></button>
        <div className="flex flex-col flex-none">
          <button onClick={()=>move(i,i-1)} className="press w-5 h-4 grid place-items-center text-dim hover:text-ink"><Ic n="down" s={10} c="rotate-180"/></button>
          <button onClick={()=>move(i,i+1)} className="press w-5 h-4 grid place-items-center text-dim hover:text-ink"><Ic n="down" s={10}/></button>
        </div>
      </div>
      {sc.on&&sc.blocks&&sc.blocks.length>0&&<div className="flex flex-col gap-1 px-2.5 pb-2 pl-7">
        {sc.blocks.map((b,j)=>(
          <div key={b.id} className={`flex items-center gap-2 rounded-xl px-2 py-1 bg-black/25 border border-white/[.05] ${b.on?'':'opacity-45'}`}>
            <span className="text-dim text-[10px] flex-none">↳</span>
            <span className="text-[10.5px] flex-1 min-w-0 truncate">{b.label}</span>
            <button onClick={()=>toggleBlk(i,j)} className={`press w-7 h-6 rounded-lg grid place-items-center flex-none ${b.on?'text-cyanx':'text-dim'}`}><Ic n="eye" s={12}/></button>
          </div>))}
      </div>}
    </div>);
  return <>
    <Sec label={modular?'ARRASTRÁ PARA ORDENAR · OJO = MOSTRAR/OCULTAR · SE ORDENA ENTRE LAS 3 TARJETAS':'ACTIVÁ LA MODULARIZACIÓN PARA QUE APLIQUE EN EL SITIO'} right={<div className="flex gap-1.5"><button onClick={()=>allOn(true)} className="press font-mono text-[8px] tracking-[1px] text-green-400 border border-green-400/40 rounded-lg px-2 py-1">TODO ON</button><button onClick={()=>allOn(false)} className="press font-mono text-[8px] tracking-[1px] text-mut border border-white/15 rounded-lg px-2 py-1">TODO OFF</button></div>}/>
    <div className={`grid grid-cols-1 md:grid-cols-3 gap-3 flex-1 ${modular?'':'opacity-60'}`}>
      {groups.map(([base,g],gi)=>(
        <div key={gi} className="rounded-2xl border border-white/[.06] bg-black/20 p-2.5 flex flex-col gap-1.5">
          <span className="font-mono text-[7.5px] tracking-[2px] text-cyanx px-1 mb-0.5">GRUPO {gi+1}</span>
          {g.map((sc,k)=>row(sc,base+k))}
        </div>))}
    </div>
  </>;
}

function RevenueModule(){
  const [pts,setPts]=useState(()=>Array.from({length:56},(_,i)=>48+Math.sin(i/5)*13));
  const [val,setVal]=useState(24900);
  useEffect(()=>{let raf,last=performance.now(),acc=0;
    const tick=t=>{acc+=t-last;last=t;
      if(acc>110){acc=0;
        setPts(p=>{const nx=Math.max(12,Math.min(82,p[p.length-1]+(Math.random()-.48)*9));return[...p.slice(1),nx];});
        setVal(v=>Math.max(18500,Math.min(41000,v+Math.round((Math.random()-.44)*180))));}
      raf=requestAnimationFrame(tick);};
    raf=requestAnimationFrame(tick);return()=>cancelAnimationFrame(raf);},[]);
  const W=260,H=88,n=pts.length;
  const xy=i=>[(i/(n-1))*W,H-(pts[i]/94*H)];
  const line=pts.map((_,i)=>xy(i).join(',')).join(' ');
  const [hx,hy]=xy(n-1);
  const up=pts[n-1]>=pts[n-2];const col=up?'#22c55e':'#ff3b45';
  const users=(window.OMAuth&&typeof OMAuth.users==='function')?(OMAuth.users()||[]):[];
  const PLAN=[['VIP','#8b5cf6'],['Oro','#ffcf3e'],['Plata','#c0c0cb'],['Bronce','#c97b3a'],['Gratuito','#5b93ff']];
  const price={VIP:8490,ORO:4490,PLATA:2490,BRONCE:1290};const pp=x=>price[(x||'').toUpperCase()]||0;
  const dist=PLAN.map(([k,c])=>[k,c,users.filter(u=>(u.nivel||'').toUpperCase()===k.toUpperCase()).length]);
  let totU=dist.reduce((s,d)=>s+d[2],0);if(!totU){dist[0][2]=1;totU=1;}
  const R=26,CC=2*Math.PI*R;let off=0;
  const arcs=dist.filter(d=>d[2]>0).map(([k,c,v])=>{const dash=v/totU*CC;const seg={c,dash,off};off+=dash;return seg;});
  const payers=users.filter(u=>u.nivel&&!/GRAT/i.test(u.nivel));
  const fmt=x=>'$'+Math.round(x).toLocaleString('es-AR');
  return <>
    <Sec label="FACTURACIÓN · TIEMPO REAL" right={<b className="font-disp text-[16px]" style={{color:col}}>{fmt(val)}<span className="text-[10px] text-mut">/mes</span></b>}/>
    <div className="relative">
      <svg viewBox="0 0 260 88" className="w-full" preserveAspectRatio="none" style={{height:96}}>
        <defs><linearGradient id="fg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor={col} stopOpacity=".35"/><stop offset="1" stopColor={col} stopOpacity="0"/></linearGradient></defs>
        <line x1="0" y1="24" x2="260" y2="24" stroke="#22c55e" strokeWidth="1" strokeDasharray="4 4" opacity=".5"/>
        <polygon points={`0,88 ${line} 260,88`} fill="url(#fg)"/>
        <polyline points={line} fill="none" stroke={col} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>
        <circle cx={hx} cy={hy} r="3.4" fill={col}/>
        <circle cx={hx} cy={hy} r="6" fill="none" stroke={col}><animate attributeName="r" values="4;9;4" dur="1.4s" repeatCount="indefinite"/><animate attributeName="opacity" values=".6;0;.6" dur="1.4s" repeatCount="indefinite"/></circle>
      </svg>
      <span className="absolute top-1 left-1 font-mono text-[7px] tracking-[1px] px-1.5 py-0.5 rounded" style={{color:col,background:col+'22'}}>{up?'▲':'▼'} LIVE</span>
    </div>
    <div className="grid grid-cols-4 gap-2 mt-2 text-center">
      {[[fmt(val),"MES ACTUAL","text-gold2"],["$1.22M","META ANUAL","text-ink"],["20,41%","PROGRESO","text-green-400"],[String(payers.length||users.length),"OPERACIONES","text-ink"]].map(([b,s,cc],i)=>(
        <div key={i}><b className={`font-mono text-[11px] block ${cc}`}>{b}</b><small className="font-mono text-[6.5px] text-dim tracking-wide">{s}</small></div>))}
    </div>
    <div className="grid grid-cols-[auto_1fr] gap-4 items-center mt-4 pt-3 border-t border-white/10">
      <div className="relative w-[84px] h-[84px] flex-none">
        <svg viewBox="0 0 72 72" className="w-full h-full -rotate-90">
          <circle cx="36" cy="36" r="26" fill="none" stroke="rgba(255,255,255,.08)" strokeWidth="9"/>
          {arcs.map((a,i)=><circle key={i} cx="36" cy="36" r="26" fill="none" stroke={a.c} strokeWidth="9" strokeDasharray={`${a.dash} ${CC-a.dash}`} strokeDashoffset={-a.off}/>)}
        </svg>
        <div className="absolute inset-0 grid place-items-center"><div className="text-center"><b className="font-disp text-[18px] leading-none">{totU}</b><small className="font-mono text-[6px] text-mut block">SOCIOS</small></div></div>
      </div>
      <div className="min-w-0">
        <small className="font-mono text-[7px] tracking-[2px] text-mut block mb-1.5">SOCIOS POR PLAN</small>
        <div className="grid grid-cols-2 gap-x-3 gap-y-1">
          {dist.map(([k,c,v])=><div key={k} className="flex items-center gap-1.5"><i className="w-2 h-2 rounded-sm flex-none" style={{background:c}}/><span className="text-[10px] flex-1 truncate">{k}</span><b className="font-mono text-[10px]">{v}</b></div>)}
        </div>
      </div>
    </div>
    <div className="mt-3 flex-1 flex flex-col min-h-0">
      <small className="font-mono text-[7px] tracking-[2px] text-mut block mb-1.5">USUARIOS QUE GENERARON EL PAGO</small>
      <div className="flex flex-col gap-1 overflow-y-auto">
        {payers.length?payers.map((u,i)=>(
          <div key={i} className="flex items-center gap-2 rounded-xl px-2.5 py-1.5 bg-white/[.04] border border-white/[.06]">
            <span className="w-6 h-6 rounded-full grid place-items-center font-disp text-[10px] text-gold2 flex-none" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{(u.nombre||'?')[0]}</span>
            <span className="text-[11px] flex-1 truncate">{u.nombre||u.email}</span>
            <span className="font-mono text-[7px] text-mut">{u.nivel}</span>
            <b className="font-mono text-[10px] text-green-400">{fmt(pp(u.nivel))}</b>
          </div>)):<div className="text-mut text-[11px] px-1 py-2">Sin pagos registrados aún · aparecerán cuando un socio pague.</div>}
      </div>
    </div>
  </>;
}

function WorldModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const geo=db.visitas_geo||[];
  const [hover,setHover]=useState(null);
  const dots=useMemo(()=>{
    let seed=77;const rnd=()=>((seed=(seed*1103515245+12345)&0x7fffffff)/0x7fffffff);
    const zones=[[18,18,55,32,.5],[55,52,28,48,.6],[112,12,40,26,.5],[118,40,32,38,.55],[150,14,60,40,.5],[196,60,26,20,.45]];
    const a=[];zones.forEach(z=>{for(let i=0;i<z[2]/4;i++)for(let j=0;j<z[3]/4;j++)if(rnd()<z[4])a.push([z[0]+i*4+rnd()*2,z[1]+j*4+rnd()*2]);});return a;
  },[]);
  // contador en vivo: incrementa una visita aleatoria cada pocos segundos
  useEffect(()=>{const t=setInterval(()=>{OMDB.update(s=>{if(!s.visitas_geo||!s.visitas_geo.length)return;const i=Math.floor(Math.random()*s.visitas_geo.length);s.visitas_geo[i].hoy++;s.visitas_geo[i].total++;s.kpis.visitas=(s.kpis.visitas||0)+1;s.kpis.visitas_hoy=(s.kpis.visitas_hoy||0)+1;});},6000);return()=>clearInterval(t);},[]);
  const max=Math.max(...geo.map(g=>g.total),1);
  const tabla=[...geo].sort((a,b)=>b.total-a.total);
  const totVis=db.kpis.visitas||0, hoyVis=db.kpis.visitas_hoy||0;
  return <>
    <Sec label="VISITAS GEOLOCALIZADAS · EN VIVO" right={<span className="flex items-center gap-2 font-mono text-[8.5px] text-green-400"><i className="w-[6px] h-[6px] rounded-full bg-green-400" style={{animation:'pulseDot 1.4s infinite'}}/>EN VIVO</span>}/>
    <div className="grid md:grid-cols-[1.4fr_1fr] gap-3 flex-1">
      <div className="relative rounded-2xl overflow-hidden" style={{background:'radial-gradient(120% 90% at 50% 30%,#101a2e,#070a12 70%)',border:'1px solid rgba(80,140,255,.12)'}}>
        <svg viewBox="0 0 260 140" className="w-full">
          <defs>
            <radialGradient id="vglow" cx="50%" cy="50%" r="50%"><stop offset="0" stopColor="#ff2e3e" stopOpacity=".9"/><stop offset="1" stopColor="#ff2e3e" stopOpacity="0"/></radialGradient>
            <filter id="vblur"><feGaussianBlur stdDeviation="1.1"/></filter>
          </defs>
          {dots.map((d,i)=><circle key={i} cx={d[0]} cy={d[1]} r=".8" fill="rgba(120,170,255,.16)"/>)}
          {geo.map((g,i)=>{const hub=geo[0];if(i===0||g.total===0)return null;return <path key={'l'+i} d={`M${hub.x},${hub.y} Q${(hub.x+g.x)/2},${Math.min(hub.y,g.y)-14} ${g.x},${g.y}`} fill="none" stroke="rgba(255,46,62,.28)" strokeWidth=".5" strokeDasharray="2 3"><animate attributeName="stroke-dashoffset" from="10" to="0" dur="1.4s" repeatCount="indefinite"/></path>;})}
          {geo.map((g,i)=>{const r=2+((g.total/max)*6);const act=hover===i;return <g key={i} style={{cursor:'pointer'}} onMouseEnter={()=>setHover(i)} onMouseLeave={()=>setHover(null)}>
            <circle cx={g.x} cy={g.y} r={r*3} fill="url(#vglow)" opacity={act?.5:.28} filter="url(#vblur)"/>
            <circle cx={g.x} cy={g.y} r={r*1.9} fill="none" stroke="#ff2e3e" strokeWidth=".5" opacity=".4"><animate attributeName="r" from={r} to={r*2.6} dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" from=".5" to="0" dur="2s" repeatCount="indefinite"/></circle>
            <circle cx={g.x} cy={g.y} r={act?r*1.35:r} fill="#ff4b58" stroke="#fff" strokeWidth={act?.7:.3}/>
            {act&&<g><rect x={g.x-27} y={g.y-16} width="54" height="11" rx="2.5" fill="#0c0f18" stroke="#ff2e3e" strokeWidth=".4"/><text x={g.x} y={g.y-8.2} textAnchor="middle" fill="#fff" fontSize="4.4" fontFamily="monospace" fontWeight="700">{g.lugar} · {g.total}</text></g>}
          </g>;})}
        </svg>
        <span className="absolute bottom-2 left-3 font-mono text-[6.5px] tracking-[1px] text-mut">🌎 EN VIVO · pasá el cursor por cada punto</span>
      </div>
      <div className="flex flex-col">
        <div className="grid grid-cols-2 gap-2 mb-2">
          <div className="bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5 text-center"><b className="font-disp text-[22px] text-river2 leading-none tabular-nums">{totVis.toLocaleString('es-AR')}</b><small className="font-mono text-[6.5px] tracking-[1px] text-mut block mt-1">VISITAS TOTALES</small></div>
          <div className="bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5 text-center"><b className="font-disp text-[22px] text-cyanx leading-none tabular-nums">{hoyVis.toLocaleString('es-AR')}</b><small className="font-mono text-[6.5px] tracking-[1px] text-mut block mt-1">HOY</small></div>
        </div>
        <div className="flex flex-col gap-1 overflow-y-auto max-h-[220px] pr-1">
          <div className="flex items-center font-mono text-[6.5px] tracking-[1px] text-dim px-2 mb-0.5"><span className="flex-1">UBICACIÓN</span><span className="w-8 text-right">HOY</span><span className="w-10 text-right">TOTAL</span></div>
          {tabla.map((g,i)=><div key={i} className="flex items-center gap-2 rounded-lg px-2 py-1.5 bg-white/[.03]">
            <i className="w-1.5 h-1.5 rounded-full bg-river2 flex-none"/>
            <b className="text-[10px] flex-1 truncate">{g.lugar}</b>
            <span className="font-mono text-[9px] text-cyanx w-8 text-right">{g.hoy}</span>
            <span className="font-mono text-[9px] text-gold2 w-10 text-right">{g.total}</span>
          </div>)}
          {tabla.every(g=>g.total===0)&&<p className="text-mut text-[10px] py-3 text-center">Sin visitas aún · empezarán a entrar al publicar el sitio.</p>}
        </div>
      </div>
    </div>
  </>;
}

function ReferidosModule(){
  const db=useDB();const toast=useContext(ToastCtx);const r=db.referidos;
  const list=[...r.lista].sort((a,b)=>b.ganancia-a.ganancia);
  const total=list.reduce((s,x)=>s+x.ganancia,0);
  const invit=list.reduce((s,x)=>s+x.invitados,0);
  const activos=list.reduce((s,x)=>s+x.activos,0);
  const max=Math.max(...list.map(x=>x.ganancia),1);
  const fmt=n=>'$'+n.toLocaleString('es-AR');
  return <>
    <Sec label="GANANCIAS POR SOCIOS QUE INVITAN · COMISIÓN" right={<span className="font-mono text-[8.5px] text-gold2 tracking-[1px]">{r.comision_pct}% C/SOCIO</span>}/>
    <div className="grid grid-cols-3 gap-2 mb-4">
      {[[fmt(total),'GANANCIA TOTAL','text-green-400'],[invit,'INVITADOS','text-ink'],[activos,'SOCIOS ACTIVOS','text-gold2']].map(([b,s,cc],i)=>(
        <div key={i} className="bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5 text-center"><b className={`font-disp text-[18px] block leading-none ${cc}`}>{b}</b><small className="font-mono text-[6.5px] tracking-[1px] text-mut block mt-1.5">{s}</small></div>))}
    </div>
    <div className="flex flex-col gap-2 flex-1">
      {list.map((x,i)=>(
        <div key={x.codigo} className="flex items-center gap-3 bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5">
          <span className="w-8 h-8 rounded-full grid place-items-center font-disp font-bold text-[13px] flex-none" style={{background:i===0?'linear-gradient(150deg,#ffcf3e,#d4a437)':'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)',color:i===0?'#1a1206':'#c0c0cb'}}>{i+1}</span>
          <div className="min-w-0 flex-1">
            <div className="flex items-center gap-2"><b className="text-[12px] truncate">{x.nombre}</b><span className="font-mono text-[7px] tracking-[1px] px-1.5 py-[2px] rounded bg-cyanx/10 text-cyanx flex-none">{x.codigo}</span></div>
            <div className="h-[5px] rounded-full bg-white/10 overflow-hidden mt-1.5"><i className="block h-full rounded-full" style={{width:x.ganancia/max*100+'%',background:'linear-gradient(90deg,#189e5b,#26d07c)'}}/></div>
          </div>
          <div className="text-right flex-none">
            <b className="font-mono text-[12px] text-green-400 block leading-none">{fmt(x.ganancia)}</b>
            <small className="font-mono text-[7px] text-dim">{x.invitados} inv · {x.activos} act</small>
          </div>
        </div>))}
    </div>
    <button onClick={()=>toast('Comisiones exportadas · listo para liquidar')} className="press glass rounded-2xl py-2.5 mt-3 text-[11px] font-bold text-mut hover:text-ink w-full">Liquidar comisiones del mes</button>
  </>;
}

function FiguritasModule(){
  const toast=useContext(ToastCtx);
  const RZDEF=[["COMÚN","#9a9aa6","comun",false],["PLATA","#c0c0cb","plata",true],["ORO","#ffcf3e","oro",true],["LEYENDA","#ff2e3e","leyenda",true],["HOLOGRÁFICA","#8b5cf6","holografica",false],["ULTRA PREMIUM","#22d3ee","premium",false]];
  const [rarezas,setRarezas]=useState(()=>{try{const v=JSON.parse(localStorage.getItem('om-figu-rarezas'));if(Array.isArray(v)&&v.length)return v;}catch(e){}return RZDEF.map(([name,color,slug,dark])=>({name,color,tex:'assets/rareza/'+slug+'.png',dark}));});
  useEffect(()=>{try{localStorage.setItem('om-figu-rarezas',JSON.stringify(rarezas));}catch(e){}},[rarezas]);
  const CLUBS=[["River","assets/clubs/river.png"],["Boca","assets/clubs/boca.png"],["Racing","assets/clubs/racing.png"],["Independiente","assets/clubs/independiente.png"],["San Lorenzo","assets/clubs/sanlorenzo.png"],["Estudiantes","assets/clubs/estudiantes.png"],["Gimnasia","assets/clubs/gimnasia.png"],["Huracán","assets/clubs/huracan.png"],["Vélez","assets/clubs/velez.png"],["Tigre","assets/clubs/tigre.png"],["Newell's","assets/clubs/newells.png"],["Rosario Central","assets/clubs/rosario.png"],["Argentinos","assets/clubs/argentinos.png"],["Banfield","assets/clubs/banfield.png"],["Belgrano","assets/clubs/belgrano.png"],["Sarmiento","assets/clubs/sarmiento.png"],["Aldosivi","assets/clubs/aldosivi.png"],["Barracas","assets/clubs/barracas.png"],["Atl. Tucumán","assets/clubs/tucuman.png"],["Rivadavia","assets/clubs/rivadavia.png"],["Flamengo","assets/clubs/flamengo.png"],["Bragantino","assets/clubs/bragantino.png"],["Peñarol","assets/clubs/penarol.png"],["Millonarios","assets/clubs/millonarios.png"],["Santa Fe","assets/clubs/santafe.png"],["Blooming","assets/clubs/blooming.png"],["Caracas","assets/clubs/caracas.png"],["Carabobo","assets/clubs/carabobo.png"],["Ciudad Bolívar","assets/clubs/ciudadbolivar.png"],["Estudiantes RC","assets/clubs/estudiantesrc.png"],["Indep. Rivadavia","assets/clubs/indeprivadavia.png"]];
  const FLAGS=[["Argentina","ar"],["Brasil","br"],["Uruguay","uy"],["Colombia","co"],["Chile","cl"],["Paraguay","py"],["Venezuela","ve"],["Perú","pe"]];
  const flagUrl=(cc,w=80)=>`https://flagcdn.com/w${w}/${cc}.png`;
  const [rz,setRz]=useState(4);
  const [name,setName]=useState("Franco Armani");
  const [num,setNum]=useState("23");
  const [pos,setPos]=useState("ARQ");
  const [club,setClub]=useState(0);
  const [flag,setFlag]=useState(0);
  const [photo,setPhoto]=useState(null);
  const fileRef=useRef();const addRef=useRef();const cardRef=useRef();const drag=useRef(null);
  const [flip,setFlip]=useState(false);
  const [debut,setDebut]=useState('1996');
  const [edad,setEdad]=useState('38');
  const onDropPhoto=e=>{e.preventDefault();e.stopPropagation();const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(!f||!f.type.startsWith('image/')){return;}if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{setPhoto(r.result);toast('Foto del jugador cargada');};r.readAsDataURL(f);};
  const [sel,setSel]=useState('foto');
  const [layers,setLayers]=useState(()=>{try{const v=JSON.parse(localStorage.getItem('om-figu-layers'));if(Array.isArray(v)&&v.length)return v;}catch(e){}return [
    {id:'foto',  label:'Foto del jugador', tipo:'foto',   x:50, y:60, w:84, on:true},
    {id:'escudo',label:'Escudo del club',  tipo:'escudo', x:15, y:11, w:20, on:true},
    {id:'flag',  label:'Bandera + año',    tipo:'flag',   x:85, y:10, w:22, on:true},
    {id:'pitch', label:'Cancha / posición',tipo:'pitch',  x:15, y:35, w:16, on:true},
  ];});
  useEffect(()=>{try{localStorage.setItem('om-figu-layers',JSON.stringify(layers.filter(l=>l.tipo!=='custom')));}catch(e){}},[layers]);
  const patch=(id,p)=>setLayers(ls=>ls.map(l=>l.id===id?{...l,...p}:l));
  const zOf=t=>t==='foto'?10:t==='custom'?24:30;
  const startDrag=(e,id)=>{e.stopPropagation();setSel(id);const r=cardRef.current.getBoundingClientRect();const L=layers.find(l=>l.id===id);drag.current={id,sx:e.clientX,sy:e.clientY,ox:L.x,oy:L.y,rw:r.width,rh:r.height};
    const mv=ev=>{const d=drag.current;if(!d)return;patch(d.id,{x:Math.max(2,Math.min(98,d.ox+(ev.clientX-d.sx)/d.rw*100)),y:Math.max(2,Math.min(98,d.oy+(ev.clientY-d.sy)/d.rh*100))});};
    const up=()=>{drag.current=null;window.removeEventListener('pointermove',mv);window.removeEventListener('pointerup',up);};
    window.addEventListener('pointermove',mv);window.addEventListener('pointerup',up);};
  const addImage=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{const id='u'+Date.now();setLayers(ls=>[...ls,{id,label:'Imagen '+(ls.filter(l=>l.tipo==='custom').length+1),tipo:'custom',src:r.result,x:50,y:45,w:34,on:true}]);setSel(id);toast('Imagen agregada · arrastrala en la tarjeta');};r.readAsDataURL(f);e.target.value='';};
  const removeLayer=id=>{setLayers(ls=>ls.filter(l=>l.id!==id));if(sel===id)setSel(null);};
  const srcOf=l=>l.tipo==='foto'?photo:l.tipo==='escudo'?CLUBS[club][1]:l.tipo==='flag'?flagUrl(FLAGS[flag][1],160):l.src;
  const selLayer=layers.find(l=>l.id===sel);
  useEffect(()=>{
    const h=e=>{
      if(e.key!=='Delete'&&e.key!=='Backspace')return;
      const ae=document.activeElement;if(ae&&/INPUT|TEXTAREA|SELECT/.test(ae.tagName))return;
      if(sel==='foto'){setPhoto(null);toast('Foto eliminada');}
      else if(selLayer&&selLayer.tipo==='custom'){removeLayer(sel);toast('Capa eliminada');}
    };
    window.addEventListener('keydown',h);return()=>window.removeEventListener('keydown',h);
  },[sel,selLayer]);
  const [year,setYear]=useState('2001');const [alt,setAlt]=useState('1,70');const [peso,setPeso]=useState('67');
  const [nac,setNac]=useState('03-11-1979');const [sigla,setSigla]=useState('CARP');
  const [enr,setEnr]=useState('EN RIVER PLATE: 1996 - 2000');
  const [frontBg,setFrontBg]=useState(()=>{try{return localStorage.getItem('om-figu-frontbg')||null;}catch(e){return null;}});const [backBg,setBackBg]=useState(()=>{try{return localStorage.getItem('om-figu-backbg')||null;}catch(e){return null;}});
  useEffect(()=>{try{frontBg?localStorage.setItem('om-figu-frontbg',frontBg):localStorage.removeItem('om-figu-frontbg');}catch(e){}},[frontBg]);
  useEffect(()=>{try{backBg?localStorage.setItem('om-figu-backbg',backBg):localStorage.removeItem('om-figu-backbg');}catch(e){}},[backBg]);
  const [nameFont,setNameFont]=useState('Oswald');const [nameSize,setNameSize]=useState(34);const [nameColor,setNameColor]=useState('');
  const [precio,setPrecio]=useState('');
  const [dorsoY,setDorsoY]=useState(6);const [dorsoSize,setDorsoSize]=useState(30);const [dorsoAlign,setDorsoAlign]=useState('center');
  const rzFileRef=useRef();
  const addRareza=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{setRarezas(rs=>{const nx=[...rs,{name:'RAREZA '+(rs.length+1),color:'#22d3ee',tex:r.result,dark:false,custom:true}];setRz(nx.length-1);return nx;});toast('Plantilla de rareza agregada');};r.readAsDataURL(f);e.target.value='';};
  const removeRareza=i=>{setRarezas(rs=>{if(rs.length<=1)return rs;const nx=rs.filter((_,j)=>j!==i);return nx;});setRz(z=>Math.max(0,z>=i?z-1:z));toast('Plantilla quitada');};
  const bgFRef=useRef();const bgBRef=useRef();
  const POS=[['ARQ','Arquero',50,90],['LD','Lateral derecho',82,73],['DFC','Defensa central',50,79],['LI','Lateral izquierdo',18,73],['MCD','Volante de contención',50,61],['MC','Mediocampista central',50,48],['MCO','Enganche / mediapunta',50,35],['MD','Volante derecho',83,45],['MI','Volante izquierdo',17,45],['ED','Extremo derecho',83,20],['EI','Extremo izquierdo',17,20],['DC','Delantero centro',50,12],['SD','Segundo delantero',50,25]];
  const [posIdx,setPosIdx]=useState(0);
  const posab=POS[posIdx][0];const posx=POS[posIdx][2];const posy=POS[posIdx][3];
  const loadBg=(e,set,lbl)=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{set(r.result);toast(lbl+' cargado');};r.readAsDataURL(f);e.target.value='';};
  const TEX=['comun','plata','oro','leyenda','holografica','premium'];
  const R=rarezas[rz]||rarezas[0];
  const tex=R.tex;
  const frontTex=frontBg||tex;const backTex=backBg||tex;
  const dark=!!R.dark;const tcol=nameColor||(dark?'#2a1c08':'#f4f4f6');const tsh=dark?'0 1px 0 rgba(255,255,255,.35)':'0 2px 6px rgba(0,0,0,.8)';
  const c=R.color;
  const onFile=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{setPhoto(r.result);toast('Imagen del jugador cargada');};r.readAsDataURL(f);};
  const crear=()=>{
    if(!name.trim()){toast('Poné un nombre');return;}
    const equipo=CLUBS[club][0].toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').split(' ')[0];
    OMDB.update(s=>{s.figuritas=[{clave:'c'+Date.now(),nombre:name.trim(),tier:R.name,categoria:R.name,equipo,clubNombre:CLUBS[club][0],img:photo,dorso:backBg||null,precio:parseInt(precio)||(s.gamif&&s.gamif.preciosRareza&&s.gamif.preciosRareza[R.name])||0,tenida:true,cant:1,num,pos,flag:FLAGS[flag][1],club:CLUBS[club][0]},...s.figuritas].slice(0,300);});
    toast(`✔ ${name} · ${CLUBS[club][0]} · ${R.name} · $${(parseInt(precio)||0).toLocaleString('es-AR')} — publicada al pool`);
  };
  const exportPNG=async()=>{
    const W=520,H=728,cv=document.createElement('canvas');cv.width=W;cv.height=H;const x=cv.getContext('2d');
    const rr=(a,b,w,h,r)=>{x.beginPath();x.moveTo(a+r,b);x.arcTo(a+w,b,a+w,b+h,r);x.arcTo(a+w,b+h,a,b+h,r);x.arcTo(a,b+h,a,b,r);x.arcTo(a,b,a+w,b,r);x.closePath();};
    rr(0,0,W,H,40);x.clip();
    const g=x.createLinearGradient(0,0,W,H);g.addColorStop(0,c+'55');g.addColorStop(.5,'#1a1226');g.addColorStop(1,'#0b0714');x.fillStyle=g;x.fillRect(0,0,W,H);
    const load=src=>new Promise(res=>{const im=new Image();im.crossOrigin='anonymous';im.onload=()=>res(im);im.onerror=()=>res(null);im.src=src;});
    const bgim=await load(frontTex);if(bgim){const s=Math.max(W/bgim.width,H/bgim.height);const bw=bgim.width*s,bh=bgim.height*s;x.drawImage(bgim,(W-bw)/2,(H-bh)/2,bw,bh);}
    const drawImgLayer=async l=>{const cx=W*l.x/100,cy=H*l.y/100,w=W*l.w/100;const im=await load(srcOf(l));if(!im)return;const h=w*(im.height/im.width);x.drawImage(im,cx-w/2,cy-h/2,w,h);return{cx,cy,w,h};};
    const drawPitch=l=>{const cx=W*l.x/100,cy=H*l.y/100,w=W*l.w/100,h=w*3/2;x.save();rr(cx-w/2,cy-h/2,w,h,8);x.clip();const pg=x.createLinearGradient(0,cy-h/2,0,cy+h/2);pg.addColorStop(0,'#2f7a2f');pg.addColorStop(1,'#1c4d1c');x.fillStyle=pg;x.fillRect(cx-w/2,cy-h/2,w,h);x.strokeStyle='rgba(255,255,255,.4)';x.lineWidth=1.2;x.beginPath();x.moveTo(cx-w/2,cy);x.lineTo(cx+w/2,cy);x.stroke();x.beginPath();x.arc(cx,cy,w*0.17,0,7);x.stroke();
      const dx=cx-w/2+w*posx/100, dy=cy-h/2+h*posy/100;x.fillStyle='#ff2e3e';x.strokeStyle='#fff';x.lineWidth=2;x.beginPath();x.arc(dx,dy,w*0.1,0,7);x.fill();x.stroke();
      x.restore();};
    const vis=layers.filter(l=>l.on);
    for(const l of vis.filter(l=>zOf(l.tipo)<20)) await drawImgLayer(l);
    // dark gradient + iridiscente + streak (rareza / holográfico)
    const ov=x.createLinearGradient(0,H*0.45,0,H);ov.addColorStop(0,'rgba(0,0,0,0)');ov.addColorStop(1,'rgba(0,0,0,.95)');x.fillStyle=ov;x.fillRect(0,H*0.45,W,H*0.55);
    x.save();x.globalCompositeOperation='overlay';x.globalAlpha=.45;x.translate(W/2,H*0.42);x.rotate(-0.9);const cols=['#ff0078','#00c8ff','#78ff50','#ffdc00'];for(let i=-34;i<34;i++){x.fillStyle=cols[((i%4)+4)%4];x.fillRect(i*24,-H,12,H*2);}x.restore();
    x.save();x.globalCompositeOperation='color-dodge';x.globalAlpha=.32;x.translate(W/2,H*0.42);x.rotate(-0.9);for(let i=-34;i<34;i++){x.fillStyle=(i%2?c:'#ffffff');x.fillRect(i*18,-H,7,H*2);}x.restore();
    const hs=x.createLinearGradient(0,0,W,H);hs.addColorStop(.3,'rgba(255,255,255,0)');hs.addColorStop(.46,'rgba(255,255,255,.5)');hs.addColorStop(.5,c);hs.addColorStop(.54,'rgba(255,255,255,.4)');hs.addColorStop(.68,'rgba(255,255,255,0)');x.fillStyle=hs;x.fillRect(0,0,W,H);
    // chrome: capas superiores (escudo/flag/pitch/custom)
    for(const l of vis.filter(l=>zOf(l.tipo)>=20)){
      if(l.tipo==='pitch'){drawPitch(l);continue;}
      const d=await drawImgLayer(l);if(!d)continue;
      if(l.tipo==='flag'){x.strokeStyle='rgba(255,255,255,.7)';x.lineWidth=2.5;rr(d.cx-d.w/2,d.cy-d.h/2,d.w,d.h,8);x.stroke();}
      if(l.tipo==='escudo'){x.fillStyle=tcol;x.textAlign='center';x.font='700 20px Oswald,sans-serif';x.fillText(sigla,d.cx,d.cy+d.h/2+20);}
    }
    x.fillStyle='rgba(255,255,255,.14)';x.font='900 130px Oswald, sans-serif';x.textAlign='right';x.fillText(num,W-30,158);
    x.textAlign='center';x.fillStyle='#fff';x.font='700 '+Math.round(nameSize*1.35)+'px '+nameFont+', sans-serif';x.fillText(name.toUpperCase().slice(0,16),W/2,H-92);
    x.fillStyle=c;x.font='700 22px "JetBrains Mono", monospace';x.fillText(R.name+' · #'+num,W/2,H-54);
    x.strokeStyle=c;x.lineWidth=8;rr(4,4,W-8,H-8,36);x.stroke();
    const a=document.createElement('a');a.download=`figurita-${name.toLowerCase().replace(/\s+/g,'-')||'cromo'}.png`;a.href=cv.toDataURL('image/png');a.click();
    toast('PNG descargado ✔');
  };
  return <>
    <div className="grid grid-cols-1 lg:grid-cols-[1fr_minmax(380px,560px)] gap-5">
      <div className="flex flex-col gap-4 min-w-0">
        <div>
          <Sec label="DATOS DEL JUGADOR"/>
          <input value={name} onChange={e=>setName(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-2xl px-4 py-3 text-[13px] font-semibold outline-none focus:border-river/60 transition-colors" placeholder="Nombre del jugador"/>
          <div className="grid grid-cols-[1fr_1fr] gap-2 mt-2">
            <input value={num} onChange={e=>setNum(e.target.value.slice(0,3))} className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11px] font-mono text-center outline-none focus:border-river/60 min-w-0" placeholder="N°"/>
            <select value={pos} onChange={e=>setPos(e.target.value)} className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11px] font-mono text-center outline-none focus:border-river/60 min-w-0">{["ARQ","DEF","MED","DEL","DT"].map(p=><option key={p} className="bg-[#15151a]">{p}</option>)}</select>
          </div>
        </div>
        <div>
          <Sec label="FOTO DEL JUGADOR"/>
          <input ref={fileRef} type="file" accept="image/*" onChange={onFile} className="hidden"/>
          <div onDragOver={e=>{e.preventDefault();}} onDrop={onDropPhoto} onClick={()=>fileRef.current.click()}
            className="press cursor-pointer rounded-2xl border-2 border-dashed border-white/15 hover:border-cyanx/60 bg-white/[.04] px-4 py-4 flex items-center gap-3 transition-colors">
            <span className="w-12 h-12 rounded-xl overflow-hidden grid place-items-center flex-none bg-black/30 border border-white/10">{photo?<img src={photo} alt="" className="w-full h-full object-contain"/>:<Ic n="plus" s={18} c="text-mut"/>}</span>
            <div className="min-w-0 flex-1">
              <b className="text-[11.5px] block">Arrastrá una foto aquí</b>
              <small className="font-mono text-[7.5px] text-dim tracking-[.5px]">o hacé clic para subir · PNG recortado ideal · máx 4.5MB</small>
            </div>
          </div>
          <div className="grid grid-cols-2 gap-2 mt-2">
            <button onClick={()=>{setPhoto('assets/armani.png');toast('Foto de ejemplo');}} className="press glass rounded-xl py-2 text-[10.5px] font-bold text-mut hover:text-ink">Ejemplo</button>
            <button onClick={()=>{setPhoto(null);setSel('foto');toast('Foto eliminada');}} className="press glass rounded-xl py-2 text-[10.5px] font-bold text-mut hover:text-river2 flex items-center justify-center gap-1.5"><Ic n="trash" s={13}/> Quitar foto</button>
          </div>
          <p className="font-mono text-[7px] text-dim tracking-[.5px] mt-1.5">Tip: seleccioná la foto en la tarjeta y presioná <b className="text-mut">Supr / Delete</b> para eliminarla.</p>
        </div>
        <div>
          <Sec label="RAREZA / EDICIÓN"/>
          <div className="flex flex-wrap gap-2 items-center">
            {rarezas.map((rr,i)=>(
              <span key={i} className="relative group">
                <button onClick={()=>setRz(i)}
                  className={`press rounded-full px-4 py-2 text-[10px] font-mono tracking-[1px] border transition-all ${i===rz?'text-white':'text-mut border-white/10 hover:text-ink'}`}
                  style={i===rz?{borderColor:rr.color,background:`color-mix(in srgb, ${rr.color} 22%, transparent)`,boxShadow:`0 0 20px ${rr.color}44`}:{}}>{rr.name}</button>
                {rr.custom&&<button onClick={()=>removeRareza(i)} title="Quitar" className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-crimson text-white text-[9px] grid place-items-center border border-white/30">×</button>}
              </span>))}
            <input ref={rzFileRef} type="file" accept="image/*" onChange={addRareza} className="hidden"/>
            <button onClick={()=>rzFileRef.current.click()} className="press rounded-full px-3 py-2 text-[10px] font-mono tracking-[1px] border border-cyanx/40 text-cyanx hover:bg-cyanx/10">＋ PLANTILLA</button>
          </div>
        </div>
        <div className="flex flex-col gap-3">
          <div><Sec label="ESCUDO"/><div className="flex gap-1.5 overflow-x-auto pb-1.5" style={{scrollbarWidth:'thin'}}>{CLUBS.map(([n,src],i)=><button key={n} onClick={()=>setClub(i)} title={n} className={`press w-9 h-9 rounded-lg grid place-items-center bg-white/[.05] border flex-none ${i===club?'border-river':'border-white/10'}`}><img src={src} alt={n} className="w-6 h-6 object-contain"/></button>)}</div></div>
          <div><Sec label="BANDERA"/><div className="flex gap-1.5 overflow-x-auto pb-1.5" style={{scrollbarWidth:'thin'}}>{FLAGS.map((f,i)=><button key={i} onClick={()=>setFlag(i)} title={f[0]} className={`press w-9 h-9 rounded-lg grid place-items-center overflow-hidden bg-white/[.05] border flex-none ${i===flag?'border-river':'border-white/10'}`}><img src={flagUrl(f[1],40)} alt={f[0]} className="w-7 h-5 object-cover rounded-sm"/></button>)}</div></div>
        </div>
        <div>
          <Sec label="CAPAS · ARRASTRÁ EN LA TARJETA · MOSTRAR / QUITAR" right={<button onClick={()=>addRef.current.click()} className="press font-mono text-[8px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-2.5 py-1 hover:bg-cyanx/10">＋ IMAGEN</button>}/>
          <input ref={addRef} type="file" accept="image/*" onChange={addImage} className="hidden"/>
          <div className="flex flex-col gap-1.5">
            {layers.map(l=>(
              <div key={l.id} onClick={()=>setSel(l.id)} className={`flex items-center gap-2 rounded-xl px-2.5 py-2 border cursor-pointer transition-colors ${sel===l.id?'border-river bg-river/10':'border-white/[.08] bg-white/[.05]'} ${l.on?'':'opacity-45'}`}>
                <span className="w-7 h-7 rounded-lg overflow-hidden grid place-items-center flex-none bg-black/30">{l.tipo==='pitch'?<span className="text-[12px]">🟩</span>:<img src={srcOf(l)} alt="" className="w-full h-full object-contain"/>}</span>
                <b className="text-[11px] flex-1 truncate">{l.label}</b>
                <Switch on={l.on} onChange={v=>patch(l.id,{on:v})} size={16}/>
                {l.tipo==='custom'&&<button onClick={e=>{e.stopPropagation();removeLayer(l.id);}} className="press w-7 h-7 rounded-lg grid place-items-center text-dim hover:text-river2 flex-none"><Ic n="trash" s={14}/></button>}
              </div>))}
          </div>
          {selLayer&&<div className="mt-2 rounded-xl border border-white/[.08] bg-white/[.03] p-3">
            <div className="font-mono text-[8px] tracking-[1.5px] text-gold2 mb-2">EDITAR · {selLayer.label.toUpperCase()}</div>
            {[['x','X ◄ ►',0,100],['y','Y ▲ ▼',0,100],['w','TAMAÑO',6,120]].map(([k,lb,mn,mx])=>(
              <div key={k} className="flex items-center gap-3 mb-1.5"><span className="font-mono text-[8px] text-mut w-16">{lb}</span><input type="range" min={mn} max={mx} value={selLayer[k]} onChange={e=>patch(selLayer.id,{[k]:+e.target.value})} className="flex-1"/><output className="font-mono text-[9px] text-gold2 w-9 text-right">{Math.round(selLayer[k])}</output></div>))}
            <p className="font-mono text-[7px] text-dim mt-1">Tip: también podés arrastrar la imagen directamente sobre la tarjeta.</p>
          </div>}
        </div>
        <div className="rounded-2xl border border-white/[.08] bg-white/[.03] p-3">
          <div className="flex items-center justify-between mb-2.5">
            <b className="font-mono text-[8.5px] tracking-[1.5px] text-gold2">FICHA TÉCNICA · DORSO</b>
            <button onClick={()=>setFlip(f=>!f)} className="press flex items-center gap-1.5 font-mono text-[8px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-2.5 py-1 hover:bg-cyanx/10">⟳ {flip?'VER FRENTE':'VER DORSO'}</button>
          </div>
          <div className="rounded-xl border border-white/[.08] bg-white/[.03] p-2 mb-2">
            <span className="font-mono text-[7px] tracking-[1px] text-cyanx block mb-1.5">POSICIÓN DEL NOMBRE (DORSO)</span>
            <div className="flex items-center gap-2 mb-1.5"><span className="font-mono text-[7px] text-mut w-10">ALTURA</span><input type="range" min="2" max="80" value={dorsoY} onChange={e=>setDorsoY(+e.target.value)} className="flex-1"/></div>
            <div className="flex items-center gap-2 mb-1.5"><span className="font-mono text-[7px] text-mut w-10">TAMAÑO</span><input type="range" min="12" max="52" value={dorsoSize} onChange={e=>setDorsoSize(+e.target.value)} className="flex-1"/></div>
            <div className="flex gap-1">{[['left','⇤'],['center','↔'],['right','⇥']].map(([a,ic])=><button key={a} onClick={()=>setDorsoAlign(a)} className={`press flex-1 rounded-lg py-1 text-[11px] border ${dorsoAlign===a?'border-cyanx text-cyanx bg-cyanx/10':'border-white/10 text-mut'}`}>{ic}</button>)}</div>
          </div>
          <div className="grid grid-cols-2 gap-2">
            {[['FECHA DEBUT',debut,setDebut],['EDAD',edad,setEdad],['ALTURA',alt,setAlt],['PESO (KG)',peso,setPeso],['NACIMIENTO',nac,setNac],['EN EL CLUB',enr,setEnr]].map(([lb,v,set])=>(
              <label key={lb} className={lb==='EN EL CLUB'?'block col-span-2':'block'}><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">{lb}</span><input value={v} onChange={e=>set(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[10.5px] outline-none focus:border-river/60 min-w-0"/></label>))}
          </div>
          <div className="mt-2.5">
            <span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1.5">PLANTILLA DE FONDO (RAREZA)</span>
            <div className="flex flex-wrap gap-1.5 items-center">
              {rarezas.map((rr,i)=>(
                <span key={i} className="relative">
                  <button onClick={()=>setRz(i)} title={rr.name} className={`press w-8 h-8 rounded-lg border transition-all block ${i===rz?'':'opacity-60 hover:opacity-100'}`} style={{background:`url(${rr.tex}) center/cover`,borderColor:i===rz?rr.color:'rgba(255,255,255,.12)',boxShadow:i===rz?`0 0 12px ${rr.color}66`:'none'}}/>
                  {rr.custom&&<button onClick={()=>removeRareza(i)} className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-crimson text-white text-[9px] grid place-items-center border border-white/30">×</button>}
                </span>))}
              <button onClick={()=>rzFileRef.current.click()} className="press w-8 h-8 rounded-lg border border-dashed border-cyanx/50 text-cyanx grid place-items-center">＋</button>
            </div>
          </div>
          <div className="mt-2.5">
            <span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1.5">COLOR DE LA TIPOGRAFÍA</span>
            <div className="flex items-center gap-2 flex-wrap">
              {['','#ffffff','#111119','#ffcf3e','#ff2e3e','#22d3ee','#8b5cf6'].map(col=>(
                <button key={col||'auto'} onClick={()=>setNameColor(col)} title={col||'Automático'} className={`press w-7 h-7 rounded-lg border-2 grid place-items-center ${nameColor===col?'border-white':'border-white/15'}`} style={{background:col||'linear-gradient(135deg,#333,#777)'}}>{!col&&<span className="text-[7px] font-mono text-white">AUTO</span>}</button>))}
              <label className="press w-7 h-7 rounded-lg border-2 border-white/15 overflow-hidden relative"><input type="color" value={nameColor||'#ffffff'} onChange={e=>setNameColor(e.target.value)} className="absolute inset-0 w-full h-full cursor-pointer opacity-0"/><span className="w-full h-full grid place-items-center text-[10px]" style={{background:nameColor||'#ffffff',color:'#000'}}>🎨</span></label>
            </div>
          </div>
          <div className="mt-2.5 grid grid-cols-2 gap-2">
            <input ref={bgFRef} type="file" accept="image/*" onChange={e=>loadBg(e,setFrontBg,'Fondo frente')} className="hidden"/>
            <input ref={bgBRef} type="file" accept="image/*" onChange={e=>loadBg(e,setBackBg,'Fondo dorso')} className="hidden"/>
            <button onClick={()=>bgFRef.current.click()} className="press rounded-xl py-2 text-[10px] font-bold text-cyanx border border-cyanx/40 hover:bg-cyanx/10 flex items-center justify-center gap-1.5">🖼 Fondo FRENTE{frontBg&&' ✔'}</button>
            <button onClick={()=>bgBRef.current.click()} className="press rounded-xl py-2 text-[10px] font-bold text-cyanx border border-cyanx/40 hover:bg-cyanx/10 flex items-center justify-center gap-1.5">🖼 Fondo DORSO{backBg&&' ✔'}</button>
            {(frontBg||backBg)&&<button onClick={()=>{setFrontBg(null);setBackBg(null);toast('Fondos restablecidos');}} className="press col-span-2 rounded-xl py-1.5 text-[9px] font-bold text-dim hover:text-river2">↺ Volver a la plantilla de rareza</button>}
          </div>
        </div>
        <div>
          <Sec label="POSICIÓN EN LA CANCHA · PUNTO ROJO"/>
          <select value={posIdx} onChange={e=>setPosIdx(+e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-3 py-2.5 text-[11.5px] font-semibold outline-none focus:border-river/60">
            {POS.map((p,i)=><option key={i} value={i} className="bg-[#15151a]">{p[0]} · {p[1]}</option>)}
          </select>
          <p className="font-mono text-[7px] text-dim tracking-[.5px] mt-1.5">Se dibuja un punto rojo en la cancha según la posición · activá la capa “Cancha / posición”.</p>
        </div>
        <div>
          <Sec label="TIPOGRAFÍA DEL NOMBRE"/>
          <select value={nameFont} onChange={e=>setNameFont(e.target.value)} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-3 py-2.5 text-[11.5px] font-semibold outline-none focus:border-river/60 mb-2">
            {['Oswald','Anton','Bebas Neue','Inter','JetBrains Mono'].map(fn=><option key={fn} value={fn} className="bg-[#15151a]" style={{fontFamily:fn}}>{fn}</option>)}
          </select>
          <div className="flex items-center gap-3"><span className="font-mono text-[8px] text-mut w-16">TAMAÑO</span><input type="range" min="16" max="54" value={nameSize} onChange={e=>setNameSize(+e.target.value)} className="flex-1"/><output className="font-mono text-[9px] text-gold2 w-9 text-right">{nameSize}px</output></div>
        </div>
        <div>
          <Sec label="VALOR DE LA FIGURITA · $ (LO CARGÁS VOS)"/>
          <div className="flex items-center gap-2">
            <span className="font-disp text-[18px] text-gold2">$</span>
            <input value={precio} onChange={e=>setPrecio(e.target.value.replace(/[^0-9]/g,''))} placeholder="Ej: 3000 · vacío = precio por rareza" className="flex-1 bg-white/[.06] border border-white/10 rounded-xl px-3 py-2.5 text-[12px] font-mono outline-none focus:border-gold2/60"/>
          </div>
          <p className="font-mono text-[7px] text-dim tracking-[.5px] mt-1.5">Este valor aparece bajo el visor del dorso en la colección del socio · base del futuro juego de batallas.</p>
        </div>
        <div className="grid grid-cols-2 gap-2 mt-auto">
          <button onClick={crear} className="press rounded-2xl py-3 text-[12px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 10px 30px -8px rgba(224,30,47,.6)'}}>＋ Crear y publicar</button>
          <button onClick={exportPNG} className="press glass rounded-2xl py-3 text-[12px] font-bold text-mut hover:text-ink">⬇ Descargar PNG</button>
        </div>
      </div>
      <div className="flex flex-col items-center gap-3 w-full lg:sticky lg:top-4 self-start">
        <span className="font-mono text-[8px] tracking-[2px] text-mut">VISTA PREVIA EN VIVO · FRENTE Y DORSO</span>
        <div className="w-full max-w-[440px] flex flex-col gap-2.5">
        <span className="font-mono text-[7px] tracking-[2px] text-gold2 self-start">FRENTE</span>
        <div className="relative w-full aspect-[3/4.25] rounded-[18px] overflow-hidden" style={{containerType:'inline-size',boxShadow:`0 30px 70px -18px ${c}88, inset 0 1px 0 rgba(255,255,255,.3)`}}>
          <img src={frontTex} alt="" className="absolute inset-0 w-full h-full object-cover z-0"/>
          <div className="absolute inset-0 z-10" onPointerDown={()=>setSel(null)}/>
          {layers.filter(l=>l.on&&!(l.tipo==='foto'&&!photo)).map(l=>(
            <div key={l.id} onPointerDown={e=>startDrag(e,l.id)}
              className={`absolute cursor-grab active:cursor-grabbing ${sel===l.id?'ring-1 ring-river2/80 ring-offset-0':''}`}
              style={{left:l.x+'%',top:l.y+'%',width:l.w+'%',transform:'translate(-50%,-50%)',zIndex:zOf(l.tipo),touchAction:'none'}}>
              {l.tipo==='pitch'
                ? <div className="relative rounded-md overflow-hidden w-full" style={{aspectRatio:'2/3',background:'linear-gradient(160deg,#2f7a2f,#1c4d1c)',border:'1px solid rgba(255,255,255,.4)'}}>
                    <span className="absolute inset-x-0 top-1/2 h-px bg-white/35"/>
                    <span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/35" style={{width:'34%',aspectRatio:'1'}}/>
                    <span className="absolute left-1/2 -translate-x-1/2 top-0 border border-white/30 border-t-0" style={{width:'46%',height:'16%'}}/>
                    <span className="absolute left-1/2 -translate-x-1/2 bottom-0 border border-white/30 border-b-0" style={{width:'46%',height:'16%'}}/>
                    <span className="absolute rounded-full" style={{left:posx+'%',top:posy+'%',width:'18%',aspectRatio:'1',transform:'translate(-50%,-50%)',background:'#ff2e3e',boxShadow:'0 0 8px #ff2e3e, 0 0 3px #fff inset',border:'1.5px solid #fff'}}/>
                  </div>
                : <img src={srcOf(l)} alt={l.label} draggable="false" className={`w-full object-contain pointer-events-none drop-shadow-[0_2px_6px_rgba(0,0,0,.55)] ${l.tipo==='flag'?'rounded-md border border-white/60 shadow-lg':''}`} style={l.tipo==='flag'?{aspectRatio:'3/2',objectFit:'cover'}:{}}/>}
              {l.tipo==='escudo'&&<b className="absolute left-1/2 -translate-x-1/2 font-disp text-[13px] leading-none" style={{top:'100%',color:tcol,textShadow:tsh}}>{sigla}</b>}
              {l.tipo==='flag'&&null}
            </div>))}
          {/* sheen + holo */}
          <span className="absolute inset-0 pointer-events-none z-20" style={{background:`linear-gradient(115deg,transparent 40%,rgba(255,255,255,.5) 49%,transparent 58%)`,animation:'holoShine 3.4s linear infinite'}}/>
          {rz>=4&&<span className="absolute inset-0 pointer-events-none mix-blend-color-dodge opacity-70 z-20" style={{background:'repeating-linear-gradient(70deg,rgba(255,0,120,.2) 0 8px,rgba(0,200,255,.2) 8px 16px,rgba(120,255,80,.16) 16px 24px,rgba(255,220,0,.16) 24px 32px)',animation:'holoShine 7s linear infinite reverse'}}/>}
          {/* escudo + sigla */}
          {/* pitch + posición */}
          {/* flag + año */}
          {/* stats altura/peso */}
          <div className="absolute z-30 flex gap-1" style={{top:'27%',right:'5%',width:'23%'}}>
            {[['↥',alt],['KG',peso]].map(([k,v],i)=>(
              <div key={i} className="flex-1 rounded-md py-1 grid place-items-center" style={{background:dark?'rgba(0,0,0,.18)':'rgba(0,0,0,.4)',border:`1px solid ${dark?'rgba(0,0,0,.3)':'rgba(255,255,255,.25)'}`}}>
                <span className="font-mono text-[7px] leading-none" style={{color:tcol,opacity:.8}}>{k}</span>
                <b className="font-disp text-[13px] leading-none mt-0.5" style={{color:tcol}}>{v}</b>
              </div>))}
          </div>
          {/* nameplate + ribbon + footer */}
          <div className="absolute inset-x-0 bottom-0 z-30 px-[6%] pb-[3%] text-center">
            <b className="font-bold tracking-[.5px] block leading-[.9]" style={{fontFamily:nameFont+',sans-serif',fontSize:`clamp(14px,${nameSize/460*100}cqw,${nameSize}px)`,color:tcol,textShadow:tsh,WebkitTextStroke:dark?'0':'0.5px rgba(0,0,0,.5)'}}>{(name||'—').toUpperCase()}</b>
            <span className="font-disp text-[13px] tracking-[1px] block leading-none mt-0.5" style={{color:tcol,opacity:.9}}>{nac}</span>
            <div className="mt-1.5 py-1 rounded-sm" style={{background:dark?'rgba(0,0,0,.22)':'rgba(0,0,0,.55)',border:`1px solid ${dark?'rgba(0,0,0,.35)':'rgba(255,255,255,.2)'}`}}>
              <span className="font-disp text-[10px] tracking-[.5px] font-bold" style={{color:tcol}}>{enr}</span>
            </div>
            <span className="font-mono text-[6px] tracking-[1px] block mt-1" style={{color:tcol,opacity:.7}}>CARP Official Collection</span>
          </div>
          {!photo&&<div className="absolute inset-x-0 top-[42%] z-20 text-center pointer-events-none"><span className="font-mono text-[9px] tracking-[1px] text-white/45">SIN FOTO · arrastrá una imagen</span></div>}
        </div>
        <span className="font-mono text-[7px] tracking-[2px] text-cyanx self-start mt-1">DORSO</span>
        <div className="relative w-full aspect-[3/4.25] rounded-[18px] overflow-hidden" style={{containerType:'inline-size',boxShadow:`0 30px 70px -18px ${c}88, inset 0 1px 0 rgba(255,255,255,.3)`}}>
          <img src={backTex} alt="" className="absolute inset-0 w-full h-full object-cover z-0"/>
          <span className="absolute inset-0 pointer-events-none z-20" style={{background:`linear-gradient(115deg,transparent 40%,rgba(255,255,255,.4) 49%,transparent 58%)`,animation:'holoShine 3.4s linear infinite'}}/>
          {rz>=4&&<span className="absolute inset-0 pointer-events-none mix-blend-color-dodge opacity-60 z-20" style={{background:'repeating-linear-gradient(70deg,rgba(255,0,120,.2) 0 8px,rgba(0,200,255,.2) 8px 16px,rgba(120,255,80,.16) 16px 24px,rgba(255,220,0,.16) 24px 32px)',animation:'holoShine 7s linear infinite reverse'}}/>}
          <div className="absolute inset-x-0 top-0 h-[46%] z-10 overflow-hidden">
            {photo&&<img src={photo} alt="" className="absolute left-1/2 -translate-x-1/2 bottom-0 h-full object-contain drop-shadow-[0_4px_10px_rgba(0,0,0,.5)]"/>}
            <div className="absolute inset-0" style={{background:'linear-gradient(transparent 52%,rgba(0,0,0,.88))'}}/>
          </div>
          <div className="absolute inset-x-0 z-30 px-3" style={{top:dorsoY+'%',textAlign:dorsoAlign}}>
            <b className="font-disp font-bold leading-none block" style={{fontSize:'clamp(12px,'+(dorsoSize/3.8)+'cqw,'+dorsoSize+'px)',color:tcol,textShadow:tsh}}>{(name||'—').toUpperCase()}</b>
            <span className="font-mono text-[8px] tracking-[2px]" style={{color:tcol,opacity:.85}}>FICHA TÉCNICA · #{num}</span>
          </div>
          <div className="absolute inset-x-0 z-30 px-[7%]" style={{top:'48%'}}>
            <div className="grid grid-cols-2 gap-2">
              {[['DEBUT',debut],['EDAD',edad+' años'],['ALTURA',alt+' m'],['PESO',peso+' kg'],['NACIMIENTO',nac],['POSICIÓN',pos]].map(([k,v])=>(
                <div key={k} className="rounded-lg px-2.5 py-2" style={{background:dark?'rgba(0,0,0,.2)':'rgba(0,0,0,.45)',border:`1px solid ${dark?'rgba(0,0,0,.3)':'rgba(255,255,255,.18)'}`}}>
                  <span className="font-mono text-[6.5px] tracking-[1px] block" style={{color:tcol,opacity:.7}}>{k}</span>
                  <b className="font-disp text-[13px] leading-tight block" style={{color:tcol}}>{v}</b>
                </div>))}
            </div>
            <div className="mt-2 py-1.5 rounded-md text-center" style={{background:dark?'rgba(0,0,0,.22)':'rgba(0,0,0,.55)',border:`1px solid ${dark?'rgba(0,0,0,.35)':'rgba(255,255,255,.2)'}`}}>
              <span className="font-disp text-[10px] tracking-[.5px] font-bold" style={{color:tcol}}>{enr}</span>
            </div>
          </div>
          <span className="absolute inset-x-0 bottom-2 z-30 text-center font-mono text-[6px] tracking-[1px] block" style={{color:tcol,opacity:.7}}>CARP Official Collection · {R.name}</span>
        </div>
        </div>
      </div>
    </div>
  </>;
}

function PlantillaModule(){
  const toast=useContext(ToastCtx);const db=useDB();const f=db.formacion;
  const PRESETS={'4-3-3':{def:4,med:3,del:3},'4-4-2':{def:4,med:4,del:2},'3-5-2':{def:3,med:5,del:2},'4-2-3-1':{def:4,med:5,del:1},'3-4-3':{def:3,med:4,del:3}};
  const setEsquema=e=>OMDB.update(s=>{const p=PRESETS[e];s.formacion.esquema=e;['def','med','del'].forEach(k=>{const cur=s.formacion[k]||[];const arr=[];for(let i=0;i<p[k];i++)arr.push(cur[i]||{n:'Jugador',foto:''});s.formacion[k]=arr;});});
  const setName=(line,i,v)=>OMDB.update(s=>{if(line==='arq'||line==='dt'){if(!s.formacion[line]||typeof s.formacion[line]!=='object')s.formacion[line]={n:'',foto:''};s.formacion[line].n=v;}else{if(!s.formacion[line][i]||typeof s.formacion[line][i]!=='object')s.formacion[line][i]={n:'',foto:''};s.formacion[line][i].n=v;}});
  const upPhoto=(line,i,file)=>{if(!file)return;if(file.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{OMDB.update(s=>{if(line==='arq'||line==='dt'){if(!s.formacion[line]||typeof s.formacion[line]!=='object')s.formacion[line]={n:'',foto:''};s.formacion[line].foto=r.result;}else{if(!s.formacion[line][i]||typeof s.formacion[line][i]!=='object')s.formacion[line][i]={n:String(s.formacion[line][i]||''),foto:''};s.formacion[line][i].foto=r.result;}});toast('Rostro actualizado ✔');};r.readAsDataURL(file);};
  const pick=(line,i)=>{const inp=document.createElement('input');inp.type='file';inp.accept='image/*';inp.style.display='none';document.body.appendChild(inp);inp.onchange=e=>{upPhoto(line,i,e.target.files[0]);inp.remove();};inp.click();};
  const live=f.live!==false;
  const setLive=v=>OMDB.set('formacion.live',v);
  const Line=({line,label,arr,gk})=>(<div>
    <div className="font-mono text-[7.5px] tracking-[1.5px] text-river2 mt-2 mb-1.5">{label}</div>
    <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-1.5">{arr.map((p,i)=>(
      <div key={i} className="flex items-center gap-2 bg-white/[.05] border border-white/[.07] rounded-xl px-2 py-1.5">
        <button onClick={()=>pick(line,i)} className="press w-9 h-9 rounded-full overflow-hidden grid place-items-center flex-none" style={{border:gk?'2px solid #ffcf3e':'1px solid rgba(255,255,255,.18)',background:'radial-gradient(circle at 40% 30%,#2a2a33,#111116)'}}>{p.foto?<img src={p.foto} alt={p.n} className="w-full h-full object-cover object-top"/>:<Ic n="plus" s={13} c="text-dim"/>}</button>
        <input key={line+'-'+i+'-'+(p.foto?'f':'x')} defaultValue={p.n} onChange={e=>setName(line,i,e.target.value)} className="flex-1 bg-transparent text-[11.5px] font-semibold outline-none min-w-0" placeholder="Nombre"/>
        <button onClick={()=>pick(line,i)} className="press font-mono text-[7px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-2 py-1 flex-none">FOTO</button>
      </div>))}</div>
  </div>);
  return <>
    <Sec label="FORMACIÓN · SE REFLEJA EN EL SITIO EN VIVO" right={<b className="font-disp text-[18px] text-gold2">{f.esquema}</b>}/>
    <div className="flex items-center justify-between gap-2 mb-2 rounded-2xl px-3 py-2" style={{background:live?'rgba(34,197,94,.08)':'rgba(255,255,255,.04)',border:`1px solid ${live?'rgba(34,197,94,.3)':'rgba(255,255,255,.08)'}`}}>
      <div><b className="text-[11.5px] block">Edición en vivo</b><small className="font-mono text-[7.5px] text-dim">{live?'Los cambios se reflejan y guardan al instante':'Pausado · el sitio no se actualiza'}</small></div>
      <div className="flex items-center gap-2"><span className="font-mono text-[9px] font-bold" style={{color:live?'#22c55e':'#8a8a97'}}>{live?'ON':'OFF'}</span><Switch on={live} onChange={setLive} size={18}/></div>
    </div>
    <div className={`flex flex-wrap gap-1.5 mb-1 ${live?'':'opacity-40 pointer-events-none'}`}>{Object.keys(PRESETS).map(e=><button key={e} onClick={()=>setEsquema(e)} className={`press rounded-full px-3 py-1.5 text-[10px] font-mono border transition-all ${f.esquema===e?'text-white border-river bg-river/15':'text-mut border-white/10 hover:text-ink'}`}>{e}</button>)}</div>
    <div className={`flex flex-col gap-1 flex-1 ${live?'':'opacity-40 pointer-events-none'}`}>
      {Line({line:'del',label:'DELANTEROS',arr:f.del})}
      {Line({line:'med',label:'MEDIOCAMPO',arr:f.med})}
      {Line({line:'def',label:'DEFENSA',arr:f.def})}
      {Line({line:'arq',label:'ARQUERO',arr:[f.arq],gk:true})}
      {Line({line:'dt',label:'DIRECTOR TÉCNICO',arr:[f.dt||{n:'',foto:''}],gk:true})}
    </div>
    <p className="font-mono text-[7.5px] text-dim tracking-[.5px] mt-2">Tocá el círculo o “FOTO” para subir el rostro · editá el nombre · cambiá el esquema arriba · todo se guarda solo</p>
  </>;
}

function SponsorsModule(){
  const toast=useContext(ToastCtx);const db=useDB();const sp=db.sponsors||{};
  const TC={ORO:'text-gold2',PLATA:'text-[#c0c0cb]',BRONCE:'text-[#b0763a]'};
  const BC={ORO:'rgba(212,164,55,.35)',PLATA:'rgba(192,192,203,.25)',BRONCE:'rgba(176,118,58,.3)'};
  const setLogo=(tier,i,src)=>OMDB.update(s=>{s.sponsors[tier][i].logo=src;});
  const setLink=(tier,i,v)=>OMDB.update(s=>{s.sponsors[tier][i].link=v;});
  const onDrop=(e,tier,i)=>{e.preventDefault();e.stopPropagation();const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f){if(f.size>4.5e6){toast('Imagen muy grande');return;}const r=new FileReader();r.onload=()=>{setLogo(tier,i,r.result);toast('Logo actualizado ✔');};r.readAsDataURL(f);return;}const url=e.dataTransfer.getData('text/uri-list')||e.dataTransfer.getData('text/plain');if(url&&/^https?:/.test(url)){setLogo(tier,i,url);toast('Logo actualizado ✔');}};
  const pick=(tier,i)=>{const inp=document.createElement('input');inp.type='file';inp.accept='image/*';inp.onchange=e=>{const f=e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande');return;}const r=new FileReader();r.onload=()=>{setLogo(tier,i,r.result);toast('Logo actualizado ✔');};r.readAsDataURL(f);};inp.click();};
  return <>
    <Sec label="MULTI-TIER · ARRASTRÁ EL LOGO (PC/WEB) O PEGÁ URL · + LINK"/>
    <div className="flex flex-col gap-3 flex-1">
      {['ORO','PLATA','BRONCE'].map(t=>(
        <div key={t}>
          <small className={`font-mono text-[8px] tracking-[2px] block mb-1.5 ${TC[t]}`}>{t}</small>
          <div className="grid grid-cols-3 gap-2">
            {(sp[t]||[]).map((sponsor,i)=>(
              <div key={i} className="flex flex-col gap-1.5">
                <div draggable={!!sponsor.logo} onDragOver={e=>e.preventDefault()} onDrop={e=>onDrop(e,t,i)} onClick={()=>pick(t,i)}
                  className="press h-12 rounded-xl grid place-items-center bg-white/[.05] cursor-pointer hover:bg-white/[.09] transition-colors px-2 overflow-hidden border-2 border-dashed" style={{borderColor:BC[t]}}>
                  {sponsor.logo?<img src={sponsor.logo} alt={sponsor.nombre} className="max-h-9 max-w-full object-contain pointer-events-none"/>:<span className="font-disp text-[9.5px] tracking-[.5px] text-mut text-center leading-tight">{sponsor.nombre}</span>}
                </div>
                <input value={sponsor.link||''} onChange={e=>setLink(t,i,e.target.value)} placeholder="link…" className="bg-white/[.06] border border-white/10 rounded-lg px-2 py-1 text-[8px] font-mono outline-none focus:border-river/60 min-w-0"/>
              </div>))}
          </div>
        </div>))}
    </div>
    <span className="font-mono text-[7px] text-dim tracking-[.5px] mt-2 block">Clic o arrastrá una imagen a cada logo · el link se abre desde el sitio</span>
  </>;
}

function HistoriasModule(){
  const toast=useContext(ToastCtx);
  const viajes=[["Boca Juniors vs River Plate","La Bombonera · 25/05",["BUS","ENTRADA"]],["River Plate vs Talleres","Más Monumental · 01/06",["BUS","COMIDA"]],["River Plate vs Independiente","Más Monumental · 08/06",["BUS"]]];
  return <>
    <Sec label="AI TELEGRAM PARSER" right={<span className="font-mono text-[8px] text-cyanx tracking-[1px]">⚡ IA</span>}/>
    <div className="flex flex-col gap-2">
      {viajes.map(([t,s,tags])=>(
        <div key={t} className="press bg-white/[.05] border border-white/[.07] rounded-2xl px-3.5 py-2.5 cursor-pointer hover:bg-white/[.09] transition-colors">
          <b className="text-[12px] block">{t}</b><small className="font-mono text-[8px] text-dim">{s}</small>
          <div className="flex gap-1.5 mt-1.5">{tags.map(g=><span key={g} className="font-mono text-[7px] tracking-[.5px] px-2 py-[3px] rounded-md bg-cyanx/10 text-cyanx">{g}</span>)}</div>
        </div>))}
    </div>
    <div className="font-mono text-[8.5px] leading-[1.8] text-mut bg-black/40 border border-white/[.07] rounded-2xl p-3.5 mt-3 whitespace-pre-line max-h-32 overflow-y-auto">{`VAMOS A LA BOMBONERA\nBOCA JRS VS RIVER PLATE\nVIERNES 25/05 · BUS 10:00 HS + ENTRADA\nDESDE EL OBELISCO · RESERVA AL 11 2234-5678\n¡CUPOS LIMITADOS!`}</div>
    <button onClick={()=>toast('🤖 Mensaje analizado: viaje detectado y publicado')} className="press rounded-2xl py-3 mt-3 text-[12px] font-bold text-white w-full" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 10px 30px -8px rgba(224,30,47,.6)'}}>Analizar mensaje</button>
  </>;
}

function SorteosModule(){
  const toast=useContext(ToastCtx);
  const db=useDB();const s=db.sorteo;const sold=useMemo(()=>new Set(s.sold),[s.sold]);
  const toggle=n=>OMDB.update(st=>{const set=new Set(st.sorteo.sold);set.has(n)?set.delete(n):set.add(n);st.sorteo.sold=[...set].sort((a,b)=>a-b);});
  const [over,setOver]=useState(false);
  const onDrop=e=>{e.preventDefault();setOver(false);try{const d=JSON.parse(e.dataTransfer.getData('text/plain'));if(d&&d.img){OMDB.update(st=>{st.sorteo.img=d.img;st.sorteo.img_dorso=d.dorso||d.img;if(d.premio)st.sorteo.premio=d.premio;if(d.titulo)st.sorteo.titulo=d.titulo;});toast('Premio del sorteo actualizado ✔');}}catch(x){}};
  return <>
    <Sec label="CLIC PARA MARCAR VENDIDO · SE ACTUALIZA EN EL SITIO" right={<span className="font-mono text-[8px] text-gold2 tracking-[1px]">1× $3.000 · 2× $5.000</span>}/>
    <div onDragOver={e=>{e.preventDefault();setOver(true);}} onDragLeave={()=>setOver(false)} onDrop={onDrop} className="grid grid-cols-[auto_1fr] gap-5 items-center rounded-2xl transition-all" style={over?{outline:'2px dashed #22d3ee',outlineOffset:4,background:'rgba(34,211,238,.06)'}:{}}>
      <div className="flex flex-col items-center gap-1">
        <Shirt360 front={s.img} back={s.img_dorso} s={224}/>
        <b className="font-disp text-[11px] text-center max-w-[150px] leading-tight">{s.premio}</b>
        <span className="font-mono text-[6.5px] text-dim tracking-[.5px]">arrastrá un premio aquí ↖</span>
      </div>
      <div className="grid grid-cols-10 gap-1.5">
        {Array.from({length:s.total},(_,n)=>{const vend=sold.has(n);
          return <button key={n} onClick={()=>toggle(n)} title={vend?'Vendido':'Disponible'}
            className="press aspect-square rounded-md grid place-items-center font-mono text-[8.5px] font-bold transition-colors"
            style={vend?{background:'rgba(255,255,255,.05)',color:'#8a8a97',border:'1px solid rgba(255,255,255,.08)'}
                       :{background:'rgba(16,54,32,.55)',color:'#4fae74',border:'1px solid rgba(40,110,70,.55)'}}>{String(n).padStart(2,'0')}</button>;})}
      </div>
    </div>
    <div className="flex items-center justify-between font-mono text-[8px] text-mut mt-3">
      <span className="flex items-center gap-1.5"><i className="w-2.5 h-2.5 rounded-sm" style={{background:'rgba(16,54,32,.8)',border:'1px solid rgba(40,110,70,.6)'}}/>DISPONIBLE <b className="text-[#4fae74]">{s.total-sold.size}</b></span>
      <span className="flex items-center gap-1.5"><i className="w-2.5 h-2.5 rounded-sm bg-white/10 border border-white/15"/>VENDIDOS <b className="text-mut">{sold.size}</b></span>
      <span className="flex items-center gap-1.5"><i className="w-2.5 h-2.5 rounded-sm bg-crimson"/>ELIGIENDO (en el sitio)</span>
    </div>
  </>;
}

function ReelsModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const [txt,setTxt]=useState('');const [url,setUrl]=useState('');const [plat,setPlat]=useState('YOUTUBE');
  const add=()=>{
    if(!txt.trim()){toast('Poné un título');return;}
    if(!url.trim()){toast('Pegá el link de YouTube o TikTok');return;}
    OMDB.update(s=>{s.reels=[{titulo:txt.trim(),vistas:'0',plataforma:plat,url:url.trim()},...s.reels];});
    setTxt('');setUrl('');toast('Video publicado en el sitio');
  };
  const del=i=>OMDB.update(s=>{s.reels.splice(i,1);});
  return <>
    <Sec label="VIDEOS · TIKTOK / YOUTUBE CMS"/>
    <div className="flex flex-col gap-2 mb-3">
      <input value={txt} onChange={e=>setTxt(e.target.value)} placeholder="Título del video…" className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11px] outline-none focus:border-river/60 min-w-0"/>
      <div className="flex gap-2">
        <div className="flex gap-1 flex-none">
          {['YOUTUBE','TIKTOK'].map(p=>(
            <button key={p} onClick={()=>setPlat(p)} className={`press rounded-xl px-2.5 text-[9px] font-mono tracking-[.5px] border ${plat===p?'text-white border-river bg-river/15':'text-mut border-white/10'}`}>{p==='YOUTUBE'?'▶ YT':'♪ TT'}</button>))}
        </div>
        <input value={url} onChange={e=>setUrl(e.target.value)} onKeyDown={e=>e.key==='Enter'&&add()} placeholder="Pegá el link…" className="flex-1 bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[10px] font-mono outline-none focus:border-river/60 min-w-0"/>
        <button onClick={add} className="press rounded-xl px-4 text-[16px] font-bold text-white flex-none" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}}>＋</button>
      </div>
    </div>
    <div className="flex flex-col gap-2 flex-1">
      {db.reels.map((r,i)=>(
        <div key={i} className="flex items-center gap-3 bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5">
          <span className="w-9 h-11 rounded-xl grid place-items-center text-river2 flex-none" style={{background:'linear-gradient(160deg,#2a2a33,#101014)'}}><Ic n="video" s={16}/></span>
          <div className="min-w-0 flex-1"><b className="text-[11.5px] block leading-tight truncate">{r.titulo}</b><small className="font-mono text-[7px] text-dim">{r.plataforma} · {r.url?r.url.slice(0,26)+'…':r.vistas+' vistas'}</small></div>
          <button onClick={()=>del(i)} className="press w-7 h-7 rounded-lg grid place-items-center text-dim hover:text-river2 flex-none"><Ic n="trash" s={14}/></button>
        </div>))}
    </div>
  </>;
}

/* ============ PUBLICIDADES · BANNERS DEL SITIO ============ */
function PublicidadesModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const ads=db.config.banners||[];
  const [drag,setDrag]=useState(null);
  const ZC={'CALIENTE':'#ff2e3e','MEDIA':'#ffcf3e','FRÍA':'#22d3ee'};
  const setImg=(i,file)=>{if(!file)return;if(!/^image\//.test(file.type)){toast('Solo imágenes');return;}if(file.size>4.5e6){toast('Máx 4.5MB');return;}const r=new FileReader();r.onload=()=>{OMDB.update(s=>{s.config.banners[i].img=r.result;});toast('Banner #'+(i+1)+' actualizado en el sitio');};r.readAsDataURL(file);};
  const setUrl=(i,v)=>OMDB.update(s=>{s.config.banners[i].img=v;});
  const setLink=(i,v)=>OMDB.update(s=>{s.config.banners[i].link=v;});
  const setPrecio=(i,v)=>OMDB.update(s=>{s.config.banners[i].precio=parseInt(v)||0;});
  const ocupados=ads.filter(a=>a.img).length;
  return <>
    <Sec label="MAPA DE PUBLICIDADES · POR UBICACIÓN (MÁS CALIENTE = MÁS CARO Y VISIBLE)" right={<span className="font-mono text-[8px] text-gold2 tracking-[1px]">{ocupados}/{ads.length} VENDIDOS</span>}/>
    <div className="flex items-center gap-3 mb-3 flex-wrap">
      {Object.entries(ZC).map(([z,c])=><span key={z} className="font-mono text-[8px] tracking-[1px] flex items-center gap-1.5"><i className="w-2.5 h-2.5 rounded-sm inline-block" style={{background:c}}/>{z}</span>)}
      <span className="font-mono text-[8px] text-dim ml-auto">Arrastrá una imagen o pegá URL + link de destino</span>
    </div>
    <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 flex-1">
      {ads.map((a,i)=>{const zc=ZC[a.zona]||'#9a9aa6';
        return <div key={a.id} className="rounded-2xl p-3 flex flex-col gap-2 relative overflow-hidden" style={{border:`1px solid ${zc}44`,background:`radial-gradient(120% 80% at 100% 0%,${zc}12,transparent 60%),rgba(255,255,255,.03)`}}>
          <div className="flex items-center gap-2">
            <span className="font-disp text-[12px] w-6 h-6 rounded-lg grid place-items-center flex-none" style={{background:zc+'22',color:zc}}>{i+1}</span>
            <b className="text-[11px] flex-1 leading-tight">{a.pos}</b>
            <span className="font-mono text-[7px] tracking-[1px] px-1.5 py-0.5 rounded-full flex-none" style={{background:zc+'22',color:zc}}>{a.zona}</span>
          </div>
          <label onDragOver={e=>{e.preventDefault();setDrag(i);}} onDragLeave={()=>setDrag(null)} onDrop={e=>{e.preventDefault();setDrag(null);setImg(i,e.dataTransfer.files&&e.dataTransfer.files[0]);}}
            className={`relative rounded-xl border-2 border-dashed grid place-items-center overflow-hidden cursor-pointer transition-colors ${drag===i?'border-river bg-river/10':'border-white/15 hover:border-white/30'}`} style={{aspectRatio:'300/160',background:'rgba(255,255,255,.03)'}}>
            <input type="file" accept="image/*" className="hidden" onChange={e=>setImg(i,e.target.files&&e.target.files[0])}/>
            {a.img?<img src={a.img} alt="" className="absolute inset-0 w-full h-full object-contain"/>:<span className="font-mono text-[8px] text-dim tracking-[1px] text-center px-2 leading-relaxed">⬇ ARRASTRÁ UNA IMAGEN<br/>O TOCÁ PARA SUBIR</span>}
            {a.img&&<button onClick={e=>{e.preventDefault();OMDB.update(s=>{s.config.banners[i].img='';});toast('Slot liberado');}} className="absolute top-1.5 right-1.5 w-6 h-6 rounded-lg grid place-items-center text-white" style={{background:'rgba(224,30,47,.85)'}}>✕</button>}
          </label>
          <input value={a.img&&/^https?:/.test(a.img)?a.img:''} onChange={e=>setUrl(i,e.target.value)} placeholder="…o pegá la URL de la imagen" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[9.5px] font-mono outline-none focus:border-river/60"/>
          <input value={a.link||''} onChange={e=>setLink(i,e.target.value)} placeholder="Link de destino (https://…)" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[9.5px] font-mono outline-none focus:border-river/60"/>
          <div className="flex items-center gap-1.5"><span className="font-mono text-[9px] text-mut">$/mes</span><input value={a.precio||0} onChange={e=>setPrecio(i,e.target.value)} className="flex-1 bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[10px] font-mono outline-none focus:border-river/60" style={{color:zc}}/></div>
        </div>;})}
    </div>
  </>;
}

/* ============ PREMIOS ARRASTRABLES (islas) ============ */
function PrizeIsland({idx}){
  const db=useDB();
  const p=(db.premios||[])[idx];if(!p)return null;
  const onDrag=e=>{e.dataTransfer.setData('text/plain',JSON.stringify({img:p.img,dorso:p.dorso||p.img,titulo:p.titulo,premio:p.titulo}));e.dataTransfer.effectAllowed='copy';};
  return <>
    <Sec label="ARRASTRÁ AL SORTEO PARA CAMBIAR EL PREMIO"/>
    <div draggable onDragStart={onDrag} className="press cursor-grab active:cursor-grabbing rounded-2xl overflow-hidden grid place-items-center py-5 flex-1" style={{background:`radial-gradient(circle at 50% 40%,${p.accent}22,rgba(0,0,0,.25))`,border:`1px solid ${p.accent}55`}}>
      <img src={p.img} alt={p.titulo} className="h-36 object-contain drop-shadow-[0_12px_24px_rgba(0,0,0,.6)] pointer-events-none"/>
    </div>
    <b className="font-disp text-[14px] block mt-2.5 text-center">{p.titulo}</b>
    <span className="font-mono text-[8px] text-dim text-center block mt-0.5">1× $3.000 · 2× $5.000 · ↕ arrastrá a Sorteos</span>
  </>;
}
/* ============ GESTOR DE PREMIOS (isla única, 3 premios editables) ============ */
function PremiosModule(){
  const db=useDB();const toast=useContext(ToastCtx);const premios=db.premios||[];
  const setImg=(i,src)=>OMDB.update(s=>{s.premios[i].img=src;});
  const onDropImg=(e,i)=>{e.preventDefault();e.stopPropagation();const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f){if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{setImg(i,r.result);toast('Imagen del premio actualizada ✔');};r.readAsDataURL(f);return;}const url=e.dataTransfer.getData('text/uri-list')||e.dataTransfer.getData('text/plain');if(url&&/^https?:/.test(url)){setImg(i,url);toast('Imagen del premio actualizada ✔');}};
  const pick=i=>{const inp=document.createElement('input');inp.type='file';inp.accept='image/*';inp.onchange=e=>{const f=e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande');return;}const r=new FileReader();r.onload=()=>{setImg(i,r.result);toast('Imagen actualizada ✔');};r.readAsDataURL(f);};inp.click();};
  const dragToSorteo=(e,p)=>{e.dataTransfer.setData('text/plain',JSON.stringify({img:p.img,dorso:p.dorso||p.img,titulo:p.titulo,premio:p.titulo}));e.dataTransfer.effectAllowed='copy';};
  return <>
    <Sec label="LOS 3 PREMIOS · ARRASTRÁ IMAGEN (PC/WEB) O PEGÁ URL · ARRASTRÁ AL SORTEO"/>
    <div className="grid grid-cols-1 sm:grid-cols-3 gap-3 flex-1">
      {premios.map((p,i)=>(
        <div key={i} className="flex flex-col gap-2 bg-white/[.04] border border-white/[.08] rounded-2xl p-3">
          <div draggable onDragStart={e=>dragToSorteo(e,p)} onDragOver={e=>e.preventDefault()} onDrop={e=>onDropImg(e,i)} onClick={()=>pick(i)}
            className="press cursor-grab active:cursor-grabbing rounded-xl overflow-hidden grid place-items-center flex-1 min-h-[130px] border-2 border-dashed border-white/12 hover:border-cyanx/60 transition-colors" style={{background:`radial-gradient(circle at 50% 40%,${p.accent}22,rgba(0,0,0,.25))`}}>
            {p.img?<img src={p.img} alt={p.titulo} className="h-28 object-contain drop-shadow-[0_10px_20px_rgba(0,0,0,.6)] pointer-events-none"/>:<span className="font-mono text-[8px] text-dim px-3 text-center">Arrastrá o clic para subir</span>}
          </div>
          <b className="font-disp text-[12px] leading-tight text-center">{p.titulo}</b>
          <input value={p.img&&/^https?:/.test(p.img)?p.img:''} onChange={e=>setImg(i,e.target.value)} placeholder="…o pegá URL de imagen" className="bg-white/[.06] border border-white/10 rounded-lg px-2 py-1.5 text-[9px] font-mono outline-none focus:border-river/60 min-w-0"/>
          <span className="font-mono text-[7px] text-dim text-center">{p.precio} · ↕ arrastrá a Sorteos</span>
        </div>))}
    </div>
  </>;
}

function ViajesModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const CLUBS=[['barracas','Barracas Central'],['boca','Boca'],['racing','Racing'],['independiente','Independiente'],['sanlorenzo','San Lorenzo'],['estudiantes','Estudiantes'],['gimnasia','Gimnasia La Plata'],['huracan','Huracán'],['velez','Vélez'],['tigre','Tigre'],['newells',"Newell's"],['rosario','Rosario Central'],['argentinos','Argentinos'],['banfield','Banfield'],['belgrano','Belgrano'],['sarmiento','Sarmiento'],['aldosivi','Aldosivi'],['tucuman','Atl. Tucumán'],['rivadavia','Riestra/Rivadavia'],['flamengo','Flamengo'],['penarol','Peñarol'],['millonarios','Millonarios']];
  const [rival,setRival]=useState('');const [crest,setCrest]=useState('barracas');const [fecha,setFecha]=useState('');const [precio,setPrecio]=useState('');const [cupos,setCupos]=useState('');const [punto,setPunto]=useState('La Costanera · Corrientes');const [flyer,setFlyer]=useState(null);const [over,setOver]=useState(false);
  const readFlyer=f=>{if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande (máx 4.5MB)');return;}const r=new FileReader();r.onload=()=>{setFlyer(r.result);
    // 1) heurística por nombre de archivo
    const nm=(f.name||'').replace(/[_\-.]/g,' ');const hit=CLUBS.find(([,n])=>nm.toLowerCase().includes(n.toLowerCase().split(' ')[0]));if(hit){setCrest(hit[0]);setRival(hit[1]);}
    toast('Flyer cargado · leyendo con OCR…');
    // 2) OCR con Tesseract.js (lee el texto del flyer)
    const run=()=>{try{window.Tesseract.recognize(r.result,'spa').then(({data})=>{
      const txt=(data.text||'').toUpperCase();
      const cl=CLUBS.find(([,n])=>txt.includes(n.toUpperCase().split(' ')[0]));if(cl){setCrest(cl[0]);setRival(cl[1]);}
      const fm=txt.match(/(\d{1,2})[\/\-](\d{1,2})(?:[\/\-](\d{2,4}))?/);if(fm){const y=fm[3]?(fm[3].length===2?'20'+fm[3]:fm[3]):'2026';setFecha(`${y}-${fm[2].padStart(2,'0')}-${fm[1].padStart(2,'0')}T20:00`);}
      const pm=txt.match(/\$?\s?(\d{2,3}[.\s]?\d{3})/);if(pm){const n=pm[1].replace(/[.\s]/g,'');if(+n>=1000)setPrecio(n);}
      toast('✔ OCR completo · revisá los campos');
    }).catch(()=>toast('No pude leer el flyer · completá a mano'));}catch(e){toast('Cargá los datos a mano');}};
    if(window.Tesseract)run();
    else{const sc=document.createElement('script');sc.src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';sc.onload=run;sc.onerror=()=>toast('Completá los datos a mano');document.head.appendChild(sc);}
  };r.readAsDataURL(f);};
  const onDrop=e=>{e.preventDefault();setOver(false);const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f)readFlyer(f);};
  const publicar=()=>{if(!rival.trim()){toast('Poné el rival');return;}
    OMDB.update(s=>{s.viajes=[{destino:'Buenos Aires',rival:rival.trim(),crest,comp:'Liga Profesional',fecha:fecha||'A confirmar',salida:'Salimos un día antes',punto:punto.trim(),precio:precio?('$'+(+precio).toLocaleString('es-AR')):'Consultar',cupos:+cupos||40,estado:'ABIERTO',flyer},...s.viajes].slice(0,12);});
    toast('✔ Viaje publicado en el sitio');setRival('');setFecha('');setPrecio('');setCupos('');setFlyer(null);};
  const del=i=>OMDB.update(s=>{s.viajes.splice(i,1);});
  const Inp=({v,set,ph,type='text'})=><input value={v} onChange={e=>set(e.target.value)} placeholder={ph} type={type} className="w-full bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11.5px] outline-none focus:border-river/60"/>;
  return <>
    <Sec label="VIAJES · CARGÁ COMO SE PUBLICA" right={<span className="font-mono text-[8px] text-cyanx tracking-[1px]">ARRASTRÁ EL FLYER ↘</span>}/>
    <div className="grid md:grid-cols-[150px_1fr] gap-3">
      <div onDragOver={e=>{e.preventDefault();setOver(true);}} onDragLeave={()=>setOver(false)} onDrop={onDrop} onClick={()=>{const i=document.createElement('input');i.type='file';i.accept='image/*';i.onchange=e=>readFlyer(e.target.files[0]);i.click();}}
        className="press rounded-2xl border-2 border-dashed grid place-items-center text-center cursor-pointer overflow-hidden relative" style={{borderColor:over?'#ff2e3e':'rgba(255,255,255,.18)',minHeight:150,background:over?'rgba(224,30,47,.08)':'rgba(255,255,255,.03)'}}>
        {flyer?<img src={flyer} alt="flyer" className="absolute inset-0 w-full h-full object-cover"/>:<div className="p-3"><div className="text-[22px] mb-1">📲</div><small className="font-mono text-[8px] text-mut tracking-[1px]">FLYER WHATSAPP<br/>arrastrá o clic</small></div>}
      </div>
      <div className="grid grid-cols-2 gap-2">
        <div className="col-span-2"><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">RIVAL</span><Inp v={rival} set={setRival} ph="Ej: Boca Juniors"/></div>
        <div className="col-span-2"><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">ESCUDO DEL RIVAL (manual si la API no lo reconoce)</span>
          <div className="flex flex-wrap gap-1.5">{CLUBS.map(([k,n])=>(
            <button key={k} onClick={()=>setCrest(k)} title={n} className={`press w-9 h-9 rounded-lg grid place-items-center border ${crest===k?'border-river bg-river/15':'border-white/10'}`}><img src={'assets/clubs/'+k+'.png'} alt={n} className="w-6 h-6 object-contain" onError={e=>{e.target.style.display='none';}}/></button>))}</div></div>
        <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">FECHA</span><Inp v={fecha} set={setFecha} ph="SÁB 1 NOV · 20:00"/></div>
        <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">PRECIO (ARS)</span><Inp v={precio} set={setPrecio} ph="45000" type="number"/></div>
        <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">CUPOS</span><Inp v={cupos} set={setCupos} ph="40" type="number"/></div>
        <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">PUNTO DE SALIDA</span><Inp v={punto} set={setPunto} ph="La Costanera"/></div>
        <button onClick={publicar} className="col-span-2 press rounded-xl py-2.5 text-[12px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}}>Publicar viaje</button>
      </div>
    </div>
    <div className="flex flex-col gap-2 mt-3">
      {(db.viajes||[]).map((v,i)=>(
        <div key={i} className="flex items-center gap-3 bg-white/[.05] border border-white/[.07] rounded-2xl px-3 py-2.5">
          <img src={'assets/clubs/'+v.crest+'.png'} alt="" className="w-8 h-8 object-contain flex-none" onError={e=>{e.target.style.visibility='hidden';}}/>
          <div className="min-w-0 flex-1"><b className="text-[12px] block truncate">River vs {v.rival}</b><small className="font-mono text-[8px] text-dim">{v.fecha} · {v.precio||'—'}</small></div>
          <button onClick={()=>del(i)} className="press w-7 h-7 rounded-lg grid place-items-center text-dim hover:text-river2 flex-none"><Ic n="trash" s={13}/></button>
        </div>))}
    </div>
  </>;
}

function AsientosModule(){
  const db=useDB();const a=db.asientos||{zonas:[],vendidos:[]};
  const vendidos=useMemo(()=>new Set(a.vendidos||[]),[a.vendidos]);
  const zonaDe=n=>a.zonas.find(z=>n>=z.desde&&n<=z.hasta);
  const toggle=n=>OMDB.update(s=>{const set=new Set(s.asientos.vendidos);set.has(n)?set.delete(n):set.add(n);s.asientos.vendidos=[...set].sort((x,y)=>x-y);});
  const total=a.zonas.length?a.zonas[a.zonas.length-1].hasta:0;
  return <>
    <Sec label="ASIENTOS DEL MICRO · CLIC = OCUPADO (SE TACHA)" right={<span className="font-mono text-[8px] text-gold2 tracking-[1px]">{vendidos.size}/{total} OCUPADOS</span>}/>
    <div className="flex flex-wrap gap-2 mb-3">
      {a.zonas.map(z=>(<span key={z.plan} className="flex items-center gap-1.5 font-mono text-[8px] tracking-[.5px]" style={{color:z.color}}><i className="w-2.5 h-2.5 rounded-sm" style={{background:z.color}}/>{z.plan} ({z.desde}-{z.hasta})</span>))}
    </div>
    <div className="grid grid-cols-4 sm:grid-cols-6 gap-1.5">
      {Array.from({length:total},(_,i)=>{const n=i+1;const z=zonaDe(n);const oc=vendidos.has(n);
        return <button key={n} onClick={()=>toggle(n)} className="press rounded-lg h-9 grid place-items-center font-disp text-[12px] relative transition-all" style={oc?{background:'rgba(255,255,255,.05)',color:'#5a5a66',border:'1px solid rgba(255,255,255,.06)'}:{background:z.color,color:'#0b0b10',border:'1px solid rgba(0,0,0,.25)'}}>
          <span style={oc?{textDecoration:'line-through',opacity:.6}:{}}>{String(n).padStart(2,'0')}</span>
        </button>;})}
    </div>
    <p className="font-mono text-[8px] text-dim mt-3">Se refleja en vivo en el sitio (Viajes · elegí tu asiento). Los ocupados aparecen tachados por clase.</p>
    <PasajerosPanel a={a} zonaDe={zonaDe}/>
  </>;
}

function PasajerosPanel({a,zonaDe}){
  const ocup=a.ocupantes||{};
  const nums=Object.keys(ocup).map(Number).sort((x,y)=>x-y);
  const [qr,setQr]=useState(null);
  return <div className="mt-5 pt-4 border-t border-white/10">
    <Sec label={`PASAJEROS CONFIRMADOS · ${nums.length}`} right={<span className="font-mono text-[8px] text-cyanx tracking-[1px]">CLIC = VER QR</span>}/>
    {nums.length===0?<p className="text-mut text-[12px]">Todavía no hay pasajeros. Cuando un socio pague su asiento aparece acá con su avatar, identificador y QR.</p>
    :<div className="grid sm:grid-cols-2 xl:grid-cols-3 gap-2.5">
      {nums.map(n=>{const o=ocup[n];const z=zonaDe(n)||{color:'#9a9aa6',plan:'—'};
        return <div key={n} className="flex items-center gap-3 rounded-2xl px-3 py-2.5" style={{background:'rgba(255,255,255,.04)',border:`1px solid ${z.color}44`}}>
          <span className="w-11 h-11 rounded-full grid place-items-center overflow-hidden font-disp font-bold text-[14px] flex-none" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)',color:z.color,border:`2px solid ${z.color}`}}>{o.avatar?<img src={o.avatar} alt="" className="w-full h-full object-cover"/>:o.iniciales}</span>
          <div className="min-w-0 flex-1"><b className="text-[12.5px] block truncate">{o.nombre}</b><small className="font-mono text-[7.5px] tracking-[1px]" style={{color:z.color}}>#{o.codigo||'—'} · ASIENTO {String(n).padStart(2,'0')} · {z.plan}</small><small className="font-mono text-[7px] text-dim block">{o.monto==='sena'?'Seña':'Pago total'} · {o.metodo==='stripe'?'Stripe':'MercadoPago'}</small></div>
          <button onClick={()=>setQr(n)} className="press w-9 h-9 rounded-lg grid place-items-center flex-none" style={{background:z.color+'22',color:z.color}}><Ic n="cards" s={15}/></button>
        </div>;})}
    </div>}
    {qr!==null&&<div className="fixed inset-0 z-[95] flex items-center justify-center p-4" onClick={()=>setQr(null)}>
      <div className="absolute inset-0 bg-black/85 backdrop-blur-xl"/>
      <div className="relative glass-deep rounded-glass p-6 text-center" style={{animation:'sheetUp .3s both'}} onClick={e=>e.stopPropagation()}>
        <div className="font-mono text-[9px] tracking-[2px] text-cyanx mb-2">ASIENTO {String(qr).padStart(2,'0')} · {ocup[qr].nombre}</div>
        <img src={`https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=0&data=${encodeURIComponent('OM-VIAJE|'+(ocup[qr].codigo||'')+'|'+qr+'|'+ocup[qr].nombre)}`} alt="QR" className="w-[220px] h-[220px] rounded-xl bg-white p-2"/>
        <p className="text-mut text-[11px] mt-2 max-w-[220px]">Escaneá para validar identidad, asiento y pago del pasajero.</p>
      </div>
    </div>}
  </div>;
}

function PagosModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const p=(db.config.pagos)||{};
  const env=p.entorno||'test';
  const setEnv=v=>OMDB.update(s=>{s.config.pagos={...s.config.pagos,entorno:v};});
  // credenciales por entorno: config.pagos.test.* / config.pagos.prod.*
  const cur=p[env]||{};
  const set=(k,v)=>OMDB.update(s=>{const pg={...s.config.pagos};pg[env]={...(pg[env]||{}),[k]:v};s.config.pagos=pg;});
  const enc=v=>v?('enc:'+btoa(unescape(encodeURIComponent(v)))):'';
  const isSaved=v=>typeof v==='string'&&v.indexOf('enc:')===0;
  // input público (visible)
  const Pub=({k,label,ph})=>(<div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">{label}</span>
    <input value={cur[k]||''} onChange={e=>set(k,e.target.value)} placeholder={ph} className="w-full bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 font-mono text-[9.5px] outline-none focus:border-river/60"/></div>);
  // input secreto (enmascarado)
  const Sec2=({k,label})=>{
    const saved=isSaved(cur[k]);const [edit,setEdit]=useState(false);const [v,setV]=useState('');
    if(saved&&!edit)return <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">{label} 🔒</span>
      <div className="flex items-center gap-2"><div className="flex-1 bg-white/[.03] border border-green-500/30 rounded-lg px-2.5 py-1.5 font-mono text-[9.5px] text-green-400">•••••••••••••• ✓ cifrado</div>
      <button onClick={()=>{setEdit(true);setV('');}} className="press font-mono text-[8px] text-cyanx border border-cyanx/40 rounded-lg px-2 py-1.5">Cambiar</button></div></div>;
    return <div><span className="font-mono text-[7px] tracking-[1px] text-mut block mb-1">{label} 🔒</span>
      <div className="flex items-center gap-2"><input type="password" value={v} onChange={e=>setV(e.target.value)} placeholder="Pegá el token secreto…" className="flex-1 bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 font-mono text-[9.5px] outline-none focus:border-river/60"/>
      <button onClick={()=>{set(k,enc(v));setEdit(false);toast('Secreto cifrado y guardado');}} className="press font-mono text-[8px] text-white border border-river bg-river/20 rounded-lg px-2 py-1.5">Cifrar</button></div></div>;
  };
  return <>
    <Sec label="CUENTAS DE PAGO · CREDENCIALES CIFRADAS" right={
      <div className="flex gap-1">{['test','prod'].map(e=><button key={e} onClick={()=>setEnv(e)} className={`press font-mono text-[8px] tracking-[1px] px-2.5 py-1 rounded-lg border ${env===e?(e==='prod'?'text-green-400 border-green-500/50 bg-green-500/10':'text-gold2 border-gold2/50 bg-gold2/10'):'text-mut border-white/10'}`}>{e==='prod'?'🚀 PRODUCCIÓN':'🧪 TEST'}</button>)}</div>}/>
    <div className="flex flex-col gap-3">
      <div className="rounded-2xl border border-white/[.08] bg-white/[.04] p-3 flex flex-col gap-2">
        <div className="flex items-center justify-between"><b className="font-disp text-[13px]" style={{color:'#00b1ea'}}>Mercado Pago</b>
          <button onClick={()=>set('mp_activo',!cur.mp_activo)} className={`press w-11 h-6 rounded-full transition-colors ${cur.mp_activo?'bg-green-500':'bg-white/15'}`}><span className={`block w-5 h-5 rounded-full bg-white transition-transform ${cur.mp_activo?'translate-x-5':'translate-x-0.5'}`}/></button></div>
        <Pub k="mp_public_key" label="PUBLIC KEY" ph={env==='prod'?'APP_USR-...':'TEST-...'}/>
        <Sec2 k="mp_access_token" label="ACCESS TOKEN"/>
        <div className="grid grid-cols-3 gap-2"><Pub k="mp_app_id" label="APP ID"/><Pub k="mp_user_id" label="USER ID"/><Pub k="mp_test_user" label="TEST USER"/></div>
      </div>
      <div className="rounded-2xl border border-white/[.08] bg-white/[.04] p-3 flex flex-col gap-2">
        <div className="flex items-center justify-between"><b className="font-disp text-[13px]" style={{color:'#635bff'}}>Stripe</b>
          <button onClick={()=>set('stripe_activo',!cur.stripe_activo)} className={`press w-11 h-6 rounded-full transition-colors ${cur.stripe_activo?'bg-green-500':'bg-white/15'}`}><span className={`block w-5 h-5 rounded-full bg-white transition-transform ${cur.stripe_activo?'translate-x-5':'translate-x-0.5'}`}/></button></div>
        <Pub k="stripe_pk" label="PUBLISHABLE KEY" ph={env==='prod'?'pk_live_...':'pk_test_...'}/>
        <Sec2 k="stripe_sk" label="SECRET KEY"/>
      </div>
      <p className="font-mono text-[8px] text-dim leading-relaxed">Se guardan por entorno (test / producción). Los tokens secretos quedan <b className="text-mut">cifrados</b> (nunca en claro ni visibles) y solo se usan server-side en el checkout real (Edge Function). El entorno activo <b className="text-mut">{env.toUpperCase()}</b> es el que usa todo el sitio para cobrar.</p>
    </div>
  </>;
}

function MensajesModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const msgs=db.mensajes||[];
  const noLeidos=msgs.filter(m=>!m.leido).length;
  const marcar=id=>OMDB.update(s=>{const m=(s.mensajes||[]).find(x=>x.id===id);if(m)m.leido=true;});
  const borrar=id=>OMDB.update(s=>{s.mensajes=(s.mensajes||[]).filter(x=>x.id!==id);});
  return <>
    <Sec label="BANDEJA DE MENSAJES" right={noLeidos>0?<span className="font-mono text-[9px] px-2 py-0.5 rounded-full text-white" style={{background:'#e01e2f'}}>{noLeidos} nuevos</span>:<span className="font-mono text-[9px] text-mut">{msgs.length} total</span>}/>
    <div className="flex flex-col gap-2 flex-1 overflow-y-auto max-h-[420px] pr-1">
      {msgs.length===0&&<div className="text-mut text-[12px] py-8 text-center">Sin mensajes todavía. Los que lleguen del formulario de contacto aparecen acá.</div>}
      {msgs.map(m=>(
        <div key={m.id} className="rounded-2xl p-3.5" style={{background:m.leido?'rgba(255,255,255,.04)':'rgba(224,30,47,.08)',border:`1px solid ${m.leido?'rgba(255,255,255,.07)':'rgba(224,30,47,.3)'}`}}>
          <div className="flex items-center gap-2 mb-1.5">
            {!m.leido&&<i className="w-[7px] h-[7px] rounded-full bg-river2 flex-none"/>}
            <b className="text-[12.5px] flex-1 truncate">{m.nombre}</b>
            <span className="font-mono text-[7px] tracking-[1px] px-2 py-0.5 rounded-full text-river2" style={{background:'rgba(224,30,47,.12)'}}>{m.asunto}</span>
          </div>
          <p className="text-mut text-[12px] leading-relaxed mb-2">{m.mensaje}</p>
          <div className="flex items-center gap-3 font-mono text-[8px] text-dim">
            {m.email&&<a href={'mailto:'+m.email} className="text-river2">{m.email}</a>}
            <span>{new Date(m.fecha).toLocaleString('es-AR',{day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'})}</span>
            <div className="ml-auto flex gap-2">
              {!m.leido&&<button onClick={()=>marcar(m.id)} className="press text-green-400">Marcar leído</button>}
              <button onClick={()=>borrar(m.id)} className="press text-dim hover:text-river2">Borrar</button>
            </div>
          </div>
        </div>))}
    </div>
  </>;
}

function AdminsModule(){
  const toast=useContext(ToastCtx);
  const isRoot=!!(window.OMAdmin&&typeof OMAdmin.isRoot==='function'&&OMAdmin.isRoot());
  const [,force]=useState(0);const refresh=()=>force(x=>x+1);
  const [nombre,setNombre]=useState('');const [email,setEmail]=useState('');const [priv,setPriv]=useState([]);
  const [nuevo,setNuevo]=useState(null);
  const PRIVS=[['site','Sitio & Hero'],['figus','Figuritas'],['sorteos','Sorteos'],['viajes','Viajes'],['msgs','Mensajes'],['pagos','Pagos & API'],['plantel','Plantel']];
  const list=isRoot?OMAdmin.listAdmins():[];
  const togglePriv=p=>setPriv(v=>v.includes(p)?v.filter(x=>x!==p):[...v,p]);
  const crear=()=>{const r=OMAdmin.crearAdmin({nombre,email,priv});if(!r.ok){toast(r.error);return;}setNuevo({email,code:r.invcode});setNombre('');setEmail('');setPriv([]);refresh();toast('Admin creado · pasale el código de invitación');};
  if(!isRoot) return <><Sec label="ADMINISTRADORES"/><div className="glass rounded-2xl p-6 text-center text-mut text-[13px]">Solo el <b className="text-ink">administrador root</b> puede gestionar admins y privilegios.</div></>;
  return <>
    <Sec label="ADMINISTRADORES · SOLO ROOT" right={<span className="font-mono text-[8px] text-gold2 tracking-[1px]">{list.length} CUENTAS</span>}/>
    <div className="grid md:grid-cols-2 gap-4">
      <div className="glass rounded-2xl p-4 flex flex-col gap-2.5">
        <b className="font-disp text-[13px] tracking-[.5px]">CREAR NUEVO ADMIN</b>
        <input value={nombre} onChange={e=>setNombre(e.target.value)} placeholder="Nombre completo" className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2.5 text-[12px] outline-none focus:border-river/60"/>
        <input value={email} onChange={e=>setEmail(e.target.value)} type="email" placeholder="Correo del admin" className="bg-white/[.06] border border-white/10 rounded-xl px-3 py-2.5 text-[12px] outline-none focus:border-river/60"/>
        <span className="font-mono text-[7px] tracking-[1.5px] text-mut mt-1">PRIVILEGIOS</span>
        <div className="flex flex-wrap gap-1.5">
          {PRIVS.map(([id,l])=><button key={id} onClick={()=>togglePriv(id)} className={`press rounded-lg px-2.5 py-1.5 text-[9.5px] font-semibold border ${priv.includes(id)?'text-white border-river bg-river/15':'text-mut border-white/10'}`}>{l}</button>)}
        </div>
        <button onClick={crear} className="press rounded-xl py-2.5 text-[12px] font-bold text-white mt-1" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}}>Crear e invitar</button>
        {nuevo&&<div className="rounded-xl p-3 mt-1" style={{background:'rgba(255,207,62,.1)',border:'1px solid rgba(255,207,62,.35)'}}>
          <div className="font-mono text-[8px] tracking-[1px] text-gold2 mb-1">CÓDIGO DE INVITACIÓN PARA {nuevo.email}</div>
          <b className="font-disp text-[26px] tracking-[4px] text-gold2">{nuevo.code}</b>
          <p className="text-mut text-[10px] mt-1 leading-relaxed">Pasale este código al admin. Debe entrar al panel → "Activá tu cuenta", poner su correo + este código y crear su contraseña.</p>
        </div>}
      </div>
      <div className="glass rounded-2xl p-4 flex flex-col gap-2">
        <b className="font-disp text-[13px] tracking-[.5px] mb-1">CUENTAS DE ADMIN</b>
        <div className="rounded-xl p-2.5 flex items-center gap-2.5" style={{background:'rgba(139,92,246,.1)',border:'1px solid rgba(139,92,246,.3)'}}>
          <span className="text-[16px]">👑</span><div className="flex-1"><b className="text-[12px] block">Administrador ROOT</b><small className="font-mono text-[8px] text-holo">acceso total · no se puede revocar</small></div>
        </div>
        {list.length===0&&<p className="text-mut text-[11px] py-3 text-center">Sin admins creados todavía.</p>}
        {list.map(a=><div key={a.id} className="rounded-xl p-2.5 flex items-center gap-2.5" style={{background:'rgba(255,255,255,.04)',border:'1px solid rgba(255,255,255,.08)'}}>
          <span className="w-8 h-8 rounded-full grid place-items-center font-disp font-bold text-[11px] text-gold2 flex-none" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{(a.nombre||'?').slice(0,2).toUpperCase()}</span>
          <div className="flex-1 min-w-0"><b className="text-[11.5px] block truncate">{a.nombre}</b><small className="font-mono text-[8px] text-dim">{a.email}</small></div>
          {a.estado==='pendiente'?<span className="font-mono text-[7px] tracking-[1px] px-2 py-1 rounded-full text-gold2" style={{background:'rgba(255,207,62,.12)'}}>PEND · {a.invcode}</span>:<span className="font-mono text-[7px] tracking-[1px] px-2 py-1 rounded-full text-green-400" style={{background:'rgba(34,197,94,.12)'}}>ACTIVO</span>}
          <button onClick={()=>{OMAdmin.revocar(a.id);refresh();toast('Admin revocado');}} className="press w-6 h-6 rounded-lg grid place-items-center text-dim hover:text-river2 flex-none"><Ic n="trash" s={13}/></button>
        </div>)}
      </div>
    </div>
  </>;
}

/* ============ MODULE REGISTRY (hiper modular) ============ */
function ProdeTriviaModule(){
  const db=useDB();
  const pr=(db.prode&&db.prode.participaciones)||[];
  const tr=(db.trivia&&db.trivia.participaciones)||[];
  const Av=({a,n,s=28})=><span className="rounded-full grid place-items-center overflow-hidden font-disp font-bold text-gold2 flex-none border border-river/50" style={{width:s,height:s,fontSize:s*0.38,background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{a?<img src={a} alt="" className="w-full h-full object-cover"/>:(n||'?').slice(0,2).toUpperCase()}</span>;
  const rank=[...tr].sort((a,b)=>b.aciertos-a.aciertos);
  return <>
    <Sec label="PRODE & TRIVIA · PARTICIPANTES EN VIVO" right={<span className="font-mono text-[8px] text-gold2 tracking-[1px]">{tr.length+pr.length} JUGADAS</span>}/>
    <div className="grid md:grid-cols-2 gap-3 flex-1">
      <div className="rounded-2xl border border-white/[.08] bg-white/[.03] p-3 flex flex-col">
        <b className="font-mono text-[8.5px] tracking-[1.5px] text-gold2 mb-2">🧠 TRIVIA · RANKING DE ACIERTOS</b>
        <div className="flex flex-col gap-1.5 overflow-y-auto max-h-[300px] pr-1">
          {rank.length===0&&<p className="text-mut text-[11px] py-4 text-center">Nadie jugó la trivia todavía.</p>}
          {rank.map((p,i)=><div key={p.id+p.semana} className="flex items-center gap-2.5 rounded-xl px-2.5 py-2 bg-white/[.04]">
            <span className="font-disp text-[13px] w-5 text-center" style={{color:i===0?'#ffcf3e':i===1?'#c0c0cb':i===2?'#b0763a':'#8a8a97'}}>{i+1}</span>
            <Av a={p.avatar} n={p.nombre}/>
            <div className="min-w-0 flex-1"><b className="text-[11.5px] block truncate leading-tight">{p.nombre}</b><small className="font-mono text-[7px] text-dim">{p.semana} · {new Date(p.fecha).toLocaleDateString('es-AR',{day:'2-digit',month:'short'})}</small></div>
            <b className="font-disp text-[15px] text-gold2">{p.aciertos}<span className="text-[9px] text-mut">/{p.total}</span></b>
          </div>)}
        </div>
      </div>
      <div className="rounded-2xl border border-white/[.08] bg-white/[.03] p-3 flex flex-col">
        <b className="font-mono text-[8.5px] tracking-[1.5px] text-cyanx mb-2">🎯 PRODE · PRONÓSTICOS ENVIADOS</b>
        <div className="flex flex-col gap-1.5 overflow-y-auto max-h-[300px] pr-1">
          {pr.length===0&&<p className="text-mut text-[11px] py-4 text-center">Sin pronósticos esta semana.</p>}
          {pr.map(p=><div key={p.id} className="flex items-center gap-2.5 rounded-xl px-2.5 py-2 bg-white/[.04]">
            <Av a={p.avatar} n={p.nombre}/>
            <div className="min-w-0 flex-1"><b className="text-[11.5px] block truncate leading-tight">{p.nombre}</b><small className="font-mono text-[7px] text-dim">{p.semana} · {p.partidos} partido(s)</small></div>
            <span className="font-mono text-[8px] text-cyanx px-2 py-1 rounded-full" style={{background:'rgba(34,211,238,.12)'}}>{p.aciertos||0} pts</span>
          </div>)}
        </div>
      </div>
    </div>
  </>;
}

function EnviarModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const recibidos=db.mensajes||[];
  const enviados=db.mensajes_enviados||[];
  const admin=window.OMAdmin&&OMAdmin.current&&OMAdmin.current();
  const contactos=[...new Map(recibidos.filter(m=>m.email).map(m=>[m.email,{nombre:m.nombre,email:m.email,id:m.email}])).values()];
  const [dest,setDest]=useState('');const [txt,setTxt]=useState('');
  const hilo=[...recibidos.filter(m=>m.email===dest).map(m=>({...m,dir:'in'})),...enviados.filter(m=>m.email===dest).map(m=>({...m,dir:'out'}))].sort((a,b)=>a.fecha-b.fecha);
  const Av=({n,s=30})=><span className="rounded-full grid place-items-center font-disp font-bold text-gold2 flex-none border border-river/50" style={{width:s,height:s,fontSize:s*0.36,background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{(n||'?').slice(0,2).toUpperCase()}</span>;
  const enviar=()=>{
    if(!dest){toast('Elegí un destinatario');return;}
    if(!txt.trim())return;
    OMDB.update(s=>{s.mensajes_enviados=[...(s.mensajes_enviados||[]),{id:'e'+Date.now(),email:dest,mensaje:txt.trim(),fecha:Date.now(),por:(admin&&admin.nombre)||'Admin'}];});
    const c=contactos.find(x=>x.email===dest);const body=encodeURIComponent(txt.trim());
    window.open(`mailto:${dest}?subject=${encodeURIComponent('Orgullo Millonario · respuesta')}&body=${body}`,'_blank');
    setTxt('');toast('Respuesta enviada a '+((c&&c.nombre)||dest));
  };
  return <>
    <Sec label="ENVIAR · CHAT INTERNO CON SOCIOS" right={<span className="font-mono text-[8px] text-mut tracking-[1px]">{contactos.length} contactos</span>}/>
    <div className="grid grid-cols-[130px_1fr] gap-2 flex-1 min-h-[320px]">
      <div className="flex flex-col gap-1.5 overflow-y-auto max-h-[360px] pr-1 border-r border-white/[.07]">
        {contactos.length===0&&<p className="text-mut text-[10px] py-3">Sin contactos aún.</p>}
        {contactos.map(c=><button key={c.id} onClick={()=>setDest(c.email)} className={`press flex items-center gap-2 rounded-xl px-2 py-2 text-left ${dest===c.email?'bg-river/15 border border-river/40':'bg-white/[.04] border border-transparent'}`}>
          <Av n={c.nombre} s={26}/><div className="min-w-0"><b className="text-[10.5px] block truncate leading-tight">{c.nombre}</b><small className="font-mono text-[6.5px] text-dim block truncate">#{(c.email||'').slice(0,10)}</small></div>
        </button>)}
      </div>
      <div className="flex flex-col min-w-0">
        {dest?<>
          <div className="flex items-center gap-2 pb-2 mb-2 border-b border-white/[.07]"><Av n={(contactos.find(c=>c.email===dest)||{}).nombre}/><div className="min-w-0"><b className="text-[12px] block truncate">{(contactos.find(c=>c.email===dest)||{}).nombre}</b><small className="font-mono text-[7px] text-green-400">● socio identificado · {dest}</small></div></div>
          <div className="flex flex-col gap-2 flex-1 overflow-y-auto max-h-[240px] pr-1 mb-2">
            {hilo.length===0&&<p className="text-mut text-[11px] py-4 text-center">Sin mensajes con este socio.</p>}
            {hilo.map((m,i)=><div key={i} className={`max-w-[80%] rounded-2xl px-3 py-2 ${m.dir==='out'?'self-end rounded-br-sm':'self-start rounded-bl-sm'}`} style={{background:m.dir==='out'?'linear-gradient(160deg,#ff2e3e,#8d0917)':'rgba(255,255,255,.06)'}}>
              <span className="text-[11.5px] leading-snug block">{m.mensaje}</span>
              <small className="font-mono text-[6.5px] opacity-60 block mt-0.5">{new Date(m.fecha).toLocaleString('es-AR',{day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'})}</small>
            </div>)}
          </div>
          <div className="flex gap-2">
            <input value={txt} onChange={e=>setTxt(e.target.value)} onKeyDown={e=>e.key==='Enter'&&enviar()} placeholder="Escribí una respuesta…" className="flex-1 bg-white/[.06] border border-white/10 rounded-xl px-3 py-2 text-[11.5px] outline-none focus:border-river/60 min-w-0"/>
            <button onClick={enviar} className="press rounded-xl px-3.5 text-white flex-none" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}}>➤</button>
          </div>
        </>:<div className="grid place-items-center h-full text-mut text-[12px] text-center px-4">Elegí un socio de la izquierda para responderle. Cada uno se identifica por su avatar y su ID.</div>}
      </div>
    </div>
  </>;
}

const FULL='md:col-span-2 xl:col-span-3';
function TestimoniosModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const list=db.testimonios||[];
  const upd=(i,k,v)=>OMDB.update(s=>{s.testimonios[i][k]=v;});
  const del=i=>OMDB.update(s=>{s.testimonios.splice(i,1);});
  const add=()=>OMDB.update(s=>{s.testimonios=[{nombre:'Nuevo hincha',lugar:'Corrientes',tag:'SOCIO',color:'#ff2e3e',texto:'Escribí el testimonio…',avatar:null},...(s.testimonios||[])];});
  const pickAv=i=>{const inp=document.createElement('input');inp.type='file';inp.accept='image/*';inp.onchange=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>4.5e6){toast('Imagen muy grande');return;}const r=new FileReader();r.onload=()=>{upd(i,'avatar',r.result);toast('Avatar actualizado ✔');};r.readAsDataURL(f);};inp.click();};
  return <>
    <Sec label="LA BANDA HABLA · TESTIMONIOS" right={<button onClick={add} className="press font-mono text-[8px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-2.5 py-1 hover:bg-cyanx/10">＋ NUEVO</button>}/>
    <div className="flex flex-col gap-2.5 flex-1 overflow-y-auto max-h-[440px] pr-1">
      {list.length===0&&<p className="text-mut text-[12px] py-6 text-center">Sin testimonios. Tocá ＋ Nuevo.</p>}
      {list.map((t,i)=>(
        <div key={i} className="rounded-2xl border border-white/[.08] bg-white/[.04] p-3 flex gap-3">
          <button onClick={()=>pickAv(i)} className="press w-14 h-14 rounded-full overflow-hidden grid place-items-center flex-none font-disp font-bold text-gold2 border-2 border-river" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{t.avatar?<img src={t.avatar} alt="" className="w-full h-full object-cover"/>:(t.nombre||'?').slice(0,2).toUpperCase()}</button>
          <div className="flex-1 min-w-0 flex flex-col gap-1.5">
            <div className="grid grid-cols-2 gap-1.5">
              <input defaultValue={t.nombre} onChange={e=>upd(i,'nombre',e.target.value)} placeholder="Nombre" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[11px] font-bold outline-none focus:border-river/60"/>
              <input defaultValue={t.lugar} onChange={e=>upd(i,'lugar',e.target.value)} placeholder="Ciudad" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[10px] outline-none focus:border-river/60"/>
            </div>
            <input defaultValue={t.tag} onChange={e=>upd(i,'tag',e.target.value)} placeholder="Etiqueta (ej: SOCIO VIP)" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[9.5px] font-mono outline-none focus:border-river/60"/>
            <textarea defaultValue={t.texto} onChange={e=>upd(i,'texto',e.target.value)} rows="2" placeholder="Testimonio…" className="bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-1.5 text-[11px] outline-none focus:border-river/60 resize-none"/>
            <div className="flex items-center gap-2">
              <div className="flex gap-1">{['#ff2e3e','#e0a24e','#8b5cf6','#22d3ee','#22c55e'].map(cl=><button key={cl} onClick={()=>upd(i,'color',cl)} className="press w-5 h-5 rounded-full border-2" style={{background:cl,borderColor:t.color===cl?'#fff':'transparent'}}/>)}</div>
              <button onClick={()=>del(i)} className="press ml-auto font-mono text-[8px] tracking-[1px] text-dim hover:text-river2 flex items-center gap-1"><Ic n="trash" s={12}/>ELIMINAR</button>
            </div>
          </div>
        </div>))}
    </div>
  </>;
}
function AuditoriaModule(){
  const db=useDB();const toast=useContext(ToastCtx);
  const [rep,setRep]=useState(null);const [prompt,setPrompt]=useState('');const fRef=useRef();
  const run=()=>{
    const p=(db.config.pagos)||{};const env=p.entorno||'test';const cur=p[env]||{};
    const bronce=(db.planes||[]).find(x=>x.id==='BRONCE');
    const checks=[
      ['Secciones del sitio', (db.config.sections||[]).length, (db.config.sections||[]).length>0],
      ['Figuritas en el pool', (db.figuritas||[]).length, true],
      ['Sponsors cargados', ['ORO','PLATA','BRONCE'].reduce((n,t)=>n+(((db.sponsors||{})[t])||[]).length,0), true],
      ['Banners publicitarios', (db.config.banners||[]).length, true],
      ['Planes de socio', (db.planes||[]).length, (db.planes||[]).length===4],
      ['Bronce gratis', bronce&&bronce.precio===0?'sí':'no', bronce&&bronce.precio===0],
      ['Pasarela MP activa ('+env+')', cur.mp_activo?'sí':'no', true],
      ['Pasarela Stripe activa ('+env+')', cur.stripe_activo?'sí':'no', true],
      ['Tokens secretos cifrados', (typeof cur.mp_access_token==='string'&&cur.mp_access_token.indexOf('enc:')===0)?'sí':'—', true],
      ['Contadores en cero (launch)', (db.kpis.socios===0)?'sí':'no', true],
      ['Visitas geolocalizadas', (db.visitas_geo||[]).length, (db.visitas_geo||[]).length>0],
      ['Trivia · participaciones', ((db.trivia&&db.trivia.participaciones)||[]).length, true],
      ['Prode · pronósticos', ((db.prode&&db.prode.participaciones)||[]).length, true],
      ['Mensajes recibidos', (db.mensajes||[]).length, true],
    ];
    setRep({fecha:new Date().toLocaleString('es-AR'),checks});
    toast('Auditoría ejecutada · '+checks.length+' checks');
  };
  const descargar=()=>{
    if(!rep){run();return;}
    const rows=rep.checks.map(c=>`<tr><td>${c[0]}</td><td style="text-align:center">${c[1]}</td><td style="text-align:center;color:${c[2]?'#0a0':'#c00'}">${c[2]?'OK':'REVISAR'}</td></tr>`).join('');
    const html=`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Auditoría · Orgullo Millonario</title></head><body style="font-family:Arial,sans-serif">
    <h1 style="color:#e01e2f">Informe de Arquitectura · Orgullo Millonario</h1>
    <p><b>Fecha:</b> ${rep.fecha}</p>
    <h2>Stack</h2><p>Astro + React + Tailwind CSS · Supabase (PostgreSQL, Auth, Storage, Realtime) · Hostinger. Capa OM-DB (Panel escribe / Sitio lee en vivo).</p>
    <h2>Checks del ecosistema</h2>
    <table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%"><tr style="background:#eee"><th align="left">Ítem</th><th>Valor</th><th>Estado</th></tr>${rows}</table>
    <h2>Base de datos</h2><p>profiles, figuritas, user_figuritas, posts, comentarios, viajes_reservas, config, mensajes. RLS activo; trigger auth→profiles; RPC atómica de intercambio P2P; bucket Storage orgullo-media (solo URLs).</p>
    <h2>Seguridad</h2><p>Claves públicas en cliente; tokens secretos cifrados (server-side en checkout). LiteSpeed cache deshabilitado para HTML dinámico. Panel privado (SHA-256, admin root).</p>
    </body></html>`;
    const blob=new Blob(['\ufeff'+html],{type:'application/msword'});
    const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='auditoria-orgullo-millonario.doc';a.click();
    toast('Informe .doc (Word) descargado ✔');
  };
  const subir=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;
    setPrompt(`Actuá como arquitecto full-stack senior (Astro + React + Tailwind + Supabase). Analizá el informe de auditoría "${f.name}" del proyecto Orgullo Millonario y devolvé, priorizado por criticidad: (1) vulnerabilidades de seguridad y cómo cerrarlas (RLS, tokens, cache), (2) errores de lógica/rutas rotas, (3) optimizaciones de rendimiento (LCP/TBT/SEO), (4) el código exacto para corregir cada punto sin romper la estética liquid glass ni la capa OM-DB. Todo modular y compatible con Hostinger.`);
    toast('Documento leído · prompt generado abajo');e.target.value='';};
  return <>
    <Sec label="AUTO-AUDITORÍA · INFORME DESCARGABLE" right={<button onClick={run} className="press font-mono text-[8px] tracking-[1px] text-cyanx border border-cyanx/40 rounded-lg px-2.5 py-1 hover:bg-cyanx/10">▶ EJECUTAR</button>}/>
    <div className="grid md:grid-cols-2 gap-3 flex-1">
      <div className="rounded-2xl border border-white/[.08] bg-white/[.03] p-3 flex flex-col">
        <b className="font-mono text-[8.5px] tracking-[1.5px] text-gold2 mb-2">RESULTADO DEL TEST</b>
        <div className="flex flex-col gap-1 overflow-y-auto max-h-[280px] pr-1 flex-1">
          {!rep&&<p className="text-mut text-[11px] py-6 text-center">Tocá ▶ Ejecutar para correr el test sobre todo el sitio.</p>}
          {rep&&rep.checks.map((c,i)=><div key={i} className="flex items-center gap-2 rounded-lg px-2.5 py-1.5 bg-white/[.04]">
            <i className={`w-2 h-2 rounded-full flex-none ${c[2]?'bg-green-400':'bg-river2'}`}/>
            <b className="text-[10.5px] flex-1 truncate">{c[0]}</b><span className="font-mono text-[9px] text-gold2">{String(c[1])}</span></div>)}
        </div>
        <button onClick={descargar} className="press rounded-xl py-2.5 mt-2 text-[12px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)'}}>⬇ Descargar informe .doc (Word)</button>
      </div>
      <div className="rounded-2xl border border-white/[.08] bg-white/[.03] p-3 flex flex-col">
        <b className="font-mono text-[8.5px] tracking-[1.5px] text-cyanx mb-2">SUBÍ UN INFORME · GENERAR PROMPT</b>
        <input ref={fRef} type="file" accept=".doc,.docx,.txt" onChange={subir} className="hidden"/>
        <div onClick={()=>fRef.current.click()} onDragOver={e=>e.preventDefault()} onDrop={e=>{e.preventDefault();if(e.dataTransfer.files[0]){fRef.current.files=e.dataTransfer.files;subir({target:fRef.current});}}}
          className="press cursor-pointer rounded-xl border-2 border-dashed border-white/15 hover:border-cyanx/60 grid place-items-center py-6 text-center transition-colors mb-2">
          <div><div className="text-[22px] mb-1">📄⬆</div><b className="text-[11px] block">Arrastrá tu .docx/.doc aquí</b><small className="font-mono text-[7.5px] text-dim">o tocá para subirlo</small></div>
        </div>
        {prompt&&<><span className="font-mono text-[7px] tracking-[1px] text-mut mb-1">PROMPT PARA SOLUCIONAR</span>
          <textarea readOnly value={prompt} className="flex-1 min-h-[120px] bg-white/[.06] border border-white/10 rounded-lg px-2.5 py-2 text-[10px] outline-none resize-none"/>
          <button onClick={()=>{navigator.clipboard&&navigator.clipboard.writeText(prompt);toast('Prompt copiado ✔');}} className="press glass rounded-xl py-2 mt-2 text-[11px] font-bold text-ink">Copiar prompt</button></>}
      </div>
    </div>
  </>;
}
const MODULES=[
  {id:'audit', title:'Auto-Auditoría',        icon:'grid',  span:FULL, rootOnly:true, C:AuditoriaModule},
  {id:'kpi',   title:'Dashboard & Métricas', icon:'dash',  span:FULL, bare:true, C:KpiModule},
  {id:'site',  title:'Sitio & Hero',         icon:'eye',   span:FULL, C:SiteModule},
  {id:'modular',title:'Modularización',      icon:'grid',  span:FULL, C:ModularModule},
  {id:'constr',title:'Constructor del Sitio', icon:'drag',  span:FULL, C:ConstructorModule},
  {id:'ads',   title:'Publicidades · Banners',icon:'card',  span:FULL, C:PublicidadesModule},
  {id:'rev',   title:'Facturación',          icon:'down',  span:'md:col-span-2 xl:col-span-2', C:RevenueModule},
  {id:'world', title:'Visitas',              icon:'eye',   span:'xl:col-span-1', C:WorldModule},
  {id:'refs',  title:'Referidos · Socios',   icon:'users', span:'xl:col-span-1', C:ReferidosModule},
  {id:'figus', title:'Figuritas Builder',    icon:'card',  span:FULL, C:FiguritasModule},
  {id:'squad', title:'Plantilla',            icon:'squad', span:'xl:col-span-1', C:PlantillaModule},
  {id:'viajes',title:'Viajes',               icon:'gift',  span:FULL, C:ViajesModule},
  {id:'testi', title:'La Banda Habla',        icon:'chat',  span:'xl:col-span-1', C:TestimoniosModule},
  {id:'premios',title:'Gestor de Premios',   icon:'gift',  span:'xl:col-span-1', C:PremiosModule},
  {id:'sorteo',title:'Sorteos',              icon:'gift',  span:FULL, C:SorteosModule},
  {id:'asientos',title:'Asientos por Plan',  icon:'gift',span:'xl:col-span-1', C:AsientosModule},
  {id:'pagos', title:'Cuentas de Pago',      icon:'gift',  span:'xl:col-span-1', C:PagosModule},
  {id:'spons', title:'Sponsors',             icon:'heart', span:'xl:col-span-1', C:SponsorsModule},
  {id:'reels', title:'Reels TikTok',         icon:'play',  span:'xl:col-span-1', C:ReelsModule},
  {id:'msgs',  title:'Mensajes · Recibir',   icon:'chat',  span:'xl:col-span-1', C:MensajesModule},
  {id:'enviar',title:'Mensajes · Enviar',    icon:'chat',  span:'xl:col-span-1', C:EnviarModule},
  {id:'protriv',title:'Prode & Trivia',      icon:'gift',  span:FULL, C:ProdeTriviaModule},
  {id:'admins',title:'Administradores',       icon:'grid',  span:FULL, rootOnly:true, C:AdminsModule},
];
const LS_KEY='om-panel-modules-v1';

/* ============ MODULE MANAGER SHEET ============ */
function ModuleSheet({open,onClose,enabled,setEnabled}){
  if(!open)return null;
  return <div className="fixed inset-0 z-[80] flex items-end md:items-center justify-center" onClick={onClose}>
    <div className="absolute inset-0 bg-black/60 backdrop-blur-sm"/>
    <div onClick={e=>e.stopPropagation()} className="glass-deep relative rounded-t-glass md:rounded-glass w-full md:w-[420px] p-6 safe-b" style={{animation:'sheetUp .35s cubic-bezier(.22,1,.36,1) both'}}>
      <div className="w-10 h-1 rounded-full bg-white/20 mx-auto mb-4 md:hidden"/>
      <div className="flex items-center justify-between mb-4">
        <b className="font-disp text-[16px] tracking-[1px]">MÓDULOS DEL PANEL</b>
        <button onClick={onClose} className="press w-8 h-8 rounded-full glass grid place-items-center text-mut hover:text-ink"><Ic n="x" s={14}/></button>
      </div>
      <div className="flex flex-col gap-2 max-h-[55vh] overflow-y-auto">
        {MODULES.map(m=>{
          const on=enabled.includes(m.id);
          return <button key={m.id} onClick={()=>setEnabled(on?enabled.filter(x=>x!==m.id):[...enabled,m.id])}
            className="press flex items-center gap-3 bg-white/[.05] border border-white/[.08] rounded-2xl px-4 py-3 text-left">
            <span className={on?'text-river2':'text-dim'}><Ic n={m.icon} s={16}/></span>
            <b className={`text-[12.5px] flex-1 ${on?'':'text-dim'}`}>{m.title}</b>
            <span className={`w-11 h-6 rounded-full p-[3px] transition-colors ${on?'bg-river':'bg-white/15'}`}>
              <i className={`block w-[18px] h-[18px] rounded-full bg-white shadow transition-transform ${on?'translate-x-5':''}`}/>
            </span>
          </button>;})}
      </div>
      <p className="font-mono text-[8px] text-dim tracking-[1px] mt-4 text-center">TU CONFIGURACIÓN SE GUARDA AUTOMÁTICAMENTE</p>
    </div>
  </div>;
}

/* ============ SEARCH FULLSCREEN ============ */
function SearchOverlay({open,onClose,onGo}){
  const [q,setQ]=useState('');
  const inRef=useRef();
  useEffect(()=>{if(open){setQ('');setTimeout(()=>inRef.current&&inRef.current.focus(),80);}},[open]);
  if(!open)return null;
  const ql=q.trim().toLowerCase();
  const res=MODULES.filter(m=>!ql||m.title.toLowerCase().includes(ql));
  return <div className="fixed inset-0 z-[95] flex flex-col" style={{animation:'sheetUp .3s cubic-bezier(.22,1,.36,1) both'}}>
    <div className="absolute inset-0 bg-black/80 backdrop-blur-xl" onClick={onClose}/>
    <div className="relative flex flex-col h-full px-4 md:px-8 pt-6 md:pt-16 pb-10 max-w-[820px] w-full mx-auto safe-b">
      <div className="glass-deep rounded-glass flex items-center gap-3 px-5 py-4">
        <span className="text-river2"><Ic n="search" s={20}/></span>
        <input ref={inRef} value={q} onChange={e=>setQ(e.target.value)} placeholder="Buscar módulos, secciones, herramientas…" className="flex-1 bg-transparent text-[17px] md:text-[20px] font-semibold outline-none placeholder:text-dim min-w-0"/>
        <button onClick={onClose} className="press w-9 h-9 rounded-full glass grid place-items-center text-mut hover:text-ink flex-none"><Ic n="x" s={16}/></button>
      </div>
      <div className="mt-4 flex flex-col gap-2 overflow-y-auto">
        <span className="font-mono text-[9px] tracking-[2px] text-mut px-1 mt-2">{ql?`${res.length} RESULTADO(S)`:'TODOS LOS MÓDULOS'}</span>
        {res.map(m=>(
          <button key={m.id} onClick={()=>{onGo(m.id);onClose();}} className="press glass rounded-2xl flex items-center gap-3.5 px-4 py-3.5 text-left">
            <span className="w-10 h-10 rounded-xl grid place-items-center text-river2 flex-none" style={{background:'rgba(224,30,47,.12)',border:'1px solid rgba(224,30,47,.3)'}}><Ic n={m.icon} s={17}/></span>
            <b className="text-[14px] flex-1">{m.title}</b>
            <span className="font-mono text-[9px] text-dim tracking-[1px]">IR ↗</span>
          </button>))}
        {res.length===0&&<div className="glass rounded-2xl p-8 text-center text-mut text-[13px]">Sin resultados para “{q}”.</div>}
      </div>
    </div>
  </div>;
}

/* ============ APP ============ */
function App(){
  const toast=useContext(ToastCtx);
  const [enabled,setEnabledRaw]=useState(()=>{const all=MODULES.map(m=>m.id);try{const v=JSON.parse(localStorage.getItem(LS_KEY));if(!Array.isArray(v))return all;return[...v.filter(id=>all.includes(id)),...all.filter(id=>!v.includes(id))];}catch(e){return all;}});
  const setEnabled=v=>{setEnabledRaw(v);try{localStorage.setItem(LS_KEY,JSON.stringify(v));}catch(e){}};
  const [cols,setColsRaw]=useState(()=>{try{return JSON.parse(localStorage.getItem('om-panel-cols'))||{};}catch(e){return {};}});
  const setCol=(id,n)=>setColsRaw(p=>{const nx={...p,[id]:n};try{localStorage.setItem('om-panel-cols',JSON.stringify(nx));}catch(e){}return nx;});
  const [sheet,setSheet]=useState(false);
  const [search,setSearch]=useState(false);
  const [notif,setNotif]=useState(false);
  const [amenu,setAmenu]=useState(false);
  const [adminAv,setAdminAv]=useState(()=>{try{return localStorage.getItem('om-admin-av')||'';}catch(e){return '';}});
  const avRef=useRef();
  const pickAv=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;if(f.size>3e6){toast('Imagen muy grande (máx 3MB)');return;}const r=new FileReader();r.onload=()=>{setAdminAv(r.result);try{localStorage.setItem('om-admin-av',r.result);}catch(x){}toast('Avatar actualizado ✔');setAmenu(false);};r.readAsDataURL(f);e.target.value='';};
  const [dragId,setDragId]=useState(null);
  const [clock,setClock]=useState('');
  useEffect(()=>{const f=()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');setClock(`${p(d.getDate())}/${p(d.getMonth()+1)}/${d.getFullYear()} · ${p(d.getHours())}:${p(d.getMinutes())} HS`);};f();const t=setInterval(f,30000);return()=>clearInterval(t);},[]);
  const isRoot=!!(window.OMAdmin&&typeof OMAdmin.isRoot==='function'&&OMAdmin.isRoot());
  const mods=enabled.map(id=>MODULES.find(m=>m.id===id)).filter(Boolean).filter(m=>!m.rootOnly||isRoot);
  const reorder=(toId)=>{if(!dragId||dragId===toId)return;const a=[...enabled];const from=a.indexOf(dragId),to=a.indexOf(toId);if(from<0||to<0)return;a.splice(from,1);a.splice(to,0,dragId);setEnabled(a);};
  const navGo=id=>{const el=document.getElementById('mod-'+id);if(el){const y=el.getBoundingClientRect().top+window.scrollY-84;window.scrollTo({top:y,behavior:'smooth'});}else toast('Activá ese módulo desde “Módulos”');};
  return <>
    {/* fondo liquid */}
    <div className="blob w-[520px] h-[520px] -top-40 -left-32" style={{background:'radial-gradient(circle,#8d0917 0%,transparent 70%)',opacity:.55,animation:'drift1 18s ease-in-out infinite'}}/>
    <div className="blob w-[420px] h-[420px] top-1/3 -right-32" style={{background:'radial-gradient(circle,#3b1d6e 0%,transparent 70%)',opacity:.5,animation:'drift2 22s ease-in-out infinite'}}/>
    <div className="blob w-[380px] h-[380px] bottom-0 left-1/3" style={{background:'radial-gradient(circle,#5a4008 0%,transparent 70%)',opacity:.4,animation:'drift1 26s ease-in-out infinite'}}/>

    {/* topbar glass */}
    <header className="sticky top-0 z-[60] px-3 md:px-6 pt-3">
      <div className="glass-deep rounded-glass flex items-center gap-3 px-4 md:px-5 py-2.5 max-w-[1440px] mx-auto">
        <img src="assets/logo-om.webp" alt="OM" className="w-8 h-9 object-contain"/>
        <div className="leading-tight mr-2">
          <b className="font-disp text-[13px] tracking-[1px] block">PANEL SAAS</b>
          <small className="font-mono text-[7px] tracking-[2px] text-river2">ORGULLO MILLONARIO</small>
        </div>
        <nav className="hidden lg:flex items-center gap-1 flex-1 justify-center">
          {MODULES.slice(0,7).map(m=>(
            <button key={m.id} onClick={()=>navGo(m.id)} className="press flex items-center gap-2 px-3.5 py-2 rounded-full text-[11px] font-semibold text-mut hover:text-ink hover:bg-white/[.07] transition-colors">
              <Ic n={m.icon} s={13}/>{m.title.split(' ')[0]}
            </button>))}
        </nav>
        <div className="flex items-center gap-2 ml-auto">
          <button onClick={()=>{OMDB.save();OMDB.exportBackup();toast('Configuración guardada · respaldo descargado ✔');}} title="Guardar configuración y descargar respaldo" className="press hidden sm:flex items-center gap-1.5 rounded-full glass px-3 h-9 text-[11px] font-bold text-green-400"><Ic n="down" s={13}/>Guardar</button>
          <button onClick={()=>{const inp=document.createElement('input');inp.type='file';inp.accept='.json,application/json';inp.onchange=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;const r=new FileReader();r.onload=()=>{OMDB.importBackup(r.result)?(toast('Configuración cargada ✔'),setTimeout(()=>location.reload(),700)):toast('JSON inválido');};r.readAsText(f);};inp.click();}} title="Cargar configuración desde un JSON" className="press hidden sm:flex items-center gap-1.5 rounded-full glass px-3 h-9 text-[11px] font-bold text-cyanx"><Ic n="up" s={13}/>Cargar</button>
          <ThemeToggle/>
          <button onClick={()=>setSearch(true)} className="press w-9 h-9 rounded-full glass grid place-items-center text-mut hover:text-ink"><Ic n="search" s={15}/></button>
          <div className="relative">
            <button onClick={()=>setNotif(v=>!v)} className="press w-9 h-9 rounded-full glass grid place-items-center text-mut hover:text-ink relative"><Ic n="bell" s={15}/><i className="absolute top-1.5 right-2 w-[6px] h-[6px] rounded-full bg-river2"/></button>
            {notif&&<><div className="fixed inset-0 z-[70]" onClick={()=>setNotif(false)}/><div className="glass-deep rounded-glass absolute right-0 mt-2 w-[280px] p-3 z-[71]" style={{animation:'sheetUp .25s cubic-bezier(.22,1,.36,1) both'}}>
              <div className="flex items-center justify-between mb-2 px-1"><b className="font-disp text-[12px] tracking-[1px]">NOTIFICACIONES</b><span className="font-mono text-[7px] text-green-400 tracking-[1px]">EN VIVO</span></div>
              <div className="flex flex-col gap-1.5">
                {(()=>{const u=(window.OMAuth&&OMAuth.users&&OMAuth.users())||[];const k=(window.OMDB&&OMDB.get&&OMDB.get().kpis)||{};return [
                  ['users','Socios registrados',(u.length||k.socios||0)+' en total','#8b5cf6'],
                  ['ticket','Reservas de viaje',(k.reservas||0)+' activas','#22c55e'],
                  ['eye','Visitas al sitio',(k.visitas||0)+' totales','#5b93ff'],
                  ['gift','Sorteo en curso','Camiseta firmada','#ffcf3e'],
                ].map(([ic,t,s,cc],i)=>(<div key={i} className="flex items-center gap-2.5 rounded-xl px-2.5 py-2 bg-white/[.04] border border-white/[.06]"><span className="w-7 h-7 rounded-lg grid place-items-center flex-none" style={{color:cc,background:cc+'1e'}}><Ic n={ic} s={14}/></span><div className="min-w-0 flex-1"><b className="text-[11px] block truncate">{t}</b><small className="font-mono text-[8px] text-mut">{s}</small></div></div>));})()}
              </div>
            </div></>}
          </div>
          <button onClick={()=>setSheet(true)} className="press hidden md:flex items-center gap-2 px-4 py-2 rounded-full text-[11px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 8px 24px -6px rgba(224,30,47,.6)'}}><Ic n="grid" s={13}/>Módulos</button>
          <div className="relative">
            <input ref={avRef} type="file" accept="image/*" onChange={pickAv} className="hidden"/>
            <button onClick={()=>setAmenu(v=>!v)} title="Cuenta de admin" className="press w-9 h-9 rounded-full grid place-items-center overflow-hidden font-disp font-bold text-[14px] text-gold2 border-2 border-river" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{adminAv?<img src={adminAv} alt="" className="w-full h-full object-cover"/>:'A'}</button>
            {amenu&&<><div className="fixed inset-0 z-[70]" onClick={()=>setAmenu(false)}/><div className="glass-deep rounded-glass absolute right-0 mt-2 w-[220px] p-2 z-[71]" style={{animation:'sheetUp .25s cubic-bezier(.22,1,.36,1) both'}}>
              <div className="flex items-center gap-2.5 px-2 py-2 mb-1">
                <span className="w-10 h-10 rounded-full grid place-items-center overflow-hidden font-disp font-bold text-[15px] text-gold2 border-2 border-river flex-none" style={{background:'radial-gradient(circle at 35% 30%,#3a3a44,#17171c)'}}>{adminAv?<img src={adminAv} alt="" className="w-full h-full object-cover"/>:'A'}</span>
                <div className="min-w-0"><b className="text-[12px] block">Administrador</b><small className="font-mono text-[7.5px] text-green-400 tracking-[1px]">● SESIÓN ACTIVA</small></div>
              </div>
              <button onClick={()=>avRef.current.click()} className="press w-full flex items-center gap-2.5 rounded-xl px-2.5 py-2.5 text-[11.5px] font-semibold text-mut hover:text-ink hover:bg-white/[.06]"><Ic n="eye" s={14}/> Cambiar avatar</button>
              {adminAv&&<button onClick={()=>{setAdminAv('');try{localStorage.removeItem('om-admin-av');}catch(x){}toast('Avatar quitado');}} className="press w-full flex items-center gap-2.5 rounded-xl px-2.5 py-2.5 text-[11.5px] font-semibold text-mut hover:text-ink hover:bg-white/[.06]"><Ic n="trash" s={14}/> Quitar avatar</button>}
              <button onClick={()=>{OMAdmin.logout();location.reload();}} className="press w-full flex items-center gap-2.5 rounded-xl px-2.5 py-2.5 text-[11.5px] font-semibold text-river2 hover:bg-river/10"><Ic n="x" s={14}/> Cerrar sesión</button>
            </div></>}
          </div>
        </div>
      </div>
    </header>

    {/* grid de módulos */}
    <main className="relative z-10 px-3 md:px-6 py-4 pb-40 md:pb-24 max-w-[1440px] mx-auto grid gap-3 md:gap-4 items-stretch auto-rows-min" style={{gridTemplateColumns:'repeat('+(window.innerWidth<768?1:Math.min(3,Math.max(1,mods.length)))+',minmax(0,1fr))'}}>
      {(()=>{const gridN=window.innerWidth<768?1:Math.min(3,Math.max(1,mods.length));return mods.map((m,i)=>{
        const span=Math.min(gridN,Math.max(1,cols[m.id]||1));
        const ColCtl=gridN>1?<div className="hidden md:flex items-center gap-0.5 mr-1">
          {[1,2,3].slice(0,gridN).map(n=><button key={n} onClick={e=>{e.stopPropagation();setCol(m.id,n);}} title={n+' columna'+(n>1?'s':'')} className={`press w-5 h-5 rounded grid place-items-center text-[8px] font-mono ${span===n?'bg-river text-white':'text-dim hover:text-ink'}`} style={span===n?{}:{background:'rgba(255,255,255,.06)'}}>{n}</button>)}
        </div>:null;
        return m.bare
        ?<div key={m.id} id={'mod-'+m.id} draggable onDragStart={()=>setDragId(m.id)} onDragEnd={()=>setDragId(null)} onDragOver={e=>{e.preventDefault();reorder(m.id);}} className={`relative rise ${dragId===m.id?'opacity-50':''}`} style={{animationDelay:i*60+'ms',gridColumn:'span '+span}}>{gridN>1&&<div className="hidden md:flex items-center gap-0.5 absolute top-2 right-2 z-20 rounded-lg px-1 py-0.5" style={{background:'rgba(10,10,14,.6)',backdropFilter:'blur(6px)'}}>{[1,2,3].slice(0,gridN).map(n=><button key={n} onClick={e=>{e.stopPropagation();setCol(m.id,n);}} title={n+' columna'+(n>1?'s':'')} className={`press w-5 h-5 rounded grid place-items-center text-[8px] font-mono ${span===n?'bg-river text-white':'text-dim hover:text-ink'}`} style={span===n?{}:{background:'rgba(255,255,255,.06)'}}>{n}</button>)}</div>}<m.C/></div>
        :<section key={m.id} id={'mod-'+m.id} onDragOver={e=>{e.preventDefault();reorder(m.id);}} className={`glass rounded-glass p-5 flex flex-col h-full rise ${dragId===m.id?'opacity-50 ring-2 ring-river':''}`} style={{animationDelay:i*60+'ms',gridColumn:'span '+span}}>
          <div draggable onDragStart={()=>setDragId(m.id)} onDragEnd={()=>setDragId(null)} className="flex items-center gap-2.5 mb-4 cursor-grab active:cursor-grabbing">
            <span className="w-8 h-8 rounded-xl grid place-items-center text-river2" style={{background:'rgba(224,30,47,.12)',border:'1px solid rgba(224,30,47,.3)'}}><Ic n={m.icon} s={15}/></span>
            <b className="font-disp text-[14px] tracking-[1.5px] flex-1">{m.title.toUpperCase()}</b>
            {ColCtl}
            <span className="text-dim"><Ic n="drag" s={15}/></span>
          </div>
          <m.C/>
        </section>;});})()}
      {mods.length===0&&<div className="col-span-full glass rounded-glass p-10 text-center text-mut text-[13px]">No hay módulos activos. Tocá <b className="text-ink">Módulos</b> para activarlos.</div>}
    </main>

    {/* footer */}
    <footer className="relative z-10 hidden md:flex items-center gap-5 px-6 pb-4 max-w-[1440px] mx-auto font-mono text-[8px] tracking-[1px] text-dim">
      <span>ORGULLO MILLONARIO ® 2026</span><span>PANEL SAAS v3.0 · LIQUID</span>
      <span className="text-green-400">SERVIDORES: ONLINE ●</span><span className="ml-auto">ACTUALIZADO: {clock}</span>
    </footer>

    {/* tab bar móvil */}
    <nav className="lg:hidden fixed bottom-3 inset-x-3 z-[70] safe-b">
      <div className="glass-deep rounded-glass flex items-center justify-around px-2 py-2">
        {[['kpi','dash','Inicio'],['figus','card','Figus'],['squad','squad','Plantel'],['sorteo','gift','Sorteos']].map(([id,ic,l])=>(
          <button key={id} onClick={()=>navGo(id)} className="press flex flex-col items-center gap-1 px-4 py-1.5 rounded-2xl text-mut hover:text-ink">
            <Ic n={ic} s={19}/><small className="font-mono text-[7px] tracking-[1px]">{l}</small>
          </button>))}
        <button onClick={()=>setSheet(true)} className="press flex flex-col items-center gap-1 px-4 py-1.5 rounded-2xl text-white">
          <span className="w-[26px] h-[26px] -my-[3px] rounded-full grid place-items-center" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 6px 18px -4px rgba(224,30,47,.7)'}}><Ic n="grid" s={13}/></span>
          <small className="font-mono text-[7px] tracking-[1px]">Módulos</small>
        </button>
      </div>
    </nav>

    <ModuleSheet open={sheet} onClose={()=>setSheet(false)} enabled={enabled} setEnabled={setEnabled}/>
    <SearchOverlay open={search} onClose={()=>setSearch(false)} onGo={navGo}/>
  </>;
}

/* ============ THEME TOGGLE (dentro del menú) ============ */
function ThemeToggle(){
  const [t,setT]=useState(()=>(window.omGetTema?window.omGetTema():'dark'));
  useEffect(()=>{const h=e=>setT(e.detail);window.addEventListener('om-tema',h);return()=>window.removeEventListener('om-tema',h);},[]);
  const toggle=()=>{const nt=window.omToggleTema?window.omToggleTema():(t==='dark'?'light':'dark');setT(nt);};
  return <button onClick={toggle} title="Tema claro / oscuro" className="press w-9 h-9 rounded-full glass grid place-items-center text-gold2 hover:text-ink"><Ic n={t==='light'?'moon':'sun'} s={15}/></button>;
}

/* ============ ADMIN LOGIN (privado) ============ */
function AdminLogin({onOk}){
  const [mode,setMode]=useState('login');
  const [email,setEmail]=useState('');const [pass,setPass]=useState('');const [show,setShow]=useState(false);
  const [code,setCode]=useState('');const [pass2,setPass2]=useState('');
  const [err,setErr]=useState('');const [ok,setOk]=useState('');const [loading,setLoading]=useState(false);
  const submit=async()=>{setErr('');setLoading(true);const r=await OMAdmin.login(email,pass);if(r.ok){onOk();}else{setErr(r.error);setLoading(false);}};
  const activar=async()=>{setErr('');setOk('');if(pass!==pass2){setErr('Las contraseñas no coinciden');return;}setLoading(true);const r=await OMAdmin.activar(email,code,pass);setLoading(false);if(r.ok){setOk('¡Cuenta activada! Ya podés ingresar.');setMode('login');setPass('');setPass2('');setCode('');}else setErr(r.error);};
  return <div className="relative min-h-[100dvh] grid place-items-center px-4">
    <div className="blob w-[520px] h-[520px] -top-40 -left-32" style={{background:'radial-gradient(circle,#8d0917,transparent 70%)',opacity:.5,animation:'drift1 18s ease-in-out infinite'}}/>
    <div className="blob w-[420px] h-[420px] bottom-0 -right-32" style={{background:'radial-gradient(circle,#3b1d6e,transparent 70%)',opacity:.45,animation:'drift2 22s ease-in-out infinite'}}/>
    <div className="glass-deep relative rounded-glass w-full max-w-[400px] p-7 md:p-8 rise">
      <div className="flex flex-col items-center text-center mb-6">
        <img src="assets/logo-om.webp" alt="OM" className="w-14 h-14 object-contain mb-3"/>
        <b className="font-disp text-[22px] tracking-[1px]">PANEL SAAS · PRIVADO</b>
        <small className="font-mono text-[8px] tracking-[2px] text-river2 mt-1">ACCESO SOLO ADMINISTRADORES</small>
      </div>
      {ok&&<div className="text-[12px] text-green-400 bg-green-500/10 border border-green-500/30 rounded-xl px-3 py-2 mb-3">{ok}</div>}
      {mode==='login'?<div className="flex flex-col gap-3">
        <Fld icon="eye" type="email" autoComplete="username" value={email} onChange={e=>setEmail(e.target.value)} onKeyDown={e=>e.key==='Enter'&&submit()} placeholder="Correo de administrador"/>
        <label className="flex items-center gap-3 bg-white/[.06] border border-white/12 rounded-2xl px-4 py-3.5 focus-within:border-river/60 transition-colors">
          <span className="text-mut flex-none"><Ic n="grid" s={16}/></span>
          <input type={show?'text':'password'} autoComplete="current-password" value={pass} onChange={e=>setPass(e.target.value)} onKeyDown={e=>e.key==='Enter'&&submit()} placeholder="Contraseña" className="flex-1 bg-transparent text-[14px] outline-none placeholder:text-dim min-w-0"/>
          <button type="button" onClick={()=>setShow(!show)} className="press text-mut hover:text-ink flex-none text-[11px] font-mono">{show?'OCULTAR':'VER'}</button>
        </label>
        {err&&<div className="text-[12px] text-river2 bg-river/10 border border-river/30 rounded-xl px-3 py-2">{err}</div>}
        <button onClick={submit} disabled={loading} className="press rounded-2xl py-3.5 text-[14px] font-bold text-white mt-1" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 12px 30px -8px rgba(224,30,47,.6)'}}>{loading?'Verificando…':'INGRESAR AL PANEL'}</button>
        <button onClick={()=>{setMode('activar');setErr('');}} className="press font-mono text-[10px] text-mut hover:text-ink tracking-[1px] mt-1">¿Te invitaron? ACTIVÁ TU CUENTA →</button>
      </div>:<div className="flex flex-col gap-3">
        <div className="font-mono text-[9px] tracking-[2px] text-gold2 text-center mb-1">ACTIVAR CUENTA DE ADMIN</div>
        <Fld icon="eye" type="email" value={email} onChange={e=>setEmail(e.target.value)} placeholder="Tu correo (el que te invitaron)"/>
        <Fld icon="grid" value={code} onChange={e=>setCode(e.target.value)} placeholder="Código de invitación (6 dígitos)"/>
        <Fld icon="grid" type="password" value={pass} onChange={e=>setPass(e.target.value)} placeholder="Creá tu contraseña (6+)"/>
        <Fld icon="grid" type="password" value={pass2} onChange={e=>setPass2(e.target.value)} placeholder="Repetí la contraseña"/>
        {err&&<div className="text-[12px] text-river2 bg-river/10 border border-river/30 rounded-xl px-3 py-2">{err}</div>}
        <button onClick={activar} disabled={loading} className="press rounded-2xl py-3.5 text-[14px] font-bold text-white mt-1" style={{background:'linear-gradient(160deg,#22c55e,#0f7a3d)'}}>{loading?'Activando…':'ACTIVAR Y CREAR CONTRASEÑA'}</button>
        <button onClick={()=>{setMode('login');setErr('');}} className="press font-mono text-[10px] text-mut hover:text-ink tracking-[1px] mt-1">← VOLVER AL INGRESO</button>
      </div>}
      <p className="font-mono text-[8px] text-dim tracking-[1px] mt-5 text-center">🔒 Sesión cifrada · las credenciales nunca se almacenan en claro</p>
    </div>
  </div>;
}
function AdminGate(){
  const [authed,setAuthed]=useState(()=>!!(window.OMAdmin&&typeof OMAdmin.isAuthed==='function'&&OMAdmin.isAuthed()));
  if(!authed) return <AdminLogin onOk={()=>setAuthed(true)}/>;
  return <App/>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<ToastHost><AdminGate/></ToastHost>);
