---
id: "RF-EDU-004"
title: "Sistema de Quizzes"
type: "Requirement"
status: "Done"
priority: "Alta"
module: "education"
epic: "OQI-002"
version: "1.0"
created_date: "2025-12-05"
updated_date: "2026-01-04"
---

# RF-EDU-004: Sistema de Quizzes

**Versión:** 1.0.0
**Fecha:** 2025-12-05
**Épica:** OQI-002 - Módulo Educativo
**Prioridad:** P1
**Story Points:** 8

---

## Descripción

El sistema debe proporcionar un sistema completo de evaluaciones interactivas (quizzes) que permita validar el conocimiento adquirido por los usuarios, con soporte para múltiples tipos de preguntas, feedback inmediato, intentos limitados, calificaciones y análisis de resultados.

---

## Requisitos Funcionales

### RF-EDU-004.1: Tipos de Preguntas

El sistema debe soportar:

| Tipo | Descripción | Características |
|------|-------------|-----------------|
| **Multiple Choice** | Una respuesta correcta | 2-6 opciones |
| **Multiple Select** | Varias respuestas correctas | Checkbox, puntuación parcial |
| **True/False** | Verdadero o falso | 2 opciones |
| **Fill in the Blank** | Completar espacios | Input de texto, validación |
| **Matching** | Emparejar elementos | Drag & drop opcional |
| **Ordering** | Ordenar elementos | Secuencia correcta |

### RF-EDU-004.2: Estructura de Quiz

Cada quiz debe tener:
- Título y descripción
- Tiempo límite (opcional)
- Número de preguntas
- Puntuación mínima para aprobar (% o puntos)
- Número de intentos permitidos (ilimitado, 1, 2, 3...)
- Modo: Práctica (sin límite) o Evaluación (formal)
- Mostrar respuestas correctas: Inmediato, Al finalizar, Nunca
- Barajear preguntas (randomizar orden)
- Barajear opciones de respuesta

### RF-EDU-004.3: Interfaz de Quiz

El sistema debe mostrar:
- Contador de preguntas (Pregunta 1 de 10)
- Barra de progreso del quiz
- Timer countdown si hay límite de tiempo
- Pregunta actual con opciones
- Botones: "Anterior", "Siguiente", "Marcar para revisión"
- Navegador de preguntas (minimap con estado: respondida, marcada, pendiente)
- Botón "Finalizar quiz" (requiere confirmación)
- Auto-submit cuando expira el tiempo

### RF-EDU-004.4: Navegación y Estados

Estados de pregunta:
- **No respondida:** Sin respuesta seleccionada
- **Respondida:** Respuesta seleccionada
- **Marcada:** Flagged para revisión posterior
- **Correcta:** Solo visible después de submit (si configurado)
- **Incorrecta:** Solo visible después de submit (si configurado)

El usuario debe poder:
- Navegar libremente entre preguntas antes de submit
- Cambiar respuestas antes de finalizar
- Marcar preguntas para revisar después
- Ver resumen antes de enviar

### RF-EDU-004.5: Calificación y Resultados

Al finalizar el quiz, mostrar:
- Puntuación obtenida (X/Y puntos o %)
- Estado: Aprobado / Reprobado
- Tiempo invertido
- Feedback general basado en score
- Desglose por pregunta (si configurado):
  - Pregunta
  - Tu respuesta
  - Respuesta correcta
  - Explicación
- Intentos restantes
- Botón "Reintentar" si aplica
- Botón "Continuar al siguiente contenido"

### RF-EDU-004.6: Historial de Intentos

El sistema debe:
- Guardar todos los intentos del usuario
- Mostrar tabla con: fecha, puntuación, tiempo, estado
- Permitir ver detalle de intento anterior
- Mostrar mejor intento destacado
- Calcular promedio de intentos
- Guardar última puntuación como oficial

### RF-EDU-004.7: Feedback y Explicaciones

El sistema debe permitir:
- Explicación de respuesta correcta (markdown)
- Explicación de por qué otras opciones son incorrectas
- Links a recursos relacionados
- Video explicativo opcional
- Sugerencias de lecciones para repasar

### RF-EDU-004.8: Analítica de Quiz

Para cada pregunta, rastrear:
- Número de veces respondida
- Número de respuestas correctas
- Número de respuestas incorrectas
- Tasa de éxito global (%)
- Tiempo promedio de respuesta
- Opción más elegida (para detectar confusión)

