// TeacherPanel.jsx — panel de la profesora (solo admin). window.TeacherPanel const { useState: useStateT, useEffect: useEffectT } = React; function studentStats(data) { const S = window.CourseStore; const units = S.units(); let unitsDone = 0, partsPassed = 0, finals = 0, sumNota = 0; units.forEach(u => { const up = data && data.progress && data.progress[u.slug]; if (!up) return; if (up.final && up.final.passed) { unitsDone++; finals++; sumNota += (up.final.best / S.FINAL.N) * 10; } [1, 2, 3, 4].forEach(n => { if (up.parts && up.parts[n] && up.parts[n].passed) partsPassed++; }); }); return { unitsDone, partsPassed, media: finals ? (sumNota / finals) : null, total: units.length }; } function fechaCorta(iso) { if (!iso) return '—'; try { const d = new Date(iso); return d.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: '2-digit' }); } catch (e) { return '—'; } } function TeacherPanel({ onBack }) { const C = window.ECCCloud; const [rows, setRows] = useStateT(null); const [err, setErr] = useStateT(''); const [sel, setSel] = useStateT(null); const cargar = async () => { setErr(''); try { const data = await C.adminListAll(); setRows(data); } catch (e) { setErr('No se pudo cargar la lista.'); setRows([]); } }; useEffectT(() => { cargar(); }, []); const reiniciar = async (r) => { const nombre = (r.data && (r.data.name || r.data.email)) || r.user_id; if (!confirm('¿Reiniciar el progreso de ' + nombre + '? No se puede deshacer.')) return; await C.adminReset(r.user_id, { email: r.data && r.data.email, name: r.data && r.data.name }); cargar(); }; const total = (window.CourseStore.units() || []).length; return (
Panel del profesorado

Alumnado

{err &&
{err}
} {rows === null ? (
Cargando alumnado…
) : rows.length === 0 ? (

Todavía no hay alumnos con progreso.

Aparecerán aquí en cuanto se registren y hagan su primer cuestionario.

) : (
{['Alumno/a', 'Unidades', 'Avance', 'Nota media', ''].map((h, i) =>
{h}
)}
{rows.map(r => { const st = studentStats(r.data || {}); const pct = Math.round((st.unitsDone / st.total) * 100); const nombre = (r.data && r.data.name) || ''; const email = (r.data && r.data.email) || r.user_id; return (
setSel(r)} style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1.2fr 1fr 0.9fr', borderTop: '1px solid var(--line)', alignItems: 'center', font: 'var(--type-body-sm)', color: 'var(--ink)', cursor: 'pointer' }}>
{nombre || '(Sin nombre)'}
{email}
act. {fechaCorta(r.updated_at)}
{st.unitsDone} / {st.total}
{pct}% · {st.partsPassed} partes
{st.media === null ? '—' : st.media.toFixed(1).replace('.', ',')}
); })}
)}

{rows && rows.length > 0 ? rows.length + ' alumno(s). ' : ''}Solo tú (cuenta de profesora) puedes ver esta página. Cada alumno solo ve su propio progreso.

{sel && setSel(null)} onChanged={cargar} />}
); } window.TeacherPanel = TeacherPanel; // ---- Detalle de un alumno (toda su información) ---- const DETAIL_GLYPH = { bolt: '⚡', star: '★', check: '✓', flame: '🔥', trophy: '🏆' }; function StudentDetail({ row, onClose, onChanged }) { const S = window.CourseStore; const units = S.units(); const data = row.data || {}; const pr = data.progress || {}; const badges = data.badges || {}; const st = studentStats(data); const pct = Math.round((st.unitsDone / st.total) * 100); const nombre = data.name || '(Sin nombre)'; const email = data.email || row.user_id; const cell = (node) => { if (!node || node.best === null || node.best === undefined) return { txt: '—', col: 'var(--ink-4)', sub: '' }; const nota = (Math.round((node.best / S.PARTIAL.N) * 100) / 10).toFixed(1).replace('.', ','); return { txt: nota, col: node.passed ? 'var(--success)' : 'var(--danger)', sub: node.attempts ? node.attempts + ' int.' : '' }; }; const finalCell = (node) => { if (!node || node.best === null || node.best === undefined) return { txt: '—', col: 'var(--ink-4)', sub: '' }; const nota = (Math.round((node.best / S.FINAL.N) * 100) / 10).toFixed(1).replace('.', ','); return { txt: nota, col: node.passed ? 'var(--success)' : 'var(--danger)', sub: node.attempts ? node.attempts + ' int.' : '' }; }; return (
e.stopPropagation()} style={{ background: 'var(--bg-canvas)', width: 'min(820px, 100%)', minHeight: '100%', padding: '0 0 60px' }}>
{nombre}
{email} · última actividad {fechaCorta(row.updated_at)}
{/* resumen */}
{[{ k: 'Avance', v: pct + '%' }, { k: 'Unidades superadas', v: st.unitsDone + ' / ' + st.total }, { k: 'Nota media (finales)', v: st.media === null ? '—' : st.media.toFixed(1).replace('.', ',') }].map(c => (
{c.v}
{c.k}
))}
{/* insignias */} {Object.keys(badges).length > 0 && (
{S.BADGES.filter(b => badges[b.id]).map(b => ( {DETAIL_GLYPH[b.icon] || '★'} {b.title} ))}
)} {/* tabla por unidad */}
{['Unidad', 'P1', 'P2', 'P3', 'P4', 'Final'].map(h =>
{h}
)}
{units.map(u => { const up = pr[u.slug] || { parts: {}, final: null }; return (
{u.ud} {u.topic}
{[1, 2, 3, 4].map(n => { const c = cell(up.parts && up.parts[n]); return (
{c.txt}{c.sub && · {c.sub}}
); })} {(() => { const c = finalCell(up.final); return
{c.txt}
; })()}
); })}
); } window.StudentDetail = StudentDetail;