// ─── Widget catalog ─────────────────────────────────────────────────────────
// Each widget declares: id, title, group, defaultSpan (out of 12), defaultVisible, render(props).
// `props` exposes the global currency mode so widgets stay coherent.

// ── Reusable atoms ─────────────────────────────────────────────────────────
function Spark({ data, color = '#3D4F5C', w = 100, h = 28, area = false }) {
  const max = Math.max(...data), min = Math.min(...data);
  const span = (max - min) || 1;
  const px = (i) => (i / (data.length - 1)) * w;
  const py = (v) => h - ((v - min) / span) * (h - 4) - 2;
  const pts = data.map((v, i) => `${px(i)},${py(v)}`).join(' ');
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
      {area && <polygon points={`0,${h} ${pts} ${w},${h}`} fill={color} opacity="0.10" />}
      <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" />
    </svg>
  );
}

function MiniDelta({ delta }) {
  if (delta == null) return null;
  const up = delta >= 0;
  return (
    <span className={`inline-flex items-center gap-0.5 text-[11px] font-medium ${up ? 'text-emerald-700' : 'text-rose-700'}`}>
      {up ? <I.Up size={11} sw={2} /> : <I.Dn size={11} sw={2} />}
      {Math.abs(delta)}%
    </span>
  );
}

// ── KPI card primitive — tuned so big numbers fit ──────────────────────────
function KPI({ label, value, secondary, delta, accent, sparkData, sparkColor, hint }) {
  return (
    <div className="group h-full bg-white rounded-md border border-slate-100 shadow-card p-4 hover:border-slate-200 transition-colors flex flex-col">
      <div className="flex items-start justify-between mb-2">
        <div className="text-[10px] uppercase tracking-[0.13em] text-slate-500 font-medium leading-tight">{label}</div>
        {accent && <span className="w-1.5 h-1.5 rounded-full flex-shrink-0 mt-1" style={{ background: accent }} />}
      </div>
      <div className="font-mono text-[22px] font-semibold tracking-tight text-ink leading-none truncate" title={typeof value === 'string' ? value : undefined}>{value}</div>
      {secondary && <div className="mt-1.5 text-[11.5px] text-slate-500 truncate">{secondary}</div>}
      <div className="flex-1" />
      <div className="mt-3 flex items-end justify-between">
        <div className="flex items-center gap-2">
          <MiniDelta delta={delta} />
          {hint && <span className="text-[10px] text-slate-400">{hint}</span>}
        </div>
        {sparkData && <Spark data={sparkData} color={sparkColor || '#3D4F5C'} w={64} h={20} />}
      </div>
    </div>
  );
}

// ── KPI widget render helpers ──────────────────────────────────────────────
const curVal = (k, cur, formatter = false) => {
  const v = cur === 'ARS' ? k.ars : k.usd;
  if (formatter) return cur === 'ARS' ? ARS_C(v) : USD_C(v);
  return cur === 'ARS' ? ARS(v) : USD(v);
};

