239 lines
7.7 KiB
Markdown
239 lines
7.7 KiB
Markdown
|
|
# 🎡 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.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧩 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
|
|||
|
|
- [ ] Integrar `setScore` y `completeLesson` con SCORM al finalizar la actividad
|
|||
|
|
- [ ] Pruebas en LMS real (Moodle)
|
|||
|
|
- [ ] Definir diseño visual final (colores, logo, imágenes del cliente)
|