# Guía de Integración - Ejercicios Módulos 4 y 5 ## Resumen de Implementación Se ha completado la capa frontend para los módulos 4 y 5 de GAMILIT con los siguientes componentes: ### 1. Hooks Creados #### `useVideoRecorder.ts` Hook para grabación de video con funcionalidades: - Permiso de cámara/micrófono - Grabación con preview en vivo - Pause/Resume - Configuración de resolución y framerate - Manejo completo de errores - Ubicación: `/apps/frontend/src/shared/hooks/useVideoRecorder.ts` **Uso:** ```tsx import { useVideoRecorder } from '@/shared/hooks/useVideoRecorder'; const { videoBlob, previewUrl, startRecording, stopRecording, isRecording } = useVideoRecorder(); // Iniciar grabación con opciones await startRecording({ width: 1280, height: 720, frameRate: 30 }); ``` ### 2. API Clients Creados #### `manualReviewApi.ts` Cliente para el servicio de revisiones manuales del backend. - Ubicación: `/apps/frontend/src/shared/api/manualReviewApi.ts` **Endpoints:** - `getPendingReviews()` - Lista de revisiones pendientes - `getReviewById(id)` - Obtener revisión específica - `startReview(submissionId)` - Iniciar revisión - `updateReview(id, updates)` - Guardar progreso - `completeReview(id, completion)` - Completar y enviar **Uso:** ```tsx import { manualReviewApi } from '@/shared/api/manualReviewApi'; // Obtener revisiones pendientes const reviews = await manualReviewApi.getPendingReviews({ moduleId: 'module-4', exerciseId: 'verificador-fake-news' }); // Completar revisión await manualReviewApi.completeReview(reviewId, { evaluations: [...], generalFeedback: 'Excelente trabajo', notifyStudent: true }); ``` #### `mediaApi.ts` Cliente para upload de archivos multimedia. - Ubicación: `/apps/frontend/src/shared/api/mediaApi.ts` **Funcionalidades:** - Upload de imágenes, audio, video - Validación de tipos y tamaños - Progress tracking - Multi-upload **Uso:** ```tsx import { mediaApi } from '@/shared/api/mediaApi'; // Upload con progress const response = await mediaApi.uploadMedia(file, { type: 'video', exerciseId: exerciseId, onProgress: (progress) => { console.log(`Upload: ${progress}%`); } }); console.log('URL:', response.url); ``` ### 3. Componentes Creados #### `MediaUploader.tsx` Componente reutilizable para upload de archivos. - Ubicación: `/apps/frontend/src/shared/components/mechanics/MediaUploader.tsx` **Features:** - Drag & drop - Preview de archivos (imagen, audio, video) - Progress bar - Validación automática - Multi-archivo **Uso:** ```tsx import { MediaUploader } from '@/shared/components/mechanics/MediaUploader'; { console.log('Uploaded:', media); }} onError={(error) => { console.error('Error:', error); }} /> ``` #### `RubricEvaluator.tsx` Componente para calificar según rúbricas. - Ubicación: `/apps/frontend/src/shared/components/mechanics/RubricEvaluator.tsx` **Features:** - Sliders para cada criterio - Cálculo automático de puntaje total - Feedback por criterio - Feedback general - Validación **Uso:** ```tsx import { RubricEvaluator } from '@/shared/components/mechanics/RubricEvaluator'; { console.log('Total score:', score); }} onValidation={(valid, errors) => { console.log('Valid:', valid); }} /> ``` ### 4. Panel de Revisión para Docentes Ubicación: `/apps/frontend/src/apps/teacher/pages/ReviewPanel/` **Componentes:** - `ReviewPanelPage.tsx` - Página principal - `ReviewList.tsx` - Lista de revisiones pendientes - `ReviewDetail.tsx` - Vista detallada con evaluación **Integración en routing:** ```tsx import { ReviewPanelPage } from '@/apps/teacher/pages/ReviewPanel'; // En teacher routes } /> ``` ## Cómo Conectar Ejercicios Módulo 4 a API Real ### Patrón General Los ejercicios del módulo 4 ya tienen frontend completo pero usan mock data. Para conectarlos: #### 1. Importar el hook de submission ```tsx import { useExerciseSubmission } from '@/features/mechanics/shared/hooks/useExerciseSubmission'; ``` #### 2. Usar el hook en el componente ```tsx const { submissionState, submitAnswers } = useExerciseSubmission({ onSuccess: (response) => { setFeedback({ score: response.score, correctAnswers: response.correctAnswers, totalQuestions: response.totalQuestions, xpEarned: response.xpEarned, mlCoinsEarned: response.mlCoinsEarned, explanations: response.feedback.explanations, }); setShowFeedback(true); onComplete?.(response); }, onError: (error) => { console.error('Submission error:', error); alert('Error al enviar respuestas: ' + error); } }); ``` #### 3. Reemplazar mock submission con API real ```tsx // ANTES (mock): const handleSubmit = async () => { const mockScore = calculateScore(); setFeedback({ score: mockScore, ... }); }; // DESPUÉS (API real): const handleSubmit = async () => { if (!exerciseId) return; await submitAnswers(exerciseId, { // Estructura específica del ejercicio selectedArticleId, claims, results, }); }; ``` ### Ejemplo Específico: VerificadorFakeNews ```tsx // En VerificadorFakeNewsExercise.tsx import { useExerciseSubmission } from '@/features/mechanics/shared/hooks/useExerciseSubmission'; export const VerificadorFakeNewsExercise: React.FC = ({ exerciseId, onComplete, onProgressUpdate, initialData, exercise, }) => { // ... existing state ... // ADD: Submission hook const { submissionState, submitAnswers } = useExerciseSubmission({ onSuccess: (response) => { setFeedback({ score: response.score, correct: response.correctAnswers, total: response.totalQuestions, xpEarned: response.xpEarned, mlCoinsEarned: response.mlCoinsEarned, bonuses: response.bonuses, feedback: response.feedback, }); setShowFeedback(true); onComplete?.(response); }, onError: (error) => { alert('Error al enviar: ' + error); } }); // MODIFY: Submit handler const handleSubmit = async () => { if (!exerciseId) return; await submitAnswers(exerciseId, { selectedArticleId, claims, verificationResults: results, }, 0); // hintsUsed }; // En el JSX, agregar loading state return ( <> {/* ... existing UI ... */} {submissionState.error && (
{submissionState.error}
)} ); }; ``` ### Ejercicios a Actualizar (Módulo 4) 1. **VerificadorFakeNews** - Patrón mostrado arriba 2. **QuizTikTok** - Similar, enviar `selectedOptions` y `swipeHistory` 3. **NavegacionHipertextual** - Enviar `navigationPath` y `documentsVisited` 4. **AnalisisMemes** - Enviar `annotations` y `analysisText` 5. **InfografiaInteractiva** - Enviar `interactedElements` y `answers` ## Cómo Conectar Ejercicios Módulo 5 a API Real Los ejercicios del módulo 5 requieren upload de multimedia: ### Patrón General #### 1. Importar componentes necesarios ```tsx import { MediaUploader } from '@/shared/components/mechanics/MediaUploader'; import { useExerciseSubmission } from '@/features/mechanics/shared/hooks/useExerciseSubmission'; import { MediaAttachmentResponse } from '@/shared/api/mediaApi'; ``` #### 2. State para media attachments ```tsx const [uploadedMedia, setUploadedMedia] = useState([]); ``` #### 3. Usar MediaUploader en UI ```tsx { setUploadedMedia(media); }} onError={(error) => { console.error('Upload error:', error); }} /> ``` #### 4. Incluir media IDs en submission ```tsx const handleSubmit = async () => { await submitAnswers(exerciseId, { textContent: diaryContent, mediaIds: uploadedMedia.map(m => m.id), // ... otros datos específicos }); }; ``` ### Ejemplo Específico: DiarioMultimedia ```tsx import React, { useState } from 'react'; import { MediaUploader } from '@/shared/components/mechanics/MediaUploader'; import { useExerciseSubmission } from '@/features/mechanics/shared/hooks/useExerciseSubmission'; import { MediaAttachmentResponse } from '@/shared/api/mediaApi'; export const DiarioMultimediaExercise: React.FC = ({ exerciseId, onComplete, }) => { const [entries, setEntries] = useState([]); const [currentTitle, setCurrentTitle] = useState(''); const [currentContent, setCurrentContent] = useState(''); const [uploadedMedia, setUploadedMedia] = useState([]); const { submissionState, submitAnswers } = useExerciseSubmission({ onSuccess: (response) => { alert(`Diario guardado! Puntaje: ${response.score}`); onComplete?.(response); } }); const handleSaveEntry = async () => { if (!currentTitle || !currentContent) return; const newEntry: DiaryEntry = { id: Date.now().toString(), date: new Date(), title: currentTitle, content: currentContent, mediaIds: uploadedMedia.map(m => m.id), }; const updatedEntries = [newEntry, ...entries]; setEntries(updatedEntries); // Clear form setCurrentTitle(''); setCurrentContent(''); setUploadedMedia([]); }; const handleSubmitDiary = async () => { if (!exerciseId) return; await submitAnswers(exerciseId, { entries: entries.map(e => ({ title: e.title, content: e.content, mediaIds: e.mediaIds, date: e.date, })) }); }; return (
{/* Entry Form */} setCurrentTitle(e.target.value)} placeholder="Título" />