Files
project-math/src/data/challenges/math.ts
Jose Luis Montañes 8d8a811ede feat: circuit builder, electronics lab, SPICE simulator, expanded skill tree
Workbenches:
- Circuit Builder: drag-and-drop logic gates, wire connections, truth table verification, fullscreen mode
- Electronics Lab: SPICE-like DC simulator with MNA solver, voltage sources, resistors, capacitors, LEDs, switches, NMOS/PMOS transistors, voltmeter, ammeter, play/pause simulation, fullscreen mode
- Explanation renderer: auto-detects ASCII truth tables and renders them as styled HTML

Skill tree:
- 65+ nodes across 19 groups spanning math → electronics → CPU → ASM → OS → networking → web
- Groups: Aritmética, Álgebra, Lógica, Electrónica, Circuitos Digitales, Secuenciales, Tu CPU, Verilog/HDL, Arquitectura Extendida, Sistemas Operativos, Programación en C, Redes, La Web, Señales, Síntesis Audio, Gráficos, Tu Consola
- Dependency highlighting: clicking a node dims all others and highlights the full path
- Group boxes with colored borders around related nodes
- Dependency chain audit: fixed illogical prerequisites throughout the tree

Content:
- 24 electronics challenges (basics, series/parallel, capacitors, diodes, transistors, op-amps, power supplies)
- 12 circuit builder challenges (logic gates, NAND universality, combinational circuits)
- Fixed all explanation spoilers: examples now use different numbers than the challenge questions
- Probe system now requires voltmeter/ammeter instruments instead of checking arbitrary node IDs

UX:
- Custom dark-themed scrollbars
- Fullscreen mode for circuit/electronics editors (portal-based, Esc to exit)
- SVG coordinate fix using getScreenCTM for accurate wire placement in fullscreen
- Meter reading labels positioned correctly regardless of component rotation
- Scratchpad defaults to closed, persists open/close state in localStorage
- Empty placeholder nodes show "Próximamente" instead of appearing completed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 03:50:07 +01:00