// ── Charts ─────────────────────────────────────────────────────────────────
function BarChart({ data, max, h = 220 }) {
  const W = 600, P = { t: 16, r: 16, b: 30, l: 36 };
  const cw = W - P.l - P.r, ch = h - P.t - P.b;
  const x = i => P.l + (i + 0.5) * (cw / data.length);
  const barW = cw / data.length * 0.30;
  const y = v => P.t + ch - (v / max) * ch;
  const ticks = [0, max / 2, max];
  const [hover, setHover] = useState(null);
  return (
    <svg viewBox={`0 0 ${W} ${h}`} className="w-full" style={{ height: h }}>
      {ticks.map((t, i) => (
        <g key={i}>
          <line x1={P.l} x2={W - P.r} y1={y(t)} y2={y(t)} stroke="#EBEEF1" />
          <text x={P.l - 8} y={y(t)} textAnchor="end" dy="0.32em" fontSize="10" fill="#8A9AA5" fontFamily="JetBrains Mono">{t}M</text>
        </g>
      ))}
      {data.map((d, i) => {
        const cx = x(i);
        const dim = hover != null && hover !== i ? 0.35 : 1;
        return (
          <g key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)} style={{ cursor: 'pointer' }}>
            <rect x={cx - barW - 2} y={y(d.ing)} width={barW} height={ch - (y(d.ing) - P.t)} fill="#3D4F5C" rx="1" opacity={dim} />
            <rect x={cx + 2}        y={y(d.eg)}  width={barW} height={ch - (y(d.eg)  - P.t)} fill="#C9A961" rx="1" opacity={dim} />
            <text x={cx} y={h - 10} textAnchor="middle" fontSize="11" fill="#677A88" fontFamily="Manrope">{d.m}</text>
            {hover === i && (
              <g>
                <rect x={cx - 50} y={y(Math.max(d.ing, d.eg)) - 48} width="100" height="40" rx="3" fill="#1F2A33" />
                <text x={cx} y={y(Math.max(d.ing, d.eg)) - 32} textAnchor="middle" fontSize="10" fill="#C9A961" fontFamily="Manrope" fontWeight="600">{d.m}</text>
                <text x={cx - 42} y={y(Math.max(d.ing, d.eg)) - 18} fontSize="10" fill="#fff">Ing</text>
                <text x={cx + 42} y={y(Math.max(d.ing, d.eg)) - 18} textAnchor="end" fontSize="10" fill="#fff" fontFamily="JetBrains Mono">{d.ing}M</text>
                <text x={cx - 42} y={y(Math.max(d.ing, d.eg)) - 6} fontSize="10" fill="#C9A961">Eg</text>
                <text x={cx + 42} y={y(Math.max(d.ing, d.eg)) - 6} textAnchor="end" fontSize="10" fill="#C9A961" fontFamily="JetBrains Mono">{d.eg}M</text>
              </g>
            )}
          </g>
        );
      })}
    </svg>
  );
}

function DonutCanales() {
  const counts = LEADS.reduce((m, l) => (m[l.canal] = (m[l.canal] || 0) + 1, m), {});
  const data = Object.entries(counts).map(([k, v]) => ({ k, v }));
  const total = data.reduce((s, d) => s + d.v, 0);
  const COL = { WhatsApp: '#1FA855', Instagram: '#B33A8E', ZonaProp: '#FF6A00', Tokko: '#3D4F5C' };
  const R = 56, r = 36, cx = 90, cy = 90;
  let acc = 0;
  const arcs = data.map(d => {
    const start = acc / total * Math.PI * 2;
    acc += d.v;
    const end = acc / total * Math.PI * 2;
    const large = end - start > Math.PI ? 1 : 0;
    const x1 = cx + R * Math.sin(start), y1 = cy - R * Math.cos(start);
    const x2 = cx + R * Math.sin(end),   y2 = cy - R * Math.cos(end);
    const x3 = cx + r * Math.sin(end),   y3 = cy - r * Math.cos(end);
    const x4 = cx + r * Math.sin(start), y4 = cy - r * Math.cos(start);
    return { d, path: `M ${x1} ${y1} A ${R} ${R} 0 ${large} 1 ${x2} ${y2} L ${x3} ${y3} A ${r} ${r} 0 ${large} 0 ${x4} ${y4} Z`, color: COL[d.k] };
  });
  return (
    <div className="flex items-center gap-5">
      <svg viewBox="0 0 180 180" className="w-[150px] h-[150px] flex-shrink-0">
        {arcs.map((a, i) => <path key={i} d={a.path} fill={a.color} />)}
        <text x={cx} y={cy - 4} textAnchor="middle" fontSize="22" fontFamily="JetBrains Mono" fontWeight="600" fill="#1F2A33">{total}</text>
        <text x={cx} y={cy + 12} textAnchor="middle" fontSize="9" fontFamily="Manrope" letterSpacing="1.5" fill="#8A9AA5">LEADS</text>
      </svg>
      <ul className="space-y-2 flex-1">
        {data.sort((a, b) => b.v - a.v).map(d => (
          <li key={d.k} className="flex items-center justify-between gap-2 text-[12px]">
            <span className="inline-flex items-center gap-2"><span className="w-2 h-2 rounded-sm" style={{ background: COL[d.k] }} /><span className="text-slate-700">{d.k}</span></span>
            <span className="font-mono text-slate-600">{d.v} <span className="text-slate-400 text-[10px]">· {Math.round(d.v/total*100)}%</span></span>
          </li>
        ))}
      </ul>
    </div>
  );
}

