// 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)}>
${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
);
}
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.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
});