Skip to content

Mentoría

Módulo API: mentorship · Página web: MyMentorshipPage · Ruta: /mentorship Componentes: MentorshipThread, ReferenceCard RTK Query: mentorshipApi (apps/web/src/store/api/mentorshipApi.ts) Spec Playwright: apps/e2e/tests/student/mentorship.spec.ts — 📋 pendiente

MétodoEndpointDescripción
GET/mentorship/threadsListar todos los hilos del usuario autenticado (como mentor o estudiante)
GET/mentorship/threads/:id/messagesMensajes paginados de un hilo (?page=1&limit=20)
POST/mentorship/threads/:id/messagesEnviar mensaje en un hilo (con referencia opcional)
PATCH/mentorship/threads/:id/readMarcar mensajes como leídos hasta un upToMessageId
POST/mentorship/threadsCrear nuevo hilo (solo para mentores / CONTENT_ADMIN)
GET/mentorship/my-threadsBuscar hilo por referencia de evaluación (?refEval=<evalId>)

US-MENT-001: Ver lista de conversaciones con mis instructores

Section titled “US-MENT-001: Ver lista de conversaciones con mis instructores”

Como estudiante autenticado quiero ver todos mis hilos de mentoría activos para saber si tengo mensajes nuevos de mis instructores y acceder a cada conversación.

Módulo: mentorship · Endpoint: GET /mentorship/threads Página: MyMentorshipPage (tab “Conversaciones”) Estado: Implementado Prioridad: Alta

AC-MENT-001.1 — Lista de hilos ordenada por actividad reciente

Section titled “AC-MENT-001.1 — Lista de hilos ordenada por actividad reciente”
Given el estudiante está autenticado y tiene hilos de mentoría
When navega a "/mentorship"
Then ve la lista de conversaciones ordenadas por "lastMessageAt" (más reciente primero)
And cada item muestra el nombre del instructor, el preview del último mensaje y el tiempo relativo

AC-MENT-001.2 — Badge de mensajes no leídos

Section titled “AC-MENT-001.2 — Badge de mensajes no leídos”
Given un hilo tiene mensajes no leídos por el estudiante (unreadByStudent > 0)
When el estudiante ve la lista de conversaciones
Then el item del hilo muestra un badge con el contador de no leídos
And el nombre del instructor aparece en negrita (fontWeight 700)
And el tab "Conversaciones" en la barra superior muestra el total de no leídos
Given el estudiante tiene hilos con y sin mensajes no leídos
When hace clic en el filtro "No leídas"
Then la lista solo muestra hilos con unreadByStudent > 0
And si no hay no leídas, la lista queda vacía

AC-MENT-001.4 — Estado vacío cuando no hay conversaciones

Section titled “AC-MENT-001.4 — Estado vacío cuando no hay conversaciones”
Given el estudiante no tiene hilos de mentoría asignados
When navega a "/mentorship"
Then ve el estado vacío "Sin conversaciones" con el mensaje explicativo
And se indica que "Cuando un mentor inicie una conversación contigo, aparecerá aquí"

US-MENT-002: Leer mensajes de un hilo de mentoría

Section titled “US-MENT-002: Leer mensajes de un hilo de mentoría”

Como estudiante quiero abrir una conversación con mi instructor para leer sus mensajes y el historial completo del hilo.

Módulo: mentorship · Endpoint: GET /mentorship/threads/:id/messages Componente: MentorshipThread Estado: Implementado Prioridad: Alta

AC-MENT-002.1 — Mensajes con separadores de fecha y de no leídos

Section titled “AC-MENT-002.1 — Mensajes con separadores de fecha y de no leídos”
Given el estudiante selecciona un hilo de la lista
When se cargan los mensajes del hilo
Then los mensajes aparecen ordenados cronológicamente (más antiguo primero)
And se insertan separadores de fecha cuando cambia el día
And si hay mensajes no leídos, aparece un separador "N mensajes nuevos" antes del primer no leído

