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
+131
View File
@@ -0,0 +1,131 @@
import type { Stage } from "../types"
const lecturas = [
{
text: "La familia de Ana tiene 4 personas. Mamá, papá, Ana y su hermano Tomás. Viven en una casa con jardín. Ana juega con su perro en el jardín.",
preguntas: [
{ q: "¿Cuántas personas tiene la familia de Ana?", op: ["4", "3", "5"], correcta: "4" },
{ q: "¿Cómo se llama el hermano de Ana?", op: ["Tomás", "Juan", "Pedro"], correcta: "Tomás" },
{ q: "¿Con quién juega Ana en el jardín?", op: ["Con su perro", "Con su hermano", "Con su mamá"], correcta: "Con su perro" },
],
emoji: "👨‍👩‍👧‍👦",
},
{
text: "El abuelo de Lucía vive en el campo. Tiene muchas gallinas y un caballo. Lucía va a visitarlo los fines de semana. Le gusta darle de comer a las gallinas.",
preguntas: [
{ q: "¿Cuándo visita Lucía a su abuelo?", op: ["Los fines de semana", "Los lunes", "Todos los días"], correcta: "Los fines de semana" },
{ q: "¿Qué animales tiene el abuelo?", op: ["Gallinas y un caballo", "Vacas y ovejas", "Perros y gatos"], correcta: "Gallinas y un caballo" },
{ q: "¿Qué le gusta hacer a Lucía?", op: ["Darle de comer a las gallinas", "Pasear al caballo", "Cocinar"], correcta: "Darle de comer a las gallinas" },
],
emoji: "🐴",
},
{
text: "La mamá de Pedro cocina empanadas. Pedro ayuda a ponerles el repollo. Papá pone la mesa. Todos preparan la cena juntos.",
preguntas: [
{ q: "¿Qué cocina la mamá de Pedro?", op: ["Empanadas", "Pasta", "Ensalada"], correcta: "Empanadas" },
{ q: "¿Con qué ayuda Pedro?", op: ["Con el repollo", "Con la mesa", "Con los platos"], correcta: "Con el repollo" },
{ q: "¿Quién pone la mesa?", op: ["Papá", "Pedro", "Mamá"], correcta: "Papá" },
],
emoji: "🥟",
},
{
text: "Sofía tiene dos hermanos: Mateo y Juan. Mateo tiene 8 años y Juan tiene 5. Sofía tiene 6. Juntos juegan a la pelota en el patio.",
preguntas: [
{ q: "¿Cuántos años tiene Mateo?", op: ["8", "5", "6"], correcta: "8" },
{ q: "¿Cuántos hermanos tiene Sofía?", op: ["Dos", "Uno", "Tres"], correcta: "Dos" },
{ q: "¿Dónde juegan?", op: ["En el patio", "En la calle", "En la plaza"], correcta: "En el patio" },
],
emoji: "⚽",
},
{
text: "La familia de Martín sale de paseo. Van al río. Mamá prepara sandwiches. Papá pesca. Los niños juegan en el agua.",
preguntas: [
{ q: "¿Adónde van de paseo?", op: ["Al río", "Al parque", "A la plaza"], correcta: "Al río" },
{ q: "¿Qué prepara mamá?", op: ["Sandwiches", "Galletitas", "Fruta"], correcta: "Sandwiches" },
{ q: "¿Qué hace papá?", op: ["Pesca", "Nada", "Duerme"], correcta: "Pesca" },
],
emoji: "🏞️",
},
{
text: "Los tíos de Valentina viven en Mar del Plata. En verano Valentina va a la playa con ellos. Hace castillos de arena y junta caracoles.",
preguntas: [
{ q: "¿Dónde viven los tíos de Valentina?", op: ["Mar del Plata", "Buenos Aires", "Córdoba"], correcta: "Mar del Plata" },
{ q: "¿Qué hace Valentina en la playa?", op: ["Castillos de arena", "Anda en bicicleta", "Juega en la nieve"], correcta: "Castillos de arena" },
{ q: "¿Qué junta Valentina?", op: ["Caracoles", "Flores", "Piedras"], correcta: "Caracoles" },
],
emoji: "🏖️",
},
{
text: "La abuela de Tomás teje bufandas. Le regaló una bufanda roja a Tomás. Él la usa todos los días porque hace frío.",
preguntas: [
{ q: "¿Por qué usa Tomás la bufanda todos los días?", op: ["Porque hace frío", "Porque es roja", "Porque su abuela la tejió"], correcta: "Porque hace frío" },
{ q: "¿Qué teje la abuela de Tomás?", op: ["Bufandas", "Medias", "Pullóveres"], correcta: "Bufandas" },
{ q: "¿De qué color es la bufanda?", op: ["Roja", "Azul", "Verde"], correcta: "Roja" },
],
emoji: "🧣",
},
{
text: "La familia de Julia tiene una mascota. Es un perro llamado Lulo. Lulo es marrón y muy juguetón. Julia lo pasea todas las tardes.",
preguntas: [
{ q: "¿Cómo se llama el perro de Julia?", op: ["Lulo", "Julia", "Marrón"], correcta: "Lulo" },
{ q: "¿De qué color es Lulo?", op: ["Marrón", "Negro", "Blanco"], correcta: "Marrón" },
{ q: "¿Cuándo lo pasea Julia?", op: ["Todas las tardes", "Los fines de semana", "A la mañana"], correcta: "Todas las tardes" },
],
emoji: "🐕",
},
{
text: "Los primos de Nico vienen a cenar. Son 6 primos más Nico. La mamá prepara pasta para todos. Ponen la mesa grande.",
preguntas: [
{ q: "¿Cuántos niños comen en total?", op: ["7", "6", "8"], correcta: "7" },
{ q: "¿Qué prepara la mamá?", op: ["Pasta", "Milanesas", "Pizza"], correcta: "Pasta" },
{ q: "¿Qué mesa ponen?", op: ["La grande", "La chica", "La de la cocina"], correcta: "La grande" },
],
emoji: "🍝",
},
{
text: "El papá de Lara le lee un cuento cada noche. El cuento favorito de Lara es sobre un dragón amigable. Lara se duerme escuchando.",
preguntas: [
{ q: "¿Cuál es el cuento favorito de Lara?", op: ["Sobre un dragón amigable", "Sobre una princesa", "Sobre un caballo"], correcta: "Sobre un dragón amigable" },
{ q: "¿Cuándo le lee papá?", op: ["Cada noche", "Por la mañana", "Los domingos"], correcta: "Cada noche" },
{ q: "¿Qué hace Lara mientras escucha?", op: ["Se duerme", "Juega", "Come"], correcta: "Se duerme" },
],
emoji: "🐉",
},
]
const exercises: any[] = []
let idx = 0
for (const l of lecturas) {
for (const p of l.preguntas) {
exercises.push({
id: `fra3-familia-${idx}`,
stage: 3,
skillCode: `lectura-familia-${idx}`,
type: "lectura",
prerequisites: idx === 0 ? [] : [`fra3-familia-${idx - 1}`],
content: {
instruction: p.q,
text: l.text,
audioText: l.text,
emoji: l.emoji,
options: p.op.map((o) => ({
label: o,
correct: o === p.correcta,
id: o.toLowerCase().slice(0, 12),
})),
},
functionalContext: "Centro de interés Decroly: la familia. Comprensión lectora de textos sobre el núcleo familiar.",
errorType: "omision-letra",
})
idx++
}
}
const stage: Stage = {
id: 3,
name: 'Centro de interés: "La familia"',
description: "Textos cortos sobre la familia con preguntas de comprensión (método Decroly).",
exercises,
}
export default stage