Actividades y Juegos
Módulos API: game-activities, escape-room, xp, fluency-test
Páginas web: 18 páginas player (una por tipo + FluidityTestPage)
Spec Playwright: apps/e2e/tests/student/games/ — 📋 pendiente
Los 16 tipos de actividad
Section titled “Los 16 tipos de actividad”Tipo (GameActivityType) | Módulo API | Player Page |
|---|---|---|
ESCAPE_ROOM | escape-room | EscapeRoomPlayerPage |
FLASHCARDS | flashcards | FlashcardsPlayerPage |
QUIZ | quiz | QuizPage |
CONVERSATIONAL_QUIZ | conversational-quiz | ConversationalQuizPage |
CROSSWORD | crossword | CrosswordPlayerPage |
DRAG_AND_DROP | drag-and-drop | DragAndDropPlayerPage |
HANGMAN | hangman | HangmanPlayerPage |
MATCHING_PAIRS | matching-pairs | MatchingPairsPlayerPage |
MEMORY_MATCH | memory-match | MemoryMatchPlayerPage |
SCENARIO_CHALLENGE | scenario-challenge | ScenarioChallengePlayerPage |
SPEED_QUIZ | speed-quiz | SpeedQuizPlayerPage |
TIMELINE_CHALLENGE | timeline-challenge | TimelineChallengePlayerPage |
TRIVIA | trivia | TriviaPlayerPage |
WORD_PUZZLE | word-puzzle | WordPuzzlePlayerPage |
ADVENTURE | adventure | AdventurePlayerPage |
FLUENCY_TEST | fluency-test | FluidityTestPage |
Flujo genérico de un juego (patrón común)
Section titled “Flujo genérico de un juego (patrón común)”Creator: POST /game-activities/roomscape/generate (IA genera contenido) → PATCH /game-activities/roomscape/:id/accept (creator acepta) → actividad disponible en el módulo
Estudiante: GET /game-activities/:id (obtener actividad) POST /game-activities/:gameActivityId/attempt (iniciar intento) PATCH /game-activities/attempts/:attemptId/progress (actualizar estado parcial) POST /game-activities/attempts/:attemptId/complete (finalizar + registra XP)
ESCAPE_ROOM extra: POST /game-activities/attempts/:attemptId/submit (responder puzzle individual) POST /game-activities/attempts/:attemptId/hint (pedir pista, con penalización XP) GET /game-activities/:gameActivityId/attempt/active (recuperar intento en curso)
FLUENCY_TEST (flujo independiente, sin intento): POST /fluency-test/submit (envía respuestas → score + perfil + recomendaciones)User Stories
Section titled “User Stories”US-GAME-001: Iniciar una actividad del módulo actual
Section titled “US-GAME-001: Iniciar una actividad del módulo actual”Como estudiante autenticado Quiero iniciar una actividad de juego disponible en mi módulo actual Para practicar los conceptos del módulo de forma interactiva
Criterios: AC-GAME-001 · Casos: TC-GAME-001, TC-GAME-002 · Prioridad: Alta · ✅ Implementado
US-GAME-002: Guardar progreso parcial durante la actividad
Section titled “US-GAME-002: Guardar progreso parcial durante la actividad”Como estudiante en mitad de una actividad Quiero que mi progreso se guarde automáticamente Para poder retomar la actividad desde el mismo punto si se interrumpe la sesión
Criterios: AC-GAME-002 · Casos: TC-GAME-003 · Prioridad: Alta · ✅ Implementado
US-GAME-003: Completar una actividad y recibir XP
Section titled “US-GAME-003: Completar una actividad y recibir XP”Como estudiante que ha finalizado una actividad Quiero recibir los puntos XP correspondientes a mi rendimiento Para avanzar en mi nivel y ver mi progreso en el dashboard
Criterios: AC-GAME-003 · Casos: TC-GAME-004, TC-GAME-005 · Prioridad: Alta · ✅ Implementado
US-GAME-004: Ver resultados y feedback al terminar
Section titled “US-GAME-004: Ver resultados y feedback al terminar”Como estudiante que acaba de completar una actividad Quiero ver mi puntuación final, el XP ganado y el tiempo empleado Para evaluar mi desempeño y saber en qué debo mejorar
Criterios: AC-GAME-004 · Casos: TC-GAME-006 · Prioridad: Alta · ✅ Implementado
US-GAME-005: Reintentar una actividad
Section titled “US-GAME-005: Reintentar una actividad”Como estudiante que no ha obtenido la puntuación deseada Quiero poder reintentar la actividad Para mejorar mi comprensión del tema y obtener más XP
Criterios: AC-GAME-005 · Casos: TC-GAME-007 · Prioridad: Media · ✅ Implementado
US-GAME-006: Pedir pista en Escape Room (con penalización)
Section titled “US-GAME-006: Pedir pista en Escape Room (con penalización)”Como estudiante atascado en un puzzle del Escape Room Quiero solicitar una pista contextual generada por IA Para desbloquearme sin abandonar la actividad, aceptando reducir mi XP final
Criterios: AC-GAME-006 · Casos: TC-GAME-008, TC-GAME-009 · Prioridad: Media · ✅ Implementado
US-GAME-007: Actividad bloqueada si el módulo no está desbloqueado
Section titled “US-GAME-007: Actividad bloqueada si el módulo no está desbloqueado”Como estudiante con plan FREE Quiero recibir un mensaje claro cuando intento acceder a una actividad que requiere plan superior Para entender qué debo hacer para acceder (actualizar mi plan)
Criterios: AC-GAME-007 · Casos: TC-GAME-010, TC-GAME-011 · Prioridad: Alta · ✅ Implementado
US-GAME-008: Recuperar intento activo al recargar la página
Section titled “US-GAME-008: Recuperar intento activo al recargar la página”Como estudiante que cierra accidentalmente la pestaña del Escape Room Quiero que al volver a la actividad mi intento en curso se recupere automáticamente Para no perder el progreso acumulado
Criterios: AC-GAME-008 · Casos: TC-GAME-012 · Prioridad: Media · ✅ Implementado
US-GAME-009: Completar el test de fluidez IA (FLUENCY_TEST)
Section titled “US-GAME-009: Completar el test de fluidez IA (FLUENCY_TEST)”Como visitante o estudiante autenticado Quiero realizar el test de diagnóstico de fluidez en IA Para conocer mi nivel actual y recibir recomendaciones de rutas de aprendizaje personalizadas
Criterios: AC-GAME-009 · Casos: TC-GAME-013, TC-GAME-014 · Prioridad: Alta · ✅ Implementado
Acceptance Criteria
Section titled “Acceptance Criteria”AC-GAME-001: Iniciar una actividad del módulo actual
Section titled “AC-GAME-001: Iniciar una actividad del módulo actual”US: US-GAME-001
AC-GAME-001.1 — Carga del payload de la actividad
Dado que soy estudiante autenticado con acceso al móduloCuando realizo GET /game-activities/:idEntonces la API retorna 200Y el cuerpo incluye: id, type, title, description, status, maxXp, timerSeconds, payloadY el campo status es "PUBLISHED"AC-GAME-001.2 — Inicio del intento
Dado que tengo el id de una actividad publicada a la que tengo accesoCuando realizo POST /game-activities/:gameActivityId/attemptEntonces la API retorna 201Y el cuerpo contiene { attempt: { id, status: "IN_PROGRESS", attemptNumber, ... }, blueprint }Y el campo attemptNumber es 1 si es el primer intentoAC-GAME-001.3 — Reutilización de intento en curso
Dado que ya tengo un intento con status "IN_PROGRESS" para esa actividadCuando realizo POST /game-activities/:gameActivityId/attempt de nuevoEntonces la API retorna el intento existente (no crea uno nuevo)Y el attemptNumber no incrementaAC-GAME-002: Guardar progreso parcial
Section titled “AC-GAME-002: Guardar progreso parcial”US: US-GAME-002
AC-GAME-002.1 — Actualización de progreso persiste
Dado que tengo un intento con status "IN_PROGRESS"Cuando realizo PATCH /game-activities/attempts/:attemptId/progress con body { activeItemId: "puzzle-2", completedItemIds: ["puzzle-1"], secondsElapsed: 120 }Entonces la API retorna 200Y el intento actualizado refleja los nuevos valores de activeItemId, completedItemIds y secondsElapsedY el campo lastActivityAt se actualizaAC-GAME-002.2 — Campos opcionales (actualización parcial)
Dado que tengo un intento activoCuando envío PATCH /progress con solo { secondsElapsed: 45 }Entonces únicamente se actualiza ese campoY activeItemId y completedItemIds mantienen sus valores anterioresAC-GAME-003: Completar una actividad y recibir XP
Section titled “AC-GAME-003: Completar una actividad y recibir XP”US: US-GAME-003
AC-GAME-003.1 — Completado exitoso (flujo simple)
Dado que tengo un intento "IN_PROGRESS" de tipo CROSSWORD, HANGMAN, TRIVIA o ADVENTURECuando realizo POST /game-activities/attempts/:attemptId/complete con body { scorePercent: 80, totalXp: 160, secondsElapsed: 240 }Entonces la API retorna 200Y el intento actualiza su status a "COMPLETED"Y se persiste un StudentXPEvent con el xp y el tipo correcto (ej. "CROSSWORD_COMPLETED")AC-GAME-003.2 — XP proporcional al score
Dado que completo una actividad con scorePercent: 50 (totalXp: 100)Y la misma actividad con scorePercent: 100 (totalXp: 200) en otro intentoEntonces el StudentXPEvent refleja el totalXp enviado por el clienteY el XP global del usuario aumenta en la cantidad correspondienteAC-GAME-003.3 — No se emite XP si totalXp es 0
Dado que completo una actividad con totalXp: 0Entonces NO se crea ningún StudentXPEventY el intento se marca como "COMPLETED" igualmenteAC-GAME-004: Ver resultados al terminar
Section titled “AC-GAME-004: Ver resultados al terminar”US: US-GAME-004
AC-GAME-004.1 — Respuesta de completado incluye datos de resultado
Dado que completo una actividad de tipo simpleCuando realizo POST /attempts/:attemptId/completeEntonces la respuesta incluye scorePercent, totalXp, completedAt y secondsElapsedAC-GAME-004.2 — Respuesta de completado de Escape Room incluye datos de puzzle
Dado que resuelvo el último puzzle de un Escape RoomCuando la API retorna la respuesta de submitAnswerEntonces el cuerpo incluye { isCorrect: true, xpEarned, attemptCompleted: true, scorePercent }Y el intento en BD tiene status "COMPLETED"AC-GAME-005: Reintentar una actividad
Section titled “AC-GAME-005: Reintentar una actividad”US: US-GAME-005
AC-GAME-005.1 — Nuevo intento crea registro independiente
Dado que ya tengo un intento "COMPLETED" para una actividadCuando realizo POST /game-activities/:gameActivityId/attempt de nuevoEntonces la API crea un nuevo GameAttempt con attemptNumber = 2Y el nuevo intento tiene status "IN_PROGRESS"Y el intento anterior no se modificaAC-GAME-005.2 — attemptNumber se incrementa correctamente
Dado que tengo 2 intentos previos (numbers 1 y 2)Cuando inicio un tercer intentoEntonces el nuevo intento tiene attemptNumber = 3AC-GAME-006: Pedir pista en Escape Room
Section titled “AC-GAME-006: Pedir pista en Escape Room”US: US-GAME-006
AC-GAME-006.1 — Pista retorna texto contextual
Dado que tengo un intento activo de Escape RoomY estoy en el puzzle con id "puzzle-3"Cuando realizo POST /game-activities/attempts/:attemptId/hint con body { itemId: "puzzle-3", attemptedAnswer: { blanks: [...] } }Entonces la API retorna 201Y el cuerpo contiene { hint: "<texto de la pista>", isDynamic: true | false }AC-GAME-006.2 — El contador hintsUsed se incrementa
Dado que el intento tiene hintsUsed = 0Cuando solicito una pistaEntonces el intento en BD actualiza hintsUsed a 1Y lastActivityAt se renuevaAC-GAME-006.3 — Fallback a pista estática si el agente IA no responde
Dado que el agente de pistas de ai-backend no está disponibleCuando solicito una pista para el puzzle "puzzle-2"Entonces la API retorna 201 igualmenteY el cuerpo contiene la staticHint del blueprint con isDynamic: falseY no se lanza ningún error 5xxAC-GAME-007: Actividad bloqueada por nivel de acceso insuficiente
Section titled “AC-GAME-007: Actividad bloqueada por nivel de acceso insuficiente”US: US-GAME-007
AC-GAME-007.1 — GET actividad deniega acceso con 403
Dado que soy estudiante con rol FREEY la actividad pertenece a un módulo con access_level "PAID_STARTER"Cuando realizo GET /game-activities/:idEntonces la API retorna 403Y el cuerpo contiene { code: "INSUFFICIENT_ACCESS_LEVEL", requiredLevel: "PAID_STARTER" }AC-GAME-007.2 — Intento de inicio también deniega acceso
Dado que el módulo requiere nivel "PAID_PRO"Y soy estudiante con rol FREECuando intento POST /game-activities/:gameActivityId/attemptEntonces la API retorna 403Y no se crea ningún GameAttempt en BDAC-GAME-007.3 — Usuario con nivel suficiente puede acceder
Dado que soy estudiante con rol PAID_STARTERY la actividad requiere nivel "PAID_STARTER"Cuando realizo GET /game-activities/:idEntonces la API retorna 200Y recibo el payload completo de la actividadAC-GAME-008: Recuperar intento activo
Section titled “AC-GAME-008: Recuperar intento activo”US: US-GAME-008
AC-GAME-008.1 — GET active retorna el intento en curso
Dado que tengo un intento con status "IN_PROGRESS" para la actividadCuando realizo GET /game-activities/:gameActivityId/attempt/activeEntonces la API retorna 200Y el cuerpo contiene el GameAttempt con sus completedItemIds, activeItemId y secondsElapsed actualesAC-GAME-008.2 — GET active lanza 404 si no hay intento activo
Dado que no tengo ningún intento IN_PROGRESS para la actividadCuando realizo GET /game-activities/:gameActivityId/attempt/activeEntonces la API retorna 404Y el cliente muestra la pantalla de inicio (no la pantalla de juego)AC-GAME-009: Test de Fluidez IA (FLUENCY_TEST)
Section titled “AC-GAME-009: Test de Fluidez IA (FLUENCY_TEST)”US: US-GAME-009
AC-GAME-009.1 — Submit retorna score, perfil y recomendaciones
Dado que soy visitante o usuario autenticadoCuando realizo POST /fluency-test/submit con body { sessionKey, answers: [{questionIndex, selectedAnswer},...], userId?, role?, industry? }Entonces la API retorna 200Y el cuerpo contiene: score (0–100), profile (array de dimensiones), profileLabel, profileDescription, recommendationsAC-GAME-009.2 — Score sincronizado al perfil si el usuario está autenticado
Dado que envío el test con un userId válidoCuando la API procesa el submitEntonces el campo fluidityScore del User en BD se actualiza con el score calculadoY se envía una notificación al usuario con su nuevo perfilAC-GAME-009.3 — Test anónimo persiste la sesión por sessionKey
Dado que envío el test sin userId (visitante anónimo)Cuando la API procesa el submitEntonces se crea o actualiza un FluidityTestSession con el sessionKey proporcionadoY el campo userId en FluidityTestSession queda como nullTest Cases
Section titled “Test Cases”TC-GAME-001: Cargar payload de una actividad publicada
Section titled “TC-GAME-001: Cargar payload de una actividad publicada”AC: AC-GAME-001.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-activity.spec.ts
Precondiciones: fixtures.starter tiene acceso al módulo de la actividad de prueba.
const res = await apiClient.get(`/game-activities/${fixtures.publishedGameActivityId}`, { headers: { Authorization: `Bearer ${fixtures.starter.token}` },})expect(res.status).toBe(200)expect(res.data).toMatchObject({ id: fixtures.publishedGameActivityId, status: 'PUBLISHED', maxXp: expect.any(Number), type: expect.any(String),})TC-GAME-002: Iniciar primer intento de una actividad
Section titled “TC-GAME-002: Iniciar primer intento de una actividad”AC: AC-GAME-001.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-activity.spec.ts
const res = await apiClient.post( `/game-activities/${fixtures.publishedGameActivityId}/attempt`, {}, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.status).toBe(201)expect(res.data.attempt).toMatchObject({ status: 'IN_PROGRESS', attemptNumber: 1, gameActivityId: fixtures.publishedGameActivityId,})TC-GAME-003: Guardar progreso parcial en intento activo
Section titled “TC-GAME-003: Guardar progreso parcial en intento activo”AC: AC-GAME-002.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-activity.spec.ts
Precondiciones: attemptId de un intento IN_PROGRESS existente.
const res = await apiClient.patch( `/game-activities/attempts/${fixtures.activeAttemptId}/progress`, { activeItemId: 'puzzle-2', completedItemIds: ['puzzle-1'], secondsElapsed: 120, }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.status).toBe(200)expect(res.data.activeItemId).toBe('puzzle-2')expect(res.data.completedItemIds).toContain('puzzle-1')expect(res.data.secondsElapsed).toBe(120)TC-GAME-004: Completar actividad simple y verificar XP emitido
Section titled “TC-GAME-004: Completar actividad simple y verificar XP emitido”AC: AC-GAME-003.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-complete.spec.ts
// 1. Completar el intentoconst completeRes = await apiClient.post( `/game-activities/attempts/${fixtures.activeAttemptId}/complete`, { scorePercent: 80, totalXp: 160, secondsElapsed: 300 }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(completeRes.status).toBe(201)expect(completeRes.data.status).toBe('COMPLETED')expect(completeRes.data.scorePercent).toBe(80)expect(completeRes.data.totalXp).toBe(160)
// 2. Verificar que el XP se registró en el StudentXPEventconst xpRes = await apiClient.get('/xp/summary', { headers: { Authorization: `Bearer ${fixtures.starter.token}` },})expect(xpRes.data.xpTotal).toBeGreaterThanOrEqual(160)TC-GAME-005: No emitir XP cuando totalXp es 0
Section titled “TC-GAME-005: No emitir XP cuando totalXp es 0”AC: AC-GAME-003.3 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-complete.spec.ts
const xpBefore = await getXpTotal(fixtures.starter.token)
await apiClient.post( `/game-activities/attempts/${fixtures.activeAttemptId}/complete`, { scorePercent: 0, totalXp: 0, secondsElapsed: 60 }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)
const xpAfter = await getXpTotal(fixtures.starter.token)// XP total no debe haber cambiadoexpect(xpAfter).toBe(xpBefore)TC-GAME-006: Respuesta de completado incluye datos de resultado
Section titled “TC-GAME-006: Respuesta de completado incluye datos de resultado”AC: AC-GAME-004.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-complete.spec.ts
const res = await apiClient.post( `/game-activities/attempts/${fixtures.activeAttemptId}/complete`, { scorePercent: 90, totalXp: 180, secondsElapsed: 200 }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.data).toMatchObject({ status: 'COMPLETED', scorePercent: 90, totalXp: 180, secondsElapsed: 200,})expect(res.data.completedAt).toBeTruthy()TC-GAME-007: Reintentar actividad incrementa attemptNumber
Section titled “TC-GAME-007: Reintentar actividad incrementa attemptNumber”AC: AC-GAME-005.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/game-retry.spec.ts
// Primer intento completadoawait completeAttempt(fixtures.activeAttemptId, fixtures.starter.token)
// Segundo intentoconst res = await apiClient.post( `/game-activities/${fixtures.publishedGameActivityId}/attempt`, {}, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.status).toBe(201)expect(res.data.attempt.attemptNumber).toBe(2)expect(res.data.attempt.status).toBe('IN_PROGRESS')TC-GAME-008: Pedir pista en Escape Room incrementa hintsUsed
Section titled “TC-GAME-008: Pedir pista en Escape Room incrementa hintsUsed”AC: AC-GAME-006.1, AC-GAME-006.2 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/escape-room-hints.spec.ts
Precondiciones: fixtures.activeEscapeRoomAttemptId con hintsUsed = 0 y puzzle “puzzle-1” disponible.
const res = await apiClient.post( `/game-activities/attempts/${fixtures.activeEscapeRoomAttemptId}/hint`, { itemId: 'puzzle-1', attemptedAnswer: {} }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.status).toBe(201)expect(res.data.hint).toBeTruthy()expect(typeof res.data.isDynamic).toBe('boolean')
// Verificar que hintsUsed se incrementó en el intentoconst attemptRes = await apiClient.get( `/game-activities/${fixtures.publishedEscapeRoomId}/attempt/active`, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(attemptRes.data.hintsUsed).toBe(1)TC-GAME-009: La pista retorna texto aunque el agente IA falle (fallback estático)
Section titled “TC-GAME-009: La pista retorna texto aunque el agente IA falle (fallback estático)”AC: AC-GAME-006.3 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/escape-room-hints.spec.ts
// Con AIBackendClient mockeado para retornar null (timeout simulado):const res = await apiClient.post( `/game-activities/attempts/${fixtures.activeEscapeRoomAttemptId}/hint`, { itemId: 'puzzle-1', attemptedAnswer: null }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(res.status).toBe(201)// staticHint del blueprint (nunca vacío en actividades publicadas)expect(res.data.hint).toBeTruthy()expect(res.data.isDynamic).toBe(false)TC-GAME-010: Acceso denegado (403) a actividad de módulo bloqueado
Section titled “TC-GAME-010: Acceso denegado (403) a actividad de módulo bloqueado”AC: AC-GAME-007.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/access-control.spec.ts
// fixtures.free = usuario con rol FREE// fixtures.paidStarterGameActivityId = actividad en módulo PAID_STARTERconst res = await apiClient.get( `/game-activities/${fixtures.paidStarterGameActivityId}`, { headers: { Authorization: `Bearer ${fixtures.free.token}` } },)expect(res.status).toBe(403)expect(res.data.code).toBe('INSUFFICIENT_ACCESS_LEVEL')expect(res.data.requiredLevel).toBe('PAID_STARTER')TC-GAME-011: Inicio de intento denegado sin nivel de acceso suficiente
Section titled “TC-GAME-011: Inicio de intento denegado sin nivel de acceso suficiente”AC: AC-GAME-007.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/access-control.spec.ts
const res = await apiClient.post( `/game-activities/${fixtures.paidProGameActivityId}/attempt`, {}, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)// starter intenta acceder a actividad de módulo PAID_PROexpect(res.status).toBe(403)expect(res.data.code).toBe('INSUFFICIENT_ACCESS_LEVEL')TC-GAME-012: Recuperar intento activo de Escape Room
Section titled “TC-GAME-012: Recuperar intento activo de Escape Room”AC: AC-GAME-008.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/escape-room-resume.spec.ts
// 1. Crear intento y actualizar progresoconst startRes = await apiClient.post( `/game-activities/${fixtures.publishedEscapeRoomId}/attempt`, {}, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)const attemptId = startRes.data.attempt.id
await apiClient.patch( `/game-activities/attempts/${attemptId}/progress`, { activeItemId: 'puzzle-2', completedItemIds: ['puzzle-1'], secondsElapsed: 90 }, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)
// 2. "Recargar" → recuperar intento activoconst resumeRes = await apiClient.get( `/game-activities/${fixtures.publishedEscapeRoomId}/attempt/active`, { headers: { Authorization: `Bearer ${fixtures.starter.token}` } },)expect(resumeRes.status).toBe(200)expect(resumeRes.data.id).toBe(attemptId)expect(resumeRes.data.activeItemId).toBe('puzzle-2')expect(resumeRes.data.completedItemIds).toContain('puzzle-1')expect(resumeRes.data.secondsElapsed).toBe(90)TC-GAME-013: Submit del test de fluidez retorna score y perfil
Section titled “TC-GAME-013: Submit del test de fluidez retorna score y perfil”AC: AC-GAME-009.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/fluency-test.spec.ts
const sessionKey = `test-session-${Date.now()}`const res = await apiClient.post('/fluency-test/submit', { sessionKey, answers: [ { questionIndex: 0, selectedAnswer: 1 }, { questionIndex: 1, selectedAnswer: 2 }, { questionIndex: 2, selectedAnswer: 1 }, { questionIndex: 3, selectedAnswer: 1 }, { questionIndex: 4, selectedAnswer: 2 }, ],})expect(res.status).toBe(201)expect(res.data.score).toBeGreaterThanOrEqual(0)expect(res.data.score).toBeLessThanOrEqual(100)expect(res.data.profileLabel).toBeTruthy()expect(res.data.recommendations).toHaveLength(3)expect(Array.isArray(res.data.profile)).toBe(true)TC-GAME-014: Submit autenticado sincroniza fluidityScore al perfil
Section titled “TC-GAME-014: Submit autenticado sincroniza fluidityScore al perfil”AC: AC-GAME-009.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/games/fluency-test.spec.ts
const sessionKey = `test-session-auth-${Date.now()}`await apiClient.post('/fluency-test/submit', { sessionKey, userId: fixtures.starter.id, answers: [ { questionIndex: 0, selectedAnswer: 1 }, { questionIndex: 1, selectedAnswer: 2 }, { questionIndex: 2, selectedAnswer: 1 }, { questionIndex: 3, selectedAnswer: 1 }, { questionIndex: 4, selectedAnswer: 2 }, ],})
// Verificar que fluidityScore se actualizó en el perfil del usuarioconst profileRes = await apiClient.get('/users/me', { headers: { Authorization: `Bearer ${fixtures.starter.token}` },})expect(typeof profileRes.data.fluidityScore).toBe('number')expect(profileRes.data.fluidityScore).toBeGreaterThan(0)