// fichaje.eu — Screen: Protocolos (admin dashboards with charts) const ScreenProtocolos = () => { return (
Mayo 2026 · IABusiness.es
Cuadro de mando
{}}/> }>Exportar informe
{/* Top KPIs */}
}/> }/> }/> }/>
{/* Chart row 1: hours over time + breakdown by team */}
+8.2% vs mes anterior}/>
{[ { l: 'Plataforma', v: '1.140h', p: 29.6, c: 'var(--brand-600)' }, { l: 'Implementación', v: '980h', p: 25.5, c: 'var(--brand-400)' }, { l: 'Comercial', v: '720h', p: 18.7, c: 'var(--violet-500)' }, { l: 'Marketing', v: '512h', p: 13.3, c: 'var(--success-500)' }, { l: 'RRHH y Dirección', v: '495h', p: 12.9, c: 'var(--warning-500)' }, ].map((t, i) => (
{t.l} {t.v} {t.p}%
))}
{/* Chart row 2: absences pie + heatmap */}
{[ { l: 'Vacaciones', v: 38, p: 64, c: 'var(--brand-500)' }, { l: 'Asuntos propios', v: 9, p: 15, c: 'var(--violet-500)' }, { l: 'Médico', v: 7, p: 12, c: 'var(--warning-500)' }, { l: 'Formación', v: 4, p: 7, c: 'var(--success-500)' }, { l: 'Otros', v: 1, p: 2, c: 'var(--slate-400)' }, ].map((row, i) => (
{row.l} {row.v} días · {row.p}%
))}
{/* Bottom: protocols & alerts */}
}>Nuevo protocolo}/>
{[ { t: 'Protocolo de fichaje', d: 'Reglas de pausas, comida y horario flexible.', sig: 24, due: '—', v: 'v3.1' }, { t: 'Política de teletrabajo 2026', d: 'Días de remoto, gastos, equipo informático.', sig: 23, due: '1 pendiente', v: 'v2.1' }, { t: 'Onboarding nuevo empleado', d: 'Checklist completo de primeras 2 semanas.', sig: 24, due: '—', v: 'v4.0' }, { t: 'Prevención de riesgos laborales', d: 'Evaluación anual obligatoria de PRL.', sig: 22, due: '2 pendientes', v: 'v1.2' }, { t: 'GDPR y tratamiento de datos', d: 'Política de privacidad y compliance.', sig: 24, due: '—', v: 'v2.0' }, { t: 'Uso responsable de IA', d: 'Política interna para empleados.', sig: 18, due: '6 pendientes', v: 'v1.0' }, ].map((p, i) => (
{p.t}
{p.v}
{p.d}
{p.sig}/24 firmados {p.due !== '—' ? {p.due} : Completo}
))}
3 activas}/>
{[ { t: 'Dani Esteve · 47h trabajadas esta semana', s: 'Por encima del límite (45h)', tone: 'danger', i: }, { t: '6 personas no han firmado "Uso responsable de IA"', s: 'Plazo vence el 25 may', tone: 'warning', i: }, { t: 'Berta Folch · vacaciones solapan con cierre Q2', s: 'Revisar antes de aprobar', tone: 'warning', i: }, ].map((a, i) => (
{a.i}
{a.t}
{a.s}
))}
); }; // Line chart: 28 days of total hours const LineChart = () => { // Generate plausible data const data = []; for (let i = 0; i < 28; i++) { const dow = i % 7; const isWeekend = dow >= 5; const base = isWeekend ? 12 : 175; const v = base + (Math.sin(i * 0.6) * 15) + (Math.cos(i * 1.1) * 8); data.push({ x: i, v: Math.max(0, v), weekend: isWeekend }); } const max = Math.max(...data.map(d => d.v)); const W = 600, H = 200; const pts = data.map((d, i) => `${(i / (data.length - 1)) * W},${H - (d.v / max) * H}`).join(' '); return (
{/* Grid */} {[0, 0.25, 0.5, 0.75, 1].map(g => ( ))} {/* Weekend bands */} {data.map((d, i) => d.weekend && ( ))} {/* Area */} {/* Line */} {/* Dots — every 7th day */} {data.filter((_, i) => i % 7 === 3).map((d, i) => { const idx = data.indexOf(d); const x = (idx / (data.length - 1)) * W; const y = H - (d.v / max) * H; return ; })} {/* X labels — weeks */} {['Sem 17', 'Sem 18', 'Sem 19', 'Sem 20'].map((l, i) => ( {l} ))}
); }; const DonutChart = () => { const data = [ { v: 29.6, c: '#1E40AF' }, { v: 25.5, c: '#3B82F6' }, { v: 18.7, c: '#8B5CF6' }, { v: 13.3, c: '#10B981' }, { v: 12.9, c: '#F59E0B' }, ]; let acc = 0; const segs = data.map(d => { const s = acc; acc += d.v; return { s, e: acc, c: d.c }; }); const R = 70, r = 44, cx = 90, cy = 90; const toXY = (p) => { const a = (p / 100) * 2 * Math.PI - Math.PI / 2; return [cx + Math.cos(a) * R, cy + Math.sin(a) * R]; }; const innerXY = (p) => { const a = (p / 100) * 2 * Math.PI - Math.PI / 2; return [cx + Math.cos(a) * r, cy + Math.sin(a) * r]; }; return (
{segs.map((s, i) => { const [x1, y1] = toXY(s.s); const [x2, y2] = toXY(s.e); const [ix2, iy2] = innerXY(s.e); const [ix1, iy1] = innerXY(s.s); const large = s.e - s.s > 50 ? 1 : 0; return ( ); })} 3.847 horas totales
); }; const Heatmap = () => { const hours = ['07','08','09','10','11','12','13','14','15','16','17','18','19','20']; const days = ['L','M','X','J','V']; // Intensity pattern: peaks around 9 entry and 13/14 break and 17/18 exit const intensity = (d, h) => { let v = 0.1; if (h === 8) v = 0.5; if (h === 9) v = 1.0; if (h === 10) v = 0.4; if (h === 11) v = 0.2; if (h === 12) v = 0.3; if (h === 13) v = 0.7; if (h === 14) v = 0.55; if (h === 15) v = 0.2; if (h === 16) v = 0.3; if (h === 17) v = 0.65; if (h === 18) v = 0.85; if (h === 19) v = 0.3; if (h === 20) v = 0.08; if (d === 4 && h >= 17) v *= 0.7; // Friday earlier exits return v; }; return (
{hours.map(h => (
{h}
))} {days.map((d, di) => (
{d}
{hours.map((h, hi) => { const v = intensity(di, parseInt(h)); return (
); })} ))}
Menor
{[0.1, 0.3, 0.5, 0.7, 0.9].map(v => (
))}
Mayor
); }; Object.assign(window, { ScreenProtocolos });