# 🎡 Ruleta Interactiva — SCORM 1.2

Actividad e-learning interactiva basada en una ruleta con preguntas de cultura general, construida con React, TypeScript, Tailwind CSS y empaquetada como contenido SCORM 1.2.

---

## 🛠️ Stack Tecnológico

| Tecnología | Versión | Uso |
|---|---|---|
| React | 19 | UI y manejo de estado |
| TypeScript | 5 | Tipado estático |
| Tailwind CSS | 4 | Estilos utilitarios |
| Vite | 6 | Bundler y dev server |
| Howler.js | 2 | Manejo de audio |
| scorm-again | 3 | Simulador SCORM en desarrollo |

---

## 📁 Estructura del Proyecto

```
src/
├── audio/
│   ├── ExtendedHowl.ts         # Clase que extiende Howl con eventos de timeupdate (250ms)
│   ├── AudioController.ts      # Controlador global de audio + sonidos precargados
│   └── audios.ts               # Constantes con rutas de archivos de audio
│
├── components/
│   ├── ui/
│   │   └── Button.tsx          # Botón reutilizable (variantes: primary, secondary, outline, danger)
│   ├── layout/
│   │   ├── Header.tsx          # Cabecera de la aplicación
│   │   └── Footer.tsx          # Pie de página
│   ├── screens/
│   │   ├── Cover.tsx           # Pantalla de portada con introducción y botón comenzar
│   │   ├── Game.tsx            # Pantalla principal del juego
│   │   └── Result.tsx          # Pantalla de cierre (3 estados)
│   └── game/
│       ├── Ruleta/
│       │   └── RuletaSVG.tsx   # Ruleta SVG con 4 secciones de colores
│       ├── QuestionModal.tsx   # Modal de pregunta con temporizador y feedback
│       └── ResultModal.tsx     # Modal interno (reservado)
│
├── data/
│   └── preguntas.json          # 4 categorías × 8 preguntas + banco genérico
│
├── hooks/
│   ├── useAudio.ts             # Hook para consumir el AudioController
│   ├── useIntentos.ts          # Manejo de intentos con persistencia en sessionStorage
│   ├── useRuleta.ts            # Lógica completa del juego
│   ├── useSCORM.ts             # Hook para inicializar y usar SCORM 1.2
│   └── useScale.ts             # Escalado adaptativo 1920×1080
│
├── layouts/
│   └── MainLayout.tsx          # Template principal con escalado adaptativo
│
├── scorm/
│   └── scorm.ts                # Funciones SCORM 1.2 con fallback a sessionStorage
│
├── types/
│   └── index.ts                # Interfaces TypeScript (Pregunta, Categoria, PreguntasData)
│
├── App.tsx                     # Manejo de pantallas y flujo de intentos
└── main.tsx                    # Entry point con simulador SCORM en desarrollo

public/
├── audios/
│   ├── click.mp3               # Sonido de clic en botones
│   ├── wheel-spin.mp3          # Sonido al girar la ruleta
│   ├── feedback-correct.mpeg   # Sonido de respuesta correcta
│   └── feedback-incorrect.mpeg # Sonido de respuesta incorrecta
├── img/
│   ├── ruleta.png              # Imagen de ruleta (respaldo)
│   └── indicador.png           # Imagen de indicador (respaldo)
└── imsmanifest.xml             # Manifiesto SCORM 1.2 requerido por el LMS
```

---

## 🎮 Flujo de la Actividad

```
Portada (Cover)
    │
    │  clic en "Comenzar" → consume 1 intento + sonido click
    ▼
Juego (Game)
    │
    │  clic en la ruleta → gira + sonido wheel-spin (4s)
    ▼
Detecta sección (4 categorías × 90°)
    │
    ▼
Modal de Pregunta (temporizador 30s)
    ├── Respuesta correcta  → sonido correct  → feedback → círculo de progreso
    ├── Respuesta incorrecta → sonido incorrect → feedback → círculo de progreso
    └── Tiempo agotado      → cuenta como incorrecta → feedback
    │
    │  10 preguntas respondidas
    ▼
Pantalla de Resultado
    ├── ≥ 8 correctas  → ¡Felicidades! (sin botón)
    ├── < 8 correctas + intentos disponibles → "Casi" + botón Reintentar
    └── Sin intentos   → Mensaje final (persiste al recargar)
```

---

## 🎯 Categorías de Preguntas

| # | Categoría | Color | Preguntas |
|---|---|---|---|
| 1 | 🏛️ Historia | Morado `#7C3AED` | 8 |
| 2 | 🌍 Geografía | Verde `#059669` | 8 |
| 3 | 🔬 Ciencia | Rojo `#DC2626` | 8 |
| 4 | 🎨 Arte y Literatura | Naranja `#D97706` | 8 |

