// ========== Admin · KPIs + Scraper de mercado ==========
function AdminPage({ go }) {
  const [data, setData]   = React.useState(null);
  const [err, setErr]     = React.useState('');
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    (async () => {
      try {
        const supa = window.holai && window.holai.supabase;
        if (!supa) throw new Error('Auth no inicializado.');
        const { data: { session } } = await supa.auth.getSession();
        if (!session) throw new Error('Sin sesión activa.');

        const r = await fetch('/api/admin/stats', {
          headers: { Authorization: 'Bearer ' + session.access_token },
        });
        const json = await r.json();
        if (!r.ok) throw new Error(json.error || `Error ${r.status}`);
        setData(json);
      } catch (e) {
        setErr(e.message || 'Error cargando stats.');
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  return (
    <div className="app-shell">
      <Sidebar route="admin" go={go}/>
      <div className="content-area">
        <TopBar
          title="Admin · KPIs"
          sub="Estado del producto en tiempo real"/>

        {loading && (
          <div className="card" style={{ padding: 40, textAlign:'center' }}>
            <div style={{ fontSize: 14, color:'var(--muted)' }}>Cargando estadísticas…</div>
          </div>
        )}

        {err && (
          <div className="card" style={{ padding: 24, background:'rgba(232,69,69,0.08)', border:'1px solid rgba(232,69,69,0.3)' }}>
            <div style={{ fontWeight: 700, color:'#E84545', marginBottom: 6 }}>No pudimos cargar las stats.</div>
            <div style={{ fontSize: 13, color:'var(--ink-2)' }}>{err}</div>
            <div style={{ fontSize: 12, color:'var(--muted)', marginTop: 10 }}>
              Verificá que SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY y ADMIN_EMAILS estén configurados en Vercel.
              Y que la migration 0001 esté aplicada en Supabase.
            </div>
          </div>
        )}

        {data && (
          <>
            {/* KPI cards */}
            <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap: 16, marginBottom: 24 }}>
              <KPI label="Usuarios totales"          value={data.summary.usersTotal}                   sub="cuentas creadas"/>
              <KPI label="Nuevos últimos 7 días"     value={data.summary.usersLast7d}                  sub="signups recientes"/>
              <KPI label="Nuevos últimos 30 días"    value={data.summary.usersLast30d}                 sub="signups mes"/>
              <KPI label="Activación"                value={pct(data.summary.activationRate)}         sub={`${data.summary.usersWithImport}/${data.summary.usersTotal} importaron`}/>
              <KPI label="PDFs importados"           value={data.summary.importsTotal}                 sub="total"/>
              <KPI label="Pólizas extraídas"         value={data.summary.policiesTotal}                sub="acumuladas"/>
            </div>

            {/* Chart signups */}
            <div className="card" style={{ padding: 22, marginBottom: 28 }}>
              <div className="eyebrow" style={{ marginBottom: 4 }}>Signups por día — últimos 30 días</div>
              <SignupsChart points={data.signupsByDay}/>
            </div>
          </>
        )}

        {/* Scraper de mercado — independiente de las stats */}
        <MarketScraper/>
      </div>
    </div>
  );
}

function pct(n) {
  if (typeof n !== 'number') return '—';
  return Math.round(n * 100) + '%';
}

function KPI({ label, value, sub }) {
  return (
    <div className="card">
      <div className="eyebrow">{label}</div>
      <div style={{ fontSize: 36, fontWeight: 800, letterSpacing:'-0.04em', lineHeight: 1, margin:'10px 0 4px' }}>{value}</div>
      <div className="muted" style={{ fontSize: 12 }}>{sub}</div>
    </div>
  );
}

// ── Scraper de mercado ───────────────────────────────────────────────────────
// Recorre el catálogo (todos los ramos o uno), filtra por tipo y criterios del
// recomendador (edad, presupuesto, coberturas), rankea con la lógica del asesor
// y visita los sitios oficiales (vía /api/admin/scrape) en lotes para:
//   · verificar liveness (status, latencia, redirecciones)
//   · validar que el nombre del producto siga publicado en la página
//   · opcionalmente revisar el folleto PDF
// Sólo preview, no persiste.
const SCRAPE_BATCH = 18;       // URLs por request (bajo el tope del endpoint)
const SCRAPE_TOTAL_MAX = 90;   // tope de productos por corrida (cortesía)

function scrapeProductUrl(p, company) {
  return (p.detail && p.detail.productoUrl) || company.url || null;
}

function coberturaHaystack(p) {
  const d = p.detail || {};
  const parts = [p.nombre, p.desc];
  ['coberturas', 'coberturasIncluidas', 'coberturasOpcionales', 'modalidades'].forEach(k => {
    if (Array.isArray(d[k])) parts.push(d[k].join(' '));
  });
  if (d.redPrestadores) parts.push(d.redPrestadores);
  return parts.filter(Boolean).join(' · ').toLowerCase();
}

function companyInactive(c) {
  return !!(c.mergedInto || c.inRunOff || c.audience);
}

// Construye la lista de productos candidatos según los filtros.
function collectCandidates({ ramoKeys, edad, presupuestoMonthly, includeInactive }) {
  const catalog = window.HOLAI_INSURANCE_CATALOG || [];
  const ramoSet = new Set(ramoKeys);
  const crit = { edad, presupuestoMonthly };
  const out = [];

  if (includeInactive) {
    // Barrido directo del catálogo: incluye fusionadas / run-off / audiencia.
    for (const c of catalog) {
      for (const p of (c.productos || [])) {
        if (!ramoSet.has(p.ramo)) continue;
        const scored = (typeof scoreProduct === 'function') ? scoreProduct(p, c, crit) : null;
        out.push(scored
          ? { ...scored, inactive: companyInactive(c) }
          : { product: p, company: c, score: null, reasons: [], inactive: true });
      }
    }
  } else if (typeof rankProducts === 'function') {
    // Sólo vigentes y scoreados, vía la misma lógica del recomendador.
    for (const k of ramoKeys) {
      rankProducts({ ramo: k, edad, presupuestoMonthly })
        .forEach(r => out.push({ ...r, inactive: false }));
    }
  }
  return out;
}

function MarketScraper() {
  const ramos = (typeof REC_RAMOS !== 'undefined') ? REC_RAMOS : [];
  const presupuestos = (typeof REC_PRESUPUESTOS !== 'undefined') ? REC_PRESUPUESTOS : [];
  const catalog = window.HOLAI_INSURANCE_CATALOG || [];
  const tipos = React.useMemo(() => [...new Set(catalog.map(c => c.tipo).filter(Boolean))], [catalog]);

  const [ramo, setRamo] = React.useState('');            // '' = todos los ramos
  const [tipo, setTipo] = React.useState('');
  const [edad, setEdad] = React.useState('');
  const [presupuestoK, setPresupuestoK] = React.useState('');
  const [coberturas, setCoberturas] = React.useState('');
  const [includeInactive, setIncludeInactive] = React.useState(false);
  const [includeBrochure, setIncludeBrochure] = React.useState(false);
  const [sortMode, setSortMode] = React.useState('score'); // 'score' | 'estado'

  const [rows, setRows] = React.useState(null);
  const [running, setRunning] = React.useState(false);
  const [progress, setProgress] = React.useState({ done: 0, total: 0 });
  const [notice, setNotice] = React.useState('');
  const [truncated, setTruncated] = React.useState(false);

  const run = async () => {
    setNotice(''); setTruncated(false); setProgress({ done: 0, total: 0 });
    if (typeof rankProducts !== 'function' && typeof scoreProduct !== 'function') {
      setNotice('El motor del recomendador no está disponible.');
      return;
    }
    setRunning(true);

    const presupuesto = presupuestos.find(p => p.k === presupuestoK);
    const edadN = edad ? parseInt(edad, 10) : null;
    const ramoKeys = ramo ? [ramo] : ramos.map(r => r.k);

    let cand = collectCandidates({
      ramoKeys,
      edad: edadN,
      presupuestoMonthly: presupuesto ? presupuesto.monthly : null,
      includeInactive,
    });

    if (tipo) cand = cand.filter(r => r.company.tipo === tipo);
    const terms = coberturas.split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
    if (terms.length) cand = cand.filter(r => { const h = coberturaHaystack(r.product); return terms.every(t => h.includes(t)); });

    // dedup por compañía + producto
    const seenP = new Set();
    cand = cand.filter(r => {
      const k = `${r.company.id}::${r.product.id || r.product.nombre}`;
      if (seenP.has(k)) return false; seenP.add(k); return true;
    });

    cand.sort((a, b) => (b.score == null ? -1 : b.score) - (a.score == null ? -1 : a.score));

    if (cand.length > SCRAPE_TOTAL_MAX) { cand = cand.slice(0, SCRAPE_TOTAL_MAX); setTruncated(true); }

    const built = cand.map((r, i) => {
      const siteUrl = scrapeProductUrl(r.product, r.company);
      const brochureUrl = includeBrochure && r.product.detail && r.product.detail.brochureUrl ? r.product.detail.brochureUrl : null;
      return {
        key: `${r.company.id}-${r.product.id || r.product.nombre}-${i}`,
        rank: i + 1,
        product: r.product, company: r.company,
        score: r.score, inactive: r.inactive,
        site: siteUrl ? { url: siteUrl, scrape: null } : null,
        brochure: brochureUrl ? { url: brochureUrl, scrape: null } : null,
      };
    });

    setRows(built);

    // Items únicos a verificar (dedup por url).
    const itemMap = new Map();
    for (const row of built) {
      if (row.site && !itemMap.has(row.site.url)) itemMap.set(row.site.url, { url: row.site.url, expect: row.product.nombre });
      if (row.brochure && !itemMap.has(row.brochure.url)) itemMap.set(row.brochure.url, { url: row.brochure.url });
    }
    const items = [...itemMap.values()];
    if (!items.length) { setRunning(false); return; }
    setProgress({ done: 0, total: items.length });

    // Token admin (si no hay Supabase, mostramos el preview client-side).
    let token = null;
    try {
      const supa = window.holai && window.holai.supabase;
      if (!supa) throw new Error('no-auth');
      const { data: { session } } = await supa.auth.getSession();
      if (!session) throw new Error('no-auth');
      token = session.access_token;
    } catch {
      setNotice('El preview funciona, pero para verificar los sitios oficiales necesitás Supabase + ADMIN_EMAILS configurados.');
      setRunning(false);
      return;
    }

    const byUrl = {};
    const norm = (u) => { try { return new URL(u).toString(); } catch { return u; } };

    for (let i = 0; i < items.length; i += SCRAPE_BATCH) {
      const chunk = items.slice(i, i + SCRAPE_BATCH);
      try {
        const resp = await fetch('/api/admin/scrape', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
          body: JSON.stringify({ items: chunk }),
        });
        const json = await resp.json().catch(() => ({}));
        if (!resp.ok) throw new Error(json.error || `Error ${resp.status}`);
        (json.results || []).forEach(r => { byUrl[r.url] = r; });
      } catch (e) {
        setNotice('Un lote falló: ' + (e.message || 'error') + '. El resto del preview es válido.');
      }
      setProgress({ done: Math.min(i + SCRAPE_BATCH, items.length), total: items.length });
      setRows(prev => (prev || []).map(row => ({
        ...row,
        site: row.site ? { ...row.site, scrape: byUrl[row.site.url] || byUrl[norm(row.site.url)] || row.site.scrape } : null,
        brochure: row.brochure ? { ...row.brochure, scrape: byUrl[row.brochure.url] || byUrl[norm(row.brochure.url)] || row.brochure.scrape } : null,
      })));
    }

    setRunning(false);
  };

  // Resumen agregado.
  const summary = React.useMemo(() => {
    const s = { total: 0, ok: 0, broken: 0, timeout: 0, nameOk: 0, nameBad: 0, pending: 0, msSum: 0, msN: 0, avgMs: null };
    (rows || []).forEach(row => {
      if (!row.site) return;
      s.total++;
      const sc = row.site.scrape;
      if (!sc) { s.pending++; return; }
      if (sc.ok) { s.ok++; if (sc.name) { sc.name.matched ? s.nameOk++ : s.nameBad++; } }
      else if (sc.error === 'timeout') s.timeout++;
      else s.broken++;
      if (typeof sc.ms === 'number') { s.msSum += sc.ms; s.msN++; }
    });
    s.avgMs = s.msN ? Math.round(s.msSum / s.msN) : null;
    return s;
  }, [rows]);

  const sorted = React.useMemo(() => {
    const arr = [...(rows || [])];
    if (sortMode === 'estado') {
      const rankOf = (row) => {
        const sc = row.site && row.site.scrape;
        if (!sc) return 5;
        if (!sc.ok) return sc.error === 'timeout' ? 1 : 0;
        if (sc.name && !sc.name.matched) return 2;
        return 4;
      };
      arr.sort((a, b) => rankOf(a) - rankOf(b) || ((b.score == null ? -1 : b.score) - (a.score == null ? -1 : a.score)));
    }
    return arr;
  }, [rows, sortMode]);

  const exportJSON = () => {
    const data = (rows || []).map(row => ({
      compania: row.company.name, tipo: row.company.tipo, ramo: row.product.ramo,
      producto: row.product.nombre, score: row.score, noVigente: !!row.inactive,
      url: row.site ? row.site.url : null,
      status: row.site && row.site.scrape ? row.site.scrape.status : null,
      ok: row.site && row.site.scrape ? row.site.scrape.ok : null,
      ms: row.site && row.site.scrape ? row.site.scrape.ms : null,
      nombreEnPagina: row.site && row.site.scrape && row.site.scrape.name ? row.site.scrape.name.matched : null,
      folleto: row.brochure ? row.brochure.url : null,
      folletoStatus: row.brochure && row.brochure.scrape ? row.brochure.scrape.status : null,
    }));
    const blob = new Blob([JSON.stringify({ exportedAt: new Date().toISOString(), rows: data }, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `holai-scrape-${Date.now()}.json`;
    a.click();
    setTimeout(() => URL.revokeObjectURL(a.href), 2000);
  };

  return (
    <div className="card" style={{ padding: 22, marginTop: 4 }}>
      <div className="eyebrow" style={{ marginBottom: 4 }}>Scraper de mercado</div>
      <h3 style={{ fontSize: 22, fontWeight: 800, letterSpacing:'-0.03em', margin:'0 0 4px' }}>
        Recorrer aseguradoras por criterios
      </h3>
      <p className="muted" style={{ fontSize: 13, margin:'0 0 18px', lineHeight: 1.5 }}>
        Filtra el catálogo por tipo/ramo y criterios del recomendador, rankea y visita los
        sitios oficiales en lotes para verificar liveness, validar que el producto siga
        publicado y (opcional) revisar el folleto. Máx {SCRAPE_TOTAL_MAX} productos por corrida.
      </p>

      {/* Filtros */}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(160px, 1fr))', gap: 12, marginBottom: 12 }}>
        <ScrapeField label="Ramo">
          <select value={ramo} onChange={e => setRamo(e.target.value)} style={scrapeSelStyle}>
            <option value="">Todos los ramos</option>
            {ramos.map(r => <option key={r.k} value={r.k}>{r.l}</option>)}
          </select>
        </ScrapeField>
        <ScrapeField label="Tipo">
          <select value={tipo} onChange={e => setTipo(e.target.value)} style={scrapeSelStyle}>
            <option value="">Todos</option>
            {tipos.map(t => <option key={t} value={t}>{t}</option>)}
          </select>
        </ScrapeField>
        <ScrapeField label="Edad">
          <input type="number" min={0} max={120} value={edad} onChange={e => setEdad(e.target.value)} placeholder="35" style={scrapeSelStyle}/>
        </ScrapeField>
        <ScrapeField label="Presupuesto">
          <select value={presupuestoK} onChange={e => setPresupuestoK(e.target.value)} style={scrapeSelStyle}>
            <option value="">Cualquiera</option>
            {presupuestos.map(p => <option key={p.k} value={p.k}>{p.l}</option>)}
          </select>
        </ScrapeField>
        <ScrapeField label="Coberturas (coma)">
          <input type="text" value={coberturas} onChange={e => setCoberturas(e.target.value)} placeholder="robo, asistencia" style={scrapeSelStyle}/>
        </ScrapeField>
      </div>

      <div className="hstack" style={{ gap: 18, flexWrap:'wrap', marginBottom: 16 }}>
        <label className="hstack" style={{ gap: 6, fontSize: 13, cursor:'pointer' }}>
          <input type="checkbox" checked={includeInactive} onChange={e => setIncludeInactive(e.target.checked)}/>
          Incluir no vigentes (fusionadas / run-off)
        </label>
        <label className="hstack" style={{ gap: 6, fontSize: 13, cursor:'pointer' }}>
          <input type="checkbox" checked={includeBrochure} onChange={e => setIncludeBrochure(e.target.checked)}/>
          Revisar folletos PDF
        </label>
      </div>

      <div className="hstack" style={{ gap: 10, flexWrap:'wrap' }}>
        <button className="btn btn-accent" onClick={run} disabled={running}>
          {running ? `Revisando ${progress.done}/${progress.total}…` : 'Scrapear'}
        </button>
        {rows && rows.length > 0 && (
          <>
            <select value={sortMode} onChange={e => setSortMode(e.target.value)} style={{ ...scrapeSelStyle, width:'auto' }}>
              <option value="score">Ordenar por score</option>
              <option value="estado">Ordenar por estado (rotos primero)</option>
            </select>
            <button className="btn btn-outline" onClick={exportJSON}>Exportar JSON</button>
          </>
        )}
      </div>

      {notice && (
        <div style={{ marginTop: 14, padding:'12px 14px', borderRadius: 10, background:'rgba(232,69,69,0.07)', border:'1px solid rgba(232,69,69,0.25)', fontSize: 13, color:'var(--ink-2)' }}>
          {notice}
        </div>
      )}

      {rows && rows.length === 0 && (
        <div style={{ marginTop: 18, padding: 24, textAlign:'center', color:'var(--muted)', fontSize: 14 }}>
          Ningún producto cumple esos filtros. Probá otro ramo/tipo o quitá coberturas.
        </div>
      )}

      {rows && rows.length > 0 && (
        <>
          {/* Resumen */}
          <div style={{ marginTop: 18, display:'flex', flexWrap:'wrap', gap: 8 }}>
            <SumChip label="Productos"      value={summary.total}/>
            <SumChip label="Sitios OK"      value={summary.ok}      tone="ok"/>
            <SumChip label="Nombre OK"      value={summary.nameOk}  tone="ok"/>
            <SumChip label="Nombre ✗"       value={summary.nameBad} tone={summary.nameBad ? 'warn' : null}/>
            <SumChip label="Rotos"          value={summary.broken}  tone={summary.broken ? 'bad' : null}/>
            <SumChip label="Timeout"        value={summary.timeout} tone={summary.timeout ? 'warn' : null}/>
            <SumChip label="Pendientes"     value={summary.pending}/>
            {summary.avgMs != null && <SumChip label="Latencia media" value={summary.avgMs + 'ms'}/>}
          </div>
          {truncated && (
            <div className="muted" style={{ marginTop: 10, fontSize: 12 }}>
              Se truncó a {SCRAPE_TOTAL_MAX} productos. Afiná los filtros (ramo/tipo) para cubrir el resto.
            </div>
          )}

          <div style={{ marginTop: 14, display:'grid', gap: 10 }}>
            {sorted.map(row => <ScrapeRow key={row.key} row={row}/>)}
          </div>
        </>
      )}
    </div>
  );
}

function SumChip({ label, value, tone }) {
  const colors = { ok:['rgba(0,200,150,0.14)','#00875A'], bad:['rgba(232,69,69,0.12)','#E84545'], warn:['rgba(255,170,0,0.16)','#A66A00'] };
  const [bg, color] = colors[tone] || ['var(--bg-warm)', 'var(--ink-2)'];
  return (
    <div style={{ background: bg, color, borderRadius: 10, padding:'8px 12px', minWidth: 84 }}>
      <div style={{ fontSize: 18, fontWeight: 800, lineHeight: 1 }}>{value}</div>
      <div className="mono" style={{ fontSize: 9, letterSpacing:'0.06em', opacity: 0.85, marginTop: 2, textTransform:'uppercase' }}>{label}</div>
    </div>
  );
}

function ScrapeField({ label, children }) {
  return (
    <label style={{ display:'block' }}>
      <div className="eyebrow" style={{ marginBottom: 6 }}>{label}</div>
      {children}
    </label>
  );
}

const scrapeSelStyle = {
  width:'100%', padding:'10px 12px', fontSize: 14, fontFamily:'inherit',
  border:'1.5px solid var(--line)', borderRadius: 10, outline:'none',
  background:'var(--bg)', color:'var(--ink)', boxSizing:'border-box',
};

function ScrapeStatusBadge({ scrape }) {
  if (!scrape) {
    return <span style={scrapeBadge('var(--bg-warm)', 'var(--muted)')}>sin verificar</span>;
  }
  if (scrape.ok) {
    return <span style={scrapeBadge('rgba(0,200,150,0.14)', '#00875A')}>{scrape.status} OK · {scrape.ms}ms</span>;
  }
  if (scrape.error === 'timeout') {
    return <span style={scrapeBadge('rgba(255,170,0,0.16)', '#A66A00')}>timeout</span>;
  }
  return <span style={scrapeBadge('rgba(232,69,69,0.12)', '#E84545')}>{scrape.status ? `HTTP ${scrape.status}` : 'sin respuesta'}</span>;
}

function NameBadge({ name }) {
  if (!name) return null;
  const ok = name.matched;
  return (
    <span style={scrapeBadge(ok ? 'rgba(0,200,150,0.14)' : 'rgba(255,170,0,0.16)', ok ? '#00875A' : '#A66A00')}>
      nombre {ok ? '✓' : '✗'} {name.hits}/{name.total}
    </span>
  );
}

function scrapeBadge(bg, color) {
  return { fontSize: 11, fontWeight: 700, padding:'3px 9px', borderRadius: 999, background: bg, color, whiteSpace:'nowrap' };
}

function ScrapeRow({ row }) {
  const { rank, product: p, company: c, score, inactive, site, brochure } = row;
  const Logo = (typeof CompanyLogo !== 'undefined') ? CompanyLogo : null;
  const scrape = site ? site.scrape : null;
  return (
    <div className="card" style={{ padding: 14, border:'1px solid var(--line)' }}>
      <div style={{ display:'flex', alignItems:'center', gap: 12 }}>
        <div style={{ width: 28, height: 28, borderRadius:'50%', background:'var(--bg-warm)', color:'var(--ink-2)', display:'grid', placeItems:'center', fontWeight: 800, fontSize: 13, fontFamily:'JetBrains Mono, monospace', flexShrink: 0 }}>{rank}</div>
        {Logo && <Logo company={c} size={40}/>}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="mono" style={{ fontSize: 10, color:'var(--muted)', textTransform:'uppercase', letterSpacing:'0.05em' }}>
            {c.name} · {c.tipo}{inactive ? ' · no vigente' : ''}
          </div>
          <div style={{ fontWeight: 800, fontSize: 15, letterSpacing:'-0.02em' }}>{p.nombre}</div>
          {site
            ? <a href={site.url} target="_blank" rel="noopener noreferrer" style={{ fontSize: 11, color:'var(--accent)', textDecoration:'none', wordBreak:'break-all' }}>{site.url}</a>
            : <span className="muted" style={{ fontSize: 11 }}>sin URL en el catálogo</span>}
        </div>
        <div style={{ textAlign:'right', flexShrink: 0 }}>
          <div className="mono muted" style={{ fontSize: 10 }}>SCORE</div>
          <div style={{ fontSize: 18, fontWeight: 800 }}>{score == null ? '—' : score}</div>
        </div>
        <div style={{ display:'flex', flexDirection:'column', gap: 4, alignItems:'flex-end', flexShrink: 0 }}>
          <ScrapeStatusBadge scrape={scrape}/>
          {scrape && scrape.ok && <NameBadge name={scrape.name}/>}
        </div>
      </div>

      {scrape && scrape.ok && scrape.content && (scrape.content.title || scrape.content.description) && (
        <div style={{ marginTop: 10, paddingTop: 10, borderTop:'1px solid var(--line)' }}>
          {scrape.content.title && <div style={{ fontSize: 13, fontWeight: 700 }}>{scrape.content.title}</div>}
          {scrape.content.description && <div className="muted" style={{ fontSize: 12, marginTop: 2, lineHeight: 1.45 }}>{scrape.content.description}</div>}
          {scrape.finalUrl && <div className="mono muted" style={{ fontSize: 10, marginTop: 4 }}>→ redirige a {scrape.finalUrl}</div>}
        </div>
      )}

      {brochure && (
        <div className="hstack" style={{ marginTop: 8, gap: 8, fontSize: 12 }}>
          <span className="mono muted" style={{ fontSize: 10 }}>FOLLETO</span>
          <a href={brochure.url} target="_blank" rel="noopener noreferrer" style={{ color:'var(--accent)', textDecoration:'none', wordBreak:'break-all', flex: 1 }}>{brochure.url}</a>
          <ScrapeStatusBadge scrape={brochure.scrape}/>
        </div>
      )}
    </div>
  );
}

// Gráfico de barras SVG nativo (sin libs). Recibe [{day, signups}] ordenado
// desc por día (más reciente primero); lo invertimos para mostrar
// cronológico izq → der.
function SignupsChart({ points }) {
  const data = [...(points || [])].reverse();
  if (!data.length) {
    return <div style={{ padding: 30, textAlign:'center', color:'var(--muted)', fontSize: 13 }}>Sin signups todavía.</div>;
  }

  const W   = 800;
  const H   = 220;
  const pad = { l: 36, r: 12, t: 18, b: 30 };
  const innerW = W - pad.l - pad.r;
  const innerH = H - pad.t - pad.b;

  const max = Math.max(1, ...data.map(d => d.signups));
  const bw  = innerW / data.length;

  return (
    <div style={{ overflowX: 'auto' }}>
      <svg viewBox={`0 0 ${W} ${H}`} style={{ width:'100%', height:'auto', minWidth: 320 }}>
        {/* Y axis ticks */}
        {[0, 0.5, 1].map(t => {
          const y = pad.t + innerH * (1 - t);
          const label = Math.round(max * t);
          return (
            <g key={t}>
              <line x1={pad.l} x2={W - pad.r} y1={y} y2={y} stroke="rgba(0,0,0,0.06)"/>
              <text x={pad.l - 8} y={y + 4} textAnchor="end" fontSize="10" fill="var(--muted)" fontFamily="JetBrains Mono, monospace">{label}</text>
            </g>
          );
        })}

        {/* Bars */}
        {data.map((d, i) => {
          const h = (d.signups / max) * innerH;
          const x = pad.l + i * bw + bw * 0.15;
          const w = bw * 0.7;
          const y = pad.t + innerH - h;
          return (
            <g key={d.day}>
              <rect x={x} y={y} width={w} height={Math.max(h, 1)} fill="var(--accent)" rx={2}/>
              <title>{d.day}: {d.signups}</title>
            </g>
          );
        })}

        {/* X labels (cada N para no saturar) */}
        {data.map((d, i) => {
          const showEvery = Math.max(1, Math.ceil(data.length / 8));
          if (i % showEvery !== 0 && i !== data.length - 1) return null;
          const x = pad.l + i * bw + bw / 2;
          const label = (d.day || '').slice(5); // MM-DD
          return (
            <text key={d.day + '-x'} x={x} y={H - 10} textAnchor="middle" fontSize="9" fill="var(--muted)" fontFamily="JetBrains Mono, monospace">
              {label}
            </text>
          );
        })}
      </svg>
    </div>
  );
}

window.AdminPage = AdminPage;