AC-MENT-002.2 — Mensajes propios distinguibles visualmente

Section titled “AC-MENT-002.2 — Mensajes propios distinguibles visualmente”
Given hay mensajes del estudiante y del instructor en el hilo
When el estudiante ve los mensajes
Then sus propios mensajes aparecen alineados a la derecha con fondo morado (var(--pr))
And los mensajes del instructor aparecen alineados a la izquierda con fondo claro (var(--bg3))

AC-MENT-002.3 — Scroll automático al mensaje más reciente

Section titled “AC-MENT-002.3 — Scroll automático al mensaje más reciente”
Given el estudiante abre un hilo con mensajes
When se completa la carga de mensajes
Then la vista hace scroll automáticamente al último mensaje
And cuando llega un nuevo mensaje al hilo activo, la vista vuelve a hacer scroll al final

AC-MENT-002.4 — Marca automática de mensajes como leídos

Section titled “AC-MENT-002.4 — Marca automática de mensajes como leídos”
Given el estudiante abre un hilo con mensajes no leídos del instructor
When se carga el componente MentorshipThread con viewerRole "STUDENT"
Then se llama a PATCH /mentorship/threads/:id/read con el id del último mensaje
And el contador unreadByStudent se resetea a 0 en la lista de hilos

US-MENT-003: Enviar mensajes al instructor

Section titled “US-MENT-003: Enviar mensajes al instructor”

Como estudiante quiero escribir y enviar mensajes a mi instructor en el hilo de mentoría para plantear dudas, pedir ayuda o dar seguimiento a una evaluación.

Módulo: mentorship · Endpoint: POST /mentorship/threads/:id/messages Componente: Composer (dentro de MentorshipThread) Estado: Implementado Prioridad: Alta

AC-MENT-003.1 — Envío de mensaje con botón o atajo de teclado

Section titled “AC-MENT-003.1 — Envío de mensaje con botón o atajo de teclado”
Given el estudiante está en un hilo de mentoría activo
When escribe texto en el compositor y hace clic en "Enviar" o presiona Ctrl+Enter
Then el mensaje se envía a POST /mentorship/threads/:id/messages
And el campo de texto queda vacío
And el nuevo mensaje aparece en la lista con alineación derecha

AC-MENT-003.2 — Botón deshabilitado con mensaje vacío

Section titled “AC-MENT-003.2 — Botón deshabilitado con mensaje vacío”
Given el compositor de mensajes está vacío o contiene solo espacios
When el estudiante intenta hacer clic en "Enviar"
Then el botón aparece deshabilitado (cursor not-allowed, fondo gris)
And no se realiza ninguna petición a la API

AC-MENT-003.3 — Notificación al instructor tras envío

Section titled “AC-MENT-003.3 — Notificación al instructor tras envío”
Given el estudiante envía un mensaje en un hilo
When el backend persiste el mensaje
Then se dispara una notificación STUDENT_MESSAGE al instructor (mentorId del hilo)
And la URL de acción de la notificación apunta a "/creator/mentorship"

US-MENT-004: Ver tarjetas de referencia adjuntas a mensajes

Section titled “US-MENT-004: Ver tarjetas de referencia adjuntas a mensajes”

Como estudiante quiero ver las tarjetas contextuales que el instructor adjunta a sus mensajes para entender a qué evaluación, lección o progreso se refiere cada mensaje.

Componente: ReferenceCard Estado: Implementado Prioridad: Media

AC-MENT-004.1 — Tipos de referencia soportados

Section titled “AC-MENT-004.1 — Tipos de referencia soportados”
Given un mensaje tiene un refType asociado
When el estudiante ve el mensaje
Then si refType = "QUIZ_ATTEMPT" muestra "Sobre tu evaluación"
And si refType = "LESSON" muestra "Sobre la lección"
And si refType = "PATH_PROGRESS" muestra "Progreso en"
And si refType = "MANUAL_EVAL" muestra "Evaluación manual recibida" con fondo verde

