// ROCA GOLF — Alerts dedicated screen + Course mini-viz (conectado a API) function CourseHoles({ categories, thresholds }) { if (!categories || !categories.length) return null; return (
{categories.map((c, i) => { const x = 50 + (i * Math.max(82, Math.floor(640 / categories.length))); const y = 110 + Math.sin(i * 1.1) * 28; const budget = c.budget || 0; const spent = c.spent || 0; const pct = budget > 0 ? (spent / budget) * 100 : 0; const lv = flagLevel(pct, thresholds); const codigo = c.codigo || c.id || "?"; const color = { ok: "#2d6a44", warn: "#d4a017", alert: "#c8651b", over: "#b22222" }[lv]; return ( {(lv === "alert" || lv === "over") && ( )} {codigo} {pct.toFixed(0)}% ); })}
); } function CategoryDonut({ categories, thresholds }) { const counts = { ok: 0, warn: 0, alert: 0, over: 0 }; categories.forEach(c => { const budget = c.budget || 0; const spent = c.spent || 0; const pct = budget > 0 ? (spent / budget) * 100 : 0; counts[flagLevel(pct, thresholds)]++; }); const total = categories.length || 1; const segs = [ { k: "ok", color: "var(--rg-flag-ok)", label: "Saludable" }, { k: "warn", color: "var(--rg-flag-warn)", label: "Aviso 80%" }, { k: "alert", color: "var(--rg-flag-alert)", label: "Alerta 90%" }, { k: "over", color: "var(--rg-flag-over)", label: "Excedido" } ]; const r = 56, circ = 2 * Math.PI * r; let off = 0; const enAlerta = counts.warn + counts.alert + counts.over; return (
{segs.map(s => { const v = counts[s.k] / total; if (!v) return null; const len = v * circ; const dash = `${len} ${circ - len}`; const el = ( ); off += len; return el; })} {enAlerta} EN ALERTA
{segs.map(s => (
{s.label}
{counts[s.k]}
))}
); } // ── Vista de Alertas ───────────────────────────────────────────────────────── function AlertsView({ thresholds, setThresholds, onNav }) { const { familias, alerts, loading } = useAppData(); const [filter, setFilter] = useState("all"); const alertCount = alerts.filter(a => a.level !== "ok").length; // Combinar alertas de API con alertas calculadas de familias const allAlerts = useMemo(() => { if (alerts.length) return alerts; return buildAlertsFromFamilias(familias, thresholds); }, [alerts, familias, thresholds]); const filtered = allAlerts.filter(a => filter === "all" ? true : a.level === filter); // Categorías para el gráfico de campo const courseCategories = familias.map(f => ({ id: f.codigo, codigo: f.codigo, budget: f.budget || 0, spent: f.spent || 0, })); return (
{loading && } {Icon.download()} Exportar} />
{/* Vista del campo (banderas por familia) + donut */}

Estado por familia

{courseCategories.length} familias · banderas indican nivel de utilización
{loading ? "Cargando…" : "Vista en vivo"}
{courseCategories.length > 0 ? :
Sin datos de familias.
}

Distribución por nivel

Por porcentaje de utilización
{/* Lista de alertas filtrable */}

Alertas activas

Ordenadas por severidad
{[ ["all", "Todas"], ["over", "Excedidos"], ["alert", "90%+"], ["warn", "80%+"], ["ok", "OK"] ].map(([k, l]) => ( ))}
{filtered.length === 0 ? (
{Icon.bell(24)}

Sin alertas en este nivel de filtro.

) : filtered.map(a => ( ))}
{/* Control de umbrales */}
); } Object.assign(window, { CourseHoles, CategoryDonut, AlertsView });