// 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 ( {ticks.map((t, i) => ( {t >= 1000 ? `$${(t / 1000).toFixed(0)}k` : `$${Math.round(t)}`} ))} {rows.map((d, i) => ( {MESES_CORTOS[(d.mes - 1) % 12]} ))} ); } // 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 => { const len = (s.v / sum) * circ; const dash = `${len} ${circ - len}`; const el = ( ); off += len; return el; })} {sum >= 1000 ? `$${(sum / 1000).toFixed(0)}K` : `$${Math.round(sum)}`} TOTAL MXN
{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}
{p.nombre}
${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 */}
{new Date().getFullYear()}
{/* 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 });