// albor · modules.jsx
// Vistas de los 17 módulos del CRM. Diseño light-Notion (CRM Gobierno) conectado al backend real.
// Cada vista se registra en window.ALBOR_MODULES[slug] y el Shell la renderiza por módulo activo.
//
// Datos: Supabase via window.sb. Decisión 14.2: paginación obligatoria, NUNCA SELECT *.

window.ALBOR_MODULES = {};
const _sb = () => window.sb;
const uS = React.useState;
const uE = React.useEffect;
const uM = React.useMemo;
const uC = React.useCallback;

// ============================================================
// Componente reutilizable: DataTable con búsqueda + paginación + modal detalle
// ============================================================
function DataTable({ titulo, descripcion, tabla, columnas, filtroExtra, ordenDefault, searchableColumns, renderDetalle, accionesGlobales, pageSize = 50 }) {
  const [filas, setFilas] = uS([]);
  const [cargando, setCargando] = uS(true);
  const [error, setError] = uS(null);
  const [pagina, setPagina] = uS(0);
  const [query, setQuery] = uS('');
  const [seleccion, setSeleccion] = uS(null);
  const [refresco, setRefresco] = uS(0);

  uE(() => {
    let mounted = true;
    (async () => {
      setCargando(true); setError(null);
      let q = window.sb.from(tabla).select(columnas.map(c => c.k).join(','));
      if (filtroExtra) q = filtroExtra(q);
      if (query.trim() && searchableColumns?.length) {
        const term = query.trim();
        const ors = searchableColumns.map(col => `${col}.ilike.%${term}%`).join(',');
        q = q.or(ors);
      }
      if (ordenDefault) q = q.order(ordenDefault.col, { ascending: ordenDefault.asc !== false });
      q = q.range(pagina * pageSize, (pagina + 1) * pageSize - 1);
      const { data, error } = await q;
      if (!mounted) return;
      if (error) setError(error.message);
      else setFilas(data ?? []);
      setCargando(false);
    })();
    return () => { mounted = false; };
  }, [pagina, refresco, query]);

  const recargar = () => setRefresco(r => r + 1);

  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">{titulo}</h1>
          {descripcion && <p className="page__desc">{descripcion}</p>}
        </div>
        <div className="page__actions">
          {accionesGlobales?.(recargar)}
          <button className="btn btn--ghost btn--sm" onClick={recargar} title="Refrescar">
            <Icon name="RefreshCw" size={13} />
          </button>
        </div>
      </div>

      {searchableColumns?.length > 0 && (
        <div className="table-search" style={{ maxWidth: 320, marginBottom: 12 }}>
          <Icon name="Search" size={13} />
          <input
            placeholder="Buscar…"
            value={query}
            onChange={(e) => { setQuery(e.target.value); setPagina(0); }}
          />
        </div>
      )}

      {error && <div className="login-error">{error}</div>}
      {cargando && <div className="muted">Cargando…</div>}
      {!cargando && !error && filas.length === 0 && (
        <div className="empty-state">
          <div className="empty-state__icon"><Icon name="Inbox" size={26} /></div>
          <div className="empty-state__title">Sin datos todavía</div>
          <div className="empty-state__desc">Esta tabla está vacía. Cargá registros desde la API, el portal o creación manual cuando esté disponible.</div>
        </div>
      )}
      {!cargando && !error && filas.length > 0 && (
        <div className="albor-tabla-wrap">
          <table className="albor-tabla">
            <thead><tr>{columnas.map(c => <th key={c.k} style={c.thStyle}>{c.label}</th>)}</tr></thead>
            <tbody>
              {filas.map((f, i) => (
                <tr key={f.id ?? i} onClick={() => renderDetalle && setSeleccion(f)} className={seleccion?.id === f.id ? 'is-selected' : ''}>
                  {columnas.map(c => (
                    <td key={c.k} style={c.tdStyle}>
                      {c.fmt ? c.fmt(f[c.k], f) : (f[c.k] ?? '—')}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
      <div className="albor-paginacion">
        <button className="btn btn--ghost btn--sm" disabled={pagina === 0} onClick={() => setPagina(p => Math.max(0, p - 1))}>
          <Icon name="ChevronLeft" size={13} /> Anterior
        </button>
        <span className="muted">Página {pagina + 1} · {filas.length} fila(s)</span>
        <button className="btn btn--ghost btn--sm" disabled={filas.length < pageSize} onClick={() => setPagina(p => p + 1)}>
          Siguiente <Icon name="ChevronRight" size={13} />
        </button>
      </div>

      {seleccion && renderDetalle && (
        <div className="modal-overlay" onClick={() => setSeleccion(null)}>
          <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 720 }}>
            <div className="modal__header">
              <h2 className="modal__title">Detalle</h2>
              <button className="btn btn--ghost btn--sm" onClick={() => setSeleccion(null)}><Icon name="X" size={14} /></button>
            </div>
            {renderDetalle(seleccion, recargar, () => setSeleccion(null))}
          </div>
        </div>
      )}
    </div>
  );
}

function KV({ k, v }) {
  return <div className="kv"><div className="kv__k">{k}</div><div className="kv__v">{v ?? '—'}</div></div>;
}

// StatCard estilo zip
function StatCard({ label, value, suffix, iconColor = 'blue', icon, trend }) {
  return (
    <div className="stat">
      <div className="stat__head">
        <span className="stat__label">{label}</span>
        <div className={`stat__icon stat__icon--${iconColor}`}><Icon name={icon} size={14} /></div>
      </div>
      <div className="stat__value">
        {value}
        {suffix && <span className="stat__value-suffix">{suffix}</span>}
      </div>
      {trend && (
        <div className={`stat__trend stat__trend--${trend.dir}`}>
          <Icon name={trend.dir === 'up' ? 'TrendingUp' : 'TrendingDown'} size={12} />
          <span>{trend.val}</span>
          {trend.note && <span className="stat__trend-label">· {trend.note}</span>}
        </div>
      )}
    </div>
  );
}

// MetaRow
function MetaRow({ label, val, max, color = 'blue' }) {
  const pct = max > 0 ? (val / max) * 100 : 0;
  const colors = { blue: '#3b82f6', emerald: '#10b981', amber: '#f59e0b', red: '#ef4444' };
  return (
    <div style={{ marginBottom: 12 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 5 }}>
        <span>{label}</span>
        <span className="mono" style={{ color: 'var(--fg-muted)' }}>{val}/{max}</span>
      </div>
      <div style={{ height: 5, background: 'var(--border)', borderRadius: 999, overflow: 'hidden' }}>
        <div style={{ width: `${Math.min(100, pct)}%`, height: '100%', background: colors[color], borderRadius: 999, transition: 'width 0.4s' }} />
      </div>
    </div>
  );
}

// ============================================================
// DASHBOARD
// ============================================================
function VistaDashboard({ config }) {
  const [stats, setStats] = uS(null);
  const [recientes, setRecientes] = uS([]);

  uE(() => {
    (async () => {
      const cnt = async (tabla, filtro) => {
        let q = window.sb.from(tabla).select('id', { count: 'exact', head: true });
        if (filtro) q = filtro(q);
        const { count } = await q;
        return count ?? 0;
      };
      const [cConcursos, cCotizaciones, cFacturas, cGarantias, cNogs] = await Promise.all([
        cnt('concursos', q => q.in('estado', ['iniciado', 'preparando', 'oferta_lista', 'enviada'])),
        cnt('cotizaciones', q => q.gte('fecha', new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10))),
        cnt('facturas', q => q.eq('estado', 'pagada')),
        cnt('garantias', q => q.eq('estado', 'activa')),
        cnt('eventos_nog', q => q.eq('estado_local', 'nuevo')),
      ]);
      setStats({ cConcursos, cCotizaciones, cFacturas, cGarantias, cNogs });

      const { data: rec } = await window.sb.from('eventos_nog')
        .select('id,nog,titulo,comprador_nombre,monto_estimado,estado_local,fecha_cierre')
        .order('release_date', { ascending: false, nullsFirst: false })
        .limit(6);
      setRecientes(rec ?? []);
    })();
  }, []);

  const nombreOrg = config?.org?.nombre_legal || config?.org?.nombre_clave || '—';

  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Dashboard</h1>
          <p className="page__desc">Vista general de {nombreOrg}</p>
        </div>
        <div className="page__actions">
          <button className="btn btn--outline btn--sm"><Icon name="BarChart3" size={13} /> Estadísticas</button>
          <button className="btn btn--primary btn--sm"><Icon name="Plus" size={13} /> Nuevo concurso</button>
        </div>
      </div>

      <div className="stats">
        <StatCard label="Concursos activos" value={stats?.cConcursos ?? '…'} suffix="abiertos" iconColor="blue" icon="Trophy" />
        <StatCard label="Cotizaciones (30d)" value={stats?.cCotizaciones ?? '…'} suffix="emitidas" iconColor="amber" icon="FileText" />
        <StatCard label="Facturas pagadas" value={stats?.cFacturas ?? '…'} iconColor="emerald" icon="Receipt" />
        <StatCard label="Garantías activas" value={stats?.cGarantias ?? '…'} suffix="vigentes" iconColor="violet" icon="ShieldCheck" />
      </div>

      <div className="dash-grid">
        <div className="card">
          <div className="card__header">
            <h2 className="card__title">NOGs recientes capturados</h2>
            <span className="muted">{stats?.cNogs ?? 0} nuevos</span>
          </div>
          <div className="card__body card__body--flush">
            {recientes.length === 0 ? (
              <div style={{ padding: 24, color: 'var(--fg-muted)', textAlign: 'center', fontSize: 13 }}>
                Sin NOGs todavía. Andá a Prefiltrado y presioná "Consultar GuateCompras".
              </div>
            ) : recientes.map(r => (
              <div key={r.id} className="listrow">
                <div className="listrow__icon">{(r.comprador_nombre || '?').slice(0, 3).toUpperCase()}</div>
                <div className="listrow__main">
                  <div className="listrow__title">{r.titulo || '(sin título)'}</div>
                  <div className="listrow__sub">
                    <span className="mono">NOG {r.nog}</span>
                    <span>·</span>
                    <span>{r.comprador_nombre || '—'}</span>
                  </div>
                </div>
                <div className="listrow__monto">{window.fmtQ(r.monto_estimado)}</div>
                <StatusBadge estado={r.estado_local} />
              </div>
            ))}
          </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div className="card">
            <div className="card__header"><h2 className="card__title">Acciones rápidas</h2></div>
            <div className="card__body">
              <button className="action">
                <div className="action__icon"><Icon name="Sparkles" size={15} /></div>
                <div>
                  <div className="action__title">Analizar PDF de licitación</div>
                  <div className="action__sub">IA extrae datos automáticamente</div>
                </div>
                <span className="action__badge">IA</span>
              </button>
              <button className="action">
                <div className="action__icon"><Icon name="Link2" size={15} /></div>
                <div>
                  <div className="action__title">Desde GuateCompras</div>
                  <div className="action__sub">Pegá el link del evento</div>
                </div>
              </button>
              <button className="action">
                <div className="action__icon"><Icon name="Plus" size={15} /></div>
                <div>
                  <div className="action__title">Nuevo concurso manual</div>
                  <div className="action__sub">Formulario completo</div>
                </div>
              </button>
            </div>
          </div>

          <div className="card">
            <div className="card__header"><h2 className="card__title">Pipeline semanal</h2></div>
            <div className="card__body" style={{ paddingTop: 4 }}>
              <MetaRow label="Cierres de concurso" val={stats?.cConcursos ?? 0} max={Math.max(stats?.cConcursos ?? 0, 10)} color="blue" />
              <MetaRow label="Cotizaciones por enviar" val={stats?.cCotizaciones ?? 0} max={Math.max(stats?.cCotizaciones ?? 0, 20)} color="amber" />
              <MetaRow label="Garantías activas" val={stats?.cGarantias ?? 0} max={Math.max(stats?.cGarantias ?? 0, 30)} color="emerald" />
              <MetaRow label="NOGs nuevos" val={stats?.cNogs ?? 0} max={Math.max(stats?.cNogs ?? 0, 50)} color="red" />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
window.ALBOR_MODULES['dashboard'] = VistaDashboard;

// ============================================================
// PREFILTRADO NOG v3 — panel Criterios + 5 tabs canónicas + Modal Preview v3
// Basado en el aprendizaje del chat de diseño (schema_evento_v3 + prefiltrado-modals-v3).
// Conectado al backend real eventos_nog (con migración 024 aplicada).
// ============================================================

// Catálogo de badges del adapter v3
const BADGES_CATALOGO = {
  captura_directa: { label: 'CAPTURA DIRECTA', bg: '#d1fae5', fg: '#065f46', desc: 'UNSPSC 43xx — cómputo core' },
  alias_validado:  { label: 'ALIAS VALIDADO',  bg: '#dbeafe', fg: '#1e40af', desc: 'Familia 44/26/39 + keyword del rubro' },
  rescatado:       { label: 'RESCATADO',       bg: '#fef3c7', fg: '#92400e', desc: 'Familia médica/excluida pero con keyword cómputo' },
  huerfano:        { label: 'HUÉRFANO',        bg: '#fee2e2', fg: '#7f1d1d', desc: 'Solo keyword, sin UNSPSC core' },
};

function BadgeChip({ slug }) {
  const b = BADGES_CATALOGO[slug];
  if (!b) return null;
  return (
    <span title={b.desc} style={{
      fontSize: 10, padding: '2px 7px', borderRadius: 4, fontWeight: 500,
      background: b.bg, color: b.fg, fontFamily: 'ui-monospace,monospace',
    }}>{b.label}</span>
  );
}

// Densidades de card (decisión 3.10)
const DENSIDADES = ['compacta', 'comoda', 'espaciada'];

function VistaPrefiltrado({ config }) {
  const perfil = config?.perfil ?? {};
  // Filtros 3 capas: capa 1 sistema (inmutable), capa 2 admin (perfil), capa 3 analista (UI)
  const filtrosSistema = perfil.filtros_sistema ?? {};
  const keywordsBoostPerfil = perfil.keywords_boost ?? [];
  const keywordsExcluirPerfil = perfil.keywords_excluir ?? [];

  const [tab, setTab] = uS('bandeja');
  const [filas, setFilas] = uS([]);
  const [cargando, setCargando] = uS(true);
  const [seleccion, setSeleccion] = uS(null);
  const [consultando, setConsultando] = uS(false);
  const [stats, setStats] = uS({});
  const [refresco, setRefresco] = uS(0);
  const [query, setQuery] = uS('');
  const [densidad, setDensidad] = uS('comoda');
  const [criteriosAbiertos, setCriteriosAbiertos] = uS(true);

  // Capa 3 analista (mutable en UI)
  const [keywordsBoostExtra, setKeywordsBoostExtra] = uS([]);     // chips adicionales
  const [keywordsExcluirExtra, setKeywordsExcluirExtra] = uS([]);
  const [filtroBadge, setFiltroBadge] = uS(null);                  // 'captura_directa' | 'alias_validado' | etc.
  const [filtroCierre, setFiltroCierre] = uS('todos');             // 'hoy' | '24h' | '48h' | '7d' | 'todos'

  const ahora = new Date().toISOString();

  // ============================================================
  // Tabs canónicas (decisión 3.7)
  // ============================================================
  const construirQueryTab = (qBase, tabActiva) => {
    let q = qBase;
    if (tabActiva === 'bandeja') {
      q = q.eq('estado_local', 'nuevo').eq('estado_portal', 'active');
    } else if (tabActiva === 'cierran_pronto') {
      q = q.eq('estado_local', 'nuevo').eq('estado_portal', 'active')
           .gte('fecha_cierre', ahora)
           .lte('fecha_cierre', new Date(Date.now() + 48 * 3600 * 1000).toISOString());
    } else if (tabActiva === 'revision') {
      q = q.eq('estado_local', 'en_revision');
    } else if (tabActiva === 'descartados') {
      q = q.eq('estado_local', 'descartado');
    } else if (tabActiva === 'cerrados_recientes') {
      q = q.neq('estado_local', 'promovido').neq('estado_portal', 'active');
    }
    return q;
  };

  const aplicarFiltrosAnalista = (q) => {
    if (filtroBadge) q = q.eq('badge', filtroBadge);
    if (filtroCierre !== 'todos') {
      const horas = { hoy: 24, '24h': 24, '48h': 48, '7d': 168 }[filtroCierre] ?? 0;
      if (horas > 0) {
        q = q.gte('fecha_cierre', ahora).lte('fecha_cierre', new Date(Date.now() + horas * 3600 * 1000).toISOString());
      }
    }
    if (query.trim()) {
      q = q.or(`nog.ilike.%${query}%,titulo.ilike.%${query}%,comprador_nombre.ilike.%${query}%`);
    }
    return q;
  };

  uE(() => {
    let mounted = true;
    (async () => {
      setCargando(true);
      let q = window.sb.from('eventos_nog_v3')
        .select('id,nog,titulo,comprador_nombre,uc_nombre,monto_estimado,fecha_cierre,fecha_apertura,estado_local,estado_portal,score_ia,release_date,matching,unspsc,badge,keywords_matched,n_items,n_documentos,n_ofertas,horas_hasta_cierre,cierre_source,items_json');
      q = construirQueryTab(q, tab);
      q = aplicarFiltrosAnalista(q);
      q = q.order('fecha_cierre', { ascending: true, nullsFirst: false }).limit(200);
      const { data, error } = await q;
      if (!mounted) return;
      if (error) console.warn('prefiltrado select', error);
      setFilas(data ?? []);
      setCargando(false);
    })();
    return () => { mounted = false; };
  }, [tab, refresco, query, filtroBadge, filtroCierre]);

  uE(() => {
    (async () => {
      const tabs = ['bandeja', 'cierran_pronto', 'revision', 'descartados', 'cerrados_recientes'];
      const counters = {};
      for (const t of tabs) {
        let q = window.sb.from('eventos_nog').select('id', { count: 'exact', head: true });
        q = construirQueryTab(q, t);
        const { count } = await q;
        counters[t] = count ?? 0;
      }
      setStats(counters);
    })();
  }, [refresco]);

  // Métricas resumen (decisión 3.10): 4 contadores arriba
  const metricas = uM(() => {
    if (!filas?.length) return { recomendados: 0, aRevisar: 0, descartados: stats.descartados ?? 0, valor: 0 };
    const recomendados = filas.filter(f => f.badge === 'captura_directa' || f.badge === 'alias_validado').length;
    const aRevisar = filas.filter(f => f.estado_local === 'en_revision' || f.score_ia == null).length;
    const valor = filas.reduce((s, f) => s + Number(f.monto_estimado || 0), 0);
    return { recomendados, aRevisar, descartados: stats.descartados ?? 0, valor };
  }, [filas, stats]);

  const cambiarEstado = async (id, nuevo) => {
    await window.sb.from('eventos_nog').update({ estado_local: nuevo }).eq('id', id);
    setSeleccion(null);
    setRefresco(r => r + 1);
  };

  const consultar = async () => {
    setConsultando(true);
    try {
      const { data, error } = await window.sb.functions.invoke('prefiltrado-consultar', {
        body: { modo: 'full', max_paginas: 5 },
      });
      if (error) throw error;
      if (!data?.ok) throw new Error(data?.error || 'falló');
      alert(`Consulta OK\nLeídos: ${data.leidos}\nNuevos: ${data.nuevos}\nActualizados: ${data.actualizados}\nDescartados: ${data.descartados}`);
    } catch (e) {
      alert('Error al consultar OCDS: ' + (e?.message ?? e) + '\n\nNota: la Edge Function prefiltrado-consultar debe estar deployada (supabase functions deploy prefiltrado-consultar).');
    } finally {
      setConsultando(false);
      setRefresco(r => r + 1);
    }
  };

  const analizar = async (nog) => {
    try {
      const { data, error } = await window.sb.functions.invoke('analizar-nog-ia', { body: { nog, tipo_analisis: 'preview_corto' } });
      if (error) throw error;
      alert('Análisis IA listo. Score: ' + (data?.resultado?.puntaje ?? 'n/d'));
      setRefresco(r => r + 1);
      setSeleccion(null);
    } catch (e) {
      alert('Error al analizar: ' + (e?.message ?? e));
    }
  };

  const toggleKeyword = (lista, setLista, kw) => {
    if (lista.includes(kw)) setLista(lista.filter(x => x !== kw));
    else setLista([...lista, kw]);
    setRefresco(r => r + 1);
  };

  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Prefiltrado</h1>
          <p className="page__desc">
            Ingesta diaria de eventos · scoring automático según tus criterios ·
            <span className="mono" style={{ marginLeft: 6 }}>UNSPSC {(filtrosSistema.unspsc_familias ?? []).join('/')} · Art.43b</span>
          </p>
        </div>
        <div className="page__actions">
          <button className="btn btn--outline btn--sm" onClick={() => setCriteriosAbiertos(c => !c)}>
            <Icon name="SlidersHorizontal" size={13} /> {criteriosAbiertos ? 'Ocultar criterios' : 'Mostrar criterios'}
          </button>
          <button className="btn btn--primary btn--sm" disabled={consultando} onClick={consultar}>
            {consultando ? <><Icon name="RefreshCw" size={13} /> Consultando…</> : <><Icon name="Cloud" size={13} /> Consultar GuateCompras</>}
          </button>
        </div>
      </div>

      {/* Strip de métricas resumen */}
      <div className="pf-metrics" style={{
        display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 16,
        padding: 14, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
      }}>
        <div>
          <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px' }}>Recomendados</div>
          <div style={{ fontSize: 22, fontWeight: 600, color: '#059669' }}>{metricas.recomendados}</div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px' }}>A revisar</div>
          <div style={{ fontSize: 22, fontWeight: 600, color: '#d97706' }}>{metricas.aRevisar}</div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px' }}>Descartados</div>
          <div style={{ fontSize: 22, fontWeight: 600, color: 'var(--fg-muted)' }}>{metricas.descartados}</div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px' }}>Valor estimado</div>
          <div style={{ fontSize: 22, fontWeight: 600 }}>{window.fmtQ(metricas.valor)}</div>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: criteriosAbiertos ? '1fr 280px' : '1fr', gap: 16 }}>
        {/* Columna principal: tabs + tabla */}
        <div>
          <div className="tabs-bar">
            {[
              ['bandeja',            'Bandeja'],
              ['cierran_pronto',     'Cierran pronto'],
              ['revision',           'Revisión'],
              ['descartados',        'Descartados'],
              ['cerrados_recientes', 'Cerrados recientes'],
            ].map(([k, l]) => (
              <button key={k} className={'tabs-bar__tab' + (tab === k ? ' is-active' : '')} onClick={() => setTab(k)}>
                {l} {stats[k] != null && <span className="count">{stats[k]}</span>}
              </button>
            ))}
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' }}>
              <select value={densidad} onChange={e => setDensidad(e.target.value)} style={{
                fontSize: 12, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--surface)',
              }}>
                {DENSIDADES.map(d => <option key={d} value={d}>{d}</option>)}
              </select>
              <div className="table-search" style={{ maxWidth: 260 }}>
                <Icon name="Search" size={13} />
                <input placeholder="NOG / título / comprador" value={query} onChange={e => setQuery(e.target.value)} />
              </div>
            </div>
          </div>

          {cargando && <div className="muted">Cargando…</div>}
          {!cargando && filas.length === 0 && (
            <div className="empty-state">
              <div className="empty-state__icon"><Icon name="Inbox" size={26} /></div>
              <div className="empty-state__title">No hay NOGs en esta tab</div>
              <div className="empty-state__desc">
                Si es la primera vez, presioná "Consultar GuateCompras" arriba para una pasada inicial.
                {tab === 'cierran_pronto' && <><br/>O probá la tab Bandeja: ahí están los nuevos sin filtrar por urgencia.</>}
              </div>
            </div>
          )}

          {!cargando && filas.length > 0 && (
            <div style={{ display: 'grid', gridTemplateColumns: densidad === 'compacta' ? '1fr 1fr 1fr' : densidad === 'espaciada' ? '1fr' : '1fr 1fr', gap: 10 }}>
              {filas.map(f => <CardNog key={f.id} f={f} densidad={densidad} onClick={() => setSeleccion(f)} />)}
            </div>
          )}
        </div>

        {/* Columna lateral: panel Criterios */}
        {criteriosAbiertos && (
          <aside style={{
            background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
            padding: 14, height: 'fit-content', position: 'sticky', top: 8,
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 600, fontSize: 13 }}>
                <Icon name="SlidersHorizontal" size={13} /> Criterios
              </div>
              <button className="btn btn--ghost btn--sm" onClick={() => { setKeywordsBoostExtra([]); setKeywordsExcluirExtra([]); setFiltroBadge(null); setFiltroCierre('todos'); }} style={{ fontSize: 11 }}>
                Reset
              </button>
            </div>

            {/* Capa 1 sistema (inmutable, chips informativos) */}
            <div style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 6 }}>Sistema (fijo)</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                <span style={chipFijoStyle}>Vigente</span>
                <span style={chipFijoStyle}>Art.43b</span>
                <span style={chipFijoStyle}>UNSPSC {(filtrosSistema.unspsc_familias ?? ['43']).join('/')}xx</span>
                <span style={chipFijoStyle}>Goods</span>
                <span style={chipFijoStyle}>Electrónica</span>
              </div>
            </div>

            {/* Capa 3: badge */}
            <div style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 6 }}>Badge de captura</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                {Object.entries(BADGES_CATALOGO).map(([slug, b]) => (
                  <button key={slug} onClick={() => setFiltroBadge(filtroBadge === slug ? null : slug)} style={{
                    ...chipToggleStyle,
                    background: filtroBadge === slug ? b.bg : 'transparent',
                    color: filtroBadge === slug ? b.fg : 'var(--fg-muted)',
                    borderColor: filtroBadge === slug ? b.fg : 'var(--border)',
                    fontWeight: filtroBadge === slug ? 500 : 400,
                  }}>{b.label}</button>
                ))}
              </div>
            </div>

            {/* Capa 3: ventana de cierre */}
            <div style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 6 }}>Ventana de cierre</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                {[['24h', '24h'], ['48h', '48h'], ['7d', '7 días'], ['todos', 'Todos']].map(([k, l]) => (
                  <button key={k} onClick={() => setFiltroCierre(k)} style={{
                    ...chipToggleStyle,
                    background: filtroCierre === k ? 'var(--primary)' : 'transparent',
                    color: filtroCierre === k ? '#fff' : 'var(--fg-muted)',
                    borderColor: filtroCierre === k ? 'var(--primary)' : 'var(--border)',
                  }}>{l}</button>
                ))}
              </div>
            </div>

            {/* Capa 3: keywords boost del perfil */}
            <div style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 6 }}>
                Keywords boost ({keywordsBoostPerfil.length})
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, maxHeight: 200, overflowY: 'auto' }}>
                {keywordsBoostPerfil.slice(0, 25).map(kw => (
                  <span key={kw} style={{ ...chipFijoStyle, background: '#eef1fe', color: '#1e40af' }}>{kw}</span>
                ))}
                {keywordsBoostPerfil.length > 25 && <span style={chipFijoStyle}>+{keywordsBoostPerfil.length - 25} más</span>}
              </div>
            </div>

            <div>
              <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 6 }}>
                Keywords excluir ({keywordsExcluirPerfil.length})
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, maxHeight: 120, overflowY: 'auto' }}>
                {keywordsExcluirPerfil.slice(0, 12).map(kw => (
                  <span key={kw} style={{ ...chipFijoStyle, background: '#fef2f2', color: '#7f1d1d' }}>{kw}</span>
                ))}
                {keywordsExcluirPerfil.length > 12 && <span style={chipFijoStyle}>+{keywordsExcluirPerfil.length - 12}</span>}
              </div>
            </div>

            <div style={{ marginTop: 14, paddingTop: 10, borderTop: '1px solid var(--border)', fontSize: 10.5, color: 'var(--fg-muted)', lineHeight: 1.5 }}>
              Las keywords boost y excluir se configuran en el perfil de rubro (Centro de Perfiles).
            </div>
          </aside>
        )}
      </div>

      {seleccion && <PrefiltradoPreviewModalV3 event={seleccion} onClose={() => setSeleccion(null)} onPromover={() => cambiarEstado(seleccion.id, 'promovido')} onDescartar={() => cambiarEstado(seleccion.id, 'descartado')} onRevisar={() => cambiarEstado(seleccion.id, 'en_revision')} onAnalizar={() => analizar(seleccion.nog)} />}
    </div>
  );
}
window.ALBOR_MODULES['prefiltrado'] = VistaPrefiltrado;