AC-MENT-004.2 — Visualización de puntuación en referencias de evaluación

Section titled “AC-MENT-004.2 — Visualización de puntuación en referencias de evaluación”
Given un mensaje tiene refType "MANUAL_EVAL" con refMeta { score, passed }
When el estudiante ve la tarjeta de referencia
Then muestra la puntuación numérica con color verde (aprobado) o rojo (no aprobado)
And el borde de la tarjeta es verde para evaluaciones manuales recibidas

US-MENT-005: Distinguir tipo de contexto de la mentoría

Section titled “US-MENT-005: Distinguir tipo de contexto de la mentoría”

Como estudiante quiero saber si mi mentoría es de tipo “Ruta” o “Corporativa” para entender el origen de la relación con mi instructor.

Estado: Implementado Prioridad: Baja

AC-MENT-005.1 — Etiqueta de contexto en el item de hilo

Section titled “AC-MENT-005.1 — Etiqueta de contexto en el item de hilo”
Given un hilo tiene contextType definido
When el estudiante ve la lista de hilos
Then si contextType = "ORG_MEMBERSHIP" muestra la etiqueta "Corporativo" en morado
And si contextType = "CREATOR_PATH" muestra la etiqueta "Ruta" en morado

AC-MENT-005.2 — Subtítulo de contexto en cabecera del hilo activo

Section titled “AC-MENT-005.2 — Subtítulo de contexto en cabecera del hilo activo”
Given el estudiante abre un hilo de mentoría
When se muestra la cabecera del hilo activo (panel derecho)
Then muestra "Mentoría corporativa" si contextType = "ORG_MEMBERSHIP"
And muestra "Mentoría de ruta" si contextType = "CREATOR_PATH"

US-MENT-006: Recibir notificación cuando me asignan un instructor

Section titled “US-MENT-006: Recibir notificación cuando me asignan un instructor”

Como estudiante quiero recibir una notificación cuando un instructor inicia un hilo de mentoría conmigo para saber que ya tengo un mentor asignado y acceder a la conversación.

Módulo: mentorship · Trigger: POST /mentorship/threads (desde el creator) Tipo de notificación: MENTOR_ASSIGNED Estado: Implementado Prioridad: Alta

AC-MENT-006.1 — Notificación enviada al crear el hilo

Section titled “AC-MENT-006.1 — Notificación enviada al crear el hilo”
Given un instructor crea un hilo de mentoría con un estudiante
When el hilo se persiste en base de datos
Then el sistema envía una notificación MENTOR_ASSIGNED al studentId del hilo
And el título es "Se te ha asignado un instructor"
And la URL de acción es "/mentorship"
And el canal incluye "in_app" y "email" (disableable: false)

AC-MENT-006.2 — No se crea hilo duplicado

Section titled “AC-MENT-006.2 — No se crea hilo duplicado”
Given ya existe un hilo activo (isArchived: false) entre el mismo mentor y estudiante
When el instructor intenta crear otro hilo con el mismo par mentor-estudiante
Then el sistema devuelve el hilo existente en lugar de crear uno nuevo
And no se envía una segunda notificación MENTOR_ASSIGNED

US-MENT-007: Recibir notificación de nuevo mensaje del instructor

Section titled “US-MENT-007: Recibir notificación de nuevo mensaje del instructor”

Como estudiante quiero recibir una notificación en tiempo real cuando mi instructor me envía un mensaje para poder responder sin tener que revisar la página manualmente.

Tipo de notificación: INSTRUCTOR_MESSAGE Estado: Implementado Prioridad: Alta

AC-MENT-007.1 — Notificación in-app y push al recibir mensaje del instructor