Para cada quiz, rastrear:
- Número de intentos totales
- Tasa de aprobación (%)
- Puntuación promedio
- Tiempo promedio de completitud
- Pregunta más difícil (menor % acierto)
- Pregunta más fácil (mayor % acierto)

---

## Datos de Entrada

| Campo | Tipo | Descripción |
|-------|------|-------------|
| quizId | string | UUID del quiz |
| answers | object | Mapa de questionId -> respuesta |

---

## Datos de Salida

```typescript
interface Quiz {
  id: string;
  title: string;
  description: string;
  lessonId?: string;
  courseId: string;
  timeLimit?: number; // minutos
  passingScore: number; // 0-100
  maxAttempts: number; // 0 = ilimitado
  questionCount: number;
  totalPoints: number;
  shuffleQuestions: boolean;
  shuffleOptions: boolean;
  showAnswers: 'immediate' | 'after_submit' | 'never';
  mode: 'practice' | 'assessment';
}

interface Question {
  id: string;
  quizId: string;
  type: 'multiple_choice' | 'multiple_select' | 'true_false' | 'fill_blank' | 'matching' | 'ordering';
  question: string; // Markdown
  points: number;
  order: number;

  // Para multiple choice/select
  options?: {
    id: string;
    text: string;
    isCorrect: boolean;
    explanation?: string;
  }[];

  // Para fill in the blank
  correctAnswers?: string[];
  caseSensitive?: boolean;

  // Para matching
  pairs?: {
    left: string;
    right: string;
  }[];

  // Para ordering
  correctOrder?: string[];

  explanation?: string; // Explicación general
  hint?: string;
  relatedResources?: {
    type: 'lesson' | 'article' | 'video';
    id: string;
    title: string;
  }[];
}

interface QuizAttempt {
  id: string;
  quizId: string;
  userId: string;
  attemptNumber: number;
  startedAt: string;
  submittedAt?: string;
  timeSpent: number; // segundos

  answers: {
    questionId: string;
    userAnswer: any;
    isCorrect: boolean;
    pointsEarned: number;
  }[];

  score: number; // 0-100
  pointsEarned: number;
  totalPoints: number;
  passed: boolean;

  analytics: {
    questionsCorrect: number;
    questionsIncorrect: number;
    questionsSkipped: number;
    avgTimePerQuestion: number;
  };
}

interface QuizResults {
  attempt: QuizAttempt;
  quiz: Quiz;
  questions: (Question & {
    userAnswer: any;
    isCorrect: boolean;
    pointsEarned: number;
  })[];
  feedback: {
    title: string;
    message: string;
    suggestions?: string[];
  };
  attemptsRemaining: number;
  canRetake: boolean;
  nextContent?: {
    type: 'lesson' | 'quiz' | 'module';
    id: string;
    title: string;
  };
}
```

---

## Reglas de Negocio

1. **Puntuación mínima:** Default 70% para aprobar
2. **Intentos:** Si se agotan intentos, usuario debe esperar 24h o contactar soporte
3. **Tiempo límite:** Si expira, se auto-submit con respuestas actuales
4. **Preguntas obligatorias:** No se puede submit sin responder todas (modo evaluación)
5. **Modo práctica:** Sin límite de intentos, sin guardar en historial oficial
6. **Partial credit:** Multiple select otorga puntos parciales (50% si elige 2/4 correctas)
7. **Shuffle:** Si está activado, orden diferente en cada intento
8. **Feedback inmediato:** Solo en modo práctica
9. **Certificación:** Quiz final de curso debe aprobarse para certificado

---

## Criterios de Aceptación