const chipFijoStyle = {
  fontSize: 10, padding: '2px 7px', borderRadius: 4,
  background: 'var(--bg)', color: 'var(--fg-muted)',
  border: '1px solid var(--border)', fontFamily: 'ui-monospace, monospace',
};
const chipToggleStyle = {
  fontSize: 10, padding: '3px 8px', borderRadius: 4,
  cursor: 'pointer', border: '1px solid var(--border)',
  background: 'transparent', color: 'var(--fg-muted)',
  fontFamily: 'ui-monospace, monospace',
};

// ============================================================
// Card de un NOG (estilo Notion, 3 densidades)
// ============================================================
function CardNog({ f, densidad, onClick }) {
  const horas = f.horas_hasta_cierre;
  const urgent = horas != null && horas >= 0 && horas <= 48;
  const cierreLabel = horas == null ? '—' :
    horas < 0 ? `cerrado hace ${Math.round(Math.abs(horas))}h` :
    horas < 24 ? `${Math.round(horas)}h` :
    `${Math.round(horas / 24)}d`;

  return (
    <div onClick={onClick} style={{
      background: 'var(--surface)',
      border: '1px solid var(--border)',
      borderRadius: 8, padding: densidad === 'compacta' ? 10 : 14,
      cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: densidad === 'compacta' ? 6 : 8,
      transition: 'box-shadow 0.12s, border-color 0.12s',
    }}
    onMouseEnter={e => e.currentTarget.style.boxShadow = '0 2px 8px rgba(10,10,30,0.06)'}
    onMouseLeave={e => e.currentTarget.style.boxShadow = 'none'}
    >
      <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
        <span className="mono" style={{ fontSize: 11, color: 'var(--fg-muted)' }}>NOG {f.nog}</span>
        {f.badge && <BadgeChip slug={f.badge} />}
        {urgent && (
          <span style={{ fontSize: 10, padding: '2px 6px', borderRadius: 3, background: '#fef3c7', color: '#92400e' }}>
            <Icon name="Clock" size={9} /> {cierreLabel}
          </span>
        )}
      </div>
      <div style={{ fontSize: densidad === 'compacta' ? 12 : 13, fontWeight: 500, lineHeight: 1.35, color: 'var(--fg)' }}>
        {f.titulo}
      </div>
      {densidad !== 'compacta' && (
        <div style={{ fontSize: 11, color: 'var(--fg-muted)', display: 'flex', alignItems: 'center', gap: 4 }}>
          <Icon name="Building2" size={10} />
          <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            {f.comprador_nombre || '—'}
          </span>
        </div>
      )}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 11 }}>
        <span style={{ fontWeight: 600, color: f.monto_estimado ? 'var(--fg)' : 'var(--fg-muted)' }}>
          {f.monto_estimado ? window.fmtQ(f.monto_estimado) : 'sin monto OCDS'}
        </span>
        <div style={{ display: 'flex', gap: 8, color: 'var(--fg-muted)' }}>
          {f.n_documentos > 0 && <span><Icon name="FileText" size={10} /> {f.n_documentos}</span>}
          {f.n_items > 0 && densidad !== 'compacta' && <span><Icon name="Package" size={10} /> {f.n_items}</span>}
          {f.score_ia != null && <span style={{ fontWeight: 600, color: f.score_ia >= 70 ? '#047857' : f.score_ia >= 40 ? '#b45309' : 'var(--fg-muted)' }}>★ {Math.round(f.score_ia)}</span>}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// MODAL PREVIEW V3 — drop-in del aprendizaje, adaptado a Supabase real
