Skip to content

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


Tipo (GameActivityType)Módulo APIPlayer Page
ESCAPE_ROOMescape-roomEscapeRoomPlayerPage
FLASHCARDSflashcardsFlashcardsPlayerPage
QUIZquizQuizPage
CONVERSATIONAL_QUIZconversational-quizConversationalQuizPage
CROSSWORDcrosswordCrosswordPlayerPage
DRAG_AND_DROPdrag-and-dropDragAndDropPlayerPage
HANGMANhangmanHangmanPlayerPage
MATCHING_PAIRSmatching-pairsMatchingPairsPlayerPage
MEMORY_MATCHmemory-matchMemoryMatchPlayerPage
SCENARIO_CHALLENGEscenario-challengeScenarioChallengePlayerPage
SPEED_QUIZspeed-quizSpeedQuizPlayerPage
TIMELINE_CHALLENGEtimeline-challengeTimelineChallengePlayerPage
TRIVIAtriviaTriviaPlayerPage
WORD_PUZZLEword-puzzleWordPuzzlePlayerPage
ADVENTUREadventureAdventurePlayerPage
FLUENCY_TESTfluency-testFluidityTestPage

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)

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


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


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ódulo
Cuando realizo GET /game-activities/:id
Entonces la API retorna 200
Y el cuerpo incluye: id, type, title, description, status, maxXp, timerSeconds, payload
Y 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 acceso
Cuando realizo POST /game-activities/:gameActivityId/attempt
Entonces la API retorna 201
Y el cuerpo contiene { attempt: { id, status: "IN_PROGRESS", attemptNumber, ... }, blueprint }
Y el campo attemptNumber es 1 si es el primer intento

AC-GAME-001.3 — Reutilización de intento en curso

Dado que ya tengo un intento con status "IN_PROGRESS" para esa actividad
Cuando realizo POST /game-activities/:gameActivityId/attempt de nuevo
Entonces la API retorna el intento existente (no crea uno nuevo)
Y el attemptNumber no incrementa

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 200
Y el intento actualizado refleja los nuevos valores de activeItemId, completedItemIds y secondsElapsed
Y el campo lastActivityAt se actualiza

AC-GAME-002.2 — Campos opcionales (actualización parcial)

Dado que tengo un intento activo
Cuando envío PATCH /progress con solo { secondsElapsed: 45 }
Entonces únicamente se actualiza ese campo
Y activeItemId y completedItemIds mantienen sus valores anteriores

AC-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 ADVENTURE
Cuando realizo POST /game-activities/attempts/:attemptId/complete
con body { scorePercent: 80, totalXp: 160, secondsElapsed: 240 }
Entonces la API retorna 200
Y 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 intento
Entonces el StudentXPEvent refleja el totalXp enviado por el cliente
Y el XP global del usuario aumenta en la cantidad correspondiente

AC-GAME-003.3 — No se emite XP si totalXp es 0

Dado que completo una actividad con totalXp: 0
Entonces NO se crea ningún StudentXPEvent
Y el intento se marca como "COMPLETED" igualmente

US: US-GAME-004

AC-GAME-004.1 — Respuesta de completado incluye datos de resultado

Dado que completo una actividad de tipo simple
Cuando realizo POST /attempts/:attemptId/complete
Entonces la respuesta incluye scorePercent, totalXp, completedAt y secondsElapsed

AC-GAME-004.2 — Respuesta de completado de Escape Room incluye datos de puzzle

Dado que resuelvo el último puzzle de un Escape Room
Cuando la API retorna la respuesta de submitAnswer
Entonces el cuerpo incluye { isCorrect: true, xpEarned, attemptCompleted: true, scorePercent }
Y el intento en BD tiene status "COMPLETED"

US: US-GAME-005

AC-GAME-005.1 — Nuevo intento crea registro independiente

Dado que ya tengo un intento "COMPLETED" para una actividad
Cuando realizo POST /game-activities/:gameActivityId/attempt de nuevo
Entonces la API crea un nuevo GameAttempt con attemptNumber = 2
Y el nuevo intento tiene status "IN_PROGRESS"
Y el intento anterior no se modifica

AC-GAME-005.2 — attemptNumber se incrementa correctamente

Dado que tengo 2 intentos previos (numbers 1 y 2)
Cuando inicio un tercer intento
Entonces el nuevo intento tiene attemptNumber = 3

US: US-GAME-006

AC-GAME-006.1 — Pista retorna texto contextual

Dado que tengo un intento activo de Escape Room
Y 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 201
Y 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 = 0
Cuando solicito una pista
Entonces el intento en BD actualiza hintsUsed a 1
Y lastActivityAt se renueva

AC-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á disponible
Cuando solicito una pista para el puzzle "puzzle-2"
Entonces la API retorna 201 igualmente
Y el cuerpo contiene la staticHint del blueprint con isDynamic: false
Y no se lanza ningún error 5xx

AC-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 FREE
Y la actividad pertenece a un módulo con access_level "PAID_STARTER"
Cuando realizo GET /game-activities/:id
Entonces la API retorna 403
Y 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 FREE
Cuando intento POST /game-activities/:gameActivityId/attempt
Entonces la API retorna 403
Y no se crea ningún GameAttempt en BD

AC-GAME-007.3 — Usuario con nivel suficiente puede acceder

Dado que soy estudiante con rol PAID_STARTER
Y la actividad requiere nivel "PAID_STARTER"
Cuando realizo GET /game-activities/:id
Entonces la API retorna 200
Y recibo el payload completo de la actividad

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 actividad
Cuando realizo GET /game-activities/:gameActivityId/attempt/active
Entonces la API retorna 200
Y el cuerpo contiene el GameAttempt con sus completedItemIds, activeItemId y secondsElapsed actuales

AC-GAME-008.2 — GET active lanza 404 si no hay intento activo

Dado que no tengo ningún intento IN_PROGRESS para la actividad
Cuando realizo GET /game-activities/:gameActivityId/attempt/active
Entonces la API retorna 404
Y 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 autenticado
Cuando realizo POST /fluency-test/submit
con body { sessionKey, answers: [{questionIndex, selectedAnswer},...], userId?, role?, industry? }
Entonces la API retorna 200
Y el cuerpo contiene: score (0–100), profile (array de dimensiones), profileLabel, profileDescription, recommendations

AC-GAME-009.2 — Score sincronizado al perfil si el usuario está autenticado

Dado que envío el test con un userId válido
Cuando la API procesa el submit
Entonces el campo fluidityScore del User en BD se actualiza con el score calculado
Y se envía una notificación al usuario con su nuevo perfil

AC-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 submit
Entonces se crea o actualiza un FluidityTestSession con el sessionKey proporcionado
Y el campo userId en FluidityTestSession queda como null

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 intento
const 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 StudentXPEvent
const 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 cambiado
expect(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 completado
await completeAttempt(fixtures.activeAttemptId, fixtures.starter.token)
// Segundo intento
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.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 intento
const 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_STARTER
const 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_PRO
expect(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 progreso
const 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 activo
const 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 usuario
const 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)