// ─── Dashboard · Operaciones (Expedientes/Compras/Logística/Inventario) ────
// 4 de las 12 secciones del Panel general (las otras: Finanzas en
// finanzas-lado.jsx, Directorio en directorio.jsx, Herramientas en
// herramientas.jsx). Extraídas de screens-dashboard.jsx (P10, split en
// screens/dashboard/). Expone a window: ExpedientesSection, ComprasSection,
// LogisticaSection, InventarioSection. Consume window.EXPEDIENTES/COMPRAS/
// LOGISTICA/INVENTARIO (core/colecciones.js), useEntityStats (ui/
// entity-list.jsx), DashSection/DashTable/srvOr/MonoId/TextBold/NumBold
// (compartido.jsx), IdDateCell/KpiCard/StatusPill/TablePill/Avatar (ui/
// primitives.jsx), COMPRA_ESTADOS/LOG_ESTADOS (dominio/pipeline.js),
// formatMoney/formatMoneyShort (dominio/formato.js).
//
// FIX (mapa-comprension.md §inv-dash, bugs severidad alta — "values/trends
// inventados"): mueren `delta`/`highlighted` (props muertas de KpiCard desde
// P4 — su cuerpo no las lee) Y los `trend` (sparkline) con arrays de 6
// números FIJOS que solo el último punto era real (`[8,9,10,10,11,11,
// stats.total]`) — un mini-gráfico de "evolución" 100% inventado. Sin una
// serie histórica real (no hay endpoint que la exponga), la doctrina de
// hidratación honesta (P5/D4) pide sacarlo entero antes que fabricar una
// forma — las 4 KpiCard de este archivo quedan sin `trend` (sin sparkline).

const { useMemo: _useMemoDB } = React;

// ─── Sección · EXPEDIENTES ────────────────────────
function ExpedientesSection({ onNav }) {
  const local = _useMemoDB(() => {
    const total = EXPEDIENTES.length;
    const active = EXPEDIENTES.filter((e) => e.status !== 'RECIBO').length;
    const totalFact = EXPEDIENTES.reduce((s, e) => s + (e.total || 0), 0);
    const withPct = EXPEDIENTES.filter((e) => e.gananciaPct != null);
    const avgPct = withPct.length ? withPct.reduce((s, e) => s + e.gananciaPct, 0) / withPct.length : null;
    return { total, active, totalFact, avgPct };
  }, []);
  // A7/A6 (BUGS-caza-01-07-2026.md #6/#7): los KPIs del Panel general se calculaban
  // con reduce sobre window.EXPEDIENTES, un array hidratado y CAPADO a BIG_LIMIT=500
  // (store.js) — con 2103 expedientes reales, "Total" mostraba 500. v_expedientes_stats
  // agrega en Postgres sobre el UNIVERSO real; con fallback al cálculo local si /stats
  // falla o todavía no llegó.
  const srvStats = window.useEntityStats ? window.useEntityStats('expedientes') : null;
  const stats = {
    total: srvOr(srvStats, 'total', local.total),
    active: srvOr(srvStats, 'activos', local.active),
    totalFact: srvOr(srvStats, 'sumTotal', local.totalFact),
    avgPct: srvOr(srvStats, 'avgGananciaPct', local.avgPct),
  };

  return (
    <DashSection
      eyebrow="Operaciones · Expedientes"
      title="Expedientes"
      route="expedientes-list"
      onNav={onNav}
      kpis={[
        { label: 'Total',             value: String(stats.total),    icon: IconFileText },
        { label: 'En proceso',        value: String(stats.active),   icon: IconClock },
        { label: 'Monto en curso',    value: formatMoneyShort(stats.totalFact), icon: IconDollar },
        { label: 'Ganancia prom.',    value: stats.avgPct != null ? `${stats.avgPct.toFixed(1)}%` : 'N/D', icon: IconTrendingUp },
      ]}
      animDelay={60}
    >
      <DashTable
        caption="Últimos 7 expedientes"
        onRowClick={() => onNav('expedientes-list')}
        rows={EXPEDIENTES}
        columns={[
          { key: 'id', label: 'Expediente', width: '180px',
            render: (e) => <IdDateCell id={e.publicId} date={e.inicio} /> },
          { key: 'paciente', label: 'Paciente', width: '1.4fr',
            render: (e) => (
              <div className="flex items-center gap-3 min-w-0">
                <Avatar name={`${e.particular.nombre} ${e.particular.apellido}`} size={32} />
                <TextBold>{e.particular.nombre} {e.particular.apellido}</TextBold>
              </div>
            ) },
          { key: 'entidad', label: 'Entidad', width: '1.2fr',
            render: (e) => e.entidad
              ? <p className="text-[12.5px] font-semibold truncate" style={{ color: 'var(--foreground)' }}>{e.entidad.razonSocial}</p>
              : <span style={{ color: 'color-mix(in oklab, var(--muted-foreground) 60%, transparent)', fontSize: 12 }}>—</span> },
          { key: 'total', label: 'Total', width: '140px', align: 'right',
            render: (e) => e.total ? <NumBold>{formatMoney(e.total)}</NumBold> : <span style={{ color: 'color-mix(in oklab, var(--muted-foreground) 60%, transparent)' }}>—</span> },
          { key: 'estado', label: 'Estado', width: '140px', align: 'right',
            render: (e) => <StatusPill status={e.status} size="sm" /> },
        ]}
      />
    </DashSection>
  );
}