// ============================================================
function PrefiltradoPreviewModalV3({ event, onClose, onPromover, onDescartar, onRevisar, onAnalizar }) {
  uE(() => {
    const h = (e) => { if (e.key === 'Escape') onClose && onClose(); };
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [onClose]);

  const horas = event.horas_hasta_cierre;
  const cierreDisplay = horas == null ? '—' :
    horas < 0 ? `cerrado hace ${Math.round(Math.abs(horas))}h` :
    horas < 24 ? `en ${Math.round(horas)}h` :
    `en ${Math.round(horas / 24)} días`;
  const cierreOk = event.cierre_source === 'ocds' || event.cierre_source === 'html_scraping';

  // Análisis IA cacheado (BLOQUE 4 backend ya lo provee)
  const [aiData, setAiData] = uS(null);
  const [analyzing, setAnalyzing] = uS(false);
  uE(() => {
    (async () => {
      const { data } = await window.sb.from('analisis_ia_nog')
        .select('resultado,modelo_usado,created_at,tokens_in,tokens_out')
        .eq('evento_id', event.id).eq('tipo_analisis', 'preview_corto').maybeSingle();
      setAiData(data?.resultado || null);
    })();
  }, [event.id]);

  const runIA = async () => {
    setAnalyzing(true);
    try {
      const { data, error } = await window.sb.functions.invoke('analizar-nog-ia', { body: { nog: event.nog, tipo_analisis: 'preview_corto' } });
      if (error) throw error;
      setAiData(data?.resultado);
    } catch (e) {
      alert('Error análisis IA: ' + (e?.message ?? e));
    } finally {
      setAnalyzing(false);
    }
  };

  const items = event.items_json ?? [];
  const item0 = items[0] || {};
  const attr = item0.attributes || {};

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 920, padding: 0 }}>
        {/* Header */}
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 6, flexWrap: 'wrap' }}>
              <span className="mono" style={{ fontSize: 11, color: 'var(--fg-muted)' }}>NOG {event.nog}</span>
              {event.badge && <BadgeChip slug={event.badge} />}
              <StatusBadge estado={event.estado_portal === 'active' ? 'vigente' : event.estado_local} />
              {aiData && <span style={{ fontSize: 10, padding: '2px 7px', borderRadius: 4, background: '#ede9fe', color: '#5b21b6', fontWeight: 500 }}>
                <Icon name="Sparkles" size={10} /> Analizado
              </span>}
            </div>
            <h2 style={{ fontSize: 16, fontWeight: 600, margin: 0, lineHeight: 1.35 }}>{event.titulo}</h2>
            <p style={{ fontSize: 12, color: 'var(--fg-muted)', marginTop: 4, margin: 0 }}>
              {event.comprador_nombre} {event.uc_nombre && event.uc_nombre !== event.comprador_nombre ? ' · ' + event.uc_nombre : ''}
            </p>
          </div>
          <button className="btn btn--ghost btn--sm" onClick={onClose}><Icon name="X" size={14} /></button>
        </div>

        {/* Strip 5 celdas */}
        <div style={{ padding: '12px 20px', background: 'var(--bg)', display: 'flex', gap: 24, flexWrap: 'wrap', borderBottom: '1px solid var(--border)' }}>
          <StripCell label="Publicado" value={window.fmtFechaCorta(event.release_date)} />
          <StripCell label="Cierra" value={cierreDisplay} sub={cierreOk ? '✓ confirmada' : `fuente: ${event.cierre_source || '—'}`} subColor={cierreOk ? '#059669' : '#d97706'} />
          <StripCell label="Ofertas" value={`${event.n_ofertas ?? 0}`} sub="hasta ahora" />
          <StripCell label="Monto" value={event.monto_estimado ? window.fmtQ(event.monto_estimado) : '—'} sub={event.monto_estimado ? '' : 'OCDS no lo trae'} subColor={event.monto_estimado ? '' : 'var(--fg-muted)'} />
          {event.score_ia != null && <StripCell label="Score IA" value={Math.round(event.score_ia) + ' / 100'} />}
        </div>

        {/* Body */}
        <div style={{ padding: '18px 20px', overflowY: 'auto', flex: 1, maxHeight: '60vh' }}>
          {!aiData && (
            <div style={{ background: '#fef3c7', border: '1px solid #fbbf24', borderRadius: 8, padding: 14, marginBottom: 16, display: 'flex', gap: 12, alignItems: 'flex-start' }}>
              <Icon name="Sparkles" size={20} style={{ color: '#92400e', marginTop: 2 }} />
              <div style={{ flex: 1 }}>
                <p style={{ fontWeight: 600, color: '#78350f', margin: 0, marginBottom: 4, fontSize: 13 }}>Aún no se ha analizado con IA</p>
                <p style={{ fontSize: 12, color: '#78350f', margin: 0, marginBottom: 10, lineHeight: 1.5 }}>
                  El análisis extrae papelería exigida, fianzas, discrepancias OCDS vs PDF, score multifactor y alertas. Se cachea permanentemente (decisión 11.1).
                </p>
                <button className="btn btn--primary btn--sm" onClick={runIA} disabled={analyzing}>
                  {analyzing ? 'Analizando…' : <><Icon name="Sparkles" size={13} /> Correr Preview con IA</>}
                </button>
              </div>
            </div>
          )}
          {aiData && (
            <div style={{ background: '#ede9fe', border: '1px solid #c4b5fd', borderRadius: 8, padding: 14, marginBottom: 16, display: 'flex', gap: 12 }}>
              <Icon name="Sparkles" size={18} style={{ color: '#5b21b6', marginTop: 2 }} />
              <div style={{ flex: 1 }}>
                <p style={{ fontWeight: 600, color: '#5b21b6', margin: 0, marginBottom: 4, fontSize: 13 }}>Resumen del análisis IA</p>
                <p style={{ fontSize: 12, color: '#3b0764', margin: 0, lineHeight: 1.5 }}>
                  {aiData.productoSolicitado || 'Análisis cacheado disponible.'}
                </p>
                {aiData.recomendacion && (
                  <p style={{ fontSize: 11, color: '#5b21b6', marginTop: 8, margin: 0 }}>
                    Recomendación: <strong>{aiData.recomendacion}</strong>
                    {aiData.puntaje != null && ` · Score ${aiData.puntaje}/100`}
                  </p>
                )}
              </div>
            </div>
          )}

          {/* Item 0 con attributes estructurados */}
          {item0.descripcion && (
            <Card title="Renglón solicitado">
              <KV k="Descripción" v={item0.descripcion} />
              <KV k="UNSPSC" v={<span className="mono" style={{ fontSize: 12 }}>{item0.unspsc}</span>} />
              <KV k="Cantidad" v={`${item0.cantidad ?? '—'} ${item0.unidad ?? ''}`} />
              {attr.tipoObligatoriedad && <KV k="Tipo" v={attr.tipoObligatoriedad} />}
              {attr.caracteristicas && (
                <div style={{ paddingTop: 8 }}>
                  <div style={{ color: 'var(--fg-muted)', fontSize: 12, marginBottom: 4 }}>Características técnicas</div>
                  <div style={{ fontSize: 12, lineHeight: 1.5, background: 'var(--bg)', padding: 10, borderRadius: 6, maxHeight: 180, overflow: 'auto' }}>
                    {attr.caracteristicas}
                  </div>
                </div>
              )}
              {(attr.caracteristicasAdicionales ?? []).length > 0 && (
                <div style={{ paddingTop: 8 }}>
                  <div style={{ color: 'var(--fg-muted)', fontSize: 12, marginBottom: 4 }}>Adicionales requeridas al oferente</div>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                    {attr.caracteristicasAdicionales.map((c, i) => (
                      <span key={i} style={{ fontSize: 11, padding: '3px 8px', borderRadius: 4, background: 'var(--bg)', border: '1px solid var(--border)' }}>{c}</span>
                    ))}
                  </div>
                </div>
              )}
            </Card>
          )}

          {/* Documentos OCDS */}
          {(event.documentos_json ?? []).length > 0 && (
            <Card title={`Documentos OCDS (${event.documentos_json.length})`} style={{ marginTop: 12 }}>
              {event.documentos_json.map((d, i) => (
                <a key={i} href={d.url} target="_blank" rel="noopener noreferrer" style={{
                  display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0',
                  borderBottom: i < event.documentos_json.length - 1 ? '1px solid var(--border)' : 'none',
                  color: 'var(--fg)', textDecoration: 'none',
                }}>
                  <Icon name="FileText" size={13} style={{ color: 'var(--fg-muted)' }} />
                  <span style={{ fontSize: 11, padding: '1px 6px', background: 'var(--bg)', borderRadius: 3, color: 'var(--fg-muted)' }}>{d.tipo}</span>
                  <span style={{ fontSize: 12, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.titulo}</span>
                  <Icon name="ExternalLink" size={11} style={{ color: 'var(--fg-muted)' }} />
                </a>
              ))}
            </Card>
          )}

          {/* Capa 2/3: keywords matched (transparencia del scoring) */}
          {(event.keywords_matched ?? []).length > 0 && (
            <Card title="Keywords que matchearon" style={{ marginTop: 12 }}>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
                {event.keywords_matched.map(kw => (
                  <span key={kw} style={{ fontSize: 11, padding: '2px 7px', borderRadius: 3, background: '#eef1fe', color: '#1e40af', fontFamily: 'ui-monospace,monospace' }}>{kw}</span>
                ))}
              </div>
            </Card>
          )}
        </div>

        {/* Footer */}
        <div style={{ padding: '12px 20px', borderTop: '1px solid var(--border)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface)' }}>
          <button className="btn btn--ghost btn--sm" onClick={onDescartar}>
            <Icon name="X" size={13} /> Descartar
          </button>
          <button className="btn btn--ghost btn--sm" onClick={onRevisar}>
            <Icon name="Eye" size={13} /> Pasar a revisión
          </button>
          {!aiData && (
            <button className="btn btn--outline btn--sm" onClick={onAnalizar}>
              <Icon name="Sparkles" size={13} /> Analizar con IA
            </button>
          )}
          <button className="btn btn--primary btn--sm" onClick={onPromover}>
            <Icon name="ArrowUpRight" size={13} /> Promover a Concursos
          </button>
        </div>
      </div>
    </div>
  );
}