Section titled “AC-MENT-007.1 — Notificación in-app y push al recibir mensaje del instructor”
Given el instructor (mentorId) envía un mensaje en un hilo
When el backend persiste el mensaje en MentorshipMessage con authorRole "MENTOR"
Then se envía una notificación INSTRUCTOR_MESSAGE al studentId del hilo
And el título es "Nuevo mensaje de tu instructor"
And el body contiene el texto del mensaje (truncado a 80 caracteres si es más largo)
And la URL de acción es "/mentorship"
And se activa deduplicación de 24h por userId + type + metadata

AC-MENT-007.2 — Evento WebSocket para actualización en tiempo real

Section titled “AC-MENT-007.2 — Evento WebSocket para actualización en tiempo real”
Given el estudiante tiene la página /mentorship abierta en el hilo activo
When el instructor envía un mensaje
Then se emite el evento "mentorship.message.sent" vía WebSocket
And el nuevo mensaje aparece en el hilo sin recargar la página
And el contador de no leídos del hilo se actualiza

US-MENT-008: Ver evaluaciones manuales de mis quizzes

Section titled “US-MENT-008: Ver evaluaciones manuales de mis quizzes”

Como estudiante quiero consultar el estado y resultados de mis evaluaciones manuales para conocer mi puntuación, el feedback del instructor y el historial de intentos.

Módulo: manual-evaluation (datos) + MentorshipPage (visualización) Endpoint: GET /manual-eval/student (via manualEvalApi) Estado: Implementado Prioridad: Alta

AC-MENT-008.1 — Tab “Evaluaciones” con estado actual

Section titled “AC-MENT-008.1 — Tab “Evaluaciones” con estado actual”
Given el estudiante tiene evaluaciones manuales asignadas
When navega a "/mentorship" y hace clic en la pestaña "Evaluaciones"
Then ve una lista de tarjetas agrupadas por quiz
And cada tarjeta muestra el estado: "En revisión" (amber), "Evaluación recibida" (verde) o "Evaluado por IA" (azul)
And el badge del tab muestra el número de evaluaciones no vistas (nuevas)

AC-MENT-008.2 — Detalle de evaluación con puntuación y feedback

Section titled “AC-MENT-008.2 — Detalle de evaluación con puntuación y feedback”
Given el estudiante selecciona una evaluación en la lista
When se muestra el panel de detalle (EvalDetail)
Then ve la puntuación sobre 100, el estado aprobado/no aprobado y el nombre del evaluador si es manual
And si hay feedback del instructor, se muestra en un bloque de texto
And si hay múltiples intentos del mismo quiz, aparece el historial cronológico de intentos

AC-MENT-008.3 — Estado “En revisión” con estimación de tiempo

Section titled “AC-MENT-008.3 — Estado “En revisión” con estimación de tiempo”
Given una evaluación tiene status "PENDING" o "IN_REVIEW"
When el estudiante ve el detalle de esa evaluación
Then se muestra el mensaje "Tu instructor revisará tu respuesta en breve"
And se indica "Tiempo estimado: menos de 48h"
And no se muestra puntuación hasta que la evaluación esté completada

AC-MENT-008.4 — Diferenciación entre evaluación manual e IA

Section titled “AC-MENT-008.4 — Diferenciación entre evaluación manual e IA”
Given hay evaluaciones con evaluationSource "MANUAL" y con "AI"
When el estudiante ve la lista de evaluaciones
Then las evaluaciones manuales muestran el icono "👤" y el label "Evaluación recibida"
And las evaluaciones de IA muestran el icono "🧠" y el label "Evaluado por IA"
And en el detalle, las manuales muestran el nombre del evaluador

US-MENT-009: Acceder a mentoría solo como usuario autenticado

Section titled “US-MENT-009: Acceder a mentoría solo como usuario autenticado”

Como sistema quiero que todas las rutas de mentoría requieran autenticación para proteger la privacidad de las conversaciones y evaluaciones.

Guard: JwtAuthGuard en MentorshipController Estado: Implementado Prioridad: Alta (seguridad)

