// ROCA GOLF — Reportes view (conectado a API)
const FAMILY_COLORS = ["#1a4d2e","#c9a961","#2d6a44","#a88838","#4a8a63","#0d2818","#7c9885","#b22222"];
const MESES_CORTOS = ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"];
function ReportsKpiCard({ label, value, sub }) {
return (
{label}
{typeof value === "string" && value.startsWith("$")
? <>${value.slice(1)}>
: value}
{sub && (
{sub}
)}
);
}
// Línea: gasto mensual
function MonthlyChart({ data }) {
// data: [{ mes: 1..12, total: number }] — si no hay datos, muestra vacío
const rows = data && data.length
? data
: MESES_CORTOS.map((_, i) => ({ mes: i + 1, total: 0 }));
const W = 720, H = 220, pad = { l: 48, r: 12, t: 16, b: 28 };
const values = rows.map(d => d.total);
const max = Math.max(...values, 1);
const min = 0;
const span = max - min || 1;
const stepX = (W - pad.l - pad.r) / (rows.length - 1);
const yFor = v => pad.t + (1 - (v - min) / span) * (H - pad.t - pad.b);
const xFor = i => pad.l + i * stepX;
const points = rows.map((d, i) => `${xFor(i)},${yFor(d.total)}`).join(" ");
const areaPts = `${xFor(0)},${H - pad.b} ${points} ${xFor(rows.length - 1)},${H - pad.b}`;
const ticks = [0, max * 0.5, max];
return (
);
}
// Donut: status de pago
function StatusDonut({ ordenes }) {
const totals = ordenes.reduce((acc, o) => {
if (o.moneda !== "MXN") return acc;
if (o.statuspago === "PAGADO") {
acc.pagado += o.total;
} else if (o.statuspago === "PAGO_PARCIAL") {
acc.pagado += o.montopagado;
acc.parcial += o.montopendiente;
} else {
acc.pendiente += o.total;
}
return acc;
}, { pagado: 0, parcial: 0, pendiente: 0 });
const segs = [
{ k: "Pagado", v: totals.pagado, c: "var(--rg-flag-ok)" },
{ k: "Parcial", v: totals.parcial, c: "var(--rg-flag-warn)" },
{ k: "Pendiente", v: totals.pendiente, c: "var(--rg-flag-over)" }
];
const sum = segs.reduce((a, s) => a + s.v, 0) || 1;
const r = 56, circ = 2 * Math.PI * r;
let off = 0;
return (
{segs.map(s => (
{s.k}
${fmtMoney(s.v, { compact: true })}
))}
);
}
// Horizontal bars: top proveedores
function TopProveedoresChart({ proveedores }) {
const sorted = (proveedores || [])
.filter(p => p.activo)
.slice()
.sort((a, b) => (b.monto || 0) - (a.monto || 0))
.slice(0, 6);
const max = Math.max(...sorted.map(s => s.monto || 0), 1);
if (!sorted.length) {
return (
Sin proveedores con órdenes registradas.
);
}
return (
{sorted.map((p, i) => (
#{i + 1}
${fmtMoney(p.monto || 0)}
))}
);
}
// Por familia (gasto + barra presupuesto)
function FamilyTable({ familias, thresholds }) {
const total = familias.reduce((a, f) => a + (f.spent || 0), 0) || 1;
const sorted = familias.slice().sort((a, b) => (b.spent || 0) - (a.spent || 0));
if (!sorted.length) {
return (
Sin datos de gasto por familia.
);
}
return (
{sorted.map((f) => {
const nombre = f.nombre || f.name || "—";
const codigo = f.codigo || f.id || "?";
const spent = f.spent || 0;
const budget = f.budget || 0;
const pct = budget > 0 ? (spent / budget) * 100 : 0;
const lv = flagLevel(pct, thresholds);
const share = (spent / total) * 100;
return (
{nombre}
Familia {codigo}
${fmtMoney(spent)}
{share.toFixed(1)}% del total
{pct.toFixed(0)}%
);
})}
);
}
function ReportsView({ thresholds, setThresholds, onNav }) {
const { ordenes, familias, proveedores, alerts, reportes, loadReportes, loading } = useAppData();
// Cargar reportes cuando se monta la vista
useEffect(() => { loadReportes(); }, []);
const alertCount = alerts.filter(a => a.level !== "ok").length;
// KPIs calculados desde ordenes
const totalMxn = ordenes.filter(o => o.moneda === "MXN").reduce((a, o) => a + (o.total || 0), 0);
const totalUsd = ordenes.filter(o => o.moneda === "USD").reduce((a, o) => a + (o.total || 0), 0);
const pendiente = ordenes.reduce((a, o) => a + (o.montopendiente || 0), 0);
const pagado = ordenes.reduce((a, o) => a + (o.montopagado || 0), 0);
const criticas = ordenes.filter(o => o.prioridad === "CRITICO").length;
// Datos mensuales desde reportes API (si están disponibles)
const mensual = reportes ? reportes.mensual : null;
return (
{loading && }
{Icon.excel()} Exportar Excel}/>
{/* Filtros de periodo */}
{/* KPIs */}
{/* Gráficas principales */}
Gasto mensual MXN
{mensual ? "Datos reales de la base de datos" : "Cargando gráfica…"}
Status de pago
Distribución MXN
{/* Familia + Top proveedores */}
Gasto por familia de insumos
{familias.length} familias · {ordenes.length} OCs
vs presupuesto
Top proveedores
Por monto ejercido
);
}
Object.assign(window, { ReportsView, FAMILY_COLORS });