// ROCA GOLF — Dashboard view (conectado a API) function KpiCardFeatured({ totalBudget, totalSpent, thresholds }) { const pct = totalBudget > 0 ? (totalSpent / totalBudget) * 100 : 0; const level = flagLevel(pct, thresholds); return (
Presupuesto total · {new Date().getFullYear()}
MXN{fmtMoney(totalBudget)}
Ejercido {fmtMoney(totalSpent)} {pct.toFixed(1)}% · {levelLabel(level)}
); } function KpiCardSimple({ label, value, currency, delta, deltaDir = "up", trend }) { return (
{label}
{currency && {currency}}{value}
{trend && } {delta && {deltaDir === "up" ? Icon.arrowUp(10) : Icon.arrowDown(10)} {delta} }
); } function CategoryRow({ cat, thresholds, onClick }) { const pct = cat.budget > 0 ? (cat.spent / cat.budget) * 100 : 0; const level = flagLevel(pct, thresholds); const nombre = cat.nombre || cat.name || "—"; const codigo = cat.codigo || cat.id || "?"; return (
onClick && onClick(cat)}>
{nombre}
{codigo}
${fmtMoney(cat.spent)} de ${fmtMoney(cat.budget)}
{pct.toFixed(0)}%
{levelLabel(level)}
); } function AlertItem({ alert }) { return (
{alert.title} {levelLabel(alert.level)}
{alert.desc}
{alert.time} · #{alert.cat}
); } function ThresholdControl({ thresholds, onChange }) { const rows = [ { key: "warn", color: "var(--rg-flag-warn)", label: "Aviso", desc: "Bandera amarilla" }, { key: "alert", color: "var(--rg-flag-alert)", label: "Alerta", desc: "Bandera naranja" }, { key: "over", color: "var(--rg-flag-over)", label: "Excedido",desc: "Bandera roja · notifica gerencia" } ]; return (

Umbrales de alerta

Aplican a todas las categorías
% del presupuesto
{rows.map(r => (
{r.label}
{r.desc}
onChange({ ...thresholds, [r.key]: +e.target.value })} className="rg-slider" />
onChange({ ...thresholds, [r.key]: +e.target.value })} />
))}
); } function RecentOCsCard() { const { ordenes } = useAppData(); const recent = ordenes.slice(0, 8).map(o => ({ folio: fmtFolio(o.folio), vendor: o.proveedor_nombre || nombreProveedor(o.proveedorid), familia: o.familia || "—", amount: o.total, moneda: o.moneda || "MXN", status: o.statuspago, })); return (

Órdenes de compra recientes

Últimas {recent.length} OCs
{recent.length === 0 ? (
Sin órdenes registradas.
) : recent.map(oc => (
{oc.vendor}
{oc.folio}
{oc.moneda === "USD" ? "USD " : "$"}{fmtMoney(oc.amount)}
))}
); } function CategoriesCard({ categories, thresholds, onCatClick }) { const [tab, setTab] = useState("all"); const filtered = categories.filter(c => { if (tab === "all") return true; const pct = c.budget > 0 ? (c.spent / c.budget) * 100 : 0; const lv = flagLevel(pct, thresholds); if (tab === "alerts") return lv === "warn" || lv === "alert" || lv === "over"; if (tab === "ok") return lv === "ok"; return true; }); return (

Familias de insumos

{categories.length} familias · por % de utilización
{filtered.length === 0 ? (
Sin familias en esta categoría.
) : filtered .slice() .sort((a, b) => { const pa = a.budget > 0 ? (a.spent / a.budget) : 0; const pb = b.budget > 0 ? (b.spent / b.budget) : 0; return pb - pa; }) .map(c => ( ))}
); } function OverBudgetBanner({ alerts }) { const over = alerts.filter(a => a.level === "over"); if (!over.length) return null; return (
{over.length} familia{over.length > 1 ? "s" : ""} excedió el presupuesto asignado
Requiere autorización de gerencia para emitir nuevas órdenes en estas familias.
); } // ── Dashboard compuesto ────────────────────────────────────────────────────── function DashboardView({ thresholds, setThresholds, onNav }) { const { familias, alerts, loading } = useAppData(); // Categorías en formato unificado para CategoryRow const categories = familias.map(f => ({ id: f.codigo, codigo: f.codigo, nombre: f.nombre, name: f.nombre, meta: "—", budget: f.budget || 0, spent: f.spent || 0, trend: f.trend || [0], })); const totalBudget = categories.reduce((a, c) => a + c.budget, 0); const totalSpent = categories.reduce((a, c) => a + c.spent, 0); const inAlert = categories.filter(c => { const pct = c.budget > 0 ? (c.spent / c.budget) * 100 : 0; return flagLevel(pct, thresholds) !== "ok"; }).length; const alertCount = alerts.filter(a => a.level !== "ok").length; return (
{loading && }
0 ? ((totalSpent / totalBudget) * 100).toFixed(1) + "%" : "—"} delta={undefined} trend={[0]} />

Alertas activas

{alertCount} requieren atención
{alerts.length === 0 ? (
Sin alertas activas.
) : alerts.slice(0, 4).map(a => ( ))}
); } Object.assign(window, { DashboardView, KpiCardFeatured, KpiCardSimple, CategoryRow, AlertItem, ThresholdControl, RecentOCsCard, CategoriesCard, OverBudgetBanner });