210 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Challenge } from '@/types/challenge';
function mathChallenge(
id: string,
nodeId: string,
title: string,
problem: string,
answer: number,
difficulty: 1 | 2 | 3 | 4 | 5 = 1,
hints: string[] = [],
tolerance = 0,
explanation?: string
): Challenge {
return {
id: `${nodeId}/${id}`,
nodeId,
title,
description: problem,
difficulty,
type: 'math-input',
hints,
xpReward: difficulty * 20,
explanation,
content: {
type: 'math-input',
problem,
answer: { type: 'numeric', value: answer, tolerance },
},
};
}
export const arithmeticChallenges: Challenge[] = [
// Addition
mathChallenge('add-01', 'arithmetic.addition', 'Suma simple', '¿Cuánto es 7 + 5?', 12, 1, ['Cuenta desde 7 hacia arriba'], 0,
'La suma es la operación más básica. Combina dos cantidades en una sola.\n\nPor ejemplo: si tienes 3 manzanas y te dan 2 más, ahora tienes 3 + 2 = 5 manzanas.\n\nTruco: para sumar mentalmente, empieza por el número más grande y cuenta hacia arriba. Por ejemplo, para 8 + 6: empieza en 8 y cuenta 6 más → 9, 10, 11, 12, 13, 14.'),
mathChallenge('add-02', 'arithmetic.addition', 'Suma de dos cifras', '¿Cuánto es 34 + 27?', 61, 1, ['Suma las unidades primero: 4+7=11']),
mathChallenge('add-03', 'arithmetic.addition', 'Suma de tres números', '¿Cuánto es 15 + 23 + 42?', 80, 1, ['Suma de dos en dos']),
mathChallenge('add-04', 'arithmetic.addition', 'Suma con centenas', '¿Cuánto es 256 + 189?', 445, 1, ['Empieza por las unidades: 6+9=15']),
// Subtraction
mathChallenge('sub-01', 'arithmetic.subtraction', 'Resta simple', '¿Cuánto es 15 - 8?', 7, 1, ['Cuenta hacia atrás desde 15'], 0,
'La resta es la operación inversa de la suma. Quita una cantidad de otra.\n\nPor ejemplo: si tienes 13 galletas y comes 5, te quedan 13 - 5 = 8.\n\nTruco: puedes pensar "¿qué le sumo a 5 para llegar a 13?". Si 5 + 8 = 13, entonces 13 - 5 = 8.'),
mathChallenge('sub-02', 'arithmetic.subtraction', 'Resta de dos cifras', '¿Cuánto es 82 - 47?', 35, 1, ['Necesitas "pedir prestado" en las unidades']),
mathChallenge('sub-03', 'arithmetic.subtraction', 'Resta con centenas', '¿Cuánto es 500 - 237?', 263, 1),
// Multiplication
mathChallenge('mul-01', 'arithmetic.multiplication', 'Multiplicación básica', '¿Cuánto es 6 × 7?', 42, 1, ['Piensa en 6 grupos de 7'], 0,
'La multiplicación es una suma repetida. 4 × 8 significa "sumar 8 cuatro veces" (o "sumar 4 ocho veces").\n\n4 × 8 = 8 + 8 + 8 + 8 = 32\n\nAprender las tablas de multiplicar de memoria es muy útil. Truco: si no recuerdas 4×8, piensa en 4×5=20 y luego suma 4×3=12 → 20+12=32.'),
mathChallenge('mul-02', 'arithmetic.multiplication', 'Multiplicación de dos cifras', '¿Cuánto es 12 × 15?', 180, 1, ['12 × 15 = 12 × 10 + 12 × 5']),
mathChallenge('mul-03', 'arithmetic.multiplication', 'Multiplicación avanzada', '¿Cuánto es 25 × 32?', 800, 2, ['25 × 32 = 25 × 4 × 8']),
// Division
mathChallenge('div-01', 'arithmetic.division', 'División exacta', '¿Cuánto es 56 ÷ 8?', 7, 1, ['¿Qué número × 8 = 56?'], 0,
'La división es la operación inversa de la multiplicación. Reparte una cantidad en partes iguales.\n\n36 ÷ 6 significa: "¿en cuántos grupos de 6 cabe 36?" o "si reparto 36 entre 6, ¿cuánto toca a cada uno?"\n\nTruco: piensa "¿qué número multiplicado por 6 da 36?". Como 6 × 6 = 36, entonces 36 ÷ 6 = 6.'),
mathChallenge('div-02', 'arithmetic.division', 'División de dos cifras', '¿Cuánto es 144 ÷ 12?', 12, 1),
mathChallenge('div-03', 'arithmetic.division', 'División con decimales', '¿Cuánto es 7 ÷ 4?', 1.75, 2, ['Divide y continúa con decimales'], 0.01),
];
export const fractionChallenges: Challenge[] = [
mathChallenge('frac-01', 'arithmetic.fractions', 'Suma de fracciones', '¿Cuánto es 1/3 + 1/6?', 0.5, 2, ['Encuentra un denominador común: 6'], 0.01,
'Las fracciones representan partes de un todo. 1/3 = "una de tres partes", 1/6 = "una de seis partes".\n\nPara sumar fracciones necesitas el mismo denominador (la parte de abajo):\n• 1/4 = 1/4\n• 1/2 = 2/4 (multiplica arriba y abajo por 2)\n• 1/4 + 2/4 = 3/4 = 0.75\n\nRegla: busca el mínimo común denominador, convierte ambas fracciones, y luego suma los numeradores.'),
mathChallenge('frac-02', 'arithmetic.fractions', 'Multiplicación de fracciones', '¿Cuánto es 2/3 × 3/4?', 0.5, 2, ['Multiplica numerador con numerador y denominador con denominador'], 0.01),
mathChallenge('frac-03', 'arithmetic.fractions', 'Fracción a decimal', '¿Cuánto es 5/8 en decimal?', 0.625, 2, ['Divide 5 entre 8'], 0.001),
];
export const decimalChallenges: Challenge[] = [
mathChallenge('dec-01', 'arithmetic.decimals', 'Suma de decimales', '¿Cuánto es 3.7 + 2.85?', 6.55, 2, ['Alinea los puntos decimales'], 0.01,
'Los decimales son otra forma de escribir fracciones. 3.7 = 3 + 7/10, y 2.85 = 2 + 85/100.\n\nPara sumar decimales, alinea los puntos decimales y suma columna por columna:\n 2.30\n+ 1.45\n------\n 3.75\n\nTruco: si un número tiene menos decimales, añade ceros al final (2.3 → 2.30) para que sea más fácil alinearlos.'),
mathChallenge('dec-02', 'arithmetic.decimals', 'Redondeo', 'Redondea 3.746 a dos decimales', 3.75, 2, ['Mira el tercer decimal: 6 ≥ 5, sube'], 0.001),
mathChallenge('dec-03', 'arithmetic.decimals', 'Multiplicación decimal', '¿Cuánto es 2.5 × 0.4?', 1, 2, ['2.5 × 0.4 = 25 × 4 ÷ 100']),
];
export const percentageChallenges: Challenge[] = [
mathChallenge('pct-01', 'arithmetic.percentages', 'Porcentaje básico', '¿Cuánto es el 25% de 200?', 50, 2, ['25% = 1/4'], 0,
'Porcentaje significa "por cada cien". 25% = 25/100 = 0.25\n\nPara calcular un porcentaje de un número, multiplica el número por el porcentaje en decimal:\n• 20% de 150 = 0.20 × 150 = 30\n\nAtajos útiles:\n• 50% = la mitad\n• 25% = un cuarto\n• 10% = mover el punto decimal una posición a la izquierda\n• 1% = mover el punto decimal dos posiciones'),
mathChallenge('pct-02', 'arithmetic.percentages', 'Descuento', 'Un artículo cuesta 80€. Con 15% de descuento, ¿cuánto pagas?', 68, 2, ['Calcula el 15% de 80 y réstalo']),
mathChallenge('pct-03', 'arithmetic.percentages', 'Porcentaje inverso', 'Si 30 es el 60% de un número, ¿cuál es ese número?', 50, 2, ['30 = 0.6 × x, entonces x = 30/0.6']),
];
export const primeChallenges: Challenge[] = [
mathChallenge('prime-01', 'number-theory.primes', '¿Es primo?', '¿Cuántos números primos hay entre 1 y 20?', 8, 2, ['Los primos son: 2, 3, 5, 7, 11, 13, 17, 19'], 0,
'Un número primo es un número mayor que 1 que solo es divisible por 1 y por sí mismo.\n\nEntre 1 y 10, los primos son: 2, 3, 5, 7 (hay 4).\n\n• 2 es primo (solo divisible por 1 y 2)\n• 4 NO es primo (divisible por 1, 2 y 4)\n• 5 es primo\n• 9 NO es primo (3 × 3 = 9)\n\nEl 1 no se considera primo. El 2 es el único primo par.\n\nPara verificar si un número es primo, comprueba si es divisible por algún número hasta su raíz cuadrada.'),
mathChallenge('prime-02', 'number-theory.primes', 'Factorización', '¿Cuál es el factor primo más grande de 84?', 7, 2, ['84 = 2 × 42 = 2 × 2 × 21 = 2 × 2 × 3 × 7']),
mathChallenge('prime-03', 'number-theory.primes', 'Primo siguiente', '¿Cuál es el siguiente número primo después de 23?', 29, 2, ['Comprueba 24, 25, 26, 27, 28, 29...']),
];
export const gcdLcmChallenges: Challenge[] = [
mathChallenge('gcd-01', 'number-theory.gcd-lcm', 'MCD básico', '¿Cuál es el MCD de 24 y 36?', 12, 2, ['Factoriza: 24=2³×3, 36=2²×3²'], 0,
'El MCD (Máximo Común Divisor) es el número más grande que divide a dos números exactamente.\n\nMétodo de factorización:\n1. Descompón cada número en factores primos:\n • 18 = 2 × 3 × 3 = 2 × 3²\n • 12 = 2 × 2 × 3 = 2² × 3\n2. Toma los factores comunes con el menor exponente:\n • Factor 2: mín(1,2) = 2¹ = 2\n • Factor 3: mín(2,1) = 3¹ = 3\n3. Multiplica: 2 × 3 = 6\n\nEl MCD de 18 y 12 es 6.\n\nMétodo alternativo (Euclides): divide el mayor entre el menor, luego el menor entre el resto, repite hasta que el resto sea 0. El último divisor es el MCD.\n• 18 ÷ 12 = 1 resto 6\n• 12 ÷ 6 = 2 resto 0 → MCD = 6'),
mathChallenge('gcd-02', 'number-theory.gcd-lcm', 'MCM básico', '¿Cuál es el MCM de 6 y 8?', 24, 2, ['MCM = (6×8)/MCD(6,8)'], 0,
'El MCM (Mínimo Común Múltiplo) es el número más pequeño que es múltiplo de ambos números.\n\n¡No confundir con el MCD!\n• MCD = el mayor número que DIVIDE a ambos (busca divisores)\n• MCM = el menor número que es MÚLTIPLO de ambos (busca múltiplos)\n\nMétodo 1 — listar múltiplos:\n• Múltiplos de 4: 4, 8, 12, 16, 20...\n• Múltiplos de 6: 6, 12, 18, 24...\n• El primero en común es 12\n\nMétodo 2 — fórmula rápida:\nMCM(a,b) = (a × b) / MCD(a,b)\nMCM(4,6) = (4 × 6) / MCD(4,6) = 24 / 2 = 12\n\nEl MCM se usa mucho para sumar fracciones con distinto denominador.'),
mathChallenge('gcd-03', 'number-theory.gcd-lcm', 'MCD de tres números', '¿Cuál es el MCD de 12, 18 y 30?', 6, 2),
];
export const variableChallenges: Challenge[] = [
mathChallenge('var-01', 'algebra.variables', 'Evaluar expresión', 'Si x = 3, ¿cuánto vale 2x + 5?', 11, 2, ['Sustituye x por 3: 2(3) + 5'], 0,
'En álgebra usamos letras (variables) para representar números desconocidos.\n\n"3x" significa "3 multiplicado por x". Si x = 4:\n• 3x = 3 × 4 = 12\n• 3x + 2 = 12 + 2 = 14\n\nEvaluar una expresión es sustituir la variable por su valor y calcular el resultado. Siempre resuelve multiplicaciones antes que sumas (orden de operaciones).'),
mathChallenge('var-02', 'algebra.variables', 'Expresión con dos variables', 'Si a = 4 y b = 7, ¿cuánto vale 3a - b + 2?', 7, 2, ['3(4) - 7 + 2 = 12 - 7 + 2']),
mathChallenge('var-03', 'algebra.variables', 'Simplificar', 'Simplifica: 3x + 2x - x. ¿Cuántos "x" quedan?', 4, 2, ['Suma los coeficientes: 3+2-1']),
];
export const equationChallenges: Challenge[] = [
mathChallenge('eq-01', 'algebra.equations', 'Ecuación simple', 'Resuelve: x + 7 = 15', 8, 3, ['Resta 7 de ambos lados'], 0,
'Una ecuación es una igualdad con una incógnita (x). Resolverla es encontrar el valor de x.\n\nRegla de oro: lo que hagas a un lado, hazlo al otro.\n\nEjemplo: x + 5 = 12\n• Queremos x sola → restamos 5 de ambos lados\n• x + 5 - 5 = 12 - 5\n• x = 7\n\nComprobación: 7 + 5 = 12 ✓\n\nOperaciones inversas: suma↔resta, multiplicación↔división.'),
mathChallenge('eq-02', 'algebra.equations', 'Ecuación con multiplicación', 'Resuelve: 3x = 21', 7, 3, ['Divide ambos lados entre 3']),
mathChallenge('eq-03', 'algebra.equations', 'Ecuación de dos pasos', 'Resuelve: 2x + 5 = 17', 6, 3, ['Primero resta 5, luego divide entre 2']),
mathChallenge('eq-04', 'algebra.equations', 'Ecuación con paréntesis', 'Resuelve: 3(x - 2) = 15', 7, 3, ['Distribuye: 3x - 6 = 15']),
];
export const linearSystemChallenges: Challenge[] = [
mathChallenge('sys-01', 'algebra.linear-systems', 'Sistema simple', 'Resuelve: x + y = 10, x - y = 2. ¿Cuánto vale x?', 6, 3, ['Suma ambas ecuaciones: 2x = 12'], 0,
'Un sistema de ecuaciones son dos (o más) ecuaciones que deben cumplirse a la vez.\n\nMétodo de eliminación:\n1. Suma o resta las ecuaciones para eliminar una variable\n2. Resuelve la variable que queda\n3. Sustituye para encontrar la otra\n\nEjemplo:\n x + y = 7\n x - y = 3\n\nSumando ambas: (x+y) + (x-y) = 7+3 → 2x = 10 → x = 5\nSustituyendo: 5 + y = 7 → y = 2'),
mathChallenge('sys-02', 'algebra.linear-systems', 'Sistema por sustitución', 'Resuelve: y = 2x, x + y = 9. ¿Cuánto vale x?', 3, 3, ['Sustituye y: x + 2x = 9']),
mathChallenge('sys-03', 'algebra.linear-systems', 'Sistema avanzado', 'Resuelve: 2x + 3y = 16, x - y = 3. ¿Cuánto vale y?', 2, 3, ['De la segunda: x = y + 3. Sustituye en la primera.']),
];
export const quadraticChallenges: Challenge[] = [
mathChallenge('quad-01', 'algebra.quadratics', 'Cuadrática simple', 'Resuelve: x² = 25. Da la solución positiva.', 5, 3, ['√25 = 5'], 0,
'Una ecuación cuadrática contiene x² (x al cuadrado). La forma general es ax² + bx + c = 0.\n\nEl caso más simple: x² = número\n• Solución: x = ±√número\n• x² = 16 → x = +4 o x = -4 (porque tanto 4×4 como (-4)×(-4) dan 16)\n\nPara ecuaciones más complejas, se usa la fórmula general:\nx = (-b ± √(b²-4ac)) / 2a\n\nO se intenta factorizar: x²-5x+6 = (x-2)(x-3) = 0 → x=2 o x=3'),
mathChallenge('quad-02', 'algebra.quadratics', 'Factorización', 'Resuelve: x² - 5x + 6 = 0. Da la solución mayor.', 3, 3, ['Factoriza: (x-2)(x-3) = 0']),
mathChallenge('quad-03', 'algebra.quadratics', 'Fórmula general', 'Resuelve: x² + 2x - 8 = 0. Da la solución positiva.', 2, 3, ['Usa: x = (-b ± √(b²-4ac)) / 2a']),
];
export const booleanChallenges: Challenge[] = [
{
id: 'logic.boolean/bool-01',
nodeId: 'logic.boolean',
title: 'AND básico',
description: 'En lógica booleana, ¿cuál es el resultado de TRUE AND FALSE?',
difficulty: 2,
type: 'multiple-choice',
hints: ['AND solo es TRUE cuando ambos operandos son TRUE'],
xpReward: 40,
explanation: 'La lógica booleana trabaja con dos valores: TRUE (verdadero) y FALSE (falso).\n\nOperadores básicos:\n\n• AND (Y): Solo es TRUE si AMBOS son TRUE\n TRUE AND TRUE = TRUE\n TRUE AND FALSE = FALSE\n FALSE AND FALSE = FALSE\n\n• OR (O): Es TRUE si AL MENOS UNO es TRUE\n TRUE OR FALSE = TRUE\n FALSE OR FALSE = FALSE\n\n• NOT (NO): Invierte el valor\n NOT TRUE = FALSE\n NOT FALSE = TRUE\n\nEstos operadores son la base de toda la computación y los circuitos digitales.',
content: {
type: 'multiple-choice',
question: '¿Cuál es el resultado de TRUE AND FALSE?',
options: ['TRUE', 'FALSE', 'NULL', 'ERROR'],
correctIndex: 1,
},
},
{
id: 'logic.boolean/bool-02',
nodeId: 'logic.boolean',
title: 'OR básico',
description: '¿Cuál es el resultado de FALSE OR TRUE?',
difficulty: 2,
type: 'multiple-choice',
hints: ['OR es TRUE cuando al menos uno es TRUE'],
xpReward: 40,
content: {
type: 'multiple-choice',
question: '¿Cuál es el resultado de FALSE OR TRUE?',
options: ['TRUE', 'FALSE'],
correctIndex: 0,
},
},
{
id: 'logic.boolean/bool-03',
nodeId: 'logic.boolean',
title: 'NOT y combinaciones',
description: '¿Cuál es el resultado de NOT (TRUE AND FALSE)?',
difficulty: 2,
type: 'multiple-choice',
hints: ['Primero evalúa TRUE AND FALSE, luego aplica NOT'],
xpReward: 40,
content: {
type: 'multiple-choice',
question: '¿Cuál es el resultado de NOT (TRUE AND FALSE)?',
options: ['TRUE', 'FALSE'],
correctIndex: 0,
},
},
];
export const binaryChallenges: Challenge[] = [
mathChallenge('bin-01', 'logic.binary', 'Decimal a binario', '¿Cuánto es 13 en binario? (escribe el número decimal que forman los dígitos binarios, ej: 1101)', 1101, 2, ['13 = 8+4+1 = 1101₂'], 0,
'El sistema binario usa solo dos dígitos: 0 y 1. Cada posición vale el doble que la anterior (de derecha a izquierda):\n\n... 16 8 4 2 1\n\nPara convertir decimal a binario, descompón en potencias de 2:\n• 11 = 8 + 2 + 1\n• 11 = 1×8 + 0×4 + 1×2 + 1×1\n• 11 en binario = 1011\n\nMétodo alternativo: divide entre 2 repetidamente y lee los restos de abajo arriba:\n• 11÷2 = 5 resto 1\n• 5÷2 = 2 resto 1\n• 2÷2 = 1 resto 0\n• 1÷2 = 0 resto 1\n→ 1011'),
mathChallenge('bin-02', 'logic.binary', 'Binario a decimal', '¿Cuánto es 10110 en decimal?', 22, 2, ['1×16 + 0×8 + 1×4 + 1×2 + 0×1']),
mathChallenge('bin-03', 'logic.binary', 'Suma binaria', '¿Cuánto es 1010 + 0110 en decimal?', 16, 2, ['1010=10, 0110=6, 10+6=16']),
];
import { allCircuitChallenges } from './circuits';
import { allElectronicsChallenges } from './electronics';
export const allChallenges: Challenge[] = [
...arithmeticChallenges,
...fractionChallenges,
...decimalChallenges,
...percentageChallenges,
...primeChallenges,
...gcdLcmChallenges,
...variableChallenges,
...equationChallenges,
...linearSystemChallenges,
...quadraticChallenges,
...booleanChallenges,
...binaryChallenges,
...allCircuitChallenges,
...allElectronicsChallenges,
];
export function getChallengeById(id: string): Challenge | undefined {
return allChallenges.find((c) => c.id === id);
}
export function getChallengesForNode(nodeId: string): Challenge[] {
return allChallenges.filter((c) => c.nodeId === nodeId);
}