```gherkin
Escenario: Usuario inicia quiz
  DADO que el usuario está en una lección con quiz
  CUANDO hace click en "Iniciar quiz"
  ENTONCES se muestra pantalla de introducción del quiz
  Y se muestra título, descripción, número de preguntas
  Y se muestra tiempo límite si aplica
  Y se muestra intentos disponibles
  Y se muestra puntuación requerida para aprobar
  Y se muestra botón "Comenzar"

Escenario: Usuario responde preguntas
  DADO que el usuario comenzó el quiz
  CUANDO selecciona una respuesta
  ENTONCES la opción se marca como seleccionada
  Y la pregunta se marca como "respondida"
  Y puede navegar a siguiente pregunta
  Y puede volver a preguntas anteriores
  Y puede cambiar respuesta antes de submit

Escenario: Usuario finaliza quiz exitosamente
  DADO que el usuario respondió todas las preguntas
  CUANDO hace click en "Finalizar quiz"
  Y confirma en el modal
  ENTONCES se calcula la puntuación
  Y se muestra pantalla de resultados
  Y se muestra "Aprobado" si score >= passing score
  Y se desbloquea siguiente contenido
  Y se otorga XP por aprobar

Escenario: Usuario reprueba quiz
  DADO que el usuario envió el quiz
  Y la puntuación es < passing score
  ENTONCES se muestra pantalla de resultados
  Y se muestra "Reprobado"
  Y se muestra feedback con áreas a mejorar
  Y se muestra "Intentos restantes: X"
  Y se muestra botón "Reintentar"
  Y siguiente contenido permanece bloqueado

Escenario: Quiz con tiempo límite expira
  DADO que el quiz tiene tiempo límite de 30 minutos
  Y el usuario está en la pregunta 5 de 10
  CUANDO el tiempo llega a 0
  ENTONCES el quiz se envía automáticamente
  Y se califica con respuestas hasta el momento
  Y preguntas sin responder cuentan como incorrectas

Escenario: Ver explicación de respuestas
  DADO que el quiz permite ver respuestas
  Y el usuario envió el quiz
  CUANDO ve los resultados
  ENTONCES se muestran todas las preguntas
  Y se destacan respuestas correctas en verde
  Y se destacan respuestas incorrectas en rojo
  Y se muestra explicación de cada respuesta
  Y se muestran recursos relacionados

Escenario: Reintentar quiz
  DADO que el usuario reprobó un quiz
  Y tiene intentos disponibles
  CUANDO hace click en "Reintentar"
  ENTONCES se inicia nuevo intento
  Y preguntas pueden estar en diferente orden
  Y respuestas anteriores no están pre-seleccionadas
  Y contador de intentos se decrementa
```

---

## Dependencias

- PostgreSQL para quizzes y resultados
- Redis para cachear quizzes activos
- WebSocket para timer en tiempo real (opcional)

---

## Notas Técnicas

- Implementar auto-save cada 30s para evitar pérdida de progreso
- Usar WebSockets para sincronizar timer entre tabs
- Encriptar respuestas correctas en frontend
- Validar respuestas en backend (nunca confiar en frontend)
- Implementar rate limiting para prevenir brute force
- Usar optimistic updates para mejor UX
- Considerar adaptive quizzes (ajustar dificultad según respuestas)

---

## Referencias

- Schema: `/backend/src/database/schemas/education.sql`
- API: `/backend/src/modules/courses/quizzes.routes.ts`
- Frontend: `/frontend/src/pages/QuizPlayer.tsx`

---

## Tareas Técnicas

**Database:**
- [ ] Tabla education.quizzes
- [ ] Tabla education.questions con FK a quiz
- [ ] Tabla education.question_options
- [ ] Tabla education.quiz_attempts
- [ ] Tabla education.quiz_answers
- [ ] Índices para queries por usuario y quiz

**Backend:**
- [ ] Endpoint GET /education/quizzes/:id (sin respuestas correctas)
- [ ] Endpoint POST /education/quizzes/:id/start
- [ ] Endpoint POST /education/quizzes/:id/submit
- [ ] Endpoint GET /education/quizzes/:id/attempts (historial)
- [ ] Endpoint GET /education/quizzes/:id/results/:attemptId
- [ ] Implementar QuizService.gradeAttempt()
- [ ] Implementar shuffle de preguntas y opciones
- [ ] Rate limiting en submit

**Frontend:**
- [ ] Crear QuizIntroPage.tsx
- [ ] Crear QuizPlayerPage.tsx
- [ ] Crear componente QuestionRenderer.tsx (soporta todos los tipos)
- [ ] Crear componente QuizNavigator.tsx
- [ ] Crear componente QuizTimer.tsx
- [ ] Crear QuizResultsPage.tsx
- [ ] Crear componente QuestionExplanation.tsx
- [ ] Auto-save de respuestas cada 30s
- [ ] Implementar quizStore
- [ ] Confirmación antes de salir (window.onbeforeunload)

**Tests:**
- [ ] Test calificación de quiz con diferentes tipos de preguntas
- [ ] Test partial credit en multiple select
- [ ] Test expiración de tiempo
- [ ] Test E2E completar quiz y aprobar

---

**Creado por:** Requirements-Analyst
**Fecha:** 2025-12-05
**Última actualización:** 2025-12-05
