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>
210 lines
16 KiB
TypeScript
210 lines
16 KiB
TypeScript
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);
|
||
}
|