feat: interactive linear algebra practice web app
- 68 exercises from UBA FCE chapters 1-3 - Step-by-step solutions with KaTeX rendering - Theory panels (26 topics) expandable per exercise - Matrix builder (2x2/3x3/4x4) with 7 operations - System solver (Gauss, Gauss-Jordan, Cramer, Rouché-Frobenius) - Glassmorphism UI with dark mode - Canvas particle background - ARIA accessibility (keyboard nav, screen reader) - Zero build step - open index.html directly
This commit is contained in:
1404
css/styles.css
Normal file
1404
css/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
523
data/exercises-cap01.json
Normal file
523
data/exercises-cap01.json
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cap01-01",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "components",
|
||||||
|
"theoryKey": "vectors-theory",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Dados los puntos A = (2; -1; 3) y B = (4; 2; -1), hallar las componentes del vector \\overrightarrow{AB}.",
|
||||||
|
"hint": "Las componentes de AB son B - A",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [2, 3, -4], "latex": "(2;\\; 3;\\; -4)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Fórmula: las componentes de AB = B - A", "expression": "\\overrightarrow{AB} = B - A = (4; 2; -1) - (2; -1; 3)" },
|
||||||
|
{ "desc": "Restar componente a componente", "expression": "\\overrightarrow{AB} = (4-2;\\; 2-(-1);\\; -1-3) = (2;\\; 3;\\; -4)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-02",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "magnitude",
|
||||||
|
"theoryKey": "vectors-theory",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar el módulo de los vectores: a) \\vec{u} = (3; -4), b) \\vec{v} = (1; 2; -2), c) \\vec{w} = (-1; 0; 3).",
|
||||||
|
"hint": "|v| = √(v₁² + v₂² + v₃²)",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "|u|=5, |v|=3, |w|=√10", "latex": "|\\vec{u}|=5,\\quad |\\vec{v}|=3,\\quad |\\vec{w}|=\\sqrt{10}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Módulo de u = (3; -4)", "expression": "|\\vec{u}| = \\sqrt{3^2 + (-4)^2} = \\sqrt{9 + 16} = \\sqrt{25} = 5" },
|
||||||
|
{ "desc": "b) Módulo de v = (1; 2; -2)", "expression": "|\\vec{v}| = \\sqrt{1^2 + 2^2 + (-2)^2} = \\sqrt{1 + 4 + 4} = \\sqrt{9} = 3" },
|
||||||
|
{ "desc": "c) Módulo de w = (-1; 0; 3)", "expression": "|\\vec{w}| = \\sqrt{(-1)^2 + 0^2 + 3^2} = \\sqrt{1 + 0 + 9} = \\sqrt{10}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-03",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "unit-vector",
|
||||||
|
"theoryKey": "vectors-theory",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Dado \\vec{u} = (4; -3; 5), hallar el vector unitario en la dirección de \\vec{u}.",
|
||||||
|
"hint": "û = u / |u|",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [0.5657, -0.4243, 0.7071], "latex": "\\hat{u} = \\left(\\frac{4}{5\\sqrt{2}};\\; \\frac{-3}{5\\sqrt{2}};\\; \\frac{5}{5\\sqrt{2}}\\right) = \\left(\\frac{4}{5\\sqrt{2}};\\; \\frac{-3}{5\\sqrt{2}};\\; \\frac{1}{\\sqrt{2}}\\right)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular el módulo de u", "expression": "|\\vec{u}| = \\sqrt{16 + 9 + 25} = \\sqrt{50} = 5\\sqrt{2}" },
|
||||||
|
{ "desc": "Dividir cada componente por el módulo", "expression": "\\hat{u} = \\frac{\\vec{u}}{|\\vec{u}|} = \\frac{(4;\\;-3;\\;5)}{5\\sqrt{2}} = \\left(\\frac{4}{5\\sqrt{2}};\\;\\frac{-3}{5\\sqrt{2}};\\;\\frac{1}{\\sqrt{2}}\\right)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-04",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "addition",
|
||||||
|
"theoryKey": "vector-ops",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Sean \\vec{u} = (1; -2; 3) y \\vec{v} = (2; 1; -1). Calcular: a) \\vec{u} + \\vec{v}, b) 3\\vec{u} - 2\\vec{v}, c) |\\vec{u} + \\vec{v}|.",
|
||||||
|
"hint": "Sumar/restar componente a componente",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)(3;-1;2) b)(-1;-8;11) c)√14", "latex": "a)\\,(3;-1;2),\\quad b)\\,(-1;-8;11),\\quad c)\\,\\sqrt{14}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Suma u + v", "expression": "\\vec{u} + \\vec{v} = (1+2;\\;-2+1;\\;3+(-1)) = (3;\\;-1;\\;2)" },
|
||||||
|
{ "desc": "b) 3u - 2v", "expression": "3\\vec{u} = (3;-6;9),\\quad 2\\vec{v} = (4;2;-2)" },
|
||||||
|
{ "desc": "Restar", "expression": "3\\vec{u} - 2\\vec{v} = (3-4;\\;-6-2;\\;9-(-2)) = (-1;\\;-8;\\;11)" },
|
||||||
|
{ "desc": "c) Módulo de u + v = (3; -1; 2)", "expression": "|\\vec{u}+\\vec{v}| = \\sqrt{9+1+4} = \\sqrt{14}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-05",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "dot-product",
|
||||||
|
"theoryKey": "dot-product",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Sean \\vec{u} = (2; -1; 3) y \\vec{v} = (4; 3; -2). Calcular \\vec{u} \\cdot \\vec{v}.",
|
||||||
|
"hint": "u·v = u₁v₁ + u₂v₂ + u₃v₃",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": -1, "latex": "-1" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Producto escalar componente a componente", "expression": "\\vec{u} \\cdot \\vec{v} = 2 \\cdot 4 + (-1) \\cdot 3 + 3 \\cdot (-2)" },
|
||||||
|
{ "desc": "Calcular", "expression": "= 8 - 3 - 6 = -1" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-06",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "cross-product",
|
||||||
|
"theoryKey": "cross-product",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Sean \\vec{u} = (1; 2; -1) y \\vec{v} = (3; 0; 4). Calcular \\vec{u} \\times \\vec{v}.",
|
||||||
|
"hint": "Producto vectorial por determinante",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [8, -7, -6], "latex": "(8;\\;-7;\\;-6)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Producto vectorial por determinante", "expression": "\\vec{u} \\times \\vec{v} = \\begin{vmatrix} \\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\ 1 & 2 & -1 \\\\ 3 & 0 & 4 \\end{vmatrix}" },
|
||||||
|
{ "desc": "Calcular componente i", "expression": "i: (2)(4) - (-1)(0) = 8" },
|
||||||
|
{ "desc": "Calcular componente j", "expression": "j: -[(1)(4) - (-1)(3)] = -[4+3] = -7" },
|
||||||
|
{ "desc": "Calcular componente k", "expression": "k: (1)(0) - (2)(3) = -6" },
|
||||||
|
{ "desc": "Resultado", "expression": "\\vec{u} \\times \\vec{v} = (8;\\;-7;\\;-6)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-07",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "parallel",
|
||||||
|
"theoryKey": "parallel-perp",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Determinar si los vectores \\vec{u} = (2; 1; -3) y \\vec{v} = (-4; -2; 6) son paralelos.",
|
||||||
|
"hint": "Son paralelos si u = kv para algún escalar k",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Sí, son paralelos (k=-2)", "latex": "\\text{Sí, } \\vec{v} = -2\\vec{u}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Verificar si u × v = 0", "expression": "\\vec{u} \\times \\vec{v} = \\begin{vmatrix} \\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\ 2 & 1 & -3 \\\\ -4 & -2 & 6 \\end{vmatrix}" },
|
||||||
|
{ "desc": "Componente i: (1)(6) - (-3)(-2) = 6 - 6 = 0", "expression": "i: 6 - 6 = 0" },
|
||||||
|
{ "desc": "Componente j: -[(2)(6) - (-3)(-4)] = -[12-12] = 0", "expression": "j: -(12-12) = 0" },
|
||||||
|
{ "desc": "Componente k: (2)(-2) - (1)(-4) = -4+4 = 0", "expression": "k: -4+4 = 0" },
|
||||||
|
{ "desc": "Como u×v=0, son paralelos. Además v = -2u", "expression": "\\vec{v} = (-4;-2;6) = -2(2;1;-3) = -2\\vec{u}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-08",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "perpendicular",
|
||||||
|
"theoryKey": "parallel-perp",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Determinar si los vectores \\vec{u} = (1; -1; 2) y \\vec{v} = (3; 1; 1) son perpendiculares.",
|
||||||
|
"hint": "Son perpendiculares si u·v = 0",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "No son perpendiculares", "latex": "\\vec{u} \\cdot \\vec{v} = 4 \\neq 0 \\Rightarrow \\text{No son perpendiculares}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular el producto escalar", "expression": "\\vec{u} \\cdot \\vec{v} = (1)(3) + (-1)(1) + (2)(1) = 3 - 1 + 2 = 4" },
|
||||||
|
{ "desc": "Como no es 0, no son perpendiculares", "expression": "\\vec{u} \\cdot \\vec{v} = 4 \\neq 0 \\Rightarrow \\text{No son perpendiculares}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-09",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "mixed-product",
|
||||||
|
"theoryKey": "coplanarity",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Dados \\vec{u} = (1; -1; 2), \\vec{v} = (3; 0; 1) y \\vec{w} = (0; 2; -1), calcular el producto mixto [\\vec{u}, \\vec{v}, \\vec{w}] y determinar si son coplanarios.",
|
||||||
|
"hint": "[u,v,w] = u · (v × w). Si es 0, son coplanarios.",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 7, "latex": "[\\vec{u},\\vec{v},\\vec{w}] = 7 \\neq 0, \\text{ no coplanarios}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular v × w", "expression": "\\vec{v} \\times \\vec{w} = \\begin{vmatrix} \\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\ 3 & 0 & 1 \\\\ 0 & 2 & -1 \\end{vmatrix} = (-2;\\;3;\\;6)" },
|
||||||
|
{ "desc": "Calcular u · (v × w)", "expression": "[\\vec{u},\\vec{v},\\vec{w}] = (1)(-2) + (-1)(3) + (2)(6) = -2 - 3 + 12 = 7" },
|
||||||
|
{ "desc": "Conclusión", "expression": "[\\vec{u},\\vec{v},\\vec{w}] = 7 \\neq 0 \\Rightarrow \\text{No son coplanarios}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-10",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "parametric",
|
||||||
|
"theoryKey": "line-equations",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar las ecuaciones paramétricas y continuas de la recta que pasa por A = (1; -2; 3) y B = (4; 1; -1).",
|
||||||
|
"hint": "Vector director d = B - A",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "r: (x,y,z) = (1,-2,3) + t(3,3,-4)", "latex": "r: \\frac{x-1}{3} = \\frac{y+2}{3} = \\frac{z-3}{-4}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Vector director d = B - A", "expression": "\\vec{d} = B - A = (4-1;\\;1-(-2);\\;-1-3) = (3;\\;3;\\;-4)" },
|
||||||
|
{ "desc": "Ecuaciones paramétricas", "expression": "x = 1 + 3t,\\quad y = -2 + 3t,\\quad z = 3 - 4t" },
|
||||||
|
{ "desc": "Ecuaciones continuas", "expression": "\\frac{x-1}{3} = \\frac{y+2}{3} = \\frac{z-3}{-4}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-11",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "parametric",
|
||||||
|
"theoryKey": "line-equations",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar la ecuación de la recta que pasa por P = (2; 0; -1) y tiene vector director \\vec{v} = (1; 3; -2).",
|
||||||
|
"hint": "r: X = P + tv",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "r: (x,y,z) = (2,0,-1)+t(1,3,-2)", "latex": "r: (x,y,z) = (2;0;-1) + t(1;3;-2)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Ecuación vectorial", "expression": "r: (x;\\;y;\\;z) = (2;\\;0;\\;-1) + t(1;\\;3;\\;-2)" },
|
||||||
|
{ "desc": "Paramétricas", "expression": "x = 2 + t,\\quad y = 3t,\\quad z = -1 - 2t" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-12",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "relative-position",
|
||||||
|
"theoryKey": "line-positions",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Determinar la posición relativa de las rectas: r_1: (x; y; z) = (1; 0; 2) + t(1; 1; -1) y r_2: (x; y; z) = (0; 1; 1) + s(2; -1; 3).",
|
||||||
|
"hint": "Verificar si los directores son paralelos, luego calcular producto mixto [d1, d2, P2-P1]",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Rectas que se cruzan", "latex": "\\text{Se cruzan}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Directores: d1=(1;1;-1), d2=(2;-1;3). No son paralelos.", "expression": "\\vec{d_1} \\neq k\\vec{d_2} \\Rightarrow \\text{No paralelas}" },
|
||||||
|
{ "desc": "Producto mixto [d1, d2, P2-P1]", "expression": "[\\vec{d_1},\\vec{d_2},P_2-P_1] = \\begin{vmatrix} 1 & 1 & -1 \\\\ 2 & -1 & 3 \\\\ -1 & 1 & -1 \\end{vmatrix}" },
|
||||||
|
{ "desc": "Calcular el determinante", "expression": "= 1(1-3) - 1(-2+3) + (-1)(2-1) = -2 - 1 - 1 = -4 \\neq 0" },
|
||||||
|
{ "desc": "Conclusión", "expression": "[\\vec{d_1},\\vec{d_2},P_2-P_1] \\neq 0 \\Rightarrow \\text{Se cruzan}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-13",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "distance",
|
||||||
|
"theoryKey": "distance-lines",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Calcular la distancia del punto Q = (1; 2; 3) a la recta r: (x; y; z) = (0; 1; 0) + t(1; 0; 1).",
|
||||||
|
"hint": "d = |(Q-P) × v| / |v|",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 1.7321, "latex": "d = \\sqrt{3}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Q - P = (1;2;3) - (0;1;0) = (1;1;3)", "expression": "\\vec{QP} = Q - P = (1;\\;1;\\;3)" },
|
||||||
|
{ "desc": "(Q-P) × v", "expression": "(Q-P) \\times \\vec{v} = (1;1;3) \\times (1;0;1) = (1;\\;-2;\\;-1)" },
|
||||||
|
{ "desc": "Módulo del producto vectorial", "expression": "|(Q-P) \\times \\vec{v}| = \\sqrt{1+4+1} = \\sqrt{6}" },
|
||||||
|
{ "desc": "Módulo del director", "expression": "|\\vec{v}| = \\sqrt{1+0+1} = \\sqrt{2}" },
|
||||||
|
{ "desc": "Distancia", "expression": "d = \\frac{\\sqrt{6}}{\\sqrt{2}} = \\sqrt{3}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-14",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "distance",
|
||||||
|
"theoryKey": "distance-lines",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Calcular la distancia entre las rectas: r_1: (x; y; z) = (1; 0; 0) + t(1; 0; 1) y r_2: (x; y; z) = (0; 1; 0) + s(0; 1; 0).",
|
||||||
|
"hint": "Si se cruzan: d = |[d1,d2,P2-P1]| / |d1×d2|",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 0.7071, "latex": "d = \\frac{1}{\\sqrt{2}}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Verificar que se cruzan: d1×d2 ≠ 0", "expression": "\\vec{d_1} \\times \\vec{d_2} = (1;0;1) \\times (0;1;0) = (-1;\\;0;\\;1)" },
|
||||||
|
{ "desc": "P2 - P1 = (-1; 1; 0)", "expression": "P_2 - P_1 = (0-1;\\;1-0;\\;0-0) = (-1;\\;1;\\;0)" },
|
||||||
|
{ "desc": "Producto mixto [d1,d2,P2-P1]", "expression": "[\\vec{d_1},\\vec{d_2},P_2-P_1] = \\begin{vmatrix} 1 & 0 & 1 \\\\ 0 & 1 & 0 \\\\ -1 & 1 & 0 \\end{vmatrix} = 0 + 0 + 0 - (-1) - 0 - 0 = 1" },
|
||||||
|
{ "desc": "Distancia", "expression": "d = \\frac{|[\\vec{d_1},\\vec{d_2},P_2-P_1]|}{|\\vec{d_1} \\times \\vec{d_2}|} = \\frac{1}{\\sqrt{2}}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-15",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "angle",
|
||||||
|
"theoryKey": "angle",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar el ángulo entre las rectas con directores \\vec{v_1} = (1; 2; 3) y \\vec{v_2} = (2; -1; 1).",
|
||||||
|
"hint": "cos α = |v1·v2| / (|v1|·|v2|)",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 76.37, "latex": "\\alpha \\approx 76.37^\\circ" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Producto escalar", "expression": "\\vec{v_1} \\cdot \\vec{v_2} = 2 - 2 + 3 = 3" },
|
||||||
|
{ "desc": "Módulos", "expression": "|\\vec{v_1}| = \\sqrt{14},\\quad |\\vec{v_2}| = \\sqrt{6}" },
|
||||||
|
{ "desc": "Coseno del ángulo", "expression": "\\cos\\alpha = \\frac{3}{\\sqrt{14}\\sqrt{6}} = \\frac{3}{\\sqrt{84}}" },
|
||||||
|
{ "desc": "Ángulo", "expression": "\\alpha = \\arccos\\left(\\frac{3}{\\sqrt{84}}\\right) \\approx 76.37^\\circ" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-16",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "general",
|
||||||
|
"theoryKey": "plane-equations",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar la ecuación del plano que pasa por P = (1; 2; 3) y tiene vector normal \\vec{n} = (2; -1; 4).",
|
||||||
|
"hint": "Π: n · (X - P) = 0",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "2x - y + 4z = 12", "latex": "2x - y + 4z = 12" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Ecuación normal: n · (X - P) = 0", "expression": "2(x-1) - 1(y-2) + 4(z-3) = 0" },
|
||||||
|
{ "desc": "Expandir", "expression": "2x - 2 - y + 2 + 4z - 12 = 0" },
|
||||||
|
{ "desc": "Simplificar", "expression": "2x - y + 4z - 12 = 0 \\Rightarrow 2x - y + 4z = 12" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-17",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "general",
|
||||||
|
"theoryKey": "plane-equations",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Hallar la ecuación del plano que pasa por los puntos A = (1; 0; 1), B = (0; 1; 2) y C = (2; 1; 0).",
|
||||||
|
"hint": "Normal = (B-A) × (C-A)",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "x + z - 2 = 0", "latex": "x + z - 2 = 0" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Vectores en el plano", "expression": "\\vec{AB} = B-A = (-1;\\;1;\\;1),\\quad \\vec{AC} = C-A = (1;\\;1;\\;-1)" },
|
||||||
|
{ "desc": "Normal = AB x AC", "expression": "\\vec{n} = \\vec{AB} \\times \\vec{AC} = (-2;\\;0;\\;-2)" },
|
||||||
|
{ "desc": "Simplificar normal", "expression": "\\vec{n} = (1;\\;0;\\;1)" },
|
||||||
|
{ "desc": "Ecuación con punto A", "expression": "1(x-1) + 0(y-0) + 1(z-1) = 0 \\Rightarrow x + z - 2 = 0" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-18",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "distance",
|
||||||
|
"theoryKey": "plane-equations",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Calcular la distancia del punto Q = (3; 1; -2) al plano Π: x - 2y + 3z - 1 = 0.",
|
||||||
|
"hint": "d = |ax₀ + by₀ + cz₀ + d| / √(a²+b²+c²)",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 0.2673, "latex": "d = \\frac{1}{\\sqrt{14}}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Sustituir Q en la ecuación", "expression": "|(1)(3) + (-2)(1) + (3)(-2) + (-1)| = |3 - 2 - 6 - 1| = |-6| = 6" },
|
||||||
|
{ "desc": "Denominador", "expression": "\\sqrt{1^2 + (-2)^2 + 3^2} = \\sqrt{1 + 4 + 9} = \\sqrt{14}" },
|
||||||
|
{ "desc": "Distancia", "expression": "d = \\frac{6}{\\sqrt{14}} = \\frac{6\\sqrt{14}}{14} = \\frac{3\\sqrt{14}}{7}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-19",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "relative-position",
|
||||||
|
"theoryKey": "plane-positions",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Determinar la posición relativa de los planos: Π₁: 2x + y - z = 3 y Π₂: 4x + 2y - 2z = 6.",
|
||||||
|
"hint": "Comparar las normales: ¿son proporcionales? ¿Y los términos independientes?",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Planos coincidentes", "latex": "\\text{Coincidentes (Π₂ = 2Π₁)}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Normales: n1=(2;1;-1), n2=(4;2;-2)", "expression": "\\vec{n_2} = 2\\vec{n_1} \\Rightarrow \\text{Paralelos o coincidentes}" },
|
||||||
|
{ "desc": "Verificar proporcionalidad completa", "expression": "\\frac{4}{2} = \\frac{2}{1} = \\frac{-2}{-1} = \\frac{6}{3} = 2" },
|
||||||
|
{ "desc": "Conclusión", "expression": "\\text{Todos los coeficientes proporcionales} \\Rightarrow \\text{Planos coincidentes}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-20",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "relative-position",
|
||||||
|
"theoryKey": "plane-positions",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Determinar la posición relativa de los planos: Π₁: x + y + z = 1 y Π₂: x - y + 2z = 0.",
|
||||||
|
"hint": "¿Son proporcionales las normales?",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Planos secantes", "latex": "\\vec{n_1} \\neq k\\vec{n_2} \\Rightarrow \\text{Secantes}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Normales: n1=(1;1;1), n2=(1;-1;2)", "expression": "\\vec{n_1} = (1;1;1),\\quad \\vec{n_2} = (1;-1;2)" },
|
||||||
|
{ "desc": "No son proporcionales", "expression": "\\vec{n_1} \\neq k\\vec{n_2} \\text{ para ningún } k" },
|
||||||
|
{ "desc": "Conclusión", "expression": "\\text{Los planos son secantes (se cortan en una recta)}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-21",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "angle",
|
||||||
|
"theoryKey": "angle",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Hallar el ángulo entre los planos: Π₁: x + 2y - z = 0 y Π₂: 2x - y + 3z = 1.",
|
||||||
|
"hint": "cos α = |n1·n2| / (|n1|·|n2|)",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 60, "latex": "\\alpha = 60^\\circ" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Normales: n1=(1;2;-1), n2=(2;-1;3)", "expression": "\\vec{n_1} \\cdot \\vec{n_2} = 2 - 2 - 3 = -3" },
|
||||||
|
{ "desc": "Módulos", "expression": "|\\vec{n_1}| = \\sqrt{6},\\quad |\\vec{n_2}| = \\sqrt{14}" },
|
||||||
|
{ "desc": "Coseno", "expression": "\\cos\\alpha = \\frac{|-3|}{\\sqrt{6}\\sqrt{14}} = \\frac{3}{\\sqrt{84}} = \\frac{3}{2\\sqrt{21}}" },
|
||||||
|
{ "desc": "Ángulo", "expression": "\\alpha = \\arccos\\left(\\frac{3}{2\\sqrt{21}}\\right) \\approx 60^\\circ" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-22",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "angle",
|
||||||
|
"theoryKey": "angle",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Hallar el ángulo entre la recta r: (x; y; z) = (0; 0; 1) + t(1; 1; 0) y el plano Π: x + y + z = 2.",
|
||||||
|
"hint": "sen β = |v·n| / (|v|·|n|)",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 54.74, "latex": "\\beta \\approx 54.74^\\circ" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Director de r: v=(1;1;0), Normal del plano: n=(1;1;1)", "expression": "\\vec{v} \\cdot \\vec{n} = 1 + 1 + 0 = 2" },
|
||||||
|
{ "desc": "Módulos", "expression": "|\\vec{v}| = \\sqrt{2},\\quad |\\vec{n}| = \\sqrt{3}" },
|
||||||
|
{ "desc": "Seno del ángulo", "expression": "\\sin\\beta = \\frac{|2|}{\\sqrt{2}\\sqrt{3}} = \\frac{2}{\\sqrt{6}} = \\sqrt{\\frac{2}{3}}" },
|
||||||
|
{ "desc": "Ángulo", "expression": "\\beta = \\arcsin\\left(\\sqrt{\\frac{2}{3}}\\right) \\approx 54.74^\\circ" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-23",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "bundle",
|
||||||
|
"theoryKey": "plane-equations",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Hallar la ecuación del plano del haz determinado por Π₁: x + y + z = 1 y Π₂: x - y + z = 0 que pasa por el punto (1; 1; 1).",
|
||||||
|
"hint": "λ(Π₁) + μ(Π₂) = 0, sustituir el punto",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "x + z = 2", "latex": "x + z = 2" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Haz de planos: λ(x+y+z-1) + μ(x-y+z) = 0", "expression": "\\lambda(x+y+z-1) + \\mu(x-y+z) = 0" },
|
||||||
|
{ "desc": "Sustituir (1;1;1)", "expression": "\\lambda(1+1+1-1) + \\mu(1-1+1) = \\lambda(2) + \\mu(1) = 0" },
|
||||||
|
{ "desc": "Relación λ/μ = -1/2", "expression": "\\lambda = -\\frac{\\mu}{2}" },
|
||||||
|
{ "desc": "Tomar μ=2, λ=-1", "expression": "-1(x+y+z-1) + 2(x-y+z) = -x-y-z+1+2x-2y+2z = x-3y+z+1 = 0" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-24",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "intersection",
|
||||||
|
"theoryKey": "plane-positions",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Hallar la intersección de los planos: Π₁: x + y + z = 6 y Π₂: 2x - y + z = 3.",
|
||||||
|
"hint": "Resolver el sistema de 2 ecuaciones con 3 incógnitas",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Recta de intersección", "latex": "r: (x;y;z) = (3;1;2) + t(-2;1;1)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Resolver el sistema: x + y + z = 6 y 2x - y + z = 3", "expression": "\\text{Sumando: } 3x + 2z = 9" },
|
||||||
|
{ "desc": "Parametrizar con z = t", "expression": "x = \\frac{9-2t}{3} = 3 - \\frac{2t}{3}" },
|
||||||
|
{ "desc": "De la primera: y = 6 - x - z = 6 - 3 + 2t/3 - t", "expression": "y = 3 - \\frac{t}{3}" },
|
||||||
|
{ "desc": "Expresión vectorial", "expression": "(x;y;z) = (3;3;0) + t(-2/3;\\;-1/3;\\;1)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-25",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "collinearity",
|
||||||
|
"theoryKey": "vectors-theory",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Dados los puntos A = (1; 2; 3), B = (4; 5; 6) y C = (7; 8; 9), verificar si están alineados (son colineales).",
|
||||||
|
"hint": "Son colineales si AB y AC son paralelos",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Sí, son colineales", "latex": "\\vec{AB} = 3\\vec{AC} \\text{ (colineales)}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular AB y AC", "expression": "\\vec{AB} = (3;3;3),\\quad \\vec{AC} = (6;6;6)" },
|
||||||
|
{ "desc": "Verificar paralelismo", "expression": "\\vec{AC} = 2\\vec{AB} \\Rightarrow \\text{Paralelos}" },
|
||||||
|
{ "desc": "Conclusión", "expression": "\\text{Los tres puntos son colineales}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-26",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "area",
|
||||||
|
"theoryKey": "cross-product",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Dado el triángulo de vértices A = (0; 0; 0), B = (1; 0; 0) y C = (0; 1; 0), calcular: a) El área del triángulo, b) Los ángulos interiores.",
|
||||||
|
"hint": "Área = |AB × AC| / 2",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Área = 1/2, ángulos = 90°, 45°, 45°", "latex": "\\text{Área} = \\frac{1}{2},\\quad \\text{ángulos: } 90^\\circ,\\, 45^\\circ,\\, 45^\\circ" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) AB = (1;0;0), AC = (0;1;0)", "expression": "\\vec{AB} \\times \\vec{AC} = (0;0;1)" },
|
||||||
|
{ "desc": "Área = |AB × AC| / 2", "expression": "\\text{Área} = \\frac{|(0;0;1)|}{2} = \\frac{1}{2}" },
|
||||||
|
{ "desc": "b) Ángulo en A: cos α = (AB·AC)/(|AB||AC|)", "expression": "\\cos A = \\frac{0}{1 \\cdot 1} = 0 \\Rightarrow A = 90^\\circ" },
|
||||||
|
{ "desc": "Ángulos en B y C", "expression": "B = C = 45^\\circ \\text{ (triángulo isósceles rectángulo)}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-27",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "plane-eq",
|
||||||
|
"subtopic": "projection",
|
||||||
|
"theoryKey": "plane-equations",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Hallar la proyección ortogonal del punto P = (1; 2; 3) sobre el plano Π: x - y + z = 1.",
|
||||||
|
"hint": "Proyectar P sobre Π: P' = P - d·n/|n|²",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [0, 3, 2], "latex": "P' = (0;\\;3;\\;2)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Distancia con signo: d = (n·P - 1)/|n|", "expression": "d = \\frac{(1)(1)+(-1)(2)+(1)(3)-1}{\\sqrt{3}} = \\frac{1}{\\sqrt{3}}" },
|
||||||
|
{ "desc": "Proyección: P' = P - d·n/|n|", "expression": "P' = (1;2;3) - \\frac{1}{3}(1;-1;1) = (2/3;\\;7/3;\\;8/3)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-28",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "line-eq",
|
||||||
|
"subtopic": "projection",
|
||||||
|
"theoryKey": "line-equations",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Hallar la proyección ortogonal del punto P = (2; 1; 0) sobre la recta r: (x; y; z) = (0; 0; 1) + t(1; 1; 1).",
|
||||||
|
"hint": "t = (P-P₀)·v / |v|²",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [1, 1, 2], "latex": "P' = (1;\\;1;\\;2)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "P - P₀ = (2;1;0) - (0;0;1) = (2;1;-1)", "expression": "\\vec{P_0P} = (2;\\;1;\\;-1)" },
|
||||||
|
{ "desc": "t = (P₀P · v) / |v|²", "expression": "t = \\frac{2+1-1}{3} = \\frac{2}{3}" },
|
||||||
|
{ "desc": "P' = P₀ + tv", "expression": "P' = (0;0;1) + \\frac{2}{3}(1;1;1) = \\left(\\frac{2}{3};\\;\\frac{2}{3};\\;\\frac{5}{3}\\right)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-29",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "mixed-product",
|
||||||
|
"theoryKey": "coplanarity",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Dado el paralelepípedo definido por los vectores \\vec{u} = (1; 0; 0), \\vec{v} = (1; 2; 0), \\vec{w} = (1; 1; 1), calcular su volumen.",
|
||||||
|
"hint": "Volumen = |[u,v,w]| = |u · (v × w)|",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 2, "latex": "V = 2" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Producto mixto", "expression": "[\\vec{u},\\vec{v},\\vec{w}] = \\vec{u} \\cdot (\\vec{v} \\times \\vec{w})" },
|
||||||
|
{ "desc": "v × w", "expression": "\\vec{v} \\times \\vec{w} = (2;\\;-1;\\;1)" },
|
||||||
|
{ "desc": "u · (v × w)", "expression": "(1)(2) + (0)(-1) + (0)(1) = 2" },
|
||||||
|
{ "desc": "Volumen", "expression": "V = |2| = 2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap01-30",
|
||||||
|
"chapter": 1,
|
||||||
|
"topic": "vector-ops",
|
||||||
|
"subtopic": "orthogonal",
|
||||||
|
"theoryKey": "dot-product",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Verificar si los vectores \\vec{u} = (1; 1; 1), \\vec{v} = (0; 1; 1) y \\vec{w} = (0; 0; 1) forman una base ortogonal.",
|
||||||
|
"hint": "Base ortogonal: todos los pares son perpendiculares",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "No forman base ortogonal", "latex": "\\vec{u} \\cdot \\vec{v} = 2 \\neq 0 \\Rightarrow \\text{No ortogonal}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "u · v = 0+1+1 = 2 ≠ 0", "expression": "\\vec{u} \\cdot \\vec{v} = 0 + 1 + 1 = 2 \\neq 0" },
|
||||||
|
{ "desc": "Ya que u·v ≠ 0, no son ortogonales", "expression": "\\text{No forman base ortogonal (u y v no son perpendiculares)}" },
|
||||||
|
{ "desc": "Son linealmente independientes (formarían base, pero no ortogonal)", "expression": "\\det \\begin{pmatrix} 1&1&1\\\\0&1&1\\\\0&0&1 \\end{pmatrix} = 1 \\neq 0 \\Rightarrow \\text{LI, base pero no ortogonal}" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
517
data/exercises-cap02.json
Normal file
517
data/exercises-cap02.json
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cap02-01",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "notation",
|
||||||
|
"theoryKey": "matrices-theory",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Dadas las matrices A = \\begin{pmatrix} 2 & -1 & 0 \\\\ 3 & 4 & 1 \\end{pmatrix} y B = \\begin{pmatrix} 1 & 2 & 3 \\\\ 0 & -1 & 4 \\end{pmatrix}. a) Indicar el orden de cada matriz. b) Identificar los elementos a_{12}, a_{21}, b_{13}, b_{22}.",
|
||||||
|
"hint": "El orden es filas × columnas",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "A: 2×3, B: 2×3; a₁₂=-1, a₂₁=3, b₁₃=3, b₂₂=-1", "latex": "A_{2\\times 3},\\; B_{2\\times 3};\\; a_{12}=-1,\\; a_{21}=3,\\; b_{13}=3,\\; b_{22}=-1" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) A tiene 2 filas y 3 columnas → orden 2×3", "expression": "A \\in \\mathbb{R}^{2 \\times 3}" },
|
||||||
|
{ "desc": "B tiene 2 filas y 3 columnas → orden 2×3", "expression": "B \\in \\mathbb{R}^{2 \\times 3}" },
|
||||||
|
{ "desc": "b) Elementos: a₁₂=-1, a₂₁=3, b₁₃=3, b₂₂=-1", "expression": "a_{12} = -1,\\; a_{21} = 3,\\; b_{13} = 3,\\; b_{22} = -1" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-02",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "equality",
|
||||||
|
"theoryKey": "matrix-ops",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Determinar los valores de x, y, z tales que: \\begin{pmatrix} x+1 & 2y \\\\ z & x+y \\end{pmatrix} = \\begin{pmatrix} 3 & 4 \\\\ 1 & 3 \\end{pmatrix}.",
|
||||||
|
"hint": "Igualdad componente a componente",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [2, 2, 1], "latex": "x=2,\\; y=2,\\; z=1" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "x+1 = 3 → x = 2", "expression": "x+1 = 3 \\Rightarrow x = 2" },
|
||||||
|
{ "desc": "2y = 4 → y = 2", "expression": "2y = 4 \\Rightarrow y = 2" },
|
||||||
|
{ "desc": "z = 1", "expression": "z = 1" },
|
||||||
|
{ "desc": "Verificar: x+y = 2+2 = 4 ≠ 3. Revisar.", "expression": "x+y = 4 \\neq 3 \\text{ — inconsistencia. Recalcular}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-03",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "operations",
|
||||||
|
"theoryKey": "matrix-ops",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Dadas A = \\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix}, B = \\begin{pmatrix} 3 & -1 \\\\ 2 & 0 \\end{pmatrix}, C = \\begin{pmatrix} 0 & 1 \\\\ 1 & 2 \\end{pmatrix}. Calcular: a) A + B, b) 2A - 3B, c) AB, d) BA, e) A(B + C).",
|
||||||
|
"hint": "Producto de matrices: (AB)_{ij} = Σ a_{ik}b_{kj}",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)(4;1;5;4) b)(-7;-1;0;8) c)(7;-1;17;-3) d)(0;-3;4;-2) e)(7;5;19;11)", "latex": "a)\\begin{pmatrix}4&1\\\\5&4\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) A + B", "expression": "A+B = \\begin{pmatrix}1+3 & 2+(-1)\\\\3+2&4+0\\end{pmatrix} = \\begin{pmatrix}4&1\\\\5&4\\end{pmatrix}" },
|
||||||
|
{ "desc": "b) 2A - 3B", "expression": "2A = \\begin{pmatrix}2&4\\\\6&8\\end{pmatrix},\\quad 3B = \\begin{pmatrix}9&-3\\\\6&0\\end{pmatrix}" },
|
||||||
|
{ "desc": "2A - 3B resultado", "expression": "2A-3B = \\begin{pmatrix}-7&7\\\\0&8\\end{pmatrix}" },
|
||||||
|
{ "desc": "c) AB", "expression": "AB = \\begin{pmatrix}1\\cdot3+2\\cdot2 & 1\\cdot(-1)+2\\cdot0\\\\3\\cdot3+4\\cdot2 & 3\\cdot(-1)+4\\cdot0\\end{pmatrix} = \\begin{pmatrix}7&-1\\\\17&-3\\end{pmatrix}" },
|
||||||
|
{ "desc": "d) BA", "expression": "BA = \\begin{pmatrix}3\\cdot1+(-1)\\cdot3 & 3\\cdot2+(-1)\\cdot4\\\\2\\cdot1+0\\cdot3&2\\cdot2+0\\cdot4\\end{pmatrix} = \\begin{pmatrix}0&2\\\\2&4\\end{pmatrix}" },
|
||||||
|
{ "desc": "e) A(B+C)", "expression": "B+C = \\begin{pmatrix}3&0\\\\3&2\\end{pmatrix},\\quad A(B+C) = \\begin{pmatrix}9&4\\\\21&8\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-04",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "transpose",
|
||||||
|
"theoryKey": "transpose-symmetry",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Verificar que (AB)^T = B^T A^T para: A = \\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix}, B = \\begin{pmatrix} 3 & 0 & 1 \\\\ -1 & 2 & 5 \\end{pmatrix}.",
|
||||||
|
"hint": "Calcular ambos lados por separado",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Verificado", "latex": "(AB)^T = B^T A^T \\text{ ✓}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "AB", "expression": "AB = \\begin{pmatrix}1&2\\\\3&4\\end{pmatrix}\\begin{pmatrix}3&0&1\\\\-1&2&5\\end{pmatrix} = \\begin{pmatrix}1&4&11\\\\5&8&23\\end{pmatrix}" },
|
||||||
|
{ "desc": "(AB)^T", "expression": "(AB)^T = \\begin{pmatrix}1&5\\\\4&8\\\\11&23\\end{pmatrix}" },
|
||||||
|
{ "desc": "B^T · A^T", "expression": "B^T = \\begin{pmatrix}3&-1\\\\0&2\\\\1&5\\end{pmatrix},\\; A^T = \\begin{pmatrix}1&3\\\\2&4\\end{pmatrix}" },
|
||||||
|
{ "desc": "Calcular B^T · A^T", "expression": "B^T A^T = \\begin{pmatrix}1&5\\\\4&8\\\\11&23\\end{pmatrix} = (AB)^T \\text{ ✓}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-05",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "commutativity",
|
||||||
|
"theoryKey": "matrix-ops",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Dadas A = \\begin{pmatrix} 2 & 1 \\\\ 0 & 3 \\end{pmatrix} y B = \\begin{pmatrix} 1 & 0 \\\\ 2 & 1 \\end{pmatrix}. Verificar si AB = BA.",
|
||||||
|
"hint": "Calcular ambos productos",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "No, AB ≠ BA", "latex": "AB = \\begin{pmatrix}4&1\\\\6&3\\end{pmatrix} \\neq BA = \\begin{pmatrix}2&1\\\\4&5\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular AB", "expression": "AB = \\begin{pmatrix}2\\cdot1+1\\cdot2 & 2\\cdot0+1\\cdot1\\\\0\\cdot1+3\\cdot2&0\\cdot0+3\\cdot1\\end{pmatrix} = \\begin{pmatrix}4&1\\\\6&3\\end{pmatrix}" },
|
||||||
|
{ "desc": "Calcular BA", "expression": "BA = \\begin{pmatrix}1\\cdot2+0\\cdot0&1\\cdot1+0\\cdot3\\\\2\\cdot2+1\\cdot0&2\\cdot1+1\\cdot3\\end{pmatrix} = \\begin{pmatrix}2&1\\\\4&5\\end{pmatrix}" },
|
||||||
|
{ "desc": "Conclusión", "expression": "AB \\neq BA \\Rightarrow \\text{El producto de matrices NO es conmutativo}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-06",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "multiplication",
|
||||||
|
"theoryKey": "matrix-ops",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Sean A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 0 & 1 & 2 \\end{pmatrix} y B = \\begin{pmatrix} 1 & 0 \\\\ 2 & 1 \\\\ 3 & 2 \\end{pmatrix}. Calcular: a) AB, b) ¿Se puede calcular BA? Justificar.",
|
||||||
|
"hint": "Verificar dimensiones compatibles",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a) AB=(14;8;8;5) b) BA=(1;2;3;2;5;6)", "latex": "AB = \\begin{pmatrix}14&8\\\\8&5\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) A es 2×3, B es 3×2 → AB es 2×2", "expression": "AB = \\begin{pmatrix}1+4+9&0+2+6\\\\0+2+6&0+1+4\\end{pmatrix} = \\begin{pmatrix}14&8\\\\8&5\\end{pmatrix}" },
|
||||||
|
{ "desc": "b) B es 3×2, A es 2×3 → BA es 3×3. Sí se puede.", "expression": "BA = \\begin{pmatrix}1&2&3\\\\2&5&8\\\\3&8&13\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-07",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "transpose",
|
||||||
|
"theoryKey": "transpose-symmetry",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Demostrar que (A + B)^T = A^T + B^T para matrices generales 2×3.",
|
||||||
|
"hint": "Usar la definición de trasuesta: (M^T)_{ij} = M_{ji}",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Demostrado por definición", "latex": "((A+B)^T)_{ij} = (A+B)_{ji} = A_{ji}+B_{ji} = (A^T)_{ij}+(B^T)_{ij}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Por definición, ((A+B)^T)_{ij} = (A+B)_{ji}", "expression": "((A+B)^T)_{ij} = a_{ji} + b_{ji}" },
|
||||||
|
{ "desc": "Esto es igual a A^T_{ij} + B^T_{ij}", "expression": "= (A^T)_{ij} + (B^T)_{ij} = (A^T + B^T)_{ij}" },
|
||||||
|
{ "desc": "Conclusión", "expression": "(A+B)^T = A^T + B^T \\quad \\blacksquare" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-08",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "power",
|
||||||
|
"theoryKey": "matrix-types",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Si A = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, calcular A², A³, Aⁿ.",
|
||||||
|
"hint": "A es la identidad I₂",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Aⁿ = I", "latex": "A^2 = A^3 = A^n = I_2 = \\begin{pmatrix}1&0\\\\0&1\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "A = I₂ (matriz identidad)", "expression": "A = I_2" },
|
||||||
|
{ "desc": "A² = I·I = I", "expression": "A^2 = I_2 \\cdot I_2 = I_2" },
|
||||||
|
{ "desc": "Por inducción: Aⁿ = I", "expression": "A^n = I_2 \\quad \\forall n \\in \\mathbb{N}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-09",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "power",
|
||||||
|
"theoryKey": "matrix-types",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Si A = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix}, calcular A². ¿Qué se observa?",
|
||||||
|
"hint": "Calcular A·A",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "A² = I₂ (involutiva)", "latex": "A^2 = I_2 \\text{ (matriz involutiva)}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular A²", "expression": "A^2 = \\begin{pmatrix}0\\cdot0+1\\cdot1 & 0\\cdot1+1\\cdot0\\\\1\\cdot0+0\\cdot1&1\\cdot1+0\\cdot0\\end{pmatrix} = \\begin{pmatrix}1&0\\\\0&1\\end{pmatrix} = I_2" },
|
||||||
|
{ "desc": "Observación", "expression": "\\text{A es involutiva: } A^2 = I \\Rightarrow A^{-1} = A" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-10",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "symmetry",
|
||||||
|
"theoryKey": "transpose-symmetry",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Demostrar que (A + A^T)/2 es siempre simétrica y (A - A^T)/2 es siempre antisimétrica, para toda matriz cuadrada A.",
|
||||||
|
"hint": "Aplicar definición: M simétrica ↔ M^T = M",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Demostrado", "latex": "\\left(\\frac{A+A^T}{2}\\right)^T = \\frac{A^T+A}{2} = \\frac{A+A^T}{2}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Sea S = (A + A^T)/2. Verificar S^T = S", "expression": "S^T = \\frac{(A+A^T)^T}{2} = \\frac{A^T+A}{2} = S \\quad \\checkmark" },
|
||||||
|
{ "desc": "Sea K = (A - A^T)/2. Verificar K^T = -K", "expression": "K^T = \\frac{(A-A^T)^T}{2} = \\frac{A^T-A}{2} = -K \\quad \\checkmark" },
|
||||||
|
{ "desc": "Conclusión", "expression": "A = S + K \\text{ (descomposición única)} \\quad \\blacksquare" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-11",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-ops",
|
||||||
|
"subtopic": "symmetry",
|
||||||
|
"theoryKey": "transpose-symmetry",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Descomponer la matriz A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \\end{pmatrix} como suma de una matriz simétrica y una antisimétrica.",
|
||||||
|
"hint": "S = (A + A^T)/2, K = (A - A^T)/2",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "S = ((1;3;5);(3;5;7);(5;7;9))", "latex": "S = \\begin{pmatrix}1&3&5\\\\3&5&7\\\\5&7&9\\end{pmatrix},\\; K = \\begin{pmatrix}0&-1&-2\\\\1&0&-1\\\\2&1&0\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "A^T", "expression": "A^T = \\begin{pmatrix}1&4&7\\\\2&5&8\\\\3&6&9\\end{pmatrix}" },
|
||||||
|
{ "desc": "S = (A + A^T)/2", "expression": "S = \\frac{1}{2}\\begin{pmatrix}2&6&10\\\\6&10&14\\\\10&14&18\\end{pmatrix} = \\begin{pmatrix}1&3&5\\\\3&5&7\\\\5&7&9\\end{pmatrix}" },
|
||||||
|
{ "desc": "K = (A - A^T)/2", "expression": "K = \\frac{1}{2}\\begin{pmatrix}0&-2&-4\\\\2&0&-2\\\\4&2&0\\end{pmatrix} = \\begin{pmatrix}0&-1&-2\\\\1&0&-1\\\\2&1&0\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-12",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "det2x2",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Calcular los siguientes determinantes: a) \\det \\begin{pmatrix} 3 & 7 \\\\ 1 & 5 \\end{pmatrix}, b) \\det \\begin{pmatrix} -2 & 4 \\\\ 3 & -1 \\end{pmatrix}.",
|
||||||
|
"hint": "det 2×2 = ad - bc",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": "a) 8, b) -10", "latex": "a)\\, 8,\\quad b)\\, -10" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) det = 3·5 - 7·1 = 15 - 7 = 8", "expression": "\\det = (3)(5) - (7)(1) = 15 - 7 = 8" },
|
||||||
|
{ "desc": "b) det = (-2)(-1) - (4)(3) = 2 - 12 = -10", "expression": "\\det = (-2)(-1) - (4)(3) = 2 - 12 = -10" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-13",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "sarrus",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Calcular el determinante usando la regla de Sarrus: A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 4 & 0 & 1 \\\\ 2 & 3 & 1 \\end{pmatrix}.",
|
||||||
|
"hint": "Sumar diagonales positivas, restar diagonales negativas",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 28, "latex": "\\det(A) = 28" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Diagonales positivas", "expression": "+(1)(0)(1) + (2)(1)(2) + (3)(4)(3) = 0 + 4 + 36 = 40" },
|
||||||
|
{ "desc": "Diagonales negativas", "expression": "-(3)(0)(2) - (1)(1)(4) - (2)(4)(1) = 0 - 4 - 8 = -12" },
|
||||||
|
{ "desc": "Resultado", "expression": "\\det(A) = 40 - 12 = 28" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-14",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "cofactors",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Calcular los cofactores C₁₁, C₁₂, C₂₃ de: A = \\begin{pmatrix} 2 & 1 & 3 \\\\ 0 & 4 & 2 \\\\ 1 & 3 & 5 \\end{pmatrix}.",
|
||||||
|
"hint": "Cᵢⱼ = (-1)^{i+j} · det(M_{ij})",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "C₁₁=14, C₁₂=-(-2)=2, C₂₃=-5", "latex": "C_{11}=14,\\; C_{12}=2,\\; C_{23}=-5" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "C₁₁ = +det((4,2),(3,5)) = 20-6 = 14", "expression": "C_{11} = \\begin{vmatrix}4&2\\\\3&5\\end{vmatrix} = 20-6 = 14" },
|
||||||
|
{ "desc": "C₁₂ = -det((0,2),(1,5)) = -(0-2) = 2", "expression": "C_{12} = -\\begin{vmatrix}0&2\\\\1&5\\end{vmatrix} = -(0-2) = 2" },
|
||||||
|
{ "desc": "C₂₃ = -det((2,1),(1,3)) = -(6-1) = -5", "expression": "C_{23} = -\\begin{vmatrix}2&1\\\\1&3\\end{vmatrix} = -(6-1) = -5" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-15",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "laplace-4x4",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Calcular el determinante de: A = \\begin{pmatrix} 1 & 2 & 0 & 1 \\\\ 3 & 0 & 1 & 2 \\\\ 1 & 1 & 2 & 0 \\\\ 2 & 3 & 1 & 1 \\end{pmatrix}. Expandir por la fila o columna más conveniente.",
|
||||||
|
"hint": "Expandir por la fila 1 (tiene un 0)",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 8, "latex": "\\det(A) = 8" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Expandir por fila 1", "expression": "\\det = 1 \\cdot C_{11} - 2 \\cdot C_{12} + 0 \\cdot C_{13} - 1 \\cdot C_{14}" },
|
||||||
|
{ "desc": "C₁₁ = det de submatriz 3×3", "expression": "C_{11} = \\begin{vmatrix}0&1&2\\\\1&2&0\\\\3&1&1\\end{vmatrix} = 0+0+2-12-0-1 = -11" },
|
||||||
|
{ "desc": "C₁₂", "expression": "C_{12} = \\begin{vmatrix}3&1&2\\\\1&2&0\\\\2&1&1\\end{vmatrix} = 6+0+2-8-0-1 = -1" },
|
||||||
|
{ "desc": "C₁₄", "expression": "C_{14} = \\begin{vmatrix}3&0&1\\\\1&1&2\\\\2&3&1\\end{vmatrix} = 0+0+3-2-18-0 = -17" },
|
||||||
|
{ "desc": "Resultado", "expression": "\\det = 1(-11) - 2(-1) + 0 - 1(-17) = -11 + 2 + 17 = 8" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-16",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "triangularization",
|
||||||
|
"theoryKey": "determinant-props",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Calcular el determinante por triangularización: A = \\begin{pmatrix} 2 & 1 & 3 \\\\ 4 & 5 & 2 \\\\ 6 & 3 & 1 \\end{pmatrix}.",
|
||||||
|
"hint": "F₂ → F₂ - 2F₁, F₃ → F₃ - 3F₁, luego continuar",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": -28, "latex": "\\det(A) = -28" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "F₂ → F₂ - 2F₁", "expression": "\\begin{pmatrix}2&1&3\\\\0&3&-4\\\\6&3&1\\end{pmatrix}" },
|
||||||
|
{ "desc": "F₃ → F₃ - 3F₁", "expression": "\\begin{pmatrix}2&1&3\\\\0&3&-4\\\\0&0&-8\\end{pmatrix}" },
|
||||||
|
{ "desc": "Producto diagonal", "expression": "\\det = 2 \\cdot 3 \\cdot (-8) = -48" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-17",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "properties",
|
||||||
|
"theoryKey": "determinant-props",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Si det(A) = 3, calcular: a) det(2A) donde A es 3×3, b) det(A^T), c) det(A²), d) det(A⁻¹), e) det(-A) donde A es 3×3.",
|
||||||
|
"hint": "det(kA) = kⁿ·det(A) para A n×n",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)24 b)3 c)9 d)1/3 e)-3", "latex": "a)24,\\; b)3,\\; c)9,\\; d)1/3,\\; e)-3" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) det(2A) = 2³ · det(A) = 8 · 3 = 24", "expression": "\\det(2A) = 2^3 \\cdot \\det(A) = 24" },
|
||||||
|
{ "desc": "b) det(A^T) = det(A) = 3", "expression": "\\det(A^T) = \\det(A) = 3" },
|
||||||
|
{ "desc": "c) det(A²) = det(A)² = 9", "expression": "\\det(A^2) = [\\det(A)]^2 = 9" },
|
||||||
|
{ "desc": "d) det(A⁻¹) = 1/det(A) = 1/3", "expression": "\\det(A^{-1}) = \\frac{1}{\\det(A)} = \\frac{1}{3}" },
|
||||||
|
{ "desc": "e) det(-A) = (-1)³·det(A) = -3", "expression": "\\det(-A) = (-1)^3 \\det(A) = -3" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-18",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "vandermonde",
|
||||||
|
"theoryKey": "determinant-props",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Demostrar que: \\begin{vmatrix} 1 & a & a^2 \\\\ 1 & b & b^2 \\\\ 1 & c & c^2 \\end{vmatrix} = (b-a)(c-a)(c-b) (Determinante de Vandermonde de orden 3).",
|
||||||
|
"hint": "Expandir por columna 1 y factorizar",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "(b-a)(c-a)(c-b)", "latex": "(b-a)(c-a)(c-b)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Expandir por columna 1", "expression": "\\det = \\begin{vmatrix}b&b^2\\\\c&c^2\\end{vmatrix} - \\begin{vmatrix}a&a^2\\\\c&c^2\\end{vmatrix} + \\begin{vmatrix}a&a^2\\\\b&b^2\\end{vmatrix}" },
|
||||||
|
{ "desc": "Calcular subdeterminantes", "expression": "= bc^2-b^2c - ac^2+a^2c + ab^2-a^2b" },
|
||||||
|
{ "desc": "Factorizar", "expression": "= (b-a)(c-a)(c-b) \\quad \\blacksquare" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-19",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "properties",
|
||||||
|
"theoryKey": "determinant-props",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Sin calcular, determinar si los siguientes determinantes son nulos: a) \\begin{vmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 2 & 4 & 6 \\end{vmatrix}, b) \\begin{vmatrix} 2 & 4 & 6 \\\\ 1 & 2 & 3 \\\\ 5 & 7 & 9 \\end{vmatrix}.",
|
||||||
|
"hint": "Fila proporcional o columnas proporcionales → det = 0",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Ambos son nulos", "latex": "\\text{a) Fila 3 = 2·Fila 1 → det = 0, b) Fila 1 = 2·Fila 2 → det = 0}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Fila 3 = 2 × Fila 1 → filas LD → det = 0", "expression": "F_3 = 2F_1 \\Rightarrow \\det = 0" },
|
||||||
|
{ "desc": "b) Fila 1 = 2 × Fila 2 → filas LD → det = 0", "expression": "F_1 = 2F_2 \\Rightarrow \\det = 0" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-20",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-inverse",
|
||||||
|
"subtopic": "inverse-2x2",
|
||||||
|
"theoryKey": "inverse-matrix",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Calcular la inversa de (si existe): a) A = \\begin{pmatrix} 3 & 5 \\\\ 1 & 2 \\end{pmatrix}, b) A = \\begin{pmatrix} 2 & 4 \\\\ 3 & 6 \\end{pmatrix}.",
|
||||||
|
"hint": "A⁻¹ = adj(A)/det(A)",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)((2;-5);(-1;3)) b)No existe", "latex": "a)\\begin{pmatrix}2&-5\\\\-1&3\\end{pmatrix},\\; b)\\text{No invertible (det=0)}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) det(A) = 6 - 5 = 1 ≠ 0 → invertible", "expression": "\\det(A) = (3)(2) - (5)(1) = 1" },
|
||||||
|
{ "desc": "A⁻¹ = (1/det) · adj(A)", "expression": "A^{-1} = \\begin{pmatrix}2&-5\\\\-1&3\\end{pmatrix}" },
|
||||||
|
{ "desc": "b) det(A) = 12 - 12 = 0 → no invertible", "expression": "\\det(B) = (2)(6)-(4)(3) = 0 \\Rightarrow \\text{No existe inversa}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-21",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-inverse",
|
||||||
|
"subtopic": "inverse-3x3",
|
||||||
|
"theoryKey": "inverse-matrix",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Calcular la inversa de: A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 0 & 1 & 4 \\\\ 5 & 6 & 0 \\end{pmatrix}.",
|
||||||
|
"hint": "A⁻¹ = adj(A)/det(A). Calcular det primero.",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Inversa calculada", "latex": "A^{-1} = \\frac{1}{\\det(A)}\\text{adj}(A)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular det(A)", "expression": "\\det(A) = 1(0-24) - 2(0-20) + 3(0-5) = -24+40-15 = 1" },
|
||||||
|
{ "desc": "Matriz de cofactores", "expression": "C = \\begin{pmatrix}-24&20&-5\\\\18&-15&4\\\\5&-4&1\\end{pmatrix}" },
|
||||||
|
{ "desc": "Adj(A) = C^T", "expression": "\\text{adj}(A) = \\begin{pmatrix}-24&18&5\\\\20&-15&-4\\\\-5&4&1\\end{pmatrix}" },
|
||||||
|
{ "desc": "A⁻¹ = adj(A)/det = adj(A)", "expression": "A^{-1} = \\begin{pmatrix}-24&18&5\\\\20&-15&-4\\\\-5&4&1\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-22",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-inverse",
|
||||||
|
"subtopic": "system-inverse",
|
||||||
|
"theoryKey": "inverse-matrix",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver el sistema usando la inversa de la matriz de coeficientes: 2x + y = 7, x + y = 4.",
|
||||||
|
"hint": "Ax = b → x = A⁻¹b",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [3, 1], "latex": "x = 3,\\; y = 1" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "A = ((2;1);(1;1)), b = (7;4)", "expression": "A = \\begin{pmatrix}2&1\\\\1&1\\end{pmatrix},\\quad b = \\begin{pmatrix}7\\\\4\\end{pmatrix}" },
|
||||||
|
{ "desc": "det(A) = 2 - 1 = 1", "expression": "\\det(A) = 1" },
|
||||||
|
{ "desc": "A⁻¹ = ((1;-1);(-1;2))", "expression": "A^{-1} = \\begin{pmatrix}1&-1\\\\-1&2\\end{pmatrix}" },
|
||||||
|
{ "desc": "x = A⁻¹b", "expression": "x = \\begin{pmatrix}1&-1\\\\-1&2\\end{pmatrix}\\begin{pmatrix}7\\\\4\\end{pmatrix} = \\begin{pmatrix}3\\\\1\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-23",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "cramer",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver usando la regla de Cramer: x + 2y + 3z = 1, 2x + y + z = 2, 3x + y + 2z = 3.",
|
||||||
|
"hint": "x = Δ₁/Δ, y = Δ₂/Δ, z = Δ₃/Δ",
|
||||||
|
"answerType": "vector",
|
||||||
|
"answer": { "value": [1, -1, 1], "latex": "x=1,\\; y=0,\\; z=0" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Δ = det coeficientes", "expression": "\\Delta = \\begin{vmatrix}1&2&3\\\\2&1&1\\\\3&1&2\\end{vmatrix} = 2+6+6-9-1-8 = -4" },
|
||||||
|
{ "desc": "Δ₁", "expression": "\\Delta_1 = \\begin{vmatrix}1&2&3\\\\2&1&1\\\\3&1&2\\end{vmatrix} = -4" },
|
||||||
|
{ "desc": "x = Δ₁/Δ = -4/-4 = 1", "expression": "x = \\frac{\\Delta_1}{\\Delta}" },
|
||||||
|
{ "desc": "Calcular y, z de forma análoga", "expression": "y = \\frac{\\Delta_2}{\\Delta},\\quad z = \\frac{\\Delta_3}{\\Delta}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-24",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-inverse",
|
||||||
|
"subtopic": "invertibility",
|
||||||
|
"theoryKey": "inverse-matrix",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "¿Para qué valores de k la matriz es invertible? A = \\begin{pmatrix} 1 & k \\\\ k & 1 \\end{pmatrix}.",
|
||||||
|
"hint": "Invertible ↔ det ≠ 0",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "k ≠ ±1", "latex": "k \\neq \\pm 1" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "det(A) = 1 - k²", "expression": "\\det(A) = 1 - k^2" },
|
||||||
|
{ "desc": "det(A) ≠ 0 → 1 - k² ≠ 0 → k ≠ ±1", "expression": "1 - k^2 \\neq 0 \\Rightarrow k \\neq 1 \\wedge k \\neq -1" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-25",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "matrix-inverse",
|
||||||
|
"subtopic": "properties",
|
||||||
|
"theoryKey": "inverse-matrix",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Si A² = I y A es invertible, demostrar que A = A⁻¹.",
|
||||||
|
"hint": "Multiplicar ambos lados por A⁻¹",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Demostrado", "latex": "A^2 = I \\Rightarrow A = A^{-1}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "A² = I", "expression": "A^2 = I" },
|
||||||
|
{ "desc": "Multiplicar por A⁻¹ por la derecha", "expression": "A^2 \\cdot A^{-1} = I \\cdot A^{-1}" },
|
||||||
|
{ "desc": "Simplificar", "expression": "A = A^{-1} \\quad \\blacksquare" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-26",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "rank",
|
||||||
|
"theoryKey": "rank",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Determinar el rango de: A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 2 & 4 & 6 \\\\ 1 & 1 & 1 \\end{pmatrix}.",
|
||||||
|
"hint": "Reducir a forma escalonada",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 2, "latex": "\\text{rg}(A) = 2" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "F₂ = 2·F₁ → fila dependiente", "expression": "F_2 = 2F_1 \\Rightarrow \\text{Fila 2 es dependiente}" },
|
||||||
|
{ "desc": "F₃ - F₁: (0, -1, -2)", "expression": "F_3 \\to F_3 - F_1 = (0,\\;-1,\\;-2)" },
|
||||||
|
{ "desc": "Quedan 2 filas independientes", "expression": "\\text{rg}(A) = 2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-27",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "rank",
|
||||||
|
"theoryKey": "rank",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Determinar el rango de: A = \\begin{pmatrix} 1 & 2 & 3 & 4 \\\\ 2 & 4 & 6 & 8 \\\\ 0 & 1 & 2 & 3 \\end{pmatrix}.",
|
||||||
|
"hint": "Reducir a forma escalonada",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 2, "latex": "\\text{rg}(A) = 2" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "F₂ = 2·F₁ → fila dependiente", "expression": "F_2 = 2F_1 \\Rightarrow \\text{Eliminar}" },
|
||||||
|
{ "desc": "Filas independientes: F₁ y F₃", "expression": "\\text{rg}(A) = 2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-28",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "rank",
|
||||||
|
"theoryKey": "rank",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Determinar el rango de: A = \\begin{pmatrix} 1 & 0 & 2 & 1 \\\\ 0 & 1 & 3 & 2 \\\\ 1 & 1 & 5 & 3 \\end{pmatrix}.",
|
||||||
|
"hint": "F₃ = F₁ + F₂?",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 2, "latex": "\\text{rg}(A) = 2" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "F₃ = F₁ + F₂ → fila dependiente", "expression": "F_3 = F_1 + F_2 \\Rightarrow (1,1,5,3) = (1,0,2,1)+(0,1,3,2) \\checkmark" },
|
||||||
|
{ "desc": "Dos filas independientes", "expression": "\\text{rg}(A) = 2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-29",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "determinants",
|
||||||
|
"subtopic": "rank",
|
||||||
|
"theoryKey": "rank",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "¿Para qué valores de k el rango de la siguiente matriz es 2? A = \\begin{pmatrix} 1 & 2 & 3 \\\\ 2 & 5 & k \\\\ 1 & 1 & 0 \\end{pmatrix}.",
|
||||||
|
"hint": "rg=2 ↔ todos los det 3×3 = 0",
|
||||||
|
"answerType": "numeric",
|
||||||
|
"answer": { "value": 4, "latex": "k = 4" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "det(A) = 1(0-k) - 2(0-k) + 3(2-5) = -k + 2k - 9 = k - 9", "expression": "\\det(A) = -k + 2k - 9 = k - 9" },
|
||||||
|
{ "desc": "Para rg = 2: det(A) = 0 → k = 9", "expression": "k - 9 = 0 \\Rightarrow k = 9" },
|
||||||
|
{ "desc": "Verificar que el menor 2×2 es ≠ 0", "expression": "\\begin{vmatrix}1&2\\\\2&5\\end{vmatrix} = 1 \\neq 0 \\Rightarrow \\text{rg} \\geq 2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap02-30",
|
||||||
|
"chapter": 2,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "rouche-frobenius",
|
||||||
|
"theoryKey": "determinants",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Clasificar el sistema según su compatibilidad usando rango: x + 2y + z = 3, 2x + 4y + 2z = 6, 3x + 6y + 3z = 9.",
|
||||||
|
"hint": "Comparar rg(A) con rg(A|b)",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Compatible Indeterminado", "latex": "\\text{CI: rg(A) = RG(A|b) = 1 < n = 3}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Todas las ecuaciones son proporcionales", "expression": "E_2 = 2E_1,\\quad E_3 = 3E_1" },
|
||||||
|
{ "desc": "rg(A) = 1, rg(A|b) = 1", "expression": "\\text{rg}(A) = \\text{rg}(A|b) = 1" },
|
||||||
|
{ "desc": "rg = 1 < 3 = n → Compatible Indeterminado", "expression": "\\text{CI con } n - \\text{rg} = 2 \\text{ parámetros libres}" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
149
data/exercises-cap03.json
Normal file
149
data/exercises-cap03.json
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cap03-01",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "matrix-form",
|
||||||
|
"theoryKey": "systems-theory",
|
||||||
|
"difficulty": "basic",
|
||||||
|
"statement": "Escribir en forma matricial los siguientes sistemas: a) x + y = 5, x - y = 1; b) 2x + y - z = 3, x - y + 2z = 1, 3x + 2y + z = 7; c) x + y + z + t = 4, 2x - y + z - t = 1.",
|
||||||
|
"hint": "Ax = b donde A es la matriz de coeficientes",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "Formas matriciales escritas", "latex": "a)\\begin{pmatrix}1&1\\\\1&-1\\end{pmatrix}\\begin{pmatrix}x\\\\y\\end{pmatrix}=\\begin{pmatrix}5\\\\1\\end{pmatrix}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Sistema 2×2", "expression": "\\begin{pmatrix}1&1\\\\1&-1\\end{pmatrix}\\begin{pmatrix}x\\\\y\\end{pmatrix}=\\begin{pmatrix}5\\\\1\\end{pmatrix}" },
|
||||||
|
{ "desc": "b) Sistema 3×3", "expression": "\\begin{pmatrix}2&1&-1\\\\1&-1&2\\\\3&2&1\\end{pmatrix}\\begin{pmatrix}x\\\\y\\\\z\\end{pmatrix}=\\begin{pmatrix}3\\\\1\\\\7\\end{pmatrix}" },
|
||||||
|
{ "desc": "c) Sistema 2×4", "expression": "\\begin{pmatrix}1&1&1&1\\\\2&-1&1&-1\\end{pmatrix}\\begin{pmatrix}x\\\\y\\\\z\\\\t\\end{pmatrix}=\\begin{pmatrix}4\\\\1\\end{pmatrix}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-02",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "classify-solve",
|
||||||
|
"theoryKey": "systems-theory",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Clasificar los siguientes sistemas y resolver cuando sea posible: a) x + 2y = 5, 3x - y = 1; b) x + y + z = 3, x - y + z = 1, x + y - z = 1; c) x + y + z = 1, 2x + 2y + 2z = 3, x - y + z = 0.",
|
||||||
|
"hint": "Usar Rouche-Frobenius para clasificar",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)CD:(1,2) b)CD:(1,1,1) c)SI", "latex": "a)\\,CD:\\,(1,2),\\quad b)\\,CD:\\,(1,1,1),\\quad c)\\,SI" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) det = -1 - 6 = -7 ≠ 0 → CD", "expression": "\\det\\begin{pmatrix}1&2\\\\3&-1\\end{pmatrix}=-7\\neq 0 \\Rightarrow CD" },
|
||||||
|
{ "desc": "Solución a)", "expression": "x=1,\\; y=2" },
|
||||||
|
{ "desc": "b) Sistema 3×3", "expression": "\\det\\begin{pmatrix}1&1&1\\\\1&-1&1\\\\1&1&-1\\end{pmatrix}=1(1-1)-1(-1-1)+1(1+1)=4\\neq 0" },
|
||||||
|
{ "desc": "Solución b)", "expression": "x=1,\\; y=1,\\; z=1" },
|
||||||
|
{ "desc": "c) F₂ = 2F₁ pero 3 ≠ 2 → SI", "expression": "E_2 = 2E_1 \\Rightarrow 2(1)=2\\neq 3 \\Rightarrow \\text{Incompatible}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-03",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "gauss",
|
||||||
|
"theoryKey": "gauss-method",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver por eliminación de Gauss: a) x + y + z = 6, 2x - y + z = 3, x + 2y - z = 5; b) x - y + z = 2, 2x + y - z = 3, 3x + 0y + 0z = 5.",
|
||||||
|
"hint": "F₂ → F₂ - 2F₁, F₃ → F₃ - F₁, etc.",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)(2;1;3) b)(5/3;1/3;2/3)", "latex": "a)\\,(2;1;3),\\quad b)\\,(5/3;\\;-2/3;\\;-1/3)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Matriz aumentada", "expression": "\\begin{pmatrix}1&1&1&|&6\\\\2&-1&1&|&3\\\\1&2&-1&|&5\\end{pmatrix}" },
|
||||||
|
{ "desc": "F₂ → F₂ - 2F₁", "expression": "\\begin{pmatrix}1&1&1&|&6\\\\0&-3&-1&|&-9\\\\0&1&-2&|&-1\\end{pmatrix}" },
|
||||||
|
{ "desc": "F₃ → F₃ + F₂/3", "expression": "\\begin{pmatrix}1&1&1&|&6\\\\0&-3&-1&|&-9\\\\0&0&-7/3&|&-4\\end{pmatrix}" },
|
||||||
|
{ "desc": "Sustitución regresiva", "expression": "z = 3,\\; y = 1,\\; x = 2" },
|
||||||
|
{ "desc": "b) 3x = 5 → x = 5/3", "expression": "3x = 5 \\Rightarrow x = \\frac{5}{3}" },
|
||||||
|
{ "desc": "Sustituir", "expression": "y = \\frac{1}{3},\\; z = \\frac{2}{3}" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-04",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "gauss-jordan",
|
||||||
|
"theoryKey": "gauss-jordan",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver por Gauss-Jordan: a) 2x + y = 5, x + 3y = 10; b) x + y + z = 3, 2x - y + z = 2, x + 2y - z = 4.",
|
||||||
|
"hint": "Reducir a forma reducida por filas (identidad)",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)(1;3) b)(2;1;0)", "latex": "a)\\,(1;3),\\quad b)\\,(2;1;0)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Matriz aumentada", "expression": "\\begin{pmatrix}2&1&|&5\\\\1&3&|&10\\end{pmatrix}" },
|
||||||
|
{ "desc": "F₁ ↔ F₂, F₂ → F₂ - 2F₁", "expression": "\\begin{pmatrix}1&3&|&10\\\\0&-5&|&-15\\end{pmatrix}" },
|
||||||
|
{ "desc": "F₂ → F₂/(-5), F₁ → F₁ - 3F₂", "expression": "\\begin{pmatrix}1&0&|&1\\\\0&1&|&3\\end{pmatrix}" },
|
||||||
|
{ "desc": "Solución: x=1, y=3", "expression": "x = 1,\\; y = 3" },
|
||||||
|
{ "desc": "b) Gauss-Jordan en 3×3", "expression": "x = 2,\\; y = 1,\\; z = 0" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-05",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "cramer",
|
||||||
|
"theoryKey": "cramer-rule",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver por la regla de Cramer: a) 3x + 2y = 12, x - y = 1; b) x + y + z = 6, x - y + z = 2, x + y - z = 0.",
|
||||||
|
"hint": "x = Δ₁/Δ, y = Δ₂/Δ, etc.",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)(2.8;1.8) b)(1;3;2)", "latex": "a)\\,(\\frac{14}{5};\\;\\frac{9}{5}),\\quad b)\\,(1;\\;3;\\;2)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) Δ = (3)(-1)-(2)(1) = -5", "expression": "\\Delta = -5" },
|
||||||
|
{ "desc": "Δ₁ = (12)(-1)-(2)(1) = -14", "expression": "\\Delta_1 = -14" },
|
||||||
|
{ "desc": "Δ₂ = (3)(1)-(12)(1) = -9", "expression": "\\Delta_2 = -9" },
|
||||||
|
{ "desc": "x = 14/5, y = 9/5", "expression": "x = \\frac{14}{5},\\; y = \\frac{9}{5}" },
|
||||||
|
{ "desc": "b) Δ = det coeficientes", "expression": "\\Delta = \\begin{vmatrix}1&1&1\\\\1&-1&1\\\\1&1&-1\\end{vmatrix} = 1(1-1)-1(-1-1)+1(1+1) = 4" },
|
||||||
|
{ "desc": "Δ₁, Δ₂, Δ₃", "expression": "x = \\frac{4}{4}=1,\\; y = \\frac{12}{4}=3,\\; z = \\frac{8}{4}=2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-06",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "rouche-frobenius",
|
||||||
|
"theoryKey": "rouche-frobenius",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Aplicar el teorema de Rouche-Frobenius para clasificar y resolver: a) x + y + z = 1, x - y + z = 0, 2x + 0y + 2z = 2; b) x + 2y + 3z = 1, 2x + 4y + 6z = 2, x + y + z = 1; c) x + y + z = 1, x + y + z = 2, 2x + 2y + 2z = 3.",
|
||||||
|
"hint": "Calcular rg(A) y rg(A|b), comparar",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)CI b)CI c)SI", "latex": "a)\\,CI,\\quad b)\\,CI,\\quad c)\\,SI" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) F₃ = F₁ + F₂ → rg(A) = 2, rg(A|b) = 2", "expression": "\\text{rg}(A)=\\text{rg}(A|b)=2 < 3 \\Rightarrow CI" },
|
||||||
|
{ "desc": "b) F₂ = 2F₁ → rg(A) = 2, rg(A|b) = 2", "expression": "\\text{rg}(A)=\\text{rg}(A|b)=2 < 3 \\Rightarrow CI" },
|
||||||
|
{ "desc": "c) E₁: x+y+z=1, E₂: x+y+z=2 (contradicción)", "expression": "1 \\neq 2 \\Rightarrow \\text{rg}(A)=1,\\;\\text{rg}(A|b)=2 \\Rightarrow SI" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-07",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "homogeneous",
|
||||||
|
"theoryKey": "homogeneous",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"statement": "Resolver los siguientes sistemas homogéneos: a) x + y = 0, x - y = 0; b) x + 2y - z = 0, 2x + 4y - 2z = 0, 3x + 6y - 3z = 0; c) x + y + z = 0, 2x - y + z = 0, x + 2y - z = 0.",
|
||||||
|
"hint": "Ax = 0 siempre tiene solución trivial. No trivial ↔ rg(A) < n",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "a)trivial:(0,0) b)CI c)trivial:(0,0,0)", "latex": "a)\\,(0;0),\\quad b)\\,CI,\\quad c)\\,(0;0;0)" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "a) det = -2 ≠ 0 → solución trivial x=y=0", "expression": "\\det = -2 \\neq 0 \\Rightarrow x=y=0" },
|
||||||
|
{ "desc": "b) Todas las ecuaciones proporcionales → rg=1 < 3 → CI", "expression": "E_2=2E_1,\\;E_3=3E_1 \\Rightarrow \\text{rg}=1,\\;\\dim(\\ker)=2" },
|
||||||
|
{ "desc": "Solución paramétrica b)", "expression": "(x,y,z) = \\alpha(-2,1,0) + \\beta(1,0,1)" },
|
||||||
|
{ "desc": "c) det ≠ 0 → trivial", "expression": "\\det = \\begin{vmatrix}1&1&1\\\\2&-1&1\\\\1&2&-1\\end{vmatrix} = 7 \\neq 0 \\Rightarrow (0;0;0)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cap03-08",
|
||||||
|
"chapter": 3,
|
||||||
|
"topic": "systems",
|
||||||
|
"subtopic": "parameter",
|
||||||
|
"theoryKey": "rouche-frobenius",
|
||||||
|
"difficulty": "advanced",
|
||||||
|
"statement": "Determinar el valor de k para que el sistema sea: a) Compatible determinado, b) Compatible indeterminado, c) Incompatible. Sistema: x + y + z = 1, 2x - y + z = k, x + 2y - z = 3.",
|
||||||
|
"hint": "Calcular det de coeficientes. Si ≠ 0 → CD. Si = 0 → analizar según k.",
|
||||||
|
"answerType": "expression",
|
||||||
|
"answer": { "value": "k≠4:CD, k=4:CI, nunca SI", "latex": "k \\neq 4: CD,\\; k = 4: CI,\\; \\text{nunca SI}" },
|
||||||
|
"solutionSteps": [
|
||||||
|
{ "desc": "Calcular det(A)", "expression": "\\det(A) = \\begin{vmatrix}1&1&1\\\\2&-1&1\\\\1&2&-1\\end{vmatrix} = 1(1-2)-1(-2-1)+1(4+1) = -1+3+5 = 7" },
|
||||||
|
{ "desc": "Como det(A) = 7 ≠ 0 siempre → siempre CD", "expression": "\\det(A) = 7 \\neq 0 \\Rightarrow \\text{Siempre CD para todo } k" },
|
||||||
|
{ "desc": "Solución para cada k", "expression": "x = \\frac{7-2k}{7},\\; y = \\frac{k+2}{7},\\; z = \\frac{k+4}{7}" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
62
data/theory-cap01.json
Normal file
62
data/theory-cap01.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"vectors-theory": {
|
||||||
|
"title": "Vectores en ℝ² y ℝ³",
|
||||||
|
"content": "Un vector es un segmento orientado con origen y extremo. En $\\mathbb{R}^2$: $\\vec{u} = (u_x; u_y)$. En $\\mathbb{R}^3$: $\\vec{u} = (u_x; u_y; u_z)$. El módulo es $|\\vec{u}| = \\sqrt{u_x^2 + u_y^2 + u_z^2}$. Vector unitario: $\\hat{u} = \\vec{u}/|\\vec{u}|$.",
|
||||||
|
"relatedExercises": ["cap01-01", "cap01-02", "cap01-03", "cap01-04"]
|
||||||
|
},
|
||||||
|
"vector-ops": {
|
||||||
|
"title": "Operaciones con Vectores",
|
||||||
|
"content": "Suma: $\\vec{u} + \\vec{v} = (u_x+v_x; u_y+v_y; u_z+v_z)$. Multiplicación por escalar: $k\\vec{u} = (k\\cdot u_x; k\\cdot u_y; k\\cdot u_z)$. Propiedades: conmutativa, asociativa, elemento neutro, elemento opuesto.",
|
||||||
|
"relatedExercises": ["cap01-04"]
|
||||||
|
},
|
||||||
|
"dot-product": {
|
||||||
|
"title": "Producto Escalar (Dot Product)",
|
||||||
|
"content": "$\\vec{u} \\cdot \\vec{v} = u_x v_x + u_y v_y + u_z v_z = |\\vec{u}|\\cdot|\\vec{v}|\\, \\cos(\\alpha)$. Propiedades: conmutativa, distributiva. Condición de perpendicularidad: $\\vec{u} \\perp \\vec{v} \\Leftrightarrow \\vec{u} \\cdot \\vec{v} = 0$.",
|
||||||
|
"relatedExercises": ["cap01-05"]
|
||||||
|
},
|
||||||
|
"cross-product": {
|
||||||
|
"title": "Producto Vectorial (Cross Product)",
|
||||||
|
"content": "$\\vec{u} \\times \\vec{v} = (u_y v_z - u_z v_y; u_z v_x - u_x v_z; u_x v_y - u_y v_x)$. Se calcula por determinante: $\\vec{u} \\times \\vec{v} = \\begin{vmatrix} \\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\ u_x & u_y & u_z \\\\ v_x & v_y & v_z \\end{vmatrix}$. $\\vec{u} \\times \\vec{v} \\perp \\vec{u}$ y $\\vec{u} \\times \\vec{v} \\perp \\vec{v}$.",
|
||||||
|
"relatedExercises": ["cap01-06"]
|
||||||
|
},
|
||||||
|
"parallel-perp": {
|
||||||
|
"title": "Paralelismo y Perpendicularidad",
|
||||||
|
"content": "Paralelos: $\\vec{u} \\parallel \\vec{v} \\Leftrightarrow \\vec{u} = k\\vec{v}$ (componentes proporcionales). Perpendiculares: $\\vec{u} \\perp \\vec{v} \\Leftrightarrow \\vec{u} \\cdot \\vec{v} = 0$.",
|
||||||
|
"relatedExercises": ["cap01-07", "cap01-08"]
|
||||||
|
},
|
||||||
|
"coplanarity": {
|
||||||
|
"title": "Producto Mixto y Coplanaridad",
|
||||||
|
"content": "Producto mixto: $[\\vec{u},\\vec{v},\\vec{w}] = \\vec{u} \\cdot (\\vec{v} \\times \\vec{w}) = \\begin{vmatrix} u_x & u_y & u_z \\\\ v_x & v_y & v_z \\\\ w_x & w_y & w_z \\end{vmatrix}$. $\\vec{u},\\vec{v},\\vec{w}$ son coplanarios $\\Leftrightarrow [\\vec{u},\\vec{v},\\vec{w}] = 0$.",
|
||||||
|
"relatedExercises": ["cap01-09", "cap01-29"]
|
||||||
|
},
|
||||||
|
"line-equations": {
|
||||||
|
"title": "Ecuaciones de la Recta",
|
||||||
|
"content": "Vectorial: $r: \\vec{X} = \\vec{P_0} + t\\vec{v}$. Paramétricas: $x = x_0 + t v_x, y = y_0 + t v_y, z = z_0 + t v_z$. Continuas: \\frac{x-x_0}{v_x} = \\frac{y-y_0}{v_y} = \\frac{z-z_0}{v_z}$.",
|
||||||
|
"relatedExercises": ["cap01-10", "cap01-11"]
|
||||||
|
},
|
||||||
|
"line-positions": {
|
||||||
|
"title": "Posición Relativa de Dos Rectas",
|
||||||
|
"content": "Paralelas: $\\vec{v_1} \\parallel \\vec{v_2}$. Secantes: $\\vec{v_1} \\neq k\\vec{v_2}$ y existe solución a $P_1 + t\\vec{v_1} = P_2 + s\\vec{v_2}$. Se cruzan: $\\vec{v_1} \\neq k\\vec{v_2}$ y $[\\vec{v_1},\\vec{v_2}, P_2-P_1] \\neq 0$.",
|
||||||
|
"relatedExercises": ["cap01-12"]
|
||||||
|
},
|
||||||
|
"distance-lines": {
|
||||||
|
"title": "Distancia entre Rectas",
|
||||||
|
"content": "Paralelas: $d(r_1,r_2) = |(P_2-P_1) \\times \\vec{v}| / |\\vec{v}|$. Si se cruzan: $d = |[\\vec{v_1},\\vec{v_2},P_2-P_1]| / |\\vec{v_1} \\times \\vec{v_2}|$.",
|
||||||
|
"relatedExercises": ["cap01-13", "cap01-14"]
|
||||||
|
},
|
||||||
|
"plane-equations": {
|
||||||
|
"title": "Ecuaciones del Plano",
|
||||||
|
"content": "Vectorial: $\\Pi: \\vec{X} = \\vec{P_0} + s\\vec{v_1} + t\\vec{v_2}$. Normal: $\\vec{n} = (a;b;c)$, ecuación general: $ax + by + cz + d = 0$ donde $d = -\\vec{n} \\cdot \\vec{P_0}$.",
|
||||||
|
"relatedExercises": ["cap01-16", "cap01-17"]
|
||||||
|
},
|
||||||
|
"plane-positions": {
|
||||||
|
"title": "Posición Relativa de Planos",
|
||||||
|
"content": "Paralelos: $\\vec{n_1} \\parallel \\vec{n_2}$. Si además $d_1/k = d_2$: coincidentes. Secantes: $\\vec{n_1} \\neq k\\vec{n_2}$ (se cortan en una recta). $\\Pi_1 \\perp \\Pi_2 \\Leftrightarrow \\vec{n_1} \\cdot \\vec{n_2} = 0$.",
|
||||||
|
"relatedExercises": ["cap01-19", "cap01-20"]
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"title": "Ángulos entre Rectas, Planos y Recta-Plano",
|
||||||
|
"content": "Entre rectas: $\\cos \\alpha = |\\vec{v_1} \\cdot \\vec{v_2}| / (|\\vec{v_1}|\\cdot|\\vec{v_2}|)$. Entre planos: $\\cos \\alpha = |\\vec{n_1} \\cdot \\vec{n_2}| / (|\\vec{n_1}|\\cdot|\\vec{n_2}|)$. Entre recta y plano: $\\sin \\beta = |\\vec{v} \\cdot \\vec{n}| / (|\\vec{v}|\\cdot|\\vec{n}|)$.",
|
||||||
|
"relatedExercises": ["cap01-15", "cap01-21", "cap01-22"]
|
||||||
|
}
|
||||||
|
}
|
||||||
42
data/theory-cap02.json
Normal file
42
data/theory-cap02.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"matrices-theory": {
|
||||||
|
"title": "Definición de Matriz",
|
||||||
|
"content": "Una matriz es un arreglo rectangular de números dispuestos en filas y columnas. Una matriz de orden $m \\times n$ tiene $m$ filas y $n$ columnas. Notación: $A = [a_{ij}]$ donde $i$ indica la fila y $j$ la columna.",
|
||||||
|
"relatedExercises": ["cap02-01", "cap02-02"]
|
||||||
|
},
|
||||||
|
"matrix-ops": {
|
||||||
|
"title": "Operaciones con Matrices",
|
||||||
|
"content": "Suma: $(A+B)_{ij} = a_{ij} + b_{ij}$ (mismo orden). Multiplicación por escalar: $(cA)_{ij} = c \\cdot a_{ij}$. Producto: $(AB)_{ij} = \\sum_{k} a_{ik} \\cdot b_{kj}$ (columnas de A = filas de B).",
|
||||||
|
"relatedExercises": ["cap02-03", "cap02-05", "cap02-06"]
|
||||||
|
},
|
||||||
|
"matrix-types": {
|
||||||
|
"title": "Matrices Especiales",
|
||||||
|
"content": "Cuadrada: $n \\times n$. Identidad $I_n$: $1$ en diagonal, $0$ en el resto. Diagonal: $a_{ij}=0$ para $i \\neq j$. Triangular superior: $a_{ij}=0$ para $i > j$. Traspuesta $A^T$: $(A^T)_{ij} = a_{ji}$.",
|
||||||
|
"relatedExercises": ["cap02-07", "cap02-08", "cap02-09"]
|
||||||
|
},
|
||||||
|
"transpose-symmetry": {
|
||||||
|
"title": "Traspuesta y Simetría",
|
||||||
|
"content": "Traspuesta: $(A^T)^T = A$, $(AB)^T = B^T A^T$. Simétrica: $A = A^T \\Leftrightarrow a_{ij} = a_{ji}$. Antisimétrica: $A = -A^T \\Leftrightarrow a_{ii}=0$. Toda matriz: $A = \\frac{A+A^T}{2} + \\frac{A-A^T}{2}$ (simétrica + antisimétrica).",
|
||||||
|
"relatedExercises": ["cap02-10", "cap02-11"]
|
||||||
|
},
|
||||||
|
"determinants": {
|
||||||
|
"title": "Determinantes",
|
||||||
|
"content": "Orden 2: $|\\begin{matrix} a & b \\\\ c & d \\end{matrix}| = ad - bc$. Orden 3 (Sarrus): suma de 3 diagonales principales menos 3 diagonales secundarias. Cofactor: $C_{ij} = (-1)^{i+j} M_{ij}$ (menor con signo).",
|
||||||
|
"relatedExercises": ["cap02-12", "cap02-13", "cap02-14"]
|
||||||
|
},
|
||||||
|
"determinant-props": {
|
||||||
|
"title": "Propiedades de los Determinantes",
|
||||||
|
"content": "$|A^T| = |A|$. Si se intercambian dos filas, cambia signo. Fila de ceros $\\Rightarrow |A|=0$. Filas proporcionales $\\Rightarrow |A|=0$. $F_i \\rightarrow F_i + cF_j$ no cambia $|A|$. $|AB| = |A| \\cdot |B|$. $|cA| = c^n |A|$ para $n \\times n$.",
|
||||||
|
"relatedExercises": ["cap02-17", "cap02-18", "cap02-19"]
|
||||||
|
},
|
||||||
|
"inverse-matrix": {
|
||||||
|
"title": "Matriz Inversa",
|
||||||
|
"content": "$A$ invertible $\\Leftrightarrow |A| \\neq 0 \\Leftrightarrow \\text{rg}(A) = n$. $A^{-1} = \\frac{1}{|A|} \\text{adj}(A)$. Para $2 \\times 2$: $A^{-1} = \\frac{1}{ad-bc} \\begin{matrix} d & -b \\\\ -c & a \\end{matrix}$.",
|
||||||
|
"relatedExercises": ["cap02-20", "cap02-21", "cap02-22", "cap02-24", "cap02-25"]
|
||||||
|
},
|
||||||
|
"rank": {
|
||||||
|
"title": "Rango de una Matriz",
|
||||||
|
"content": "Rango = orden del menor no nulo mayor. Equivale al número de filas no nulas en forma escalonada. Operaciones elementales (permutar, multiplicar por $c \\neq 0$, $F_i + cF_j$) no cambian el rango.",
|
||||||
|
"relatedExercises": ["cap02-26", "cap02-27", "cap02-28", "cap02-29"]
|
||||||
|
}
|
||||||
|
}
|
||||||
32
data/theory-cap03.json
Normal file
32
data/theory-cap03.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"systems-theory": {
|
||||||
|
"title": "Sistemas de Ecuaciones Lineales",
|
||||||
|
"content": "Sistema de $m$ ecuaciones con $n$ incógnitas: $A \\cdot X = B$. $A$ (matriz coeficientes $m \\times n$), $X$ (incógnitas $n \\times 1$), $B$ (términos independientes $m \\times 1$). Matriz ampliada $A' = [A|B]$ de dimensión $m \\times (n+1)$.",
|
||||||
|
"relatedExercises": ["cap03-01", "cap03-02"]
|
||||||
|
},
|
||||||
|
"gauss-method": {
|
||||||
|
"title": "Método de Eliminación de Gauss",
|
||||||
|
"content": "Transformar la matriz ampliada a forma triangular mediante operaciones elementales ($F_i \\leftrightarrow F_j$, $F_i \\rightarrow cF_i$, $F_i \\rightarrow F_i + cF_j$) y resolver por sustitución regresiva. Pivote: elemento $a_{ii} \\neq 0$.",
|
||||||
|
"relatedExercises": ["cap03-03"]
|
||||||
|
},
|
||||||
|
"gauss-jordan": {
|
||||||
|
"title": "Método de Gauss-Jordan",
|
||||||
|
"content": "Llevar la matriz ampliada a forma escalonada reducida por filas: cada pivote es 1 y tiene ceros arriba y abajo. La solución se lee directamente sin sustitución regresiva.",
|
||||||
|
"relatedExercises": ["cap03-04"]
|
||||||
|
},
|
||||||
|
"cramer-rule": {
|
||||||
|
"title": "Regla de Cramer",
|
||||||
|
"content": "Para sistemas $n \\times n$ con $|A| \\neq 0$: $x_j = \\frac{|A_j|}{|A|}$ donde $A_j$ es $A$ con columna $j$ reemplazada por $B$. Requiere $n+1$ determinantes — impracticable para sistemas grandes.",
|
||||||
|
"relatedExercises": ["cap03-05"]
|
||||||
|
},
|
||||||
|
"rouche-frobenius": {
|
||||||
|
"title": "Teorema de Rouché-Frobenius",
|
||||||
|
"content": "Sea $r = \\text{rg}(A)$, $r' = \\text{rg}(A'|)$. Si $r \\neq r'$: SI (incompatible). Si $r = r' = n$: SCD (única solución). Si $r = r' < n$: SCI (infinitas soluciones, $n-r$ parámetros libres).",
|
||||||
|
"relatedExercises": ["cap03-06", "cap03-08"]
|
||||||
|
},
|
||||||
|
"homogeneous": {
|
||||||
|
"title": "Sistemas Homogéneos",
|
||||||
|
"content": "Sistema $AX = 0$ con todos los términos independientes nulos. SIEMPRE tiene al menos la solución trivial $X = 0$. Tiene soluciones no triviales $\\Leftrightarrow \\text{rg}(A) < n \\Leftrightarrow |A| = 0$ (sistema cuadrado).",
|
||||||
|
"relatedExercises": ["cap03-07"]
|
||||||
|
}
|
||||||
|
}
|
||||||
48
index.html
Normal file
48
index.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Álgebra Lineal - Práctica Interactiva</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||||
|
<link rel="stylesheet" href="css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar" role="navigation" aria-label="Navegación principal">
|
||||||
|
<div class="navbar__brand">
|
||||||
|
<span class="navbar__icon">𝕊</span>
|
||||||
|
<span class="navbar__title">Álgebra Lineal</span>
|
||||||
|
</div>
|
||||||
|
<div class="navbar__links">
|
||||||
|
<a href="#/" class="navbar__link" data-route="home">Inicio</a>
|
||||||
|
<a href="#/exercises" class="navbar__link" data-route="exercises">Ejercicios</a>
|
||||||
|
<a href="#/workspace" class="navbar__link" data-route="workspace">Taller</a>
|
||||||
|
</div>
|
||||||
|
<button class="navbar__theme-toggle" id="themeToggle" title="Cambiar tema">
|
||||||
|
<span class="theme-icon">☾</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main id="app" class="app-container" role="main" aria-live="polite">
|
||||||
|
<div class="loading-screen">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<p>Cargando...</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>Álgebra Lineal Interactivo — UBA FCE</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||||
|
<script src="js/utils/katex-render.js"></script>
|
||||||
|
<script src="js/math-engine.js"></script>
|
||||||
|
<script src="js/particles.js"></script>
|
||||||
|
<script src="js/components/exercise-browser.js"></script>
|
||||||
|
<script src="js/components/exercise-viewer.js"></script>
|
||||||
|
<script src="js/components/matrix-builder.js"></script>
|
||||||
|
<script src="js/components/system-solver.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
250
js/app.js
Normal file
250
js/app.js
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* App — Hash router, component lifecycle, navigation, event bus
|
||||||
|
*/
|
||||||
|
const App = (() => {
|
||||||
|
let currentComponent = null;
|
||||||
|
let appEl = null;
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ pattern: /^\/$/, handler: showHome },
|
||||||
|
{ pattern: /^\/exercises$/, handler: showExercises },
|
||||||
|
{ pattern: /^\/exercise\/(.+)$/, handler: showExercise },
|
||||||
|
{ pattern: /^\/workspace$/, handler: showWorkspace },
|
||||||
|
{ pattern: /^\/workspace\/matrix$/, handler: showMatrixBuilder },
|
||||||
|
{ pattern: /^\/workspace\/system$/, handler: showSystemSolver }
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Event Bus ──
|
||||||
|
const bus = {};
|
||||||
|
function on(event, fn) {
|
||||||
|
(bus[event] = bus[event] || []).push(fn);
|
||||||
|
}
|
||||||
|
function emit(event, data) {
|
||||||
|
(bus[event] || []).forEach(fn => fn(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Theme ──
|
||||||
|
function initTheme() {
|
||||||
|
const saved = localStorage.getItem('algebra-theme');
|
||||||
|
if (saved === 'dark') {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||||
|
if (isDark) {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
localStorage.setItem('algebra-theme', 'light');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
localStorage.setItem('algebra-theme', 'dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Navigation ──
|
||||||
|
function navigate(path) {
|
||||||
|
window.location.hash = '#' + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHash() {
|
||||||
|
return window.location.hash.slice(1) || '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchRoute(hash) {
|
||||||
|
for (const route of routes) {
|
||||||
|
const match = hash.match(route.pattern);
|
||||||
|
if (match) {
|
||||||
|
return { handler: route.handler, params: match.slice(1) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { handler: showHome, params: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateActiveNav() {
|
||||||
|
const hash = getHash();
|
||||||
|
document.querySelectorAll('.navbar__link').forEach(link => {
|
||||||
|
const route = link.getAttribute('data-route');
|
||||||
|
link.classList.remove('navbar__link--active');
|
||||||
|
if (route === 'home' && (hash === '/' || hash === '')) {
|
||||||
|
link.classList.add('navbar__link--active');
|
||||||
|
} else if (route === 'exercises' && hash.startsWith('/exercise')) {
|
||||||
|
link.classList.add('navbar__link--active');
|
||||||
|
} else if (route === 'workspace' && hash.startsWith('/workspace')) {
|
||||||
|
link.classList.add('navbar__link--active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Component Lifecycle ──
|
||||||
|
function destroyCurrent() {
|
||||||
|
if (currentComponent && typeof currentComponent.destroy === 'function') {
|
||||||
|
currentComponent.destroy();
|
||||||
|
}
|
||||||
|
currentComponent = null;
|
||||||
|
if (appEl) appEl.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountComponent(componentObj, initArgs) {
|
||||||
|
destroyCurrent();
|
||||||
|
currentComponent = componentObj;
|
||||||
|
componentObj.init(appEl, initArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Page Handlers ──
|
||||||
|
function showHome() {
|
||||||
|
destroyCurrent();
|
||||||
|
appEl.innerHTML = `
|
||||||
|
<div class="home-page">
|
||||||
|
<div class="home-page__hero">
|
||||||
|
<h1>Álgebra Lineal Interactivo</h1>
|
||||||
|
<p>Practicá los ejercicios de Vectores, Recta, Plano, Matrices, Determinantes y Sistemas de Ecuaciones. Paso a paso, con verificación automática.</p>
|
||||||
|
</div>
|
||||||
|
<div class="home-page__cards">
|
||||||
|
<div class="home-card" data-nav="/exercises">
|
||||||
|
<div class="home-card__icon">📚</div>
|
||||||
|
<div class="home-card__title">Ejercicios</div>
|
||||||
|
<div class="home-card__desc">68 ejercicios de los capítulos 1 a 3 con solución paso a paso</div>
|
||||||
|
</div>
|
||||||
|
<div class="home-card" data-nav="/workspace">
|
||||||
|
<div class="home-card__icon">🧮</div>
|
||||||
|
<div class="home-card__title">Taller</div>
|
||||||
|
<div class="home-card__desc">Constructor de matrices y resolvedor de sistemas interactivos</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
// Click handlers for home cards
|
||||||
|
appEl.querySelectorAll('.home-card[data-nav]').forEach(card => {
|
||||||
|
card.addEventListener('click', () => navigate(card.dataset.nav));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExercises() {
|
||||||
|
mountComponent(ExerciseBrowser);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExercise(params) {
|
||||||
|
mountComponent(ExerciseViewer, { id: params[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWorkspace() {
|
||||||
|
destroyCurrent();
|
||||||
|
appEl.innerHTML = `
|
||||||
|
<div class="workspace">
|
||||||
|
<div class="workspace__header">
|
||||||
|
<h2>Taller de Cálculo</h2>
|
||||||
|
<p>Operá con matrices y resolvé sistemas de ecuaciones</p>
|
||||||
|
</div>
|
||||||
|
<div class="workspace__tabs">
|
||||||
|
<button class="workspace__tab workspace__tab--active" data-ws="matrix">Constructor de Matrices</button>
|
||||||
|
<button class="workspace__tab" data-ws="system">Resolvedor de Sistemas</button>
|
||||||
|
</div>
|
||||||
|
<div class="workspace__content" id="workspaceContent"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
setupWorkspaceTabs();
|
||||||
|
// Default to matrix
|
||||||
|
const wsContent = document.getElementById('workspaceContent');
|
||||||
|
MatrixBuilder.init(wsContent);
|
||||||
|
currentComponent = MatrixBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWorkspaceTabs() {
|
||||||
|
const tabs = appEl.querySelectorAll('.workspace__tab');
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
tabs.forEach(t => t.classList.remove('workspace__tab--active'));
|
||||||
|
tab.classList.add('workspace__tab--active');
|
||||||
|
const ws = tab.dataset.ws;
|
||||||
|
const wsContent = document.getElementById('workspaceContent');
|
||||||
|
if (!wsContent) return;
|
||||||
|
|
||||||
|
if (currentComponent && typeof currentComponent.destroy === 'function') {
|
||||||
|
currentComponent.destroy();
|
||||||
|
}
|
||||||
|
wsContent.innerHTML = '';
|
||||||
|
|
||||||
|
if (ws === 'matrix') {
|
||||||
|
MatrixBuilder.init(wsContent);
|
||||||
|
currentComponent = MatrixBuilder;
|
||||||
|
} else if (ws === 'system') {
|
||||||
|
SystemSolver.init(wsContent);
|
||||||
|
currentComponent = SystemSolver;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMatrixBuilder() {
|
||||||
|
navigate('/workspace');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSystemSolver() {
|
||||||
|
navigate('/workspace');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Router ──
|
||||||
|
function handleRoute() {
|
||||||
|
const hash = getHash();
|
||||||
|
const { handler, params } = matchRoute(hash);
|
||||||
|
updateActiveNav();
|
||||||
|
|
||||||
|
// Fade out
|
||||||
|
if (appEl) {
|
||||||
|
appEl.style.opacity = '0';
|
||||||
|
appEl.style.transition = 'opacity 150ms ease-out';
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
handler(params);
|
||||||
|
emit('routechange', hash);
|
||||||
|
// Fade in
|
||||||
|
if (appEl) {
|
||||||
|
appEl.style.opacity = '1';
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Init ──
|
||||||
|
function init() {
|
||||||
|
appEl = document.getElementById('app');
|
||||||
|
if (!appEl) {
|
||||||
|
console.error('[App] #app element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
// Init particles background
|
||||||
|
if (typeof Particles !== 'undefined' && Particles.init) {
|
||||||
|
Particles.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme toggle
|
||||||
|
const themeBtn = document.getElementById('themeToggle');
|
||||||
|
if (themeBtn) {
|
||||||
|
themeBtn.addEventListener('click', toggleTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash change listener
|
||||||
|
window.addEventListener('hashchange', handleRoute);
|
||||||
|
|
||||||
|
// Initial route
|
||||||
|
handleRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigate,
|
||||||
|
on,
|
||||||
|
emit,
|
||||||
|
getCurrentComponent: () => currentComponent
|
||||||
|
};
|
||||||
|
})();
|
||||||
200
js/components/exercise-browser.js
Normal file
200
js/components/exercise-browser.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* ExerciseBrowser — Topic filter tabs, exercise list, search, navigation to viewer
|
||||||
|
*/
|
||||||
|
const ExerciseBrowser = (() => {
|
||||||
|
let container = null;
|
||||||
|
let exercises = [];
|
||||||
|
let filteredExercises = [];
|
||||||
|
let activeTopic = 'all';
|
||||||
|
let searchText = '';
|
||||||
|
let exerciseDataLoaded = {};
|
||||||
|
let ac = null;
|
||||||
|
let searchDebounceTimer = null;
|
||||||
|
|
||||||
|
const TOPICS = [
|
||||||
|
{ key: 'all', label: 'Todos', icon: '📚' },
|
||||||
|
{ key: 'vector-ops', label: 'Vectores', icon: '➡️' },
|
||||||
|
{ key: 'line-eq', label: 'Rectas', icon: '📏' },
|
||||||
|
{ key: 'plane-eq', label: 'Planos', icon: '📐' },
|
||||||
|
{ key: 'matrix-ops', label: 'Matrices', icon: '🔲' },
|
||||||
|
{ key: 'determinants', label: 'Determinantes', icon: 'det' },
|
||||||
|
{ key: 'matrix-inverse', label: 'Inversa', icon: '🔄' },
|
||||||
|
{ key: 'systems', label: 'Sistemas', icon: '⚖️' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const CHAPTER_NAMES = {
|
||||||
|
1: 'Cap. 1 — Vectores, Recta y Plano',
|
||||||
|
2: 'Cap. 2 — Matrices y Determinantes',
|
||||||
|
3: 'Cap. 3 — Sistemas de Ecuaciones'
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadExerciseData() {
|
||||||
|
if (Object.keys(exerciseDataLoaded).length > 0) return exerciseDataLoaded;
|
||||||
|
const chapters = [1, 2, 3];
|
||||||
|
const promises = chapters.map(async (ch) => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`data/exercises-cap0${ch}.json`);
|
||||||
|
exerciseDataLoaded[ch] = await resp.json();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[ExerciseBrowser] Failed to load cap${ch}:`, e);
|
||||||
|
exerciseDataLoaded[ch] = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
exercises = Object.values(exerciseDataLoaded).flat();
|
||||||
|
return exerciseDataLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopicCounts() {
|
||||||
|
const counts = { all: exercises.length };
|
||||||
|
exercises.forEach(ex => {
|
||||||
|
counts[ex.topic] = (counts[ex.topic] || 0) + 1;
|
||||||
|
});
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterExercises() {
|
||||||
|
filteredExercises = exercises.filter(ex => {
|
||||||
|
const topicMatch = activeTopic === 'all' || ex.topic === activeTopic;
|
||||||
|
if (!searchText) return topicMatch;
|
||||||
|
const search = searchText.toLowerCase();
|
||||||
|
const textMatch =
|
||||||
|
(ex.statement || '').toLowerCase().includes(search) ||
|
||||||
|
(ex.id || '').toLowerCase().includes(search) ||
|
||||||
|
(ex.subtopic || '').toLowerCase().includes(search);
|
||||||
|
return topicMatch && textMatch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTopicTabs() {
|
||||||
|
const counts = getTopicCounts();
|
||||||
|
return `<div class="topic-tabs">${TOPICS.map(t => {
|
||||||
|
const isActive = t.key === activeTopic ? ' topic-tab--active' : '';
|
||||||
|
const count = counts[t.key] || 0;
|
||||||
|
return `<button class="topic-tab${isActive}" data-topic="${t.key}">
|
||||||
|
<span>${t.icon}</span> ${t.label} <span class="topic-tab__count">${count}</span>
|
||||||
|
</button>`;
|
||||||
|
}).join('')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExerciseCards() {
|
||||||
|
if (filteredExercises.length === 0) {
|
||||||
|
return `<div class="exercise-browser__empty"><p>No se encontraron ejercicios</p></div>`;
|
||||||
|
}
|
||||||
|
return `<div class="exercise-grid">${filteredExercises.map(ex => {
|
||||||
|
const topicInfo = TOPICS.find(t => t.key === ex.topic) || { label: ex.topic };
|
||||||
|
const difficultyClass = `difficulty--${ex.difficulty || 'basic'}`;
|
||||||
|
const diffLabel = { basic: 'Básico', intermediate: 'Intermedio', advanced: 'Avanzado' };
|
||||||
|
return `<div class="exercise-card" data-exercise-id="${ex.id}" tabindex="0" role="button" aria-label="Ver ejercicio ${ex.id}">
|
||||||
|
<div class="exercise-card__id">${ex.id}</div>
|
||||||
|
<span class="exercise-card__topic">${topicInfo.icon || ''} ${topicInfo.label}</span>
|
||||||
|
<div class="exercise-card__statement">${ex.statement}</div>
|
||||||
|
<div class="exercise-card__difficulty ${difficultyClass}">${diffLabel[ex.difficulty] || ex.difficulty}</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeAttr(str) {
|
||||||
|
return (str || '').replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
if (!container) return;
|
||||||
|
filterExercises();
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="exercise-browser">
|
||||||
|
<div class="exercise-browser__header">
|
||||||
|
<h2>Ejercicios</h2>
|
||||||
|
<p>Seleccioná un tema para filtrar los ejercicios</p>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="exercise-browser__search" placeholder="Buscar ejercicios..." value="${searchText}">
|
||||||
|
${renderTopicTabs()}
|
||||||
|
${renderExerciseCards()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
// Skip KaTeX on cards — text+math mixing causes spacing loss in math mode.
|
||||||
|
// Full KaTeX rendering happens in exercise-viewer when user opens the exercise.
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMathInElements() {
|
||||||
|
// Disabled: KaTeX rendering on cards removed due to text spacing issues.
|
||||||
|
// The statement is shown as plain text; full rendering in exercise-viewer.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTopicClick(e) {
|
||||||
|
const tab = e.target.closest('.topic-tab');
|
||||||
|
if (!tab) return;
|
||||||
|
activeTopic = tab.dataset.topic;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCardClick(e) {
|
||||||
|
const card = e.target.closest('.exercise-card');
|
||||||
|
if (!card) return;
|
||||||
|
const id = card.dataset.exerciseId;
|
||||||
|
if (id && typeof App !== 'undefined') {
|
||||||
|
App.navigate(`/exercise/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch(e) {
|
||||||
|
clearTimeout(searchDebounceTimer);
|
||||||
|
searchDebounceTimer = setTimeout(() => {
|
||||||
|
searchText = e.target.value;
|
||||||
|
filterExercises();
|
||||||
|
// Re-render only the grid, not the tabs
|
||||||
|
const grid = container.querySelector('.exercise-grid');
|
||||||
|
const empty = container.querySelector('.exercise-browser__empty');
|
||||||
|
if (grid || empty) {
|
||||||
|
const parent = grid ? grid.parentElement : empty.parentElement;
|
||||||
|
const targetEl = grid || empty;
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.innerHTML = renderExerciseCards();
|
||||||
|
targetEl.replaceWith(temp.firstElementChild);
|
||||||
|
renderMathInElements();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(el) {
|
||||||
|
container = el;
|
||||||
|
ac = new AbortController();
|
||||||
|
loadExerciseData().then(() => {
|
||||||
|
render();
|
||||||
|
container.addEventListener('click', handleTopicClick, { signal: ac.signal });
|
||||||
|
container.addEventListener('click', handleCardClick, { signal: ac.signal });
|
||||||
|
container.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
const card = e.target.closest('.exercise-card');
|
||||||
|
if (card) { e.preventDefault(); card.click(); }
|
||||||
|
}
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
container.addEventListener('input', (e) => {
|
||||||
|
if (e.target.classList.contains('exercise-browser__search')) {
|
||||||
|
handleSearch(e);
|
||||||
|
}
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (ac) {
|
||||||
|
ac.abort();
|
||||||
|
ac = null;
|
||||||
|
}
|
||||||
|
clearTimeout(searchDebounceTimer);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
container = null;
|
||||||
|
activeTopic = 'all';
|
||||||
|
searchText = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExercises() {
|
||||||
|
return exercises;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init, destroy, getExercises, loadExerciseData };
|
||||||
|
})();
|
||||||
330
js/components/exercise-viewer.js
Normal file
330
js/components/exercise-viewer.js
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
/**
|
||||||
|
* ExerciseViewer — Problem display with KaTeX, step-by-step solution reveal, interactive attempt
|
||||||
|
*/
|
||||||
|
const ExerciseViewer = (() => {
|
||||||
|
let container = null;
|
||||||
|
let currentExercise = null;
|
||||||
|
let revealedSteps = 0;
|
||||||
|
let ac = null; // AbortController for event listener cleanup
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return (str || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findExercise(id) {
|
||||||
|
await ExerciseBrowser.loadExerciseData();
|
||||||
|
const exercises = ExerciseBrowser.getExercises();
|
||||||
|
return exercises.find(ex => ex.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProblem(ex) {
|
||||||
|
const diffLabel = { basic: 'Básico', intermediate: 'Intermedio', advanced: 'Avanzado' };
|
||||||
|
const topicLabels = {
|
||||||
|
'vector-ops': 'Vectores',
|
||||||
|
'line-eq': 'Rectas',
|
||||||
|
'plane-eq': 'Planos',
|
||||||
|
'matrix-ops': 'Matrices',
|
||||||
|
'determinants': 'Determinantes',
|
||||||
|
'matrix-inverse': 'Inversa',
|
||||||
|
'systems': 'Sistemas'
|
||||||
|
};
|
||||||
|
return `
|
||||||
|
<div class="exercise-viewer__problem">
|
||||||
|
<div class="exercise-viewer__problem-header">
|
||||||
|
<span class="exercise-viewer__problem-id">${ex.id}</span>
|
||||||
|
<span class="exercise-viewer__problem-topic">${topicLabels[ex.topic] || ex.topic} — ${diffLabel[ex.difficulty] || ex.difficulty}</span>
|
||||||
|
</div>
|
||||||
|
<div class="exercise-viewer__statement" id="exercise-statement"></div>
|
||||||
|
${ex.hint ? `<button class="exercise-viewer__hint-btn" id="hintBtn">💡 Ver pista</button>
|
||||||
|
<div class="exercise-viewer__hint" id="hintBox">${escapeHtml(ex.hint)}</div>` : ''}
|
||||||
|
${ex.theoryKey ? `<button class="exercise-viewer__theory-btn" id="theoryBtn">📖 Ver teoría</button>
|
||||||
|
<div class="exercise-viewer__theory theory-panel" id="theoryBox"></div>` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="exercise-viewer__attempt">
|
||||||
|
<h3>Tu respuesta</h3>
|
||||||
|
<div class="attempt-input">
|
||||||
|
<input type="text" class="attempt-input__field" id="attemptInput" placeholder="Ingresá tu respuesta...">
|
||||||
|
<button class="attempt-input__submit" id="attemptSubmit">Verificar</button>
|
||||||
|
</div>
|
||||||
|
<div class="attempt-feedback" id="attemptFeedback" role="status" aria-live="polite"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="exercise-viewer__solution">
|
||||||
|
<h3>Solución paso a paso</h3>
|
||||||
|
<ol class="solution-steps" id="solutionSteps"></ol>
|
||||||
|
<button class="reveal-btn" id="revealBtn">Mostrar siguiente paso ▼</button>
|
||||||
|
<div class="solution-answer" id="solutionAnswer">
|
||||||
|
<div class="solution-answer__label">Respuesta final</div>
|
||||||
|
<div id="answerContent"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let theoryCache = {};
|
||||||
|
|
||||||
|
function renderStatement(ex) {
|
||||||
|
const el = document.getElementById('exercise-statement');
|
||||||
|
if (!el) return;
|
||||||
|
const latex = ex.statement;
|
||||||
|
if (!latex) {
|
||||||
|
el.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof KatexRenderer !== 'undefined' && KatexRenderer.renderInline) {
|
||||||
|
try {
|
||||||
|
KatexRenderer.renderInline(el, latex);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[ExerciseViewer] KaTeX render error, falling back to text:', e);
|
||||||
|
el.textContent = latex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el.textContent = latex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTheory(chapter, theoryKey) {
|
||||||
|
const cacheKey = `${chapter}-${theoryKey}`;
|
||||||
|
if (theoryCache[cacheKey]) return theoryCache[cacheKey];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`data/theory-cap0${chapter}.json`);
|
||||||
|
const data = await resp.json();
|
||||||
|
theoryCache[cacheKey] = data[theoryKey] || null;
|
||||||
|
return theoryCache[cacheKey];
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[ExerciseViewer] Failed to load theory:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTheoryPanel(theory) {
|
||||||
|
const box = document.getElementById('theoryBox');
|
||||||
|
if (!box) return;
|
||||||
|
const title = escapeHtml(theory.title || '');
|
||||||
|
const content = theory.content || '';
|
||||||
|
box.innerHTML = `<div class="theory-panel"><h4>${title}</h4><div class="theory-content" id="theoryContent"></div></div>`;
|
||||||
|
const contentEl = document.getElementById('theoryContent');
|
||||||
|
if (contentEl && typeof KatexRenderer !== 'undefined') {
|
||||||
|
contentEl.textContent = content;
|
||||||
|
KatexRenderer.renderAll(contentEl);
|
||||||
|
} else if (contentEl) {
|
||||||
|
contentEl.textContent = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAnswer(userInput, exercise) {
|
||||||
|
if (!exercise.answer) return { correct: false, message: 'No hay respuesta definida para este ejercicio.' };
|
||||||
|
const ans = exercise.answer;
|
||||||
|
const input = userInput.trim();
|
||||||
|
|
||||||
|
if (exercise.answerType === 'numeric') {
|
||||||
|
const num = parseFloat(input);
|
||||||
|
if (isNaN(num)) return { correct: false, message: 'Ingresá un número válido.' };
|
||||||
|
const correct = Math.abs(num - ans.value) < 0.01;
|
||||||
|
return { correct, message: correct ? '¡Correcto! 🎉' : 'Incorrecto. Intentá de nuevo.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exercise.answerType === 'vector') {
|
||||||
|
// Parse vector format: (1; 2; 3) or (1,2,3)
|
||||||
|
const vecMatch = input.replace(/\s/g, '').match(/[\(-]?([-\d.]+)[;,]([-\d.]+)(?:[;,]([-\d.]+))?[;\)]?/);
|
||||||
|
if (!vecMatch) return { correct: false, message: 'Formato: (1; 2; 3) o (1,2,3)' };
|
||||||
|
const vals = vecMatch.slice(1).filter(v => v !== undefined).map(Number);
|
||||||
|
const answerVals = ans.value;
|
||||||
|
if (vals.length !== answerVals.length) return { correct: false, message: `Se esperan ${answerVals.length} componentes.` };
|
||||||
|
const correct = vals.every((v, i) => Math.abs(v - answerVals[i]) < 0.01);
|
||||||
|
return { correct, message: correct ? '¡Correcto! 🎉' : 'Incorrecto. Intentá de nuevo.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exercise.answerType === 'expression') {
|
||||||
|
// Normalize and compare expression answers
|
||||||
|
// Handle both text answers and LaTeX answers
|
||||||
|
const normalizeText = (s) => {
|
||||||
|
if (!s) return '';
|
||||||
|
return s
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, ''); // Remove diacritics for accent-insensitive comparison
|
||||||
|
};
|
||||||
|
const normalizeLatex = (s) => {
|
||||||
|
if (!s) return '';
|
||||||
|
return s
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.replace(/\\vec\{([^}]*)\}/g, '\\vec{$1}')
|
||||||
|
.replace(/\\overrightarrow\{([^}]*)\}/g, '\\overrightarrow{$1}')
|
||||||
|
.replace(/\\cdot/g, '\\cdot')
|
||||||
|
.replace(/\\times/g, '\\times');
|
||||||
|
};
|
||||||
|
const normInput = normalizeText(input);
|
||||||
|
const normAnswer = normalizeText(ans.value);
|
||||||
|
if (!normAnswer) return { correct: false, message: 'Este ejercicio requiere verificación manual. Mirá la solución.' };
|
||||||
|
const correct = normInput === normAnswer;
|
||||||
|
return { correct, message: correct ? '¡Correcto! 🎉' : 'Incorrecto. Intentá de nuevo.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { correct: false, message: 'Tipo de respuesta no soportado.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealNextStep() {
|
||||||
|
if (!currentExercise || !currentExercise.solutionSteps) return;
|
||||||
|
const steps = currentExercise.solutionSteps;
|
||||||
|
const stepsEl = document.getElementById('solutionSteps');
|
||||||
|
const revealBtn = document.getElementById('revealBtn');
|
||||||
|
|
||||||
|
if (revealedSteps < steps.length) {
|
||||||
|
const step = steps[revealedSteps];
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'solution-step';
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="solution-step__desc">${escapeHtml(step.desc)}</div>
|
||||||
|
<div class="solution-step__expression" data-step="${revealedSteps}"></div>
|
||||||
|
`;
|
||||||
|
stepsEl.appendChild(li);
|
||||||
|
|
||||||
|
// Render math
|
||||||
|
const mathEl = li.querySelector('.solution-step__expression');
|
||||||
|
if (mathEl && typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(mathEl, step.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealedSteps++;
|
||||||
|
|
||||||
|
// Hide button if all steps revealed
|
||||||
|
if (revealedSteps >= steps.length) {
|
||||||
|
revealBtn.textContent = 'Mostrar respuesta final ▼';
|
||||||
|
revealBtn.onclick = revealAnswer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealAnswer() {
|
||||||
|
if (!currentExercise || !currentExercise.answer) return;
|
||||||
|
const answerEl = document.getElementById('solutionAnswer');
|
||||||
|
const contentEl = document.getElementById('answerContent');
|
||||||
|
const revealBtn = document.getElementById('revealBtn');
|
||||||
|
|
||||||
|
if (contentEl && typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(contentEl, currentExercise.answer.latex);
|
||||||
|
} else if (contentEl) {
|
||||||
|
contentEl.textContent = currentExercise.answer.latex;
|
||||||
|
}
|
||||||
|
|
||||||
|
answerEl.classList.add('solution-answer--visible');
|
||||||
|
revealBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEvents() {
|
||||||
|
ac = new AbortController();
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
const backBtn = container.querySelector('.exercise-viewer__back');
|
||||||
|
if (backBtn) {
|
||||||
|
backBtn.addEventListener('click', () => {
|
||||||
|
if (typeof App !== 'undefined') App.navigate('/exercises');
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hint button
|
||||||
|
const hintBtn = document.getElementById('hintBtn');
|
||||||
|
if (hintBtn) {
|
||||||
|
hintBtn.addEventListener('click', () => {
|
||||||
|
const hintBox = document.getElementById('hintBox');
|
||||||
|
hintBox.classList.toggle('exercise-viewer__hint--visible');
|
||||||
|
hintBtn.textContent = hintBox.classList.contains('exercise-viewer__hint--visible') ? '🙈 Ocultar pista' : '💡 Ver pista';
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theory button
|
||||||
|
const theoryBtn = document.getElementById('theoryBtn');
|
||||||
|
if (theoryBtn && currentExercise && currentExercise.theoryKey) {
|
||||||
|
theoryBtn.addEventListener('click', async () => {
|
||||||
|
const theoryBox = document.getElementById('theoryBox');
|
||||||
|
const isVisible = theoryBox.classList.contains('exercise-viewer__theory--visible');
|
||||||
|
if (isVisible) {
|
||||||
|
theoryBox.classList.remove('exercise-viewer__theory--visible');
|
||||||
|
theoryBtn.textContent = '📖 Ver teoría';
|
||||||
|
} else {
|
||||||
|
const chapter = currentExercise.chapter || 1;
|
||||||
|
const theory = await loadTheory(chapter, currentExercise.theoryKey);
|
||||||
|
if (theory) {
|
||||||
|
renderTheoryPanel(theory);
|
||||||
|
theoryBox.classList.add('exercise-viewer__theory--visible');
|
||||||
|
theoryBtn.textContent = '📕 Ocultar teoría';
|
||||||
|
} else {
|
||||||
|
theoryBox.innerHTML = '<p>Teoría no disponible.</p>';
|
||||||
|
theoryBox.classList.add('exercise-viewer__theory--visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt
|
||||||
|
const submitBtn = document.getElementById('attemptSubmit');
|
||||||
|
const attemptInput = document.getElementById('attemptInput');
|
||||||
|
const feedback = document.getElementById('attemptFeedback');
|
||||||
|
|
||||||
|
if (submitBtn && currentExercise) {
|
||||||
|
const doCheck = () => {
|
||||||
|
const result = checkAnswer(attemptInput.value, currentExercise);
|
||||||
|
feedback.className = 'attempt-feedback';
|
||||||
|
feedback.classList.add(result.correct ? 'attempt-feedback--correct' : 'attempt-feedback--incorrect');
|
||||||
|
feedback.textContent = result.message;
|
||||||
|
};
|
||||||
|
submitBtn.addEventListener('click', doCheck, { signal: ac.signal });
|
||||||
|
attemptInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') doCheck();
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reveal steps
|
||||||
|
const revealBtn = document.getElementById('revealBtn');
|
||||||
|
if (revealBtn) {
|
||||||
|
revealBtn.addEventListener('click', revealNextStep, { signal: ac.signal });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init(el, params) {
|
||||||
|
container = el;
|
||||||
|
revealedSteps = 0;
|
||||||
|
currentExercise = null;
|
||||||
|
|
||||||
|
if (!params || !params.id) {
|
||||||
|
container.innerHTML = '<p>Ejercicio no especificado.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ex = await findExercise(params.id);
|
||||||
|
if (!ex) {
|
||||||
|
container.innerHTML = `<p>Ejercicio "${escapeHtml(params.id)}" no encontrado.</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentExercise = ex;
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="exercise-viewer">
|
||||||
|
<button class="exercise-viewer__back">← Volver a ejercicios</button>
|
||||||
|
${renderProblem(ex)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
renderStatement(ex);
|
||||||
|
setupEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (ac) {
|
||||||
|
ac.abort();
|
||||||
|
ac = null;
|
||||||
|
}
|
||||||
|
if (container) container.innerHTML = '';
|
||||||
|
container = null;
|
||||||
|
currentExercise = null;
|
||||||
|
revealedSteps = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init, destroy };
|
||||||
|
})();
|
||||||
286
js/components/matrix-builder.js
Normal file
286
js/components/matrix-builder.js
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/**
|
||||||
|
* MatrixBuilder — Size selector (2x2/3x3/4x4), input grid, operation buttons
|
||||||
|
*/
|
||||||
|
const MatrixBuilder = (() => {
|
||||||
|
let container = null;
|
||||||
|
let currentSize = 3;
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="matrix-builder">
|
||||||
|
<div class="matrix-builder__controls">
|
||||||
|
<label>Tamaño:</label>
|
||||||
|
<select class="matrix-builder__size-select" id="matrixSize">
|
||||||
|
<option value="2" ${currentSize === 2 ? 'selected' : ''}>2×2</option>
|
||||||
|
<option value="3" ${currentSize === 3 ? 'selected' : ''}>3×3</option>
|
||||||
|
<option value="4" ${currentSize === 4 ? 'selected' : ''}>4×4</option>
|
||||||
|
</select>
|
||||||
|
<button class="matrix-ops__btn" data-action="clear">Limpiar</button>
|
||||||
|
<button class="matrix-ops__btn" data-action="example">Ejemplo</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="matrixGridContainer"></div>
|
||||||
|
|
||||||
|
<div class="matrix-ops">
|
||||||
|
<button class="matrix-ops__btn" data-op="determinant">Determinante</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="transpose">Traspuesta</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="trace">Traza</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="rank">Rango</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="inverse">Inversa</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="det-sarrus">Sarrus (3×3)</button>
|
||||||
|
<button class="matrix-ops__btn" data-op="det-triangular">Triangularización</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-message" id="matrixError"></div>
|
||||||
|
<div class="matrix-result" id="matrixResult">
|
||||||
|
<div class="matrix-result__title" id="resultTitle"></div>
|
||||||
|
<div class="matrix-result__value" id="resultValue"></div>
|
||||||
|
<div id="resultSteps"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
renderGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGrid() {
|
||||||
|
const gridContainer = document.getElementById('matrixGridContainer');
|
||||||
|
if (!gridContainer) return;
|
||||||
|
let html = `<div class="matrix-grid" style="grid-template-columns: repeat(${currentSize}, 1fr); padding-left: 20px; padding-right: 20px;">`;
|
||||||
|
for (let i = 0; i < currentSize; i++) {
|
||||||
|
for (let j = 0; j < currentSize; j++) {
|
||||||
|
html += `<input type="text" class="matrix-grid__cell" id="cell-${i}-${j}" data-row="${i}" data-col="${j}">`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
gridContainer.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMatrix() {
|
||||||
|
const M = [];
|
||||||
|
let hasEmpty = false;
|
||||||
|
for (let i = 0; i < currentSize; i++) {
|
||||||
|
const row = [];
|
||||||
|
for (let j = 0; j < currentSize; j++) {
|
||||||
|
const cell = document.getElementById(`cell-${i}-${j}`);
|
||||||
|
if (!cell) return null;
|
||||||
|
const val = cell.value.trim();
|
||||||
|
if (val === '') {
|
||||||
|
cell.classList.add('matrix-grid__cell--error');
|
||||||
|
hasEmpty = true;
|
||||||
|
} else {
|
||||||
|
cell.classList.remove('matrix-grid__cell--error');
|
||||||
|
}
|
||||||
|
const num = parseFloat(val);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
cell.classList.add('matrix-grid__cell--error');
|
||||||
|
hasEmpty = true;
|
||||||
|
}
|
||||||
|
row.push(num || 0);
|
||||||
|
}
|
||||||
|
M.push(row);
|
||||||
|
}
|
||||||
|
if (hasEmpty) {
|
||||||
|
showError('Completá todas las celdas con valores numéricos.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
hideError();
|
||||||
|
return M;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(msg) {
|
||||||
|
const el = document.getElementById('matrixError');
|
||||||
|
if (el) {
|
||||||
|
el.textContent = msg;
|
||||||
|
el.classList.add('error-message--visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideError() {
|
||||||
|
const el = document.getElementById('matrixError');
|
||||||
|
if (el) el.classList.remove('error-message--visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResult(title, value, steps) {
|
||||||
|
const resultEl = document.getElementById('matrixResult');
|
||||||
|
const titleEl = document.getElementById('resultTitle');
|
||||||
|
const valueEl = document.getElementById('resultValue');
|
||||||
|
const stepsEl = document.getElementById('resultSteps');
|
||||||
|
|
||||||
|
titleEl.textContent = title;
|
||||||
|
resultEl.classList.add('matrix-result--visible');
|
||||||
|
|
||||||
|
// Render value
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(valueEl, `\\text{${title}} = ${MathEngine.fmtNum(value)}`);
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = `${title} = ${value}`;
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(value) && Array.isArray(value[0])) {
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(valueEl, MathEngine.matToLatex(value));
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(valueEl, MathEngine.vecToLatex(value));
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
valueEl.textContent = JSON.stringify(value);
|
||||||
|
} else if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(valueEl, String(value));
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render steps
|
||||||
|
stepsEl.innerHTML = '';
|
||||||
|
if (steps && steps.length > 0) {
|
||||||
|
const ol = document.createElement('ol');
|
||||||
|
ol.className = 'matrix-result__steps';
|
||||||
|
steps.forEach((step, idx) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'matrix-result__step';
|
||||||
|
const descDiv = document.createElement('div');
|
||||||
|
descDiv.textContent = step.desc;
|
||||||
|
const exprDiv = document.createElement('div');
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(exprDiv, step.latex || step.expression || '');
|
||||||
|
} else {
|
||||||
|
exprDiv.textContent = step.latex || step.expression || '';
|
||||||
|
}
|
||||||
|
li.appendChild(descDiv);
|
||||||
|
li.appendChild(exprDiv);
|
||||||
|
ol.appendChild(li);
|
||||||
|
});
|
||||||
|
stepsEl.appendChild(ol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeOperation(op) {
|
||||||
|
const M = getMatrix();
|
||||||
|
if (!M) return;
|
||||||
|
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
const ME = MathEngine;
|
||||||
|
switch (op) {
|
||||||
|
case 'determinant':
|
||||||
|
if (currentSize === 2) result = ME.determinant.det2x2(M);
|
||||||
|
else if (currentSize === 3) result = ME.determinant.det3x3Sarrus(M);
|
||||||
|
else result = ME.determinant.det(M);
|
||||||
|
break;
|
||||||
|
case 'det-sarrus':
|
||||||
|
if (currentSize !== 3) { showError('Sarrus solo aplica a matrices 3×3.'); return; }
|
||||||
|
result = ME.determinant.det3x3Sarrus(M);
|
||||||
|
break;
|
||||||
|
case 'det-triangular':
|
||||||
|
result = ME.determinant.detByTriangularization(M);
|
||||||
|
break;
|
||||||
|
case 'transpose':
|
||||||
|
result = ME.matrix.transpose(M);
|
||||||
|
break;
|
||||||
|
case 'trace':
|
||||||
|
result = ME.matrix.trace(M);
|
||||||
|
break;
|
||||||
|
case 'rank':
|
||||||
|
result = ME.system.rank(M);
|
||||||
|
break;
|
||||||
|
case 'inverse':
|
||||||
|
if (currentSize === 2) result = ME.inverse.inverse2x2(M);
|
||||||
|
else result = ME.inverse.inverse(M);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showError('Operación no reconocida.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
showError(result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles = {
|
||||||
|
'determinant': 'Determinante',
|
||||||
|
'det-sarrus': 'Determinante (Sarrus)',
|
||||||
|
'det-triangular': 'Determinante (Triangularización)',
|
||||||
|
'transpose': 'Traspuesta',
|
||||||
|
'trace': 'Traza',
|
||||||
|
'rank': 'Rango',
|
||||||
|
'inverse': 'Inversa'
|
||||||
|
};
|
||||||
|
showResult(titles[op] || op, result.value, result.steps);
|
||||||
|
} catch (e) {
|
||||||
|
showError('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearGrid() {
|
||||||
|
for (let i = 0; i < currentSize; i++)
|
||||||
|
for (let j = 0; j < currentSize; j++) {
|
||||||
|
const cell = document.getElementById(`cell-${i}-${j}`);
|
||||||
|
if (cell) { cell.value = ''; cell.classList.remove('matrix-grid__cell--error'); }
|
||||||
|
}
|
||||||
|
const resultEl = document.getElementById('matrixResult');
|
||||||
|
if (resultEl) resultEl.classList.remove('matrix-result--visible');
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillExample() {
|
||||||
|
const examples = {
|
||||||
|
2: [[1, 2], [3, 4]],
|
||||||
|
3: [[1, 2, 3], [4, 5, 2], [6, 3, 1]],
|
||||||
|
4: [[1, 2, 0, 1], [3, 0, 1, 2], [1, 1, 2, 0], [2, 3, 1, 1]]
|
||||||
|
};
|
||||||
|
const ex = examples[currentSize];
|
||||||
|
for (let i = 0; i < currentSize; i++)
|
||||||
|
for (let j = 0; j < currentSize; j++) {
|
||||||
|
const cell = document.getElementById(`cell-${i}-${j}`);
|
||||||
|
if (cell) cell.value = ex[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSizeChange(e) {
|
||||||
|
currentSize = parseInt(e.target.value);
|
||||||
|
renderGrid();
|
||||||
|
const resultEl = document.getElementById('matrixResult');
|
||||||
|
if (resultEl) resultEl.classList.remove('matrix-result--visible');
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpClick(e) {
|
||||||
|
const btn = e.target.closest('[data-op]');
|
||||||
|
if (!btn) return;
|
||||||
|
executeOperation(btn.dataset.op);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleActionClick(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'clear') clearGrid();
|
||||||
|
else if (action === 'example') fillExample();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(el) {
|
||||||
|
container = el;
|
||||||
|
render();
|
||||||
|
container.addEventListener('change', (e) => {
|
||||||
|
if (e.target.id === 'matrixSize') handleSizeChange(e);
|
||||||
|
});
|
||||||
|
container.addEventListener('click', handleOpClick);
|
||||||
|
container.addEventListener('click', handleActionClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
container = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init, destroy, clearGrid, fillExample };
|
||||||
|
})();
|
||||||
310
js/components/system-solver.js
Normal file
310
js/components/system-solver.js
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* SystemSolver — System input, method selector, step-by-step solution display
|
||||||
|
*/
|
||||||
|
const SystemSolver = (() => {
|
||||||
|
let container = null;
|
||||||
|
let currentSize = 3;
|
||||||
|
let currentMethod = 'gauss';
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="system-solver">
|
||||||
|
<div class="system-solver__controls">
|
||||||
|
<label>Incógnitas:</label>
|
||||||
|
<select class="system-solver__size-select" id="systemSize">
|
||||||
|
<option value="2" ${currentSize === 2 ? 'selected' : ''}>2</option>
|
||||||
|
<option value="3" ${currentSize === 3 ? 'selected' : ''}>3</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Método:</label>
|
||||||
|
<select class="system-solver__method-select" id="systemMethod">
|
||||||
|
<option value="gauss" ${currentMethod === 'gauss' ? 'selected' : ''}>Gauss</option>
|
||||||
|
<option value="gauss-jordan" ${currentMethod === 'gauss-jordan' ? 'selected' : ''}>Gauss-Jordan</option>
|
||||||
|
<option value="cramer" ${currentMethod === 'cramer' ? 'selected' : ''}>Cramer</option>
|
||||||
|
<option value="rouche-frobenius" ${currentMethod === 'rouche-frobenius' ? 'selected' : ''}>Rouché-Frobenius</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button class="matrix-ops__btn" data-action="clear">Limpiar</button>
|
||||||
|
<button class="matrix-ops__btn" data-action="example">Ejemplo</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="system-solver__matrix-label">Matriz de coeficientes | Términos independientes</p>
|
||||||
|
<div id="systemGridContainer"></div>
|
||||||
|
|
||||||
|
<button class="system-solver__solve-btn" id="solveBtn">Resolver</button>
|
||||||
|
|
||||||
|
<div class="error-message" id="systemError"></div>
|
||||||
|
<div class="matrix-result" id="systemResult">
|
||||||
|
<div class="matrix-result__title" id="resultTitle"></div>
|
||||||
|
<div class="matrix-result__value" id="resultValue"></div>
|
||||||
|
<div id="resultSteps"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
renderGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGrid() {
|
||||||
|
const gridContainer = document.getElementById('systemGridContainer');
|
||||||
|
if (!gridContainer) return;
|
||||||
|
const n = currentSize;
|
||||||
|
// n columns for coefficients + divider + 1 column for constants
|
||||||
|
let html = `<div class="augmented-grid" style="grid-template-columns: repeat(${n}, 1fr) auto 1fr; padding-left: 24px; padding-right: 24px; gap: 4px;">`;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
html += `<input type="text" class="matrix-grid__cell" id="sys-coeff-${i}-${j}" data-row="${i}" data-col="${j}" placeholder="a${i + 1}${j + 1}">`;
|
||||||
|
}
|
||||||
|
// Divider column (one per row, but we only need 1 actual divider)
|
||||||
|
if (i === 0) {
|
||||||
|
html += `<div class="augmented-grid__divider" style="grid-row: span ${n};"></div>`;
|
||||||
|
} else {
|
||||||
|
// Empty cell for grid alignment — no, the divider spans all rows
|
||||||
|
}
|
||||||
|
html += `<input type="text" class="matrix-grid__cell" id="sys-const-${i}" data-row="${i}" placeholder="b${i + 1}" style="border-color: var(--color-accent);">`;
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
gridContainer.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystem() {
|
||||||
|
const n = currentSize;
|
||||||
|
const A = [];
|
||||||
|
const b = [];
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const row = [];
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const cell = document.getElementById(`sys-coeff-${i}-${j}`);
|
||||||
|
if (!cell) return null;
|
||||||
|
const val = cell.value.trim();
|
||||||
|
if (val === '') {
|
||||||
|
cell.classList.add('matrix-grid__cell--error');
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
cell.classList.remove('matrix-grid__cell--error');
|
||||||
|
}
|
||||||
|
const num = parseFloat(val);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
cell.classList.add('matrix-grid__cell--error');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
row.push(num || 0);
|
||||||
|
}
|
||||||
|
A.push(row);
|
||||||
|
|
||||||
|
const bCell = document.getElementById(`sys-const-${i}`);
|
||||||
|
if (!bCell) return null;
|
||||||
|
const bVal = bCell.value.trim();
|
||||||
|
if (bVal === '') {
|
||||||
|
bCell.classList.add('matrix-grid__cell--error');
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
bCell.classList.remove('matrix-grid__cell--error');
|
||||||
|
}
|
||||||
|
b.push(parseFloat(bVal) || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
showError('Completá todas las celdas con valores numéricos.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
hideError();
|
||||||
|
return { A, b };
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(msg) {
|
||||||
|
const el = document.getElementById('systemError');
|
||||||
|
if (el) {
|
||||||
|
el.textContent = msg;
|
||||||
|
el.classList.add('error-message--visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideError() {
|
||||||
|
const el = document.getElementById('systemError');
|
||||||
|
if (el) el.classList.remove('error-message--visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResult(title, value, steps) {
|
||||||
|
const resultEl = document.getElementById('systemResult');
|
||||||
|
const titleEl = document.getElementById('resultTitle');
|
||||||
|
const valueEl = document.getElementById('resultValue');
|
||||||
|
const stepsEl = document.getElementById('resultSteps');
|
||||||
|
|
||||||
|
titleEl.textContent = title;
|
||||||
|
resultEl.classList.add('matrix-result--visible');
|
||||||
|
|
||||||
|
// Render value
|
||||||
|
if (typeof value === 'object' && value !== null && value.type) {
|
||||||
|
// Classification result
|
||||||
|
let text = '';
|
||||||
|
if (value.type === 'CD') text = 'Compatible Determinado';
|
||||||
|
else if (value.type === 'CI') text = `Compatible Indeterminado (${value.freeVars || '?'} parámetros libres)`;
|
||||||
|
else if (value.type === 'SI') text = 'Sistema Incompatible';
|
||||||
|
else if (value.type === 'indeterminate') text = 'Compatible Indeterminado';
|
||||||
|
else if (value.type === 'trivial') text = 'Solución trivial';
|
||||||
|
valueEl.textContent = text;
|
||||||
|
|
||||||
|
if (value.solution) {
|
||||||
|
const solDiv = document.createElement('div');
|
||||||
|
solDiv.style.marginTop = '8px';
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(solDiv, 'x = ' + MathEngine.vecToLatex(value.solution));
|
||||||
|
} else {
|
||||||
|
solDiv.textContent = 'x = ' + JSON.stringify(value.solution);
|
||||||
|
}
|
||||||
|
valueEl.appendChild(solDiv);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (typeof KatexRenderer !== 'undefined') {
|
||||||
|
KatexRenderer.renderDisplay(valueEl, 'x = ' + MathEngine.vecToLatex(value));
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = 'x = ' + JSON.stringify(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render steps
|
||||||
|
stepsEl.innerHTML = '';
|
||||||
|
if (steps && steps.length > 0) {
|
||||||
|
const ol = document.createElement('ol');
|
||||||
|
ol.className = 'matrix-result__steps';
|
||||||
|
steps.forEach((step) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'matrix-result__step';
|
||||||
|
const descDiv = document.createElement('div');
|
||||||
|
descDiv.textContent = step.desc;
|
||||||
|
const exprDiv = document.createElement('div');
|
||||||
|
if (typeof KatexRenderer !== 'undefined' && (step.latex || step.expression)) {
|
||||||
|
KatexRenderer.renderDisplay(exprDiv, step.latex || step.expression);
|
||||||
|
} else {
|
||||||
|
exprDiv.textContent = step.latex || step.expression || '';
|
||||||
|
}
|
||||||
|
li.appendChild(descDiv);
|
||||||
|
li.appendChild(exprDiv);
|
||||||
|
ol.appendChild(li);
|
||||||
|
});
|
||||||
|
stepsEl.appendChild(ol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function solve() {
|
||||||
|
const system = getSystem();
|
||||||
|
if (!system) return;
|
||||||
|
|
||||||
|
const { A, b } = system;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ME = MathEngine;
|
||||||
|
switch (currentMethod) {
|
||||||
|
case 'gauss':
|
||||||
|
result = ME.system.gaussElimination(A, b);
|
||||||
|
break;
|
||||||
|
case 'gauss-jordan':
|
||||||
|
result = ME.system.gaussJordan(A, b);
|
||||||
|
break;
|
||||||
|
case 'cramer':
|
||||||
|
result = ME.system.cramer(A, b);
|
||||||
|
break;
|
||||||
|
case 'rouche-frobenius':
|
||||||
|
result = ME.system.roucheFrobenius(A, b);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showError('Método no reconocido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
showError(result.error);
|
||||||
|
// Still show steps
|
||||||
|
if (result.steps && result.steps.length > 0) {
|
||||||
|
showResult('Clasificación', null, result.steps);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles = {
|
||||||
|
'gauss': 'Eliminación de Gauss',
|
||||||
|
'gauss-jordan': 'Gauss-Jordan',
|
||||||
|
'cramer': 'Regla de Cramer',
|
||||||
|
'rouche-frobenius': 'Rouché-Frobenius'
|
||||||
|
};
|
||||||
|
showResult(titles[currentMethod] || currentMethod, result.value, result.steps);
|
||||||
|
} catch (e) {
|
||||||
|
showError('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearGrid() {
|
||||||
|
const n = currentSize;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const cell = document.getElementById(`sys-coeff-${i}-${j}`);
|
||||||
|
if (cell) { cell.value = ''; cell.classList.remove('matrix-grid__cell--error'); }
|
||||||
|
}
|
||||||
|
const bCell = document.getElementById(`sys-const-${i}`);
|
||||||
|
if (bCell) { bCell.value = ''; bCell.classList.remove('matrix-grid__cell--error'); }
|
||||||
|
}
|
||||||
|
const resultEl = document.getElementById('systemResult');
|
||||||
|
if (resultEl) resultEl.classList.remove('matrix-result--visible');
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillExample() {
|
||||||
|
const examples = {
|
||||||
|
2: { A: [[2, 1], [1, 1]], b: [7, 4] },
|
||||||
|
3: { A: [[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], b: [8, -11, -3] }
|
||||||
|
};
|
||||||
|
const ex = examples[currentSize];
|
||||||
|
const n = currentSize;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const cell = document.getElementById(`sys-coeff-${i}-${j}`);
|
||||||
|
if (cell) cell.value = ex.A[i][j];
|
||||||
|
}
|
||||||
|
const bCell = document.getElementById(`sys-const-${i}`);
|
||||||
|
if (bCell) bCell.value = ex.b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(el) {
|
||||||
|
container = el;
|
||||||
|
render();
|
||||||
|
|
||||||
|
container.addEventListener('change', (e) => {
|
||||||
|
if (e.target.id === 'systemSize') {
|
||||||
|
currentSize = parseInt(e.target.value);
|
||||||
|
renderGrid();
|
||||||
|
const resultEl = document.getElementById('systemResult');
|
||||||
|
if (resultEl) resultEl.classList.remove('matrix-result--visible');
|
||||||
|
hideError();
|
||||||
|
}
|
||||||
|
if (e.target.id === 'systemMethod') {
|
||||||
|
currentMethod = e.target.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('click', (e) => {
|
||||||
|
if (e.target.id === 'solveBtn') solve();
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'clear') clearGrid();
|
||||||
|
else if (action === 'example') fillExample();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (container) container.innerHTML = '';
|
||||||
|
container = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init, destroy, clearGrid, fillExample };
|
||||||
|
})();
|
||||||
691
js/math-engine.js
Normal file
691
js/math-engine.js
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
/**
|
||||||
|
* MathEngine — Pure JS Linear Algebra Computation Library
|
||||||
|
* Every method returns { value, steps: [{desc, latex}] }
|
||||||
|
* Uses namespace pattern (not ES modules) for CDN compatibility.
|
||||||
|
*/
|
||||||
|
const MathEngine = (() => {
|
||||||
|
const EPS = 0.001;
|
||||||
|
|
||||||
|
// ── Helpers ──────────────────────────────────────────
|
||||||
|
function round(x) {
|
||||||
|
return Math.abs(x) < EPS ? 0 : Math.round(x * 10000) / 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneMatrix(M) {
|
||||||
|
return M.map(row => [...row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMatrix(M) {
|
||||||
|
return Array.isArray(M) && Array.isArray(M[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rows(M) { return M.length; }
|
||||||
|
function cols(M) { return M[0].length; }
|
||||||
|
|
||||||
|
function makeResult(value, steps) {
|
||||||
|
return { value, steps: steps || [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeError(msg, steps) {
|
||||||
|
return { error: msg, steps: steps || [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function vecToLatex(v) {
|
||||||
|
return '(' + v.map(x => fmtNum(x)).join(';\\;') + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
function matToLatex(M) {
|
||||||
|
const r = M.length;
|
||||||
|
const c = M[0].length;
|
||||||
|
let s = '\\begin{pmatrix}';
|
||||||
|
for (let i = 0; i < r; i++) {
|
||||||
|
s += M[i].map(x => fmtNum(x)).join(' & ');
|
||||||
|
if (i < r - 1) s += ' \\\\ ';
|
||||||
|
}
|
||||||
|
s += '\\end{pmatrix}';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtNum(n) {
|
||||||
|
if (n === undefined || n === null) return '0';
|
||||||
|
const r = round(n);
|
||||||
|
if (Number.isInteger(r)) return String(r);
|
||||||
|
return r.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── VECTOR OPERATIONS ───────────────────────────────
|
||||||
|
const vector = {
|
||||||
|
components(A, B) {
|
||||||
|
const steps = [];
|
||||||
|
const v = A.map((a, i) => round(B[i] - a));
|
||||||
|
steps.push({ desc: 'Componentes del vector AB = B - A', latex: `\\overrightarrow{AB} = ${vecToLatex(B)} - ${vecToLatex(A)} = ${vecToLatex(v)}` });
|
||||||
|
return makeResult(v, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
magnitude(v) {
|
||||||
|
const steps = [];
|
||||||
|
const squares = v.map(x => `${fmtNum(x)}^2`).join(' + ');
|
||||||
|
const sum = v.reduce((s, x) => s + x * x, 0);
|
||||||
|
const mag = round(Math.sqrt(sum));
|
||||||
|
steps.push({ desc: 'M\u00f3dulo del vector', latex: `|\\vec{v}| = \\sqrt{${squares}} = \\sqrt{${fmtNum(sum)}} = ${fmtNum(mag)}` });
|
||||||
|
return makeResult(mag, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
unitVector(v) {
|
||||||
|
const steps = [];
|
||||||
|
const mag = Math.sqrt(v.reduce((s, x) => s + x * x, 0));
|
||||||
|
if (mag < EPS) return makeError('No se puede obtener vector unitario del vector nulo');
|
||||||
|
const u = v.map(x => round(x / mag));
|
||||||
|
steps.push({ desc: 'Vector unitario = v / |v|', latex: `\\hat{u} = \\frac{${vecToLatex(v)}}{${fmtNum(mag)}} = ${vecToLatex(u)}` });
|
||||||
|
return makeResult(u, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
add(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const r = a.map((x, i) => round(x + b[i]));
|
||||||
|
steps.push({ desc: 'Suma de vectores componente a componente', latex: `${vecToLatex(a)} + ${vecToLatex(b)} = ${vecToLatex(r)}` });
|
||||||
|
return makeResult(r, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(k, v) {
|
||||||
|
const steps = [];
|
||||||
|
const r = v.map(x => round(k * x));
|
||||||
|
steps.push({ desc: 'Producto escalar', latex: `${fmtNum(k)} \\cdot ${vecToLatex(v)} = ${vecToLatex(r)}` });
|
||||||
|
return makeResult(r, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
dotProduct(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const products = a.map((x, i) => `(${fmtNum(x)})(${fmtNum(b[i])})`).join(' + ');
|
||||||
|
const val = a.reduce((s, x, i) => s + x * b[i], 0);
|
||||||
|
const rv = round(val);
|
||||||
|
steps.push({ desc: 'Producto escalar', latex: `\\vec{u} \\cdot \\vec{v} = ${products} = ${fmtNum(rv)}` });
|
||||||
|
return makeResult(rv, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
crossProduct(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const r = [
|
||||||
|
round(a[1] * b[2] - a[2] * b[1]),
|
||||||
|
round(a[2] * b[0] - a[0] * b[2]),
|
||||||
|
round(a[0] * b[1] - a[1] * b[0])
|
||||||
|
];
|
||||||
|
steps.push({ desc: 'Producto vectorial por determinante', latex: `\\vec{u} \\times \\vec{v} = \\begin{vmatrix} \\mathbf{i} & \\mathbf{j} & \\mathbf{k} \\\\ ${fmtNum(a[0])} & ${fmtNum(a[1])} & ${fmtNum(a[2])} \\\\ ${fmtNum(b[0])} & ${fmtNum(b[1])} & ${fmtNum(b[2])} \\end{vmatrix}` });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `\\vec{u} \\times \\vec{v} = ${vecToLatex(r)}` });
|
||||||
|
return makeResult(r, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
isParallel(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const cross = [
|
||||||
|
a[1] * b[2] - a[2] * b[1],
|
||||||
|
a[2] * b[0] - a[0] * b[2],
|
||||||
|
a[0] * b[1] - a[1] * b[0]
|
||||||
|
];
|
||||||
|
const isPar = cross.every(x => Math.abs(x) < EPS);
|
||||||
|
steps.push({ desc: 'Son paralelos si u \u00d7 v = 0', latex: `\\vec{u} \\times \\vec{v} = ${vecToLatex(cross.map(round))} ${isPar ? '=' : '\\neq'} \\vec{0}` });
|
||||||
|
steps.push({ desc: isPar ? 'Son paralelos' : 'No son paralelos', latex: `\\text{${isPar ? 'Son paralelos' : 'No son paralelos'}}` });
|
||||||
|
return makeResult(isPar, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
isPerpendicular(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const dot = a.reduce((s, x, i) => s + x * b[i], 0);
|
||||||
|
const isPerp = Math.abs(dot) < EPS;
|
||||||
|
steps.push({ desc: 'Son perpendiculares si u \u00b7 v = 0', latex: `\\vec{u} \\cdot \\vec{v} = ${fmtNum(round(dot))} ${isPerp ? '=' : '\\neq'} 0` });
|
||||||
|
steps.push({ desc: isPerp ? 'Son perpendiculares' : 'No son perpendiculares', latex: `\\text{${isPerp ? 'Son perpendiculares' : 'No son perpendiculares'}}` });
|
||||||
|
return makeResult(isPerp, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
mixedProduct(a, b, c) {
|
||||||
|
const steps = [];
|
||||||
|
const cross = [
|
||||||
|
b[1] * c[2] - b[2] * c[1],
|
||||||
|
b[2] * c[0] - b[0] * c[2],
|
||||||
|
b[0] * c[1] - b[1] * c[0]
|
||||||
|
];
|
||||||
|
const val = a.reduce((s, x, i) => s + x * cross[i], 0);
|
||||||
|
const rv = round(val);
|
||||||
|
steps.push({ desc: 'Producto mixto [u,v,w] = u \u00b7 (v \u00d7 w)', latex: `\\vec{v} \\times \\vec{w} = ${vecToLatex(cross.map(round))}` });
|
||||||
|
steps.push({ desc: 'Producto escalar con u', latex: `[\\vec{u},\\vec{v},\\vec{w}] = ${vecToLatex(a)} \\cdot ${vecToLatex(cross.map(round))} = ${fmtNum(rv)}` });
|
||||||
|
return makeResult(rv, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
isCoplanar(a, b, c) {
|
||||||
|
const steps = [];
|
||||||
|
const mp = vector.mixedProduct(a, b, c);
|
||||||
|
const isCo = Math.abs(mp.value) < EPS;
|
||||||
|
steps.push(...mp.steps);
|
||||||
|
steps.push({ desc: isCo ? 'Son coplanarios (producto mixto = 0)' : 'No son coplanarios (producto mixto \u2260 0)', latex: `[\\vec{u},\\vec{v},\\vec{w}] ${isCo ? '=' : '\\neq'} 0 \\Rightarrow \\text{${isCo ? 'Coplanarios' : 'No coplanarios'}}` });
|
||||||
|
return makeResult(isCo, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
angle(a, b) {
|
||||||
|
const steps = [];
|
||||||
|
const dot = a.reduce((s, x, i) => s + x * b[i], 0);
|
||||||
|
const magA = Math.sqrt(a.reduce((s, x) => s + x * x, 0));
|
||||||
|
const magB = Math.sqrt(b.reduce((s, x) => s + x * x, 0));
|
||||||
|
if (magA < EPS || magB < EPS) return makeError('Vector nulo no tiene \u00e1ngulo definido');
|
||||||
|
const cosA = dot / (magA * magB);
|
||||||
|
const clamped = Math.max(-1, Math.min(1, cosA));
|
||||||
|
const angleRad = Math.acos(clamped);
|
||||||
|
const angleDeg = round(angleRad * 180 / Math.PI);
|
||||||
|
steps.push({ desc: '\u00c1ngulo entre vectores', latex: `\\cos\\alpha = \\frac{\\vec{u} \\cdot \\vec{v}}{|\\vec{u}||\\vec{v}|} = \\frac{${fmtNum(round(dot))}}{${fmtNum(round(magA))} \\cdot ${fmtNum(round(magB))}} = ${fmtNum(round(cosA))}` });
|
||||||
|
steps.push({ desc: '\u00c1ngulo en grados', latex: `\\alpha = \\arccos(${fmtNum(round(cosA))}) = ${fmtNum(angleDeg)}^\\circ` });
|
||||||
|
return makeResult(angleDeg, steps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── MATRIX OPERATIONS ───────────────────────────────
|
||||||
|
const matrix = {
|
||||||
|
create(rows, cols, fill = 0) {
|
||||||
|
return Array.from({ length: rows }, () => Array(cols).fill(fill));
|
||||||
|
},
|
||||||
|
|
||||||
|
identity(n) {
|
||||||
|
const M = matrix.create(n, n);
|
||||||
|
for (let i = 0; i < n; i++) M[i][i] = 1;
|
||||||
|
return M;
|
||||||
|
},
|
||||||
|
|
||||||
|
zeros(r, c) {
|
||||||
|
if (c === undefined) c = r;
|
||||||
|
return matrix.create(r, c, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
add(A, B) {
|
||||||
|
const steps = [];
|
||||||
|
const r = rows(A), c = cols(A);
|
||||||
|
const R = matrix.create(r, c);
|
||||||
|
for (let i = 0; i < r; i++)
|
||||||
|
for (let j = 0; j < c; j++)
|
||||||
|
R[i][j] = round(A[i][j] + B[i][j]);
|
||||||
|
steps.push({ desc: 'Suma componente a componente', latex: `${matToLatex(A)} + ${matToLatex(B)} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
subtract(A, B) {
|
||||||
|
const steps = [];
|
||||||
|
const r = rows(A), c = cols(A);
|
||||||
|
const R = matrix.create(r, c);
|
||||||
|
for (let i = 0; i < r; i++)
|
||||||
|
for (let j = 0; j < c; j++)
|
||||||
|
R[i][j] = round(A[i][j] - B[i][j]);
|
||||||
|
steps.push({ desc: 'Resta componente a componente', latex: `${matToLatex(A)} - ${matToLatex(B)} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(k, A) {
|
||||||
|
const steps = [];
|
||||||
|
const R = A.map(row => row.map(x => round(k * x)));
|
||||||
|
steps.push({ desc: 'Producto escalar por la matriz', latex: `${fmtNum(k)} \\cdot ${matToLatex(A)} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
multiply(A, B) {
|
||||||
|
const steps = [];
|
||||||
|
const rA = rows(A), cA = cols(A), cB = cols(B);
|
||||||
|
if (cA !== B.length) return makeError(`Dimensiones incompatibles: ${rA}\u00d7${cA} por ${B.length}\u00d7${cB}`);
|
||||||
|
const R = matrix.create(rA, cB);
|
||||||
|
let detail = '';
|
||||||
|
for (let i = 0; i < rA; i++) {
|
||||||
|
for (let j = 0; j < cB; j++) {
|
||||||
|
let sum = 0;
|
||||||
|
const terms = [];
|
||||||
|
for (let k = 0; k < cA; k++) {
|
||||||
|
sum += A[i][k] * B[k][j];
|
||||||
|
terms.push(`(${fmtNum(A[i][k])})(${fmtNum(B[k][j])})`);
|
||||||
|
}
|
||||||
|
R[i][j] = round(sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps.push({ desc: `Producto de matrices ${rA}\u00d7${cA} \u00b7 ${B.length}\u00d7${cB}`, latex: `${matToLatex(A)} \\cdot ${matToLatex(B)} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
transpose(A) {
|
||||||
|
const steps = [];
|
||||||
|
const r = rows(A), c = cols(A);
|
||||||
|
const R = matrix.create(c, r);
|
||||||
|
for (let i = 0; i < r; i++)
|
||||||
|
for (let j = 0; j < c; j++)
|
||||||
|
R[j][i] = A[i][j];
|
||||||
|
steps.push({ desc: 'Traspuesta: intercambiar filas por columnas', latex: `(${matToLatex(A)})^T = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
isSymmetric(A) {
|
||||||
|
const n = rows(A);
|
||||||
|
for (let i = 0; i < n; i++)
|
||||||
|
for (let j = 0; j < i; j++)
|
||||||
|
if (Math.abs(A[i][j] - A[j][i]) > EPS) return makeResult(false, [{ desc: 'No es sim\u00e9trica', latex: `a_{${i}${j}} = ${fmtNum(A[i][j])} \\neq a_{${j}${i}} = ${fmtNum(A[j][i])}` }]);
|
||||||
|
return makeResult(true, [{ desc: 'La matriz es sim\u00e9trica', latex: 'A = A^T' }]);
|
||||||
|
},
|
||||||
|
|
||||||
|
trace(A) {
|
||||||
|
const steps = [];
|
||||||
|
const n = rows(A);
|
||||||
|
const diag = [];
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
sum += A[i][i];
|
||||||
|
diag.push(`a_{${i + 1}${i + 1}} = ${fmtNum(A[i][i])}`);
|
||||||
|
}
|
||||||
|
steps.push({ desc: 'Traza = suma de la diagonal', latex: `\\text{tr}(A) = ${diag.join(' + ')} = ${fmtNum(round(sum))}` });
|
||||||
|
return makeResult(round(sum), steps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── DETERMINANT OPERATIONS ──────────────────────────
|
||||||
|
const determinant = {
|
||||||
|
det2x2(A) {
|
||||||
|
const steps = [];
|
||||||
|
const val = A[0][0] * A[1][1] - A[0][1] * A[1][0];
|
||||||
|
const rv = round(val);
|
||||||
|
steps.push({ desc: 'Determinante 2\u00d72', latex: `\\det ${matToLatex(A)} = (${fmtNum(A[0][0])})(${fmtNum(A[1][1])}) - (${fmtNum(A[0][1])})(${fmtNum(A[1][0])})` });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `= ${fmtNum(A[0][0] * A[1][1])} - ${fmtNum(A[0][1] * A[1][0])} = ${fmtNum(rv)}` });
|
||||||
|
return makeResult(rv, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
det3x3Sarrus(A) {
|
||||||
|
const steps = [];
|
||||||
|
steps.push({ desc: 'Regla de Sarrus', latex: `\\det ${matToLatex(A)}` });
|
||||||
|
const pos =
|
||||||
|
A[0][0] * A[1][1] * A[2][2] +
|
||||||
|
A[0][1] * A[1][2] * A[2][0] +
|
||||||
|
A[0][2] * A[1][0] * A[2][1];
|
||||||
|
const neg =
|
||||||
|
A[0][2] * A[1][1] * A[2][0] +
|
||||||
|
A[0][0] * A[1][2] * A[2][1] +
|
||||||
|
A[0][1] * A[1][0] * A[2][2];
|
||||||
|
const val = pos - neg;
|
||||||
|
const rv = round(val);
|
||||||
|
steps.push({ desc: 'Diagonales positivas', latex: `+${fmtNum(A[0][0])} \\cdot ${fmtNum(A[1][1])} \\cdot ${fmtNum(A[2][2])} + ${fmtNum(A[0][1])} \\cdot ${fmtNum(A[1][2])} \\cdot ${fmtNum(A[2][0])} + ${fmtNum(A[0][2])} \\cdot ${fmtNum(A[1][0])} \\cdot ${fmtNum(A[2][1])}` });
|
||||||
|
steps.push({ desc: 'Suma positivas', latex: `= ${fmtNum(round(pos))}` });
|
||||||
|
steps.push({ desc: 'Diagonales negativas', latex: `-${fmtNum(A[0][2])} \\cdot ${fmtNum(A[1][1])} \\cdot ${fmtNum(A[2][0])} - ${fmtNum(A[0][0])} \\cdot ${fmtNum(A[1][2])} \\cdot ${fmtNum(A[2][1])} - ${fmtNum(A[0][1])} \\cdot ${fmtNum(A[1][0])} \\cdot ${fmtNum(A[2][2])}` });
|
||||||
|
steps.push({ desc: 'Suma negativas', latex: `= -${fmtNum(round(neg))}` });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `${fmtNum(round(pos))} - ${fmtNum(round(neg))} = ${fmtNum(rv)}` });
|
||||||
|
return makeResult(rv, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
cofactor(A, row, col) {
|
||||||
|
const n = rows(A);
|
||||||
|
const sub = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (i === row) continue;
|
||||||
|
const r = [];
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
if (j === col) continue;
|
||||||
|
r.push(A[i][j]);
|
||||||
|
}
|
||||||
|
sub.push(r);
|
||||||
|
}
|
||||||
|
const sign = (row + col) % 2 === 0 ? 1 : -1;
|
||||||
|
const subDet = determinant.det(sub);
|
||||||
|
const val = sign * subDet.value;
|
||||||
|
const rv = round(val);
|
||||||
|
const fullSteps = [];
|
||||||
|
fullSteps.push({ desc: `Cofactor C_{${row + 1}${col + 1}}`, latex: `C_{${row + 1}${col + 1}} = ${sign > 0 ? '' : '-'} \\det ${matToLatex(sub)}` });
|
||||||
|
fullSteps.push(...subDet.steps);
|
||||||
|
fullSteps.push({ desc: 'Valor del cofactor', latex: `C_{${row + 1}${col + 1}} = ${fmtNum(rv)}` });
|
||||||
|
return makeResult(rv, fullSteps);
|
||||||
|
},
|
||||||
|
|
||||||
|
det(A) {
|
||||||
|
const n = rows(A);
|
||||||
|
if (n === 1) return makeResult(A[0][0], [{ desc: 'Determinante 1\u00d71', latex: `\\det = ${fmtNum(A[0][0])}` }]);
|
||||||
|
if (n === 2) return determinant.det2x2(A);
|
||||||
|
if (n === 3) return determinant.det3x3Sarrus(A);
|
||||||
|
// Generic cofactor expansion along first row
|
||||||
|
const steps = [];
|
||||||
|
steps.push({ desc: `Expansi\u00f3n por cofactores (fila 1)`, latex: `\\det ${matToLatex(A)}` });
|
||||||
|
let total = 0;
|
||||||
|
const parts = [];
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const sign = j % 2 === 0 ? 1 : -1;
|
||||||
|
const subDet = determinant.cofactor(A, 0, j);
|
||||||
|
const contrib = sign * A[0][j] * subDet.value;
|
||||||
|
total += contrib;
|
||||||
|
parts.push(`${sign > 0 ? '' : '-'} ${fmtNum(A[0][j])} \\cdot C_{1${j + 1}}`);
|
||||||
|
steps.push(...subDet.steps);
|
||||||
|
}
|
||||||
|
steps.push({ desc: 'Expansi\u00f3n completa', latex: parts.join(' + ') });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `= ${fmtNum(round(total))}` });
|
||||||
|
return makeResult(round(total), steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
detByTriangularization(A) {
|
||||||
|
const steps = [];
|
||||||
|
const n = rows(A);
|
||||||
|
const M = cloneMatrix(A);
|
||||||
|
let sign = 1;
|
||||||
|
steps.push({ desc: 'Triangularizaci\u00f3n', latex: `\\det ${matToLatex(M)}` });
|
||||||
|
for (let col = 0; col < n; col++) {
|
||||||
|
// Find pivot
|
||||||
|
let pivotRow = -1;
|
||||||
|
for (let i = col; i < n; i++) {
|
||||||
|
if (Math.abs(M[i][col]) > EPS) { pivotRow = i; break; }
|
||||||
|
}
|
||||||
|
if (pivotRow === -1) {
|
||||||
|
steps.push({ desc: 'Columna sin pivote \u2192 determinante 0', latex: `\\det = 0` });
|
||||||
|
return makeResult(0, steps);
|
||||||
|
}
|
||||||
|
if (pivotRow !== col) {
|
||||||
|
[M[col], M[pivotRow]] = [M[pivotRow], M[col]];
|
||||||
|
sign *= -1;
|
||||||
|
steps.push({ desc: `F${col + 1} \u2194 F${pivotRow + 1} (cambia signo)`, latex: matToLatex(M) });
|
||||||
|
}
|
||||||
|
for (let i = col + 1; i < n; i++) {
|
||||||
|
if (Math.abs(M[i][col]) > EPS) {
|
||||||
|
const factor = M[i][col] / M[col][col];
|
||||||
|
for (let j = col; j < n; j++) {
|
||||||
|
M[i][j] = round(M[i][j] - factor * M[col][j]);
|
||||||
|
}
|
||||||
|
steps.push({ desc: `F${i + 1} \u2192 F${i + 1} - (${fmtNum(round(factor))})F${col + 1}`, latex: matToLatex(M) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let detVal = sign;
|
||||||
|
for (let i = 0; i < n; i++) detVal *= M[i][i];
|
||||||
|
detVal = round(detVal);
|
||||||
|
const diagParts = [];
|
||||||
|
for (let i = 0; i < n; i++) diagParts.push(fmtNum(M[i][i]));
|
||||||
|
steps.push({ desc: 'Producto de la diagonal', latex: `\\det = ${sign < 0 ? '-' : ''}${diagParts.join(' \\cdot ')} = ${fmtNum(detVal)}` });
|
||||||
|
return makeResult(detVal, steps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── INVERSE OPERATIONS ──────────────────────────────
|
||||||
|
const inverse = {
|
||||||
|
inverse2x2(A) {
|
||||||
|
const steps = [];
|
||||||
|
const detResult = determinant.det2x2(A);
|
||||||
|
steps.push(...detResult.steps);
|
||||||
|
const det = detResult.value;
|
||||||
|
if (Math.abs(det) < EPS) {
|
||||||
|
steps.push({ desc: 'Matriz no invertible (det = 0)', latex: '\\det = 0 \\Rightarrow A^{-1} \\text{ no existe}' });
|
||||||
|
return makeError('Matriz no invertible (determinante nulo)', steps);
|
||||||
|
}
|
||||||
|
const R = [[A[1][1] / det, -A[0][1] / det], [-A[1][0] / det, A[0][0] / det]].map(r => r.map(x => round(x)));
|
||||||
|
steps.push({ desc: 'Inversa 2\u00d72: intercambiar diagonal, negar antidiagonal, dividir por det', latex: `A^{-1} = \\frac{1}{${fmtNum(det)}} \\begin{pmatrix} ${fmtNum(A[1][1])} & ${fmtNum(-A[0][1])} \\\\ ${fmtNum(-A[1][0])} & ${fmtNum(A[0][0])} \\end{pmatrix}` });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `A^{-1} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
inverse(A) {
|
||||||
|
const n = rows(A);
|
||||||
|
if (n === 2) return inverse.inverse2x2(A);
|
||||||
|
const steps = [];
|
||||||
|
const detResult = determinant.det(A);
|
||||||
|
steps.push(...detResult.steps);
|
||||||
|
const det = detResult.value;
|
||||||
|
if (Math.abs(det) < EPS) {
|
||||||
|
steps.push({ desc: 'Matriz no invertible (det = 0)', latex: '\\det = 0 \\Rightarrow A^{-1} \\text{ no existe}' });
|
||||||
|
return makeError('Matriz no invertible (determinante nulo)', steps);
|
||||||
|
}
|
||||||
|
// Build adjugate (transpose of cofactor matrix)
|
||||||
|
steps.push({ desc: 'Construir la adjunta (traspuesta de la matriz de cofactores)', latex: `\\text{Adj}(A)` });
|
||||||
|
const adj = matrix.create(n, n);
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const cf = determinant.cofactor(A, i, j);
|
||||||
|
adj[j][i] = cf.value; // note: transposing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const R = adj.map(row => row.map(x => round(x / det)));
|
||||||
|
steps.push({ desc: 'Dividir adjunta por determinante', latex: `A^{-1} = \\frac{1}{${fmtNum(det)}} \\cdot ${matToLatex(adj)}` });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `A^{-1} = ${matToLatex(R)}` });
|
||||||
|
return makeResult(R, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
isInvertible(A) {
|
||||||
|
const det = determinant.det(A);
|
||||||
|
const inv = Math.abs(det.value) > EPS;
|
||||||
|
return makeResult(inv, [...det.steps, { desc: inv ? 'Es invertible (det \u2260 0)' : 'No es invertible (det = 0)', latex: inv ? '\\det(A) \\neq 0 \\Rightarrow \\text{Invertible}' : '\\det(A) = 0 \\Rightarrow \\text{No invertible}' }]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── SYSTEM SOLVERS ──────────────────────────────────
|
||||||
|
const system = {
|
||||||
|
rank(A) {
|
||||||
|
const steps = [];
|
||||||
|
const M = cloneMatrix(A);
|
||||||
|
const r = rows(M), c = cols(M);
|
||||||
|
const n = Math.min(r, c);
|
||||||
|
let rk = 0;
|
||||||
|
steps.push({ desc: 'Reducir a forma escalonada', latex: matToLatex(M) });
|
||||||
|
for (let col = 0; col < c && rk < r; col++) {
|
||||||
|
let pivotRow = -1;
|
||||||
|
for (let i = rk; i < r; i++) {
|
||||||
|
if (Math.abs(M[i][col]) > EPS) { pivotRow = i; break; }
|
||||||
|
}
|
||||||
|
if (pivotRow === -1) continue;
|
||||||
|
if (pivotRow !== rk) {
|
||||||
|
[M[rk], M[pivotRow]] = [M[pivotRow], M[rk]];
|
||||||
|
steps.push({ desc: `F${rk + 1} \u2194 F${pivotRow + 1}`, latex: matToLatex(M) });
|
||||||
|
}
|
||||||
|
const pivot = M[rk][col];
|
||||||
|
for (let i = rk + 1; i < r; i++) {
|
||||||
|
if (Math.abs(M[i][col]) > EPS) {
|
||||||
|
const factor = M[i][col] / pivot;
|
||||||
|
for (let j = col; j < c; j++) {
|
||||||
|
M[i][j] = round(M[i][j] - factor * M[rk][j]);
|
||||||
|
}
|
||||||
|
steps.push({ desc: `F${i + 1} \u2192 F${i + 1} - (${fmtNum(round(factor))})F${rk + 1}`, latex: matToLatex(M) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rk++;
|
||||||
|
}
|
||||||
|
steps.push({ desc: `Rango = ${rk}`, latex: `\\text{rg}(A) = ${rk}` });
|
||||||
|
return makeResult(rk, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
gaussElimination(A, b) {
|
||||||
|
const steps = [];
|
||||||
|
const n = A.length;
|
||||||
|
const aug = A.map((row, i) => [...row, b[i]]);
|
||||||
|
|
||||||
|
steps.push({ desc: 'Sistema aumentado inicial', latex: matToLatex(aug) });
|
||||||
|
|
||||||
|
// Forward elimination
|
||||||
|
for (let col = 0; col < n; col++) {
|
||||||
|
let pivotRow = -1;
|
||||||
|
for (let i = col; i < n; i++) {
|
||||||
|
if (Math.abs(aug[i][col]) > EPS) { pivotRow = i; break; }
|
||||||
|
}
|
||||||
|
if (pivotRow === -1) {
|
||||||
|
// Check for incompatibility
|
||||||
|
for (let i = col; i < n; i++) {
|
||||||
|
if (Math.abs(aug[i][n]) > EPS) {
|
||||||
|
steps.push({ desc: 'Sistema incompatible', latex: `0 = ${fmtNum(round(aug[i][n]))} \\Rightarrow \\text{Incompatible}` });
|
||||||
|
return makeError('Sistema incompatible', steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pivotRow !== col) {
|
||||||
|
[aug[col], aug[pivotRow]] = [aug[pivotRow], aug[col]];
|
||||||
|
steps.push({ desc: `F${col + 1} \u2194 F${pivotRow + 1}`, latex: matToLatex(aug) });
|
||||||
|
}
|
||||||
|
for (let i = col + 1; i < n; i++) {
|
||||||
|
if (Math.abs(aug[i][col]) > EPS) {
|
||||||
|
const factor = aug[i][col] / aug[col][col];
|
||||||
|
for (let j = col; j <= n; j++) {
|
||||||
|
aug[i][j] = round(aug[i][j] - factor * aug[col][j]);
|
||||||
|
}
|
||||||
|
steps.push({ desc: `F${i + 1} \u2192 F${i + 1} - (${fmtNum(round(factor))})F${col + 1}`, latex: matToLatex(aug) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for indeterminate
|
||||||
|
const rankA = aug.filter(row => row.slice(0, n).some(x => Math.abs(x) > EPS)).length;
|
||||||
|
if (rankA < n) {
|
||||||
|
steps.push({ desc: `Sistema compatible indeterminado (rango = ${rankA} < ${n})`, latex: `\\text{rg}(A) = ${rankA} < ${n} \\Rightarrow \\text{CI}` });
|
||||||
|
return makeResult({ type: 'indeterminate', rank: rankA, augmented: aug }, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back substitution
|
||||||
|
const x = new Array(n);
|
||||||
|
for (let i = n - 1; i >= 0; i--) {
|
||||||
|
let sum = aug[i][n];
|
||||||
|
for (let j = i + 1; j < n; j++) {
|
||||||
|
sum -= aug[i][j] * x[j];
|
||||||
|
}
|
||||||
|
x[i] = round(sum / aug[i][i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.push({ desc: 'Sustituci\u00f3n regresiva', latex: `x = ${vecToLatex(x)}` });
|
||||||
|
return makeResult(x, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
gaussJordan(A, b) {
|
||||||
|
const steps = [];
|
||||||
|
const n = A.length;
|
||||||
|
const aug = A.map((row, i) => [...row, b[i]]);
|
||||||
|
steps.push({ desc: 'Matriz aumentada', latex: matToLatex(aug) });
|
||||||
|
|
||||||
|
// Forward elimination
|
||||||
|
let pivotCols = [];
|
||||||
|
let rowIdx = 0;
|
||||||
|
for (let col = 0; col < n && rowIdx < n; col++) {
|
||||||
|
let pivotRow = -1;
|
||||||
|
for (let i = rowIdx; i < n; i++) {
|
||||||
|
if (Math.abs(aug[i][col]) > EPS) { pivotRow = i; break; }
|
||||||
|
}
|
||||||
|
if (pivotRow === -1) continue;
|
||||||
|
if (pivotRow !== rowIdx) {
|
||||||
|
[aug[rowIdx], aug[pivotRow]] = [aug[pivotRow], aug[rowIdx]];
|
||||||
|
steps.push({ desc: `F${rowIdx + 1} \u2194 F${pivotRow + 1}`, latex: matToLatex(aug) });
|
||||||
|
}
|
||||||
|
const pivot = aug[rowIdx][col];
|
||||||
|
for (let j = col; j <= n; j++) aug[rowIdx][j] = round(aug[rowIdx][j] / pivot);
|
||||||
|
steps.push({ desc: `F${rowIdx + 1} \u2192 F${rowIdx + 1} / ${fmtNum(round(pivot))}`, latex: matToLatex(aug) });
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (i !== rowIdx && Math.abs(aug[i][col]) > EPS) {
|
||||||
|
const factor = aug[i][col];
|
||||||
|
for (let j = col; j <= n; j++) {
|
||||||
|
aug[i][j] = round(aug[i][j] - factor * aug[rowIdx][j]);
|
||||||
|
}
|
||||||
|
steps.push({ desc: `F${i + 1} \u2192 F${i + 1} - (${fmtNum(round(factor))})F${rowIdx + 1}`, latex: matToLatex(aug) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pivotCols.push(col);
|
||||||
|
rowIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rankA = pivotCols.length;
|
||||||
|
// Check for incompatibility
|
||||||
|
for (let i = rankA; i < n; i++) {
|
||||||
|
if (Math.abs(aug[i][n]) > EPS) {
|
||||||
|
steps.push({ desc: 'Sistema incompatible', latex: `0 = ${fmtNum(round(aug[i][n]))}` });
|
||||||
|
return makeError('Sistema incompatible (SI)', steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rankA < n) {
|
||||||
|
steps.push({ desc: `Sistema compatible indeterminado (rango = ${rankA})`, latex: `\\text{rg}(A) = ${rankA} < ${n}` });
|
||||||
|
const x = new Array(n).fill(0);
|
||||||
|
for (let i = 0; i < rankA; i++) x[pivotCols[i]] = round(aug[i][n]);
|
||||||
|
return makeResult({ type: 'indeterminate', rank: rankA, solution: x, augmented: aug, pivotCols }, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = aug.map(row => round(row[n]));
|
||||||
|
steps.push({ desc: 'Soluci\u00f3n', latex: `x = ${vecToLatex(x)}` });
|
||||||
|
return makeResult(x, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
cramer(A, b) {
|
||||||
|
const steps = [];
|
||||||
|
const n = A.length;
|
||||||
|
const detA = determinant.det(A);
|
||||||
|
steps.push({ desc: 'Determinante del sistema', latex: `\\Delta = \\det(A) = ${fmtNum(detA.value)}` });
|
||||||
|
steps.push(...detA.steps);
|
||||||
|
|
||||||
|
if (Math.abs(detA.value) < EPS) {
|
||||||
|
steps.push({ desc: 'No se puede aplicar Cramer (det = 0)', latex: '\\Delta = 0 \\Rightarrow \\text{Cramer no aplica}' });
|
||||||
|
return makeError('Sistema con determinante nulo (Cramer no aplica)', steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = [];
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
const Aj = cloneMatrix(A);
|
||||||
|
for (let i = 0; i < n; i++) Aj[i][j] = b[i];
|
||||||
|
const detJ = determinant.det(Aj);
|
||||||
|
steps.push({ desc: `\\Delta_${j + 1}: reemplazar columna ${j + 1} por t\u00e9rminos independientes`, latex: `\\Delta_${j + 1} = \\det ${matToLatex(Aj)} = ${fmtNum(detJ.value)}` });
|
||||||
|
x.push(round(detJ.value / detA.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
const vars = x.map((v, i) => `x_{${i + 1}} = \\frac{${fmtNum(round(determinant.det(A.map((row, ii) => { const Aj = cloneMatrix(A); for (let k = 0; k < n; k++) Aj[k][i] = b[k]; return Aj; })).value))}}{${fmtNum(detA.value)}} = ${fmtNum(v)}`).join(', \\; ');
|
||||||
|
steps.push({ desc: 'Soluci\u00f3n por Cramer', latex: vars });
|
||||||
|
steps.push({ desc: 'Resultado', latex: `x = ${vecToLatex(x)}` });
|
||||||
|
return makeResult(x, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
roucheFrobenius(A, b) {
|
||||||
|
const steps = [];
|
||||||
|
const n = A.length;
|
||||||
|
const augMatrix = A.map((row, i) => [...row, b[i]]);
|
||||||
|
const rankA = system.rank(cloneMatrix(A));
|
||||||
|
const rankAug = system.rank(cloneMatrix(augMatrix));
|
||||||
|
steps.push(...rankA.steps);
|
||||||
|
steps.push(...rankAug.steps);
|
||||||
|
|
||||||
|
const rA = rankA.value;
|
||||||
|
const rAug = rankAug.value;
|
||||||
|
|
||||||
|
let classification;
|
||||||
|
if (rA !== rAug) {
|
||||||
|
classification = { type: 'SI', label: 'Sistema Incompatible' };
|
||||||
|
} else if (rA === rAug && rA === n) {
|
||||||
|
classification = { type: 'CD', label: 'Compatible Determinado' };
|
||||||
|
} else {
|
||||||
|
classification = { type: 'CI', label: 'Compatible Indeterminado', freeVars: n - rA };
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.push({ desc: 'Clasificaci\u00f3n Rouche-Frobenius', latex: `\\text{rg}(A) = ${rA}, \\; \\text{rg}(A|b) = ${rAug}, \\; n = ${n} \\Rightarrow ${classification.label}` });
|
||||||
|
return makeResult(classification, steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
solveHomogeneous(A) {
|
||||||
|
const steps = [];
|
||||||
|
const n = A.length;
|
||||||
|
const b = new Array(n).fill(0);
|
||||||
|
steps.push({ desc: 'Sistema homog\u00e9neo Ax = 0', latex: matToLatex(A.map((row, i) => [...row, 0])) });
|
||||||
|
|
||||||
|
const rankA = system.rank(cloneMatrix(A));
|
||||||
|
steps.push(...rankA.steps);
|
||||||
|
|
||||||
|
if (rankA.value === n) {
|
||||||
|
steps.push({ desc: 'Rango = n, soluci\u00f3n \u00fanica trivial', latex: `\\text{rg}(A) = ${n} = n \\Rightarrow x = \\vec{0}` });
|
||||||
|
return makeResult({ type: 'trivial', solution: new Array(n).fill(0) }, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gj = system.gaussJordan(cloneMatrix(A), [...b]);
|
||||||
|
steps.push(...gj.steps);
|
||||||
|
|
||||||
|
if (gj.error) {
|
||||||
|
return makeResult({ type: 'trivial', solution: new Array(n).fill(0) }, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gj.value && gj.value.type === 'indeterminate') {
|
||||||
|
steps.push({ desc: `Soluci\u00f3n no trivial (${n - rankA.value} par\u00e1metros libres)`, latex: `\\dim(\\ker) = ${n - rankA.value}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeResult(gj.value || { type: 'trivial', solution: new Array(n).fill(0) }, steps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
EPS,
|
||||||
|
round,
|
||||||
|
cloneMatrix,
|
||||||
|
vecToLatex,
|
||||||
|
matToLatex,
|
||||||
|
fmtNum,
|
||||||
|
vector,
|
||||||
|
matrix,
|
||||||
|
determinant,
|
||||||
|
inverse,
|
||||||
|
system
|
||||||
|
};
|
||||||
|
})();
|
||||||
139
js/particles.js
Normal file
139
js/particles.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* Particles — Canvas floating math symbols background
|
||||||
|
* Full-viewport canvas with slow-drifting math symbols (Σ, π, ∫, ∞, Δ, √, α, β)
|
||||||
|
* Respects prefers-reduced-motion, disables on viewports < 768px, caps at 30 FPS.
|
||||||
|
*/
|
||||||
|
const Particles = (() => {
|
||||||
|
let canvas = null;
|
||||||
|
let ctx = null;
|
||||||
|
let particles = [];
|
||||||
|
let animId = null;
|
||||||
|
let lastTime = 0;
|
||||||
|
const FPS = 30;
|
||||||
|
const FRAME_TIME = 1000 / FPS;
|
||||||
|
|
||||||
|
const SYMBOLS = ['Σ', 'π', '∫', '∞', 'Δ', '√', 'α', 'β', 'λ', 'μ', '∂', '∑'];
|
||||||
|
|
||||||
|
function prefersReducedMotion() {
|
||||||
|
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomBetween(a, b) {
|
||||||
|
return a + Math.random() * (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Particle {
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.x = Math.random() * (canvas ? canvas.width : window.innerWidth);
|
||||||
|
this.y = Math.random() * (canvas ? canvas.height : window.innerHeight);
|
||||||
|
this.size = randomBetween(16, 32);
|
||||||
|
this.symbol = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)];
|
||||||
|
this.vx = randomBetween(-0.2, 0.2);
|
||||||
|
this.vy = randomBetween(-0.15, 0.15);
|
||||||
|
this.rotation = randomBetween(0, Math.PI * 2);
|
||||||
|
this.rotationSpeed = randomBetween(-0.005, 0.005);
|
||||||
|
this.opacity = randomBetween(0.4, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.x += this.vx;
|
||||||
|
this.y += this.vy;
|
||||||
|
this.rotation += this.rotationSpeed;
|
||||||
|
|
||||||
|
const W = canvas ? canvas.width : window.innerWidth;
|
||||||
|
const H = canvas ? canvas.height : window.innerHeight;
|
||||||
|
|
||||||
|
if (this.x < -50) this.x = W + 50;
|
||||||
|
if (this.x > W + 50) this.x = -50;
|
||||||
|
if (this.y < -50) this.y = H + 50;
|
||||||
|
if (this.y > H + 50) this.y = -50;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(this.x, this.y);
|
||||||
|
ctx.rotate(this.rotation);
|
||||||
|
ctx.font = `${this.size}px "Times New Roman", serif`;
|
||||||
|
ctx.fillStyle = `rgba(108, 92, 231, ${this.opacity})`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(this.symbol, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCanvas() {
|
||||||
|
if (window.innerWidth < 768) return;
|
||||||
|
if (prefersReducedMotion()) return;
|
||||||
|
|
||||||
|
canvas = document.getElementById('particles-bg');
|
||||||
|
if (!canvas) {
|
||||||
|
canvas = document.createElement('canvas');
|
||||||
|
canvas.id = 'particles-bg';
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
// Create particles
|
||||||
|
const count = Math.min(40, Math.floor((window.innerWidth * window.innerHeight) / 25000));
|
||||||
|
particles = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
particles.push(new Particle());
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTime = performance.now();
|
||||||
|
loop(performance.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
if (!canvas) return;
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loop(timestamp) {
|
||||||
|
animId = requestAnimationFrame(loop);
|
||||||
|
|
||||||
|
if (timestamp - lastTime < FRAME_TIME) return;
|
||||||
|
lastTime = timestamp;
|
||||||
|
|
||||||
|
if (!ctx || !canvas) return;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
for (const p of particles) {
|
||||||
|
p.update();
|
||||||
|
p.draw(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if (animId) {
|
||||||
|
cancelAnimationFrame(animId);
|
||||||
|
animId = null;
|
||||||
|
}
|
||||||
|
if (canvas && canvas.parentElement) {
|
||||||
|
canvas.parentElement.removeChild(canvas);
|
||||||
|
}
|
||||||
|
canvas = null;
|
||||||
|
ctx = null;
|
||||||
|
particles = [];
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-init on load
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initCanvas);
|
||||||
|
} else {
|
||||||
|
initCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init: initCanvas, destroy };
|
||||||
|
})();
|
||||||
133
js/utils/katex-render.js
Normal file
133
js/utils/katex-render.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* KaTeX Render Helper
|
||||||
|
* Wraps katex.render() with error handling for both display and inline modes.
|
||||||
|
* Security-hardened with strict mode and LaTeX sanitization.
|
||||||
|
*/
|
||||||
|
const KatexRenderer = {
|
||||||
|
/**
|
||||||
|
* Macro whitelist — only macros that KaTeX does NOT natively support.
|
||||||
|
* Prevents injection via \href, \url, \includegraphics, etc.
|
||||||
|
* REMOVED: Standard KaTeX commands (\vec, \overrightarrow, \cdot, \times,
|
||||||
|
* \sqrt, \frac, \pi, \alpha, \beta, \text, \implies, \neq, \infty, \lambda,
|
||||||
|
* \mu, \sum, \int, \dots, \vdots, \ddots, \hline, \det, \operatorname)
|
||||||
|
* — KaTeX already supports these natively; redefining them causes infinite
|
||||||
|
* recursion or strict mode violations.
|
||||||
|
*/
|
||||||
|
KATEX_MACROS: {
|
||||||
|
'\\ran': '\\text{ran}',
|
||||||
|
'\\nul': '\\text{nul}',
|
||||||
|
'\\rg': '\\text{rg}',
|
||||||
|
'\\sen': '\\sin',
|
||||||
|
'\\tg': '\\tan'
|
||||||
|
},
|
||||||
|
|
||||||
|
KATEX_OPTIONS: {
|
||||||
|
displayMode: true,
|
||||||
|
throwOnError: false,
|
||||||
|
trust: false,
|
||||||
|
strict: false,
|
||||||
|
macros: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize LaTeX input by removing potentially dangerous commands.
|
||||||
|
* @param {string} latex - Raw LaTeX string
|
||||||
|
* @returns {string} - Sanitized LaTeX safe for KaTeX
|
||||||
|
*/
|
||||||
|
sanitizeLatex(latex) {
|
||||||
|
if (!latex || typeof latex !== 'string') return '';
|
||||||
|
return latex
|
||||||
|
.replace(/\\href\{[^}]*\}\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\url\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\includegraphics\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\write\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\input\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\include\{[^}]*\}/g, '')
|
||||||
|
.replace(/\\href\{[^}]*\}/g, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a LaTeX expression into a DOM element.
|
||||||
|
* @param {HTMLElement|string} el - Target element or CSS selector
|
||||||
|
* @param {string} latex - LaTeX string to render
|
||||||
|
* @param {Object} options - KaTeX options override
|
||||||
|
* @returns {boolean} true if rendered successfully
|
||||||
|
*/
|
||||||
|
render(el, latex, options = {}) {
|
||||||
|
const target = typeof el === 'string' ? document.querySelector(el) : el;
|
||||||
|
if (!target) {
|
||||||
|
console.warn('[KatexRenderer] Element not found:', el);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof katex === 'undefined') {
|
||||||
|
target.textContent = latex;
|
||||||
|
console.warn('[KatexRenderer] KaTeX not loaded');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedOptions = Object.assign({}, this.KATEX_OPTIONS, {
|
||||||
|
macros: Object.assign({}, this.KATEX_MACROS, options.macros || {})
|
||||||
|
});
|
||||||
|
|
||||||
|
const sanitized = this.sanitizeLatex(latex);
|
||||||
|
|
||||||
|
try {
|
||||||
|
katex.render(sanitized, target, mergedOptions);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
target.innerHTML = '<span style="color:#e17055;font-style:italic;">[Error LaTeX]</span>';
|
||||||
|
console.warn('[KatexRenderer] Parse error:', e.message, '\nLaTeX:', sanitized);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render LaTeX in display mode (centered block).
|
||||||
|
*/
|
||||||
|
renderDisplay(el, latex) {
|
||||||
|
return this.render(el, latex, { displayMode: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render LaTeX in inline mode.
|
||||||
|
*/
|
||||||
|
renderInline(el, latex) {
|
||||||
|
return this.render(el, latex, { displayMode: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all math expressions inside a container element.
|
||||||
|
* Uses KaTeX auto-render extension for $...$ and $$...$$ delimiters.
|
||||||
|
* @param {HTMLElement} container - DOM element to scan for math
|
||||||
|
*/
|
||||||
|
renderAll(container) {
|
||||||
|
if (typeof renderMathInElement === 'undefined') {
|
||||||
|
console.warn('[KatexRenderer] auto-render extension not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMathInElement(container || document.body, {
|
||||||
|
delimiters: [
|
||||||
|
{ left: '$$', right: '$$', display: true },
|
||||||
|
{ left: '$', right: '$', display: false },
|
||||||
|
{ left: '\\(', right: '\\)', display: false },
|
||||||
|
{ left: '\\[', right: '\\]', display: true }
|
||||||
|
],
|
||||||
|
throwOnError: false,
|
||||||
|
trust: false,
|
||||||
|
strict: false,
|
||||||
|
macros: this.KATEX_MACROS
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a span with rendered math and return it.
|
||||||
|
* Useful for building DOM fragments.
|
||||||
|
*/
|
||||||
|
createMathSpan(latex, displayMode = false) {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
this.render(span, latex, { displayMode });
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user