feat: fase 2+3 — expansion masiva multi-perfil + bug fixes + hardening

Fase 2 (expansion):
- Francesca: +6 etapas matematicas (5-10), +7 etapas lectura (3-9), +2 ortografia
- Sebastian: +8 etapas matematicas (4-11), +6 lectura, +3 historia, +3 geografia, +1 banderas
- Nuevas paginas: ortografia, lectura, geografia, matematicas
- ~900 ejercicios nuevos, paridad ~500 por perfil

Fase 3 (bug fixes + hardening):
- Fix: SessionLog auto-create en grade route
- Fix: engine cache (no borrar antes de revisar)
- Fix: FSRS cards actualizadas en flujo curriculum
- Fix: responseMs real (no hardcodeado 3000)
- Fix: options dentro de content en sebastian etapa-1
- Fix: trazo-letra consonantes crash
- Security: execFileSync anti-command-injection en TTS
- DB: 18 cascade deletes + indices + unique constraints
- APIs debug: reset, state, seed
- Docker: espeak-ng + prisma + user/group
- E2E: 23 tests nuevos (curriculum-flow.spec.ts)
- Code review: tailwind colors, middleware dev mode, eslint config

Verificado: tsc exit 0, build exit 0, 61 E2E tests pass
This commit is contained in:
renato97
2026-07-25 22:07:28 -03:00
parent 25614704dc
commit a3360ec6e4
67 changed files with 5294 additions and 213 deletions
+80
View File
@@ -0,0 +1,80 @@
import type { Stage } from "../types"
type Prob = {
question: string
answer: number
hint: string
emoji: string
distractors: number[]
}
const problems: Prob[] = [
{ question: "Compraste 3 caramelos a $2 cada uno. ¿Cuánto gastaste?", answer: 6, hint: "3 × 2", emoji: "🍬", distractors: [5, 8] },
{ question: "Tenés $20 y querés comprar 4 juguetes iguales. ¿Cuánto cuesta cada uno?", answer: 5, hint: "20 ÷ 4", emoji: "🧸", distractors: [4, 6] },
{ question: "Un libro cuesta $12 y una lapicera cuesta $3. ¿Cuánto cuestan los dos?", answer: 15, hint: "12 + 3", emoji: "📚", distractors: [14, 16] },
{ question: "Tenés $15 y gastás $7 en un helado. ¿Cuánto te queda?", answer: 8, hint: "15 - 7", emoji: "🍦", distractors: [7, 9] },
{ question: "Un cuaderno cuesta $8. Compraste 3 cuadernos. ¿Cuánto pagaste?", answer: 24, hint: "8 × 3", emoji: "📓", distractors: [22, 27] },
{ question: "Una mesa mide 120 cm de alto. Una silla mide 45 cm. ¿Cuánto miden juntos?", answer: 165, hint: "120 + 45", emoji: "📏", distractors: [155, 175] },
{ question: "Una botella tiene 1 litro de agua. Serviste 4 vasos de 250 ml cada uno. ¿Cuánto serviste?", answer: 1000, hint: "4 × 250", emoji: "💧", distractors: [750, 1250] },
{ question: "Compraste 2 kilos de manzanas a $5 el kilo. ¿Cuánto pagaste?", answer: 10, hint: "2 × 5", emoji: "🍎", distractors: [8, 12] },
{ question: "Un tren tiene 3 vagones. En cada vagón viajan 6 personas. ¿Cuántas personas viajan?", answer: 18, hint: "3 × 6", emoji: "🚂", distractors: [16, 20] },
{ question: "Una pizza tiene 8 porciones. Comiste 3. ¿Cuántas quedan?", answer: 5, hint: "8 - 3", emoji: "🍕", distractors: [4, 6] },
{ question: "Gastaste $50 en el supermercado con $100. ¿Cuánto vuelto recibís?", answer: 50, hint: "100 - 50", emoji: "🛒", distractors: [40, 60] },
{ question: "Un lápiz cuesta $3 y una goma cuesta $2. Compraste 2 lápices y 1 goma. ¿Cuánto pagaste?", answer: 8, hint: "2 × 3 + 2", emoji: "✏️", distractors: [7, 10] },
{ question: "Tu clase tiene 24 alumnos. Los dividís en 4 equipos iguales. ¿Cuántos alumnos por equipo?", answer: 6, hint: "24 ÷ 4", emoji: "👥", distractors: [5, 7] },
{ question: "Una receta usa 250 gramos de harina. Hacés 4 recetas. ¿Cuántos gramos necesitás?", answer: 1000, hint: "250 × 4", emoji: "🧁", distractors: [800, 1200] },
{ question: "Corriste 3 vueltas a la plaza. Cada vuelta es 400 metros. ¿Cuánto corriste?", answer: 1200, hint: "3 × 400", emoji: "🏃", distractors: [1000, 1400] },
{ question: "Una caja tiene 12 huevos. Usaste 4 para cocinar. ¿Cuántos quedan?", answer: 8, hint: "12 - 4", emoji: "🥚", distractors: [7, 9] },
{ question: "Compraste 5 paquetes de figuritas a $3 cada uno. ¿Cuánto pagaste?", answer: 15, hint: "5 × 3", emoji: "🃏", distractors: [14, 18] },
{ question: "Son las 10:00. Pasan 3 horas. ¿Qué hora es?", answer: 13, hint: "10 + 3", emoji: "⏰", distractors: [12, 14] },
{ question: "Una regla mide 30 cm. Tenés 4 reglas iguales puestas en fila. ¿Cuánto miden?", answer: 120, hint: "4 × 30", emoji: "📏", distractors: [110, 130] },
{ question: "Compraste 2 docenas de facturas. ¿Cuántas facturas son?", answer: 24, hint: "2 × 12", emoji: "🥐", distractors: [22, 26] },
{ question: "Tenés $100. Comprás un juguete de $35 y otro de $28. ¿Cuánto gastaste?", answer: 63, hint: "35 + 28", emoji: "🧸", distractors: [60, 65] },
{ question: "En una pecera hay 15 peces. Compraste 8 más. ¿Cuántos peces hay?", answer: 23, hint: "15 + 8", emoji: "🐠", distractors: [22, 24] },
{ question: "Tenés 24 lápices y los repartís entre 6 amigos. ¿Cuántos lápices recibe cada uno?", answer: 4, hint: "24 ÷ 6", emoji: "✏️", distractors: [3, 5] },
{ question: "Gastaste $12 en la mañana y $18 en la tarde. ¿Cuánto gastaste en total?", answer: 30, hint: "12 + 18", emoji: "💰", distractors: [28, 32] },
{ question: "Un camión lleva 45 cajas. Descargaron 18. ¿Cuántas cajas quedan?", answer: 27, hint: "45 - 18", emoji: "🚚", distractors: [26, 28] },
{ question: "Una hormiga camina 5 cm por minuto. ¿Cuánto camina en 7 minutos?", answer: 35, hint: "5 × 7", emoji: "🐜", distractors: [33, 40] },
{ question: "Tenés $50. Compras una remera de $22 y una gorra de $15. ¿Cuánto vuelto recibís?", answer: 13, hint: "50 - 22 - 15", emoji: "👕", distractors: [12, 15] },
{ question: "En un álbum caben 9 figuritas por página. Tenés 6 páginas llenas. ¿Cuántas figuritas tenés?", answer: 54, hint: "9 × 6", emoji: "🖼️", distractors: [52, 56] },
{ question: "Un paquete de galletitas trae 18. Comiste 5. ¿Cuántas quedan?", answer: 13, hint: "18 - 5", emoji: "🍪", distractors: [12, 14] },
{ question: "Ahorraste $8 por semana durante 6 semanas. ¿Cuánto ahorraste?", answer: 48, hint: "8 × 6", emoji: "🏦", distractors: [46, 50] },
]
const exercises: any[] = []
for (let i = 0; i < problems.length; i++) {
const p = problems[i]
const ds = p.distractors.slice()
if (ds.length < 2) {
ds.push(p.answer + 2)
}
exercises.push({
id: `fra10-prob-${i}`,
stage: 10,
skillCode: `prob-dinero-${i}`,
type: "problema",
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
content: {
instruction: "Resolvé el problema:",
text: p.question,
audioText: p.question,
emoji: p.emoji,
options: [
{ label: `$${p.answer}`, correct: true, id: `c-${i}` },
{ label: `$${ds[0]}`, correct: false, id: `w1-${i}` },
{ label: `$${ds[1]}`, correct: false, id: `w2-${i}` },
].sort(() => Math.random() - 0.5),
},
functionalContext: `Resolver problemas con dinero y medidas (vida práctica Montessori): ${p.hint}.`,
errorType: "discriminacion-auditiva",
})
}
const stage: Stage = {
id: 10,
name: "Problemas con dinero y medidas",
description: "Aplicar operaciones en contexto de la vida real con dinero ($), medidas (cm, kg, L) y tiempo.",
exercises,
}
export default stage