Auth Flows
Módulos API: auth, users · Páginas web: LoginPage, RegisterPage, OAuthCallback
Specs Playwright: apps/e2e/tests/auth/ — 📋 pendientes de crear
User Stories
Section titled “User Stories”US-AUTH-001: Registro con email y contraseña
Section titled “US-AUTH-001: Registro con email y contraseña”Como visitante no registrado Quiero crear una cuenta con mi email y una contraseña Para acceder a la plataforma y comenzar a aprender
Criterios: AC-AUTH-001 · Casos: TC-AUTH-001 · Prioridad: Alta · ✅ Implementado
US-AUTH-002: Login con email y contraseña
Section titled “US-AUTH-002: Login con email y contraseña”Como usuario registrado Quiero iniciar sesión con mis credenciales Para acceder a mi cuenta y continuar mi progreso
Criterios: AC-AUTH-002 · Casos: TC-AUTH-003 · Prioridad: Alta · ✅ Implementado
US-AUTH-003: Login con Google
Section titled “US-AUTH-003: Login con Google”Como visitante con cuenta de Google Quiero iniciar sesión con OAuth de Google Para no gestionar una contraseña separada
Criterios: AC-AUTH-003 · Casos: TC-AUTH-005 · Prioridad: Alta · ✅ Implementado
US-AUTH-004: Login con LinkedIn
Section titled “US-AUTH-004: Login con LinkedIn”Como visitante con cuenta de LinkedIn Quiero iniciar sesión con OAuth de LinkedIn Para acceder usando mi identidad profesional
Criterios: AC-AUTH-004 · Casos: TC-AUTH-006 · Prioridad: Media · ✅ Implementado
US-AUTH-005: Recuperación de contraseña
Section titled “US-AUTH-005: Recuperación de contraseña”Como usuario registrado que olvidó su contraseña Quiero recibir un enlace de restablecimiento en mi email Para recuperar acceso sin intervención de soporte
Criterios: AC-AUTH-005 · Casos: TC-AUTH-007 · Prioridad: Alta · ✅ Implementado
US-AUTH-006: Cierre de sesión
Section titled “US-AUTH-006: Cierre de sesión”Como usuario autenticado Quiero cerrar mi sesión en el dispositivo actual Para proteger mi cuenta en dispositivos compartidos
Criterios: AC-AUTH-006 · Casos: TC-AUTH-009 · Prioridad: Media · ✅ Implementado
US-AUTH-007: Persistencia de sesión
Section titled “US-AUTH-007: Persistencia de sesión”Como usuario autenticado que cierra el navegador Quiero que mi sesión persista al volver a abrir la aplicación Para no autenticarme en cada visita
Criterios: AC-AUTH-007 · Casos: TC-AUTH-007 · Prioridad: Media · ✅ Implementado
US-AUTH-008: Redirección al acceder a ruta protegida sin sesión
Section titled “US-AUTH-008: Redirección al acceder a ruta protegida sin sesión”Como visitante no autenticado Quiero ser redirigido al login al intentar acceder a rutas protegidas Para ser guiado hacia la autenticación en lugar de ver un error
Criterios: AC-AUTH-008 · Casos: TC-AUTH-010 · Prioridad: Alta · ✅ Implementado
Acceptance Criteria
Section titled “Acceptance Criteria”AC-AUTH-001: Registro con email y contraseña
Section titled “AC-AUTH-001: Registro con email y contraseña”US: US-AUTH-001
AC-AUTH-001.1 — Registro exitoso
Dado que soy un visitante no registradoCuando introduzco un email único válido y una contraseña que cumple los requisitosY envío el formulario de registroEntonces se crea mi cuenta con rol FREEY soy redirigido al dashboard de estudianteY recibo un email de bienvenidaAC-AUTH-001.2 — Email ya registrado
Dado que ya existe una cuenta con el email "test@example.com"Cuando intento registrarme con ese mismo emailEntonces veo un error indicando que el email ya está registradoY no se crea ninguna cuenta nuevaY la API responde con status 409AC-AUTH-001.3 — Contraseña inválida
Dado que estoy en el formulario de registroCuando introduzco una contraseña que no cumple los requisitos mínimosEntonces veo el mensaje de validación con los requisitosY el formulario no se envíaAC-AUTH-002: Login con email y contraseña
Section titled “AC-AUTH-002: Login con email y contraseña”US: US-AUTH-002
AC-AUTH-002.1 — Login exitoso
Dado que tengo una cuenta registrada con email y contraseñaCuando introduzco mis credenciales correctasEntonces inicio sesión y soy redirigido al dashboardY el token JWT queda almacenado en el clienteAC-AUTH-002.2 — Credenciales incorrectas (anti-enumeración)
Dado que introduzco una contraseña incorrecta para un email existenteEntonces recibo el mensaje "Credenciales incorrectas"Y no se inicia sesiónY el mensaje es idéntico al que se muestra para email inexistenteAC-AUTH-003: Login con Google
Section titled “AC-AUTH-003: Login con Google”US: US-AUTH-003
AC-AUTH-003.1 — Primera vez (auto-registro)
Dado que soy visitante sin cuenta previaCuando autorizo el acceso con GoogleEntonces se crea una cuenta con mi email de Google y rol FREEY soy redirigido al dashboardAC-AUTH-003.2 — Cuenta existente (vinculación)
Dado que tengo cuenta previa con el mismo email de GoogleCuando autorizo el acceso con GoogleEntonces inicio sesión en mi cuenta existente sin crear una nuevaAC-AUTH-003.3 — Usuario cancela el popup
Dado que hago clic en "Continuar con Google"Cuando cancelo el popup de autorizaciónEntonces vuelvo al login sin error críticoAC-AUTH-004: Login con LinkedIn
Section titled “AC-AUTH-004: Login con LinkedIn”US: US-AUTH-004
AC-AUTH-004.1 — Primera vez
Dado que soy visitante sin cuenta previaCuando autorizo el acceso con LinkedInEntonces se crea una cuenta con mi email de LinkedIn y rol FREEAC-AUTH-004.2 — Cuenta existente
Dado que tengo cuenta con el mismo email de LinkedInCuando autorizo el accesoEntonces inicio sesión en mi cuenta existenteAC-AUTH-005: Recuperación de contraseña
Section titled “AC-AUTH-005: Recuperación de contraseña”US: US-AUTH-005
AC-AUTH-005.1 — Solicitud con email registrado
Dado que tengo cuenta con email y contraseñaCuando solicito restablecer contraseña con mi emailEntonces recibo el mensaje de confirmación genéricoY recibo un email con enlace de un solo uso válido 60 minutosAC-AUTH-005.2 — Email no registrado (anti-enumeración)
Dado que introduzco un email no registradoEntonces veo el mismo mensaje de confirmación que para email existenteY no se envía ningún emailAC-AUTH-005.3 — Restablecimiento exitoso con token válido
Dado que tengo un enlace de restablecimiento válidoCuando introduzco y confirmo una nueva contraseña válidaEntonces mi contraseña se actualizaY el enlace queda invalidado (no se puede reusar)Y soy redirigido al loginAC-AUTH-005.4 — Token expirado
Dado que el enlace ha superado los 60 minutos de validezCuando intento acceder a élEntonces veo el mensaje de enlace expiradoY se me ofrece solicitar uno nuevoAC-AUTH-006: Cierre de sesión
Section titled “AC-AUTH-006: Cierre de sesión”US: US-AUTH-006
AC-AUTH-006.1 — Logout exitoso
Dado que tengo sesión activaCuando hago clic en "Cerrar sesión"Entonces mi sesión queda invalidada en el clienteY soy redirigido a la página pública o al loginAC-AUTH-006.2 — Rutas protegidas bloqueadas tras logout
Dado que acabo de cerrar sesiónCuando navego a "/dashboard"Entonces soy redirigido al loginY no veo ningún dato de la sesión anteriorAC-AUTH-007: Persistencia de sesión
Section titled “AC-AUTH-007: Persistencia de sesión”US: US-AUTH-007
AC-AUTH-007.1 — Sesión persiste tras cerrar el navegador
Dado que tengo sesión activaCuando cierro y vuelvo a abrir la pestaña con la misma URLEntonces sigo autenticado sin necesidad de hacer loginAC-AUTH-007.2 — Sesión expirada redirige al login
Dado que mi token JWT ha expiradoCuando intento acceder a cualquier ruta protegidaEntonces soy redirigido al loginAC-AUTH-008: Redirección de rutas protegidas
Section titled “AC-AUTH-008: Redirección de rutas protegidas”US: US-AUTH-008
AC-AUTH-008.1 — Redirección con returnUrl
Dado que soy visitante no autenticadoCuando accedo directamente a "/dashboard/courses"Entonces soy redirigido a "/login?returnUrl=/dashboard/courses"Y tras autenticarme voy a la URL originalAC-AUTH-008.2 — Rutas públicas accesibles sin auth
Dado que soy visitante no autenticadoCuando accedo a "/", "/login", "/register" o "/verify/{uuid}"Entonces puedo verlas sin redirecciónAC-AUTH-008.3 — Portal de rol incorrecto denegado
Dado que soy usuario con rol FREECuando accedo directamente a "/admin"Entonces veo pantalla de acceso denegado o soy redirigidoY no veo ningún dato del panel adminTest Cases
Section titled “Test Cases”TC-AUTH-001: Registro exitoso
Section titled “TC-AUTH-001: Registro exitoso”AC: AC-AUTH-001.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/register.spec.ts
Precondiciones: El email de prueba no existe en la BD (usar email único por ejecución: qa-${Date.now()}@nappai.test)
Pasos:
- Navegar a
/register - Introducir email único válido
- Introducir contraseña válida (≥8 chars, 1 mayúscula, 1 número)
- Enviar formulario
Resultado esperado: Redirige a /dashboard, API retorna 201, usuario tiene rol FREE
// data-testid a confirmar en el componente:// 'register-email', 'register-password', 'register-submit', 'register-error'const email = `qa-test-${Date.now()}@nappai.test`TC-AUTH-002: Registro rechazado — email duplicado
Section titled “TC-AUTH-002: Registro rechazado — email duplicado”AC: AC-AUTH-001.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/register.spec.ts
const res = await apiClient.post('/auth/register', { email: fixtures.free.email, password: 'ValidPass123!'})expect(res.status).toBe(409)TC-AUTH-003: Login exitoso
Section titled “TC-AUTH-003: Login exitoso”AC: AC-AUTH-002.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/login.spec.ts
Precondiciones: fixtures.free existe con credenciales email/password
// data-testid: 'login-email', 'login-password', 'login-submit'TC-AUTH-004: Login rechazado — credenciales incorrectas
Section titled “TC-AUTH-004: Login rechazado — credenciales incorrectas”AC: AC-AUTH-002.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient.post('/auth/login', { email: fixtures.free.email, password: 'WrongPassword!'})expect(res.status).toBe(401)// Verificar que el mensaje NO revela si el email existe:expect(res.data.message).not.toMatch(/email|usuario/)TC-AUTH-005: Login Google — primera vez
Section titled “TC-AUTH-005: Login Google — primera vez”AC: AC-AUTH-003.1 · Prioridad: Alta · Tipo: Manual · Estado: ⚠️ Manual Nota: No automatizable directamente (popup real de Google). Considerar interceptar el callback OAuth.
TC-AUTH-006: Login LinkedIn — primera vez
Section titled “TC-AUTH-006: Login LinkedIn — primera vez”AC: AC-AUTH-004.1 · Prioridad: Media · Tipo: Manual · Estado: ⚠️ Manual
TC-AUTH-007: Recuperación de contraseña — flujo completo
Section titled “TC-AUTH-007: Recuperación de contraseña — flujo completo”AC: AC-AUTH-005.1 + AC-AUTH-005.3 · Prioridad: Alta · Tipo: API + Manual · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/password-reset.spec.ts
// Step 1: solicitar resetconst req = await apiClient.post('/auth/forgot-password', { email: fixtures.free.email })expect(req.status).toBe(200)// Step 2: obtener token (via BD de test o email mock)// Step 3: usar el tokenconst reset = await apiClient.post('/auth/reset-password', { token, password: 'NewPass123!' })expect(reset.status).toBe(200)// Step 4: verificar que el token no se puede reusarconst reuse = await apiClient.post('/auth/reset-password', { token, password: 'AnotherPass123!' })expect(reuse.status).toBe(400)TC-AUTH-008: Token de restablecimiento expirado
Section titled “TC-AUTH-008: Token de restablecimiento expirado”AC: AC-AUTH-005.4 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient.post('/auth/reset-password', { token: 'expired-token-xxx', password: 'NewPass123!' })expect(res.status).toBe(400)TC-AUTH-009: Logout invalida la sesión
Section titled “TC-AUTH-009: Logout invalida la sesión”AC: AC-AUTH-006.2 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/logout.spec.ts
// data-testid: 'user-menu', 'logout-button'await loginAs(page, fixtures.free)await page.getByTestId('user-menu').click()await page.getByTestId('logout-button').click()await page.goto('/dashboard')await expect(page).toHaveURL(/\/login/)TC-AUTH-010: Redirección al login con returnUrl
Section titled “TC-AUTH-010: Redirección al login con returnUrl”AC: AC-AUTH-008.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/auth/protected-routes.spec.ts
const page = await context.newPage() // sin sesiónawait page.goto('/dashboard/courses')await expect(page).toHaveURL(/\/login/)await expect(page).toHaveURL(/returnUrl/)