// ─── Sección · COMPRAS ────────────────────────────
function ComprasSection({ onNav }) {
  const totalMes = COMPRAS.reduce((s, c) => s + c.total, 0);
  const counts = {};
  COMPRAS.forEach(c => counts[c.estado] = (counts[c.estado] || 0) + 1);
  // A7 (BUGS-caza-01-07-2026.md #7): v_compras_stats agrega en Postgres sobre el universo
  // real (total/monto/conteo por estado); fallback si /stats falla o no llegó.
  const srvStats = window.useEntityStats ? window.useEntityStats('compras') : null;
  const byEstadoSrv = srvOr(srvStats, 'byEstado', null);
  const kTotal = srvOr(srvStats, 'total', COMPRAS.length);
  const kRecibidas = byEstadoSrv ? (byEstadoSrv.RECIBIDA || 0) : (counts.RECIBIDA || 0);
  const kTransito = byEstadoSrv
    ? ((byEstadoSrv.EN_TRANSITO || 0) + (byEstadoSrv.PENDIENTE || 0))
    : ((counts.EN_TRANSITO || 0) + (counts.PENDIENTE || 0));
  const kMonto = srvOr(srvStats, 'sumTotal', totalMes);

  return (
    <DashSection
      eyebrow="Operaciones · Compras"
      title="Órdenes de Compra"
      route="compras"
      onNav={onNav}
      kpis={[
        { label: 'Órdenes del mes', value: String(kTotal),       icon: IconShoppingCart },
        { label: 'Recibidas',       value: String(kRecibidas), icon: IconCheckCircle },
        { label: 'En tránsito',     value: String(kTransito), icon: IconTruck },
        { label: 'Monto comprado',  value: formatMoneyShort(kMonto), icon: IconDollar },
      ]}
      animDelay={240}
    >
      <DashTable
        caption="Últimas 7 órdenes de compra"
        onRowClick={() => onNav('compras')}
        rows={COMPRAS}
        columns={[
          { key: 'numero', label: 'Orden', width: '180px',
            render: (c) => <IdDateCell id={c.numero} date={c.fecha} /> },
          { key: 'proveedor', label: 'Proveedor', width: '1.4fr',
            render: (c) => <TextBold>{c.proveedor}</TextBold> },
          { key: 'items', label: 'Ítems', width: '90px', align: 'right',
            render: (c) => <NumBold>{c.items}</NumBold> },
          { key: 'total', label: 'Total', width: '150px', align: 'right',
            render: (c) => <NumBold>{formatMoney(c.total)}</NumBold> },
          { key: 'estado', label: 'Estado', width: '140px', align: 'right',
            render: (c) => { const e = COMPRA_ESTADOS[c.estado] || { label: c.estado || '—', tone: 'neutral' }; return <TablePill label={e.label} tone={e.tone} />; } },
        ]}
      />
    </DashSection>
  );
}

