From cce68ac9cbc1986c9ca2c3f8600414f26f17a903 Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Fri, 19 Dec 2025 00:02:57 -0600 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20Migrar=2018=20archivos=20de?= =?UTF-8?q?=20mec=C3=A1nicas=20desde=20workspace-old?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mecánicas auxiliares migradas: - CallToAction (mockData, schemas, types) - CollagePrensa (mockData, schemas, types) - ComprensiónAuditiva (mockData, schemas, types) - TextoEnMovimiento (mockData, schemas, types) Mecánicas módulo 2 migradas: - ConstruccionHipotesis (causaEfectoMockData, causaEfectoSchemas) - LecturaInferencial (mockData, schemas) - PrediccionNarrativa (schemas) - PuzzleContexto (schemas) Build frontend validado exitosamente. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../CallToAction/callToActionMockData.ts | 39 ++++ .../CallToAction/callToActionSchemas.ts | 35 +++ .../CallToAction/callToActionTypes.ts | 59 +++++ .../CollagePrensa/collagePrensaMockData.ts | 24 ++ .../CollagePrensa/collagePrensaSchemas.ts | 45 ++++ .../CollagePrensa/collagePrensaTypes.ts | 70 ++++++ .../comprensionAuditivaMockData.ts | 49 ++++ .../comprensionAuditivaSchemas.ts | 32 +++ .../comprensionAuditivaTypes.ts | 56 +++++ .../textoEnMovimientoMockData.ts | 66 ++++++ .../textoEnMovimientoSchemas.ts | 46 ++++ .../textoEnMovimientoTypes.ts | 72 ++++++ .../causaEfectoMockData.ts | 134 +++++++++++ .../causaEfectoSchemas.ts | 91 ++++++++ .../lecturaInferencialMockData.ts | 151 +++++++++++++ .../lecturaInferencialSchemas.ts | 104 +++++++++ .../prediccionNarrativaSchemas.ts | 92 ++++++++ .../PuzzleContexto/puzzleContextoSchemas.ts | 72 ++++++ .../REQ-ANALISIS-MIGRACION-ORIGEN-DESTINO.md | 210 ++++++++++++++++++ 19 files changed, 1447 insertions(+) create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionTypes.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaTypes.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaTypes.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoTypes.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialMockData.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/PrediccionNarrativa/prediccionNarrativaSchemas.ts create mode 100644 projects/gamilit/apps/frontend/src/features/mechanics/module2/PuzzleContexto/puzzleContextoSchemas.ts create mode 100644 projects/gamilit/orchestration/analisis-migracion/REQ-ANALISIS-MIGRACION-ORIGEN-DESTINO.md diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionMockData.ts new file mode 100644 index 0000000..b2b4733 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionMockData.ts @@ -0,0 +1,39 @@ +import { CallToActionData } from './callToActionTypes'; +import { DifficultyLevel } from '@shared/types/educational.types'; + +export const mockCallToActionExercises: CallToActionData[] = [{ + id: 'call-to-action-001', + title: 'Call to Action: Campaña por las Mujeres en STEM', + description: 'Crea una campaña de acción social inspirada en el legado de Marie Curie para promover la participación de mujeres en ciencia y tecnología.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 600, + topic: 'Marie Curie - Activismo Social', + hints: [ + { id: 'h1', text: 'Piensa en las barreras que enfrentan las mujeres en STEM actualmente', cost: 10 }, + { id: 'h2', text: 'Conecta tu campaña con los valores y logros de Marie Curie', cost: 15 }, + { id: 'h3', text: 'Define acciones concretas y alcanzables para tu campaña', cost: 20 } + ], + availableCauses: [ + 'Más mujeres en STEM', + 'Becas científicas para mujeres', + 'Reconocimiento a científicas', + 'Educación científica inclusiva', + 'Igualdad en investigación' + ], + availableTags: [ + 'Ciencia', + 'Educación', + 'Igualdad', + 'Marie Curie', + 'Mujeres', + 'Investigación', + 'Nobel', + 'Física', + 'Química' + ], + minGoal: 50, + maxGoal: 1000, + goalStep: 50, + minSignatures: 0, + maxSignatures: 50 +}]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionSchemas.ts new file mode 100644 index 0000000..b5d020f --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionSchemas.ts @@ -0,0 +1,35 @@ +import { z } from 'zod'; + +export const campaignSchema = z.object({ + id: z.string(), + title: z.string().min(1, 'El título es requerido'), + cause: z.string().min(1, 'La causa es requerida'), + description: z.string().min(10, 'La descripción debe tener al menos 10 caracteres'), + goal: z.number().min(50).max(1000), + signatures: z.number().min(0), + tags: z.array(z.string()) +}); + +export const callToActionDataSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + difficulty: z.enum(['beginner', 'elementary', 'pre_intermediate', 'intermediate', 'upper_intermediate', 'advanced', 'proficient', 'native']), + estimatedTime: z.number(), + topic: z.string(), + hints: z.array(z.object({ id: z.string(), text: z.string(), cost: z.number() })), + availableCauses: z.array(z.string()), + availableTags: z.array(z.string()), + minGoal: z.number(), + maxGoal: z.number(), + goalStep: z.number(), + minSignatures: z.number(), + maxSignatures: z.number() +}); + +export const callToActionStateSchema = z.object({ + campaigns: z.array(campaignSchema), + score: z.number(), + timeSpent: z.number(), + hintsUsed: z.number() +}); diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionTypes.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionTypes.ts new file mode 100644 index 0000000..b6a28e7 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CallToAction/callToActionTypes.ts @@ -0,0 +1,59 @@ +import { BaseExercise } from '@shared/components/mechanics/mechanicsTypes'; + +export interface Campaign { + id: string; + title: string; + cause: string; + description: string; + goal: number; + signatures: number; + tags: string[]; +} + +export interface CallToActionData extends BaseExercise { + availableCauses: string[]; + availableTags: string[]; + minGoal: number; + maxGoal: number; + goalStep: number; + minSignatures: number; + maxSignatures: number; +} + +export interface ExerciseProgressUpdate { + currentStep: number; + totalSteps: number; + score: number; + hintsUsed: number; + timeSpent: number; +} + +// Exercise State for auto-save +export interface CallToActionState { + campaigns: Campaign[]; + score: number; + timeSpent: number; + hintsUsed: number; +} + +// Exercise Actions Interface for Parent Control +export interface CallToActionActions { + getState: () => CallToActionState; + reset: () => void; + validate: () => Promise; + createCampaign?: (campaign: Omit) => void; +} + +// Standardized Exercise Props Interface +export interface CallToActionExerciseProps { + moduleId: number; + lessonId: number; + exerciseId: string; + userId: string; + onComplete?: (score: number, timeSpent: number) => void; + onExit?: () => void; + onProgressUpdate?: (progress: ExerciseProgressUpdate) => void; + initialData?: Partial; + difficulty?: 'easy' | 'medium' | 'hard'; + actionsRef?: React.MutableRefObject; +} diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaMockData.ts new file mode 100644 index 0000000..b72702d --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaMockData.ts @@ -0,0 +1,24 @@ +import { CollagePrensaData } from './collagePrensaTypes'; +import { DifficultyLevel } from '@shared/types/educational.types'; + +export const mockCollagePrensaExercises: CollagePrensaData[] = [{ + id: 'collage-prensa-001', + title: 'Collage de Prensa: Marie Curie y sus Logros', + description: 'Crea un collage estilo periódico sobre Marie Curie y sus descubrimientos científicos.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 480, + topic: 'Marie Curie - Producción Visual', + hints: [ + { id: 'h1', text: 'Combina imágenes con titulares impactantes', cost: 10 }, + { id: 'h2', text: 'Usa texto descriptivo para explicar los descubrimientos', cost: 15 }, + { id: 'h3', text: 'Organiza los elementos de forma visualmente atractiva', cost: 20 } + ], + newspaperTitle: 'LE JOURNAL SCIENTIFIQUE', + newspaperDate: 'Paris, 1903', + canvasAspectRatio: '3/4', + minCanvasHeight: 800, + defaultHeadlineText: 'MARIE CURIE GANA PREMIO NOBEL', + defaultBodyText: 'La científica descubre el radio...', + defaultElementWidth: 30, + defaultElementHeight: 30 +}]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaSchemas.ts new file mode 100644 index 0000000..d48f1be --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaSchemas.ts @@ -0,0 +1,45 @@ +import { z } from 'zod'; + +export const uploadedFileSchema = z.object({ + id: z.string(), + name: z.string(), + url: z.string().url(), + type: z.string() +}); + +export const collageElementSchema = z.object({ + id: z.string(), + type: z.enum(['image', 'text', 'headline']), + content: z.string(), + x: z.number(), + y: z.number(), + width: z.number().min(1), + height: z.number().min(1), + rotation: z.number() +}); + +export const collagePrensaDataSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + difficulty: z.enum(['beginner', 'elementary', 'pre_intermediate', 'intermediate', 'upper_intermediate', 'advanced', 'proficient', 'native']), + estimatedTime: z.number(), + topic: z.string(), + hints: z.array(z.object({ id: z.string(), text: z.string(), cost: z.number() })), + newspaperTitle: z.string(), + newspaperDate: z.string(), + canvasAspectRatio: z.string(), + minCanvasHeight: z.number(), + defaultHeadlineText: z.string(), + defaultBodyText: z.string(), + defaultElementWidth: z.number(), + defaultElementHeight: z.number() +}); + +export const collagePrensaStateSchema = z.object({ + elements: z.array(collageElementSchema), + uploadedFiles: z.array(uploadedFileSchema), + score: z.number(), + timeSpent: z.number(), + hintsUsed: z.number() +}); diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaTypes.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaTypes.ts new file mode 100644 index 0000000..bee034f --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/CollagePrensa/collagePrensaTypes.ts @@ -0,0 +1,70 @@ +import { BaseExercise } from '@shared/components/mechanics/mechanicsTypes'; + +export interface UploadedFile { + id: string; + name: string; + url: string; + type: string; +} + +export interface CollageElement { + id: string; + type: 'image' | 'text' | 'headline'; + content: string; + x: number; + y: number; + width: number; + height: number; + rotation: number; +} + +export interface CollagePrensaData extends BaseExercise { + newspaperTitle: string; + newspaperDate: string; + canvasAspectRatio: string; + minCanvasHeight: number; + defaultHeadlineText: string; + defaultBodyText: string; + defaultElementWidth: number; + defaultElementHeight: number; +} + +export interface ExerciseProgressUpdate { + currentStep: number; + totalSteps: number; + score: number; + hintsUsed: number; + timeSpent: number; +} + +// Exercise State for auto-save +export interface CollagePrensaState { + elements: CollageElement[]; + uploadedFiles: UploadedFile[]; + score: number; + timeSpent: number; + hintsUsed: number; +} + +// Exercise Actions Interface for Parent Control +export interface CollagePrensaActions { + getState: () => CollagePrensaState; + reset: () => void; + validate: () => Promise; + addElement?: (element: CollageElement) => void; + removeElement?: (elementId: string) => void; +} + +// Standardized Exercise Props Interface +export interface CollagePrensaExerciseProps { + moduleId: number; + lessonId: number; + exerciseId: string; + userId: string; + onComplete?: (score: number, timeSpent: number) => void; + onExit?: () => void; + onProgressUpdate?: (progress: ExerciseProgressUpdate) => void; + initialData?: Partial; + difficulty?: 'easy' | 'medium' | 'hard'; + actionsRef?: React.MutableRefObject; +} diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaMockData.ts new file mode 100644 index 0000000..f45ee79 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaMockData.ts @@ -0,0 +1,49 @@ +import { ComprensiónAuditivaData } from './comprensionAuditivaTypes'; +import { DifficultyLevel } from '@shared/types/educational.types'; + +export const mockComprensiónAuditivaExercises: ComprensiónAuditivaData[] = [{ + id: 'comprension-auditiva-001', + title: 'Comprensión Auditiva: La Vida de Marie Curie', + description: 'Escucha el audio sobre Marie Curie y responde las preguntas de comprensión.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 420, + topic: 'Marie Curie - Comprensión Oral', + hints: [ + { id: 'h1', text: 'Escucha con atención los años y fechas mencionados', cost: 10 }, + { id: 'h2', text: 'Presta atención a los nombres de los elementos descubiertos', cost: 15 }, + { id: 'h3', text: 'Puedes reproducir el audio varias veces', cost: 5 } + ], + audioUrl: 'https://example.com/marie-curie-biography.mp3', + audioTitle: 'Biografía de Marie Curie', + audioDuration: 180, + questions: [ + { + id: 'q1', + time: 10, + question: '¿Dónde nació Marie Curie?', + options: ['Francia', 'Polonia', 'Rusia', 'Alemania'], + correctAnswer: 1 + }, + { + id: 'q2', + time: 30, + question: '¿Qué elemento descubrió Marie Curie junto con su esposo?', + options: ['Uranio', 'Polonio y Radio', 'Hierro', 'Oro'], + correctAnswer: 1 + }, + { + id: 'q3', + time: 50, + question: '¿Cuántos Premios Nobel ganó Marie Curie?', + options: ['Uno', 'Dos', 'Tres', 'Ninguno'], + correctAnswer: 1 + }, + { + id: 'q4', + time: 70, + question: '¿En qué campos ganó Marie Curie los Premios Nobel?', + options: ['Física y Química', 'Medicina y Física', 'Química y Medicina', 'Literatura y Paz'], + correctAnswer: 0 + } + ] +}]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaSchemas.ts new file mode 100644 index 0000000..bebda32 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaSchemas.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +export const questionSchema = z.object({ + id: z.string(), + time: z.number().min(0), + question: z.string().min(1, 'La pregunta es requerida'), + options: z.array(z.string()).min(2, 'Se requieren al menos 2 opciones'), + correctAnswer: z.number().min(0) +}); + +export const comprensionAuditivaDataSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + difficulty: z.enum(['beginner', 'elementary', 'pre_intermediate', 'intermediate', 'upper_intermediate', 'advanced', 'proficient', 'native']), + estimatedTime: z.number(), + topic: z.string(), + hints: z.array(z.object({ id: z.string(), text: z.string(), cost: z.number() })), + audioUrl: z.string().url(), + audioTitle: z.string(), + audioDuration: z.number().min(0), + questions: z.array(questionSchema) +}); + +export const comprensionAuditivaStateSchema = z.object({ + answers: z.record(z.string(), z.number()), + currentTime: z.number(), + showResults: z.boolean(), + score: z.number(), + timeSpent: z.number(), + hintsUsed: z.number() +}); diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaTypes.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaTypes.ts new file mode 100644 index 0000000..5880a8a --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/ComprensiónAuditiva/comprensionAuditivaTypes.ts @@ -0,0 +1,56 @@ +import { BaseExercise } from '@shared/components/mechanics/mechanicsTypes'; + +export interface Question { + id: string; + time: number; + question: string; + options: string[]; + correctAnswer: number; +} + +export interface ComprensiónAuditivaData extends BaseExercise { + audioUrl: string; + audioTitle: string; + audioDuration: number; + questions: Question[]; +} + +export interface ExerciseProgressUpdate { + currentStep: number; + totalSteps: number; + score: number; + hintsUsed: number; + timeSpent: number; +} + +// Exercise State for auto-save +export interface ComprensiónAuditivaState { + answers: Record; + currentTime: number; + showResults: boolean; + score: number; + timeSpent: number; + hintsUsed: number; +} + +// Exercise Actions Interface for Parent Control +export interface ComprensiónAuditivaActions { + getState: () => ComprensiónAuditivaState; + reset: () => void; + validate: () => Promise; + submitAnswer?: (questionId: string, answerIndex: number) => void; +} + +// Standardized Exercise Props Interface +export interface ComprensiónAuditivaExerciseProps { + moduleId: number; + lessonId: number; + exerciseId: string; + userId: string; + onComplete?: (score: number, timeSpent: number) => void; + onExit?: () => void; + onProgressUpdate?: (progress: ExerciseProgressUpdate) => void; + initialData?: Partial; + difficulty?: 'easy' | 'medium' | 'hard'; + actionsRef?: React.MutableRefObject; +} diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoMockData.ts new file mode 100644 index 0000000..18a66af --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoMockData.ts @@ -0,0 +1,66 @@ +import { TextoEnMovimientoData } from './textoEnMovimientoTypes'; +import { DifficultyLevel } from '@shared/types/educational.types'; + +export const mockTextoEnMovimientoExercises: TextoEnMovimientoData[] = [{ + id: 'texto-movimiento-001', + title: 'Texto en Movimiento: Cronología de Marie Curie', + description: 'Crea animaciones de texto sobre Marie Curie y sus descubrimientos científicos.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 360, + topic: 'Marie Curie - Producción Multimedia', + hints: [ + { id: 'h1', text: 'Combina diferentes tipos de animación para mayor impacto', cost: 10 }, + { id: 'h2', text: 'Ajusta la duración de cada texto para crear un ritmo agradable', cost: 15 }, + { id: 'h3', text: 'Usa colores que resalten sobre el fondo oscuro', cost: 10 } + ], + animations: [ + { + id: 'fadeIn', + name: 'Aparecer', + variants: { hidden: { opacity: 0 }, visible: { opacity: 1 } } + }, + { + id: 'slideUp', + name: 'Deslizar Arriba', + variants: { hidden: { y: 100, opacity: 0 }, visible: { y: 0, opacity: 1 } } + }, + { + id: 'slideDown', + name: 'Deslizar Abajo', + variants: { hidden: { y: -100, opacity: 0 }, visible: { y: 0, opacity: 1 } } + }, + { + id: 'slideLeft', + name: 'Deslizar Izquierda', + variants: { hidden: { x: -100, opacity: 0 }, visible: { x: 0, opacity: 1 } } + }, + { + id: 'slideRight', + name: 'Deslizar Derecha', + variants: { hidden: { x: 100, opacity: 0 }, visible: { x: 0, opacity: 1 } } + }, + { + id: 'scale', + name: 'Escalar', + variants: { hidden: { scale: 0, opacity: 0 }, visible: { scale: 1, opacity: 1 } } + }, + { + id: 'rotate', + name: 'Rotar', + variants: { hidden: { rotate: -180, opacity: 0 }, visible: { rotate: 0, opacity: 1 } } + }, + { + id: 'bounce', + name: 'Rebotar', + variants: { hidden: { y: -100, opacity: 0 }, visible: { y: 0, opacity: 1 } } + } + ], + availableColors: ['#f97316', '#1e3a8a', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6'], + minDuration: 0.5, + maxDuration: 5, + durationStep: 0.5, + minFontSize: 16, + maxFontSize: 96, + fontSizeStep: 4, + defaultText: 'Marie Curie' +}]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoSchemas.ts new file mode 100644 index 0000000..b466e5e --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoSchemas.ts @@ -0,0 +1,46 @@ +import { z } from 'zod'; + +export const animatedTextSchema = z.object({ + id: z.string(), + text: z.string().min(1, 'El texto es requerido'), + animation: z.string(), + duration: z.number().min(0.5).max(5), + color: z.string(), + fontSize: z.number().min(16).max(96) +}); + +export const animationConfigSchema = z.object({ + id: z.string(), + name: z.string(), + variants: z.object({ + hidden: z.record(z.unknown()), + visible: z.record(z.unknown()) + }) +}); + +export const textoEnMovimientoDataSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + difficulty: z.enum(['beginner', 'elementary', 'pre_intermediate', 'intermediate', 'upper_intermediate', 'advanced', 'proficient', 'native']), + estimatedTime: z.number(), + topic: z.string(), + hints: z.array(z.object({ id: z.string(), text: z.string(), cost: z.number() })), + animations: z.array(animationConfigSchema), + availableColors: z.array(z.string()), + minDuration: z.number(), + maxDuration: z.number(), + durationStep: z.number(), + minFontSize: z.number(), + maxFontSize: z.number(), + fontSizeStep: z.number(), + defaultText: z.string() +}); + +export const textoEnMovimientoStateSchema = z.object({ + texts: z.array(animatedTextSchema), + isPlaying: z.boolean(), + score: z.number(), + timeSpent: z.number(), + hintsUsed: z.number() +}); diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoTypes.ts b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoTypes.ts new file mode 100644 index 0000000..91098fa --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/auxiliar/TextoEnMovimiento/textoEnMovimientoTypes.ts @@ -0,0 +1,72 @@ +import { BaseExercise } from '@shared/components/mechanics/mechanicsTypes'; + +export interface AnimatedText { + id: string; + text: string; + animation: string; + duration: number; + color: string; + fontSize: number; +} + +export interface AnimationConfig { + id: string; + name: string; + variants: { + hidden: Record; + visible: Record; + }; +} + +export interface TextoEnMovimientoData extends BaseExercise { + animations: AnimationConfig[]; + availableColors: string[]; + minDuration: number; + maxDuration: number; + durationStep: number; + minFontSize: number; + maxFontSize: number; + fontSizeStep: number; + defaultText: string; +} + +export interface ExerciseProgressUpdate { + currentStep: number; + totalSteps: number; + score: number; + hintsUsed: number; + timeSpent: number; +} + +// Exercise State for auto-save +export interface TextoEnMovimientoState { + texts: AnimatedText[]; + isPlaying: boolean; + score: number; + timeSpent: number; + hintsUsed: number; +} + +// Exercise Actions Interface for Parent Control +export interface TextoEnMovimientoActions { + getState: () => TextoEnMovimientoState; + reset: () => void; + validate: () => Promise; + addText?: (text: AnimatedText) => void; + removeText?: (textId: string) => void; + togglePlay?: () => void; +} + +// Standardized Exercise Props Interface +export interface TextoEnMovimientoExerciseProps { + moduleId: number; + lessonId: number; + exerciseId: string; + userId: string; + onComplete?: (score: number, timeSpent: number) => void; + onExit?: () => void; + onProgressUpdate?: (progress: ExerciseProgressUpdate) => void; + initialData?: Partial; + difficulty?: 'easy' | 'medium' | 'hard'; + actionsRef?: React.MutableRefObject; +} diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoMockData.ts new file mode 100644 index 0000000..5ca8725 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoMockData.ts @@ -0,0 +1,134 @@ +/** + * Causa-Efecto Mock Data - Marie Curie Cause and Effect Relationships + * Drag and drop exercise for understanding causal relationships in science + */ + +import { DifficultyLevel } from '@shared/types/educational.types'; +import type { CausaEfectoData } from './causaEfectoTypes'; + +export const causaEfectoMockExercise: CausaEfectoData = { + id: 'causa-efecto-001', + type: 'matching', + title: 'Construcción de Hipótesis: Causa y Efecto', + description: + 'Relaciona las causas con sus consecuencias en la vida científica de Marie Curie.', + instructions: + 'Arrastra cada consecuencia (derecha) hacia la causa correspondiente (izquierda). Analiza cuidadosamente las relaciones causales entre los eventos científicos y sus resultados.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 360, // 6 minutes + maxAttempts: 3, + hints: [ + { + id: 'hint-001', + text: 'Considera el orden cronológico de los eventos y cómo cada descubrimiento llevó a nuevas oportunidades o desafíos.', + cost: 10, + }, + { + id: 'hint-002', + text: 'Piensa en las consecuencias tanto científicas como personales de cada acción de Marie Curie.', + cost: 15, + }, + { + id: 'hint-003', + text: 'Algunas causas pueden tener múltiples efectos. Busca las relaciones lógicas más directas entre causa y consecuencia.', + cost: 20, + }, + ], + config: { + allowMultiple: true, // Una causa puede tener múltiples consecuencias + showFeedback: true, + dragAndDrop: true, + }, + content: { + causes: [ + { + id: 'causa-1', + text: 'Marie Curie trabajó con materiales radiactivos sin protección durante años', + }, + { + id: 'causa-2', + text: 'Marie descubrió dos nuevos elementos químicos: el polonio y el radio', + }, + { + id: 'causa-3', + text: 'El esposo de Marie, Pierre Curie, murió en un accidente en 1906', + }, + { + id: 'causa-4', + text: 'Marie fue la primera mujer en ganar un Premio Nobel', + }, + { + id: 'causa-5', + text: 'Durante la Primera Guerra Mundial, Marie desarrolló unidades móviles de rayos X', + }, + { + id: 'causa-6', + text: 'Marie compartió sus investigaciones sobre radiactividad libremente con la comunidad científica', + }, + ], + consequences: [ + { + id: 'consecuencia-1', + text: 'Desarrolló anemia aplásica y murió de leucemia en 1934', + }, + { + id: 'consecuencia-2', + text: 'Recibió el Premio Nobel de Química en 1911, siendo la primera persona en ganar dos premios Nobel', + }, + { + id: 'consecuencia-3', + text: 'Abrió camino para que más mujeres ingresaran a la ciencia', + }, + { + id: 'consecuencia-4', + text: 'Marie asumió la cátedra de física de Pierre, convirtiéndose en la primera profesora mujer de la Sorbona', + }, + { + id: 'consecuencia-5', + text: 'Salvó la vida de miles de soldados heridos mediante diagnósticos médicos precisos', + }, + { + id: 'consecuencia-6', + text: 'Aceleró el progreso científico global al no patentar sus descubrimientos', + }, + { + id: 'consecuencia-7', + text: 'Sus cuadernos de investigación permanecen radiactivos hasta hoy, requiriendo protección especial para consultarlos', + }, + { + id: 'consecuencia-8', + text: 'El polonio recibió su nombre en honor a Polonia, su país natal', + }, + { + id: 'consecuencia-9', + text: 'Tuvo que enfrentar sola la responsabilidad de criar a sus dos hijas mientras continuaba su investigación', + }, + { + id: 'consecuencia-10', + text: 'Las aplicaciones médicas del radio revolucionaron el tratamiento del cáncer', + }, + ], + }, +}; + +/** + * Expected correct matches for testing/validation: + * + * causa-1 → consecuencia-1 (trabajó sin protección → murió de leucemia) + * causa-1 → consecuencia-7 (trabajó sin protección → cuadernos radiactivos) + * + * causa-2 → consecuencia-2 (descubrió elementos → Nobel de Química) + * causa-2 → consecuencia-8 (descubrió elementos → polonio honra a Polonia) + * causa-2 → consecuencia-10 (descubrió elementos → aplicaciones médicas) + * + * causa-3 → consecuencia-4 (Pierre murió → Marie asumió su cátedra) + * causa-3 → consecuencia-9 (Pierre murió → criar hijas sola) + * + * causa-4 → consecuencia-3 (primera Nobel → abrió camino para mujeres) + * + * causa-5 → consecuencia-5 (unidades móviles rayos X → salvó soldados) + * + * causa-6 → consecuencia-6 (compartió investigación → aceleró progreso) + */ + +export const causaEfectoMockData = [causaEfectoMockExercise]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoSchemas.ts new file mode 100644 index 0000000..42e6910 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/ConstruccionHipotesis/causaEfectoSchemas.ts @@ -0,0 +1,91 @@ +/** + * Causa-Efecto Zod Schemas + * Validation schemas for cause-effect drag & drop exercise + * ⚠️ FE-059: correctCauseIds is NEVER sent by backend (sanitized for security) + */ + +import { z } from 'zod'; + +/** + * A cause schema (left column, fixed) + */ +export const causeSchema = z.object({ + id: z.string(), + text: z.string().min(1), +}); + +/** + * A consequence schema (right column, draggable) + * Note: correctCauseIds is never present (backend sanitizes it) + */ +export const consequenceSchema = z.object({ + id: z.string(), + text: z.string().min(1), + correctCauseIds: z.never().optional(), +}); + +/** + * Configuration for the exercise schema + */ +export const causaEfectoConfigSchema = z.object({ + allowMultiple: z.boolean(), + showFeedback: z.boolean(), + dragAndDrop: z.boolean(), +}); + +/** + * Content structure for the exercise schema + */ +export const causaEfectoContentSchema = z.object({ + causes: z.array(causeSchema).min(1), + consequences: z.array(consequenceSchema).min(1), +}); + +/** + * Complete exercise data structure schema + */ +export const causaEfectoExerciseSchema = z.object({ + id: z.string(), + type: z.literal('causa_efecto'), + title: z.string().min(1), + description: z.string().optional(), + instructions: z.string().optional(), + config: causaEfectoConfigSchema, + content: causaEfectoContentSchema, + difficulty: z.enum(['easy', 'medium', 'hard']).optional(), +}); + +/** + * User's matches schema (cause ID -> array of consequence IDs) + */ +export const causeMatchesSchema = z.record(z.string(), z.array(z.string())); + +/** + * Answers structure for CausaEfecto exercise schema + */ +export const causaEfectoAnswersSchema = z.object({ + matches: causeMatchesSchema, +}); + +/** + * Exercise state schema (for auto-save) + */ +export const causaEfectoStateSchema = z.object({ + matches: causeMatchesSchema, + score: z.number().min(0).max(100), + timeSpent: z.number().min(0), + hintsUsed: z.number().min(0), + completed: z.boolean(), +}); + +/** + * Type exports + */ +export type Cause = z.infer; +export type Consequence = z.infer; +export type CausaEfectoConfig = z.infer; +export type CausaEfectoContent = z.infer; +export type CausaEfectoExercise = z.infer; +export type CauseMatches = z.infer; +export type CausaEfectoAnswers = z.infer; +export type CausaEfectoState = z.infer; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialMockData.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialMockData.ts new file mode 100644 index 0000000..d295e41 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialMockData.ts @@ -0,0 +1,151 @@ +/** + * Lectura Inferencial Mock Data - Marie Curie Reading Comprehension + * Multiple choice exercise for inferential reading comprehension + */ + +import { DifficultyLevel } from '@shared/types/educational.types'; +import type { LecturaInferencialData } from './lecturaInferencialTypes'; + +export const lecturaInferencialMockExercise: LecturaInferencialData = { + id: 'lectura-inferencial-001', + type: 'multiple-choice', + title: 'Lectura Inferencial: Los Primeros Años de Marie Curie', + description: + 'Lee el texto sobre los inicios de Marie Curie y responde las preguntas haciendo inferencias basadas en el contexto.', + instructions: + 'Lee cuidadosamente el pasaje y selecciona la respuesta más apropiada para cada pregunta. Las respuestas requieren hacer inferencias más allá de lo explícitamente dicho en el texto.', + difficulty: DifficultyLevel.INTERMEDIATE, + estimatedTime: 480, // 8 minutes + maxAttempts: 3, + hints: [ + { + id: 'hint-001', + text: 'Presta atención a las motivaciones implícitas de los personajes y el contexto histórico de la época.', + cost: 10, + }, + { + id: 'hint-002', + text: 'Considera las barreras sociales que enfrentaban las mujeres en la educación superior durante el siglo XIX.', + cost: 15, + }, + { + id: 'hint-003', + text: 'Las respuestas correctas no siempre están explícitas en el texto. Analiza las relaciones causa-efecto y el contexto cultural.', + cost: 20, + }, + ], + config: { + timePerQuestion: 120, // 2 minutes per question + allowReview: true, + showExplanations: true, + shuffleQuestions: false, + shuffleOptions: true, + }, + content: { + passage: ` +Varsovia, 1883. Maria Sklodowska tenía apenas 16 años cuando completó sus estudios secundarios con medalla de oro. Sin embargo, su brillante desempeño académico no le garantizaba el acceso a la universidad. En Polonia, bajo el dominio ruso, las mujeres tenían prohibido asistir a la educación superior. + +Maria y su hermana mayor, Bronya, soñaban con estudiar en la Universidad de la Sorbona en París, pero la situación económica de la familia lo hacía imposible. Su padre, maestro de física y matemáticas, había perdido sus ahorros en malas inversiones, y mantenía a la familia con un salario modesto. + +Las dos hermanas idearon un plan audaz: Maria trabajaría como institutriz durante seis años para financiar los estudios de Bronya en París. Una vez que Bronya se graduara como médica y empezara a ejercer, sería ella quien financiaría los estudios de Maria. Durante esos años, Maria enseñaba durante el día a los hijos de familias acomodadas y estudiaba por las noches, devorando libros de física, química y matemáticas que su padre le enviaba. + +En 1891, a los 24 años, Maria finalmente llegó a París con lo mínimo: algunas mudas de ropa, unos cuantos libros y la determinación férrea que la caracterizaría toda su vida. Se inscribió en la Facultad de Ciencias de la Sorbona como "Marie", la versión francesa de su nombre. Vivía en una buhardilla helada del Barrio Latino, con frecuencia sin calefacción ni comida suficiente, dedicando cada momento a sus estudios. + +Dos años después, Marie obtuvo su licenciatura en Física, siendo la primera de su promoción. Un año más tarde completó una segunda licenciatura en Matemáticas. Su talento excepcional había superado todos los obstáculos que la sociedad y las circunstancias habían puesto en su camino. + `.trim(), + questions: [ + { + id: 'li-q1', + question: + '¿Qué se puede inferir sobre la relación entre Maria y su hermana Bronya?', + options: [ + 'Eran rivales académicas que competían por la atención de su padre', + 'Compartían una profunda confianza mutua y compromiso con el éxito de la otra', + 'Bronya obligó a Maria a trabajar para pagar sus estudios', + 'Maria sentía resentimiento por tener que esperar su turno', + ], + correctAnswer: 1, + explanation: + 'El pacto entre las hermanas demuestra una confianza extraordinaria. Maria aceptó trabajar seis años con la promesa de que Bronya la apoyaría después. Este acuerdo requería una fe mutua profunda y un compromiso compartido con la educación, mostrando una relación basada en solidaridad y sacrificio recíproco.', + inference_type: 'motivacion', + }, + { + id: 'li-q2', + question: + '¿Por qué el texto menciona específicamente que Maria llegó a París "con lo mínimo"?', + options: [ + 'Para mostrar que era descuidada con sus pertenencias', + 'Para enfatizar su humildad económica y su enfoque en lo esencial', + 'Para sugerir que no valoraba las posesiones materiales', + 'Para indicar que había perdido su equipaje en el viaje', + ], + correctAnswer: 1, + explanation: + 'La mención de sus escasas pertenencias contrasta con su "determinación férrea", enfatizando que a pesar de la pobreza material, su riqueza intelectual y motivación eran inmensas. Este detalle subraya el sacrificio y la priorización de sus objetivos académicos sobre el confort material.', + inference_type: 'contexto_situacional', + }, + { + id: 'li-q3', + question: + '¿Qué sugiere el cambio de "Maria" a "Marie" al inscribirse en la Sorbona?', + options: [ + 'Quería ocultar su identidad polaca por vergüenza', + 'Era un requisito obligatorio de la universidad francesa', + 'Buscaba adaptarse al contexto francés para facilitar su integración', + 'Fue un error administrativo de la universidad', + ], + correctAnswer: 2, + explanation: + 'El cambio a la versión francesa de su nombre sugiere una adaptación práctica al nuevo entorno. Para una mujer extranjera en el París del siglo XIX, ya enfrentaba múltiples barreras; adaptar su nombre era una estrategia pragmática para integrarse mejor y reducir obstáculos adicionales, sin necesariamente renunciar a su identidad.', + inference_type: 'interpretacion', + }, + { + id: 'li-q4', + question: + 'Basándose en el texto, ¿qué efecto probablemente tuvo la experiencia como institutriz en el desarrollo de Marie?', + options: [ + 'Le hizo perder interés en la ciencia al dedicarse a la enseñanza', + 'Desarrolló habilidades de comunicación y disciplina que fortalecieron su carácter', + 'Fue únicamente una pérdida de tiempo que retrasó su carrera', + 'La convenció de que prefería enseñar a investigar', + ], + correctAnswer: 1, + explanation: + 'Aunque no se menciona explícitamente, el texto indica que durante esos seis años Marie "estudiaba por las noches" mientras trabajaba de día. Esta experiencia de simultanear trabajo, estudio y sacrificio personal probablemente fortaleció su disciplina, perseverancia y capacidad de organización, cualidades esenciales para su futura carrera científica.', + inference_type: 'causa_efecto', + }, + { + id: 'li-q5', + question: + '¿Qué se puede concluir sobre las condiciones de vida de Marie en París?', + options: [ + 'Vivía cómodamente gracias al apoyo financiero de Bronya', + 'Eligió vivir en pobreza extrema a pesar de tener otras opciones', + 'Sus condiciones precarias reflejaban su compromiso total con los estudios y recursos limitados', + 'La universidad le proporcionaba alojamiento inadecuado', + ], + correctAnswer: 2, + explanation: + 'La descripción de su "buhardilla helada", frecuentemente "sin calefacción ni comida suficiente" mientras dedicaba "cada momento a sus estudios" revela que Marie vivía en condiciones de extrema austeridad. Esto no era una elección caprichosa, sino el resultado de recursos muy limitados combinados con una dedicación absoluta a sus estudios, sacrificando comodidades básicas por su educación.', + inference_type: 'conclusion', + }, + { + id: 'li-q6', + question: + '¿Qué predice el texto sobre el futuro académico de Marie después de obtener sus licenciaturas?', + options: [ + 'Probablemente regresaría a Polonia satisfecha con sus logros', + 'Continuaría superando obstáculos y alcanzando logros mayores en la ciencia', + 'Dejaría la ciencia para dedicarse a enseñar como su padre', + 'Se conformaría con un puesto académico modesto', + ], + correctAnswer: 1, + explanation: + 'La frase final "Su talento excepcional había superado todos los obstáculos" junto con la descripción constante de su "determinación férrea" y capacidad para triunfar en circunstancias adversas, sugiere fuertemente que Marie continuaría enfrentando y superando desafíos. El tono del texto establece un patrón de perseverancia y excelencia que anticipa logros futuros aún mayores.', + inference_type: 'prediccion', + }, + ], + }, +}; + +export const lecturaInferencialMockData = [lecturaInferencialMockExercise]; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialSchemas.ts new file mode 100644 index 0000000..d32651e --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/LecturaInferencial/lecturaInferencialSchemas.ts @@ -0,0 +1,104 @@ +/** + * Lectura Inferencial Zod Schemas + * Validation schemas for inferential reading comprehension exercise + */ + +import { z } from 'zod'; + +/** + * Types of inferences students can make + */ +export const inferenceTypeSchema = z.enum([ + 'causa_efecto', + 'contexto_situacional', + 'motivacion', + 'prediccion', + 'conclusion', + 'interpretacion', +]); + +/** + * Individual multiple choice question schema + */ +export const inferenceQuestionSchema = z.object({ + id: z.string(), + question: z.string().min(1), + options: z.array(z.string()).min(2), + correctAnswer: z.number().min(0), + explanation: z.string().min(1), + inference_type: inferenceTypeSchema, +}); + +/** + * Student's answer to a question schema + */ +export const questionAnswerSchema = z.object({ + questionId: z.string(), + selectedOption: z.number().min(0), + isCorrect: z.boolean(), + timeSpent: z.number().min(0), +}); + +/** + * Configuration for the exercise schema + */ +export const lecturaInferencialConfigSchema = z.object({ + timePerQuestion: z.number().min(0).optional(), + allowReview: z.boolean().optional(), + showExplanations: z.boolean().optional(), + shuffleQuestions: z.boolean().optional(), + shuffleOptions: z.boolean().optional(), +}); + +/** + * Content structure for the exercise schema + */ +export const lecturaInferencialContentSchema = z.object({ + passage: z.string().min(1), + questions: z.array(inferenceQuestionSchema).min(1), +}); + +/** + * Complete exercise data structure schema + */ +export const lecturaInferencialExerciseSchema = z.object({ + id: z.string(), + type: z.literal('lectura_inferencial'), + title: z.string().min(1), + description: z.string().optional(), + instructions: z.string().optional(), + config: lecturaInferencialConfigSchema, + content: lecturaInferencialContentSchema, + difficulty: z.enum(['easy', 'medium', 'hard']).optional(), +}); + +/** + * Exercise progress/state schema + */ +export const lecturaInferencialProgressSchema = z.object({ + answers: z.array(questionAnswerSchema), + currentQuestionIndex: z.number().min(0), + timeSpent: z.number().min(0), + score: z.number().min(0).max(100), + hintsUsed: z.number().min(0), + completed: z.boolean(), +}); + +/** + * Answers structure for LecturaInferencial exercise schema + */ +export const lecturaInferencialAnswersSchema = z.object({ + questions: z.record(z.string(), z.number().min(0)), +}); + +/** + * Type exports + */ +export type InferenceType = z.infer; +export type InferenceQuestion = z.infer; +export type QuestionAnswer = z.infer; +export type LecturaInferencialConfig = z.infer; +export type LecturaInferencialContent = z.infer; +export type LecturaInferencialExercise = z.infer; +export type LecturaInferencialProgress = z.infer; +export type LecturaInferencialAnswers = z.infer; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/PrediccionNarrativa/prediccionNarrativaSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/PrediccionNarrativa/prediccionNarrativaSchemas.ts new file mode 100644 index 0000000..b651ed6 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/PrediccionNarrativa/prediccionNarrativaSchemas.ts @@ -0,0 +1,92 @@ +/** + * Prediccion Narrativa Zod Schemas + * Validation schemas for narrative prediction exercise + * ⚠️ FE-059: isCorrect is NEVER sent by backend (sanitized for security) + */ + +import { z } from 'zod'; + +/** + * Prediction option schema + * Note: isCorrect is never present (backend sanitizes it) + */ +export const predictionOptionSchema = z.object({ + id: z.string(), + text: z.string().min(1), + isCorrect: z.never().optional(), + explanation: z.string().min(1), +}); + +/** + * Scenario schema + */ +export const scenarioSchema = z.object({ + id: z.string(), + context: z.string().min(1), + beginning: z.string().min(1), + question: z.string().min(1), + predictions: z.array(predictionOptionSchema).min(2), + contextualHint: z.string().optional(), +}); + +/** + * Exercise data schema + */ +export const prediccionNarrativaExerciseSchema = z.object({ + id: z.string(), + type: z.literal('prediccion_narrativa').optional(), + title: z.string().min(1), + subtitle: z.string().optional(), + description: z.string().optional(), + instructions: z.string().optional(), + scenarios: z.array(scenarioSchema).min(1), +}); + +/** + * User answer for a scenario schema + */ +export const scenarioAnswerSchema = z.object({ + scenarioId: z.string(), + selectedPredictionId: z.string().nullable(), + isCorrect: z.boolean().nullable(), +}); + +/** + * Exercise progress update schema + */ +export const exerciseProgressUpdateSchema = z.object({ + currentStep: z.number().min(0), + totalSteps: z.number().min(1), + score: z.number().min(0).max(100), + hintsUsed: z.number().min(0), + timeSpent: z.number().min(0), +}); + +/** + * Exercise state schema (for auto-save) + */ +export const prediccionNarrativaStateSchema = z.object({ + answers: z.array(scenarioAnswerSchema), + score: z.number().min(0).max(100), + timeSpent: z.number().min(0), + hintsUsed: z.number().min(0), + showResults: z.boolean(), +}); + +/** + * Answers structure schema + */ +export const prediccionNarrativaAnswersSchema = z.object({ + scenarios: z.record(z.string(), z.string().nullable()), +}); + +/** + * Type exports + */ +export type PredictionOption = z.infer; +export type Scenario = z.infer; +export type PrediccionNarrativaExercise = z.infer; +export type ScenarioAnswer = z.infer; +export type ExerciseProgressUpdate = z.infer; +export type PrediccionNarrativaState = z.infer; +export type PrediccionNarrativaAnswers = z.infer; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/PuzzleContexto/puzzleContextoSchemas.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module2/PuzzleContexto/puzzleContextoSchemas.ts new file mode 100644 index 0000000..ce1547c --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/PuzzleContexto/puzzleContextoSchemas.ts @@ -0,0 +1,72 @@ +/** + * Puzzle Contexto Zod Schemas + * Validation schemas for context puzzle ordering exercise + * ⚠️ FE-059: correctPosition and correctOrder are NEVER sent by backend (sanitized for security) + */ + +import { z } from 'zod'; + +/** + * Fragment schema + * Note: correctPosition is never present (backend sanitizes it) + */ +export const fragmentSchema = z.object({ + id: z.string(), + label: z.string(), + text: z.string().min(1), + correctPosition: z.never().optional(), +}); + +/** + * Exercise data schema + * Note: correctOrder is never present (backend sanitizes it) + */ +export const puzzleContextoExerciseSchema = z.object({ + id: z.string(), + type: z.literal('puzzle_contexto').optional(), + title: z.string().min(1), + subtitle: z.string().optional(), + description: z.string().min(1), + instructions: z.string().optional(), + completeInference: z.string().min(1), + fragments: z.array(fragmentSchema).min(2), + correctOrder: z.never().optional(), +}); + +/** + * Exercise progress update schema + */ +export const exerciseProgressUpdateSchema = z.object({ + currentStep: z.number().min(0), + totalSteps: z.number().min(1), + score: z.number().min(0).max(100), + hintsUsed: z.number().min(0), + timeSpent: z.number().min(0), +}); + +/** + * Exercise state schema (for auto-save) + */ +export const puzzleContextoStateSchema = z.object({ + currentOrder: z.array(z.string()), + isComplete: z.boolean(), + score: z.number().min(0).max(100), + timeSpent: z.number().min(0), + hintsUsed: z.number().min(0), +}); + +/** + * Answers structure schema + */ +export const puzzleContextoAnswersSchema = z.object({ + order: z.array(z.string()).min(1), +}); + +/** + * Type exports + */ +export type Fragment = z.infer; +export type PuzzleContextoExercise = z.infer; +export type ExerciseProgressUpdate = z.infer; +export type PuzzleContextoState = z.infer; +export type PuzzleContextoAnswers = z.infer; diff --git a/projects/gamilit/orchestration/analisis-migracion/REQ-ANALISIS-MIGRACION-ORIGEN-DESTINO.md b/projects/gamilit/orchestration/analisis-migracion/REQ-ANALISIS-MIGRACION-ORIGEN-DESTINO.md new file mode 100644 index 0000000..7a7a195 --- /dev/null +++ b/projects/gamilit/orchestration/analisis-migracion/REQ-ANALISIS-MIGRACION-ORIGEN-DESTINO.md @@ -0,0 +1,210 @@ +# Análisis de Requerimientos: Migración ORIGEN → DESTINO + +**Fecha:** 2025-12-18 +**Analista:** Requirements-Analyst +**Proyecto:** GAMILIT + +--- + +## 1. IDENTIFICACIÓN DE WORKSPACES + +| Aspecto | ORIGEN (Viejo) | DESTINO (Nuevo) | +|---------|----------------|-----------------| +| **Path** | `/home/isem/workspace-old/wsl-ubuntu/workspace/workspace-gamilit/gamilit/projects/gamilit` | `/home/isem/workspace/projects/gamilit` | +| **Propósito** | Producción/Desarrollo activo | Desarrollo nuevo | +| **Prioridad** | Contiene desarrollos más recientes | Destino de migración | + +--- + +## 2. RESUMEN EJECUTIVO DE DIFERENCIAS + +### 2.1 Estadísticas Generales + +| Componente | ORIGEN | DESTINO | Diferencia | +|------------|--------|---------|------------| +| Backend (.ts) | 845 archivos | 845 archivos | **IDÉNTICO** | +| Frontend (.ts/.tsx) | 935 archivos | 917 archivos | **+18 en ORIGEN** | +| Database (.sql) | 562 archivos | 562 archivos | **IDÉNTICO** | +| Docs (.md) | 431 archivos | 435 archivos | **+4 en DESTINO** | + +### 2.2 Archivos Críticos Raíz + +| Archivo | Estado | +|---------|--------| +| `package.json` | IDÉNTICO | +| `package-lock.json` | IDÉNTICO | +| `ecosystem.config.js` | IDÉNTICO | +| `tsconfig.json` | IDÉNTICO | +| `README.md` | IDÉNTICO | + +--- + +## 3. DETALLE DE DIFERENCIAS + +### 3.1 Frontend - 18 Archivos SOLO en ORIGEN + +**Mecánicas Auxiliares:** +``` +features/mechanics/auxiliar/CallToAction/ +├── callToActionMockData.ts +├── callToActionSchemas.ts +└── callToActionTypes.ts + +features/mechanics/auxiliar/CollagePrensa/ +├── collagePrensaMockData.ts +├── collagePrensaSchemas.ts +└── collagePrensaTypes.ts + +features/mechanics/auxiliar/ComprensiónAuditiva/ +├── comprensionAuditivaMockData.ts +├── comprensionAuditivaSchemas.ts +└── comprensionAuditivaTypes.ts + +features/mechanics/auxiliar/TextoEnMovimiento/ +├── textoEnMovimientoMockData.ts +├── textoEnMovimientoSchemas.ts +└── textoEnMovimientoTypes.ts +``` + +**Módulo 2 - Mecánicas:** +``` +features/mechanics/module2/ConstruccionHipotesis/ +├── causaEfectoMockData.ts +└── causaEfectoSchemas.ts + +features/mechanics/module2/LecturaInferencial/ +├── lecturaInferencialMockData.ts +└── lecturaInferencialSchemas.ts + +features/mechanics/module2/PrediccionNarrativa/ +└── prediccionNarrativaSchemas.ts + +features/mechanics/module2/PuzzleContexto/ +└── puzzleContextoSchemas.ts +``` + +### 3.2 Database Seeds - Diferencias de Estructura + +**Directorios con diferencias:** +- `dev/auth_management/` +- `dev/educational_content/` +- `dev/notifications/` +- `prod/auth/` +- `prod/auth_management/` + +### 3.3 Documentación - Archivos SOLO en DESTINO + +**En docs/90-transversal/arquitectura/especificaciones/:** +``` +ET-WS-001-websocket.md +ET-TSK-001-cron-jobs.md +ET-HLT-001-health-checks.md +ET-AUD-001-sistema-auditoria.md +``` + +### 3.4 Orchestration - Directorios SOLO en DESTINO + +``` +analisis-migracion-2025-12-18/ +analisis-homologacion-database-2025-12-18/ +analisis-backend-2025-12-18/ +analisis-frontend-validacion/ +reportes/ +``` + +--- + +## 4. PLAN DE MIGRACIÓN PROPUESTO + +### FASE 1: Frontend (PRIORIDAD ALTA) +**Acción:** Copiar 18 archivos faltantes de mecánicas + +```bash +# Ejecutar desde workspace raíz +ORIGEN="/home/isem/workspace-old/wsl-ubuntu/workspace/workspace-gamilit/gamilit/projects/gamilit" +DESTINO="/home/isem/workspace/projects/gamilit" + +# Copiar mecánicas auxiliares +rsync -av "$ORIGEN/apps/frontend/src/features/mechanics/auxiliar/" \ + "$DESTINO/apps/frontend/src/features/mechanics/auxiliar/" + +# Copiar mecánicas módulo 2 faltantes +rsync -av "$ORIGEN/apps/frontend/src/features/mechanics/module2/" \ + "$DESTINO/apps/frontend/src/features/mechanics/module2/" +``` + +### FASE 2: Database Seeds (PRIORIDAD MEDIA) +**Acción:** Sincronizar seeds (revisar cambios antes) + +```bash +# Revisar diferencias primero +diff -rq "$ORIGEN/apps/database/seeds/" "$DESTINO/apps/database/seeds/" + +# Sincronizar si es apropiado +rsync -av "$ORIGEN/apps/database/seeds/" "$DESTINO/apps/database/seeds/" +``` + +### FASE 3: Documentación (DECISIÓN REQUERIDA) +**Opciones:** + +| Opción | Descripción | Recomendación | +|--------|-------------|---------------| +| A | Mantener docs del DESTINO (más completo) | **RECOMENDADO** | +| B | Sobrescribir con ORIGEN | No recomendado | +| C | Merge manual selectivo | Si hay conflictos | + +### FASE 4: Orchestration (DECISIÓN REQUERIDA) +**Los siguientes directorios existen SOLO en DESTINO:** + +- `analisis-migracion-2025-12-18/` +- `analisis-homologacion-database-2025-12-18/` +- `analisis-backend-2025-12-18/` +- `analisis-frontend-validacion/` +- `reportes/` + +**Recomendación:** MANTENER estos análisis en el destino (son documentación de auditoría). + +--- + +## 5. VALIDACIONES POST-MIGRACIÓN + +```bash +# 1. Verificar build frontend +cd $DESTINO/apps/frontend && npm run build + +# 2. Verificar build backend +cd $DESTINO/apps/backend && npm run build + +# 3. Ejecutar tests +cd $DESTINO && npm run test + +# 4. Validar API contract +npm run validate:api-contract + +# 5. Verificar seeds cargando BD +cd $DESTINO/apps/database && ./drop-and-recreate-database.sh +``` + +--- + +## 6. RIESGOS IDENTIFICADOS + +| Riesgo | Probabilidad | Impacto | Mitigación | +|--------|--------------|---------|------------| +| Conflictos en seeds | Media | Alto | Revisar diff antes de merge | +| Pérdida de docs del destino | Baja | Medio | Backup previo | +| Build failures post-migración | Media | Alto | Tests antes de commit | + +--- + +## 7. RECOMENDACIONES FINALES + +1. **Crear backup del DESTINO antes de migrar** +2. **Priorizar FASE 1** (18 archivos frontend críticos) +3. **Mantener documentación del DESTINO** (más completa) +4. **Validar builds después de cada fase** +5. **Commit después de cada fase exitosa** + +--- + +*Generado por Requirements-Analyst | SIMCO v1.4.0*