AC-MENT-009.1 — Acceso denegado a usuarios anónimos

Section titled “AC-MENT-009.1 — Acceso denegado a usuarios anónimos”
Given un usuario no autenticado intenta acceder a cualquier endpoint de /mentorship
When el servidor procesa la petición
Then devuelve HTTP 401 Unauthorized
And el frontend redirige a la página de login

AC-MENT-009.2 — Solo participantes del hilo pueden leer sus mensajes

Section titled “AC-MENT-009.2 — Solo participantes del hilo pueden leer sus mensajes”
Given un usuario autenticado intenta acceder a mensajes de un hilo en el que no participa
When llama a GET /mentorship/threads/:id/messages
Then el servidor devuelve HTTP 403 Forbidden con el mensaje "You do not have access to this thread"

TC-MENT-001 — Carga de hilos de mentoría con badges de no leídos

Section titled “TC-MENT-001 — Carga de hilos de mentoría con badges de no leídos”

Cubre: AC-MENT-001.1, AC-MENT-001.2 Tipo: E2E

test('TC-MENT-001: muestra lista de hilos ordenada con badges de no leídos', async ({ page }) => {
// Arrange — usuario con dos hilos, uno con mensajes no leídos
await page.goto('/mentorship')
// Act — esperar carga
await page.waitForSelector('[data-testid="thread-list"]')
// Assert — primer hilo es el más reciente
const items = page.locator('[data-testid="thread-item"]')
await expect(items).toHaveCount(2)
// El hilo con unread tiene badge visible
const firstItem = items.first()
await expect(firstItem.locator('[data-testid="unread-badge"]')).toBeVisible()
// El nombre del instructor en el hilo no leído tiene font-weight bold
await expect(firstItem.locator('[data-testid="mentor-name"]')).toHaveCSS('font-weight', '700')
})

TC-MENT-002 — Filtro “No leídas” filtra correctamente

Section titled “TC-MENT-002 — Filtro “No leídas” filtra correctamente”

Cubre: AC-MENT-001.3 Tipo: E2E

test('TC-MENT-002: filtro "No leídas" muestra solo hilos con unread > 0', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
await page.waitForSelector('[data-testid="thread-item"]')
// Act
await page.click('[data-testid="filter-unread"]')
// Assert — solo deben aparecer hilos con mensajes no leídos
const items = page.locator('[data-testid="thread-item"]')
for (let i = 0; i < await items.count(); i++) {
await expect(items.nth(i).locator('[data-testid="unread-badge"]')).toBeVisible()
}
})

TC-MENT-003 — Abrir hilo carga mensajes con separadores y hace scroll al final

Section titled “TC-MENT-003 — Abrir hilo carga mensajes con separadores y hace scroll al final”

Cubre: AC-MENT-002.1, AC-MENT-002.3 Tipo: E2E

test('TC-MENT-003: apertura de hilo carga mensajes con separadores de fecha', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
await page.waitForSelector('[data-testid="thread-item"]')
// Act — seleccionar el primer hilo
await page.locator('[data-testid="thread-item"]').first().click()
// Assert — mensajes visibles
await expect(page.locator('[data-testid="message-bubble"]').first()).toBeVisible()
// Separadores de fecha presentes
await expect(page.locator('[data-testid="date-separator"]').first()).toBeVisible()
// El viewport está en el final del scroll (último mensaje visible)
const lastBubble = page.locator('[data-testid="message-bubble"]').last()
await expect(lastBubble).toBeInViewport()
})

TC-MENT-004 — Mensajes propios alineados a la derecha, los del instructor a la izquierda

Section titled “TC-MENT-004 — Mensajes propios alineados a la derecha, los del instructor a la izquierda”

Cubre: AC-MENT-002.2 Tipo: E2E