> Las preguntas se seleccionan aleatoriamente sin repetir dentro de cada categoría.
> Si se agotan las preguntas de una categoría, se usa el **banco genérico** (5 preguntas).

---

## 🏆 Criterios de Aprobación

| Criterio | Valor |
|---|---|
| Preguntas por actividad | 10 |
| Mínimo para aprobar | 8 correctas |
| Intentos totales | 3 |
| Tiempo por pregunta | 30 segundos |
| Tiempo agotado | Cuenta como incorrecta |

---

## 🔄 Sistema de Intentos

- El intento se consume al hacer clic en **"Comenzar"**
- Se persiste en `sessionStorage`, por lo que recargar la página **no** recupera el intento
- Al agotar los 3 intentos, la pantalla de cierre final persiste aunque se recargue

---

## 📐 Escalado Adaptativo

El hook `useScale` escala toda la actividad a una resolución base de **1920×1080** manteniendo proporciones en cualquier pantalla:

```ts
const BASE_WIDTH = 1920
const BASE_HEIGHT = 1080
const scale = Math.min(window.innerWidth / BASE_WIDTH, window.innerHeight / BASE_HEIGHT)
```

> Solo orientación horizontal. No aplica diseño para pantallas verticales.

---

## 🔊 Audio

Los audios se precargan al iniciar la aplicación mediante `AudioController`:

```ts
sounds.click?.play()       // Botones de navegación
sounds.wheelSpin?.play()   // Al girar la ruleta
sounds.correct?.play()     // Respuesta correcta
sounds.incorrect?.play()   // Respuesta incorrecta
```

---

## 📡 SCORM 1.2

El módulo SCORM (`src/scorm/scorm.ts`) implementa todas las funciones estándar con **fallback automático a `sessionStorage`** cuando no hay LMS disponible:

| Función | CMI Key |
|---|---|
| `initSCORM()` | Inicializa y establece status `incomplete` |
| `getStudentName()` | `cmi.core.student_name` |
| `getLessonStatus()` / `setLessonStatus()` | `cmi.core.lesson_status` |
| `getLessonLocation()` / `setLessonLocation()` | `cmi.core.lesson_location` |
| `getScore()` / `setScore()` | `cmi.core.score.raw/max/min` |
| `getSuspendData()` / `setSuspendData()` | `cmi.suspend_data` |
| `getScormData()` / `setScormData()` | Cualquier clave CMI |
| `finishSCORM()` | Calcula `session_time` en HH:MM:SS y cierra |
| `completeLesson()` | Establece status `completed` |

> En desarrollo, `scorm-again` simula el `window.API` del LMS automáticamente.

### Integración con el flujo del juego

`useSCORM` se inicializa en `App.tsx` al montar la aplicación. Al terminar las 10 preguntas (`handleGameEnd`):

```ts
setScore(correctas * 10, 100, 0)          // Escala el puntaje a 100
setLessonStatus(aprobado ? 'passed' : 'failed')
finishSCORM()                              // Envía session_time y cierra
```

> El score se envía escalado a 100 para que coincida con el `masteryscore: 80` del manifiesto.

---

## 🧩 Componente Button

```tsx
<Button variant="primary" size="lg" onClick={fn}>Texto</Button>
<Button variant="secondary" size="md">Texto</Button>
<Button variant="outline" size="sm">Texto</Button>
<Button variant="danger" disabled>Texto</Button>
```

---

## 🚀 Comandos

```bash
# Instalar dependencias
npm install

# Desarrollo
npm run dev

# Build para producción
npm run build

# Verificar TypeScript
npx tsc --noEmit
```

---

## 📦 Empaquetado SCORM

Para publicar en un LMS (Moodle, Cornerstone, etc.):

1. Ejecutar `npm run build`
2. Abrir la carpeta `dist/` — el `imsmanifest.xml` se copia automáticamente
3. Seleccionar **todo** el contenido de `dist/` y comprimir en un `.zip`
4. Subir el `.zip` al LMS como paquete SCORM 1.2

> El `masteryscore` está configurado en **80** en el `imsmanifest.xml`.

---

## 📋 Pendientes

- [ ] Reemplazar categorías y preguntas de cultura general por el contenido real del cliente
- [ ] Definir diseño visual final (colores, logo, imágenes del cliente)

## ✅ Resuelto

- [x] Integrar `setScore` y `setLessonStatus` con SCORM al finalizar la actividad
- [x] Llamar `finishSCORM` al terminar el juego
- [x] Configurar `base: './'` en Vite para rutas relativas en Moodle
- [x] Rutas de audios relativas (`./audios/...`) para compatibilidad con LMS
- [x] Pruebas en LMS real (Moodle)