// ─── Sección · LOGÍSTICA ──────────────────────────
function LogisticaSection({ onNav }) {
  const counts = {};
  LOGISTICA.forEach(l => counts[l.estado] = (counts[l.estado] || 0) + 1);
  // A7 (BUGS-caza-01-07-2026.md #7): v_logistica_stats agrega en Postgres sobre el
  // universo real (byTipo/byEstadoPorTipo, el estado vive anidado por tipo Ingreso/
  // Egreso). Sumamos ambos lados para el conteo GLOBAL por estado (mismo criterio que
  // el cálculo local, que tampoco distingue tipo).
  const srvStats = window.useEntityStats ? window.useEntityStats('logistica') : null;
  const byTipoSrv = srvOr(srvStats, 'byTipo', null);
  const byEstadoPorTipoSrv = srvOr(srvStats, 'byEstadoPorTipo', null);
  const srvEstado = (key) => byEstadoPorTipoSrv
    ? Object.keys(byEstadoPorTipoSrv).reduce((s, tipo) => s + ((byEstadoPorTipoSrv[tipo] || {})[key] || 0), 0)
    : null;
  const kTotal = byTipoSrv ? Object.values(byTipoSrv).reduce((s, n) => s + (Number(n) || 0), 0) : LOGISTICA.length;
  const kPendientes = byEstadoPorTipoSrv != null ? srvEstado('PENDIENTE') : (counts.PENDIENTE || 0);
  const kTransito = byEstadoPorTipoSrv != null ? srvEstado('EN_TRANSITO') : (counts.EN_TRANSITO || 0);
  const kEntregados = byEstadoPorTipoSrv != null ? srvEstado('ENTREGADO') : (counts.ENTREGADO || 0);

  return (
    <DashSection
      eyebrow="Operaciones · Logística"
      title="Logística"
      route="logistica"
      onNav={onNav}
      kpis={[
        { label: 'Remitos en curso', value: String(kTotal), icon: IconTruck },
        { label: 'Pendientes',       value: String(kPendientes), icon: IconClock },
        { label: 'En tránsito',      value: String(kTransito),  icon: IconArrowUpRight },
        { label: 'Entregados',       value: String(kEntregados), icon: IconCheckCircle },
      ]}
      animDelay={300}
    >
      <DashTable
        caption="Últimos 7 remitos"
        onRowClick={() => onNav('logistica')}
        rows={LOGISTICA}
        columns={[
          { key: 'id', label: 'ID · Fecha', width: '200px',
            render: (l) => <IdDateCell id={l.publicId} date={l.fechaCreacion} /> },
          { key: 'proveedor', label: 'Proveedor', width: '1.4fr',
            render: (l) => <TextBold>{l.proveedor}</TextBold> },
          { key: 'remito', label: 'Remito', width: '180px',
            render: (l) => <span className="font-mono text-[11.5px] font-semibold tabular-nums" style={{ color: 'color-mix(in oklab, var(--foreground) 85%, transparent)' }}>{l.remito}</span> },
          { key: 'entrega', label: 'Entrega', width: '110px', align: 'right',
            render: (l) => (
              <span className="text-[12px] font-mono tabular-nums" style={{ color: l.fechaEntrega ? 'var(--foreground)' : 'color-mix(in oklab, var(--muted-foreground) 50%, transparent)' }}>
                {l.fechaEntrega ? formatDate(l.fechaEntrega) : '—'}
              </span>
            ) },
          { key: 'estado', label: 'Estado', width: '140px', align: 'right',
            render: (l) => <TablePill label={LOG_ESTADOS[l.estado].label} tone={LOG_ESTADOS[l.estado].tone} /> },
        ]}
      />
    </DashSection>
  );
}