test('TC-MENT-004: mensajes propios a la derecha, del instructor a la izquierda', async ({ page }) => {
// Arrange — hilo con mensajes de ambas partes
await page.goto('/mentorship')
await page.locator('[data-testid="thread-item"]').first().click()
await page.waitForSelector('[data-testid="message-bubble"]')
// Assert — mensajes del estudiante (isSelf=true) tienen justify-content: row-reverse
const selfMessage = page.locator('[data-testid="message-self"]').first()
await expect(selfMessage).toHaveCSS('flex-direction', 'row-reverse')
// Mensajes del instructor tienen dirección normal
const instructorMessage = page.locator('[data-testid="message-other"]').first()
await expect(instructorMessage).toHaveCSS('flex-direction', 'row')
})

TC-MENT-005 — Marcar mensajes como leídos al abrir el hilo

Section titled “TC-MENT-005 — Marcar mensajes como leídos al abrir el hilo”

Cubre: AC-MENT-002.4 Tipo: Integration (API)

test('TC-MENT-005: abrir hilo con no leídos llama al endpoint markRead', async ({ page, request }) => {
// Arrange — autenticar y obtener un hilo con unread > 0
const token = await getAuthToken(request)
const threads = await request.get('/api/mentorship/threads', {
headers: { Authorization: `Bearer ${token}` },
})
const [threadWithUnread] = (await threads.json()).filter((t: any) => t.unreadByStudent > 0)
expect(threadWithUnread).toBeDefined()
// Act — abrir el hilo en el frontend
await page.goto('/mentorship')
await page.locator(`[data-testid="thread-item"][data-id="${threadWithUnread.id}"]`).click()
// Assert — el hilo ya no tiene no leídos (la API fue llamada)
await page.waitForTimeout(500) // esperar la mutación
const updated = await request.get('/api/mentorship/threads', {
headers: { Authorization: `Bearer ${token}` },
})
const updatedThread = (await updated.json()).find((t: any) => t.id === threadWithUnread.id)
expect(updatedThread.unreadByStudent).toBe(0)
})

TC-MENT-006 — Envío de mensaje con Ctrl+Enter

Section titled “TC-MENT-006 — Envío de mensaje con Ctrl+Enter”

Cubre: AC-MENT-003.1 Tipo: E2E

test('TC-MENT-006: Ctrl+Enter envía el mensaje y limpia el compositor', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
await page.locator('[data-testid="thread-item"]').first().click()
await page.waitForSelector('[data-testid="message-composer"]')
// Act
const textarea = page.locator('[data-testid="message-textarea"]')
await textarea.fill('Hola, tengo una duda sobre el módulo 3')
await textarea.press('Control+Enter')
// Assert — campo vacío y mensaje aparece en la lista
await expect(textarea).toHaveValue('')
await expect(page.locator('[data-testid="message-bubble"]').last()).toContainText('Hola, tengo una duda sobre el módulo 3')
})

TC-MENT-007 — Botón “Enviar” deshabilitado cuando el texto está vacío

Section titled “TC-MENT-007 — Botón “Enviar” deshabilitado cuando el texto está vacío”

Cubre: AC-MENT-003.2 Tipo: E2E

test('TC-MENT-007: botón Enviar deshabilitado con mensaje vacío', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
await page.locator('[data-testid="thread-item"]').first().click()
await page.waitForSelector('[data-testid="message-composer"]')
// Assert — botón deshabilitado inicialmente
const sendBtn = page.locator('[data-testid="send-button"]')
await expect(sendBtn).toBeDisabled()
// Act — escribir y borrar
await page.locator('[data-testid="message-textarea"]').fill(' ')
// Assert — sigue deshabilitado con solo espacios
await expect(sendBtn).toBeDisabled()
})

TC-MENT-008 — Tarjeta de referencia MANUAL_EVAL con puntuación aprobado/no aprobado

Section titled “TC-MENT-008 — Tarjeta de referencia MANUAL_EVAL con puntuación aprobado/no aprobado”

Cubre: AC-MENT-004.1, AC-MENT-004.2 Tipo: E2E