function FunnelPipeline() {
  const counts = ETAPAS.map(e => ({ e, n: LEADS.filter(l => l.etapa === e).length }));
  const max = Math.max(...counts.map(c => c.n)) || 1;
  return (
    <div className="space-y-2">
      {counts.map((c, i) => {
        const w = 40 + (c.n / max) * 60;
        return (
          <div key={c.e} className="flex items-center gap-3">
            <span className="text-[11px] uppercase tracking-[0.1em] text-slate-500 w-[88px] flex-shrink-0">{c.e}</span>
            <div className="flex-1 h-6 bg-slate-50 rounded-sm relative overflow-hidden">
              <div className="h-full rounded-sm flex items-center justify-end pr-2"
                style={{ width: `${w}%`, background: `linear-gradient(90deg, ${['#677A88','#3D4F5C','#C9A961','#A88A47','#3F8A6B'][i]}22, ${['#677A88','#3D4F5C','#C9A961','#A88A47','#3F8A6B'][i]})` }}>
                <span className="text-[11px] font-mono font-medium text-white">{c.n}</span>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function BrokerRanking() {
  const rows = [
    { code: 'SM', ops: 6, usd: 1_120_000 },
    { code: 'JD', ops: 4, usd:   780_000 },
    { code: 'LA', ops: 2, usd:   620_000 },
    { code: 'MV', ops: 2, usd:   320_000 },
  ];
  const max = Math.max(...rows.map(r => r.usd));
  return (
    <ul className="space-y-3">
      {rows.map((r, i) => (
        <li key={r.code} className="flex items-center gap-3">
          <span className="font-serif text-[18px] text-slate-300 w-3">{i + 1}</span>
          <BrokerChip code={r.code} />
          <div className="flex-1 min-w-0">
            <div className="flex items-center justify-between">
              <span className="text-[12.5px] text-ink font-medium truncate">{BROKERS[r.code].nombre}</span>
              <span className="text-[11px] text-slate-500 font-mono">{USD_C(r.usd)}</span>
            </div>
            <div className="mt-1 h-1 bg-slate-100 rounded-full overflow-hidden">
              <div className="h-full bg-gold" style={{ width: `${r.usd / max * 100}%` }} />
            </div>
            <div className="text-[10px] text-slate-400 mt-0.5">{r.ops} operaciones</div>
          </div>
        </li>
      ))}
    </ul>
  );
}

// ── useLiveDashboardData — fetcha KPIs del backend; cae a mock si falla ───
function useLiveDashboardData() {
  const [liveKpis,  setLiveKpis]  = useState(null);
  const [liveCajas, setLiveCajas] = useState(null);

  useEffect(() => {
    const hoy     = new Date();
    const anio    = hoy.getFullYear();
    const mes     = String(hoy.getMonth() + 1).padStart(2, '0');
    const lastDay = new Date(anio, hoy.getMonth() + 1, 0).getDate();
    const desde   = `${anio}-${mes}-01`;
    const hasta   = `${anio}-${mes}-${String(lastDay).padStart(2, '0')}`;

    Promise.allSettled([
      fetch(`${API_BASE}/api/finanzas?tipo=INGRESO&desde=${desde}&hasta=${hasta}`, { credentials: 'include' }).then(r => r.json()),
      fetch(`${API_BASE}/api/finanzas?tipo=EGRESO&desde=${desde}&hasta=${hasta}`,  { credentials: 'include' }).then(r => r.json()),
      fetch(`${API_BASE}/api/operaciones`,                                          { credentials: 'include' }).then(r => r.json()),
      fetch(`${API_BASE}/api/leads`,                                                { credentials: 'include' }).then(r => r.json()),
      fetch(`${API_BASE}/api/cajas`,                                                { credentials: 'include' }).then(r => r.json()),
    ]).then(([ingR, egR, opsR, leadsR, cajasR]) => {

      // ── Ingresos del mes ─────────────────────────────────────────────
      let ingARS = KPIS.ingresos_brutos.ars, ingUSD = KPIS.ingresos_brutos.usd;
      if (ingR.status === 'fulfilled') {
        const rows = Array.isArray(ingR.value) ? ingR.value : (ingR.value?.data ?? []);
        ingARS = rows.filter(r => r.moneda === 'ARS').reduce((s, r) => s + Number(r.monto), 0);
        ingUSD = rows.filter(r => r.moneda === 'USD').reduce((s, r) => s + Number(r.monto), 0);
      }

      // ── Gastos del mes ───────────────────────────────────────────────
      let egARS = KPIS.gastos.ars, egUSD = KPIS.gastos.usd;
      if (egR.status === 'fulfilled') {
        const rows = Array.isArray(egR.value) ? egR.value : (egR.value?.data ?? []);
        egARS = rows.filter(r => r.moneda === 'ARS').reduce((s, r) => s + Number(r.monto), 0);
        egUSD = rows.filter(r => r.moneda === 'USD').reduce((s, r) => s + Number(r.monto), 0);
      }

      // ── Operaciones ──────────────────────────────────────────────────
      let opsCount = KPIS.operaciones.count, opsMonto = KPIS.operaciones.monto_usd;
      let opsTicket = KPIS.operaciones.ticket_avg_usd;
      let pipeVal = KPIS.pipeline_usd.value, pipeCnt = KPIS.pipeline_usd.count;
      if (opsR.status === 'fulfilled') {
        const ops      = Array.isArray(opsR.value) ? opsR.value : (opsR.value?.data ?? []);
        const cerradas = ops.filter(o => o.estado === 'CERRADA');
        const abiertas = ops.filter(o => !['CERRADA', 'ANULADA'].includes(o.estado));
        opsCount  = cerradas.length;
        opsMonto  = cerradas.reduce((s, o) => s + Number(o.montoUSD || 0), 0);
        opsTicket = opsCount > 0 ? Math.round(opsMonto / opsCount) : 0;
        pipeVal   = abiertas.reduce((s, o) => s + Number(o.montoUSD || 0), 0);
        pipeCnt   = abiertas.length;
      }

      // ── Leads ────────────────────────────────────────────────────────
      let leadsActivos = KPIS.leads.activos;
      if (leadsR.status === 'fulfilled') {
        const leads = Array.isArray(leadsR.value) ? leadsR.value : (leadsR.value?.data ?? []);
        leadsActivos = leads.filter(l => l.estado !== 'CERRADO').length;
      }

      // ── Cajas ────────────────────────────────────────────────────────
      let cajaARS = KPIS.caja.ars, cajaUSD = KPIS.caja.usd;
      if (cajasR.status === 'fulfilled') {
        const _META = {
          CAJA_CHICA:  { id: 'chica',  nombre: 'Caja Chica',     tipo: 'efectivo', color: '#10b981' },
          CAJA_ALVARO: { id: 'alvaro', nombre: 'Caja Álvaro',    tipo: 'efectivo', color: '#6366f1' },
          CAJA_MAY:    { id: 'may',    nombre: 'Caja May',        tipo: 'efectivo', color: '#f59e0b' },
          BANCO:       { id: 'banco',  nombre: 'Cuenta Bancaria', tipo: 'banco',    color: '#3b82f6', alias: 'Galicia · 0010-4421-3' },
        };
        const rows   = Array.isArray(cajasR.value) ? cajasR.value : (cajasR.value?.data ?? []);
        const mapped = rows.map(c => {
          const meta = _META[c.nombre] ?? { id: c.id, nombre: c.nombre, tipo: 'efectivo', color: '#677A88' };
          return { ...meta, ars: Number(c.saldoARS), usd: Number(c.saldoUSD) };
        });
        setLiveCajas(mapped);
        const chica = mapped.find(c => c.id === 'chica');
        if (chica) { cajaARS = chica.ars; cajaUSD = chica.usd; }
      }

      setLiveKpis({
        ...KPIS,
        ingresos_brutos: { ...KPIS.ingresos_brutos, ars: ingARS, usd: ingUSD },
        ingresos_netos:  { ...KPIS.ingresos_netos,  ars: ingARS - egARS, usd: ingUSD - egUSD },
        gastos:          { ...KPIS.gastos, ars: egARS, usd: egUSD },
        operaciones:     { ...KPIS.operaciones, count: opsCount, monto_usd: opsMonto, ticket_avg_usd: opsTicket },
        pipeline_usd:    { value: pipeVal, count: pipeCnt },
        leads:           { ...KPIS.leads, activos: leadsActivos },
        caja:            { ars: cajaARS, usd: cajaUSD },
      });
    });
  }, []);

  return { kpis: liveKpis, cajas: liveCajas };
}

// ── Widget catalog ─────────────────────────────────────────────────────────
const WIDGETS = [
  // —— Ingresos ——————————————————————————————————————————————
  { id: 'ing_brutos', group: 'Ingresos', title: 'Ingresos brutos', desc: 'Total operaciones cerradas', span: 3, render: ({ cur, kpis: K = KPIS }) => (
    <KPI label="Ingresos brutos · mes" value={curVal(K.ingresos_brutos, cur, true)}
      secondary={cur === 'ARS' ? '≈ ' + USD_C(K.ingresos_brutos.usd) : '≈ ' + ARS_C(K.ingresos_brutos.ars)}
      delta={K.ingresos_brutos.delta} accent="#3D4F5C"
      sparkData={CHART_6M.map(d => d.ing)} />
  )},
  { id: 'ing_netos', group: 'Ingresos', title: 'Ingresos netos', desc: 'Bruto − comisiones a 3ros − gastos op.', span: 3, render: ({ cur, kpis: K = KPIS }) => (
    <KPI label="Ingresos netos · mes" value={curVal(K.ingresos_netos, cur, true)}
      secondary={cur === 'ARS' ? '≈ ' + USD_C(K.ingresos_netos.usd) : '≈ ' + ARS_C(K.ingresos_netos.ars)}
      delta={K.ingresos_netos.delta} accent="#3F8A6B"
      sparkData={CHART_6M.map(d => d.ing - d.eg)} sparkColor="#3F8A6B" />
  )},
  { id: 'ing_compart', group: 'Ingresos', title: 'Ingresos compartidos', desc: 'Pagado a otras inmobiliarias', span: 3, render: ({ cur }) => (
    <KPI label="Compartidos · mes" value={curVal(KPIS.ingresos_compartidos, cur, true)}
      secondary={`${Math.round(KPIS.ingresos_compartidos.usd / KPIS.ingresos_brutos.usd * 100)}% del bruto`}
      delta={KPIS.ingresos_compartidos.delta} accent="#A88A47" />
  )},
  { id: 'com_propias', group: 'Ingresos', title: 'Comisiones propias', desc: 'Comisiones cobradas sólo por Lanven', span: 3, render: ({ cur }) => (
    <KPI label="Comisiones propias" value={curVal(KPIS.comisiones_propias, cur, true)}
      secondary={cur === 'ARS' ? '≈ ' + USD_C(KPIS.comisiones_propias.usd) : '≈ ' + ARS_C(KPIS.comisiones_propias.ars)}
      delta={KPIS.comisiones_propias.delta} accent="#C9A961" />
  )},
  { id: 'com_pend', group: 'Ingresos', title: 'Comisiones pendientes', desc: 'Aún sin cobrar', span: 3, render: ({ cur }) => (
    <KPI label="Pendientes de cobro" value={curVal(KPIS.comisiones_pendientes, cur, true)}
      secondary={`${KPIS.comisiones_pendientes.qty} operaciones`}
      delta={KPIS.comisiones_pendientes.delta} accent="#B98A2E"
      hint="atención" />
  )},

  // —— Operación ——————————————————————————————————————————————
  { id: 'ops_mes', group: 'Operación', title: 'Operaciones cerradas', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Operaciones cerradas · mes" value={K.operaciones.count}
      secondary={<>Monto · <span className="font-mono">{USD_C(K.operaciones.monto_usd)}</span></>}
      delta={null} accent="#3D4F5C" hint={`+${K.operaciones.delta} vs abril`} />
  )},
  { id: 'ticket_avg', group: 'Operación', title: 'Ticket promedio', desc: 'Monto promedio por operación', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Ticket promedio" value={USD_C(K.operaciones.ticket_avg_usd)}
      secondary="por operación cerrada" accent="#677A88" />
  )},
  { id: 'pipeline', group: 'Operación', title: 'Pipeline en USD', desc: 'Valor de operaciones en proceso', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Pipeline" value={USD_C(K.pipeline_usd.value)}
      secondary={`${K.pipeline_usd.count} operaciones abiertas`} accent="#A88A47" />
  )},

  // —— Finanzas ——————————————————————————————————————————————
  { id: 'gastos_mes', group: 'Finanzas', title: 'Gastos del mes', span: 3, render: ({ cur, kpis: K = KPIS }) => (
    <KPI label="Gastos · mes" value={curVal(K.gastos, cur, true)}
      secondary={cur === 'ARS' ? '≈ ' + USD_C(K.gastos.usd) : '≈ ' + ARS_C(K.gastos.ars)}
      delta={K.gastos.delta} accent="#C9A961"
      sparkData={CHART_6M.map(d => d.eg)} sparkColor="#C9A961" />
  )},
  { id: 'caja_chica', group: 'Finanzas', title: 'Caja chica', desc: 'Saldo en doble moneda', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Caja chica" value={ARS_C(K.caja.ars)}
      secondary={<><span className="font-mono">{USD_C(K.caja.usd)}</span></>}
      accent="#3F8A6B" hint="ARS + USD" />
  )},

  // —— Leads ——————————————————————————————————————————————
  { id: 'leads_act', group: 'Leads', title: 'Leads activos', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Leads activos" value={K.leads.activos}
      secondary={`+${K.leads.nuevos_semana} esta semana`} accent="#3F8A6B" />
  )},
  { id: 'conversion', group: 'Leads', title: 'Tasa de conversión', desc: 'Leads → operación cerrada', span: 3, render: ({ kpis: K = KPIS }) => (
    <KPI label="Conversión leads → ops" value={`${K.leads.conversion}%`}
      secondary="últimos 90 días" delta={+1.2} accent="#A88A47" />
  )},

  // —— Stock ——————————————————————————————————————————————
  { id: 'prop_stock', group: 'Stock', title: 'Propiedades en stock', span: 3, render: () => (
    <KPI label="Propiedades" value={KPIS.propiedades.total}
      secondary={`${KPIS.propiedades.disponibles} disp · ${KPIS.propiedades.reservadas} res`}
      accent="#677A88" hint={`${KPIS.propiedades.vendidas_mes} cerradas este mes`} />
  )},
  { id: 'top_broker', group: 'Stock', title: 'Top broker del mes', span: 3, render: () => {
    const t = KPIS.top_broker;
    return (
      <KPI label="Top broker · mes" value={USD_C(t.usd)}
        secondary={<><BrokerChip code={t.code} size={16} /> <span className="ml-1.5">{BROKERS[t.code].nombre}</span></>}
        accent="#C9A961" hint={`${t.ops} operaciones`} />
    );
  }},

  // —— Gráficos y bloques anchos ——————————————————————————————————
  { id: 'chart_ingvseg', group: 'Análisis', title: 'Ingresos vs egresos', desc: 'Últimos 6 meses', span: 8, render: () => (
    <Card title="Ingresos vs egresos"
      action={
        <div className="flex items-center gap-4 text-[11px] text-slate-500">
          <span className="inline-flex items-center gap-1.5"><span className="w-2 h-2 rounded-sm bg-slate-700" />Ingresos</span>
          <span className="inline-flex items-center gap-1.5"><span className="w-2 h-2 rounded-sm bg-gold" />Egresos</span>
          <span className="text-slate-300">·</span><span>6 meses</span>
        </div>
      }>
      <BarChart data={CHART_6M} max={150} />
    </Card>
  )},
  { id: 'alertas', group: 'Análisis', title: 'Alertas', span: 4, render: () => (
    <Card title="Alertas" action={<span className="text-[11px] text-slate-400">{ALERTAS.length} activas</span>}>
      <ul className="space-y-3 mt-1">
        {ALERTAS.map((a, i) => {
          const sevBg = { ok: 'bg-emerald-500', warn: 'bg-amber-500', bad: 'bg-rose-500' }[a.sev];
          return (
            <li key={i} className="flex items-start gap-3 group cursor-pointer">
              <span className={`mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0 ${sevBg}`} />
              <div className="flex-1 min-w-0">
                <div className="text-[10px] uppercase tracking-[0.12em] text-slate-400">{a.tipo}</div>
                <div className="text-[12.5px] text-ink mt-0.5 leading-snug">{a.texto}</div>
              </div>
              <span className="text-[11px] text-slate-400 font-mono mt-0.5">{a.fecha}</span>
            </li>
          );
        })}
      </ul>
    </Card>
  )},
  { id: 'canales', group: 'Análisis', title: 'Leads por canal', span: 4, render: () => (
    <Card title="Leads por canal" action={<span className="text-[11px] text-slate-400">últimos 30 días</span>}>
      <DonutCanales />
    </Card>
  )},
  { id: 'funnel', group: 'Análisis', title: 'Embudo de leads', span: 4, render: () => (
    <Card title="Embudo · pipeline">
      <FunnelPipeline />
    </Card>
  )},
  { id: 'broker_rank', group: 'Análisis', title: 'Ranking de brokers', span: 4, render: () => (
    <Card title="Ranking brokers · mes">
      <BrokerRanking />
    </Card>
  )},
  { id: 'ultimas_ops', group: 'Análisis', title: 'Últimas operaciones', span: 8, render: () => (
    <Card title="Últimas operaciones"
      action={<button className="text-[11px] text-slate-500 hover:text-ink inline-flex items-center gap-1">Ver todas <I.Arrow size={12} /></button>}>
      <table className="w-full text-[12.5px]">
        <thead>
          <tr className="text-[10px] uppercase tracking-[0.1em] text-slate-400 text-left">
            <th className="font-medium py-2">Fecha</th><th className="font-medium">Propiedad</th><th className="font-medium">Tipo</th>
            <th className="font-medium text-right">Monto</th><th className="font-medium">Brokers</th><th className="font-medium">Estado</th>
          </tr>
        </thead>
        <tbody>
          {OPERACIONES.slice(0, 5).map(op => (
            <tr key={op.id} className="border-t border-slate-100 tr-hover">
              <td className="py-3 text-slate-500 font-mono text-[11.5px]">{op.fecha.slice(5)}</td>
              <td className="text-ink truncate max-w-[220px]" title={op.propiedad}>{op.propiedad}</td>
              <td className="text-slate-600">{op.tipo}</td>
              <td className="text-right font-mono text-ink">{USD(op.monto_usd)}</td>
              <td><BrokerStack codes={op.brokers} /></td>
              <td><StatusBadge status={op.estado} /></td>
            </tr>
          ))}
        </tbody>
      </table>
    </Card>
  )},
  { id: 'ultimos_leads', group: 'Análisis', title: 'Últimos leads', span: 4, render: () => (
    <Card title="Últimos leads"
      action={<button className="text-[11px] text-slate-500 hover:text-ink inline-flex items-center gap-1">Ver kanban <I.Arrow size={12} /></button>}>
      <ul className="divide-y divide-slate-100 -mx-5">
        {LEADS.slice(0, 5).map(l => (
          <li key={l.id} className="px-5 py-3 flex items-center gap-3 hover:bg-canvas cursor-pointer transition-colors">
            <ChannelIcon canal={l.canal} />
            <div className="flex-1 min-w-0">
              <div className="text-[13px] text-ink font-medium leading-tight truncate">{l.nombre}</div>
              <div className="text-[11px] text-slate-500 truncate">{l.interes}</div>
            </div>
            <div className="text-right">
              <div className="text-[11px] text-slate-500">{l.last}</div>
              <div className="text-[11px] mt-0.5 font-mono text-slate-700">{l.score}</div>
            </div>
            <BrokerChip code={l.broker} size={20} />
          </li>
        ))}
      </ul>
    </Card>
  )},
  { id: 'vencs', group: 'Análisis', title: 'Próximos vencimientos', span: 4, render: () => (
    <Card title="Próximos vencimientos">
      <ul className="space-y-3">
        {[
          { f: '24/05', t: 'Reserva R-0086 · Palmera Nova',     m: USD_C(12_400), sev: 'warn' },
          { f: '27/05', t: 'Comisión OP-0141 · Honduras 5410',  m: USD_C(180),    sev: 'warn' },
          { f: '30/05', t: 'Alquiler Soler 4119 renueva',       m: USD_C(1_250),  sev: 'ok' },
          { f: '02/06', t: 'Vencimiento exclusividad Olleros',  m: '—',           sev: 'ok' },
        ].map((v, i) => (
          <li key={i} className="flex items-center gap-3">
            <div className="flex-shrink-0 w-12 text-center">
              <div className="text-[9px] uppercase tracking-[0.1em] text-slate-400">{v.f.split('/')[1] === '05' ? 'May' : 'Jun'}</div>
              <div className="font-serif text-[18px] text-ink leading-none">{v.f.split('/')[0]}</div>
            </div>
            <div className="w-px self-stretch bg-slate-100" />
            <div className="flex-1 min-w-0">
              <div className="text-[12.5px] text-ink truncate">{v.t}</div>
              <div className="text-[10px] text-slate-500 font-mono mt-0.5">{v.m}</div>
            </div>
          </li>
        ))}
      </ul>
    </Card>
  )},
];

const WIDGETS_BY_ID = Object.fromEntries(WIDGETS.map(w => [w.id, w]));

// Default layout — first time the user lands on the dashboard
const DEFAULT_LAYOUT = [
  'ing_brutos', 'ing_netos', 'gastos_mes', 'ops_mes',
  'com_propias', 'com_pend', 'caja_chica', 'leads_act',
  'chart_ingvseg', 'alertas',
  'ultimas_ops', 'ultimos_leads',
];

window.WIDGETS = WIDGETS;
window.WIDGETS_BY_ID = WIDGETS_BY_ID;
window.DEFAULT_LAYOUT = DEFAULT_LAYOUT;
window.KPI = KPI;
window.Spark = Spark;
window.useLiveDashboardData = useLiveDashboardData;