// ─── Sección · INVENTARIO ─────────────────────────
function InventarioSection({ onNav }) {
  const counts = { ok: 0, bajo: 0, critico: 0, agotado: 0 };
  INVENTARIO.forEach(i => { counts[INVENTARIO_ESTADO(i)]++; });
  // P24 (03/07/2026): deriva de costoUnitario/stock directo (real, siempre presente en el
  // hidratado) en vez de `i.valuacion` — ese campo NO lo manda el server (no es columna,
  // serializeInventario nunca lo setea) y solo existía en memoria para un producto editado
  // EN ESTA SESIÓN (screens/inventario/detalle.jsx lo asigna al guardar) — para el resto
  // quedaba `undefined`, sumando NaN al reduce. Mismo criterio de "todo número visible se
  // deriva de los datos" (public/erp/CLAUDE.md §10).
  const valuacion = INVENTARIO.reduce((s, i) => s + (Number(i.stock) || 0) * (Number(i.costoUnitario) || 0), 0);
  const hasCosto = INVENTARIO.some((i) => Number(i.costoUnitario) > 0);
  // A7 (BUGS-caza-01-07-2026.md #7): v_inventario_stats agrega en Postgres sobre el
  // universo real. Desde el fix de #13/M6 (20260701170000_inventario_bajo_critico_
  // union.sql), `srvStats.bajoCritico` es la UNIÓN exacta bajo∪crítico∪agotado (mismo
  // criterio que INVENTARIO_ESTADO in {bajo,critico,agotado}) — ya NO es una
  // aproximación. Este KPI del dashboard, a diferencia del de Inventario, muestra
  // "Bajo / crítico" SIN agotados (el agotado no tiene su propia tarjeta acá, pero
  // tampoco debe inflar esta); como agotado (sinStock) está siempre contenido en la
  // unión, restarlo la deja en exactamente bajo+crítico. `kOk` = total − unión (el
  // complemento exacto de "no OK"), sin restar sinStock aparte (ya está adentro de la
  // unión — restarlo dos veces era el mismo bug de doble conteo que #13/M6).
  const srvStats = window.useEntityStats ? window.useEntityStats('inventario') : null;
  const kTotal = srvOr(srvStats, 'total', INVENTARIO.length);
  const kValuacion = srvOr(srvStats, 'valorTotal', valuacion);
  const kBajoCritico = srvStats ? Math.max(0, srvStats.bajoCritico - srvStats.sinStock) : (counts.bajo + counts.critico);
  const kOk = srvStats ? Math.max(0, kTotal - srvStats.bajoCritico) : counts.ok;

  const ESTADO_CFG = {
    ok:       { label: 'OK',       tone: 'pos'  },
    bajo:     { label: 'Bajo',     tone: 'warn' },
    critico:  { label: 'Crítico',  tone: 'neg'  },
    agotado:  { label: 'Agotado',  tone: 'neg'  },
  };

  return (
    <DashSection
      eyebrow="Operaciones · Inventario"
      title="Inventario"
      route="inventario"
      onNav={onNav}
      kpis={[
        { label: 'Productos',     value: String(kTotal), icon: IconPackage },
        { label: 'Stock OK',      value: String(kOk), icon: IconCheckCircle },
        { label: 'Bajo / crítico', value: String(kBajoCritico), icon: IconClock },
        // P24: mismo criterio honesto que InventarioScreen (screens/inventario/lista.jsx) —
        // sin costo real cargado, "Valuación" no es un total verificado, es 0 por ausencia
        // de dato. Ver el comentario de `hasCosto` más arriba.
        { label: 'Valuación',     value: hasCosto ? formatMoneyShort(kValuacion) : 'Sin datos', icon: IconDollar },
      ]}
      animDelay={420}
    >
      <DashTable
        caption="Últimos 7 ítems del catálogo"
        onRowClick={() => onNav('inventario')}
        rows={INVENTARIO}
        columns={[
          { key: 'sku', label: 'SKU', width: '180px',
            render: (it) => <IdDateCell id={it.sku} date={it.fechaCreacion} /> },
          { key: 'descripcion', label: 'Descripción · Categoría', width: '1.6fr',
            render: (it) => (
              <div className="min-w-0">
                <TextBold>{it.descripcion}</TextBold>
                <p className="text-[11px] font-bold uppercase tracking-[0.08em] mt-0.5" style={{ color: 'var(--muted-foreground)' }}>{it.categoria}</p>
              </div>
            ) },
          { key: 'stock', label: 'Stock', width: '110px', align: 'right',
            render: (it) => (
              <div className="text-right">
                <p className="text-[14px] font-extrabold tabular-nums" style={{ color: 'var(--foreground)' }}>{it.stock}</p>
                <p className="text-[10.5px] tabular-nums" style={{ color: 'var(--muted-foreground)' }}>mín. {it.minimo}</p>
              </div>
            ) },
          { key: 'estado', label: 'Estado', width: '120px', align: 'right',
            render: (it) => {
              const cfg = ESTADO_CFG[INVENTARIO_ESTADO(it)];
              return <TablePill label={cfg.label} tone={cfg.tone} />;
            } },
          { key: 'valuacion', label: 'Valuación', width: '140px', align: 'right',
            // P24: derivado real (stock×costoUnitario), no el `it.valuacion` fantasma
            // (nunca lo manda el server — ver comentario de arriba). `null` cuando no hay
            // costo real cargado: formatMoney(null) ya rinde "—" honesto.
            render: (it) => <NumBold>{formatMoney(Number(it.costoUnitario) > 0 ? it.stock * it.costoUnitario : null)}</NumBold> },
        ]}
      />
    </DashSection>
  );
}

Object.assign(window, { ExpedientesSection, ComprasSection, LogisticaSection, InventarioSection });
