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:
@@ -1,18 +1,18 @@
|
||||
import type { Stage } from '../types'
|
||||
|
||||
const ejercicios = [
|
||||
const ejercicios: any[] = [
|
||||
{
|
||||
id: 'seb-suma-1',
|
||||
content: {
|
||||
instruction: 'Sumá: 15 + 27',
|
||||
audioText: '15 más 27 es',
|
||||
emoji: '➕',
|
||||
options: [
|
||||
{ label: '42', correct: true, id: '42' },
|
||||
{ label: '40', correct: false, id: '40' },
|
||||
{ label: '45', correct: false, id: '45' },
|
||||
],
|
||||
},
|
||||
options: [
|
||||
{ label: '42', correct: true, id: '42' },
|
||||
{ label: '40', correct: false, id: '40' },
|
||||
{ label: '45', correct: false, id: '45' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'seb-suma-2',
|
||||
@@ -20,12 +20,12 @@ const ejercicios = [
|
||||
instruction: 'Sumá: 34 + 56',
|
||||
audioText: '34 más 56 es',
|
||||
emoji: '➕',
|
||||
options: [
|
||||
{ label: '90', correct: true, id: '90' },
|
||||
{ label: '80', correct: false, id: '80' },
|
||||
{ label: '100', correct: false, id: '100' },
|
||||
],
|
||||
},
|
||||
options: [
|
||||
{ label: '90', correct: true, id: '90' },
|
||||
{ label: '80', correct: false, id: '80' },
|
||||
{ label: '100', correct: false, id: '100' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'seb-suma-3',
|
||||
@@ -33,12 +33,12 @@ const ejercicios = [
|
||||
instruction: 'Sumá: 78 + 22',
|
||||
audioText: '78 más 22 es',
|
||||
emoji: '➕',
|
||||
options: [
|
||||
{ label: '100', correct: true, id: '100' },
|
||||
{ label: '99', correct: false, id: '99' },
|
||||
{ label: '101', correct: false, id: '101' },
|
||||
],
|
||||
},
|
||||
options: [
|
||||
{ label: '100', correct: true, id: '100' },
|
||||
{ label: '99', correct: false, id: '99' },
|
||||
{ label: '101', correct: false, id: '101' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'seb-suma-4',
|
||||
@@ -46,12 +46,12 @@ const ejercicios = [
|
||||
instruction: 'Sumá: 45 + 55',
|
||||
audioText: '45 más 55 es',
|
||||
emoji: '➕',
|
||||
options: [
|
||||
{ label: '100', correct: true, id: '100' },
|
||||
{ label: '95', correct: false, id: '95' },
|
||||
{ label: '110', correct: false, id: '110' },
|
||||
],
|
||||
},
|
||||
options: [
|
||||
{ label: '100', correct: true, id: '100' },
|
||||
{ label: '95', correct: false, id: '95' },
|
||||
{ label: '110', correct: false, id: '110' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'seb-suma-5',
|
||||
@@ -59,12 +59,12 @@ const ejercicios = [
|
||||
instruction: 'Sumá: 123 + 456',
|
||||
audioText: '123 más 456 es',
|
||||
emoji: '➕',
|
||||
options: [
|
||||
{ label: '579', correct: true, id: '579' },
|
||||
{ label: '569', correct: false, id: '569' },
|
||||
{ label: '580', correct: false, id: '580' },
|
||||
],
|
||||
},
|
||||
options: [
|
||||
{ label: '579', correct: true, id: '579' },
|
||||
{ label: '569', correct: false, id: '569' },
|
||||
{ label: '580', correct: false, id: '580' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -72,7 +72,8 @@ const exercises: any[] = []
|
||||
for (let i = 0; i < ejercicios.length; i++) {
|
||||
const e = ejercicios[i]
|
||||
exercises.push({
|
||||
...e,
|
||||
id: e.id,
|
||||
content: e.content,
|
||||
stage: 1,
|
||||
skillCode: `seb-suma-${i + 1}`,
|
||||
type: 'eleccion',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const problems = [
|
||||
{ q: "Compré 3 lápices a $15 cada uno y pagué con $100. ¿Cuánto vuelto recibí?", correct: "$55", wrong1: "$45", wrong2: "$85", emoji: "✏️" },
|
||||
{ q: "Tenía 45 figuritas. Regalé 18. Mi hermano me dio 12 más. ¿Cuántas tengo ahora?", correct: "39", wrong1: "35", wrong2: "42", emoji: "🃏" },
|
||||
{ q: "En una granja hay 24 vacas y el doble de gallinas. ¿Cuántos animales hay?", correct: "72", wrong1: "48", wrong2: "96", emoji: "🐄" },
|
||||
{ q: "Una caja tiene 12 bombones. Compré 5 cajas. Repartí 18 bombones en la fiesta. ¿Cuántos me quedan?", correct: "42", wrong1: "38", wrong2: "48", emoji: "🍫" },
|
||||
{ q: "Lucas ahorra $25 por semana. ¿Cuánto ahorra en 4 semanas? Si gasta $30 en un juguete, ¿cuánto le queda?", correct: "$70", wrong1: "$60", wrong2: "$80", emoji: "🏦" },
|
||||
{ q: "En una biblioteca hay 5 estantes con 30 libros cada uno. Se prestaron 42 libros. ¿Cuántos quedan?", correct: "108", wrong1: "98", wrong2: "112", emoji: "📚" },
|
||||
{ q: "Un autobús lleva 45 pasajeros. En la primera parada bajan 12 y suben 8. ¿Cuántos pasajeros hay ahora?", correct: "41", wrong1: "39", wrong2: "43", emoji: "🚌" },
|
||||
{ q: "María tiene 8 años. Su mamá tiene el triple de años que ella. ¿Cuántos años tiene la mamá?", correct: "24", wrong1: "22", wrong2: "26", emoji: "👩👧" },
|
||||
{ q: "Había 36 galletitas en un frasco. Comí 9. Después mamá puso 15 más. ¿Cuántas hay ahora?", correct: "42", wrong1: "38", wrong2: "45", emoji: "🍪" },
|
||||
{ q: "Un equipo anotó 12 puntos en el primer tiempo y 18 en el segundo. El otro equipo anotó 21 en total. ¿Quién ganó?", correct: "El primero", wrong1: "El segundo", wrong2: "EMPATE", emoji: "🏀" },
|
||||
{ q: "Compré 2 paquetes de figuritas a $8 cada uno y 3 caramelos a $2 cada uno. ¿Cuánto gasté?", correct: "$22", wrong1: "$20", wrong2: "$24", emoji: "🛍️" },
|
||||
{ q: "Un rectángulo mide 8 cm de largo y 5 cm de ancho. ¿Cuál es su perímetro?", correct: "26 cm", wrong1: "24 cm", wrong2: "28 cm", emoji: "📐" },
|
||||
{ q: "En un kiosco vendieron 35 helados el sábado y 28 el domingo. Cada helado cuesta $4. ¿Cuánto recaudaron?", correct: "$252", wrong1: "$240", wrong2: "$260", emoji: "🍦" },
|
||||
{ q: "Tres amigos juntan su dinero: Juan $50, Ana $75 y Pedro $25. Quieren comprar un juego de $180. ¿Cuánto les falta?", correct: "$30", wrong1: "$20", wrong2: "$40", emoji: "🎮" },
|
||||
{ q: "Un paquete de 6 facturas cuesta $18. ¿Cuánto cuesta cada factura? ¿Cuánto cuestan 10 facturas?", correct: "$30", wrong1: "$28", wrong2: "$32", emoji: "🥐" },
|
||||
{ q: "Un avión viaja a 800 km/h. ¿Cuántos kilómetros recorre en 3 horas?", correct: "2400 km", wrong1: "2200 km", wrong2: "2600 km", emoji: "✈️" },
|
||||
{ q: "En un cine hay 20 filas de 15 butacas cada una. Se vendieron 240 entradas. ¿Cuántas butacas quedaron libres?", correct: "60", wrong1: "50", wrong2: "70", emoji: "🎬" },
|
||||
{ q: "Valentina tiene $500. Gasta $180 en un libro y $95 en una remera. ¿Cuánto le queda?", correct: "$225", wrong1: "$215", wrong2: "$235", emoji: "👚" },
|
||||
{ q: "Un tanque tiene 250 litros de agua. Se usaron 75 litros para regar y 120 para limpiar. ¿Cuántos litros quedan?", correct: "55 L", wrong1: "45 L", wrong2: "65 L", emoji: "💧" },
|
||||
{ q: "En una carrera, el primero llegó en 45 minutos. El segundo llegó 12 minutos después. ¿En qué tiempo llegó el segundo?", correct: "57 min", wrong1: "55 min", wrong2: "59 min", emoji: "🏃" },
|
||||
{ q: "Un productor tiene 350 manzanas. Vende 125 el lunes y 98 el martes. ¿Cuántas le quedan?", correct: "127", wrong1: "117", wrong2: "137", emoji: "🍎" },
|
||||
{ q: "Hay 4 bolsas con 15 pelotitas cada una. Se caen 8 pelotitas. ¿Cuántas quedan?", correct: "52", wrong1: "48", wrong2: "55", emoji: "⚾" },
|
||||
{ q: "Una receta usa 2 huevos. Si hacés 5 recetas, ¿cuántos huevos necesitás? Si tenés 15, ¿cuántos sobran?", correct: "5", wrong1: "3", wrong2: "7", emoji: "🥚" },
|
||||
{ q: "Un jardín tiene 28 flores blancas y 14 rojas. La mitad de las blancas se marchitaron. ¿Cuántas flores quedan?", correct: "28", wrong1: "21", wrong2: "35", emoji: "🌸" },
|
||||
{ q: "Ahorré $8 por mes durante 9 meses. Gasté la cuarta parte en un regalo. ¿Cuánto gasté?", correct: "$18", wrong1: "$16", wrong2: "$20", emoji: "🎁" },
|
||||
{ q: "En un estacionamiento hay 4 filas de 12 autos cada una. Salen 15 autos. ¿Cuántos quedan?", correct: "33", wrong1: "30", wrong2: "36", emoji: "🚗" },
|
||||
{ q: "Un álbum tiene 200 espacios. Tengo 85 figuritas. Mi amigo me da 30 más. ¿Cuántas me faltan?", correct: "85", wrong1: "75", wrong2: "95", emoji: "📖" },
|
||||
{ q: "Un saco de papas pesa 25 kg. Si vendo 3 sacos y sobran 12 kg para mi casa, ¿cuántos kg vendí?", correct: "63 kg", wrong1: "58 kg", wrong2: "68 kg", emoji: "🥔" },
|
||||
{ q: "Hay 120 libros en una biblioteca. El 50% son de cuentos. ¿Cuántos cuentos hay?", correct: "60", wrong1: "50", wrong2: "70", emoji: "📚" },
|
||||
{ q: "Un termómetro marca 15°C. Sube 8°C y luego baja 12°C. ¿Cuánto marca ahora?", correct: "11°C", wrong1: "10°C", wrong2: "12°C", emoji: "🌡️" },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < problems.length; i++) {
|
||||
const e = problems[i]
|
||||
exercises.push({
|
||||
id: `seb10-prob-${i}`,
|
||||
stage: 10,
|
||||
skillCode: `problema-multistep-${i}`,
|
||||
type: "problema",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: "Resolvé el problema:",
|
||||
text: e.q,
|
||||
audioText: e.q,
|
||||
emoji: e.emoji,
|
||||
options: [
|
||||
{ label: e.correct, correct: true, id: `c-${i}` },
|
||||
{ label: e.wrong1, correct: false, id: `w1-${i}` },
|
||||
{ label: e.wrong2, correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Resolver problemas de varios pasos que requieren 2-3 operaciones combinadas.",
|
||||
errorType: "discriminacion-auditiva",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 10,
|
||||
name: "Problemas de varios pasos",
|
||||
description: "Resolver problemas que requieren 2-3 operaciones combinadas en contexto cotidiano.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const exercises: any[] = []
|
||||
|
||||
// Perímetro de cuadrado y rectángulo
|
||||
const perims = [
|
||||
{ fig: "cuadrado", lado: 5, peri: 20 },
|
||||
{ fig: "cuadrado", lado: 8, peri: 32 },
|
||||
{ fig: "cuadrado", lado: 12, peri: 48 },
|
||||
{ fig: "rectángulo", a: 6, b: 4, peri: 20 },
|
||||
{ fig: "rectángulo", a: 8, b: 3, peri: 22 },
|
||||
{ fig: "rectángulo", a: 10, b: 5, peri: 30 },
|
||||
{ fig: "rectángulo", a: 7, b: 7, peri: 28 },
|
||||
{ fig: "cuadrado", lado: 15, peri: 60 },
|
||||
{ fig: "rectángulo", a: 9, b: 6, peri: 30 },
|
||||
{ fig: "cuadrado", lado: 20, peri: 80 },
|
||||
]
|
||||
|
||||
let idx = 0
|
||||
for (const p of perims) {
|
||||
const q = p.fig === "cuadrado"
|
||||
? `Un cuadrado tiene lados de ${p.lado} cm. ¿Cuál es su perímetro?`
|
||||
: `Un rectángulo mide ${p.a} cm × ${p.b} cm. ¿Cuál es su perímetro?`
|
||||
exercises.push({
|
||||
id: `seb11-geo-perim-${idx}`,
|
||||
stage: 11,
|
||||
skillCode: `perimetro-${idx}`,
|
||||
type: "eleccion",
|
||||
prerequisites: idx === 0 ? [] : [exercises[idx - 1].id],
|
||||
content: {
|
||||
instruction: q,
|
||||
text: q,
|
||||
audioText: q,
|
||||
emoji: "📐",
|
||||
options: [
|
||||
{ label: `${p.peri} cm`, correct: true, id: `c-${idx}` },
|
||||
{ label: `${p.peri + 5} cm`, correct: false, id: `w1-${idx}` },
|
||||
{ label: `${p.peri - 5} cm`, correct: false, id: `w2-${idx}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Calcular perímetros de cuadrados y rectángulos en problemas geométricos.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
idx++
|
||||
}
|
||||
|
||||
// Área básica
|
||||
const areas = [
|
||||
{ fig: "cuadrado", lado: 4, area: 16 },
|
||||
{ fig: "rectángulo", a: 6, b: 3, area: 18 },
|
||||
{ fig: "cuadrado", lado: 7, area: 49 },
|
||||
{ fig: "rectángulo", a: 8, b: 5, area: 40 },
|
||||
{ fig: "cuadrado", lado: 10, area: 100 },
|
||||
{ fig: "rectángulo", a: 9, b: 4, area: 36 },
|
||||
{ fig: "cuadrado", lado: 6, area: 36 },
|
||||
{ fig: "rectángulo", a: 12, b: 5, area: 60 },
|
||||
{ fig: "cuadrado", lado: 9, area: 81 },
|
||||
{ fig: "rectángulo", a: 7, b: 5, area: 35 },
|
||||
]
|
||||
|
||||
for (const a of areas) {
|
||||
const q = a.fig === "cuadrado"
|
||||
? `Un cuadrado tiene lados de ${a.lado} cm. ¿Cuál es su área?`
|
||||
: `Un rectángulo mide ${a.a} cm × ${a.b} cm. ¿Cuál es su área?`
|
||||
exercises.push({
|
||||
id: `seb11-geo-area-${idx}`,
|
||||
stage: 11,
|
||||
skillCode: `area-${idx}`,
|
||||
type: "eleccion",
|
||||
prerequisites: [exercises[idx - 1].id],
|
||||
content: {
|
||||
instruction: q,
|
||||
text: q,
|
||||
audioText: q,
|
||||
emoji: "📏",
|
||||
options: [
|
||||
{ label: `${a.area} cm²`, correct: true, id: `c-${idx}` },
|
||||
{ label: `${a.area + 5} cm²`, correct: false, id: `w1-${idx}` },
|
||||
{ label: `${a.area - 5} cm²`, correct: false, id: `w2-${idx}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Calcular áreas de cuadrados y rectángulos en problemas geométricos básicos.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
idx++
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 11,
|
||||
name: "Geometría básica",
|
||||
description: "Calcular perímetros de cuadrados y rectángulos, y áreas básicas.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const restas = [
|
||||
{ a: 300, b: 100 }, { a: 150, b: 25 }, { a: 200, b: 75 },
|
||||
{ a: 300, b: 147 }, { a: 502, b: 268 }, { a: 400, b: 237 },
|
||||
{ a: 600, b: 345 }, { a: 750, b: 288 }, { a: 803, b: 417 },
|
||||
{ a: 901, b: 456 }, { a: 500, b: 333 }, { a: 624, b: 189 },
|
||||
{ a: 700, b: 456 }, { a: 850, b: 372 }, { a: 430, b: 256 },
|
||||
{ a: 555, b: 278 }, { a: 671, b: 394 }, { a: 723, b: 467 },
|
||||
{ a: 810, b: 543 }, { a: 345, b: 168 }, { a: 900, b: 678 },
|
||||
{ a: 512, b: 235 }, { a: 450, b: 189 }, { a: 632, b: 254 },
|
||||
{ a: 740, b: 386 },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < restas.length; i++) {
|
||||
const r = restas[i]
|
||||
const res = r.a - r.b
|
||||
exercises.push({
|
||||
id: `seb-resta-${r.a}-${r.b}`,
|
||||
stage: 4,
|
||||
skillCode: `resta-${r.a}-${r.b}`,
|
||||
type: "eleccion",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: `Restá: ${r.a} - ${r.b}`,
|
||||
audioText: `${r.a} menos ${r.b}`,
|
||||
emoji: "➖",
|
||||
options: [
|
||||
{ label: String(res), correct: true, id: String(res) },
|
||||
{ label: String(res + 10), correct: false, id: `w1-${i}` },
|
||||
{ label: String(res - 10), correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Restar números de 3 dígitos con préstamo usando método Montessori (sellos con intercambio).",
|
||||
errorType: "discriminacion-auditiva",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 4,
|
||||
name: "Restas con préstamo (3 dígitos)",
|
||||
description: "Resolver restas con llevadas de números de hasta 3 dígitos con método Montessori.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const mults = [
|
||||
{ a: 12, b: 34 }, { a: 23, b: 45 }, { a: 34, b: 21 }, { a: 15, b: 32 },
|
||||
{ a: 42, b: 23 }, { a: 21, b: 43 }, { a: 31, b: 22 }, { a: 45, b: 12 },
|
||||
{ a: 56, b: 11 }, { a: 24, b: 32 }, { a: 33, b: 23 }, { a: 62, b: 14 },
|
||||
{ a: 17, b: 25 }, { a: 28, b: 31 }, { a: 73, b: 12 }, { a: 52, b: 21 },
|
||||
{ a: 67, b: 12 }, { a: 44, b: 23 }, { a: 91, b: 11 }, { a: 54, b: 18 },
|
||||
{ a: 26, b: 32 }, { a: 38, b: 21 }, { a: 47, b: 16 }, { a: 63, b: 14 },
|
||||
{ a: 81, b: 12 },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < mults.length; i++) {
|
||||
const m = mults[i]
|
||||
const res = m.a * m.b
|
||||
exercises.push({
|
||||
id: `seb5-mult-${m.a}x${m.b}`,
|
||||
stage: 5,
|
||||
skillCode: `mult-${m.a}x${m.b}`,
|
||||
type: "eleccion",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: `Multiplicá: ${m.a} × ${m.b}`,
|
||||
audioText: `${m.a} por ${m.b}`,
|
||||
emoji: "✖️",
|
||||
options: [
|
||||
{ label: String(res), correct: true, id: String(res) },
|
||||
{ label: String(res + m.a), correct: false, id: `w1-${i}` },
|
||||
{ label: String(res - m.b), correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Multiplicar números de 2 dígitos con método Montessori (cuadrícula, geometría).",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 5,
|
||||
name: "Multiplicación de 2 dígitos × 2 dígitos",
|
||||
description: "Resolver multiplicaciones de 2 dígitos por 2 dígitos con descomposición posicional.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const divisions = [
|
||||
{ a: 144, b: 6 }, { a: 156, b: 4 }, { a: 168, b: 7 }, { a: 192, b: 8 },
|
||||
{ a: 225, b: 5 }, { a: 256, b: 8 }, { a: 289, b: 9 }, { a: 324, b: 6 },
|
||||
{ a: 360, b: 5 }, { a: 378, b: 7 }, { a: 392, b: 8 }, { a: 425, b: 5 },
|
||||
{ a: 441, b: 7 }, { a: 486, b: 6 }, { a: 504, b: 8 }, { a: 540, b: 9 },
|
||||
{ a: 576, b: 8 }, { a: 625, b: 5 }, { a: 648, b: 8 }, { a: 672, b: 7 },
|
||||
{ a: 729, b: 9 }, { a: 784, b: 8 }, { a: 810, b: 9 }, { a: 864, b: 8 },
|
||||
{ a: 900, b: 6 },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < divisions.length; i++) {
|
||||
const d = divisions[i]
|
||||
const res = d.a / d.b
|
||||
exercises.push({
|
||||
id: `seb6-div-${d.a}-${d.b}`,
|
||||
stage: 6,
|
||||
skillCode: `div-${d.a}-${d.b}`,
|
||||
type: "eleccion",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: `Dividí: ${d.a} ÷ ${d.b}`,
|
||||
audioText: `${d.a} dividido ${d.b}`,
|
||||
emoji: "➗",
|
||||
options: [
|
||||
{ label: String(res), correct: true, id: String(res) },
|
||||
{ label: String(res + 1), correct: false, id: `w1-${i}` },
|
||||
{ label: String(res - 1), correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Dividir números de 3 dígitos por 1 dígito con método Montessori (tubos de ensayo).",
|
||||
errorType: "discriminacion-auditiva",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 6,
|
||||
name: "División larga (1 divisor)",
|
||||
description: "Resolver divisiones de números grandes por 1 dígito, incluyendo restos.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
// Equivalences: 1/2 = 2/4 = 3/6, 1/3 = 2/6, 1/4 = 2/8
|
||||
const equivalencias: { a: string; b: string; result: boolean }[] = [
|
||||
{ a: "1/2", b: "2/4", result: true },
|
||||
{ a: "1/2", b: "3/6", result: true },
|
||||
{ a: "1/3", b: "2/6", result: true },
|
||||
{ a: "1/4", b: "2/8", result: true },
|
||||
{ a: "3/4", b: "6/8", result: true },
|
||||
{ a: "2/3", b: "4/6", result: true },
|
||||
{ a: "1/2", b: "2/3", result: false },
|
||||
{ a: "1/3", b: "1/4", result: false },
|
||||
{ a: "1/4", b: "1/2", result: false },
|
||||
{ a: "3/4", b: "1/2", result: false },
|
||||
{ a: "2/3", b: "3/4", result: false },
|
||||
{ a: "1/2", b: "1/3", result: false },
|
||||
{ a: "2/4", b: "3/6", result: true },
|
||||
{ a: "1/3", b: "3/9", result: true },
|
||||
{ a: "1/4", b: "3/12", result: true },
|
||||
]
|
||||
|
||||
// Sumas y restas del mismo denominador
|
||||
const opFracs: { numA: number; den: number; numB: number; op: "+" | "-"; resultado: string }[] = [
|
||||
{ numA: 1, den: 4, numB: 2, op: "+", resultado: "3/4" },
|
||||
{ numA: 2, den: 5, numB: 1, op: "+", resultado: "3/5" },
|
||||
{ numA: 3, den: 8, numB: 2, op: "+", resultado: "5/8" },
|
||||
{ numA: 1, den: 3, numB: 1, op: "+", resultado: "2/3" },
|
||||
{ numA: 2, den: 7, numB: 3, op: "+", resultado: "5/7" },
|
||||
{ numA: 3, den: 4, numB: 1, op: "-", resultado: "2/4" },
|
||||
{ numA: 4, den: 5, numB: 1, op: "-", resultado: "3/5" },
|
||||
{ numA: 5, den: 8, numB: 2, op: "-", resultado: "3/8" },
|
||||
{ numA: 2, den: 3, numB: 1, op: "-", resultado: "1/3" },
|
||||
{ numA: 6, den: 7, numB: 2, op: "-", resultado: "4/7" },
|
||||
{ numA: 1, den: 6, numB: 2, op: "+", resultado: "3/6" },
|
||||
{ numA: 3, den: 10, numB: 4, op: "+", resultado: "7/10" },
|
||||
{ numA: 7, den: 9, numB: 2, op: "-", resultado: "5/9" },
|
||||
{ numA: 5, den: 6, numB: 1, op: "-", resultado: "4/6" },
|
||||
{ numA: 2, den: 9, numB: 5, op: "+", resultado: "7/9" },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
let idx = 0
|
||||
|
||||
// Part 1: equivalence recognition (conciencia)
|
||||
for (const eq of equivalencias) {
|
||||
exercises.push({
|
||||
id: `seb7-frac-eq-${idx}`,
|
||||
stage: 7,
|
||||
skillCode: `frac-eq-${idx}`,
|
||||
type: "conciencia",
|
||||
prerequisites: idx === 0 ? [] : [exercises[idx - 1].id],
|
||||
content: {
|
||||
instruction: eq.result
|
||||
? `¿${eq.a} es equivalente a ${eq.b}?`
|
||||
: `¿${eq.a} es equivalente a ${eq.b}?`,
|
||||
audioText: `${eq.a} es equivalente a ${eq.b}?`,
|
||||
emoji: "🥧",
|
||||
options: [
|
||||
{ label: "Verdadero", correct: eq.result, id: `true-${idx}` },
|
||||
{ label: "Falso", correct: !eq.result, id: `false-${idx}` },
|
||||
],
|
||||
},
|
||||
functionalContext: "Reconocer equivalencias entre fracciones con círculos Montessori.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
idx++
|
||||
}
|
||||
|
||||
// Part 2: addition/subtraction (eleccion)
|
||||
for (const op of opFracs) {
|
||||
const wrong1 = `${op.numA + op.numB + 1}/${op.den}`
|
||||
const wrong2 = `${Math.abs(op.numA - op.numB + op.den)}/${op.den}`
|
||||
exercises.push({
|
||||
id: `seb7-frac-op-${idx}`,
|
||||
stage: 7,
|
||||
skillCode: `frac-op-${idx}`,
|
||||
type: "eleccion",
|
||||
prerequisites: [exercises[idx - 1].id],
|
||||
content: {
|
||||
instruction: `Resolvé: ${op.numA}/${op.den} ${op.op} ${op.numB}/${op.den}`,
|
||||
audioText: `${op.numA} ${op.op === "+" ? "más" : "menos"} ${op.numB}, sobre ${op.den}`,
|
||||
emoji: "🧮",
|
||||
options: [
|
||||
{ label: op.resultado, correct: true, id: `c-${idx}` },
|
||||
{ label: wrong1, correct: false, id: `w1-${idx}` },
|
||||
{ label: wrong2, correct: false, id: `w2-${idx}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Sumar y restar fracciones con el mismo denominador usando círculos Montessori.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
idx++
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 7,
|
||||
name: "Fracciones: equivalencias y operaciones",
|
||||
description: "Reconocer fracciones equivalentes y sumar/restar fracciones con el mismo denominador.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const decimalExercises = [
|
||||
// Read/identify decimals
|
||||
{ q: "¿0.5 es lo mismo que?", correct: "0.50", wrong1: "0.05", wrong2: "5.0", emoji: "🔢" },
|
||||
{ q: "¿Cuánto es 1.25 + 0.25?", correct: "1.50", wrong1: "1.00", wrong2: "1.75", emoji: "➕" },
|
||||
{ q: "¿Cuánto es 2.5 - 1.3?", correct: "1.2", wrong1: "1.0", wrong2: "1.8", emoji: "➖" },
|
||||
{ q: "¿0.75 es igual a qué fracción?", correct: "3/4", wrong1: "1/4", wrong2: "1/2", emoji: "🥧" },
|
||||
{ q: "¿0.5 es igual a qué fracción?", correct: "1/2", wrong1: "1/3", wrong2: "1/4", emoji: "🥧" },
|
||||
{ q: "¿Cuál número es mayor: 1.5 o 1.25?", correct: "1.5", wrong1: "1.25", wrong2: "Son iguales", emoji: "📏" },
|
||||
{ q: "¿Cuánto es 3.2 + 1.8?", correct: "5.0", wrong1: "4.0", wrong2: "5.5", emoji: "➕" },
|
||||
{ q: "¿Cuánto es 4.75 - 2.25?", correct: "2.50", wrong1: "2.00", wrong2: "2.75", emoji: "➖" },
|
||||
{ q: "¿Cuántos decimales tiene 3.14?", correct: "2", wrong1: "1", wrong2: "3", emoji: "🔢" },
|
||||
{ q: "¿0.25 es igual a qué fracción?", correct: "1/4", wrong1: "1/2", wrong2: "3/4", emoji: "🥧" },
|
||||
{ q: "¿Cuál es mayor: 0.8 o 0.75?", correct: "0.8", wrong1: "0.75", wrong2: "Son iguales", emoji: "📏" },
|
||||
{ q: "¿Cuánto es 1.2 × 2?", correct: "2.4", wrong1: "2.2", wrong2: "2.8", emoji: "✖️" },
|
||||
{ q: "¿Cuánto es 3.0 ÷ 2?", correct: "1.5", wrong1: "1.0", wrong2: "2.0", emoji: "➗" },
|
||||
{ q: "¿0.20 es lo mismo que?", correct: "0.2", wrong1: "0.02", wrong2: "2.0", emoji: "🔢" },
|
||||
{ q: "Ordená de menor a mayor: 1.2, 1.02, 1.20", correct: "1.02, 1.2, 1.20", wrong1: "1.2, 1.02, 1.20", wrong2: "1.20, 1.2, 1.02", emoji: "📊" },
|
||||
{ q: "¿0.1 + 0.2 = ?", correct: "0.3", wrong1: "0.12", wrong2: "0.03", emoji: "➕" },
|
||||
{ q: "¿0.5 × 10 = ?", correct: "5", wrong1: "0.05", wrong2: "50", emoji: "✖️" },
|
||||
{ q: "¿0.25 + 0.75 = ?", correct: "1.0", wrong1: "0.50", wrong2: "1.5", emoji: "➕" },
|
||||
{ q: "¿Cómo se escribe cinco centésimos?", correct: "0.05", wrong1: "0.5", wrong2: "0.005", emoji: "✏️" },
|
||||
{ q: "¿1 ÷ 4 = ?", correct: "0.25", wrong1: "0.5", wrong2: "0.75", emoji: "➗" },
|
||||
{ q: "¿0.9 + 0.1 = ?", correct: "1.0", wrong1: "0.10", wrong2: "1.10", emoji: "➕" },
|
||||
{ q: "¿Cuánto es 2.5 × 2?", correct: "5.0", wrong1: "4.5", wrong2: "5.5", emoji: "✖️" },
|
||||
{ q: "¿0.75 - 0.25 = ?", correct: "0.50", wrong1: "0.25", wrong2: "0.75", emoji: "➖" },
|
||||
{ q: "¿1/2 escrito como decimal es?", correct: "0.5", wrong1: "0.25", wrong2: "0.75", emoji: "🔢" },
|
||||
{ q: "¿0.3 es mayor, menor o igual a 0.30?", correct: "Igual", wrong1: "Mayor", wrong2: "Menor", emoji: "📏" },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < decimalExercises.length; i++) {
|
||||
const e = decimalExercises[i]
|
||||
exercises.push({
|
||||
id: `seb8-dec-${i}`,
|
||||
stage: 8,
|
||||
skillCode: `decimal-${i}`,
|
||||
type: "conciencia",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: e.q,
|
||||
text: e.q,
|
||||
audioText: e.q,
|
||||
emoji: e.emoji,
|
||||
options: [
|
||||
{ label: e.correct, correct: true, id: `c-${i}` },
|
||||
{ label: e.wrong1, correct: false, id: `w1-${i}` },
|
||||
{ label: e.wrong2, correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Leer, comparar y operar con decimales usando material base-10 Montessori.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 8,
|
||||
name: "Decimales",
|
||||
description: "Leer, comparar y operar con decimales usando bloques base-10 como material concreto.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Stage } from "../types"
|
||||
|
||||
const pctExercises = [
|
||||
{ q: "¿Cuánto es el 50% de 100?", correct: "50", wrong1: "25", wrong2: "75", emoji: "💰" },
|
||||
{ q: "¿Cuánto es el 25% de 100?", correct: "25", wrong1: "50", wrong2: "10", emoji: "💰" },
|
||||
{ q: "¿Cuánto es el 10% de 100?", correct: "10", wrong1: "20", wrong2: "5", emoji: "💰" },
|
||||
{ q: "Si un libro cuesta $200 con 50% de descuento, ¿cuánto pagás?", correct: "$100", wrong1: "$50", wrong2: "$150", emoji: "📚" },
|
||||
{ q: "¿Cuánto es el 50% de 50?", correct: "25", wrong1: "10", wrong2: "30", emoji: "🧮" },
|
||||
{ q: "¿Cuánto es el 25% de 200?", correct: "50", wrong1: "25", wrong2: "75", emoji: "🧮" },
|
||||
{ q: "¿Cuánto es el 10% de 50?", correct: "5", wrong1: "10", wrong2: "2", emoji: "🧮" },
|
||||
{ q: "Una remera cuesta $80. Hay 25% de descuento. ¿Cuánto pagás?", correct: "$60", wrong1: "$55", wrong2: "$65", emoji: "👕" },
|
||||
{ q: "Una pizza cuesta $120. Hay 50% de descuento. ¿Cuánto pagás?", correct: "$60", wrong1: "$50", wrong2: "$70", emoji: "🍕" },
|
||||
{ q: "¿50% de 10 es igual a?", correct: "5", wrong1: "2", wrong2: "7", emoji: "🧮" },
|
||||
{ q: "¿25% de 40 es igual a?", correct: "10", wrong1: "5", wrong2: "15", emoji: "🧮" },
|
||||
{ q: "¿10% de 200 es igual a?", correct: "20", wrong1: "10", wrong2: "30", emoji: "🧮" },
|
||||
{ q: "Si dejás 10% de propina en $150, ¿cuánto es la propina?", correct: "$15", wrong1: "$10", wrong2: "$20", emoji: "💵" },
|
||||
{ q: "¿Cuánto es 50% de 30?", correct: "15", wrong1: "10", wrong2: "20", emoji: "🧮" },
|
||||
{ q: "Si el 10% de un número es 5, ¿cuál es el número?", correct: "50", wrong1: "20", wrong2: "30", emoji: "🧮" },
|
||||
{ q: "Un pantalón cuesta $250 y tiene 20% de descuento. ¿Cuánto pagás?", correct: "$200", wrong1: "$225", wrong2: "$175", emoji: "👖" },
|
||||
{ q: "¿50% es igual a qué fracción?", correct: "1/2", wrong1: "1/4", wrong2: "2/3", emoji: "🥧" },
|
||||
{ q: "¿25% es igual a qué fracción?", correct: "1/4", wrong1: "1/2", wrong2: "1/3", emoji: "🥧" },
|
||||
{ q: "¿10% es igual a qué fracción?", correct: "1/10", wrong1: "1/5", wrong2: "1/20", emoji: "🥧" },
|
||||
{ q: "En una clase de 30 alumnos, 50% son varones. ¿Cuántos varones hay?", correct: "15", wrong1: "10", wrong2: "20", emoji: "👥" },
|
||||
]
|
||||
|
||||
const exercises: any[] = []
|
||||
for (let i = 0; i < pctExercises.length; i++) {
|
||||
const e = pctExercises[i]
|
||||
exercises.push({
|
||||
id: `seb9-pct-${i}`,
|
||||
stage: 9,
|
||||
skillCode: `porcentaje-${i}`,
|
||||
type: "eleccion",
|
||||
prerequisites: i === 0 ? [] : [exercises[i - 1].id],
|
||||
content: {
|
||||
instruction: e.q,
|
||||
text: e.q,
|
||||
audioText: e.q,
|
||||
emoji: e.emoji,
|
||||
options: [
|
||||
{ label: e.correct, correct: true, id: `c-${i}` },
|
||||
{ label: e.wrong1, correct: false, id: `w1-${i}` },
|
||||
{ label: e.wrong2, correct: false, id: `w2-${i}` },
|
||||
].sort(() => Math.random() - 0.5),
|
||||
},
|
||||
functionalContext: "Calcular porcentajes básicos (50%, 25%, 10%) aplicados a descuentos, propinas y situaciones cotidianas.",
|
||||
errorType: "confusion-letra",
|
||||
})
|
||||
}
|
||||
|
||||
const stage: Stage = {
|
||||
id: 9,
|
||||
name: "Porcentajes",
|
||||
description: "Calcular porcentajes básicos (50%, 25%, 10%) en situaciones cotidianas como descuentos y propinas.",
|
||||
exercises,
|
||||
}
|
||||
|
||||
export default stage
|
||||
Reference in New Issue
Block a user