test('TC-MENT-008: tarjeta de referencia MANUAL_EVAL muestra puntuación con color correcto', async ({ page }) => {
// Arrange — hilo con mensaje que tiene refType MANUAL_EVAL y refMeta { score: 85, passed: true }
await page.goto('/mentorship')
await page.locator('[data-testid="thread-item"]').first().click()
// Assert — tarjeta de referencia visible
const refCard = page.locator('[data-testid="reference-card"]').first()
await expect(refCard).toBeVisible()
await expect(refCard).toContainText('Evaluación manual recibida')
// Puntuación con color verde (passed: true)
const scoreChip = refCard.locator('[data-testid="ref-score"]')
await expect(scoreChip).toHaveCSS('color', 'rgb(21, 128, 61)') // #15803D
})

TC-MENT-009 — Tab Evaluaciones muestra estados PENDING, MANUAL y AI

Section titled “TC-MENT-009 — Tab Evaluaciones muestra estados PENDING, MANUAL y AI”

Cubre: AC-MENT-008.1, AC-MENT-008.4 Tipo: E2E

test('TC-MENT-009: tab evaluaciones muestra los tres estados con iconos correctos', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
// Act — cambiar al tab Evaluaciones
await page.click('[data-testid="tab-evaluations"]')
await page.waitForSelector('[data-testid="eval-item"]')
// Assert — ítems con estados distintos
const pendingItem = page.locator('[data-testid="eval-item"][data-status="pending"]').first()
await expect(pendingItem).toContainText('En revisión')
const manualItem = page.locator('[data-testid="eval-item"][data-status="manual"]').first()
await expect(manualItem).toContainText('Evaluación recibida')
const aiItem = page.locator('[data-testid="eval-item"][data-status="ai"]').first()
await expect(aiItem).toContainText('Evaluado por IA')
})

TC-MENT-010 — Detalle de evaluación muestra feedback e historial de intentos

Section titled “TC-MENT-010 — Detalle de evaluación muestra feedback e historial de intentos”

Cubre: AC-MENT-008.2, AC-MENT-008.3 Tipo: E2E

test('TC-MENT-010: detalle de evaluación muestra feedback y estimación cuando está en revisión', async ({ page }) => {
// Arrange
await page.goto('/mentorship')
await page.click('[data-testid="tab-evaluations"]')
await page.waitForSelector('[data-testid="eval-item"]')
// Seleccionar evaluación PENDING
const pendingEval = page.locator('[data-testid="eval-item"]').filter({ hasText: 'En revisión' }).first()
await pendingEval.click()
// Assert — panel de detalle visible con estimación de tiempo
await expect(page.locator('[data-testid="eval-detail"]')).toBeVisible()
await expect(page.locator('[data-testid="eval-detail"]')).toContainText('menos de 48h')
await expect(page.locator('[data-testid="eval-detail"]')).toContainText('Tu instructor revisará tu respuesta en breve')
})

TC-MENT-011 — Usuarios no autenticados reciben 401 en todos los endpoints de mentoría

Section titled “TC-MENT-011 — Usuarios no autenticados reciben 401 en todos los endpoints de mentoría”

Cubre: AC-MENT-009.1 Tipo: Integration (API)

test('TC-MENT-011: request sin token a /mentorship/threads devuelve 401', async ({ request }) => {
// Act — sin header Authorization
const response = await request.get('/api/mentorship/threads')
// Assert
expect(response.status()).toBe(401)
})

TC-MENT-012 — Acceso a mensajes de hilo ajeno devuelve 403

Section titled “TC-MENT-012 — Acceso a mensajes de hilo ajeno devuelve 403”

Cubre: AC-MENT-009.2 Tipo: Integration (API)