function StripCell({ label, value, sub, subColor }) {
  return (
    <div>
      <div style={{ fontSize: 10, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px' }}>{label}</div>
      <div style={{ fontSize: 13, fontWeight: 600, marginTop: 2 }}>{value}</div>
      {sub && <div style={{ fontSize: 10, color: subColor || 'var(--fg-muted)', marginTop: 1 }}>{sub}</div>}
    </div>
  );
}

function Card({ title, children, style }) {
  return (
    <div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, padding: 14, ...style }}>
      <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--fg-muted)', textTransform: 'uppercase', letterSpacing: '0.3px', marginBottom: 8 }}>{title}</div>
      {children}
    </div>
  );
}
// ============================================================
// CONCURSOS — Kanban + Tabla
// ============================================================
function VistaConcursos({ config }) {
  const [vista, setVista] = uS('kanban');
  const [concursos, setConcursos] = uS([]);
  const [eventos, setEventos] = uS({});
  const [cargando, setCargando] = uS(true);
  const [refresco, setRefresco] = uS(0);

  uE(() => {
    (async () => {
      setCargando(true);
      const { data } = await window.sb.from('concursos')
        .select('id,evento_id,estado,monto_oferta_propio,margen_estimado_pct,responsable_id,created_at,updated_at')
        .order('updated_at', { ascending: false })
        .limit(200);
      const concursosFull = data ?? [];
      setConcursos(concursosFull);
      const evIds = [...new Set(concursosFull.map(c => c.evento_id).filter(Boolean))];
      if (evIds.length) {
        const { data: evs } = await window.sb.from('eventos_nog')
          .select('id,nog,titulo,comprador_nombre,monto_estimado,fecha_cierre')
          .in('id', evIds);
        setEventos(Object.fromEntries((evs ?? []).map(e => [e.id, e])));
      }
      setCargando(false);
    })();
  }, [refresco]);

  // Mapear estados del backend a colores del pipeline visual
  const estadosBackend = ['iniciado', 'preparando', 'oferta_lista', 'enviada', 'cerrada', 'adjudicada_a_nosotros', 'adjudicada_otro', 'desierta', 'cancelada', 'archivada'];
  const colorPorEstado = {
    iniciado:               { bg: '#eff6ff', fg: '#1d4ed8', border: '#bfdbfe', emoji: '🌱', label: 'Iniciado' },
    preparando:             { bg: '#fffbeb', fg: '#b45309', border: '#fde68a', emoji: '📋', label: 'Preparando' },
    oferta_lista:           { bg: '#ecfdf5', fg: '#047857', border: '#a7f3d0', emoji: '📦', label: 'Oferta lista' },
    enviada:                { bg: '#eef2ff', fg: '#4338ca', border: '#c7d2fe', emoji: '🚀', label: 'Enviada' },
    cerrada:                { bg: '#f1f5f9', fg: '#475569', border: '#e2e8f0', emoji: '🔒', label: 'Cerrada' },
    adjudicada_a_nosotros:  { bg: '#ecfdf5', fg: '#047857', border: '#a7f3d0', emoji: '🏆', label: 'Adjudicada (nosotros)' },
    adjudicada_otro:        { bg: '#fef2f2', fg: '#b91c1c', border: '#fecaca', emoji: '❌', label: 'Perdida' },
    desierta:               { bg: '#f9fafb', fg: '#6b7280', border: '#e5e7eb', emoji: '🏜️', label: 'Desierta' },
    cancelada:              { bg: '#fef2f2', fg: '#b91c1c', border: '#fecaca', emoji: '✕', label: 'Cancelada' },
    archivada:              { bg: '#f5f5f4', fg: '#57534e', border: '#e7e5e4', emoji: '📦', label: 'Archivada' },
  };

  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Concursos</h1>
          <p className="page__desc">Pipeline activo de NOGs promovidos · {concursos.length} totales</p>
        </div>
        <div className="page__actions">
          <button className="btn btn--ghost btn--sm" onClick={() => setRefresco(r => r + 1)}><Icon name="RefreshCw" size={13} /></button>
          <button className="btn btn--primary btn--sm"><Icon name="Plus" size={13} /> Nuevo concurso</button>
        </div>
      </div>

      <div className="tabs">
        <button className={`tab ${vista === 'kanban' ? 'tab--active' : ''}`} onClick={() => setVista('kanban')}>
          <Icon name="LayoutGrid" size={14} /> Pipeline
        </button>
        <button className={`tab ${vista === 'tabla' ? 'tab--active' : ''}`} onClick={() => setVista('tabla')}>
          <Icon name="List" size={14} /> Tabla
        </button>
      </div>

      {cargando && <div className="muted">Cargando…</div>}
      {!cargando && concursos.length === 0 && (
        <div className="empty-state">
          <div className="empty-state__icon"><Icon name="Trophy" size={26} /></div>
          <div className="empty-state__title">Sin concursos todavía</div>
          <div className="empty-state__desc">Promové un NOG desde Prefiltrado para iniciar un concurso.</div>
        </div>
      )}

      {!cargando && vista === 'kanban' && concursos.length > 0 && (
        <div className="kanban">
          {estadosBackend.map(est => {
            const s = colorPorEstado[est];
            const items = concursos.filter(c => c.estado === est);
            if (items.length === 0 && !['iniciado', 'preparando', 'oferta_lista', 'enviada'].includes(est)) return null;
            return (
              <div key={est} className="kanban__col" style={{ background: s.bg, border: `1px solid ${s.border}` }}>
                <div className="kanban__col-header" style={{ color: s.fg }}>
                  <span className="kanban__col-emoji">{s.emoji}</span>
                  <span className="kanban__col-label">{s.label}</span>
                  <span className="kanban__col-count" style={{ color: s.fg }}>{items.length}</span>
                </div>
                <div className="kanban__cards">
                  {items.map(c => {
                    const ev = eventos[c.evento_id] ?? {};
                    return (
                      <div key={c.id} className="kcard">
                        <div className="kcard__title">{ev.titulo || '(sin título)'}</div>
                        <div className="kcard__entidad">
                          <Icon name="Building2" size={10} />
                          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.comprador_nombre || '—'}</span>
                        </div>
                        <div className="kcard__chips">
                          <span className="kcard__nog mono">NOG {ev.nog || '—'}</span>
                        </div>
                        <div className="kcard__meta">
                          <span className="kcard__monto">{window.fmtQ(c.monto_oferta_propio || ev.monto_estimado)}</span>
                          {ev.fecha_cierre && (
                            <span className="kcard__fecha">
                              <Icon name="Clock" size={10} /> {window.fmtFechaCorta(ev.fecha_cierre)}
                            </span>
                          )}
                        </div>
                      </div>
                    );
                  })}
                  {items.length === 0 && (
                    <div style={{ fontSize: 11, color: s.fg, opacity: 0.5, textAlign: 'center', padding: 16 }}>
                      Sin concursos
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      )}

      {!cargando && vista === 'tabla' && concursos.length > 0 && (
        <div className="table-wrap">
          <table className="table">
            <thead>
              <tr><th>NOG</th><th>Título</th><th>Comprador</th><th>Estado</th><th style={{ textAlign: 'right' }}>Monto</th><th>Cierre</th></tr>
            </thead>
            <tbody>
              {concursos.map(c => {
                const ev = eventos[c.evento_id] ?? {};
                const s = colorPorEstado[c.estado] ?? colorPorEstado.iniciado;
                return (
                  <tr key={c.id}>
                    <td className="mono" style={{ fontSize: 12, color: 'var(--fg-muted)' }}>{ev.nog ?? '—'}</td>
                    <td style={{ fontWeight: 500 }}>{ev.titulo || '(sin título)'}</td>
                    <td style={{ color: 'var(--fg-muted)' }}>{ev.comprador_nombre ?? '—'}</td>
                    <td><span className="badge" style={{ background: s.bg, color: s.fg, borderColor: s.border }}>
                      <span className="badge__emoji">{s.emoji}</span>{s.label}
                    </span></td>
                    <td style={{ textAlign: 'right', fontVariantNumeric: 'tabular-nums', fontWeight: 500 }}>{window.fmtQ(c.monto_oferta_propio || ev.monto_estimado)}</td>
                    <td style={{ color: 'var(--fg-muted)', fontSize: 12 }}>{window.fmtFecha(ev.fecha_cierre)}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}
window.ALBOR_MODULES['concursos'] = VistaConcursos;

// ============================================================
// DIRECTORIO (Entidades / UCs / Contactos)
// ============================================================
function VistaDirectorio() {
  const [tab, setTab] = uS('entidades');
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Directorio</h1>
          <p className="page__desc">Entidades compradoras del Estado, unidades compradoras y contactos.</p>
        </div>
      </div>
      <div className="tabs-bar">
        {[['entidades', 'Entidades'], ['ucs', 'Unidades Compradoras'], ['contactos', 'Contactos']].map(([k, l]) => (
          <button key={k} className={'tabs-bar__tab' + (tab === k ? ' is-active' : '')} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>
      {tab === 'entidades' && <DataTable
        titulo="" descripcion=""
        tabla="entidades"
        searchableColumns={['nombre', 'tipo']}
        columnas={[
          { k: 'id', label: 'ID', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v}</span> },
          { k: 'nombre', label: 'Nombre', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
          { k: 'tipo', label: 'Tipo' },
          { k: 'departamento', label: 'Depto' },
        ]}
        ordenDefault={{ col: 'nombre' }}
      />}
      {tab === 'ucs' && <DataTable
        titulo="" descripcion=""
        tabla="unidades_compradoras"
        searchableColumns={['nombre']}
        columnas={[
          { k: 'id', label: 'ID', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v}</span> },
          { k: 'nombre', label: 'Nombre' },
          { k: 'entidad_id', label: 'Entidad', fmt: v => v ? <span className="mono" style={{ fontSize: 12 }}>{v}</span> : '—' },
        ]}
        ordenDefault={{ col: 'nombre' }}
      />}
      {tab === 'contactos' && <DataTable
        titulo="" descripcion=""
        tabla="contactos_persona"
        searchableColumns={['nombre', 'email', 'puesto']}
        columnas={[
          { k: 'nombre', label: 'Nombre', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
          { k: 'puesto', label: 'Puesto' },
          { k: 'email', label: 'Email' },
          { k: 'telefono', label: 'Teléfono' },
        ]}
        ordenDefault={{ col: 'nombre' }}
      />}
    </div>
  );
}
window.ALBOR_MODULES['directorio'] = VistaDirectorio;

// ============================================================
// NPG
// ============================================================
window.ALBOR_MODULES['npg'] = () => <DataTable
  titulo="NPG" descripcion="Solicitudes de baja cuantía Art.43 — ingreso manual."
  tabla="solicitudes_npg"
  searchableColumns={['npg', 'titulo']}
  columnas={[
    { k: 'npg', label: 'NPG', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v ?? '—'}</span> },
    { k: 'titulo', label: 'Título', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
    { k: 'monto_estimado', label: 'Monto est.', fmt: v => <span style={{ fontVariantNumeric: 'tabular-nums' }}>{window.fmtQ(v)}</span>, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
    { k: 'fecha_limite', label: 'Fecha límite', fmt: window.fmtFecha },
    { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
  ]}
  ordenDefault={{ col: 'fecha_recepcion', asc: false }}
  renderDetalle={(s) => (
    <>
      <KV k="Título" v={s.titulo} />
      <KV k="NPG" v={s.npg} />
      <KV k="Monto" v={window.fmtQ(s.monto_estimado)} />
      <KV k="Estado" v={<StatusBadge estado={s.estado} />} />
    </>
  )}
/>;

// ============================================================
// OFERENTES
// ============================================================
window.ALBOR_MODULES['oferentes'] = () => <DataTable
  titulo="Oferentes" descripcion="Inteligencia competitiva: oferentes detectados en NOGs adjudicados."
  tabla="oferentes"
  searchableColumns={['nombre_comercial', 'razon_social', 'nit']}
  columnas={[
    { k: 'nit', label: 'NIT', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v ?? '—'}</span> },
    { k: 'nombre_comercial', label: 'Nombre comercial', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
    { k: 'razon_social', label: 'Razón social' },
    { k: 'created_at', label: 'Primer visto', fmt: window.fmtFecha },
  ]}
  ordenDefault={{ col: 'nombre_comercial' }}
/>;

// ============================================================
// FACTURACIÓN — Tabs
// ============================================================
function VistaFacturacion() {
  const [tab, setTab] = uS('cotizaciones');
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Facturación</h1>
          <p className="page__desc">Cotizaciones, OC, facturas, pagos y fianzas.</p>
        </div>
      </div>
      <div className="tabs-bar">
        {[['cotizaciones', 'Cotizaciones'], ['oc', 'OC'], ['facturas', 'Facturas'], ['pagos', 'Pagos'], ['fianzas', 'Fianzas']].map(([k, l]) => (
          <button key={k} className={'tabs-bar__tab' + (tab === k ? ' is-active' : '')} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>
      {tab === 'cotizaciones' && <DataTable
        titulo="" tabla="cotizaciones"
        columnas={[
          { k: 'numero', label: 'Número', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v}</span> },
          { k: 'origen_tipo', label: 'Origen' },
          { k: 'fecha', label: 'Fecha', fmt: window.fmtFecha },
          { k: 'total', label: 'Total', fmt: v => window.fmtQFull(v), thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right', fontVariantNumeric: 'tabular-nums', fontWeight: 500 } },
          { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
        ]}
        ordenDefault={{ col: 'fecha', asc: false }}
      />}
      {tab === 'oc' && <DataTable titulo="" tabla="ordenes_compra"
        columnas={[
          { k: 'numero_oc', label: 'Número', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v}</span> },
          { k: 'fecha', label: 'Fecha', fmt: window.fmtFecha },
          { k: 'monto_total', label: 'Monto', fmt: window.fmtQFull, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
          { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
        ]} ordenDefault={{ col: 'fecha', asc: false }} />}
      {tab === 'facturas' && <DataTable titulo="" tabla="facturas"
        columnas={[
          { k: 'numero_factura', label: 'Factura', fmt: v => <span className="mono">{v}</span> },
          { k: 'serie', label: 'Serie' },
          { k: 'fecha', label: 'Fecha', fmt: window.fmtFecha },
          { k: 'total', label: 'Total', fmt: window.fmtQFull, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
          { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
        ]} ordenDefault={{ col: 'fecha', asc: false }} />}
      {tab === 'pagos' && <DataTable titulo="" tabla="pagos_recibidos"
        columnas={[
          { k: 'fecha', label: 'Fecha', fmt: window.fmtFecha },
          { k: 'monto', label: 'Monto', fmt: window.fmtQFull, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
          { k: 'medio', label: 'Medio' },
          { k: 'referencia', label: 'Referencia' },
        ]} ordenDefault={{ col: 'fecha', asc: false }} />}
      {tab === 'fianzas' && <DataTable titulo="" tabla="fianzas"
        columnas={[
          { k: 'tipo', label: 'Tipo' },
          { k: 'numero_poliza', label: 'Póliza', fmt: v => <span className="mono">{v}</span> },
          { k: 'monto', label: 'Monto', fmt: window.fmtQFull, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
          { k: 'fecha_vencimiento', label: 'Vencimiento', fmt: window.fmtFecha },
          { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
        ]} ordenDefault={{ col: 'fecha_vencimiento' }} />}
    </div>
  );
}
window.ALBOR_MODULES['facturacion'] = VistaFacturacion;

// ============================================================
// GARANTÍAS
// ============================================================
window.ALBOR_MODULES['garantias'] = () => <DataTable
  titulo="Garantías" descripcion="Garantías post-venta, renovaciones y alertas de vencimiento."
  tabla="garantias"
  searchableColumns={['producto_descripcion', 'numero_serie']}
  columnas={[
    { k: 'producto_descripcion', label: 'Producto' },
    { k: 'numero_serie', label: 'Serie', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v ?? '—'}</span> },
    { k: 'fecha_inicio', label: 'Inicio', fmt: window.fmtFecha },
    { k: 'fecha_fin', label: 'Fin', fmt: window.fmtFecha },
    { k: 'meses_garantia', label: 'Meses' },
    { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
  ]}
  ordenDefault={{ col: 'fecha_fin' }}
/>;

// ============================================================
// SOPORTE
// ============================================================
window.ALBOR_MODULES['soporte'] = () => <DataTable
  titulo="Soporte" descripcion="Tickets de soporte técnico post-venta."
  tabla="tickets_soporte"
  searchableColumns={['asunto']}
  columnas={[
    { k: 'asunto', label: 'Asunto', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
    { k: 'prioridad', label: 'Prioridad', fmt: v => <StatusBadge estado={v} /> },
    { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
    { k: 'fecha_apertura', label: 'Abierto', fmt: window.fmtFechaHora },
  ]}
  ordenDefault={{ col: 'fecha_apertura', asc: false }}
/>;

// ============================================================
// INVENTARIO — tabs
// ============================================================
function VistaInventario() {
  const [tab, setTab] = uS('productos');
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Inventario</h1>
          <p className="page__desc">Catálogo de productos, marcas y stock.</p>
        </div>
      </div>
      <div className="tabs-bar">
        {[['productos', 'Productos'], ['marcas', 'Marcas'], ['stock', 'Stock']].map(([k, l]) => (
          <button key={k} className={'tabs-bar__tab' + (tab === k ? ' is-active' : '')} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>
      {tab === 'productos' && <DataTable titulo="" tabla="productos"
        searchableColumns={['nombre', 'sku']}
        columnas={[
          { k: 'sku', label: 'SKU', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v ?? '—'}</span> },
          { k: 'nombre', label: 'Nombre' },
          { k: 'categoria', label: 'Categoría' },
          { k: 'precio_base', label: 'Precio', fmt: window.fmtQFull, thStyle: { textAlign: 'right' }, tdStyle: { textAlign: 'right' } },
          { k: 'activo', label: 'Activo', fmt: v => v ? '✓' : '—' },
        ]} ordenDefault={{ col: 'nombre' }} />}
      {tab === 'marcas' && <DataTable titulo="" tabla="marcas"
        columnas={[
          { k: 'slug', label: 'Slug', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v}</span> },
          { k: 'nombre', label: 'Nombre' },
          { k: 'activo', label: 'Activo', fmt: v => v ? '✓' : '—' },
        ]} ordenDefault={{ col: 'nombre' }} />}
      {tab === 'stock' && <DataTable titulo="" tabla="stock"
        columnas={[
          { k: 'producto_id', label: 'Producto', fmt: v => <span className="mono" style={{ fontSize: 12 }}>{v?.slice(0, 8)}</span> },
          { k: 'ubicacion', label: 'Ubicación' },
          { k: 'cantidad', label: 'Cant.' },
          { k: 'cantidad_reservada', label: 'Reservada' },
          { k: 'cantidad_disponible', label: 'Disponible' },
        ]} ordenDefault={{ col: 'producto_id' }} />}
    </div>
  );
}
window.ALBOR_MODULES['inventario'] = VistaInventario;

// ============================================================
// ANÁLISIS IA — histórico cache
// ============================================================
window.ALBOR_MODULES['analisis_ia'] = () => <DataTable
  titulo="Análisis IA" descripcion="Cache de análisis de NOGs (permanente — decisión 11.1)."
  tabla="analisis_ia_nog"
  columnas={[
    { k: 'tipo_analisis', label: 'Tipo' },
    { k: 'modelo_usado', label: 'Modelo', fmt: v => <span className="mono" style={{ fontSize: 11.5 }}>{v}</span> },
    { k: 'tokens_in', label: 'Tokens in' },
    { k: 'tokens_out', label: 'Tokens out' },
    { k: 'costo_usd', label: 'USD', fmt: v => `$${Number(v ?? 0).toFixed(4)}` },
    { k: 'created_at', label: 'Cuándo', fmt: window.fmtFechaHora },
  ]}
  ordenDefault={{ col: 'created_at', asc: false }}
  renderDetalle={(a) => (
    <>
      <KV k="Tipo" v={a.tipo_analisis} />
      <KV k="Modelo" v={a.modelo_usado} />
      <KV k="Resultado" v={<pre style={{ whiteSpace: 'pre-wrap', fontSize: 11.5, background: 'var(--bg)', padding: 12, borderRadius: 6, maxHeight: 320, overflow: 'auto' }}>{JSON.stringify(a.resultado, null, 2)}</pre>} />
    </>
  )}
/>;

// ============================================================
// SALUD DEL SISTEMA
// ============================================================
function VistaSaludSistema() {
  const [tab, setTab] = uS('logs');
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Salud del Sistema</h1>
          <p className="page__desc">Logs, cron, integraciones y uso de IA.</p>
        </div>
      </div>
      <div className="tabs-bar">
        {[['logs', 'System logs'], ['cron', 'Cron'], ['integ', 'Integraciones'], ['ai_usage', 'Uso IA']].map(([k, l]) => (
          <button key={k} className={'tabs-bar__tab' + (tab === k ? ' is-active' : '')} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>
      {tab === 'logs' && <DataTable titulo="" tabla="system_logs"
        columnas={[
          { k: 'created_at', label: 'Cuándo', fmt: window.fmtFechaHora },
          { k: 'nivel', label: 'Nivel', fmt: v => <StatusBadge estado={v} /> },
          { k: 'origen', label: 'Origen' },
          { k: 'mensaje', label: 'Mensaje' },
        ]} ordenDefault={{ col: 'created_at', asc: false }} />}
      {tab === 'cron' && <DataTable titulo="" tabla="cron_executions"
        columnas={[
          { k: 'iniciado_en', label: 'Inicio', fmt: window.fmtFechaHora },
          { k: 'job_nombre', label: 'Job' },
          { k: 'estado', label: 'Estado', fmt: v => <StatusBadge estado={v} /> },
          { k: 'duracion_ms', label: 'Dur. (ms)' },
        ]} ordenDefault={{ col: 'iniciado_en', asc: false }} />}
      {tab === 'integ' && <DataTable titulo="" tabla="integration_errors"
        columnas={[
          { k: 'created_at', label: 'Cuándo', fmt: window.fmtFechaHora },
          { k: 'integracion', label: 'Integración' },
          { k: 'status_code', label: 'Status' },
          { k: 'error_mensaje', label: 'Error', fmt: v => <span className="truncate" style={{ display: 'inline-block', maxWidth: 320 }} title={v}>{v}</span> },
          { k: 'resuelto', label: 'Resuelto', fmt: v => v ? '✓' : '—' },
        ]} ordenDefault={{ col: 'created_at', asc: false }} />}
      {tab === 'ai_usage' && <DataTable titulo="" tabla="ai_usage_tracking"
        columnas={[
          { k: 'fecha', label: 'Fecha', fmt: window.fmtFecha },
          { k: 'modelo', label: 'Modelo', fmt: v => <span className="mono" style={{ fontSize: 11.5 }}>{v}</span> },
          { k: 'llamadas_total', label: 'Llamadas' },
          { k: 'tokens_in_total', label: 'Tokens in' },
          { k: 'tokens_out_total', label: 'Tokens out' },
          { k: 'costo_total_usd', label: 'USD', fmt: v => `$${Number(v).toFixed(4)}` },
        ]} ordenDefault={{ col: 'fecha', asc: false }} />}
    </div>
  );
}
window.ALBOR_MODULES['salud_sistema'] = VistaSaludSistema;

// ============================================================
// CENTRO DE DISEÑO
// ============================================================
function VistaCentroDiseno() {
  const [tokens, setTokens] = uS(null);
  uE(() => { fetch('/design/tokens.json').then(r => r.json()).then(setTokens).catch(() => setTokens({ error: true })); }, []);
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Centro de Diseño</h1>
          <p className="page__desc">Design tokens del sistema · paleta light-Notion (CRM Gobierno).</p>
        </div>
      </div>
      {!tokens && <div className="muted">Cargando tokens…</div>}
      {tokens && tokens.error && <div className="login-error">No se pudo cargar design/tokens.json</div>}
      {tokens && !tokens.error && (
        <>
          <div className="card">
            <div className="card__header"><h2 className="card__title">Resumen</h2></div>
            <div className="card__body">
              <KV k="Versión" v={tokens.version} />
              <KV k="Modo default" v={tokens.modo_default} />
              <KV k="Colores light" v={Object.keys(tokens.color?.light ?? {}).length} />
              <KV k="Colores dark" v={Object.keys(tokens.color?.dark ?? {}).length} />
              <KV k="Tipografía: tamaños" v={Object.keys(tokens.tipografia?.tamano ?? {}).length} />
              <KV k="Espaciado: pasos" v={Object.keys(tokens.espacio ?? {}).length} />
            </div>
          </div>
          <div className="card" style={{ marginTop: 16 }}>
            <div className="card__header"><h2 className="card__title">Paleta light</h2></div>
            <div className="card__body" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 8 }}>
              {Object.entries(tokens.color?.light ?? {}).map(([k, v]) => (
                <div key={k} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 6, border: '1px solid var(--border)', borderRadius: 6 }}>
                  <div style={{ width: 28, height: 28, background: v, borderRadius: 4, border: '1px solid var(--border)' }} />
                  <div style={{ fontSize: 11, flex: 1, minWidth: 0 }}>
                    <div className="mono" style={{ fontSize: 10.5, color: 'var(--fg-muted)' }}>{k}</div>
                    <div className="mono" style={{ fontSize: 11 }}>{v}</div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </>
      )}
    </div>
  );
}
window.ALBOR_MODULES['centro_diseno'] = VistaCentroDiseno;

// ============================================================
// CENTRO DE IA
// ============================================================
function VistaCentroIa() {
  const [mes, setMes] = uS(null);
  uE(() => {
    window.sb.from('gasto_mensual_por_org')
      .select('org_id,mes,llamadas_mes,tokens_in_mes,tokens_out_mes,costo_total_usd_mes')
      .order('mes', { ascending: false }).limit(12)
      .then(({ data }) => setMes(data ?? []));
  }, []);
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Centro de IA</h1>
          <p className="page__desc">Routing por tarea, gasto mensual, alertas.</p>
        </div>
      </div>
      <div className="card">
        <div className="card__header"><h2 className="card__title">Routing actual (default)</h2></div>
        <div className="card__body">
          <KV k="preview_nog" v={<span className="mono">openai/gpt-oss-20b:free</span>} />
          <KV k="tdr_analysis" v={<span className="mono">openai/gpt-oss-120b:free</span>} />
          <KV k="Fallback" v="120b ↔ 20b automático con backoff 250/500/1000ms" />
          <KV k="Límite mensual" v={<>USD 20 <span className="muted">— alertas 50/75/100%</span></>} />
        </div>
      </div>
      <div className="card" style={{ marginTop: 16 }}>
        <div className="card__header"><h2 className="card__title">Gasto últimos 12 meses</h2></div>
        <div className="card__body card__body--flush">
          {!mes && <div className="muted" style={{ padding: 16 }}>Cargando…</div>}
          {mes && mes.length === 0 && <div className="muted" style={{ padding: 16 }}>Sin gasto registrado todavía.</div>}
          {mes && mes.length > 0 && (
            <table className="table">
              <thead><tr><th>Mes</th><th>Llamadas</th><th>Tokens in</th><th>Tokens out</th><th style={{ textAlign: 'right' }}>USD</th></tr></thead>
              <tbody>
                {mes.map((r, i) => (
                  <tr key={i}>
                    <td>{window.fmtFecha(r.mes)}</td>
                    <td>{r.llamadas_mes}</td>
                    <td>{r.tokens_in_mes}</td>
                    <td>{r.tokens_out_mes}</td>
                    <td style={{ textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>${Number(r.costo_total_usd_mes ?? 0).toFixed(4)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}
window.ALBOR_MODULES['centro_ia'] = VistaCentroIa;

// ============================================================
// CENTRO DE PERFILES
// ============================================================
window.ALBOR_MODULES['centro_perfiles'] = () => <DataTable
  titulo="Centro de Perfiles de Rubro" descripcion="Catálogo de perfiles disponibles. Edición avanzada en construcción."
  tabla="perfiles_rubro"
  columnas={[
    { k: 'slug', label: 'Slug', fmt: v => <span className="mono">{v}</span> },
    { k: 'nombre', label: 'Nombre', fmt: v => <span style={{ fontWeight: 500 }}>{v}</span> },
    { k: 'version', label: 'Versión' },
    { k: 'activo', label: 'Activo', fmt: v => v ? '✓' : '—' },
  ]}
  ordenDefault={{ col: 'nombre' }}
/>;

// ============================================================
// CALENDARIO (placeholder visual)
// ============================================================
window.ALBOR_MODULES['calendario'] = () => (
  <div className="page">
    <div className="page__header">
      <div>
        <h1 className="page__title">Calendario</h1>
        <p className="page__desc">Vista cronológica de cierres de NOGs, vencimientos de fianzas y garantías.</p>
      </div>
    </div>
    <div className="empty-state">
      <div className="empty-state__icon"><Icon name="Calendar" size={26} /></div>
      <div className="empty-state__title">En construcción</div>
      <div className="empty-state__desc">Agregará cierres de NOGs, vencimientos de fianzas y de garantías en una vista unificada.</div>
    </div>
  </div>
);

// ============================================================
// SISTEMA (configuración de la org actual)
// ============================================================
function VistaSistema({ config }) {
  if (!config) return <div className="page"><div className="muted">Cargando…</div></div>;
  const org = config.org ?? {};
  const perfil = config.perfil ?? {};
  return (
    <div className="page">
      <div className="page__header">
        <div>
          <h1 className="page__title">Sistema</h1>
          <p className="page__desc">Configuración general de la organización.</p>
        </div>
      </div>
      <div className="card">
        <div className="card__header"><h2 className="card__title">Organización activa</h2></div>
        <div className="card__body">
          <KV k="ID" v={<span className="mono" style={{ fontSize: 12 }}>{org.org_id}</span>} />
          <KV k="Nombre clave" v={<span className="mono">{org.nombre_clave}</span>} />
          <KV k="Nombre legal" v={org.nombre_legal} />
          <KV k="Perfil de rubro" v={<span className="mono">{org.perfil_rubro}</span>} />
          <KV k="Módulos activos" v={Array.isArray(org.modulos_activos) ? org.modulos_activos.length : 0} />
          <KV k="Super-admin" v={config.es_super_admin ? <><Icon name="ShieldCheck" size={13} /> Sí — acceso global</> : 'No'} />
        </div>
      </div>
      <div className="card" style={{ marginTop: 16 }}>
        <div className="card__header"><h2 className="card__title">Perfil de rubro</h2></div>
        <div className="card__body">
          <KV k="Keywords boost" v={(perfil.keywords_boost ?? []).length + ' términos'} />
          <KV k="Keywords excluir" v={(perfil.keywords_excluir ?? []).length + ' términos'} />
          <KV k="Papelería típica" v={(perfil.papeleria_tipica ?? []).length + ' tipos'} />
          <KV k="Tags UC disponibles" v={(perfil.tags_uc_disponibles ?? []).length + ' tags'} />
          <KV k="UNSPSC familias" v={(perfil.filtros_sistema?.unspsc_familias ?? []).join(', ')} />
          <KV k="Modalidad" v={`${perfil.filtros_sistema?.modalidad_principal ?? '—'} / ${perfil.filtros_sistema?.modalidad_especifica ?? '—'}`} />
        </div>
      </div>
    </div>
  );
}
window.ALBOR_MODULES['sistema'] = VistaSistema;

window.dispatchEvent(new CustomEvent('albor:modules-ready'));
