From f35cda8d2956476885188354b91ba11e21554e37 Mon Sep 17 00:00:00 2001 From: eeminionn <109454414+eeminionn@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:59:49 -0400 Subject: [PATCH] feat: simplify student and mentor workflows --- docs/UX_AUDIT_AULA_3_3.md | 66 ++++++++ v2/e2e/frontend-only.spec.ts | 4 +- v2/e2e/mentor.spec.ts | 20 ++- v2/e2e/student.spec.ts | 8 +- v2/src/components/AppShell.tsx | 19 +-- v2/src/models/reviews.ts | 44 ++++++ v2/src/pages/DashboardPage.tsx | 42 ++--- v2/src/pages/MentorPage.tsx | 245 ++++++++++++------------------ v2/src/pages/MissionWorkspace.tsx | 28 ++-- v2/src/styles.css | 108 +++++-------- v2/tests/reviews.test.ts | 20 +++ 11 files changed, 341 insertions(+), 263 deletions(-) create mode 100644 docs/UX_AUDIT_AULA_3_3.md create mode 100644 v2/src/models/reviews.ts create mode 100644 v2/tests/reviews.test.ts diff --git a/docs/UX_AUDIT_AULA_3_3.md b/docs/UX_AUDIT_AULA_3_3.md new file mode 100644 index 0000000..10173b9 --- /dev/null +++ b/docs/UX_AUDIT_AULA_3_3.md @@ -0,0 +1,66 @@ +# Auditoría de interacción: Aula 3.3 + +## Objetivo + +Hacer que el aula sea fácil de entender para estudiantes que están empezando y rápida de operar para un profesor con diez alumnos. Esta revisión no representa una certificación de Stanford, MIT o UDD: aplica principios públicos de diseño centrado en las personas y usabilidad publicados por esas instituciones. + +## Referentes aplicados + +- Stanford d.school: observar a las personas, definir el problema, probar y volver a iterar. [Design Thinking Bootleg](https://dschool.stanford.edu/tools/design-thinking-bootleg) +- MIT: priorizar facilidad de aprendizaje, eficiencia, prevención de errores y satisfacción. [Usability](https://ocw.mit.edu/courses/6-831-user-interface-design-and-implementation-spring-2011/pages/in-class-activities/usability/) +- MIT: mantener visibles el estado del sistema, el control del usuario y la protección de su trabajo. [Heuristic Evaluation](https://web.mit.edu/6.813/www/sp16/classes/20-heuristic-evaluation/) +- UDD: diseñar la interacción a partir del comportamiento humano y no solo desde la tecnología. [Diseño de Interacción Digital](https://www.udd.cl/internacional/catalogo-cursos-diseno-digital/) + +## Recorrido del estudiante + +### Problemas observados + +- El inicio repetía dos accesos principales para continuar la misma tarea. +- Una frase prometía un catálogo de práctica que ya no está disponible para estudiantes. +- Las tareas se abrían mediante una flecha pequeña, aunque toda la fila parecía interactiva. +- El compilador usaba palabras como `tests`, `diagnósticos` y `provider`, poco útiles para un principiante. +- El resultado explicaba el estado técnico antes que el siguiente paso. + +### Cambios aplicados + +- Un solo botón principal lleva a la próxima tarea. +- Toda la fila de una tarea abre su workspace y funciona con teclado. +- El inicio explica que allí aparecen solamente las tareas asignadas. +- El compilador habla de pruebas, errores para corregir y próximos pasos. +- Los mensajes distinguen con palabras simples si todo salió bien, si hay resultados incorrectos o si el servicio no pudo ejecutar. + +## Recorrido del profesor + +### Problemas observados + +- La navegación lateral se repetía en una segunda barra con las mismas ocho secciones. +- Ocho indicadores tenían la misma importancia y desplazaban la acción principal. +- El contador de revisiones podía mostrar estados sin una entrega realmente disponible. +- “Matriz del curso” y los criterios “Correctitud” o “Casos límite” requerían interpretación. +- La revisión podía generar un enlace inválido si la versión de una misión ya no estaba en el catálogo local. + +### Cambios aplicados + +- Se conserva una sola navegación. +- Las entregas por revisar aparecen primero; luego se muestran cuatro datos esenciales. +- Los indicadores secundarios quedan bajo “Ver más indicadores”. +- El contador usa exactamente la misma cola que la pantalla de revisiones. +- “Estado de las tareas” reemplaza “Matriz del curso”. +- La lista opcional de revisión usa tres preguntas directas y los botones indican claramente “Pedir cambios” o cuántos XP se aprobarán. +- “Ver misión” aparece únicamente cuando la versión existe. + +## Validación pendiente con personas + +Una evaluación heurística detecta problemas probables, pero no reemplaza observar a usuarios reales. Antes de agregar funciones grandes conviene hacer dos sesiones de 20 minutos: una con un estudiante resolviendo y entregando una misión, y otra con eeminionn creando una tarea y corrigiendo dos entregas. Registrar dónde dudan, qué texto leen y cuántas acciones necesitan. + +## Funciones grandes no implementadas + +Estas ideas necesitan una prueba breve de necesidad antes de construirlas: + +1. Recorrido guiado para la primera sesión del estudiante y lista de preparación inicial para el profesor. +2. Plantillas de corrección configurables por tarea, con criterios definidos por eeminionn. +3. Corrección en lote para revisar o aprobar varias entregas similares. +4. Alertas pedagógicas que sugieran a quién ayudar según atrasos, errores repetidos y uso de pistas. +5. Panel histórico del curso con tendencias semanales y exportación de resultados. + +La regla para una próxima iteración será sencilla: una función nueva debe reducir tiempo, evitar un error frecuente o responder una necesidad observada en las sesiones. Si no cumple una de esas condiciones, no entra al aula. diff --git a/v2/e2e/frontend-only.spec.ts b/v2/e2e/frontend-only.spec.ts index da65fa7..7236e07 100644 --- a/v2/e2e/frontend-only.spec.ts +++ b/v2/e2e/frontend-only.spec.ts @@ -92,10 +92,10 @@ test("frontend sandbox displays mentor controls but blocks mutations", async ({ await page.getByRole("link", { name: /^Revisiones/ }).click(); await expect( - page.getByRole("button", { name: "Solicitar cambios" }), + page.getByRole("button", { name: "Pedir cambios" }), ).toBeDisabled(); await expect( - page.getByRole("button", { name: "Aprobar y asignar XP" }), + page.getByRole("button", { name: /Aprobar \(\+\d+ XP\)/ }), ).toBeDisabled(); await page.getByRole("link", { name: "Premios", exact: true }).click(); await page.getByRole("button", { name: "Nuevo premio" }).click(); diff --git a/v2/e2e/mentor.spec.ts b/v2/e2e/mentor.spec.ts index e876292..704bc6f 100644 --- a/v2/e2e/mentor.spec.ts +++ b/v2/e2e/mentor.spec.ts @@ -18,8 +18,8 @@ test("mentor reviews a pending submission and awards XP once", async ({ page }) .getByLabel("Comentario en línea 2") .fill("El acumulador debe actualizarse dentro del bucle."); await page.getByRole("button", { name: "Agregar" }).click(); - await page.getByLabel("Correctitud").check(); - await page.getByRole("button", { name: "Aprobar y asignar XP" }).click(); + await page.getByLabel("Da la respuesta correcta").check(); + await page.getByRole("button", { name: /Aprobar \(\+\d+ XP\)/ }).click(); await expect(page.getByText("Cola al día")).toBeVisible(); await page.getByRole("button", { name: "Ver como estudiante" }).click(); @@ -56,6 +56,22 @@ test("mentor creates and edits a configurable invitation", async ({ page }) => { await expect(invitation.getByRole("button", { name: "Copiar" })).toBeDisabled(); }); +test("mentor overview prioritizes actions without duplicate navigation", async ({ + page, +}) => { + await loginAsMentor(page); + await expect( + page.getByRole("button", { name: /entrega espera revisión/ }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Estado de las tareas" }), + ).toBeVisible(); + await expect( + page.getByRole("link", { name: "Resumen", exact: true }), + ).toHaveCount(1); + await expect(page.getByText("Ver más indicadores")).toBeVisible(); +}); + test("mentor creates an assignment for selected students", async ({ page }) => { await loginAsMentor(page); await page.getByRole("link", { name: "Tareas", exact: true }).click(); diff --git a/v2/e2e/student.spec.ts b/v2/e2e/student.spec.ts index 08c042c..af7e384 100644 --- a/v2/e2e/student.spec.ts +++ b/v2/e2e/student.spec.ts @@ -34,7 +34,7 @@ test("opens an assigned mission in two actions and runs visible tests", async ({ await page.getByRole("button", { name: "Ejecutar" }).click(); await expect( - page.getByText("El código corre, pero aún no pasa todos los tests"), + page.getByText("Tu código funciona, pero hay resultados por corregir"), ).toBeVisible({ timeout: 15_000 }); await expect(page.getByText("Qué hacer ahora")).toBeVisible(); await expect(page.getByText("Compara un caso paso a paso")).toBeVisible(); @@ -68,6 +68,10 @@ test("prioritizes pending tasks and shows the mentor brief before the mission", await expect(pending.locator(".assignment-row").first()).toContainText( "Variables y acumuladores", ); + await expect(pending.locator(".assignment-row").first()).toHaveAttribute( + "href", + /mission\/la-once-de-tomatin/, + ); await approved.locator("summary").click(); await expect(approved).toContainText("Condiciones booleanas"); @@ -254,7 +258,7 @@ test("keeps student edits after running code opened from feedback", async ({ window.__TOMATIN_EDITOR__?.setValue(code); }, revisedCode); await page.getByRole("button", { name: "Ejecutar" }).click(); - await expect(page.getByText("Todos los tests pasaron")).toBeVisible({ + await expect(page.getByText("Todo salió bien")).toBeVisible({ timeout: 15_000, }); await expect diff --git a/v2/src/components/AppShell.tsx b/v2/src/components/AppShell.tsx index dc9130a..7a8479c 100644 --- a/v2/src/components/AppShell.tsx +++ b/v2/src/components/AppShell.tsx @@ -28,6 +28,7 @@ import { useNavigate, } from "react-router-dom"; import { ProfileAvatar } from "@/components/ProfileAvatar"; +import { getPendingReviews } from "@/models/reviews"; import { useClassroom } from "@/state/classroom-context"; const studentNavigation = [ @@ -81,6 +82,7 @@ export function AppShell() { const isMentor = isActorStaff && !isStudentPreview; const students = snapshot?.profiles.filter((entry) => entry.role === "student") ?? []; + const pendingReviews = snapshot ? getPendingReviews(snapshot).length : 0; return (
@@ -109,7 +111,7 @@ export function AppShell() {
diff --git a/v2/src/models/reviews.ts b/v2/src/models/reviews.ts new file mode 100644 index 0000000..2f537f4 --- /dev/null +++ b/v2/src/models/reviews.ts @@ -0,0 +1,44 @@ +import type { + Assignment, + Attempt, + ClassroomSnapshot, + Profile, + StudentProgress, +} from "@/types"; + +export interface PendingReview { + progress: StudentProgress; + attempt: Attempt; + student: Profile; + assignment: Assignment; +} + +export function getPendingReviews( + snapshot: ClassroomSnapshot, +): PendingReview[] { + return snapshot.progress.flatMap((progress) => { + if (progress.status !== "awaiting_review") return []; + + const attempt = snapshot.attempts + .filter( + (entry) => + entry.userId === progress.userId && + entry.assignmentId === progress.assignmentId && + entry.kind === "submit", + ) + .sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + )[0]; + const student = snapshot.profiles.find( + (entry) => entry.id === progress.userId, + ); + const assignment = snapshot.assignments.find( + (entry) => entry.id === progress.assignmentId, + ); + + return attempt && student && assignment + ? [{ progress, attempt, student, assignment }] + : []; + }); +} diff --git a/v2/src/pages/DashboardPage.tsx b/v2/src/pages/DashboardPage.tsx index 8e766bc..4e5eef1 100644 --- a/v2/src/pages/DashboardPage.tsx +++ b/v2/src/pages/DashboardPage.tsx @@ -6,7 +6,6 @@ import { CircleDot, ChevronDown, Clock3, - Code2, ExternalLink, Github, MessageSquareText, @@ -110,8 +109,8 @@ export function Component() { ); const status = state?.status ?? "not_started"; const overdue = isOverdue(assignment.dueAt, status); - return ( -
+ const content = ( + <>
{String(mission?.order ?? 0).padStart(2, "0")}
@@ -127,15 +126,24 @@ export function Component() { {mission ? ( - + ) : null} + + ); + return mission ? ( + + {content} + + ) : ( +
+ {content}
); })} @@ -157,7 +165,7 @@ export function Component() {

{isMentor ? "Revisa entregas, atrasos y actividad desde un solo lugar." - : "Tareas asignadas primero; el resto del catálogo queda disponible para practicar."} + : "Aquí aparecen solamente las tareas que te asignó tu profesor."}

{isMentor ? ( @@ -165,14 +173,6 @@ export function Component() {