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
Endpoints reales del módulo
Section titled “Endpoints reales del módulo”| Método | Endpoint | Descripción |
|---|---|---|
GET | /mentorship/threads | Listar todos los hilos del usuario autenticado (como mentor o estudiante) |
GET | /mentorship/threads/:id/messages | Mensajes paginados de un hilo (?page=1&limit=20) |
POST | /mentorship/threads/:id/messages | Enviar mensaje en un hilo (con referencia opcional) |
PATCH | /mentorship/threads/:id/read | Marcar mensajes como leídos hasta un upToMessageId |
POST | /mentorship/threads | Crear nuevo hilo (solo para mentores / CONTENT_ADMIN) |
GET | /mentorship/my-threads | Buscar hilo por referencia de evaluación (?refEval=<evalId>) |
User Stories
Section titled “User Stories”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íaWhen 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 relativoAC-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 conversacionesThen el item del hilo muestra un badge con el contador de no leídosAnd el nombre del instructor aparece en negrita (fontWeight 700)And el tab "Conversaciones" en la barra superior muestra el total de no leídosAC-MENT-001.3 — Filtro “No leídas”
Section titled “AC-MENT-001.3 — Filtro “No leídas””Given el estudiante tiene hilos con y sin mensajes no leídosWhen hace clic en el filtro "No leídas"Then la lista solo muestra hilos con unreadByStudent > 0And si no hay no leídas, la lista queda vacíaAC-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 asignadosWhen navega a "/mentorship"Then ve el estado vacío "Sin conversaciones" con el mensaje explicativoAnd 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 listaWhen se cargan los mensajes del hiloThen los mensajes aparecen ordenados cronológicamente (más antiguo primero)And se insertan separadores de fecha cuando cambia el díaAnd si hay mensajes no leídos, aparece un separador "N mensajes nuevos" antes del primer no leídoAC-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 hiloWhen el estudiante ve los mensajesThen 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 mensajesWhen se completa la carga de mensajesThen la vista hace scroll automáticamente al último mensajeAnd cuando llega un nuevo mensaje al hilo activo, la vista vuelve a hacer scroll al finalAC-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 instructorWhen se carga el componente MentorshipThread con viewerRole "STUDENT"Then se llama a PATCH /mentorship/threads/:id/read con el id del último mensajeAnd el contador unreadByStudent se resetea a 0 en la lista de hilosUS-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 activoWhen escribe texto en el compositor y hace clic en "Enviar" o presiona Ctrl+EnterThen el mensaje se envía a POST /mentorship/threads/:id/messagesAnd el campo de texto queda vacíoAnd el nuevo mensaje aparece en la lista con alineación derechaAC-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 espaciosWhen 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 APIAC-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 hiloWhen el backend persiste el mensajeThen 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 asociadoWhen el estudiante ve el mensajeThen 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 verdeAC-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 referenciaThen 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 recibidasUS-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 definidoWhen el estudiante ve la lista de hilosThen si contextType = "ORG_MEMBERSHIP" muestra la etiqueta "Corporativo" en moradoAnd si contextType = "CREATOR_PATH" muestra la etiqueta "Ruta" en moradoAC-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íaWhen 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 estudianteWhen el hilo se persiste en base de datosThen el sistema envía una notificación MENTOR_ASSIGNED al studentId del hiloAnd 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 estudianteWhen el instructor intenta crear otro hilo con el mismo par mentor-estudianteThen el sistema devuelve el hilo existente en lugar de crear uno nuevoAnd no se envía una segunda notificación MENTOR_ASSIGNEDUS-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 hiloWhen el backend persiste el mensaje en MentorshipMessage con authorRole "MENTOR"Then se envía una notificación INSTRUCTOR_MESSAGE al studentId del hiloAnd 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 + metadataAC-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 activoWhen el instructor envía un mensajeThen se emite el evento "mentorship.message.sent" vía WebSocketAnd el nuevo mensaje aparece en el hilo sin recargar la páginaAnd el contador de no leídos del hilo se actualizaUS-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 asignadasWhen navega a "/mentorship" y hace clic en la pestaña "Evaluaciones"Then ve una lista de tarjetas agrupadas por quizAnd 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 listaWhen 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 manualAnd si hay feedback del instructor, se muestra en un bloque de textoAnd si hay múltiples intentos del mismo quiz, aparece el historial cronológico de intentosAC-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ónThen 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é completadaAC-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 evaluacionesThen 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 evaluadorUS-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 /mentorshipWhen el servidor procesa la peticiónThen devuelve HTTP 401 UnauthorizedAnd el frontend redirige a la página de loginAC-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 participaWhen llama a GET /mentorship/threads/:id/messagesThen el servidor devuelve HTTP 403 Forbidden con el mensaje "You do not have access to this thread"Test Cases
Section titled “Test Cases”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))})Notas sobre el modelo de datos
Section titled “Notas sobre el modelo de datos”| Campo | Tipo | Descripción |
|---|---|---|
MentorshipThread.mentorRole | CREATOR | CORP_ADMIN | Rol del mentor que inició el hilo |
MentorshipThread.contextType | CREATOR_PATH | ORG_MEMBERSHIP | Origen de la relación: ruta formativa o membresía corporativa |
MentorshipThread.contextPathIds | string[] | IDs de las rutas de contexto del hilo |
MentorshipMessage.authorRole | MENTOR | STUDENT | Rol del autor (no el userId, para facilitar la visualización) |
MentorshipMessage.refType | QUIZ_ATTEMPT | LESSON | PATH_PROGRESS | MANUAL_EVAL | Tipo de referencia contextual adjunta al mensaje |
MentorshipThread.unreadByStudent | Int | Contador de mensajes del instructor no leídos por el estudiante |
MentorshipThread.unreadByMentor | Int | Contador de mensajes del estudiante no leídos por el mentor |
Notificaciones activas
Section titled “Notificaciones activas”| Tipo | Canal | Severity | Receptor | Disableable |
|---|---|---|---|---|
MENTOR_ASSIGNED | in_app, email | info | Estudiante | No |
INSTRUCTOR_MESSAGE | in_app, push | info | Estudiante | Sí |
STUDENT_MESSAGE | in_app, push | info | Instructor (CONTENT_ADMIN) | Sí |