/* Santos OS — chart primitives + per-metric chart configs for Analítica */ const Tc = window.T; function HBar({ label, value, max, color, note }) { const pct = Math.max((value/max)*100, 3); return (
{label} {note}
); } // deterministic pseudo-series function series(base, n, varc, seed=7) { const out = []; let s = seed; for (let i=0;i {data.map((v,i)=>{ const h=Math.max(3,(v/max)*H), x=i*(bw+gap), y=H-h; const last=i===n-1; return {labels && (i%2===0||last) && {labels[i]}} ; })} ); } function AreaLine({ data, color }) { const max=Math.max(...data), min=Math.min(...data), W=326, H=130, n=data.length; const pad=(max-min)*0.2||1; const pts=data.map((v,i)=>[i*(W/(n-1)), H-((v-min+pad)/((max-min)+pad*2))*H]); const path=pts.map((p,i)=>`${i?'L':'M'}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' '); const area=`${path} L${W} ${H} L0 ${H} Z`; return ( {pts.map((p,i)=>i===pts.length-1 && )} ); } function Gauge({ pct, color, label }) { const r=52, c=2*Math.PI*r, off=c*(1-pct/100); return (
{pct}% {label}
); } function Donut({ segments }) { const total=segments.reduce((a,s)=>a+s.v,0); let acc=0; const stops=segments.map(s=>{ const a=acc/total*360, b=(acc+s.v)/total*360; acc+=s.v; return `${s.c} ${a}deg ${b}deg`; }).join(','); return (
{segments.map(s=>(
{s.l} {s.v}
))}
); } const DAYS = Array.from({length:14},(_,i)=>`${10+i}`); // metric → chart card config window.CHART_CFG = { ventas: { title:'Ventas diarias', sub:'Últimos 14 días · Lempiras', render:()=> }, ticket: { title:'Ticket promedio', sub:'Tendencia · 14 días', render:()=> }, captura: { title:'Captura de cliente', sub:'% de ventas con Club Santos', render:()=> <> }, miembros: { title:'Composición de miembros', sub:'Club Santos por nivel', render:()=> }, uptime: { title:'Disponibilidad POS', sub:'Promedio últimas 24h', render:()=> }, stockouts: { title:'Stockouts críticos', sub:'SKUs vitales sin stock · 14 días', render:()=> }, despachos: { title:'Despachos del CEDI', sub:'Valor enviado a sucursales · 14 días', render:()=> }, ordenes: { title:'Órdenes procesadas', sub:'Pedidos internos · 14 días', render:()=> }, transito: { title:'SKUs en tránsito', sub:'Por destino', render:()=> }, frio: { title:'Cadena de frío', sub:'Temperatura °C · 24h (rango 2–8°C)', render:()=> }, lotes: { title:'Lotes por vencer', sub:'Inventario por ventana de caducidad', render:()=>
{[['< 30 días',2,Tc.danger],['30–60 días',2,Tc.warning],['60–90 días',3,Tc.primaryMid]].map(([l,v,c])=>)}
}, }; Object.assign(window, { BarSeries, AreaLine, Gauge, Donut, HBar });