test('TC-MENT-012: estudiante ajeno al hilo recibe 403 al pedir mensajes', async ({ request }) => {
// Arrange — autenticar como un estudiante diferente al del hilo
const tokenOther = await getAuthToken(request, { email: 'otro.estudiante@test.com' })
const threadId = 'thread-id-que-pertenece-a-otro-estudiante'
// Act
const response = await request.get(`/api/mentorship/threads/${threadId}/messages`, {
headers: { Authorization: `Bearer ${tokenOther}` },
})
// Assert
expect(response.status()).toBe(403)
const body = await response.json()
expect(body.message).toContain('You do not have access to this thread')
})

TC-MENT-013 — Notificación INSTRUCTOR_MESSAGE al estudiante cuando el instructor envía un mensaje

Section titled “TC-MENT-013 — Notificación INSTRUCTOR_MESSAGE al estudiante cuando el instructor envía un mensaje”

Cubre: AC-MENT-007.1 Tipo: Integration (API)

test('TC-MENT-013: envío de mensaje como mentor dispara notificación INSTRUCTOR_MESSAGE', async ({ request }) => {
// Arrange
const mentorToken = await getAuthToken(request, { role: 'CONTENT_ADMIN' })
const threadId = 'thread-id-existente'
const messageBody = 'Has completado el módulo con éxito. Enhorabuena.'
// Act — mentor envía mensaje
const response = await request.post(`/api/mentorship/threads/${threadId}/messages`, {
headers: { Authorization: `Bearer ${mentorToken}` },
data: { body: messageBody },
})
// Assert — mensaje creado
expect(response.status()).toBe(201)
const msg = await response.json()
expect(msg.authorRole).toBe('MENTOR')
// Verificar que la notificación fue enviada al estudiante (requiere endpoint de notificaciones)
// La deduplicación de 24h previene duplicados por userId + type + metadata
})

TC-MENT-014 — Paginación de mensajes (page y limit)

Section titled “TC-MENT-014 — Paginación de mensajes (page y limit)”

Cubre: AC-MENT-002.1 Tipo: Integration (API)

test('TC-MENT-014: mensajes paginados devuelven total y totalPages correctos', async ({ request }) => {
// Arrange
const token = await getAuthToken(request)
const threads = await request.get('/api/mentorship/threads', {
headers: { Authorization: `Bearer ${token}` },
})
const [thread] = await threads.json()
// Act — primera página con limit 5
const page1 = await request.get(`/api/mentorship/threads/${thread.id}/messages?page=1&limit=5`, {
headers: { Authorization: `Bearer ${token}` },
})
const data = await page1.json()
// Assert
expect(page1.status()).toBe(200)
expect(data).toHaveProperty('messages')
expect(data).toHaveProperty('total')
expect(data).toHaveProperty('totalPages')
expect(data.messages.length).toBeLessThanOrEqual(5)
expect(data.totalPages).toBe(Math.ceil(data.total / 5))
})

CampoTipoDescripción
MentorshipThread.mentorRoleCREATOR | CORP_ADMINRol del mentor que inició el hilo
MentorshipThread.contextTypeCREATOR_PATH | ORG_MEMBERSHIPOrigen de la relación: ruta formativa o membresía corporativa
MentorshipThread.contextPathIdsstring[]IDs de las rutas de contexto del hilo
MentorshipMessage.authorRoleMENTOR | STUDENTRol del autor (no el userId, para facilitar la visualización)
MentorshipMessage.refTypeQUIZ_ATTEMPT | LESSON | PATH_PROGRESS | MANUAL_EVALTipo de referencia contextual adjunta al mensaje
MentorshipThread.unreadByStudentIntContador de mensajes del instructor no leídos por el estudiante
MentorshipThread.unreadByMentorIntContador de mensajes del estudiante no leídos por el mentor
TipoCanalSeverityReceptorDisableable
MENTOR_ASSIGNEDin_app, emailinfoEstudianteNo
INSTRUCTOR_MESSAGEin_app, pushinfoEstudiante
STUDENT_MESSAGEin_app, pushinfoInstructor (CONTENT_ADMIN)