-
+
diff --git a/src/components/QuizzesCards.js b/src/components/QuizzesCards.js
index 8d77094..5797b4c 100644
--- a/src/components/QuizzesCards.js
+++ b/src/components/QuizzesCards.js
@@ -5,6 +5,7 @@ import {
svgBinaryTrees,
svgPiles,
svgQueue,
+ svgSimpleList,
} from '../assets/svg/SvgConstans';
export const quizzes = [
@@ -16,7 +17,7 @@ export const quizzes = [
},
{
- id: 1,
+ id: 2,
imagen: svgSearchMethods,
titulo: 'Métodos de Busqueda',
value: 'Métodos de Busqueda',
@@ -24,26 +25,33 @@ export const quizzes = [
{
id: 3,
+ imagen: svgSimpleList,
+ titulo: 'Listas Simples',
+ value: 'Listas Simples',
+ },
+
+ {
+ id: 4,
imagen: svgLinkedList,
- tiulo: 'Listas Enlazadas',
+ titulo: 'Listas Enlazadas',
value: 'Listas Enlazadas',
},
{
- id: 4,
+ id: 5,
imagen: svgBinaryTrees,
titulo: 'Arboles Binarios',
value: 'Arboles Binarios',
},
{
- id: 5,
+ id: 6,
imagen: svgPiles,
titulo: 'Pilas',
value: 'Pilas',
},
{
- id: 6,
+ id: 7,
imagen: svgQueue,
titulo: 'Colas',
value: 'Colas',
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 1903693..e1db01e 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -6,6 +6,7 @@
"quizzesPageTitle": "Challenge your knowledge",
"quizzesPageSubtitle": "Choose a quiz and find out if you have potential as a programmer",
"sortingMethods": "Sorting methods",
+ "dataStructures": "Data Structures",
"searchingMethods": "Searching Methods",
"linkedLists": "Linked Lists",
"binaryTrees": "Binary Trees",
diff --git a/src/i18n/es.json b/src/i18n/es.json
index 708cc6b..5eadac6 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -6,6 +6,7 @@
"quizzesPageTitle": "Desafía tus conocimientos",
"quizzesPageSubtitle": "Elije un quiz y descubre si tienes potencial como programador",
"sortingMethods": "Métodos de Ordenamiento",
+ "dataStructures": "Estructuras de Datos",
"searchingMethods": "Métodos de búsqueda",
"linkedLists": "Listas enlazadas",
"binaryTrees": "Arboles binarios",
diff --git a/src/index.css b/src/index.css
index 6bade6f..ff327b5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -50,3 +50,48 @@
@apply mx-auto my-0 max-w-5xl;
}
}
+
+/* Animaciones personalizadas para búsqueda binaria */
+@keyframes fadeInScale {
+ 0% {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes pulse-glow {
+ 0%,
+ 100% {
+ box-shadow: 0 0 20px rgba(16, 185, 129, 0.6);
+ }
+ 50% {
+ box-shadow: 0 0 40px rgba(16, 185, 129, 0.9);
+ }
+}
+
+.animate-fadeInScale {
+ animation: fadeInScale 0.5s ease-out forwards;
+}
+
+.animate-fadeIn {
+ animation: fadeIn 0.5s ease-out forwards;
+}
+
+.animate-pulse-glow {
+ animation: pulse-glow 2s ease-in-out infinite;
+}
diff --git a/src/quiz/data/quizzes.js b/src/quiz/data/quizzes.js
index 6c1c92b..9db303c 100644
--- a/src/quiz/data/quizzes.js
+++ b/src/quiz/data/quizzes.js
@@ -882,144 +882,268 @@ export const sortingMethodsQuiz = {
],
};
-export const searchMethodsQuiz = {
- name: 'Métodos de Búsqueda',
+export const linearSearchQuiz = {
+ name: 'Búsqueda Lineal',
questions: [
{
- statement:
- '¿Cuál de los siguientes métodos de búsqueda es más eficiente en un conjunto de datos ordenado?',
- question:
- 'En un conjunto de datos ordenado, ¿cuál método de búsqueda es más eficiente?',
+ statement: '¿Cuál es la definición correcta de búsqueda lineal?',
+ question: '¿Qué es la búsqueda lineal?',
options: {
- A: 'Búsqueda lineal',
- B: 'Búsqueda binaria',
- C: 'Búsqueda en árbol',
- D: 'Búsqueda hash',
+ A: 'Un algoritmo que divide el conjunto de datos a la mitad en cada paso.',
+ B: 'Un algoritmo que recorre secuencialmente cada elemento hasta encontrar el objetivo.',
+ C: 'Un algoritmo que utiliza una función hash para encontrar elementos.',
+ D: 'Un algoritmo que busca en un árbol binario.',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda binaria es más eficiente en conjuntos de datos ordenados, ya que reduce el espacio de búsqueda a la mitad en cada iteración.',
+ 'La búsqueda lineal es un algoritmo que recorre secuencialmente cada elemento del conjunto de datos hasta encontrar el objetivo o llegar al final.',
},
{
statement:
- '¿Cuál de los siguientes métodos de búsqueda es más adecuado para conjuntos de datos pequeños y no ordenados?',
+ '¿Cuál es la complejidad temporal de la búsqueda lineal en el peor caso?',
question:
- 'En conjuntos de datos pequeños y no ordenados, ¿cuál método de búsqueda es más adecuado?',
+ '¿Cuál es la complejidad de tiempo de búsqueda lineal en el peor caso?',
options: {
- A: 'Búsqueda lineal',
- B: 'Búsqueda binaria',
- C: 'Búsqueda en árbol',
- D: 'Búsqueda hash',
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
},
- answer: 'A',
+ answer: 'C',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda lineal es más adecuada para conjuntos de datos pequeños y no ordenados, ya que implica revisar cada elemento uno por uno.',
+ 'En el peor caso, la búsqueda lineal tiene una complejidad temporal de O(n), ya que puede necesitar revisar todos los elementos del conjunto de datos.',
},
{
statement:
- '¿Qué método de búsqueda se beneficia más de conjuntos de datos ordenados en términos de tiempo?',
- question:
- 'Entre estos métodos de búsqueda, ¿cuál se beneficia más de conjuntos de datos ordenados en términos de tiempo?',
+ '¿En qué tipo de conjunto de datos es más eficiente usar búsqueda lineal?',
+ question: '¿Cuándo es más apropiado usar búsqueda lineal?',
+ options: {
+ A: 'En conjuntos de datos grandes y ordenados.',
+ B: 'En conjuntos de datos pequeños o no ordenados.',
+ C: 'En conjuntos de datos con millones de elementos.',
+ D: 'Solo en estructuras de árbol binario.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La búsqueda lineal es más eficiente para conjuntos de datos pequeños o cuando los datos no están ordenados, ya que no podemos hacer suposiciones sobre la disposición de los elementos.',
+ },
+ {
+ statement: '¿Cuál es la principal ventaja de la búsqueda lineal?',
+ question: '¿Cuál es la ventaja principal de la búsqueda lineal?',
options: {
- A: 'Búsqueda lineal',
- B: 'Búsqueda binaria',
- C: 'Búsqueda en árbol',
- D: 'Búsqueda hash',
+ A: 'Es muy rápida.',
+ B: 'Funciona en conjuntos de datos no ordenados.',
+ C: 'Usa menos memoria que otros métodos.',
+ D: 'Siempre tiene complejidad O(1).',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda binaria se beneficia enormemente de conjuntos de datos ordenados, ya que reduce la cantidad de elementos a considerar en cada paso.',
+ 'La principal ventaja de la búsqueda lineal es que funciona en cualquier conjunto de datos, ordenado o no ordenado, sin necesidad de preprocesamiento.',
+ },
+ {
+ statement: '¿Cuál es la principal desventaja de la búsqueda lineal?',
+ question: '¿Cuál es la desventaja principal de la búsqueda lineal?',
+ options: {
+ A: 'No es estable.',
+ B: 'Usa mucha memoria.',
+ C: 'Es lenta en conjuntos de datos grandes.',
+ D: 'No funciona en arrays.',
+ },
+ answer: 'C',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La principal desventaja de la búsqueda lineal es su lentitud en conjuntos de datos grandes, ya que puede requerir revisar todos los elementos en el peor caso.',
},
{
statement:
- '¿Cuál de los siguientes métodos de búsqueda es más eficiente en términos de tiempo en conjuntos de datos grandes?',
- question:
- 'En conjuntos de datos grandes, ¿cuál método de búsqueda es más eficiente en términos de tiempo?',
+ '¿Es necesario que los datos estén ordenados para usar búsqueda lineal?',
+ question: '¿Los datos deben estar ordenados para búsqueda lineal?',
options: {
- A: 'Búsqueda lineal',
- B: 'Búsqueda binaria',
- C: 'Búsqueda en árbol',
- D: 'Búsqueda hash',
+ A: 'Sí, siempre.',
+ B: 'No, nunca.',
+ C: 'Solo si hay más de 1000 elementos.',
+ D: 'Depende del lenguaje de programación.',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda hash es más eficiente en términos de tiempo en conjuntos de datos grandes, ya que permite el acceso directo a los elementos utilizando una función de hash.',
+ 'No es necesario que los datos estén ordenados para usar búsqueda lineal. El algoritmo funciona en cualquier conjunto de datos, ordenado o no.',
},
{
statement:
- '¿En qué tipo de estructura de datos es común realizar la búsqueda en profundidad (DFS) y la búsqueda en amplitud (BFS)?',
+ '¿Cuál es el mejor caso de complejidad temporal para la búsqueda lineal?',
question:
- '¿En qué tipo de estructura de datos es común realizar tanto la búsqueda en profundidad (DFS) como la búsqueda en amplitud (BFS)?',
+ '¿Cuál es el mejor caso de complejidad temporal para búsqueda lineal?',
options: {
- A: 'Listas enlazadas',
- B: 'Árboles binarios',
- C: 'Colas',
- D: 'Grafos',
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
},
- answer: 'D',
+ answer: 'A',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda en profundidad (DFS) y la búsqueda en amplitud (BFS) son comunes en la exploración de grafos para encontrar caminos y conexiones entre nodos.',
+ 'El mejor caso de complejidad temporal para la búsqueda lineal es O(1), que ocurre cuando el elemento buscado es el primero del conjunto de datos.',
},
{
statement:
- '¿En qué tipo de estructura de datos es útil implementar una tabla hash para la búsqueda eficiente?',
+ '¿Cuántas comparaciones se necesitan en promedio para encontrar un elemento usando búsqueda lineal?',
+ question: '¿Cuántas comparaciones en promedio para búsqueda lineal?',
+ options: {
+ A: '1 comparación',
+ B: 'n/2 comparaciones',
+ C: 'log n comparaciones',
+ D: 'n comparaciones',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'En promedio, la búsqueda lineal requiere n/2 comparaciones, ya que en el caso promedio el elemento se encuentra aproximadamente en la mitad del conjunto de datos.',
+ },
+ ],
+};
+
+export const binarySearchQuiz = {
+ name: 'Búsqueda Binaria',
+ questions: [
+ {
+ statement: '¿Cuál es la definición correcta de búsqueda binaria?',
+ question: '¿Qué es la búsqueda binaria?',
+ options: {
+ A: 'Un algoritmo que recorre secuencialmente cada elemento.',
+ B: 'Un algoritmo que divide el conjunto ordenado a la mitad en cada paso.',
+ C: 'Un algoritmo que utiliza una función hash.',
+ D: 'Un algoritmo que busca en un árbol binario.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La búsqueda binaria es un algoritmo eficiente que divide el conjunto de datos ordenado a la mitad en cada paso, eliminando la mitad donde el objetivo no puede estar.',
+ },
+ {
+ statement:
+ '¿Cuál es la complejidad temporal de la búsqueda binaria en el peor caso?',
question:
- '¿En qué tipo de estructura de datos es útil implementar una tabla hash para la búsqueda eficiente?',
+ '¿Cuál es la complejidad de tiempo de búsqueda binaria en el peor caso?',
options: {
- A: 'Listas enlazadas',
- B: 'Árboles binarios',
- C: 'Colas',
- D: 'Arrays',
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Las tablas hash son particularmente útiles para la búsqueda eficiente en arrays o arreglos, ya que permiten acceso directo a través de una función de hash.',
+ 'La complejidad temporal de la búsqueda binaria en el peor caso es O(log n), ya que en cada iteración divide el espacio de búsqueda por la mitad.',
+ },
+ {
+ statement: '¿Cuál es el requisito principal para usar búsqueda binaria?',
+ question: '¿Cuál es el requisito previo para búsqueda binaria?',
+ options: {
+ A: 'Los datos deben estar en un array.',
+ B: 'Los datos deben estar ordenados.',
+ C: 'Los datos deben ser números.',
+ D: 'Los datos deben ser únicos.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'El requisito principal para usar búsqueda binaria es que los datos deben estar ordenados, de lo contrario el algoritmo no funcionará correctamente.',
},
{
statement:
'¿Cuál es la principal ventaja de la búsqueda binaria sobre la búsqueda lineal?',
question:
- '¿Cuál es la principal ventaja de la búsqueda binaria en comparación con la búsqueda lineal?',
+ '¿Cuál es la ventaja de búsqueda binaria frente a búsqueda lineal?',
options: {
A: 'Es más simple de implementar.',
- B: 'Es más rápida en todo tipo de conjuntos de datos.',
- C: 'Es más precisa.',
- D: 'Requiere menos memoria.',
+ B: 'Es significativamente más rápida en conjuntos grandes.',
+ C: 'Funciona con datos no ordenados.',
+ D: 'Usa menos memoria.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La principal ventaja de la búsqueda binaria es que es significativamente más rápida en conjuntos de datos grandes, con una complejidad de O(log n) frente a O(n) de la búsqueda lineal.',
+ },
+ {
+ statement: '¿Cuál es la desventaja principal de la búsqueda binaria?',
+ question: '¿Cuál es la desventaja de la búsqueda binaria?',
+ options: {
+ A: 'Es lenta.',
+ B: 'Requiere que los datos estén ordenados.',
+ C: 'No funciona con arrays.',
+ D: 'Usa mucha memoria.',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La principal ventaja de la búsqueda binaria es su velocidad, ya que reduce significativamente el número de comparaciones necesarias, especialmente en conjuntos de datos grandes.',
+ 'La principal desventaja es que requiere que los datos estén previamente ordenados, lo cual puede ser costoso computacionalmente si hay que ordenar primero.',
},
{
statement:
- '¿Cuál de los siguientes métodos de búsqueda es especialmente eficiente para conjuntos de datos dispersos?',
- question:
- '¿Cuál de estos métodos de búsqueda es especialmente eficiente para conjuntos de datos dispersos?',
+ '¿Cómo se llama el punto central en el algoritmo de búsqueda binaria?',
+ question: '¿Cómo se llama el punto central en búsqueda binaria?',
options: {
- A: 'Búsqueda lineal',
- B: 'Búsqueda binaria',
- C: 'Búsqueda en árbol',
- D: 'Búsqueda hash',
+ A: 'Pivot',
+ B: 'Middle',
+ C: 'Centro',
+ D: 'Mitad',
},
- answer: 'D',
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'En la búsqueda binaria, el punto central se suele llamar "middle" (del inglés) o punto medio, que es el elemento usado para dividir el conjunto de datos.',
+ },
+ {
+ statement:
+ '¿Cuántas veces máximo se divide el conjunto de datos en una búsqueda binaria de 1 millón de elementos?',
+ question: '¿Cuántas divisiones máximo para 1 millón de elementos?',
+ options: {
+ A: 'Aproximadamente 10 divisiones',
+ B: 'Aproximadamente 20 divisiones',
+ C: 'Aproximadamente 100 divisiones',
+ D: 'Aproximadamente 1000 divisiones',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Para 1 millón de elementos, la búsqueda binaria requiere un máximo de aproximadamente 20 divisiones (log₂(1,000,000) ≈ 20), demostrando su eficiencia.',
+ },
+ {
+ statement:
+ '¿Es la búsqueda binaria siempre mejor que la búsqueda lineal?',
+ question: '¿Es búsqueda binaria siempre mejor?',
+ options: {
+ A: 'Sí, en todos los casos.',
+ B: 'No, en conjuntos pequeños la búsqueda lineal puede ser comparativa.',
+ C: 'No, la búsqueda lineal es siempre mejor.',
+ D: 'Depende del lenguaje de programación.',
+ },
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'La búsqueda hash es especialmente eficiente para conjuntos de datos dispersos o con muchos valores faltantes, ya que puede mapear rápidamente claves a valores.',
+ 'No es siempre mejor. En conjuntos de datos muy pequeños, la búsqueda lineal puede ser tan rápida o más debido al overhead de la búsqueda binaria, y además requiere datos ordenados.',
},
],
};
@@ -1422,3 +1546,145 @@ export const queueQuiz = {
},
],
};
+
+export const simpleListQuiz = {
+ name: 'Listas Simples',
+ questions: [
+ {
+ statement:
+ '¿Cuál es la definición correcta de una lista simple enlazada?',
+ question:
+ 'En el contexto de estructuras de datos, ¿qué es una lista simple?',
+ options: {
+ A: 'Una estructura lineal de datos donde cada nodo almacena un valor y una referencia al siguiente nodo.',
+ B: 'Una estructura que solo puede almacenar números enteros.',
+ C: 'Una estructura de datos que almacena elementos en orden aleatorio.',
+ D: 'Una estructura de árbol binario con múltiples ramificaciones.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Una lista simple enlazada es una estructura lineal donde cada nodo contiene un valor y una referencia (puntero) al siguiente nodo en la secuencia.',
+ },
+ {
+ statement:
+ '¿Cuál es la principal diferencia entre una lista simple y una lista doblemente enlazada?',
+ question:
+ '¿Qué diferencia existe entre una lista simple y una lista doblemente enlazada?',
+ options: {
+ A: 'La lista simple es más rápida.',
+ B: 'La lista simple tiene referencias solo al siguiente nodo, mientras que la doblemente enlazada tiene referencias al anterior y siguiente.',
+ C: 'La lista doblemente enlazada ocupa menos memoria.',
+ D: 'Ambas son exactamente iguales en estructura.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La diferencia principal es que una lista simple solo tiene una referencia al siguiente nodo, mientras que una lista doblemente enlazada tiene referencias tanto al nodo anterior como al siguiente.',
+ },
+ {
+ statement:
+ '¿Cuál es la complejidad temporal para insertar un elemento al inicio de una lista simple enlazada?',
+ question:
+ '¿Cuál es la complejidad temporal para insertar un elemento al inicio de una lista simple?',
+ options: {
+ A: 'O(1)',
+ B: 'O(n)',
+ C: 'O(log n)',
+ D: 'O(n log n)',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La inserción al inicio de una lista simple es O(1) porque solo requiere ajustar el puntero del nuevo nodo al nodo que era la cabeza y actualizar la cabeza.',
+ },
+ {
+ statement:
+ '¿Cuál es la complejidad temporal para buscar un elemento en una lista simple enlazada?',
+ question:
+ '¿Cuál es la complejidad temporal promedio para buscar en una lista simple enlazada?',
+ options: {
+ A: 'O(1)',
+ B: 'O(n)',
+ C: 'O(log n)',
+ D: 'O(n^2)',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La búsqueda en una lista simple enlazada es O(n) en el caso promedio, porque potencialmente tienes que recorrer toda la lista desde el inicio hasta encontrar el elemento.',
+ },
+ {
+ statement:
+ '¿Qué operación es más eficiente en una lista simple comparada con un array?',
+ question:
+ '¿En qué operación es más eficiente una lista simple respecto a un array?',
+ options: {
+ A: 'En la inserción al inicio.',
+ B: 'En el acceso aleatorio a elementos.',
+ C: 'En la búsqueda binaria.',
+ D: 'En el almacenamiento de datos contiguos.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La inserción al inicio de una lista simple es mucho más eficiente que en un array. En un array, insertar al inicio requiere desplazar todos los elementos (O(n)), mientras que en una lista simple es O(1).',
+ },
+ {
+ statement:
+ '¿Cuál es la ventaja principal de usar una lista simple sobre un array en programación?',
+ question:
+ '¿Cuál es la principal ventaja de una lista simple sobre un array?',
+ options: {
+ A: 'Acceso más rápido a elementos aleatorios.',
+ B: 'Mayor flexibilidad en la inserción y eliminación de elementos.',
+ C: 'Menor uso de memoria en general.',
+ D: 'Mejor rendimiento en búsquedas.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'La principal ventaja de una lista simple es su flexibilidad para insertar y eliminar elementos en cualquier posición, especialmente en el inicio, lo que es O(1) en comparación con un array.',
+ },
+ {
+ statement:
+ '¿Qué sucede cuando intentas acceder a un nodo después de la cola de una lista simple?',
+ question:
+ '¿Qué sucede si intentas acceder más allá del último nodo de una lista simple?',
+ options: {
+ A: 'Regresa al primer nodo.',
+ B: 'Accedes a un valor nulo o NULL.',
+ C: 'Se crea automáticamente un nuevo nodo.',
+ D: 'Se genera un error de desbordamiento.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'El último nodo de una lista simple tiene una referencia nula (NULL) al siguiente, lo que indica el final de la lista. Intentar acceder más allá resultaría en acceder a NULL.',
+ },
+ {
+ statement:
+ '¿Cuál es la mejor estrategia para eliminar un nodo específico de una lista simple?',
+ question:
+ '¿Cuál es la estrategia correcta para eliminar un nodo específico de una lista simple?',
+ options: {
+ A: 'Buscar el nodo anterior al que se desea eliminar y redirigir su referencia.',
+ B: 'Simplemente establecer el nodo a NULL.',
+ C: 'Recrear toda la lista sin ese nodo.',
+ D: 'Utilizar un algoritmo de búsqueda binaria.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Para eliminar un nodo de una lista simple, primero debes encontrar el nodo anterior al que deseas eliminar, y luego redirigir su referencia al nodo siguiente del nodo a eliminar.',
+ },
+ ],
+};
diff --git a/src/quiz/data/quizzesEn.js b/src/quiz/data/quizzesEn.js
index 3f0def8..404128b 100644
--- a/src/quiz/data/quizzesEn.js
+++ b/src/quiz/data/quizzesEn.js
@@ -865,143 +865,262 @@ export const sortingMethodsQuizEn = {
],
};
-export const searchMethodsQuizEn = {
- name: 'Search Methods',
+export const linearSearchQuizEn = {
+ name: 'Linear Search',
questions: [
{
- statement:
- 'Which of the following search methods is more efficient on a sorted dataset?',
- question: 'On a sorted dataset, which search method is more efficient?',
+ statement: 'What is the correct definition of linear search?',
+ question: 'What is linear search?',
options: {
- A: 'Linear Search',
- B: 'Binary Search',
- C: 'Tree Search',
- D: 'Hash Search',
+ A: 'An algorithm that halves the dataset at each step.',
+ B: 'An algorithm that sequentially traverses each element until finding the target.',
+ C: 'An algorithm that uses a hash function to find elements.',
+ D: 'An algorithm that searches in a binary tree.',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Binary Search is more efficient on sorted datasets because it halves the search space with each iteration.',
+ 'Linear search is an algorithm that sequentially traverses each element of the dataset until finding the target or reaching the end.',
},
{
statement:
- 'Which of the following search methods is more suitable for small and unsorted datasets?',
+ 'What is the time complexity of linear search in the worst case?',
question:
- 'On small and unsorted datasets, which search method is more suitable?',
+ 'What is the time complexity of linear search in the worst case?',
+ options: {
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
+ },
+ answer: 'C',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'In the worst case, linear search has a time complexity of O(n) since it may need to check all elements in the dataset.',
+ },
+ {
+ statement: 'On what type of dataset is linear search most efficient?',
+ question: 'When is it most appropriate to use linear search?',
+ options: {
+ A: 'On large and sorted datasets.',
+ B: 'On small or unsorted datasets.',
+ C: 'On datasets with millions of elements.',
+ D: 'Only on binary tree structures.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Linear search is most efficient for small datasets or when data is not sorted, as we cannot make assumptions about element arrangement.',
+ },
+ {
+ statement: 'What is the main advantage of linear search?',
+ question: 'What is the main advantage of linear search?',
+ options: {
+ A: 'It is very fast.',
+ B: 'It works on unsorted datasets.',
+ C: 'It uses less memory than other methods.',
+ D: 'It always has O(1) complexity.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The main advantage of linear search is that it works on any dataset, sorted or unsorted, without requiring preprocessing.',
+ },
+ {
+ statement: 'What is the main disadvantage of linear search?',
+ question: 'What is the main disadvantage of linear search?',
options: {
- A: 'Linear Search',
- B: 'Binary Search',
- C: 'Tree Search',
- D: 'Hash Search',
+ A: 'It is not stable.',
+ B: 'It uses a lot of memory.',
+ C: 'It is slow on large datasets.',
+ D: 'It does not work on arrays.',
+ },
+ answer: 'C',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The main disadvantage of linear search is its slowness on large datasets, as it may require checking all elements in the worst case.',
+ },
+ {
+ statement: 'Is it necessary for data to be sorted to use linear search?',
+ question: 'Must data be sorted for linear search?',
+ options: {
+ A: 'Yes, always.',
+ B: 'No, never.',
+ C: 'Only if there are more than 1000 elements.',
+ D: 'It depends on the programming language.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'It is not necessary for data to be sorted for linear search. The algorithm works on any dataset, sorted or unsorted.',
+ },
+ {
+ statement: 'What is the best case time complexity for linear search?',
+ question: 'What is the best case time complexity for linear search?',
+ options: {
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
},
answer: 'A',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Linear Search is more suitable for small and unsorted datasets as it involves checking each element one by one.',
+ 'The best case time complexity for linear search is O(1), which occurs when the searched element is the first in the dataset.',
},
{
statement:
- 'Which search method benefits the most from sorted datasets in terms of time?',
- question:
- 'Among these search methods, which benefits the most from sorted datasets in terms of time?',
+ 'On average, how many comparisons are needed to find an element using linear search?',
+ question: 'On average, how many comparisons for linear search?',
options: {
- A: 'Linear Search',
- B: 'Binary Search',
- C: 'Tree Search',
- D: 'Hash Search',
+ A: '1 comparison',
+ B: 'n/2 comparisons',
+ C: 'log n comparisons',
+ D: 'n comparisons',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Binary Search greatly benefits from sorted datasets as it reduces the number of elements to consider at each step.',
+ 'On average, linear search requires n/2 comparisons, as the element is typically found approximately halfway through the dataset.',
},
+ ],
+};
+
+export const binarySearchQuizEn = {
+ name: 'Binary Search',
+ questions: [
{
- statement:
- 'Which of the following search methods is more efficient in terms of time on large datasets?',
- question:
- 'On large datasets, which search method is more efficient in terms of time?',
+ statement: 'What is the correct definition of binary search?',
+ question: 'What is binary search?',
options: {
- A: 'Linear Search',
- B: 'Binary Search',
- C: 'Tree Search',
- D: 'Hash Search',
+ A: 'An algorithm that sequentially traverses each element.',
+ B: 'An algorithm that halves the sorted dataset at each step.',
+ C: 'An algorithm that uses a hash function.',
+ D: 'An algorithm that searches in a binary tree.',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Hash Search is more efficient in terms of time on large datasets, as it allows direct access to elements using a hash function.',
+ 'Binary search is an efficient algorithm that halves the sorted dataset at each step, eliminating the half where the target cannot be.',
},
{
statement:
- 'In what type of data structure is it common to perform Depth-First Search (DFS) and Breadth-First Search (BFS)?',
+ 'What is the time complexity of binary search in the worst case?',
question:
- 'In what type of data structure is it common to perform both Depth-First Search (DFS) and Breadth-First Search (BFS)?',
+ 'What is the time complexity of binary search in the worst case?',
options: {
- A: 'Linked Lists',
- B: 'Binary Trees',
- C: 'Queues',
- D: 'Graphs',
+ A: 'O(1)',
+ B: 'O(log n)',
+ C: 'O(n)',
+ D: 'O(n²)',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Depth-First Search (DFS) and Breadth-First Search (BFS) are common in exploring graphs to find paths and connections between nodes.',
+ 'The time complexity of binary search in the worst case is O(log n), as it halves the search space at each iteration.',
},
{
- statement:
- 'In what type of data structure is it useful to implement a hash table for efficient searching?',
- question:
- 'In what type of data structure is it useful to implement a hash table for efficient searching?',
+ statement: 'What is the main requirement to use binary search?',
+ question: 'What is the main requirement for binary search?',
options: {
- A: 'Linked Lists',
- B: 'Binary Trees',
- C: 'Queues',
- D: 'Arrays',
+ A: 'Data must be in an array.',
+ B: 'Data must be sorted.',
+ C: 'Data must be numbers.',
+ D: 'Data must be unique.',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Hash tables are particularly useful for efficient searching in arrays, as they allow direct access through a hash function.',
+ 'The main requirement for binary search is that data must be sorted; otherwise the algorithm will not work correctly.',
},
{
statement:
'What is the main advantage of binary search over linear search?',
- question:
- 'What is the main advantage of binary search compared to linear search?',
+ question: 'What is the advantage of binary search over linear search?',
options: {
A: 'It is simpler to implement.',
- B: 'It is faster across all types of datasets.',
- C: 'It is more precise.',
- D: 'It requires less memory.',
+ B: 'It is significantly faster on large datasets.',
+ C: 'It works with unsorted data.',
+ D: 'It uses less memory.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The main advantage of binary search is that it is significantly faster on large datasets, with O(log n) complexity versus O(n) for linear search.',
+ },
+ {
+ statement: 'What is the main disadvantage of binary search?',
+ question: 'What is the disadvantage of binary search?',
+ options: {
+ A: 'It is slow.',
+ B: 'It requires data to be sorted.',
+ C: 'It does not work on arrays.',
+ D: 'It uses a lot of memory.',
},
answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'The main advantage of binary search is its speed, as it significantly reduces the number of comparisons needed, especially on large datasets.',
+ 'The main disadvantage is that it requires data to be pre-sorted, which can be computationally expensive if sorting is needed first.',
},
{
statement:
- 'Which of the following search methods is particularly efficient for sparse datasets?',
- question:
- 'Which of these search methods is particularly efficient for sparse datasets?',
+ 'What is the central point called in the binary search algorithm?',
+ question: 'What is the central point called in binary search?',
options: {
- A: 'Linear Search',
- B: 'Binary Search',
- C: 'Tree Search',
- D: 'Hash Search',
+ A: 'Pivot',
+ B: 'Middle',
+ C: 'Center',
+ D: 'Midpoint',
},
- answer: 'D',
+ answer: 'B',
selectedAnswer: null,
state: 'UNANSWERED',
feedback:
- 'Hash Search is especially efficient for sparse datasets or datasets with many missing values, as it can quickly map keys to values.',
+ 'In binary search, the central point is typically called "middle", which is the element used to divide the dataset.',
+ },
+ {
+ statement:
+ 'At most, how many times is the dataset divided in a binary search of 1 million elements?',
+ question: 'At most, how many divisions for 1 million elements?',
+ options: {
+ A: 'Approximately 10 divisions',
+ B: 'Approximately 20 divisions',
+ C: 'Approximately 100 divisions',
+ D: 'Approximately 1000 divisions',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'For 1 million elements, binary search requires at most approximately 20 divisions (log₂(1,000,000) ≈ 20), demonstrating its efficiency.',
+ },
+ {
+ statement: 'Is binary search always better than linear search?',
+ question: 'Is binary search always better?',
+ options: {
+ A: 'Yes, in all cases.',
+ B: 'No, on small datasets linear search can be comparable.',
+ C: 'No, linear search is always better.',
+ D: 'It depends on the programming language.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'It is not always better. On very small datasets, linear search can be as fast or faster due to binary search overhead, and binary search requires sorted data.',
},
],
};
@@ -1397,3 +1516,143 @@ export const queueQuizEn = {
},
],
};
+
+export const simpleListQuizEn = {
+ name: 'Simple Lists',
+ questions: [
+ {
+ statement:
+ 'What is the correct definition of a simple singly linked list?',
+ question: 'In the context of data structures, what is a simple list?',
+ options: {
+ A: 'A linear data structure where each node stores a value and a reference to the next node.',
+ B: 'A structure that can only store integers.',
+ C: 'A data structure that stores elements in random order.',
+ D: 'A binary tree structure with multiple branches.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'A simple singly linked list is a linear structure where each node contains a value and a reference (pointer) to the next node in the sequence.',
+ },
+ {
+ statement:
+ 'What is the main difference between a simple list and a doubly linked list?',
+ question:
+ 'What difference exists between a simple list and a doubly linked list?',
+ options: {
+ A: 'The simple list is faster.',
+ B: 'The simple list has references only to the next node, while the doubly linked list has references to both previous and next.',
+ C: 'The doubly linked list uses less memory.',
+ D: 'Both are exactly the same in structure.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The main difference is that a simple list has only one reference to the next node, while a doubly linked list has references to both the previous and next nodes.',
+ },
+ {
+ statement:
+ 'What is the time complexity for inserting an element at the beginning of a simple singly linked list?',
+ question:
+ 'What is the time complexity for inserting an element at the beginning of a simple list?',
+ options: {
+ A: 'O(1)',
+ B: 'O(n)',
+ C: 'O(log n)',
+ D: 'O(n log n)',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ "Insertion at the beginning of a simple list is O(1) because it only requires adjusting the new node's pointer to the current head node and updating the head.",
+ },
+ {
+ statement:
+ 'What is the time complexity for searching an element in a simple singly linked list?',
+ question:
+ 'What is the average time complexity for searching in a simple singly linked list?',
+ options: {
+ A: 'O(1)',
+ B: 'O(n)',
+ C: 'O(log n)',
+ D: 'O(n^2)',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Searching in a simple singly linked list is O(n) on average, because you potentially have to traverse the entire list from the beginning to find the element.',
+ },
+ {
+ statement:
+ 'What operation is more efficient in a simple list compared to an array?',
+ question:
+ 'In which operation is a simple list more efficient compared to an array?',
+ options: {
+ A: 'In insertion at the beginning.',
+ B: 'In random access to elements.',
+ C: 'In binary search.',
+ D: 'In contiguous data storage.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'Insertion at the beginning of a simple list is much more efficient than in an array. In an array, inserting at the beginning requires shifting all elements (O(n)), while in a simple list it is O(1).',
+ },
+ {
+ statement:
+ 'What is the main advantage of using a simple list over an array in programming?',
+ question: 'What is the main advantage of a simple list over an array?',
+ options: {
+ A: 'Faster access to random elements.',
+ B: 'Greater flexibility in inserting and deleting elements.',
+ C: 'Lower overall memory usage.',
+ D: 'Better performance in searches.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The main advantage of a simple list is its flexibility for inserting and deleting elements at any position, especially at the beginning, which is O(1) compared to an array.',
+ },
+ {
+ statement:
+ 'What happens when you try to access a node after the tail of a simple list?',
+ question:
+ 'What happens if you try to access beyond the last node of a simple list?',
+ options: {
+ A: 'It returns to the first node.',
+ B: 'You access a null or NULL value.',
+ C: 'A new node is automatically created.',
+ D: 'An overflow error is generated.',
+ },
+ answer: 'B',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'The last node of a simple list has a null (NULL) reference to the next node, indicating the end of the list. Attempting to access beyond it would result in accessing NULL.',
+ },
+ {
+ statement:
+ 'What is the best strategy for deleting a specific node from a simple list?',
+ question:
+ 'What is the correct strategy for deleting a specific node from a simple list?',
+ options: {
+ A: 'Find the node before the one you want to delete and redirect its reference.',
+ B: 'Simply set the node to NULL.',
+ C: 'Recreate the entire list without that node.',
+ D: 'Use a binary search algorithm.',
+ },
+ answer: 'A',
+ selectedAnswer: null,
+ state: 'UNANSWERED',
+ feedback:
+ 'To delete a node from a simple list, you must first find the node before the one you want to delete, and then redirect its reference to the next node of the node to be deleted.',
+ },
+ ],
+};
diff --git a/src/quiz/helpers/getAnimationNameByQuizName.js b/src/quiz/helpers/getAnimationNameByQuizName.js
index 0142d30..0b3e8cb 100644
--- a/src/quiz/helpers/getAnimationNameByQuizName.js
+++ b/src/quiz/helpers/getAnimationNameByQuizName.js
@@ -4,6 +4,10 @@ export const getAnimationNameByQuizName = quizName => {
Quicksort: 'quick',
ShellSort: 'shell',
'Insertion Sort': 'insertion',
+ 'Listas Simples': 'simpleList',
+ Pilas: 'stack',
+ 'Búsqueda Lineal': 'linearSearch',
+ 'Búsqueda Binaria': 'binarySearch',
};
return animationNames[quizName];
diff --git a/src/quiz/helpers/getQuizByName.js b/src/quiz/helpers/getQuizByName.js
index d8f2b3b..4e276e6 100644
--- a/src/quiz/helpers/getQuizByName.js
+++ b/src/quiz/helpers/getQuizByName.js
@@ -1,21 +1,25 @@
import { defaultLanguage } from '../../main';
import {
+ binarySearchQuiz,
+ binarySearchQuizEn,
binaryTreeQuiz,
binaryTreeQuizEn,
bubbleSortQuiz,
bubbleSortQuizEn,
insertionSortQuiz,
insertionSortQuizEn,
+ linearSearchQuiz,
+ linearSearchQuizEn,
linkedListQuiz,
linkedListQuizEn,
queueQuiz,
queueQuizEn,
quickSortQuiz,
quickSortQuizEn,
- searchMethodsQuiz,
- searchMethodsQuizEn,
shellSortQuiz,
shellSortQuizEn,
+ simpleListQuiz,
+ simpleListQuizEn,
sortingFundamentalsQuiz,
sortingFundamentalsQuizEn,
sortingMethodsQuiz,
@@ -34,11 +38,13 @@ export const getQuizByName = name => {
quick: quickSortQuiz,
shell: shellSortQuiz,
insertion: insertionSortQuiz,
+ linearSearch: linearSearchQuiz,
+ binarySearch: binarySearchQuiz,
linkedList: linkedListQuiz,
- searchMethods: searchMethodsQuiz,
binaryTree: binaryTreeQuiz,
stacks: stackQuiz,
queues: queueQuiz,
+ simpleList: simpleListQuiz,
};
const quizzesEn = {
@@ -48,11 +54,13 @@ export const getQuizByName = name => {
quick: quickSortQuizEn,
shell: shellSortQuizEn,
insertion: insertionSortQuizEn,
+ linearSearch: linearSearchQuizEn,
+ binarySearch: binarySearchQuizEn,
linkedList: linkedListQuizEn,
- searchMethods: searchMethodsQuizEn,
binaryTree: binaryTreeQuizEn,
stacks: stackQuizEn,
queues: queueQuizEn,
+ simpleList: simpleListQuizEn,
};
return userLanguage == 'es' ? quizzes[name] : quizzesEn[name];
diff --git a/src/quiz/pages/DataStructuresPage.jsx b/src/quiz/pages/DataStructuresPage.jsx
new file mode 100644
index 0000000..8c72122
--- /dev/null
+++ b/src/quiz/pages/DataStructuresPage.jsx
@@ -0,0 +1,80 @@
+import { useDispatch } from 'react-redux';
+import CardTabla from '../../components/CardTabla';
+import { SearchMethodGrid } from '../components';
+import { useEffect } from 'react';
+import { resetStateForQuiz } from '../../store';
+import {
+ svgSimpleList,
+ svgLinkedList,
+ svgBinaryTrees,
+ svgPiles,
+ svgQueue,
+} from '../../assets/svg/SvgConstans';
+import { useTranslation } from 'react-i18next';
+
+export const DataStructuresPage = () => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(resetStateForQuiz());
+ }, [dispatch]);
+
+ const dataStructures = [
+ {
+ id: 1,
+ titulo: t('simpleLists'),
+ param: 'simpleList',
+ imagen: svgSimpleList,
+ },
+ {
+ id: 2,
+ titulo: t('linkedLists'),
+ param: 'linkedList',
+ imagen: svgLinkedList,
+ },
+ {
+ id: 3,
+ titulo: t('binaryTrees'),
+ param: 'binaryTree',
+ imagen: svgBinaryTrees,
+ },
+ {
+ id: 4,
+ titulo: t('stacks'),
+ param: 'stacks',
+ imagen: svgPiles,
+ },
+ {
+ id: 5,
+ titulo: t('queues'),
+ param: 'queues',
+ imagen: svgQueue,
+ },
+ ];
+
+ return (
+
+
+ {t('dataStructures') || 'Estructuras de Datos'}
+
+
+
+
+ {dataStructures.map(estructura => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/quiz/pages/SearchMethodsPage.jsx b/src/quiz/pages/SearchMethodsPage.jsx
new file mode 100644
index 0000000..82d6c94
--- /dev/null
+++ b/src/quiz/pages/SearchMethodsPage.jsx
@@ -0,0 +1,51 @@
+import { useDispatch } from 'react-redux';
+import CardTabla from '../../components/CardTabla';
+import { SearchMethodGrid } from '../components';
+import { useEffect } from 'react';
+import { resetStateForQuiz } from '../../store';
+import { svgLinearSearch, svgBinarySearch } from '../../assets/svg/SvgConstans';
+import { useTranslation } from 'react-i18next';
+
+export const SearchMethodsPage = () => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(resetStateForQuiz());
+ }, [dispatch]);
+
+ const searchMethods = [
+ {
+ id: 1,
+ titulo: t('linearSearch') || 'Búsqueda Lineal',
+ param: 'linearSearch',
+ imagen: svgLinearSearch,
+ },
+ {
+ id: 2,
+ titulo: t('binarySearch') || 'Búsqueda Binaria',
+ param: 'binarySearch',
+ imagen: svgBinarySearch,
+ },
+ ];
+
+ return (
+
+
+ {t('searchingMethods') || 'Métodos de Búsqueda'}
+
+
+
+
+ {searchMethods.map(metodo => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/quiz/pages/index.js b/src/quiz/pages/index.js
index cb0bf6d..5528e41 100644
--- a/src/quiz/pages/index.js
+++ b/src/quiz/pages/index.js
@@ -1 +1,3 @@
export * from './SortingMethodsPage';
+export * from './SearchMethodsPage';
+export * from './DataStructuresPage';
diff --git a/src/quiz/views/QuizView.jsx b/src/quiz/views/QuizView.jsx
index 2bd8011..4ca4f84 100644
--- a/src/quiz/views/QuizView.jsx
+++ b/src/quiz/views/QuizView.jsx
@@ -23,6 +23,7 @@ import {
canFinishQuiz,
formatQuestionIndicator,
getQuizByName,
+ getAnimationNameByQuizName,
} from '../helpers';
import { questionStates, questionType } from '../constants';
import {
@@ -152,7 +153,9 @@ export const QuizView = () => {
{shouldShowFeedbackButton && (
{
- navigate(`/animacion/${quizName}`);
+ navigate(
+ `/animacion/${getAnimationNameByQuizName(quiz.name)}`
+ );
}}
>
Ver retroalimentación
diff --git a/src/store/binarySearch/binarySearchSlice.js b/src/store/binarySearch/binarySearchSlice.js
new file mode 100644
index 0000000..d79d8c7
--- /dev/null
+++ b/src/store/binarySearch/binarySearchSlice.js
@@ -0,0 +1,120 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ array: [],
+ searchValue: null,
+ stepHistory: [],
+ history: -1,
+ isPlaying: false,
+ timeIds: [],
+ timeStep: 1,
+ repeat: false,
+ currentIndex: -1,
+ leftIndex: -1,
+ rightIndex: -1,
+ midIndex: -1,
+ foundIndex: -1,
+ description: '',
+ funAction: null,
+ currentLine: -1,
+};
+
+export const binarySearchSlice = createSlice({
+ name: 'binarySearch',
+ initialState,
+ reducers: {
+ setArrayBinarySearch: (state, action) => {
+ state.array = action.payload;
+ state.stepHistory = [];
+ state.history = -1;
+ state.foundIndex = -1;
+ state.description = '';
+ },
+
+ setSearchValueBinarySearch: (state, action) => {
+ state.searchValue = action.payload;
+ },
+
+ actionButtonBinarySearch: (state, action) => {
+ const { trace, value } = action.payload;
+ state.stepHistory = trace;
+ state.history = -1;
+ state.searchValue = value;
+ state.repeat = false;
+ state.funAction = 'busquedaBinaria';
+ },
+
+ updateVisualizationBinarySearch: (state, action) => {
+ const step = action.payload;
+ if (step) {
+ state.array = step.array || state.array;
+ state.currentIndex = step.currentIndex ?? -1;
+ state.leftIndex = step.leftIndex ?? -1;
+ state.rightIndex = step.rightIndex ?? -1;
+ state.midIndex = step.midIndex ?? -1;
+ state.foundIndex = step.found ? step.midIndex : (step.foundIndex ?? -1);
+ state.description = step.message || step.description || '';
+ state.currentLine = step.currentLine ?? -1;
+ }
+ },
+
+ incrementHistoryBinarySearch: state => {
+ if (state.history < state.stepHistory.length - 1) {
+ state.history += 1;
+ }
+ },
+
+ decrementHistoryBinarySearch: state => {
+ if (state.history > 0) {
+ state.history -= 1;
+ }
+ },
+
+ setPlayingBinarySearch: (state, action) => {
+ state.isPlaying = action.payload;
+ },
+
+ setTimeIdBinarySearch: (state, action) => {
+ state.timeIds = action.payload;
+ },
+
+ restoreTimeIdBinarySearch: state => {
+ state.timeIds = [];
+ },
+
+ restoreRepeatBinarySearch: state => {
+ state.repeat = true;
+ },
+
+ resetBinarySearch: state => {
+ state.stepHistory = [];
+ state.history = -1;
+ state.currentIndex = -1;
+ state.leftIndex = -1;
+ state.rightIndex = -1;
+ state.midIndex = -1;
+ state.foundIndex = -1;
+ state.description = '';
+ state.isPlaying = false;
+ },
+
+ fullResetBinarySearch: () => initialState,
+ },
+});
+
+export const {
+ setArrayBinarySearch,
+ setSearchValueBinarySearch,
+ actionButtonBinarySearch,
+ updateVisualizationBinarySearch,
+ incrementHistoryBinarySearch,
+ decrementHistoryBinarySearch,
+ setPlayingBinarySearch,
+ setTimeIdBinarySearch,
+ restoreTimeIdBinarySearch,
+ restoreRepeatBinarySearch,
+ resetBinarySearch,
+ fullResetBinarySearch,
+} = binarySearchSlice.actions;
+
+export default binarySearchSlice.reducer;
diff --git a/src/store/binarySearch/index.js b/src/store/binarySearch/index.js
new file mode 100644
index 0000000..b3d09e0
--- /dev/null
+++ b/src/store/binarySearch/index.js
@@ -0,0 +1,15 @@
+export {
+ setArrayBinarySearch,
+ setSearchValueBinarySearch,
+ actionButtonBinarySearch,
+ updateVisualizationBinarySearch,
+ incrementHistoryBinarySearch,
+ decrementHistoryBinarySearch,
+ setPlayingBinarySearch,
+ setTimeIdBinarySearch,
+ restoreTimeIdBinarySearch,
+ restoreRepeatBinarySearch,
+ resetBinarySearch,
+ fullResetBinarySearch,
+} from './binarySearchSlice';
+export { default as binarySearchSlice } from './binarySearchSlice';
diff --git a/src/store/doubleList/doubleListSlice.js b/src/store/doubleList/doubleListSlice.js
new file mode 100644
index 0000000..55ea310
--- /dev/null
+++ b/src/store/doubleList/doubleListSlice.js
@@ -0,0 +1,149 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ InsertarAlInicio,
+ InsertarAlFinal,
+ InsertarEnPosicion,
+ EliminarDelInicio,
+ EliminarDelFinal,
+ EliminarEnPosicion,
+ Buscar,
+} from '../../animations/doubleList/algorithms';
+
+const initialState = {
+ head: null,
+ stepHistory: [],
+ history: -1,
+ firstSet: [],
+ secondSet: [],
+ thirdSet: [],
+ isHead: -1,
+ highlightedNode: null,
+ pointer: null,
+ currentIndex: null,
+ srcHead: null,
+ playing: false,
+ funAction: null,
+ timeIds: [],
+};
+
+export const doubleListSlice = createSlice({
+ name: 'doubleList',
+ initialState,
+ reducers: {
+ setHeadDoubleList: (state, action) => {
+ state.srcHead = action.payload;
+ state.head = state.srcHead;
+ state.isHead = state.srcHead ? state.srcHead.getValue() : -1;
+ },
+
+ actionButtonDoubleList: (state, action) => {
+ const { action: operation, value, position } = action.payload;
+ const head = state.head;
+
+ let result = null;
+ switch (operation) {
+ case 'insertarAlInicio':
+ result = InsertarAlInicio(head, value);
+ break;
+ case 'insertarAlFinal':
+ result = InsertarAlFinal(head, value);
+ break;
+ case 'insertarEnPosicion':
+ result = InsertarEnPosicion(head, value, position);
+ break;
+ case 'eliminarDelInicio':
+ result = EliminarDelInicio(head);
+ break;
+ case 'eliminarDelFinal':
+ result = EliminarDelFinal(head);
+ break;
+ case 'eliminarEnPosicion':
+ result = EliminarEnPosicion(head, position);
+ break;
+ case 'buscar':
+ result = Buscar(head, value);
+ break;
+ default:
+ break;
+ }
+
+ if (result) {
+ state.stepHistory = result.historialPasos;
+ state.funAction = operation;
+ state.srcHead = result.nuevaCabeza;
+ state.head = state.srcHead;
+ state.history = -1;
+ state.isHead = state.srcHead ? state.srcHead.getValue() : -1;
+ state.firstSet = [];
+ state.secondSet = [];
+ state.thirdSet = [];
+ state.highlightedNode = null;
+ state.pointer = null;
+ state.currentIndex = null;
+ }
+ },
+
+ restoreRepeatDoubleList: state => {
+ state.head = state.srcHead;
+ state.history = -1;
+ state.firstSet = [];
+ state.secondSet = [];
+ state.thirdSet = [];
+ state.highlightedNode = null;
+ state.pointer = null;
+ state.currentIndex = null;
+ state.playing = false;
+ },
+
+ restoreTimeIdDoubleList: state => {
+ state.timeIds.forEach(timeoutId => clearTimeout(timeoutId));
+ state.timeIds = [];
+ },
+
+ updateVisualizationDoubleList: (state, action) => {
+ const currVisualization = action.payload;
+ state.head = currVisualization.head;
+ state.firstSet = [...currVisualization.firstSet];
+ state.secondSet = [...currVisualization.secondSet];
+ state.thirdSet = [...currVisualization.thirdSet];
+ state.isHead = currVisualization.isHead;
+ state.highlightedNode = currVisualization.highlightedNode;
+ state.pointer = currVisualization.pointer;
+ state.currentIndex = currVisualization.currentIndex;
+ },
+
+ setTimeIdDoubleList: (state, action) => {
+ state.timeIds = action.payload;
+ },
+
+ incrementHistoryDoubleList: state => {
+ if (state.history < state.stepHistory.length - 1) {
+ state.history += 1;
+ }
+ },
+
+ decrementHistoryDoubleList: state => {
+ if (state.history > -1) {
+ state.history -= 1;
+ }
+ },
+
+ setPlayingDoubleList: (state, action) => {
+ state.playing = action.payload;
+ },
+ },
+});
+
+export const {
+ setHeadDoubleList,
+ actionButtonDoubleList,
+ restoreRepeatDoubleList,
+ restoreTimeIdDoubleList,
+ updateVisualizationDoubleList,
+ setTimeIdDoubleList,
+ incrementHistoryDoubleList,
+ decrementHistoryDoubleList,
+ setPlayingDoubleList,
+} = doubleListSlice.actions;
+
+export default doubleListSlice.reducer;
diff --git a/src/store/doubleList/index.js b/src/store/doubleList/index.js
new file mode 100644
index 0000000..2246e64
--- /dev/null
+++ b/src/store/doubleList/index.js
@@ -0,0 +1 @@
+export * from './doubleListSlice';
diff --git a/src/store/index.js b/src/store/index.js
index 29d009d..459c489 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -3,3 +3,6 @@ export * from './quiz';
export * from './sorts';
export * from './stack';
export * from './simpleList';
+export * from './doubleList';
+export * from './linearSearch';
+export * from './binarySearch';
diff --git a/src/store/linearSearch/index.js b/src/store/linearSearch/index.js
new file mode 100644
index 0000000..9229754
--- /dev/null
+++ b/src/store/linearSearch/index.js
@@ -0,0 +1,15 @@
+export {
+ setArrayLinearSearch,
+ setSearchValueLinearSearch,
+ actionButtonLinearSearch,
+ updateVisualizationLinearSearch,
+ incrementHistoryLinearSearch,
+ decrementHistoryLinearSearch,
+ setPlayingLinearSearch,
+ setTimeIdLinearSearch,
+ restoreTimeIdLinearSearch,
+ restoreRepeatLinearSearch,
+ resetLinearSearch,
+ fullResetLinearSearch,
+} from './linearSearchSlice';
+export { default as linearSearchSlice } from './linearSearchSlice';
diff --git a/src/store/linearSearch/linearSearchSlice.js b/src/store/linearSearch/linearSearchSlice.js
new file mode 100644
index 0000000..cfb5cf4
--- /dev/null
+++ b/src/store/linearSearch/linearSearchSlice.js
@@ -0,0 +1,111 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ array: [],
+ searchValue: null,
+ stepHistory: [],
+ history: -1,
+ isPlaying: false,
+ timeIds: [],
+ timeStep: 1,
+ repeat: false,
+ currentIndex: -1,
+ foundIndex: -1,
+ description: '',
+ funAction: null,
+ currentLine: -1,
+};
+
+export const linearSearchSlice = createSlice({
+ name: 'linearSearch',
+ initialState,
+ reducers: {
+ setArrayLinearSearch: (state, action) => {
+ state.array = action.payload;
+ state.stepHistory = [];
+ state.history = -1;
+ state.foundIndex = -1;
+ state.description = '';
+ },
+
+ setSearchValueLinearSearch: (state, action) => {
+ state.searchValue = action.payload;
+ },
+
+ actionButtonLinearSearch: (state, action) => {
+ const { trace, value } = action.payload;
+ state.stepHistory = trace;
+ state.history = -1;
+ state.searchValue = value;
+ state.repeat = false;
+ state.funAction = 'busquedaLineal';
+ },
+
+ updateVisualizationLinearSearch: (state, action) => {
+ const step = action.payload;
+ if (step) {
+ state.array = step.array || state.array;
+ state.currentIndex = step.currentIndex ?? -1;
+ state.foundIndex = step.foundIndex ?? -1;
+ state.description = step.description || '';
+ state.currentLine = step.currentLine ?? -1;
+ }
+ },
+
+ incrementHistoryLinearSearch: state => {
+ if (state.history < state.stepHistory.length - 1) {
+ state.history += 1;
+ }
+ },
+
+ decrementHistoryLinearSearch: state => {
+ if (state.history > 0) {
+ state.history -= 1;
+ }
+ },
+
+ setPlayingLinearSearch: (state, action) => {
+ state.isPlaying = action.payload;
+ },
+
+ setTimeIdLinearSearch: (state, action) => {
+ state.timeIds = action.payload;
+ },
+
+ restoreTimeIdLinearSearch: state => {
+ state.timeIds = [];
+ },
+
+ restoreRepeatLinearSearch: state => {
+ state.repeat = true;
+ },
+
+ resetLinearSearch: state => {
+ state.stepHistory = [];
+ state.history = -1;
+ state.currentIndex = -1;
+ state.foundIndex = -1;
+ state.description = '';
+ state.isPlaying = false;
+ },
+
+ fullResetLinearSearch: () => initialState,
+ },
+});
+
+export const {
+ setArrayLinearSearch,
+ setSearchValueLinearSearch,
+ actionButtonLinearSearch,
+ updateVisualizationLinearSearch,
+ incrementHistoryLinearSearch,
+ decrementHistoryLinearSearch,
+ setPlayingLinearSearch,
+ setTimeIdLinearSearch,
+ restoreTimeIdLinearSearch,
+ restoreRepeatLinearSearch,
+ resetLinearSearch,
+ fullResetLinearSearch,
+} = linearSearchSlice.actions;
+
+export default linearSearchSlice.reducer;
diff --git a/src/store/simpleList/simpleListSlice.js b/src/store/simpleList/simpleListSlice.js
index 6c323bd..a0a3919 100644
--- a/src/store/simpleList/simpleListSlice.js
+++ b/src/store/simpleList/simpleListSlice.js
@@ -23,7 +23,7 @@ const initialState = {
srcHead: null,
playing: false,
funAction: null,
- timeId: [],
+ timeIds: [],
};
export const simpleListSlice = createSlice({
@@ -96,8 +96,8 @@ export const simpleListSlice = createSlice({
},
restoreTimeIdSimpleList: state => {
- state.timeId.forEach(timeoutId => clearTimeout(timeoutId));
- state.timeId = [];
+ state.timeIds.forEach(timeoutId => clearTimeout(timeoutId));
+ state.timeIds = [];
},
updateVisualizationSimpleList: (state, action) => {
@@ -113,7 +113,7 @@ export const simpleListSlice = createSlice({
},
setTimeIdSimpleList: (state, action) => {
- state.timeId = action.payload;
+ state.timeIds = action.payload;
},
incrementHistorySimpleList: state => {
diff --git a/src/store/store.js b/src/store/store.js
index 262bcf4..11a847a 100644
--- a/src/store/store.js
+++ b/src/store/store.js
@@ -3,6 +3,9 @@ import { quizSlice } from './quiz';
import { sortsSlice } from './sorts';
import { stackSlice } from './stack';
import simpleListSlice from './simpleList/simpleListSlice';
+import doubleListSlice from './doubleList/doubleListSlice';
+import linearSearchSlice from './linearSearch/linearSearchSlice';
+import binarySearchSlice from './binarySearch/binarySearchSlice';
export const store = configureStore({
reducer: {
@@ -10,6 +13,9 @@ export const store = configureStore({
sorts: sortsSlice.reducer,
stack: stackSlice.reducer,
simpleList: simpleListSlice,
+ doubleList: doubleListSlice,
+ linearSearch: linearSearchSlice,
+ binarySearch: binarySearchSlice,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
diff --git a/src/theme/theme.js b/src/theme/theme.js
index e0b4b99..10151fa 100644
--- a/src/theme/theme.js
+++ b/src/theme/theme.js
@@ -11,6 +11,14 @@ export const theme = createTheme({
tertiary: {
main: '#ECE6E4',
},
+ info: {
+ main: '#0284C7',
+ dark: '#0369A1',
+ },
+ warning: {
+ main: '#EA580C',
+ dark: '#C2410C',
+ },
gray: {
main: '#1E1E1E',
primary: '#D9D9D9',
From eb72ef39837c4246f680dfbf6912c7853446ae4e Mon Sep 17 00:00:00 2001
From: jdbcamilo <149972010+jdbcamilo@users.noreply.github.com>
Date: Sat, 6 Dec 2025 10:28:30 -0500
Subject: [PATCH 08/18] =?UTF-8?q?Dise=C3=B1o=20minimalista=20para=20retroa?=
=?UTF-8?q?limentaci=C3=B3n=20en=20quizes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- CorrectAnswerDialog: Simplificado sin gradient, solo fondo verde sólido con checkmark
- WrongAnswerDialog: 3 secciones minimalistas (header rojo, opción verde, explicación azul)
- QuizAnswers: Rediseño completo con comparativa de respuestas en dos columnas
- Todos los componentes sin emojis, diseño limpio y profesional
---
src/quiz/components/AnswerButton.jsx | 31 ++-
src/quiz/components/AnswersGrid.jsx | 6 +-
src/quiz/components/CorrectAnswerDialog.jsx | 11 +-
src/quiz/components/QuizQuestion.jsx | 8 +-
src/quiz/components/QuizStatement.jsx | 8 +-
src/quiz/components/WrongAnswerDialog.jsx | 61 +++++-
src/quiz/views/QuizAnswers.jsx | 204 +++++++++++++++-----
src/quiz/views/QuizView.jsx | 157 ++++++++++-----
8 files changed, 372 insertions(+), 114 deletions(-)
diff --git a/src/quiz/components/AnswerButton.jsx b/src/quiz/components/AnswerButton.jsx
index be4706e..98b036d 100644
--- a/src/quiz/components/AnswerButton.jsx
+++ b/src/quiz/components/AnswerButton.jsx
@@ -10,16 +10,33 @@ export const AnswerButton = ({
ariaLabel = '',
}) => {
return (
- onAnswerSelected(answerLetter)}
disabled={disabled}
- ariaLabel={ariaLabel}
+ aria-label={ariaLabel}
+ className={`w-full p-4 mb-3 text-left rounded-lg border-2 transition-all duration-200 ${
+ isSelected
+ ? 'border-primary bg-blue-50 font-semibold'
+ : 'border-gray-300 bg-white hover:border-gray-400'
+ } ${disabled ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
- {`${answerLetter}. ${answerContent}`}
-
+
+
+ {answerLetter}
+
+
+ {answerContent}
+
+
+
);
};
diff --git a/src/quiz/components/AnswersGrid.jsx b/src/quiz/components/AnswersGrid.jsx
index 827fb3f..9bc3055 100644
--- a/src/quiz/components/AnswersGrid.jsx
+++ b/src/quiz/components/AnswersGrid.jsx
@@ -1,11 +1,7 @@
import PropTypes from 'prop-types';
export const AnswersGrid = ({ children }) => {
- return (
-
- {children}
-
- );
+ return {children}
;
};
AnswersGrid.propTypes = {
diff --git a/src/quiz/components/CorrectAnswerDialog.jsx b/src/quiz/components/CorrectAnswerDialog.jsx
index d5f5c00..103f7ab 100644
--- a/src/quiz/components/CorrectAnswerDialog.jsx
+++ b/src/quiz/components/CorrectAnswerDialog.jsx
@@ -3,8 +3,15 @@ import { useTranslation } from 'react-i18next';
export const CorrectAnswerDialog = () => {
const { t } = useTranslation();
return (
-
- {t('correctAnswer')}
+
);
};
diff --git a/src/quiz/components/QuizQuestion.jsx b/src/quiz/components/QuizQuestion.jsx
index 61566c6..8b0792b 100644
--- a/src/quiz/components/QuizQuestion.jsx
+++ b/src/quiz/components/QuizQuestion.jsx
@@ -1,7 +1,13 @@
import PropTypes from 'prop-types';
export const QuizQuestion = ({ question }) => {
- return
{question} ;
+ return (
+
+
+ {question}
+
+
+ );
};
QuizQuestion.propTypes = {
diff --git a/src/quiz/components/QuizStatement.jsx b/src/quiz/components/QuizStatement.jsx
index 333d295..387019c 100644
--- a/src/quiz/components/QuizStatement.jsx
+++ b/src/quiz/components/QuizStatement.jsx
@@ -1,7 +1,13 @@
import PropTypes from 'prop-types';
export const QuizStatement = ({ statement }) => {
- return
{statement} ;
+ return (
+
+ );
};
QuizStatement.propTypes = {
diff --git a/src/quiz/components/WrongAnswerDialog.jsx b/src/quiz/components/WrongAnswerDialog.jsx
index 4d02ad4..17a21ba 100644
--- a/src/quiz/components/WrongAnswerDialog.jsx
+++ b/src/quiz/components/WrongAnswerDialog.jsx
@@ -1,10 +1,65 @@
import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
-export const WrongAnswerDialog = () => {
+const WrongAnswerDialogComponent = ({
+ correctAnswer,
+ correctOption,
+ feedback,
+}) => {
const { t } = useTranslation();
+
return (
-
- {t('wrongAnswer')}
+
+ {/* Header - Respuesta Incorrecta */}
+
+
+ {/* Respuesta Correcta */}
+ {correctAnswer && correctOption && (
+
+
+
+ {t('correctOption')}
+
+
+ Opción {correctAnswer}
+
+
+
+ {correctOption}
+
+
+ )}
+
+ {/* Explicación */}
+ {feedback && (
+
+
+
+ {t('explanation')}
+
+
+
+ {feedback}
+
+
+ )}
);
};
+
+WrongAnswerDialogComponent.propTypes = {
+ correctAnswer: PropTypes.string,
+ correctOption: PropTypes.string,
+ feedback: PropTypes.string,
+};
+
+export const WrongAnswerDialog = WrongAnswerDialogComponent;
diff --git a/src/quiz/views/QuizAnswers.jsx b/src/quiz/views/QuizAnswers.jsx
index 42313ab..12add9b 100644
--- a/src/quiz/views/QuizAnswers.jsx
+++ b/src/quiz/views/QuizAnswers.jsx
@@ -18,60 +18,162 @@ export const QuizAnswers = () => {
}
return (
-
- {questions.map((question, index) => (
-
-
-
- {Object.entries(question.options).map(([key, value]) => (
- {}}
- isSelected={false}
- disabled={true}
- />
- ))}
-
-
{t('correctOption')}
-
-
{t('youChoose')}
-
-
{t('explanation')}:
-
{question.feedback}
- {question.type === questionType.PRACTICAL && (
-
{
- dispatch(setCurrentQuestion(question));
- navigate(`/animacion/${getAnimationNameByQuizName(quizName)}`);
- }}
+
+
+ {/* Header */}
+
+
+ Revisión de Respuestas
+
+
+ Aquí puedes ver todas tus respuestas y la retroalimentación
+
+
+
+ {/* Preguntas */}
+
+ {questions.map((question, index) => (
+
- {' '}
- {t('seeFeedback')}{' '}
-
- )}
+ {/* Número y pregunta */}
+
+
+ Pregunta {index + 1}
+
+
+ {question.statement}
+
+
+
+ {/* Comparativa de respuestas */}
+
+ {/* Respuesta correcta */}
+
+
+
+ ✓
+
+
+ {t('correctOption')}
+
+
+
+
+ Opción {question.answer}
+
+
+ {question.options[question.answer]}
+
+
+
+
+ {/* Tu respuesta */}
+
+
+
+ {question.selectedAnswer === question.answer ? '✓' : '✗'}
+
+
+ {t('youChoose')}
+
+
+
+
+ Opción {question.selectedAnswer}
+
+
+ {question.options[question.selectedAnswer]}
+
+
+
+
+
+ {/* Explicación */}
+
+
+
💡
+
+
+ {t('explanation')}
+
+
+ {question.feedback}
+
+
+
+
+
+ {/* Botón ver retroalimentación */}
+ {question.type === questionType.PRACTICAL && (
+
{
+ dispatch(setCurrentQuestion(question));
+ navigate(
+ `/animacion/${getAnimationNameByQuizName(quizName)}`
+ );
+ }}
+ className="w-full bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded-lg font-semibold transition-colors"
+ >
+ 📺 {t('seeFeedback')}
+
+ )}
+
+ ))}
- ))}
-
-
navigate(-1)}>{t('back')}
-
navigate('/quizzes')}>
- {t('exit')}
-
+ {/* Footer */}
+
+ navigate(-1)}
+ className="bg-gray-500 hover:bg-gray-600 text-white px-6 py-2 rounded-lg font-semibold transition-colors"
+ >
+ {t('back')}
+
+ navigate('/quizzes')}
+ className="bg-primary hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-semibold transition-colors"
+ >
+ {t('exit')}
+
+
);
diff --git a/src/quiz/views/QuizView.jsx b/src/quiz/views/QuizView.jsx
index 4ca4f84..fb92934 100644
--- a/src/quiz/views/QuizView.jsx
+++ b/src/quiz/views/QuizView.jsx
@@ -84,6 +84,11 @@ export const QuizView = () => {
const [isAlertDialogVisible, setIsAlertDialogVisible] = useState(false);
const handleOnNextQuestion = () => {
+ // Si no ha seleccionado ninguna respuesta, no hacer nada
+ if (selectedAnswer === null) {
+ return;
+ }
+
if (state === questionStates.UNANSWERED && selectedAnswer != null) {
dispatch(checkAnswer());
return;
@@ -128,63 +133,127 @@ export const QuizView = () => {
return (
<>
-
-
-
-
+
+ {/* Panel izquierdo - Navegación y retroalimentación (STICKY REAL) */}
+
+ {/* Progreso */}
+
+
+
+ Progreso
+
+
+ {Math.round(
+ ((currentQuestionIndex + 1) / totalQuestions) * 100
+ )}
+ %
+
+
+
+
+ Pregunta {currentQuestionIndex + 1} de {totalQuestions}
+
-
-
-
- {Object.entries(options).map(([key, value]) => (
-
+
+ Retroalimentación
+
+
+ {isQuestionCorrect &&
}
+ {isQuestionIncorrect && (
+
- ))}
-
- {isQuestionCorrect &&
}
- {isQuestionIncorrect &&
}
+ )}
+ {state === questionStates.UNANSWERED && (
+
+ Selecciona una respuesta para continuar
+
+ )}
+
+
+
+ {/* Botones de navegación */}
+
+
+ {isPreviousButtonVisible && (
+
+ {t('previous')}
+
+ )}
+ {isQuizAtTheEnd ? (
+
+ {t('finish')}
+
+ ) : (
+
+ {t('next')}
+
+ )}
+
+
{shouldShowFeedbackButton && (
-
{
navigate(
`/animacion/${getAnimationNameByQuizName(quiz.name)}`
);
}}
+ className="w-full bg-orange-500 hover:bg-orange-600 text-white px-3 py-2 rounded-lg transition-colors text-sm font-semibold"
>
Ver retroalimentación
-
- )}
-
-
-
-
-
- {isPreviousButtonVisible && (
-
- {t('previous')}
-
- )}
- {isQuizAtTheEnd ? (
-
- {t('finish')}
-
- ) : (
-
- {t('next')}
-
+
)}
-
- {formatQuestionIndicator(currentQuestionIndex, totalQuestions)}
-
-
+
+
+ {/* Panel derecho - Pregunta y opciones */}
+
+
+
+
+
+
+ Selecciona la respuesta correcta:
+
+
+ {Object.entries(options).map(([key, value]) => (
+
+ ))}
+
+
+
+
From 84c77ad9cef21b06381498b0464e71f9c6cd06f3 Mon Sep 17 00:00:00 2001
From: jdbcamilo <149972010+jdbcamilo@users.noreply.github.com>
Date: Sat, 6 Dec 2025 10:33:18 -0500
Subject: [PATCH 09/18] =?UTF-8?q?Minijuegos=20organizados=20por=20temas=20?=
=?UTF-8?q?y=20categor=C3=ADas?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- HomeMiniJuegos: Ahora muestra minijuegos organizados por categorías (Métodos de Ordenamiento, Estructuras de Datos, Métodos de Búsqueda)
- Integración con topicCategories del sistema existente
- MiniGameCard: Agrega badge 'Próximamente' en los minijuegos
- Mejor organización y jerarquía visual
- Grid responsive según pantalla
---
src/components/HomeMiniJuegos.jsx | 90 ++++++++++++++++---------------
src/components/MiniGameCard.jsx | 40 ++++++++++----
2 files changed, 75 insertions(+), 55 deletions(-)
diff --git a/src/components/HomeMiniJuegos.jsx b/src/components/HomeMiniJuegos.jsx
index cee0865..abda2db 100644
--- a/src/components/HomeMiniJuegos.jsx
+++ b/src/components/HomeMiniJuegos.jsx
@@ -1,59 +1,61 @@
import '../styles/mediaquerys.css';
import MiniGameCard from './MiniGameCard';
-
-import {
- svgGameCards,
- svgGameMarbles,
- svgGameThief,
- svgGameSaveHomeland,
- svgGameRescueOperation,
- svgGamePrivacy,
- svgGameFruitSplash,
- svgGameMilitary,
-} from '../assets/svg/SvgConstans';
+import { topicCategories } from './HomeCartas';
import { useTranslation } from 'react-i18next';
export const HomeMiniJuegos = () => {
const { t } = useTranslation();
return (
-
-
-
- Aprende mientras
- juegas en CodeScape3D
-
-
- Selecciona tu minijuego favorito y presiona en "jugar"
-
+
+ {/* Header */}
+
+
+
+ Minijuegos
+
+
+ Aprende mientras juegas. Explora los minijuegos de cada tema
+
+
-
-
-
-
-
-
-
-
-
+ {/* Categories */}
+
+ {topicCategories.map(categoria => (
+
+ {/* Category Title */}
+
+
+ {categoria.nombre}
+
+
+
+
+ {/* Topics Grid */}
+
+ {categoria.temas.map(tema => (
+
+ ))}
+
+
+ ))}
-
-
-
{t('comingSoon')}
+ {/* Coming Soon Banner */}
+
+
+
+ Más minijuegos próximamente
+
+
{t('comingSoon')}
+
);
diff --git a/src/components/MiniGameCard.jsx b/src/components/MiniGameCard.jsx
index 6b82e0d..4fe1537 100644
--- a/src/components/MiniGameCard.jsx
+++ b/src/components/MiniGameCard.jsx
@@ -1,21 +1,39 @@
import { Link } from 'react-router-dom';
import '../styles/mediaquerys.css';
-import { HomeMiniJuegos } from './HomeMiniJuegos';
-const MiniGameCard = ({ logo, titulo, ruta }) => {
+const MiniGameCard = ({ logo, titulo, ruta, comingSoon = true }) => {
return (
-
-
-
{logo}
+
+ {/* Logo Container */}
+
-
-
{titulo}
+ {/* Title Container */}
+
-
-
+
+ {/* Coming Soon Badge */}
+ {comingSoon && (
+
+
+ Próximamente
+
+
+ )}
+
+
);
};
From 32054638038793a9a4722b28da3a59752b718ff5 Mon Sep 17 00:00:00 2001
From: FrankGarcia
Date: Sat, 6 Dec 2025 10:56:36 -0500
Subject: [PATCH 10/18] Quizes respuesta incorrecta arreglado
---
src/i18n/es.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/es.json b/src/i18n/es.json
index 5eadac6..fe27c4f 100644
--- a/src/i18n/es.json
+++ b/src/i18n/es.json
@@ -17,7 +17,7 @@
"previous": "Volver",
"finish": "Finalizar",
"correctAnswer": "Respuesta correcta!!",
- "wrongAnswer": "Respuesta incorrecta :C",
+ "wrongAnswer": "Respuesta incorrecta ",
"accept": "Acceptar",
"uncompletedQuizText": "Debes responder todas las preguntas para finalizar el quiz",
"correctAnswers": "Respuestas correctas",
From 7662fd230624acb3a0b9a7aab7ed465979339ceb Mon Sep 17 00:00:00 2001
From: FrankGarcia
Date: Thu, 5 Feb 2026 10:26:32 -0500
Subject: [PATCH 11/18] format
---
.../doubleList/components/DoubleListChart.jsx | 3 +++
src/animations/index.js | 1 +
.../simpleList/components/SimpleListChart.jsx | 3 +++
.../stack/components/StackChart.jsx | 4 +++-
src/animations/views/AnimationView.jsx | 17 +++++++++-----
src/assets/svg/SvgConstans.jsx | 4 ++--
src/components/BasicButton.jsx | 23 ++++++++++++++-----
src/components/HomeCartas.js | 11 ++++++++-
src/index.css | 14 +++++++++++
.../helpers/getAnimationNameByQuizName.js | 1 +
src/quiz/views/QuizView.jsx | 2 +-
src/store/index.js | 1 +
src/store/store.js | 2 ++
13 files changed, 69 insertions(+), 17 deletions(-)
diff --git a/src/animations/doubleList/components/DoubleListChart.jsx b/src/animations/doubleList/components/DoubleListChart.jsx
index c5c6f79..2c2350b 100644
--- a/src/animations/doubleList/components/DoubleListChart.jsx
+++ b/src/animations/doubleList/components/DoubleListChart.jsx
@@ -132,6 +132,8 @@ const NodeComponent = ({
}
}, [isHighlighted]);
+ const isDangerNode = secondSet.includes(value);
+
return (
{/* Indicador HEAD - solo para el primer nodo */}
@@ -167,6 +169,7 @@ const NodeComponent = ({
${getBackgroundColor()}
${isHighlighted ? 'ring-2 sm:ring-4 ring-purple-400 ring-opacity-75 animate-pulse scale-105' : ''}
${isAnimating ? 'animate-wiggle' : ''}
+ ${isDangerNode ? 'animate-pulse-glow-danger ring-2 ring-red-400' : ''}
cursor-pointer
`}
style={{
diff --git a/src/animations/index.js b/src/animations/index.js
index 7873357..53a9c6e 100644
--- a/src/animations/index.js
+++ b/src/animations/index.js
@@ -1,6 +1,7 @@
export * from './views';
export * from './sorting';
export * from './stack';
+export * from './queue';
export * from './simpleList';
export * from './doubleList';
export * from './linearSearch';
diff --git a/src/animations/simpleList/components/SimpleListChart.jsx b/src/animations/simpleList/components/SimpleListChart.jsx
index a2adb77..685237f 100644
--- a/src/animations/simpleList/components/SimpleListChart.jsx
+++ b/src/animations/simpleList/components/SimpleListChart.jsx
@@ -144,6 +144,8 @@ const NodeComponent = ({
}
}, [isHighlighted]);
+ const isDangerNode = secondSet.includes(value);
+
return (
{/* Indicador HEAD - solo para el primer nodo */}
@@ -179,6 +181,7 @@ const NodeComponent = ({
${getBackgroundColor()}
${isHighlighted ? 'ring-2 sm:ring-4 ring-blue-400 ring-opacity-75 animate-pulse scale-105' : ''}
${isAnimating ? 'animate-wiggle' : ''}
+ ${isDangerNode ? 'animate-pulse-glow-danger ring-2 ring-red-400' : ''}
cursor-pointer
`}
style={{
diff --git a/src/animations/stack/components/StackChart.jsx b/src/animations/stack/components/StackChart.jsx
index 8a586cc..0754a2b 100644
--- a/src/animations/stack/components/StackChart.jsx
+++ b/src/animations/stack/components/StackChart.jsx
@@ -93,9 +93,11 @@ const StackNode = ({ node, isHead, firstSet, secondSet, height, fontSize }) => {
return 'bg-secondary';
};
+ const isDangerNode = secondSet.includes(node.value);
+
return (
{
'flex-grow mb-4 w-full max-w-full overflow-hidden items-center justify-center ' +
([
'stack',
+ 'queue',
'simpleList',
'doubleList',
'linearSearch',
@@ -96,6 +98,8 @@ export const AnimationView = () => {
switch (animacion) {
case 'stack':
return 'Pilas';
+ case 'queue':
+ return 'Colas';
case 'simpleList':
return 'Listas Simples';
case 'doubleList':
@@ -113,9 +117,7 @@ export const AnimationView = () => {
const getQuizNameFromAnimation = () => {
const quizMapping = {
stack: 'stacks',
- simpleList: 'simpleList',
- doubleList: 'doubleList',
- linearSearch: 'linearSearch',
+ queue: 'queues',
binarySearch: 'binarySearch',
bubble: 'bubble',
quick: 'quick',
@@ -134,7 +136,8 @@ export const AnimationView = () => {
/>
- {animacion === 'stack' &&
}
+ {animacion === 'stack' &&
}{' '}
+ {animacion === 'queue' &&
}{' '}
{animacion === 'simpleList' &&
}
{animacion === 'doubleList' &&
}
{animacion === 'linearSearch' &&
}
@@ -144,7 +147,8 @@ export const AnimationView = () => {
- {animacion === 'stack' && }
+ {animacion === 'stack' && }{' '}
+ {animacion === 'queue' && }{' '}
{animacion === 'simpleList' && }
{animacion === 'doubleList' && }
{animacion === 'linearSearch' && }
@@ -153,7 +157,8 @@ export const AnimationView = () => {
- {animacion === 'stack' && }
+ {animacion === 'stack' && }{' '}
+ {animacion === 'queue' && }{' '}
{animacion === 'simpleList' && }
{animacion === 'doubleList' && }
{animacion === 'linearSearch' && }
diff --git a/src/assets/svg/SvgConstans.jsx b/src/assets/svg/SvgConstans.jsx
index 5b1d9a3..15ad438 100644
--- a/src/assets/svg/SvgConstans.jsx
+++ b/src/assets/svg/SvgConstans.jsx
@@ -217,8 +217,8 @@ export const svgPilesHome = (
export const svgQueue = (
diff --git a/src/components/BasicButton.jsx b/src/components/BasicButton.jsx
index 148afd7..b5505c6 100644
--- a/src/components/BasicButton.jsx
+++ b/src/components/BasicButton.jsx
@@ -4,24 +4,32 @@ import PropTypes from 'prop-types';
export const BasicButton = ({
children,
onClick = () => {},
- backgroundColor = 'gray.main',
+ backgroundColor = null,
disabled = false,
ariaLabel = '',
+ size = 'medium',
+ className = '',
+ sx = {},
}) => {
+ const isSmall = size === 'small';
+
return (
@@ -36,4 +44,7 @@ BasicButton.propTypes = {
backgroundColor: PropTypes.string,
disabled: PropTypes.bool,
ariaLabel: PropTypes.string,
+ size: PropTypes.oneOf(['small', 'medium', 'large']),
+ className: PropTypes.string,
+ sx: PropTypes.object,
};
diff --git a/src/components/HomeCartas.js b/src/components/HomeCartas.js
index 153094d..2c8228d 100644
--- a/src/components/HomeCartas.js
+++ b/src/components/HomeCartas.js
@@ -13,6 +13,7 @@ import {
svgDoubleList,
svgLinearSearch,
svgBinarySearch,
+ svgQueue,
} from '../assets/svg/SvgConstans';
export const courses = [
@@ -114,6 +115,13 @@ export const topic = [
param: 'binarySearch',
imagen: svgBinarySearch,
},
+ {
+ id: 12,
+ curso: 'Programacion2',
+ titulo: 'Colas',
+ param: 'queue',
+ imagen: svgQueue,
+ },
];
export const topicCategories = [
@@ -124,6 +132,7 @@ export const topicCategories = [
t =>
![
'stack',
+ 'queue',
'simpleList',
'doubleList',
'linearSearch',
@@ -135,7 +144,7 @@ export const topicCategories = [
id: 2,
nombre: 'Estructuras de Datos',
temas: topic.filter(t =>
- ['stack', 'simpleList', 'doubleList'].includes(t.param)
+ ['stack', 'queue', 'simpleList', 'doubleList'].includes(t.param)
),
},
{
diff --git a/src/index.css b/src/index.css
index ff327b5..6209c68 100644
--- a/src/index.css
+++ b/src/index.css
@@ -84,10 +84,24 @@
}
}
+@keyframes pulse-glow-danger {
+ 0%,
+ 100% {
+ box-shadow: 0 0 20px rgba(255, 87, 87, 0.6);
+ }
+ 50% {
+ box-shadow: 0 0 40px rgba(255, 87, 87, 0.95);
+ }
+}
+
.animate-fadeInScale {
animation: fadeInScale 0.5s ease-out forwards;
}
+.animate-pulse-glow-danger {
+ animation: pulse-glow-danger 2s ease-in-out infinite;
+}
+
.animate-fadeIn {
animation: fadeIn 0.5s ease-out forwards;
}
diff --git a/src/quiz/helpers/getAnimationNameByQuizName.js b/src/quiz/helpers/getAnimationNameByQuizName.js
index 0b3e8cb..4093329 100644
--- a/src/quiz/helpers/getAnimationNameByQuizName.js
+++ b/src/quiz/helpers/getAnimationNameByQuizName.js
@@ -6,6 +6,7 @@ export const getAnimationNameByQuizName = quizName => {
'Insertion Sort': 'insertion',
'Listas Simples': 'simpleList',
Pilas: 'stack',
+ Colas: 'queue',
'Búsqueda Lineal': 'linearSearch',
'Búsqueda Binaria': 'binarySearch',
};
diff --git a/src/quiz/views/QuizView.jsx b/src/quiz/views/QuizView.jsx
index a92a1f4..fb92934 100644
--- a/src/quiz/views/QuizView.jsx
+++ b/src/quiz/views/QuizView.jsx
@@ -214,7 +214,7 @@ export const QuizView = () => {
)}
- >>>>>>> origin/feature/quiz-y-busqueda-implementados
+
{shouldShowFeedbackButton && (
{
diff --git a/src/store/index.js b/src/store/index.js
index 459c489..8d46ab6 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -6,3 +6,4 @@ export * from './simpleList';
export * from './doubleList';
export * from './linearSearch';
export * from './binarySearch';
+export * from './queue';
diff --git a/src/store/store.js b/src/store/store.js
index 11a847a..6544e23 100644
--- a/src/store/store.js
+++ b/src/store/store.js
@@ -6,6 +6,7 @@ import simpleListSlice from './simpleList/simpleListSlice';
import doubleListSlice from './doubleList/doubleListSlice';
import linearSearchSlice from './linearSearch/linearSearchSlice';
import binarySearchSlice from './binarySearch/binarySearchSlice';
+import { queueSlice } from './queue';
export const store = configureStore({
reducer: {
@@ -16,6 +17,7 @@ export const store = configureStore({
doubleList: doubleListSlice,
linearSearch: linearSearchSlice,
binarySearch: binarySearchSlice,
+ queue: queueSlice.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
From 2695f005e8c9938fa0f06dad323202ed169e5854 Mon Sep 17 00:00:00 2001
From: FrankGarcia
Date: Sat, 14 Feb 2026 23:39:43 -0500
Subject: [PATCH 12/18] Esctruturas de datos y animaciones completas
---
src/animations/queue/algorithms/Nodo.js | 32 ++
.../queue/algorithms/QueueAlgorithm.jsx | 422 ++++++++++++++
src/animations/queue/algorithms/helpers.jsx | 33 ++
src/animations/queue/algorithms/index.js | 14 +
.../queue/components/QueueChart.jsx | 164 ++++++
src/animations/queue/components/QueueCode.jsx | 208 +++++++
.../queue/components/QueueControls.jsx | 541 ++++++++++++++++++
src/animations/queue/components/index.js | 3 +
src/animations/queue/index.js | 2 +
src/assets/svg/SvgConstans.jsx | 30 +-
src/components/CardTabla.jsx | 8 +-
src/components/QuizCard.jsx | 4 +-
src/index.css | 15 +
src/quiz/pages/DataStructuresPage.jsx | 4 +-
src/store/queue/index.js | 1 +
src/store/queue/queueSlice.js | 191 +++++++
16 files changed, 1657 insertions(+), 15 deletions(-)
create mode 100644 src/animations/queue/algorithms/Nodo.js
create mode 100644 src/animations/queue/algorithms/QueueAlgorithm.jsx
create mode 100644 src/animations/queue/algorithms/helpers.jsx
create mode 100644 src/animations/queue/algorithms/index.js
create mode 100644 src/animations/queue/components/QueueChart.jsx
create mode 100644 src/animations/queue/components/QueueCode.jsx
create mode 100644 src/animations/queue/components/QueueControls.jsx
create mode 100644 src/animations/queue/components/index.js
create mode 100644 src/animations/queue/index.js
create mode 100644 src/store/queue/index.js
create mode 100644 src/store/queue/queueSlice.js
diff --git a/src/animations/queue/algorithms/Nodo.js b/src/animations/queue/algorithms/Nodo.js
new file mode 100644
index 0000000..0c54a48
--- /dev/null
+++ b/src/animations/queue/algorithms/Nodo.js
@@ -0,0 +1,32 @@
+class Nodo {
+ constructor(value) {
+ this.value = value;
+ this.next = null;
+ }
+
+ getValue() {
+ return this.value;
+ }
+
+ setValue(value) {
+ this.value = value;
+ }
+
+ getNext() {
+ return this.next;
+ }
+
+ setNext(nextNode) {
+ this.next = nextNode;
+ }
+
+ hasNext() {
+ return this.next !== null;
+ }
+
+ toString() {
+ return `Nodo(${this.value})`;
+ }
+}
+
+export default Nodo;
diff --git a/src/animations/queue/algorithms/QueueAlgorithm.jsx b/src/animations/queue/algorithms/QueueAlgorithm.jsx
new file mode 100644
index 0000000..7f0e3e0
--- /dev/null
+++ b/src/animations/queue/algorithms/QueueAlgorithm.jsx
@@ -0,0 +1,422 @@
+import { newTraceQueue, moveInHistoryRecordQueue } from './helpers';
+import Nodo from './Nodo';
+
+// Copiar la cola completa
+const copiarCola = head => {
+ if (head === null) return null;
+
+ let nuevaCabeza = new Nodo(head.getValue());
+ let actualNuevo = nuevaCabeza;
+ let actualViejo = head.getNext();
+
+ while (actualViejo !== null) {
+ actualNuevo.setNext(new Nodo(actualViejo.getValue()));
+ actualNuevo = actualNuevo.getNext();
+ actualViejo = actualViejo.getNext();
+ }
+
+ return nuevaCabeza;
+};
+
+const crearRegistro = (
+ head,
+ primerConjunto = [],
+ segundoConjunto = [],
+ indiceActual = 0,
+ nodoAEliminar = null
+) => {
+ const highlightedNode =
+ primerConjunto && primerConjunto.length > 0 ? primerConjunto[0] : null;
+ const pointer =
+ segundoConjunto && segundoConjunto.length > 0 ? segundoConjunto[0] : null;
+
+ return {
+ head: copiarCola(head),
+ firstSet: [...primerConjunto],
+ secondSet: [...segundoConjunto],
+ isHead: head ? head.getValue() : -1,
+ currentIndex: indiceActual,
+ highlightedNode,
+ pointer,
+ deletingNode: nodoAEliminar,
+ };
+};
+
+// Enqueue: añadir al final (tail)
+export const Enqueue = (head, valor) => {
+ const historialPasos = [];
+
+ // Línea 0: creación del nuevo nodo
+ historialPasos.push(crearRegistro(head, [], [], 0));
+
+ const nuevoNodo = new Nodo(valor);
+ historialPasos.push(crearRegistro(head, [valor], [], 0));
+
+ if (head === null) {
+ // Línea 1: head === null
+ head = nuevoNodo;
+ historialPasos.push(crearRegistro(head, [], [], 1));
+ } else {
+ // Línea 2: else {
+ let actual = head;
+ historialPasos.push(crearRegistro(head, [], [actual.getValue()], 2));
+
+ // Línea 5: recorrido de la cola
+ while (actual.getNext() !== null) {
+ actual = actual.getNext();
+ historialPasos.push(crearRegistro(head, [valor], [actual.getValue()], 5));
+ }
+
+ // Línea 6: enlazar al final
+ actual.setNext(nuevoNodo);
+ historialPasos.push(crearRegistro(head, [], [nuevoNodo.getValue()], 6));
+ }
+
+ // Línea 7: cierre
+ historialPasos.push(crearRegistro(head, [], [], 7));
+
+ return { historialPasos, nuevaCabeza: head };
+};
+
+// Dequeue: eliminar del frente (head)
+export const Dequeue = cabeza => {
+ const historialPasos = [];
+
+ // Línea 0: if (head === null)
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+
+ if (cabeza === null) {
+ // Se mantiene en la línea 0 (retorno por vacío)
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ // Línea 1: valor = head.getValue() - resaltar el nodo en rojo
+ const valorEliminado = cabeza.getValue();
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+
+ // Mantener el resaltado en rojo (sin desaparecer aún) - varios pasos
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+
+ // AHORA SÍ: Marcar el nodo para animación de desaparición (sin el resaltado rojo pulsante)
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+
+ // Mantener la animación de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+
+ // Línea 2: head = head.getNext() - ahora sí eliminar
+ cabeza = cabeza.getNext();
+ historialPasos.push(crearRegistro(cabeza, [], [], 2));
+
+ // Última línea (fin): mantenerse en la 2 para mostrar estado final
+ historialPasos.push(crearRegistro(cabeza, [], [], 2));
+
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
+};
+
+// EliminarFinal: eliminar el último nodo (tail)
+export const EliminarFinal = cabeza => {
+ const historialPasos = [];
+
+ // Línea 0: if (head === null)
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+
+ if (cabeza === null) {
+ // Cola vacía
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ // Línea 1: if (head.getNext() === null) - solo un elemento
+ if (cabeza.getNext() === null) {
+ const valorEliminado = cabeza.getValue();
+ // Resaltar en rojo el único nodo
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 1));
+ // Marcar para desaparecer
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 1, valorEliminado));
+ // Eliminar
+ cabeza = null;
+ historialPasos.push(crearRegistro(cabeza, [], [], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
+ }
+
+ // Línea 2: actual = head
+ let nodoActual = cabeza;
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 2));
+
+ // Línea 3: recorrer hasta el penúltimo nodo
+ while (nodoActual.getNext() && nodoActual.getNext().getNext() !== null) {
+ nodoActual = nodoActual.getNext();
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 3));
+ }
+
+ // Encontrar el último nodo y resaltarlo en rojo
+ const valorEliminado = nodoActual.getNext().getValue();
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+
+ // Mantener el resaltado en rojo (sin desaparecer aún) - varios pasos
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+
+ // AHORA SÍ: Marcar el nodo para animación de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+
+ // Mantener la animación de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+
+ // Línea 4: actual.setNext(null) - eliminar el último nodo
+ nodoActual.setNext(null);
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+
+ // Última línea (fin)
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
+};
+
+// Insertar: insertar en posición (0 = frente)
+export const Insertar = (cabeza, valor, posicion) => {
+ const historialPasos = [];
+
+ // Línea 0: creación del nodo
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+
+ const nuevoNodo = new Nodo(valor);
+ historialPasos.push(crearRegistro(cabeza, [valor], [], 0));
+
+ if (cabeza === null) {
+ if (posicion === 0) {
+ // Línea 1: head === null && posicion === 0
+ cabeza = nuevoNodo;
+ historialPasos.push(crearRegistro(cabeza, [], [], 1));
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+ return { historialPasos, nuevaCabeza: cabeza };
+ } else {
+ // Línea 1: condición fallida -> return error
+ historialPasos.push(crearRegistro(cabeza, [], [], 1));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+ }
+
+ // calcular tamaño
+ let tamano = 0;
+ let temp = cabeza;
+ while (temp !== null) {
+ tamano++;
+ temp = temp.getNext();
+ }
+
+ if (posicion >= tamano) {
+ // insertar al final (enqueue)
+ let actual = cabeza;
+ while (actual.getNext() !== null) actual = actual.getNext();
+ actual.setNext(nuevoNodo);
+ historialPasos.push(crearRegistro(cabeza, [], [nuevoNodo.getValue()], 7));
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ if (posicion === 0) {
+ // insertar al frente (dequeue-front insertion)
+ nuevoNodo.setNext(cabeza);
+ cabeza = nuevoNodo;
+ historialPasos.push(crearRegistro(cabeza, [], [], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ // insertar en posición intermedia
+ let indice = 0;
+ let nodoActual = cabeza;
+
+ // Línea 4: actual = head
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 4));
+
+ // Línea 5/6: for y avance
+ while (indice < posicion - 1 && nodoActual.getNext() !== null) {
+ nodoActual = nodoActual.getNext();
+ indice++;
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 6));
+ }
+
+ // Línea 7: nuevoNodo.setNext(actual.getNext())
+ nuevoNodo.setNext(nodoActual.getNext());
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 7));
+
+ // Línea 8: actual.setNext(nuevoNodo)
+ nodoActual.setNext(nuevoNodo);
+ historialPasos.push(crearRegistro(cabeza, [], [nuevoNodo.getValue()], 8));
+
+ // Línea 9: cierre
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+
+ return { historialPasos, nuevaCabeza: cabeza };
+};
+
+// Extraer: eliminar en posición específica (índices alineados con ExtraerCode)
+export const Extraer = (cabeza, posicion) => {
+ const historialPasos = [];
+
+ // Línea 0: chequeo de cola vacía
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+
+ if (cabeza === null) {
+ historialPasos.push(crearRegistro(cabeza, [], [], 0));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ let tamano = 0;
+ let temp = cabeza;
+ while (temp !== null) {
+ tamano++;
+ temp = temp.getNext();
+ }
+
+ // Línea 1: posición fuera de rango
+ if (posicion >= tamano) {
+ historialPasos.push(crearRegistro(cabeza, [], [], 1));
+ return { historialPasos, nuevaCabeza: cabeza };
+ }
+
+ // Eliminar al frente (línea 2)
+ if (posicion === 0) {
+ const valor = cabeza.getValue();
+ // Resaltar en rojo el nodo a eliminar
+ historialPasos.push(crearRegistro(cabeza, [], [valor], 2));
+ // Mantener el resaltado en rojo (sin desaparecer aún) - varios pasos
+ historialPasos.push(crearRegistro(cabeza, [], [valor], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valor], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valor], 2));
+ // AHORA SÍ: Marcar para animación de desaparición (sin el resaltado rojo pulsante)
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valor));
+ // Mantener la animación de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valor));
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valor));
+ // Ahora sí eliminar el nodo
+ cabeza = cabeza.getNext();
+ historialPasos.push(crearRegistro(cabeza, [], [], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado: valor };
+ }
+
+ // Eliminar intermedio
+ let indice = 0;
+ let nodoActual = cabeza;
+
+ // Línea 4: inicializar "actual" - resaltar en rojo
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 4));
+
+ // Línea 5: recorrer hasta el nodo previo - resaltar cada nodo en rojo
+ while (indice < posicion - 1) {
+ nodoActual = nodoActual.getNext();
+ indice++;
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 5));
+ }
+
+ // Línea 7: encontrar el nodo a eliminar y resaltarlo en rojo
+ const valorEliminado = nodoActual.getNext().getValue();
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 7));
+
+ // Mantener el resaltado en rojo (sin desaparecer aún) - varios pasos
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 7));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 7));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 7));
+
+ // AHORA SÍ: Marcar el nodo para animación de desaparición (el nodo aún existe en la cola)
+ // Ya NO lo ponemos en secondSet para que no tenga el efecto de pulso rojo
+ historialPasos.push(crearRegistro(cabeza, [], [], 7, valorEliminado));
+
+ // Mantener la animación de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 7, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 7, valorEliminado));
+
+ // Línea 8: reasignar puntero para eliminar - ahora sí eliminamos el nodo
+ nodoActual.setNext(nodoActual.getNext().getNext());
+ historialPasos.push(crearRegistro(cabeza, [], [], 8));
+
+ // Línea 9: fin
+ historialPasos.push(crearRegistro(cabeza, [], [], 9));
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
+};
+
+export const EnqueueCode = ({ codeRef }) => {
+ return (
+
+ {`nuevoNodo = new Nodo(valor)`}
+ {`if (head === null) head = nuevoNodo`}
+ {`else {`}
+ {` actual = head`}
+ {` while (actual.getNext() !== null)`}
+ {` actual = actual.getNext()`}
+ {` actual.setNext(nuevoNodo)`}
+ {`}`}
+
+ );
+};
+
+export const DequeueCode = ({ codeRef }) => {
+ return (
+
+ {`if (head === null) return error`}
+ {`valor = head.getValue()`}
+ {`head = head.getNext()`}
+
+ );
+};
+
+export const EliminarFinalCode = ({ codeRef }) => {
+ return (
+
+ {`if (head === null) return error`}
+ {`if (head.getNext() === null) { head = null; return }`}
+ {`actual = head`}
+ {`while (actual.getNext().getNext() !== null)`}
+ {` actual = actual.getNext()`}
+ {`actual.setNext(null)`}
+
+ );
+};
+
+export const InsertarCode = ({ codeRef }) => {
+ return (
+
+ {`nuevoNodo = new Nodo(valor)`}
+ {`if (head === null && posicion === 0) head = nuevoNodo`}
+ {`else if (posicion === 0) { nuevoNodo.setNext(head); head = nuevoNodo }`}
+ {`else {`}
+ {` actual = head`}
+ {` for (i = 0; i < posicion - 1; i++)`}
+ {` actual = actual.getNext()`}
+ {` nuevoNodo.setNext(actual.getNext())`}
+ {` actual.setNext(nuevoNodo)`}
+ {`}`}
+
+ );
+};
+
+export const ExtraerCode = ({ codeRef }) => {
+ return (
+
+ {`if (head === null) return error`}
+ {`if (posicion >= tamano) return error`}
+ {`if (posicion === 0) head = head.getNext()`}
+ {`else {`}
+ {` actual = head`}
+ {` for (i = 0; i < posicion - 1; i++)`}
+ {` actual = actual.getNext()`}
+ {` eliminado = actual.getNext()`}
+ {` actual.setNext(actual.getNext().getNext())`}
+ {`}`}
+
+ );
+};
diff --git a/src/animations/queue/algorithms/helpers.jsx b/src/animations/queue/algorithms/helpers.jsx
new file mode 100644
index 0000000..c663f1c
--- /dev/null
+++ b/src/animations/queue/algorithms/helpers.jsx
@@ -0,0 +1,33 @@
+export const newTraceQueue = head => {
+ return [
+ {
+ head: head,
+ firstSet: [],
+ secondSet: [],
+ isHead: head ? head.getValue() : -1,
+ currentIndex: null,
+ highlightedNode: null,
+ pointer: null,
+ },
+ ];
+};
+
+export const moveInHistoryRecordQueue = (
+ stepHistory,
+ head,
+ firstSet = [],
+ secondSet = [],
+ currentIndex = null,
+ highlightedNode = null,
+ pointer = null
+) => {
+ stepHistory.push({
+ head: head,
+ firstSet: [...firstSet],
+ secondSet: [...secondSet],
+ isHead: head ? head.getValue() : -1,
+ currentIndex: currentIndex,
+ highlightedNode: highlightedNode,
+ pointer: pointer,
+ });
+};
diff --git a/src/animations/queue/algorithms/index.js b/src/animations/queue/algorithms/index.js
new file mode 100644
index 0000000..171c068
--- /dev/null
+++ b/src/animations/queue/algorithms/index.js
@@ -0,0 +1,14 @@
+export * from './helpers';
+export * from './Nodo';
+import * as QA from './QueueAlgorithm';
+
+export const Enqueue = QA.Enqueue;
+export const Dequeue = QA.Dequeue;
+export const EliminarFinal = QA.EliminarFinal;
+export const InsertarQueue = QA.Insertar;
+export const ExtraerQueue = QA.Extraer;
+export const EnqueueCode = QA.EnqueueCode;
+export const DequeueCode = QA.DequeueCode;
+export const EliminarFinalCode = QA.EliminarFinalCode;
+export const InsertarCode = QA.InsertarCode;
+export const ExtraerCode = QA.ExtraerCode;
diff --git a/src/animations/queue/components/QueueChart.jsx b/src/animations/queue/components/QueueChart.jsx
new file mode 100644
index 0000000..22e0cf3
--- /dev/null
+++ b/src/animations/queue/components/QueueChart.jsx
@@ -0,0 +1,164 @@
+import { useSelector } from 'react-redux';
+import { useState, useEffect } from 'react';
+
+// Convertir cola a array (head -> tail)
+const queueToArray = (node, arr = []) => {
+ if (!node) return arr;
+ arr.push(node);
+ return queueToArray(node.next, arr);
+};
+
+export const QueueChart = () => {
+ const {
+ head,
+ isHead,
+ firstSet,
+ secondSet,
+ highlightedNode,
+ pointer,
+ deletingNode,
+ } = useSelector(state => state.queue);
+
+ const nodes = queueToArray(head);
+ const nodeCount = nodes.length;
+
+ // Calcular tamaño dinámico basado en ancho disponible
+ const containerWidth = 520; // valor aproximado
+ const padding = 32;
+ const availableWidth = containerWidth - padding;
+
+ const getNodeStyles = () => {
+ // Mantener un tamaño de fuente legible y fijo para evitar que se haga demasiado pequeño
+ const fontSize = 14;
+
+ if (nodeCount === 0) return { width: 80, gap: 12, fontSize, height: 40 };
+
+ // Caso especial: un solo elemento debe verse más grande y centrado
+ if (nodeCount === 1) return { width: 140, gap: 12, fontSize, height: 48 };
+
+ const maxNodeWidth = 120;
+ const minNodeWidth = 60;
+ const maxGap = 16;
+ const minGap = 6;
+
+ const maxSpaceNeeded = nodeCount * maxNodeWidth + (nodeCount - 1) * maxGap;
+ if (maxSpaceNeeded <= availableWidth) {
+ return { width: maxNodeWidth, gap: maxGap, fontSize, height: 40 };
+ }
+
+ const ratio = availableWidth / maxSpaceNeeded;
+ const calculatedWidth = Math.max(
+ minNodeWidth,
+ Math.floor(maxNodeWidth * ratio)
+ );
+ const calculatedGap = Math.max(minGap, Math.floor(maxGap * ratio));
+
+ // No reducir la fuente más abajo de fontSize fijo
+ return { width: calculatedWidth, gap: calculatedGap, fontSize, height: 40 };
+ };
+
+ const { width, gap, fontSize, height } = getNodeStyles();
+
+ const getBackgroundColor = value => {
+ if (firstSet.includes(value)) return 'bg-success';
+ if (secondSet.includes(value)) return 'bg-danger';
+ return 'bg-secondary';
+ };
+
+ const ArrowComponent = ({ arrowWidth = 32 }) => {
+ return (
+
+ );
+ };
+
+ // Mostrar mensaje cuando la cola está vacía
+ if (!head || nodeCount === 0) {
+ return (
+
+ );
+ }
+
+ /* NodeComponent (simplificado temporalmente) */
+
+ return (
+
+
+ {nodes.map((node, index) => {
+ const isSingle = nodeCount === 1;
+ const label = isSingle
+ ? 'front / rear'
+ : index === 0
+ ? 'front'
+ : index === nodes.length - 1
+ ? 'rear'
+ : null;
+ const isDangerNode = secondSet.includes(node.value);
+ const isDeleting = deletingNode === node.value;
+ const isNewNode = firstSet.includes(node.value);
+ const bgClass = firstSet.includes(node.value)
+ ? 'bg-success'
+ : isDangerNode
+ ? 'bg-danger'
+ : 'bg-secondary';
+
+ // Determinar qué animación aplicar
+ let animationClass = '';
+ let animationDelay = '0ms';
+
+ if (isDeleting) {
+ animationClass = 'animate-fadeOutScale';
+ } else if (isNewNode) {
+ animationClass = 'animate-fadeInScale';
+ animationDelay = `${index * 150}ms`;
+ }
+
+ return (
+
+
+
{node.value}
+ {label && (
+
+ {label}
+
+ )}
+
+
+ );
+ })}
+
+
+ );
+};
diff --git a/src/animations/queue/components/QueueCode.jsx b/src/animations/queue/components/QueueCode.jsx
new file mode 100644
index 0000000..081e3bd
--- /dev/null
+++ b/src/animations/queue/components/QueueCode.jsx
@@ -0,0 +1,208 @@
+import { useEffect, useRef, useState } from 'react';
+import { useSelector } from 'react-redux';
+import {
+ EnqueueCode,
+ DequeueCode,
+ EliminarFinalCode,
+ InsertarCode,
+ ExtraerCode,
+} from '../algorithms/QueueAlgorithm';
+import { SvgIconDropdown, svgAlgo } from '../../../assets/svg/SvgConstans';
+import { useTranslation } from 'react-i18next';
+
+export const QueueCode = () => {
+ const codeRef = useRef(null);
+ const [prevCurrentLine, setPrevCurrentLine] = useState(null);
+ const { funAction, history, stepHistory } = useSelector(state => state.queue);
+ const [infoVisible, setInfoVisible] = useState(false);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (history === -1 || !stepHistory || history >= stepHistory.length) {
+ if (
+ prevCurrentLine !== null &&
+ codeRef.current?.childNodes[prevCurrentLine]
+ ) {
+ codeRef.current.childNodes[prevCurrentLine].classList.remove(
+ 'bg-secondary'
+ );
+ setPrevCurrentLine(null);
+ }
+ return;
+ }
+
+ const currentStep = stepHistory[history];
+ if (!currentStep) return;
+
+ const currentLine = currentStep.currentIndex;
+
+ const currentElement = codeRef.current?.childNodes[currentLine];
+ const prevElement =
+ prevCurrentLine !== null
+ ? codeRef.current?.childNodes[prevCurrentLine]
+ : null;
+
+ if (prevElement && prevCurrentLine !== currentLine) {
+ prevElement.classList.remove('bg-secondary');
+ }
+
+ if (currentElement && currentLine !== prevCurrentLine) {
+ currentElement.classList.add('bg-secondary');
+ }
+
+ setPrevCurrentLine(currentLine);
+ }, [history, stepHistory, funAction, prevCurrentLine]);
+
+ useEffect(() => {
+ return () => {
+ if (codeRef.current && prevCurrentLine !== null) {
+ const element = codeRef.current.childNodes[prevCurrentLine];
+ if (element) element.classList.remove('bg-secondary');
+ }
+ };
+ }, [prevCurrentLine]);
+
+ const toggleInfoPanel = () => {
+ setInfoVisible(!infoVisible);
+ };
+
+ const renderCode = funAction => {
+ switch (funAction) {
+ case 'enqueue':
+ return ;
+ case 'dequeue':
+ return ;
+ case 'eliminarFinal':
+ return ;
+ case 'insertar':
+ return ;
+ case 'extraer':
+ return ;
+ default:
+ return (
+
+
+ {t('selectOperation') ||
+ 'Selecciona una operación para ver su algoritmo'}
+
+
+ {t('availableOperations') ||
+ 'Enqueue, Dequeue, Eliminar Final, Insertar, Extraer'}
+
+
+ );
+ }
+ };
+
+ return (
+
+
+
+ {svgAlgo}
+ {t('algorithm') || 'Algoritmo'}
+
+
+
+
+ {t('explanation') || 'Explicación'}
+
+
+
+
+
+
+
{renderCode(funAction)}
+
+
+ {infoVisible && (
+
+
+
+
+ {t('definition') || 'Definición'}:
+
+
+ {t('queueDefinition') ||
+ 'Una cola es una estructura FIFO (First In, First Out).'}
+
+
+
+
+ {t('principalOperations') || 'Operaciones principales'}:
+
+
+
+ Enqueue:
+
+ Añade un elemento al final de la cola
+
+
+
+ Dequeue:
+
+ Elimina el elemento del frente de la cola
+
+
+
+
+ Eliminar Final:
+
+
+ Elimina el último elemento de la cola
+
+
+
+
+ Insertar:
+
+
+ Inserta un elemento en una posición específica
+
+
+
+
+ Extraer:
+
+
+ Elimina un elemento en una posición específica
+
+
+
+
+
+
+ {t('utility') || 'Utilidad'}:
+
+
+ {t('queueUsage') ||
+ 'Útil para manejar tareas en orden temporal, colas de mensajes y procesos.'}
+
+
+
+
+ {t('implementations') || 'Implementaciones'}:
+
+
+ {t('queueImplementations') || 'Arrays, listas enlazadas, etc.'}
+
+
+
+
+ )}
+
+ );
+};
+
+export default QueueCode;
diff --git a/src/animations/queue/components/QueueControls.jsx b/src/animations/queue/components/QueueControls.jsx
new file mode 100644
index 0000000..8f5f3b1
--- /dev/null
+++ b/src/animations/queue/components/QueueControls.jsx
@@ -0,0 +1,541 @@
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ decrementHistoryQueue,
+ incrementHistoryQueue,
+ actionButtonQueue,
+ restoreRepeatQueue,
+ restoreTimeIdQueue,
+ setHeadQueue,
+ setPlayingQueue,
+ setTimeIdQueue,
+ updateVisualizationQueue,
+} from '../../../store';
+import { Alert, Button, Snackbar, TextField } from '@mui/material';
+import { useEffect, useState, useCallback, useRef } from 'react';
+import { BasicButton } from '../../../components';
+import {
+ svgPause,
+ svgBack,
+ svgPlay,
+ svgRepeat,
+ svgForward,
+} from '../../../assets/svg/SvgConstans';
+
+export const QueueControls = () => {
+ const queueState = useSelector(state => state.queue);
+ const dispatch = useDispatch();
+
+ const [value, setValue] = useState('');
+ const [insertPosition, setInsertPosition] = useState('');
+ const [extractPosition, setExtractPosition] = useState('');
+ const [toast, setToast] = useState('');
+ const [open, setOpen] = useState(false);
+ const [isAnimating, setIsAnimating] = useState(false);
+
+ const animationExecutedRef = useRef(false);
+ const currentFunActionRef = useRef(null);
+
+ const handleValueChange = event => {
+ const inputValue = event.target.value;
+ if (inputValue === '' || (!isNaN(inputValue) && inputValue !== null)) {
+ setValue(inputValue);
+ }
+ };
+
+ const handleInsertPositionChange = event => {
+ const inputValue = event.target.value;
+ if (inputValue === '' || (!isNaN(inputValue) && inputValue >= 0)) {
+ setInsertPosition(inputValue);
+ }
+ };
+
+ const handleExtractPositionChange = event => {
+ const inputValue = event.target.value;
+ if (inputValue === '' || (!isNaN(inputValue) && inputValue >= 0)) {
+ setExtractPosition(inputValue);
+ }
+ };
+
+ const showError = useCallback(message => {
+ setToast(message);
+ setOpen(true);
+ }, []);
+
+ const runAnimation = useCallback(
+ (stepHistory, startFromBeginning = false) => {
+ if (!stepHistory || stepHistory.length === 0) {
+ setIsAnimating(false);
+ return;
+ }
+
+ dispatch(restoreTimeIdQueue());
+ dispatch(setPlayingQueue(true));
+ setIsAnimating(true);
+
+ const timeIds = [];
+
+ if (startFromBeginning) {
+ dispatch(updateVisualizationQueue(stepHistory[0]));
+ dispatch(incrementHistoryQueue());
+ }
+
+ const startIndex = startFromBeginning ? 1 : 0;
+
+ for (let i = startIndex; i < stepHistory.length; i++) {
+ const step = stepHistory[i];
+ const timeoutId = setTimeout(
+ () => {
+ dispatch(updateVisualizationQueue(step));
+ dispatch(incrementHistoryQueue());
+ },
+ (i - startIndex) * (250 / (queueState.timeStep || 1))
+ );
+
+ timeIds.push(timeoutId);
+ }
+
+ const finalTimeoutId = setTimeout(
+ () => {
+ const finalState = stepHistory[stepHistory.length - 1];
+ if (finalState) {
+ dispatch(updateVisualizationQueue(finalState));
+ }
+
+ dispatch(setPlayingQueue(false));
+ dispatch(restoreTimeIdQueue());
+ setIsAnimating(false);
+ animationExecutedRef.current = false;
+ },
+ (stepHistory.length - startIndex) * (250 / (queueState.timeStep || 1))
+ );
+
+ timeIds.push(finalTimeoutId);
+ dispatch(setTimeIdQueue(timeIds));
+ },
+ [dispatch, queueState.timeStep]
+ );
+
+ const executeAnimation = useCallback(
+ (action, value, position = null) => {
+ if (isAnimating) {
+ showError('Espera a que termine la animación actual');
+ return;
+ }
+
+ try {
+ dispatch(restoreTimeIdQueue());
+ dispatch(setPlayingQueue(false));
+
+ const actionPayload = { action, value };
+ if (position !== null) actionPayload.position = position;
+
+ dispatch(setHeadQueue(queueState.head));
+ dispatch(actionButtonQueue(actionPayload));
+ dispatch(restoreRepeatQueue());
+
+ animationExecutedRef.current = false;
+ currentFunActionRef.current = action;
+ } catch (error) {
+ console.error('Error executing animation:', error);
+ showError('Error al ejecutar la animación');
+ setIsAnimating(false);
+ }
+ },
+ [dispatch, queueState.head, isAnimating, showError]
+ );
+
+ useEffect(() => {
+ if (
+ queueState.stepHistory &&
+ queueState.stepHistory.length > 0 &&
+ queueState.funAction &&
+ queueState.funAction === currentFunActionRef.current &&
+ !queueState.playing &&
+ !animationExecutedRef.current
+ ) {
+ animationExecutedRef.current = true;
+ runAnimation(queueState.stepHistory, true);
+ }
+ }, [queueState.stepHistory, queueState.funAction, runAnimation]);
+
+ useEffect(() => {
+ if (queueState.funAction !== currentFunActionRef.current) {
+ animationExecutedRef.current = false;
+ currentFunActionRef.current = queueState.funAction;
+ }
+ }, [queueState.funAction]);
+
+ const handleEnqueue = () => {
+ const numValue = value === '' ? null : parseInt(value);
+ if (numValue === null) {
+ showError('Ingresa un valor válido');
+ return;
+ }
+
+ if (queueState.elementos && queueState.elementos.length >= 7) {
+ showError('La cola está llena (máximo 7 elementos)');
+ return;
+ }
+
+ if (queueState.elementos && queueState.elementos.includes(numValue)) {
+ showError('El elemento ya existe en la cola');
+ return;
+ }
+
+ executeAnimation('enqueue', numValue);
+ setValue('');
+ };
+
+ const handleDequeue = () => {
+ if (!queueState.head) {
+ showError('La cola está vacía');
+ return;
+ }
+
+ const frontValue = queueState.head.getValue();
+ executeAnimation('dequeue', frontValue);
+ };
+
+ const handleEliminarFinal = () => {
+ if (!queueState.head) {
+ showError('La cola está vacía');
+ return;
+ }
+
+ executeAnimation('eliminarFinal', null);
+ };
+
+ const handleInsert = () => {
+ const numValue = value === '' ? null : parseInt(value);
+ const pos = insertPosition === '' ? null : parseInt(insertPosition);
+
+ if (numValue === null) {
+ showError('Ingresa un valor válido para insertar');
+ return;
+ }
+
+ if (pos === null || pos < 0) {
+ showError('Ingresa una posición válida (≥ 0)');
+ return;
+ }
+
+ if (queueState.elementos && queueState.elementos.includes(numValue)) {
+ showError('El elemento ya existe en la cola');
+ return;
+ }
+
+ if (queueState.elementos && queueState.elementos.length >= 7) {
+ showError('La cola está llena (máximo 7 elementos)');
+ return;
+ }
+
+ const maxPosition = queueState.elementos ? queueState.elementos.length : 0;
+ if (pos > maxPosition) {
+ showError(`La posición máxima válida es ${maxPosition}`);
+ return;
+ }
+
+ executeAnimation('insertar', numValue, pos);
+ setValue('');
+ setInsertPosition('');
+ };
+
+ const handleExtraer = () => {
+ if (!queueState.head) {
+ showError('La cola está vacía');
+ return;
+ }
+
+ const pos = extractPosition === '' ? null : parseInt(extractPosition);
+ if (pos === null || pos < 0) {
+ showError('Ingresa una posición válida (≥ 0)');
+ return;
+ }
+
+ const maxPosition = queueState.elementos
+ ? queueState.elementos.length - 1
+ : 0;
+ if (pos > maxPosition) {
+ showError(`La posición máxima válida es ${maxPosition}`);
+ return;
+ }
+
+ executeAnimation('extraer', null, pos);
+ setExtractPosition('');
+ };
+
+ const onPlayPause = e => {
+ e.preventDefault();
+ if (queueState.playing) {
+ pause();
+ } else {
+ play();
+ }
+ };
+
+ const play = () => {
+ if (queueState.stepHistory && queueState.stepHistory.length > 0) {
+ const currentHistory = queueState.history || -1;
+ const remainingSteps = queueState.stepHistory.slice(
+ Math.max(0, currentHistory + 1)
+ );
+
+ if (remainingSteps.length > 0) {
+ animationExecutedRef.current = true;
+ runAnimation(remainingSteps, false);
+ } else {
+ dispatch(restoreRepeatQueue());
+ animationExecutedRef.current = true;
+ runAnimation(queueState.stepHistory, true);
+ }
+ }
+ };
+
+ const pause = () => {
+ dispatch(setPlayingQueue(false));
+ dispatch(restoreTimeIdQueue());
+ setIsAnimating(false);
+ };
+
+ const goBackward = () => {
+ if (queueState.playing) pause();
+
+ const currentHistory = queueState.history || -1;
+ if (currentHistory > 0 && queueState.stepHistory) {
+ const previousStep = queueState.stepHistory[currentHistory - 1];
+ dispatch(decrementHistoryQueue());
+ dispatch(updateVisualizationQueue(previousStep));
+ }
+ };
+
+ const goForward = () => {
+ if (queueState.playing) pause();
+
+ const currentHistory = queueState.history || -1;
+ if (
+ queueState.stepHistory &&
+ currentHistory < queueState.stepHistory.length - 1
+ ) {
+ const nextStep = queueState.stepHistory[currentHistory + 1];
+ dispatch(incrementHistoryQueue());
+ dispatch(updateVisualizationQueue(nextStep));
+ }
+ };
+
+ const repeat = () => {
+ if (queueState.stepHistory && queueState.stepHistory.length > 0) {
+ dispatch(restoreTimeIdQueue());
+ dispatch(restoreRepeatQueue());
+ animationExecutedRef.current = true;
+ runAnimation(queueState.stepHistory, true);
+ }
+ };
+
+ useEffect(() => {
+ return () => {
+ dispatch(restoreTimeIdQueue());
+ };
+ }, [dispatch]);
+
+ const playPauseIcon = queueState.playing ? svgPause : svgPlay;
+
+ return (
+
+ {/* Controles de reproducción */}
+
+
+ {svgBack}
+
+
+
+ {playPauseIcon}
+
+
+ =
+ (queueState.stepHistory ? queueState.stepHistory.length - 1 : 0)
+ }
+ title="Avanzar"
+ >
+ {svgForward}
+
+
+
+ {svgRepeat}
+
+
+
+ {/* Controles Enqueue/Dequeue */}
+
+
+
+
+ Enqueue
+
+
+ Dequeue
+
+
+
+
+ {/* Controles para Insertar */}
+
+
+
+ Insertar
+
+
+
+ {/* Controles para Extraer */}
+
+
+
+ Extraer
+
+
+
+ {/* Control Eliminar Final */}
+
+
+ Eliminar Final
+
+
+
+ {/* Notificaciones */}
+
setOpen(false)}
+ >
+ {toast}
+
+
+ );
+};
+
+export default QueueControls;
diff --git a/src/animations/queue/components/index.js b/src/animations/queue/components/index.js
new file mode 100644
index 0000000..cb074c9
--- /dev/null
+++ b/src/animations/queue/components/index.js
@@ -0,0 +1,3 @@
+export * from './QueueChart';
+export * from './QueueControls';
+export * from './QueueCode';
diff --git a/src/animations/queue/index.js b/src/animations/queue/index.js
new file mode 100644
index 0000000..44413ea
--- /dev/null
+++ b/src/animations/queue/index.js
@@ -0,0 +1,2 @@
+export * from './algorithms';
+export * from './components';
diff --git a/src/assets/svg/SvgConstans.jsx b/src/assets/svg/SvgConstans.jsx
index 15ad438..571ad26 100644
--- a/src/assets/svg/SvgConstans.jsx
+++ b/src/assets/svg/SvgConstans.jsx
@@ -166,10 +166,24 @@ export const svgLinkedList = (
);
+export const svgLinkedListDataStructure = (
+
+
+
+);
+
export const svgBinaryTrees = (
@@ -183,8 +197,8 @@ export const svgBinaryTrees = (
export const svgPiles = (
@@ -217,8 +231,8 @@ export const svgPilesHome = (
export const svgQueue = (
@@ -638,8 +652,8 @@ export const svgMenu = (
export const svgSimpleList = (
diff --git a/src/components/CardTabla.jsx b/src/components/CardTabla.jsx
index cfa5e11..1e998fd 100644
--- a/src/components/CardTabla.jsx
+++ b/src/components/CardTabla.jsx
@@ -6,9 +6,11 @@ const CardTabla = ({ tema, ruta = '' }) => {
return (
-
-
{imagen}
-
{titulo}
+
+
+ {imagen}
+
+
{titulo}
);
diff --git a/src/components/QuizCard.jsx b/src/components/QuizCard.jsx
index 370d6c3..cc07ddf 100644
--- a/src/components/QuizCard.jsx
+++ b/src/components/QuizCard.jsx
@@ -6,12 +6,12 @@ const QuizCard = ({ logo, titulo, ruta }) => {
return (
-
+
{logo}
-
+
{titulo}
diff --git a/src/index.css b/src/index.css
index 6209c68..4f2f673 100644
--- a/src/index.css
+++ b/src/index.css
@@ -63,6 +63,17 @@
}
}
+@keyframes fadeOutScale {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ 100% {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+}
+
@keyframes fadeIn {
0% {
opacity: 0;
@@ -98,6 +109,10 @@
animation: fadeInScale 0.5s ease-out forwards;
}
+.animate-fadeOutScale {
+ animation: fadeOutScale 0.5s ease-out forwards;
+}
+
.animate-pulse-glow-danger {
animation: pulse-glow-danger 2s ease-in-out infinite;
}
diff --git a/src/quiz/pages/DataStructuresPage.jsx b/src/quiz/pages/DataStructuresPage.jsx
index 8c72122..3f3f263 100644
--- a/src/quiz/pages/DataStructuresPage.jsx
+++ b/src/quiz/pages/DataStructuresPage.jsx
@@ -5,7 +5,7 @@ import { useEffect } from 'react';
import { resetStateForQuiz } from '../../store';
import {
svgSimpleList,
- svgLinkedList,
+ svgLinkedListDataStructure,
svgBinaryTrees,
svgPiles,
svgQueue,
@@ -31,7 +31,7 @@ export const DataStructuresPage = () => {
id: 2,
titulo: t('linkedLists'),
param: 'linkedList',
- imagen: svgLinkedList,
+ imagen: svgLinkedListDataStructure,
},
{
id: 3,
diff --git a/src/store/queue/index.js b/src/store/queue/index.js
new file mode 100644
index 0000000..0034225
--- /dev/null
+++ b/src/store/queue/index.js
@@ -0,0 +1 @@
+export * from './queueSlice';
diff --git a/src/store/queue/queueSlice.js b/src/store/queue/queueSlice.js
new file mode 100644
index 0000000..0c07947
--- /dev/null
+++ b/src/store/queue/queueSlice.js
@@ -0,0 +1,191 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ Dequeue,
+ Enqueue,
+ EliminarFinal,
+ InsertarQueue,
+ ExtraerQueue,
+} from '../../animations';
+
+const initialState = {
+ head: null,
+ elementos: [],
+ stepHistory: [],
+ timeStep: 1,
+
+ funAction: null,
+
+ history: -1,
+ firstSet: [],
+ secondSet: [],
+ highlightedNode: null,
+ pointer: null,
+ deletingNode: null,
+ timeId: [],
+ isHead: -1,
+
+ srcHead: null,
+ playing: false,
+};
+
+export const queueSlice = createSlice({
+ name: 'queue',
+ initialState,
+ reducers: {
+ setHeadQueue: (state, action) => {
+ restoreQueue(state, action);
+ restoreTimeIdQueue(state);
+ },
+ actionButtonQueue: (state, action) => {
+ createRecordQueue(state, action);
+ },
+ restoreQueue: (state, action) => {
+ state.head = action.payload;
+ state.stepHistory = [];
+ state.timeStep = 1;
+
+ state.funAction = null;
+
+ state.history = -1;
+ state.firstSet = [];
+ state.secondSet = [];
+ state.timeId = [];
+
+ state.srcHead = action.payload;
+
+ updateElementsFromHead(state);
+ },
+ restoreTimeIdQueue: state => {
+ state.timeId.forEach(timeoutId => clearTimeout(timeoutId));
+ state.timeId = [];
+ },
+ updateVisualizationQueue: (state, action) => {
+ const currVisualization = action.payload;
+
+ let elementos = [];
+ let current = currVisualization.head;
+ while (current !== null) {
+ elementos.push(current.getValue());
+ current = current.getNext();
+ }
+
+ state.head = currVisualization.head;
+ state.firstSet = currVisualization.firstSet;
+ state.secondSet = currVisualization.secondSet;
+ state.highlightedNode = currVisualization.highlightedNode || null;
+ state.pointer = currVisualization.pointer || null;
+ state.deletingNode = currVisualization.deletingNode || null;
+ state.isHead = currVisualization.isHead;
+ state.elementos = elementos;
+ },
+ restoreRepeatQueue: state => {
+ const head = state.srcHead;
+ state.head = head;
+ state.history = -1;
+ state.firstSet = [];
+ state.secondSet = [];
+ state.deletingNode = null;
+ state.isHead = -1;
+
+ updateElementsFromHead(state);
+ },
+
+ setTimeIdQueue: (state, action) => {
+ state.timeId.push(...action.payload);
+ },
+
+ incrementHistoryQueue: state => {
+ state.history = state.history + 1;
+ },
+
+ decrementHistoryQueue: state => {
+ state.history = state.history - 1;
+ },
+
+ setPlayingQueue: (state, action) => {
+ state.playing = action.payload;
+ },
+ },
+});
+
+const updateElementsFromHead = state => {
+ let elementos = [];
+ let current = state.head;
+ while (current !== null) {
+ elementos.push(current.getValue());
+ current = current.getNext();
+ }
+ state.elementos = elementos;
+};
+
+const createRecordQueue = (state, action) => {
+ const head = state.head;
+ const isHead = state.isHead;
+ const value = action.payload.value;
+ const position = action.payload.position;
+
+ const funAction = getActionQueue(action.payload.action);
+ if (funAction) {
+ let result;
+
+ if (action.payload.action === 'insertar') {
+ result = funAction(head, value, position, isHead);
+ } else if (action.payload.action === 'extraer') {
+ result = funAction(head, position, isHead);
+ } else {
+ result = funAction(head, value, isHead);
+ }
+
+ if (result && result.historialPasos) {
+ state.stepHistory = result.historialPasos;
+ state.funAction = action.payload.action;
+
+ if (result.nuevaCabeza !== undefined) {
+ state.srcHead = result.nuevaCabeza;
+ }
+ }
+ }
+};
+
+export const getActionQueue = action => {
+ const actions = {
+ enqueue: Enqueue,
+ dequeue: Dequeue,
+ eliminarFinal: EliminarFinal,
+ insertar: InsertarQueue,
+ extraer: ExtraerQueue,
+ };
+ return actions[action] || null;
+};
+
+export const verificarCola = (head, elemento) => {
+ let current = head;
+ while (current !== null) {
+ if (current.getValue() === elemento) return true;
+ current = current.getNext();
+ }
+ return false;
+};
+
+const {
+ restoreQueue,
+ restoreTimeIdQueue,
+ updateVisualizationQueue,
+ restoreRepeatQueue,
+} = queueSlice.actions;
+
+export const {
+ actionButtonQueue,
+ setHeadQueue,
+ decrementHistoryQueue,
+ incrementHistoryQueue,
+ setTimeIdQueue,
+ setPlayingQueue,
+} = queueSlice.actions;
+
+export {
+ restoreQueue,
+ restoreTimeIdQueue,
+ updateVisualizationQueue,
+ restoreRepeatQueue,
+};
From a169fad7a99f39a3483bfc579262076cf0c5cfc9 Mon Sep 17 00:00:00 2001
From: FrankDev
Date: Wed, 25 Feb 2026 15:42:51 -0500
Subject: [PATCH 13/18] Animaciones de pilas corregidas y boton volver quizzes
---
.../stack/algorithms/StackAlgorithm.jsx | 198 ++++++++++--------
.../stack/components/StackChart.jsx | 65 ++++--
src/quiz/pages/DataStructuresPage.jsx | 17 +-
src/quiz/pages/SearchMethodsPage.jsx | 17 +-
src/quiz/pages/SortingMethodsPage.jsx | 17 +-
src/store/stack/stackSlice.js | 4 +
6 files changed, 202 insertions(+), 116 deletions(-)
diff --git a/src/animations/stack/algorithms/StackAlgorithm.jsx b/src/animations/stack/algorithms/StackAlgorithm.jsx
index b7a9891..80787f0 100644
--- a/src/animations/stack/algorithms/StackAlgorithm.jsx
+++ b/src/animations/stack/algorithms/StackAlgorithm.jsx
@@ -23,7 +23,8 @@ const crearRegistro = (
cabeza,
primerConjunto = [],
segundoConjunto = [],
- indiceActual = 0
+ indiceActual = 0,
+ nodoAEliminar = null
) => {
return {
head: copiarPila(cabeza),
@@ -31,6 +32,7 @@ const crearRegistro = (
secondSet: [...segundoConjunto],
isHead: cabeza ? cabeza.getValue() : -1,
currentIndex: indiceActual,
+ deletingNode: nodoAEliminar,
};
};
@@ -84,11 +86,19 @@ export const Pop = cabeza => {
// Paso 3: Guardar el valor a eliminar
const valorEliminado = cabeza.getValue();
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 2));
+
+ // Resaltar en rojo (secondSet) varios pasos como en la cola
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+
+ // Preparar la animación de desaparición (deletingNode)
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valorEliminado));
// Paso 4: Actualizar el tope para que apunte al siguiente elemento
cabeza = cabeza.getNext();
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [], 3));
// Paso 5: Mostrar estado final
historialPasos.push(crearRegistro(cabeza, [], [], 4));
@@ -112,18 +122,18 @@ export const Sumergir = cabeza => {
// Paso 3: Guardar el nodo del tope y su valor
const nodoTope = cabeza;
const valorTope = nodoTope.getValue();
- historialPasos.push(crearRegistro(cabeza, [valorTope], [], 2));
+ historialPasos.push(crearRegistro(cabeza, [valorTope], [], 2, valorTope));
// Paso 4: Actualizar el tope para que apunte al segundo elemento
cabeza = cabeza.getNext();
- historialPasos.push(crearRegistro(cabeza, [valorTope], [], 3));
+ historialPasos.push(crearRegistro(cabeza, [valorTope], [], 3, valorTope));
// Paso 5: Buscar el último nodo de la pila
let nodoActual = cabeza;
while (nodoActual.getNext() !== null) {
nodoActual = nodoActual.getNext();
historialPasos.push(
- crearRegistro(cabeza, [valorTope], [nodoActual.getValue()], 4)
+ crearRegistro(cabeza, [valorTope], [nodoActual.getValue()], 4, valorTope)
);
}
@@ -172,8 +182,8 @@ export const Insertar = (cabeza, valor, posicion) => {
temp = temp.getNext();
}
- // Paso 5: Si la posición es mayor o igual al tamaño, insertar en el tope (push)
- if (posicion >= tamanoPila) {
+ // Paso 5: Si posición <= 0, insertar en el tope (cima)
+ if (posicion <= 0) {
nuevoNodo.setNext(cabeza);
cabeza = nuevoNodo;
historialPasos.push(crearRegistro(cabeza, [], [], 2));
@@ -181,8 +191,8 @@ export const Insertar = (cabeza, valor, posicion) => {
return { historialPasos, nuevaCabeza: cabeza };
}
- // Paso 6: Si la posición es 0, insertar al fondo
- if (posicion === 0) {
+ // Paso 6: Si posición >= tamaño, insertar al fondo
+ if (posicion >= tamanoPila) {
// Encontrar el último nodo
let nodoActual = cabeza;
historialPasos.push(crearRegistro(cabeza, [valor], [], 3));
@@ -197,34 +207,33 @@ export const Insertar = (cabeza, valor, posicion) => {
// Insertar al final
nodoActual.setNext(nuevoNodo);
historialPasos.push(crearRegistro(cabeza, [], [nuevoNodo.getValue()], 4));
+ historialPasos.push(crearRegistro(cabeza, [], [], 5));
+ return { historialPasos, nuevaCabeza: cabeza };
}
- // Paso 7: Insertar en posición intermedia
- else {
- // Convertir posición (desde el fondo) a índice (desde el tope)
- let indiceDesdeTop = tamanoPila - posicion - 1;
- let nodoActual = cabeza;
- let contador = 0;
- historialPasos.push(crearRegistro(cabeza, [valor], [], 3));
- // Navegar hasta la posición anterior
- while (contador < indiceDesdeTop) {
- nodoActual = nodoActual.getNext();
- contador++;
- historialPasos.push(
- crearRegistro(cabeza, [valor], [nodoActual.getValue()], 3)
- );
- }
+ // Paso 7: Insertar en posición intermedia (contando desde el tope)
+ let nodoActual = cabeza;
+ let contador = 0;
+ historialPasos.push(crearRegistro(cabeza, [valor], [], 3));
- // Insertar el nuevo nodo
- nuevoNodo.setNext(nodoActual.getNext());
+ // Navegar hasta la posición anterior
+ while (contador < posicion - 1) {
+ nodoActual = nodoActual.getNext();
+ contador++;
historialPasos.push(
- crearRegistro(cabeza, [valor], [nodoActual.getValue()], 4)
+ crearRegistro(cabeza, [valor], [nodoActual.getValue()], 3)
);
-
- nodoActual.setNext(nuevoNodo);
- historialPasos.push(crearRegistro(cabeza, [], [nuevoNodo.getValue()], 4));
}
+ // Insertar el nuevo nodo
+ nuevoNodo.setNext(nodoActual.getNext());
+ historialPasos.push(
+ crearRegistro(cabeza, [valor], [nodoActual.getValue()], 4)
+ );
+
+ nodoActual.setNext(nuevoNodo);
+ historialPasos.push(crearRegistro(cabeza, [], [nuevoNodo.getValue()], 4));
+
// Paso 8: Mostrar estado final
historialPasos.push(crearRegistro(cabeza, [], [], 5));
@@ -252,83 +261,88 @@ export const Extraer = (cabeza, posicion) => {
temp = temp.getNext();
}
- // Paso 4: Verificar que la posición sea válida
- if (posicion >= tamanoPila) {
+ // Paso 4: Verificar que la posición sea válida (0 <= posición < tamanoPila)
+ if (posicion < 0 || posicion >= tamanoPila) {
historialPasos.push(crearRegistro(cabeza, [], [], 1));
return { historialPasos, nuevaCabeza: cabeza };
}
let valorEliminado;
- // Paso 5: Si la posición es la última (tope), eliminar del tope (pop)
- if (posicion === tamanoPila - 1) {
+ // Caso 1: Eliminar en posición 0 (tope -> pop-like)
+ if (posicion === 0) {
valorEliminado = cabeza.getValue();
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 2));
+
+ // Resaltar en rojo varios pasos
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 2));
+
+ // Preparar desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 2, valorEliminado));
cabeza = cabeza.getNext();
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
}
- // Paso 6: Eliminar en otras posiciones
- else {
- historialPasos.push(crearRegistro(cabeza, [], [], 2));
- // Si la posición es 0 (fondo), usar lógica especial
- if (posicion === 0) {
- // Caso especial: eliminar el último nodo de la cadena
- if (tamanoPila === 1) {
- // Solo hay un elemento, eliminarlo
- valorEliminado = cabeza.getValue();
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 3));
- cabeza = null;
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 4));
- } else {
- // Encontrar el penúltimo nodo
- let nodoActual = cabeza;
- while (nodoActual.getNext().getNext() !== null) {
- nodoActual = nodoActual.getNext();
- historialPasos.push(
- crearRegistro(cabeza, [], [nodoActual.getValue()], 2)
- );
- }
-
- // Eliminar el último nodo
- valorEliminado = nodoActual.getNext().getValue();
- historialPasos.push(
- crearRegistro(cabeza, [valorEliminado], [nodoActual.getValue()], 3)
- );
-
- nodoActual.setNext(null);
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 4));
- }
- }
- // Eliminar en posición intermedia
- else {
- // Convertir posición (desde el fondo) a índice (desde el tope)
- let indiceDesdeTop = tamanoPila - posicion - 1;
- let nodoActual = cabeza;
- let contador = 0;
-
- // Navegar hasta el nodo anterior al que se va a eliminar
- while (contador < indiceDesdeTop - 1) {
- nodoActual = nodoActual.getNext();
- contador++;
- historialPasos.push(
- crearRegistro(cabeza, [], [nodoActual.getValue()], 2)
- );
- }
-
- // Guardar el valor a eliminar
- valorEliminado = nodoActual.getNext().getValue();
+ // Caso 2: Eliminar en la última posición (fondo)
+ if (posicion === tamanoPila - 1) {
+ // Encontrar el penúltimo nodo
+ let nodoActual = cabeza;
+ while (nodoActual.getNext().getNext() !== null) {
+ nodoActual = nodoActual.getNext();
historialPasos.push(
- crearRegistro(cabeza, [valorEliminado], [nodoActual.getValue()], 3)
+ crearRegistro(cabeza, [], [nodoActual.getValue()], 2)
);
-
- // Eliminar el nodo de la cadena
- nodoActual.setNext(nodoActual.getNext().getNext());
- historialPasos.push(crearRegistro(cabeza, [valorEliminado], [], 4));
}
+
+ // Valor a eliminar (el último)
+ valorEliminado = nodoActual.getNext().getValue();
+
+ // Resaltar en rojo antes de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+
+ // Preparar desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+
+ // Eliminar el último nodo
+ nodoActual.setNext(null);
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+ return { historialPasos, nuevaCabeza: cabeza, valorEliminado };
+ }
+
+ // Caso 3: Eliminar en posición intermedia (contando desde el tope)
+ // Navegar hasta el nodo anterior al que se va a eliminar
+ let nodoActual = cabeza;
+ let contador = 0;
+ while (contador < posicion - 1) {
+ nodoActual = nodoActual.getNext();
+ contador++;
+ historialPasos.push(crearRegistro(cabeza, [], [nodoActual.getValue()], 2));
}
+ // Guardar el valor a eliminar
+ valorEliminado = nodoActual.getNext().getValue();
+
+ // Resaltar en rojo antes de desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+ historialPasos.push(crearRegistro(cabeza, [], [valorEliminado], 3));
+
+ // Preparar desaparición
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+ historialPasos.push(crearRegistro(cabeza, [], [], 3, valorEliminado));
+
+ // Eliminar el nodo de la cadena
+ nodoActual.setNext(nodoActual.getNext().getNext());
+ historialPasos.push(crearRegistro(cabeza, [], [], 4));
+
// Paso 7: Mostrar estado final
historialPasos.push(crearRegistro(cabeza, [], [], 4));
diff --git a/src/animations/stack/components/StackChart.jsx b/src/animations/stack/components/StackChart.jsx
index 0754a2b..4efbe2b 100644
--- a/src/animations/stack/components/StackChart.jsx
+++ b/src/animations/stack/components/StackChart.jsx
@@ -1,4 +1,5 @@
import { useSelector } from 'react-redux';
+import { useState, useEffect } from 'react';
// Función para contar nodos en la pila
const countNodes = node => {
@@ -14,7 +15,7 @@ const stackToArray = (node, arr = []) => {
};
export const StackChart = () => {
- const { head, isHead, firstSet, secondSet } = useSelector(
+ const { head, isHead, firstSet, secondSet, deletingNode } = useSelector(
state => state.stack
);
@@ -77,6 +78,7 @@ export const StackChart = () => {
isHead={isHead}
firstSet={firstSet}
secondSet={secondSet}
+ deletingNode={deletingNode}
height={height}
fontSize={fontSize}
/>
@@ -86,35 +88,68 @@ export const StackChart = () => {
);
};
-const StackNode = ({ node, isHead, firstSet, secondSet, height, fontSize }) => {
+const StackNode = ({
+ node,
+ isHead,
+ firstSet,
+ secondSet,
+ deletingNode,
+ height,
+ fontSize,
+}) => {
const getBackgroundColor = () => {
if (firstSet.includes(node.value)) return 'bg-success';
if (secondSet.includes(node.value)) return 'bg-danger';
return 'bg-secondary';
};
- const isDangerNode = secondSet.includes(node.value);
+ const isDangerNode =
+ secondSet.includes(node.value) || deletingNode === node.value;
+ const isDeleting = deletingNode === node.value;
+ const isNewNode = firstSet.includes(node.value);
+ const bgClass = firstSet.includes(node.value)
+ ? 'bg-success'
+ : isDangerNode
+ ? 'bg-danger'
+ : 'bg-secondary';
+
+ // Determinar qué animación aplicar
+ let animationClass = '';
+
+ if (isDeleting) {
+ animationClass = 'animate-fadeOutScale';
+ } else if (isNewNode) {
+ animationClass = 'animate-fadeInScale';
+ }
return (
- {node.value === isHead && (
-
- ⬅️
- head
-
- )}
- {node.value}
+
+ {node.value === isHead && (
+
+
+ head
+
+ )}
+ {node.value}
+
);
};
diff --git a/src/quiz/pages/DataStructuresPage.jsx b/src/quiz/pages/DataStructuresPage.jsx
index 3f3f263..0910a36 100644
--- a/src/quiz/pages/DataStructuresPage.jsx
+++ b/src/quiz/pages/DataStructuresPage.jsx
@@ -1,4 +1,5 @@
import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
import CardTabla from '../../components/CardTabla';
import { SearchMethodGrid } from '../components';
import { useEffect } from 'react';
@@ -15,6 +16,7 @@ import { useTranslation } from 'react-i18next';
export const DataStructuresPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
+ const navigate = useNavigate();
useEffect(() => {
dispatch(resetStateForQuiz());
@@ -55,9 +57,18 @@ export const DataStructuresPage = () => {
return (
-
- {t('dataStructures') || 'Estructuras de Datos'}
-
+
+
navigate(-1)}
+ className="bg-gray-900 py-2 px-5 text-left rounded-r-3xl hover:bg-gray-800 transition-colors"
+ >
+ {t('back')}
+
+
+ {t('dataStructures') || 'Estructuras de Datos'}
+
+
+
diff --git a/src/quiz/pages/SearchMethodsPage.jsx b/src/quiz/pages/SearchMethodsPage.jsx
index 82d6c94..a74ed18 100644
--- a/src/quiz/pages/SearchMethodsPage.jsx
+++ b/src/quiz/pages/SearchMethodsPage.jsx
@@ -1,4 +1,5 @@
import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
import CardTabla from '../../components/CardTabla';
import { SearchMethodGrid } from '../components';
import { useEffect } from 'react';
@@ -9,6 +10,7 @@ import { useTranslation } from 'react-i18next';
export const SearchMethodsPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
+ const navigate = useNavigate();
useEffect(() => {
dispatch(resetStateForQuiz());
@@ -31,9 +33,18 @@ export const SearchMethodsPage = () => {
return (
-
- {t('searchingMethods') || 'Métodos de Búsqueda'}
-
+
+
navigate(-1)}
+ className="bg-gray-900 py-2 px-5 text-left rounded-r-3xl hover:bg-gray-800 transition-colors"
+ >
+ {t('back')}
+
+
+ {t('searchingMethods') || 'Métodos de Búsqueda'}
+
+
+
diff --git a/src/quiz/pages/SortingMethodsPage.jsx b/src/quiz/pages/SortingMethodsPage.jsx
index 4e8cb86..53ce894 100644
--- a/src/quiz/pages/SortingMethodsPage.jsx
+++ b/src/quiz/pages/SortingMethodsPage.jsx
@@ -1,4 +1,5 @@
import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
import CardTabla from '../../components/CardTabla';
import { topicCategories } from '../../components/HomeCartas';
import { SearchMethodGrid } from '../components';
@@ -10,6 +11,7 @@ import { useTranslation } from 'react-i18next';
export const SortingMethodsPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
+ const navigate = useNavigate();
useEffect(() => {
dispatch(resetStateForQuiz());
@@ -22,9 +24,18 @@ export const SortingMethodsPage = () => {
return (
-
- {t('sortingMethods')}
-
+
+
navigate(-1)}
+ className="bg-gray-900 py-2 px-5 text-left rounded-r-3xl hover:bg-gray-800 transition-colors"
+ >
+ {t('back')}
+
+
+ {t('sortingMethods')}
+
+
+
diff --git a/src/store/stack/stackSlice.js b/src/store/stack/stackSlice.js
index 532cf9c..ef2a066 100644
--- a/src/store/stack/stackSlice.js
+++ b/src/store/stack/stackSlice.js
@@ -12,6 +12,7 @@ const initialState = {
history: -1,
firstSet: [],
secondSet: [],
+ deletingNode: null,
timeId: [],
isHead: -1,
@@ -41,6 +42,7 @@ export const stackSlice = createSlice({
state.history = -1;
state.firstSet = [];
state.secondSet = [];
+ state.deletingNode = null;
state.timeId = [];
state.srcHead = action.payload;
@@ -69,6 +71,7 @@ export const stackSlice = createSlice({
state.secondSet = currVisualization.secondSet;
state.isHead = currVisualization.isHead;
state.elementos = elementos;
+ state.deletingNode = currVisualization.deletingNode || null;
},
restoreRepeatStack: state => {
const head = state.srcHead;
@@ -76,6 +79,7 @@ export const stackSlice = createSlice({
state.history = -1;
state.firstSet = [];
state.secondSet = [];
+ state.deletingNode = null;
state.isHead = -1;
// Actualizar elementos desde la cabeza restaurada
From bf31c82c060d91b9c461d687ab26c4786ce0fcb0 Mon Sep 17 00:00:00 2001
From: FrankDev
Date: Wed, 4 Mar 2026 18:37:29 -0500
Subject: [PATCH 14/18] Animaciones Corregidas,Codigos de JAVA, Implementacion
de GoogleAnalytics
---
GOOGLE_ANALYTICS_SETUP.md | 183 ++++++++++++++++++
package-lock.json | 14 +-
package.json | 11 +-
src/App.jsx | 61 +++---
.../binarySearch/algorithms/index.js | 26 +--
.../algorithms/DoubleListAlgorithm.jsx | 127 ++++++------
.../linearSearch/algorithms/index.js | 12 +-
.../queue/algorithms/QueueAlgorithm.jsx | 87 +++++----
.../algorithms/SimpleListAlgorithm.jsx | 98 +++++-----
.../sorting/algorithms/BubbleSort.jsx | 19 +-
.../sorting/algorithms/HeapSort.jsx | 17 +-
.../sorting/algorithms/InsertionSort.jsx | 19 +-
.../sorting/algorithms/QuickSort.jsx | 14 +-
.../sorting/algorithms/SelectionSort.jsx | 18 +-
.../sorting/algorithms/ShellSort.jsx | 24 ++-
.../stack/algorithms/StackAlgorithm.jsx | 74 ++++---
src/hooks/usePageTracking.js | 12 ++
src/main.jsx | 4 +
src/utils/googleAnalytics.js | 42 ++++
src/utils/trackingEvents.js | 43 ++++
20 files changed, 649 insertions(+), 256 deletions(-)
create mode 100644 GOOGLE_ANALYTICS_SETUP.md
create mode 100644 src/hooks/usePageTracking.js
create mode 100644 src/utils/googleAnalytics.js
create mode 100644 src/utils/trackingEvents.js
diff --git a/GOOGLE_ANALYTICS_SETUP.md b/GOOGLE_ANALYTICS_SETUP.md
new file mode 100644
index 0000000..c6ccf11
--- /dev/null
+++ b/GOOGLE_ANALYTICS_SETUP.md
@@ -0,0 +1,183 @@
+# Google Analytics - Guía de Implementación
+
+## ¿Qué se ha instalado?
+
+Google Analytics ya está integrado en tu proyecto y funcionando automáticamente. Aquí te explico qué hace:
+
+---
+
+## 📊 Rastreo Automático (Ya está funcionando)
+
+### 1. **Rastreo de Páginas Vistas**
+
+- Google Analytics rastrea automáticamente cada vez que un usuario navega a una nueva página
+- Se registran las rutas: `/`, `/quizzes`, `/animacion/:animacion`, `/quiz/:quizName`, etc.
+
+### 2. **Rastreo de Interacciones Automáticas**
+
+Gracias a la "Medición Mejorada" que configuramos, Google Analytics registra:
+
+- ✅ **Visitas de página** - Cada página que visita
+- ✅ **Desplazamientos (Scroll)** - Si bajan en la página
+- ✅ **Clics de salida** - Links que salen de tu sitio
+- ✅ **Tiempo en página** - Cuánto tiempo pasan en cada sección
+- ✅ **Rebotes** - Si salen sin interactuar
+
+---
+
+## 🎯 Rastreo Personalizado (Eventos Específicos)
+
+Ahora puedes rastrear eventos específicos que importan para tu negocio.
+
+### Ejemplo 1: Rastrear cuando inicia un quiz
+
+**En un componente de botón de quiz:**
+
+```jsx
+import { trackEvent } from '../utils/googleAnalytics';
+
+function QuizButton({ quizName }) {
+ const handleClick = () => {
+ // Rastrear el evento
+ trackEvent('quiz_iniciado', {
+ nombre_quiz: quizName,
+ timestamp: new Date().toISOString(),
+ });
+
+ // Luego navegar al quiz
+ navigate(`/quiz/${quizName}`);
+ };
+
+ return Iniciar {quizName} ;
+}
+```
+
+### Ejemplo 2: Rastrear cuando completa un quiz
+
+```jsx
+import { trackEvent } from '../utils/googleAnalytics';
+
+function QuizResults({ score, totalQuestions }) {
+ useEffect(() => {
+ // Rastrear que completó el quiz
+ trackEvent('quiz_completado', {
+ puntuacion: score,
+ total_preguntas: totalQuestions,
+ porcentaje: (score / totalQuestions) * 100,
+ });
+ }, [score, totalQuestions]);
+
+ return (
+
+
+ Resultado: {score}/{totalQuestions}
+
+
+ );
+}
+```
+
+### Ejemplo 3: Rastrear vistas de animaciones
+
+```jsx
+import { trackEvent } from '../utils/googleAnalytics';
+
+function AnimationView({ animationName }) {
+ useEffect(() => {
+ trackEvent('animacion_vista', {
+ nombre_animacion: animationName,
+ timestamp: new Date().toISOString(),
+ });
+ }, [animationName]);
+
+ return {/* Contenido de la animación */}
;
+}
+```
+
+---
+
+## 📈 ¿Dónde ver los datos en Google Analytics?
+
+1. **Ir a:** https://analytics.google.com
+2. **Seleccionar tu propiedad:** CodeScape3D
+3. **Ver en el panel:**
+ - **Usuarios en tiempo real** - Quién está usando ahora
+ - **Páginas principales** - Las secciones más visitadas
+ - **Duración promedio de sesión** - Cuánto tiempo pasan
+ - **Tasa de rebote** - Quiénes entran y se van sin interactuar
+ - **Eventos** - Los eventos personalizados que registraste
+
+---
+
+## 📝 ID de Medición
+
+Tu ID de medición es: **G-6G1Z8N0TNS**
+
+Este ID ya está configurado en tu proyecto y está enviando datos a Google Analytics.
+
+---
+
+## 🔍 Funciones Disponibles
+
+### `trackEvent(eventName, eventParams)`
+
+Rastrear un evento personalizado:
+
+```jsx
+import { trackEvent } from '../utils/googleAnalytics';
+
+trackEvent('evento_personalizado', {
+ parametro1: 'valor1',
+ parametro2: 'valor2',
+});
+```
+
+### `trackPageView(pageName)`
+
+Rastrear una página manualmente:
+
+```jsx
+import { trackPageView } from '../utils/googleAnalytics';
+
+trackPageView('/mi-pagina-especial');
+```
+
+---
+
+## 🚀 Pasos Siguientes Recomendados
+
+Para maximizar el valor de Google Analytics, considera rastrear:
+
+1. **Búsquedas** - Si tu app tiene búsqueda
+
+ ```jsx
+ trackEvent('busqueda_realizada', { query: searchTerm });
+ ```
+
+2. **Intentos fallidos** - Errores en quizzes
+
+ ```jsx
+ trackEvent('respuesta_incorrecta', { quiz_name: name });
+ ```
+
+3. **Descargas/Exportes** - Si los usuarios pueden descargar recursos
+
+ ```jsx
+ trackEvent('contenido_descargado', { tipo: 'pdf' });
+ ```
+
+4. **Tiempo en secciones** - Para ver qué les cuesta más trabajo
+
+---
+
+## ⚠️ Importante
+
+- Google Analytics tarda **~24-48 horas** en mostrar datos nuevos en el dashboard (datos históricos aparecen más rápido)
+- Los cambios en eventos personalizados se ven casi inmediatamente
+- **Consulta la política de privacidad** de tu sitio - Los usuarios deben saber que estás recopilando datos
+
+---
+
+## 📞 Preguntas?
+
+¿Necesitas rastrear algo específico? Implementa un evento personalizado usando `trackEvent()` con los parámetros que necesites.
diff --git a/package-lock.json b/package-lock.json
index 6852f45..a5f4384 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,8 @@
"react-dom": "^18.2.0",
"react-i18next": "^14.1.2",
"react-redux": "^8.1.2",
- "react-router-dom": "^6.14.2"
+ "react-router-dom": "^6.14.2",
+ "web-vitals": "^5.1.0"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
@@ -12287,6 +12288,12 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/web-vitals": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.1.0.tgz",
+ "integrity": "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==",
+ "license": "Apache-2.0"
+ },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -21450,6 +21457,11 @@
"makeerror": "1.0.12"
}
},
+ "web-vitals": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.1.0.tgz",
+ "integrity": "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="
+ },
"webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
diff --git a/package.json b/package.json
index e8842e7..0a3cee9 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,8 @@
"react-dom": "^18.2.0",
"react-i18next": "^14.1.2",
"react-redux": "^8.1.2",
- "react-router-dom": "^6.14.2"
+ "react-router-dom": "^6.14.2",
+ "web-vitals": "^5.1.0"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
@@ -42,17 +43,17 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
+ "husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
+ "lint-staged": "^14.0.0",
"postcss": "^8.4.27",
+ "prettier": "^3.0.1",
"tailwindcss": "^3.3.3",
"vite": "^4.4.5",
"vite-plugin-pwa": "^0.16.4",
- "whatwg-fetch": "^3.6.17",
- "husky": "^8.0.3",
- "lint-staged": "^14.0.0",
- "prettier": "^3.0.1"
+ "whatwg-fetch": "^3.6.17"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,json,css,md}": [
diff --git a/src/App.jsx b/src/App.jsx
index 84c8ae5..9eb7534 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -21,39 +21,46 @@ import { AnimationView } from './animations';
import { AppTheme } from './theme';
import { Provider } from 'react-redux';
import { store } from './store';
+import { usePageTracking } from './hooks/usePageTracking';
+
+function AppContent() {
+ // Rastrear cambios de página automáticamente
+ usePageTracking();
+
+ return (
+
+
+
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+}
function App() {
return (
-
-
-
- } />
- } />
- } />
- }
- />
- }
- />
- }
- />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
+
diff --git a/src/animations/binarySearch/algorithms/index.js b/src/animations/binarySearch/algorithms/index.js
index ed2f4f9..feee8b5 100644
--- a/src/animations/binarySearch/algorithms/index.js
+++ b/src/animations/binarySearch/algorithms/index.js
@@ -136,20 +136,20 @@ export const busquedaBinaria = (arr, target) => {
};
export const BusquedaBinariaCode = () => {
- return `function busquedaBinaria(arr, target) {
- let izquierda = 0;
- let derecha = arr.length - 1;
- while (izquierda <= derecha) {
- let medio = Math.floor((izq + der) / 2);
- if (arr[medio] === target) {
- return medio; // Encontrado
- } else if (arr[medio] < target) {
- izquierda = medio + 1;
- } else {
- derecha = medio - 1;
+ return `public static int busquedaBinaria(int[] arr, int target) {
+ int izquierda = 0;
+ int derecha = arr.length - 1;
+ while (izquierda <= derecha) {
+ int medio = (izquierda + derecha) / 2;
+ if (arr[medio] == target) {
+ return medio;
+ } else if (arr[medio] < target) {
+ izquierda = medio + 1;
+ } else {
+ derecha = medio - 1;
+ }
}
- }
- return -1; // No encontrado
+ return -1;
}`;
};
diff --git a/src/animations/doubleList/algorithms/DoubleListAlgorithm.jsx b/src/animations/doubleList/algorithms/DoubleListAlgorithm.jsx
index 129c752..5277604 100644
--- a/src/animations/doubleList/algorithms/DoubleListAlgorithm.jsx
+++ b/src/animations/doubleList/algorithms/DoubleListAlgorithm.jsx
@@ -902,89 +902,108 @@ export const Buscar = (cabeza, valor) => {
// Componentes de código para visualización
export const InsertarAlInicioCode = ({ codeRef }) => (
-
{'// Insertar al inicio - Lista Doble'}
-
{'nuevoNodo = new NodoDoble(valor)'}
-
{'nuevoNodo.next = cabeza'}
-
{'if (cabeza != null) cabeza.prev = nuevoNodo'}
-
{'cabeza = nuevoNodo'}
-
{'return cabeza'}
+
{'public void insertarAlInicio(int valor) {'}
+
{' NodeDoble nuevoNodo = new NodeDoble(valor);'}
+
{' nuevoNodo.setNext(cabeza);'}
+
{' if (cabeza != null) cabeza.setPrev(nuevoNodo);'}
+
{' cabeza = nuevoNodo;'}
+
{'}'}
);
export const InsertarAlFinalCode = ({ codeRef }) => (
-
{'// Insertar al final - Lista Doble'}
-
{'nuevoNodo = new NodoDoble(valor)'}
-
{'if (cabeza == null) return nuevoNodo'}
-
{'actual = cabeza'}
-
{'while (actual.next != null)'}
-
{' actual = actual.next'}
-
{'actual.next = nuevoNodo'}
-
{'nuevoNodo.prev = actual'}
-
{'return cabeza'}
+
{'public void insertarAlFinal(int valor) {'}
+
{' NodeDoble nuevoNodo = new NodeDoble(valor);'}
+
{' if (cabeza == null) { cabeza = nuevoNodo; return; }'}
+
{' NodeDoble actual = cabeza;'}
+
{' while (actual.getNext() != null)'}
+
{' actual = actual.getNext();'}
+
{' actual.setNext(nuevoNodo);'}
+
{' nuevoNodo.setPrev(actual);'}
+
{'}'}
);
export const InsertarEnPosicionCode = ({ codeRef }) => (
-
{'// Insertar en posición - Lista Doble'}
-
{'if (pos == 0) return insertarAlInicio()'}
-
{'nuevoNodo = new NodoDoble(valor)'}
-
{'actual = cabeza; cont = 0'}
-
{'while (cont < pos - 1 && actual != null)'}
-
{' actual = actual.next; cont++'}
-
{'nuevoNodo.next = actual.next'}
-
{'nuevoNodo.prev = actual'}
-
{'if (actual.next != null) actual.next.prev = nuevoNodo'}
-
{'actual.next = nuevoNodo'}
-
{'return cabeza'}
+
{'public void insertarEnPosicion(int valor, int pos) {'}
+
{' if (pos == 0) { insertarAlInicio(valor); return; }'}
+
{' NodeDoble nuevoNodo = new NodeDoble(valor);'}
+
{' NodeDoble actual = cabeza;'}
+
{' for (int i = 0; i < pos - 1 && actual != null; i++)'}
+
{' actual = actual.getNext();'}
+
{' if (actual == null) return;'}
+
{' nuevoNodo.setNext(actual.getNext());'}
+
{' nuevoNodo.setPrev(actual);'}
+
{' if (actual.getNext() != null)'}
+
{' actual.getNext().setPrev(nuevoNodo);'}
+
{' actual.setNext(nuevoNodo);'}
+
{'}'}
);
export const EliminarDelInicioCode = ({ codeRef }) => (
-
{'// Eliminar del inicio - Lista Doble'}
-
{'if (cabeza == null) return null'}
-
{'cabeza = cabeza.next'}
-
{'if (cabeza != null) cabeza.prev = null'}
-
{'return cabeza'}
+
{'public int eliminarDelInicio() {'}
+
{' if (cabeza == null) return -1;'}
+
{' int valor = cabeza.getValue();'}
+
{' cabeza = cabeza.getNext();'}
+
{' if (cabeza != null) cabeza.setPrev(null);'}
+
{' return valor;'}
+
{'}'}
);
export const EliminarDelFinalCode = ({ codeRef }) => (
-
{'// Eliminar del final - Lista Doble'}
-
{'if (cabeza == null) return null'}
-
{'if (cabeza.next == null) return null'}
-
{'actual = cabeza'}
-
{'while (actual.next != null)'}
-
{' actual = actual.next'}
-
{'actual.prev.next = null'}
-
{'return cabeza'}
+
{'public int eliminarDelFinal() {'}
+
{' if (cabeza == null) return -1;'}
+
{' if (cabeza.getNext() == null) {'}
+
{' int valor = cabeza.getValue();'}
+
{' cabeza = null;'}
+
{' return valor;'}
+
{' }'}
+
{' NodeDoble actual = cabeza;'}
+
{' while (actual.getNext() != null)'}
+
{' actual = actual.getNext();'}
+
{' int valor = actual.getValue();'}
+
{' actual.getPrev().setNext(null);'}
+
{' return valor;'}
+
{'}'}
);
export const EliminarEnPosicionCode = ({ codeRef }) => (
-
{'// Eliminar en posición - Lista Doble'}
-
{'if (pos == 0) return eliminarDelInicio()'}
-
{'actual = cabeza; cont = 0'}
-
{'while (cont < pos && actual != null)'}
-
{' actual = actual.next; cont++'}
-
{'if (actual.prev != null) actual.prev.next = actual.next'}
-
{'if (actual.next != null) actual.next.prev = actual.prev'}
-
{'return cabeza'}
+
{'public int eliminarEnPosicion(int pos) {'}
+
{' if (pos == 0) return eliminarDelInicio();'}
+
{' NodeDoble actual = cabeza;'}
+
{' for (int i = 0; i < pos && actual != null; i++)'}
+
{' actual = actual.getNext();'}
+
{' if (actual == null) return -1;'}
+
{' int valor = actual.getValue();'}
+
{' if (actual.getPrev() != null)'}
+
{' actual.getPrev().setNext(actual.getNext());'}
+
{' if (actual.getNext() != null)'}
+
{' actual.getNext().setPrev(actual.getPrev());'}
+
{' return valor;'}
+
{'}'}
);
export const BuscarCode = ({ codeRef }) => (
-
{'// Buscar en lista doble'}
-
{'if (cabeza == null) return -1'}
-
{'actual = cabeza; pos = 0'}
-
{'while (actual != null)'}
-
{' if (actual.valor == buscado) return pos'}
-
{' actual = actual.next; pos++'}
-
{'return -1'}
+
{'public int buscar(int valor) {'}
+
{' if (cabeza == null) return -1;'}
+
{' NodeDoble actual = cabeza;'}
+
{' int pos = 0;'}
+
{' while (actual != null) {'}
+
{' if (actual.getValue() == valor) return pos;'}
+
{' actual = actual.getNext();'}
+
{' pos++;'}
+
{' }'}
+
{' return -1;'}
+
{'}'}
);
diff --git a/src/animations/linearSearch/algorithms/index.js b/src/animations/linearSearch/algorithms/index.js
index 3f1cfbb..da44bb9 100644
--- a/src/animations/linearSearch/algorithms/index.js
+++ b/src/animations/linearSearch/algorithms/index.js
@@ -88,12 +88,12 @@ export const busquedaLineal = (array, target) => {
};
export const BusquedaLinealCode = () => {
- return `function busquedaLineal(arreglo, objetivo) {
- for (let i = 0; i < arreglo.length; i++) {
- if (arreglo[i] === objetivo) {
- return i; // Encontrado
+ return `public static int busquedaLineal(int[] arreglo, int objetivo) {
+ for (int i = 0; i < arreglo.length; i++) {
+ if (arreglo[i] == objetivo) {
+ return i;
+ }
}
- }
- return -1; // No encontrado
+ return -1;
}`;
};
diff --git a/src/animations/queue/algorithms/QueueAlgorithm.jsx b/src/animations/queue/algorithms/QueueAlgorithm.jsx
index 7f0e3e0..7ad10ba 100644
--- a/src/animations/queue/algorithms/QueueAlgorithm.jsx
+++ b/src/animations/queue/algorithms/QueueAlgorithm.jsx
@@ -352,13 +352,17 @@ export const Extraer = (cabeza, posicion) => {
export const EnqueueCode = ({ codeRef }) => {
return (
- {`nuevoNodo = new Nodo(valor)`}
- {`if (head === null) head = nuevoNodo`}
- {`else {`}
- {` actual = head`}
- {` while (actual.getNext() !== null)`}
- {` actual = actual.getNext()`}
- {` actual.setNext(nuevoNodo)`}
+ {`public void enqueue(int valor) {`}
+ {` Node nuevoNodo = new Node(valor);`}
+ {` if (head == null) {`}
+ {` head = nuevoNodo;`}
+ {` return;`}
+ {` }`}
+ {` Node actual = head;`}
+ {` while (actual.getNext() != null) {`}
+ {` actual = actual.getNext();`}
+ {` }`}
+ {` actual.setNext(nuevoNodo);`}
{`}`}
);
@@ -367,9 +371,12 @@ export const EnqueueCode = ({ codeRef }) => {
export const DequeueCode = ({ codeRef }) => {
return (
- {`if (head === null) return error`}
- {`valor = head.getValue()`}
- {`head = head.getNext()`}
+ {`public int dequeue() {`}
+ {` if (head == null) return -1;`}
+ {` int valor = head.getValue();`}
+ {` head = head.getNext();`}
+ {` return valor;`}
+ {`}`}
);
};
@@ -377,12 +384,18 @@ export const DequeueCode = ({ codeRef }) => {
export const EliminarFinalCode = ({ codeRef }) => {
return (
- {`if (head === null) return error`}
- {`if (head.getNext() === null) { head = null; return }`}
- {`actual = head`}
- {`while (actual.getNext().getNext() !== null)`}
- {` actual = actual.getNext()`}
- {`actual.setNext(null)`}
+ {`public void eliminarFinal() {`}
+ {` if (head == null) return;`}
+ {` if (head.getNext() == null) {`}
+ {` head = null;`}
+ {` return;`}
+ {` }`}
+ {` Node actual = head;`}
+ {` while (actual.getNext().getNext() != null) {`}
+ {` actual = actual.getNext();`}
+ {` }`}
+ {` actual.setNext(null);`}
+ {`}`}
);
};
@@ -390,15 +403,16 @@ export const EliminarFinalCode = ({ codeRef }) => {
export const InsertarCode = ({ codeRef }) => {
return (
- {`nuevoNodo = new Nodo(valor)`}
- {`if (head === null && posicion === 0) head = nuevoNodo`}
- {`else if (posicion === 0) { nuevoNodo.setNext(head); head = nuevoNodo }`}
- {`else {`}
- {` actual = head`}
- {` for (i = 0; i < posicion - 1; i++)`}
- {` actual = actual.getNext()`}
- {` nuevoNodo.setNext(actual.getNext())`}
- {` actual.setNext(nuevoNodo)`}
+ {`public void insertar(int valor, int posicion) {`}
+ {` Node nuevoNodo = new Node(valor);`}
+ {` if (head == null && posicion == 0) {`}
+ {` head = nuevoNodo;`}
+ {` return;`}
+ {` }`}
+ {` if (posicion == 0) {`}
+ {` nuevoNodo.setNext(head);`}
+ {` head = nuevoNodo;`}
+ {` }`}
{`}`}
);
@@ -407,15 +421,20 @@ export const InsertarCode = ({ codeRef }) => {
export const ExtraerCode = ({ codeRef }) => {
return (
- {`if (head === null) return error`}
- {`if (posicion >= tamano) return error`}
- {`if (posicion === 0) head = head.getNext()`}
- {`else {`}
- {` actual = head`}
- {` for (i = 0; i < posicion - 1; i++)`}
- {` actual = actual.getNext()`}
- {` eliminado = actual.getNext()`}
- {` actual.setNext(actual.getNext().getNext())`}
+ {`public int extraer(int posicion) {`}
+ {` if (head == null) return -1;`}
+ {` if (posicion == 0) {`}
+ {` int valor = head.getValue();`}
+ {` head = head.getNext();`}
+ {` return valor;`}
+ {` }`}
+ {` Node actual = head;`}
+ {` for (int i = 0; i < posicion - 1; i++) {`}
+ {` actual = actual.getNext();`}
+ {` }`}
+ {` int valor = actual.getNext().getValue();`}
+ {` actual.setNext(actual.getNext().getNext());`}
+ {` return valor;`}
{`}`}
);
diff --git a/src/animations/simpleList/algorithms/SimpleListAlgorithm.jsx b/src/animations/simpleList/algorithms/SimpleListAlgorithm.jsx
index 871bd34..c135df6 100644
--- a/src/animations/simpleList/algorithms/SimpleListAlgorithm.jsx
+++ b/src/animations/simpleList/algorithms/SimpleListAlgorithm.jsx
@@ -1129,9 +1129,9 @@ export const DrawLinkedList = ({ record, cabeza }) => {
// Funciones de visualización de código
export const InsertarAlInicioCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'nuevoNodo = new Nodo(valor)',
- 'nuevoNodo.setNext(cabeza)',
- 'cabeza = nuevoNodo',
+ 'Node nuevoNodo = new Node(valor);',
+ 'nuevoNodo.setNext(cabeza);',
+ 'cabeza = nuevoNodo;',
];
return (
@@ -1152,13 +1152,12 @@ export const InsertarAlInicioCode = ({ codeRef, highlightedLines = [] }) => {
export const InsertarAlFinalCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'nuevoNodo = new Nodo(valor)',
- 'if (cabeza === null) cabeza = nuevoNodo',
- 'else {',
- ' actual = cabeza',
- ' while (actual.getNext() !== null)',
- ' actual = actual.getNext()',
- ' actual.setNext(nuevoNodo)',
+ 'Node nuevoNodo = new Node(valor);',
+ 'if (cabeza == null) { cabeza = nuevoNodo; return; }',
+ 'Node actual = cabeza;',
+ 'while (actual.getNext() != null)',
+ ' actual = actual.getNext();',
+ 'actual.setNext(nuevoNodo);',
];
return (
@@ -1179,14 +1178,16 @@ export const InsertarAlFinalCode = ({ codeRef, highlightedLines = [] }) => {
export const InsertarEnPosicionCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'if (posicion === 0) return insertarAlInicio(valor)',
- 'nuevoNodo = new Nodo(valor)',
- 'if (cabeza === null) return error',
- 'actual = cabeza',
- 'for (i = 0; i < posicion - 1; i++)',
- ' if (actual === null) return error',
- 'nuevoNodo.setNext(actual.getNext())',
- 'actual.setNext(nuevoNodo)',
+ 'if (posicion == 0) return insertarAlInicio(valor);',
+ 'Node nuevoNodo = new Node(valor);',
+ 'if (cabeza == null) return;',
+ 'Node actual = cabeza;',
+ 'for (int i = 0; i < posicion - 1; i++) {',
+ ' if (actual == null) return;',
+ ' actual = actual.getNext();',
+ '}',
+ 'nuevoNodo.setNext(actual.getNext());',
+ 'actual.setNext(nuevoNodo);',
];
return (
@@ -1207,9 +1208,10 @@ export const InsertarEnPosicionCode = ({ codeRef, highlightedLines = [] }) => {
export const EliminarDelInicioCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'if (cabeza === null) return error',
- 'valorEliminado = cabeza.getValue()',
- 'cabeza = cabeza.getNext()',
+ 'if (cabeza == null) return -1;',
+ 'int valor = cabeza.getValue();',
+ 'cabeza = cabeza.getNext();',
+ 'return valor;',
];
return (
@@ -1230,15 +1232,18 @@ export const EliminarDelInicioCode = ({ codeRef, highlightedLines = [] }) => {
export const EliminarDelFinalCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'if (cabeza === null) return error',
- 'if (cabeza.getNext() === null) {',
- ' valorEliminado = cabeza.getValue()',
- ' cabeza = null',
- '} else {',
- ' while (actual.getNext().getNext() !== null)',
- ' actual = actual.getNext()',
- ' valorEliminado = actual.getNext().getValue()',
- ' actual.setNext(null)',
+ 'if (cabeza == null) return -1;',
+ 'if (cabeza.getNext() == null) {',
+ ' int valor = cabeza.getValue();',
+ ' cabeza = null;',
+ ' return valor;',
+ '}',
+ 'Node actual = cabeza;',
+ 'while (actual.getNext().getNext() != null)',
+ ' actual = actual.getNext();',
+ 'int valor = actual.getNext().getValue();',
+ 'actual.setNext(null);',
+ 'return valor;',
];
return (
@@ -1259,13 +1264,14 @@ export const EliminarDelFinalCode = ({ codeRef, highlightedLines = [] }) => {
export const EliminarEnPosicionCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'if (posicion === 0) return eliminarDelInicio()',
- 'actual = cabeza',
- 'for (i = 0; i < posicion - 1; i++)',
- ' actual = actual.getNext()',
- 'if (actual === null || actual.getNext() === null) return error',
- 'valorEliminado = actual.getNext().getValue()',
- 'actual.setNext(actual.getNext().getNext())',
+ 'if (posicion == 0) return eliminarDelInicio();',
+ 'Node actual = cabeza;',
+ 'for (int i = 0; i < posicion - 1; i++)',
+ ' actual = actual.getNext();',
+ 'if (actual == null || actual.getNext() == null) return -1;',
+ 'int valor = actual.getNext().getValue();',
+ 'actual.setNext(actual.getNext().getNext());',
+ 'return valor;',
];
return (
@@ -1286,16 +1292,16 @@ export const EliminarEnPosicionCode = ({ codeRef, highlightedLines = [] }) => {
export const BuscarCode = ({ codeRef, highlightedLines = [] }) => {
const lines = [
- 'if (cabeza === null) return not found',
- 'actual = cabeza',
- 'posicion = 0',
- 'while (actual !== null) {',
- ' if (actual.getValue() === valor)',
- ' return found at posicion',
- ' actual = actual.getNext()',
- ' posicion++',
+ 'if (cabeza == null) return -1;',
+ 'Node actual = cabeza;',
+ 'int posicion = 0;',
+ 'while (actual != null) {',
+ ' if (actual.getValue() == valor)',
+ ' return posicion;',
+ ' actual = actual.getNext();',
+ ' posicion++;',
'}',
- 'return not found',
+ 'return -1;',
];
return (
diff --git a/src/animations/sorting/algorithms/BubbleSort.jsx b/src/animations/sorting/algorithms/BubbleSort.jsx
index 5e16f08..30f1e2a 100644
--- a/src/animations/sorting/algorithms/BubbleSort.jsx
+++ b/src/animations/sorting/algorithms/BubbleSort.jsx
@@ -59,13 +59,18 @@ export const BubbleSortLegend = createKey('Comparing', 'Swapping');
export const BubbleSortCode = ({ codeRef }) => {
return (
- {`do`}
- {` swapped = falso
- for i = 1 to lastUnsortedIndex - 1`}
- {` if left > right`}
- {` swap(left, right)
- swapped = true; swapCounter++`}
- {`while swapped`}
+ {`public static void bubbleSort(int[] arr) {`}
+ {` int n = arr.length;`}
+ {` for (int i = 0; i < n - 1; i++) {`}
+ {` for (int j = 0; j < n - i - 1; j++) {`}
+ {` if (arr[j] > arr[j + 1]) {`}
+ {` int temp = arr[j];`}
+ {` arr[j] = arr[j + 1];`}
+ {` arr[j + 1] = temp;`}
+ {` }`}
+ {` }`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/sorting/algorithms/HeapSort.jsx b/src/animations/sorting/algorithms/HeapSort.jsx
index 6ac1b8c..4067dbb 100644
--- a/src/animations/sorting/algorithms/HeapSort.jsx
+++ b/src/animations/sorting/algorithms/HeapSort.jsx
@@ -231,12 +231,17 @@ export const HeapSortLegend = createKey('Comparing', 'Swapping', null, 'Heap');
export const HeapSortCode = ({ codeRef }) => {
return (
- {`BuildMaxHeap(arr)`}
- {`n = length(arr)`}
- {`// Extract elements from the heap one by one
-for i from n - 1 down to 1:`}
- {` Swap(arr[0], arr[i])`}
- {` MaxHeapify(arr, 0, i)`}
+ {`public static void heapSort(int[] arr) {`}
+ {` int n = arr.length;`}
+ {` for (int i = n / 2 - 1; i >= 0; i--)`}
+ {` maxHeapify(arr, n, i);`}
+ {` for (int i = n - 1; i > 0; i--) {`}
+ {` int temp = arr[0];`}
+ {` arr[0] = arr[i];`}
+ {` arr[i] = temp;`}
+ {` maxHeapify(arr, i, 0);`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/sorting/algorithms/InsertionSort.jsx b/src/animations/sorting/algorithms/InsertionSort.jsx
index 2b41752..a8dbf7c 100644
--- a/src/animations/sorting/algorithms/InsertionSort.jsx
+++ b/src/animations/sorting/algorithms/InsertionSort.jsx
@@ -55,14 +55,17 @@ export const InsertionSortLegend = createKey(
export const InsertionSortCode = ({ codeRef }) => {
return (
- {`mark first element as sorted`}
- {`for each unsorted element X
- 'extract' the element X`}
- {` for j = lastSortedIndex down to 0`}
- {` if current element j > X
- move sorted element
- to the right by 1`}
- {` break loop and insert X here`}
+ {`public static void insertionSort(int[] arr) {`}
+ {` for (int i = 1; i < arr.length; i++) {`}
+ {` int key = arr[i];`}
+ {` int j = i - 1;`}
+ {` while (j >= 0 && arr[j] > key) {`}
+ {` arr[j + 1] = arr[j];`}
+ {` j--;`}
+ {` }`}
+ {` arr[j + 1] = key;`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/sorting/algorithms/QuickSort.jsx b/src/animations/sorting/algorithms/QuickSort.jsx
index aba8199..fd289fa 100644
--- a/src/animations/sorting/algorithms/QuickSort.jsx
+++ b/src/animations/sorting/algorithms/QuickSort.jsx
@@ -117,13 +117,13 @@ export const QuickSortLegend = createKey(
export const QuickSortCode = ({ codeRef }) => {
return (
- {`for each (unsorted) partition
-set random element as pivot
- storeIndex = pivotIndex+1`}
- {` for i = pivotIndex+1 to rightmostIndex
- if (a[i] < a[pivot])`}
- {` swap(i, storeIndex); storeIndex++`}
- {` swap(pivot, storeIndex-1)`}
+ {`public static void quickSort(int[] arr, int low, int high) {`}
+ {` if (low < high) {`}
+ {` int pi = partition(arr, low, high);`}
+ {` quickSort(arr, low, pi - 1);`}
+ {` quickSort(arr, pi + 1, high);`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/sorting/algorithms/SelectionSort.jsx b/src/animations/sorting/algorithms/SelectionSort.jsx
index 67ca5be..c545955 100644
--- a/src/animations/sorting/algorithms/SelectionSort.jsx
+++ b/src/animations/sorting/algorithms/SelectionSort.jsx
@@ -108,12 +108,18 @@ export const SelectionSortLegend = createKey('Comparing', 'Swapping');
export const SelectionSortCode = ({ codeRef }) => {
return (
- {`repeat (numOfElements - 1) times
- set the first unsorted element as the minimum
- for each of the unsorted elements`}
- {` if element < currentMinimum`}
- {` set element as new minimum`}
- {` swap minimum with first unsorted position`}
+ {`public static void selectionSort(int[] arr) {`}
+ {` int n = arr.length;`}
+ {` for (int i = 0; i < n - 1; i++) {`}
+ {` int minIdx = i;`}
+ {` for (int j = i + 1; j < n; j++) {`}
+ {` if (arr[j] < arr[minIdx]) minIdx = j;`}
+ {` }`}
+ {` int temp = arr[i];`}
+ {` arr[i] = arr[minIdx];`}
+ {` arr[minIdx] = temp;`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/sorting/algorithms/ShellSort.jsx b/src/animations/sorting/algorithms/ShellSort.jsx
index fe69e0e..6447898 100644
--- a/src/animations/sorting/algorithms/ShellSort.jsx
+++ b/src/animations/sorting/algorithms/ShellSort.jsx
@@ -58,16 +58,20 @@ export const ShellSortLegend = createKey('Comparing', 'Swapping');
export const ShellSortCode = ({ codeRef }) => {
return (
- {`for (i = num/2; i>0; i = i/2) {
- for (j = i; j < num; j++) {
- for(k = j-i; k >= 0; k = k-i) {`}
- {` if (arr[k+i] < arr[k])`}
- {` tmp = arr[k];
- arr[k] = arr[k+i];
- arr[k+i] = tmp;`}
- {` else {
- break;
- }`}
+ {`public static void shellSort(int[] arr) {`}
+ {` int n = arr.length;`}
+ {` for (int gap = n / 2; gap > 0; gap /= 2) {`}
+ {` for (int i = gap; i < n; i++) {`}
+ {` int temp = arr[i];`}
+ {` int j = i;`}
+ {` while (j >= gap && arr[j - gap] > temp) {`}
+ {` arr[j] = arr[j - gap];`}
+ {` j -= gap;`}
+ {` }`}
+ {` arr[j] = temp;`}
+ {` }`}
+ {` }`}
+ {`}`}
);
};
diff --git a/src/animations/stack/algorithms/StackAlgorithm.jsx b/src/animations/stack/algorithms/StackAlgorithm.jsx
index 80787f0..711c91a 100644
--- a/src/animations/stack/algorithms/StackAlgorithm.jsx
+++ b/src/animations/stack/algorithms/StackAlgorithm.jsx
@@ -353,10 +353,15 @@ export const Extraer = (cabeza, posicion) => {
export const PushCode = ({ codeRef }) => {
return (
- {` nuevoNodo = new Nodo(valor)`}
- {`if (cabeza === null) cabeza = nuevoNodo`}
- {`nuevoNodo.setNext(cabeza)`}
- {`cabeza = nuevoNodo`}
+ {`public void push(int valor) {`}
+ {` Node nuevoNodo = new Node(valor);`}
+ {` if (cabeza == null) {`}
+ {` cabeza = nuevoNodo;`}
+ {` } else {`}
+ {` nuevoNodo.setNext(cabeza);`}
+ {` cabeza = nuevoNodo;`}
+ {` }`}
+ {`}`}
);
};
@@ -364,9 +369,12 @@ export const PushCode = ({ codeRef }) => {
export const PopCode = ({ codeRef }) => {
return (
- {`if (cabeza === null) return error`}
- {`valorEliminado = cabeza.getValue()`}
- {`cabeza = cabeza.getNext()`}
+ {`public int pop() {`}
+ {` if (cabeza == null) return -1;`}
+ {` int valor = cabeza.getValue();`}
+ {` cabeza = cabeza.getNext();`}
+ {` return valor;`}
+ {`}`}
);
};
@@ -374,11 +382,16 @@ export const PopCode = ({ codeRef }) => {
export const SumergirCode = ({ codeRef }) => {
return (
- {`if (cabeza === null || cabeza.getNext() === null) return`}
- {`nodoTope = cabeza`}
- {`cabeza = cabeza.getNext()`}
- {`while (nodoActual.getNext() !== null) nodoActual = nodoActual.getNext()`}
- {`nodoTope.setNext(null); nodoActual.setNext(nodoTope)`}
+ {`public void sumergir() {`}
+ {` if (cabeza == null || cabeza.getNext() == null) return;`}
+ {` Node nodo = cabeza;`}
+ {` cabeza = cabeza.getNext();`}
+ {` Node siguiente = cabeza.getNext();`}
+ {` while (siguiente != null) {`}
+ {` siguiente = siguiente.getNext();`}
+ {` }`}
+ {` siguiente.setNext(nodo);`}
+ {`}`}
);
};
@@ -386,12 +399,19 @@ export const SumergirCode = ({ codeRef }) => {
export const InsertarCode = ({ codeRef }) => {
return (
- {`nuevoNodo = new Nodo(valor)`}
- {`if (cabeza === null && posicion === 0) cabeza = nuevoNodo`}
- {`while (temp !== null) { tamanoPila++; temp = temp.getNext() }`}
- {`nuevoNodo.setNext(cabeza); cabeza = nuevoNodo`}
- {`ultimo.setNext(nuevoNodo)`}
- {`indiceDesdeTop = tamanoPila - posicion - 1`}
+ {`public void insertar(int valor, int posicion) {`}
+ {` Node nuevoNodo = new Node(valor);`}
+ {` if (cabeza == null && posicion == 0) {`}
+ {` cabeza = nuevoNodo;`}
+ {` return;`}
+ {` }`}
+ {` int tamanoPila = 0;`}
+ {` Node temp = cabeza;`}
+ {` while (temp != null) {`}
+ {` tamanoPila++;`}
+ {` temp = temp.getNext();`}
+ {` }`}
+ {`}`}
);
};
@@ -399,14 +419,16 @@ export const InsertarCode = ({ codeRef }) => {
export const ExtraerCode = ({ codeRef }) => {
return (
- {`if (cabeza === null) return error`}
- {`while (temp !== null) { tamanoPila++; temp = temp.getNext() }`}
- {`if (posicion >= tamanoPila) return error`}
- {`valorEliminado = cabeza.getValue(); cabeza = cabeza.getNext()`}
- {`penúltimo.setNext(null)`}
- {`indiceDesdeTop = tamanoPila - posicion - 1`}
- {`valorEliminado = nodoActual.getNext().getValue()`}
- {`nodoActual.setNext(nodoActual.getNext().getNext())`}
+ {`public int extraer(int posicion) {`}
+ {` if (cabeza == null) return -1;`}
+ {` int tamanoPila = 0;`}
+ {` Node temp = cabeza;`}
+ {` while (temp != null) {`}
+ {` tamanoPila++;`}
+ {` temp = temp.getNext();`}
+ {` }`}
+ {` if (posicion >= tamanoPila) return -1;`}
+ {`}`}
);
};
diff --git a/src/hooks/usePageTracking.js b/src/hooks/usePageTracking.js
new file mode 100644
index 0000000..06cba5a
--- /dev/null
+++ b/src/hooks/usePageTracking.js
@@ -0,0 +1,12 @@
+import { useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+import { trackPageView } from '../utils/googleAnalytics';
+
+export const usePageTracking = () => {
+ const location = useLocation();
+
+ useEffect(() => {
+ // Rastrear cada vez que cambia la ruta
+ trackPageView(location.pathname);
+ }, [location.pathname]);
+};
diff --git a/src/main.jsx b/src/main.jsx
index ca820a1..39ba509 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -6,6 +6,7 @@ import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './i18n/en.json';
import es from './i18n/es.json';
+import { initializeGoogleAnalytics } from './utils/googleAnalytics';
export const defaultLanguage = 'es';
const storedLanguage = localStorage.getItem('lang') || defaultLanguage;
@@ -25,6 +26,9 @@ i18next.use(initReactI18next).init({
},
});
+// Inicializar Google Analytics
+initializeGoogleAnalytics('G-6G1Z8N0TNS');
+
ReactDOM.createRoot(document.getElementById('root')).render(
//
diff --git a/src/utils/googleAnalytics.js b/src/utils/googleAnalytics.js
new file mode 100644
index 0000000..57d3770
--- /dev/null
+++ b/src/utils/googleAnalytics.js
@@ -0,0 +1,42 @@
+// Inicializar Google Analytics
+export const initializeGoogleAnalytics = measurementId => {
+ // Crear el script de Google Analytics
+ const script = document.createElement('script');
+ script.async = true;
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
+
+ // Agregar el script al head del documento
+ document.head.appendChild(script);
+
+ // Inicializar dataLayer de Google Analytics
+ window.dataLayer = window.dataLayer || [];
+
+ function gtag() {
+ window.dataLayer.push(arguments);
+ }
+
+ gtag('js', new Date());
+ gtag('config', measurementId, {
+ page_path: window.location.pathname,
+ });
+
+ // Guardar gtag en window para usarla en toda la app
+ window.gtag = gtag;
+};
+
+// Función para rastrear eventos personalizados
+export const trackEvent = (eventName, eventParams = {}) => {
+ if (window.gtag) {
+ window.gtag('event', eventName, eventParams);
+ }
+};
+
+// Función para rastrear cambios de página
+export const trackPageView = pageName => {
+ if (window.gtag) {
+ window.gtag('config', '', {
+ page_path: pageName,
+ page_title: pageName,
+ });
+ }
+};
diff --git a/src/utils/trackingEvents.js b/src/utils/trackingEvents.js
new file mode 100644
index 0000000..97b17e6
--- /dev/null
+++ b/src/utils/trackingEvents.js
@@ -0,0 +1,43 @@
+// Ejemplo de cómo rastrear eventos personalizados en tu proyecto
+import { trackEvent } from './googleAnalytics';
+
+// Ejemplo 1: Rastrear cuando un usuario hace clic en un botón de quiz
+export const handleQuizClick = quizName => {
+ trackEvent('quiz_started', {
+ quiz_name: quizName,
+ timestamp: new Date().toISOString(),
+ });
+};
+
+// Ejemplo 2: Rastrear cuando un usuario completa un quiz
+export const handleQuizComplete = (quizName, score) => {
+ trackEvent('quiz_completed', {
+ quiz_name: quizName,
+ score: score,
+ timestamp: new Date().toISOString(),
+ });
+};
+
+// Ejemplo 3: Rastrear cuando un usuario ve una animación
+export const handleAnimationView = animationName => {
+ trackEvent('animation_viewed', {
+ animation_name: animationName,
+ timestamp: new Date().toISOString(),
+ });
+};
+
+// Ejemplo 4: Rastrear búsquedas
+export const handleSearch = searchQuery => {
+ trackEvent('search_performed', {
+ search_query: searchQuery,
+ timestamp: new Date().toISOString(),
+ });
+};
+
+// Ejemplo 5: Rastrear descargas o exports
+export const handleExport = fileType => {
+ trackEvent('content_exported', {
+ file_type: fileType,
+ timestamp: new Date().toISOString(),
+ });
+};
From 3f2c4aa8ded7b8646f5dce0c1f7003715ae533af Mon Sep 17 00:00:00 2001
From: FrankDev
Date: Wed, 4 Mar 2026 19:25:17 -0500
Subject: [PATCH 15/18] =?UTF-8?q?fix:=20Corregir=20rastreo=20de=20p=C3=A1g?=
=?UTF-8?q?inas=20en=20Google=20Analytics?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/utils/googleAnalytics.js | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/utils/googleAnalytics.js b/src/utils/googleAnalytics.js
index 57d3770..b3ab265 100644
--- a/src/utils/googleAnalytics.js
+++ b/src/utils/googleAnalytics.js
@@ -1,5 +1,11 @@
+// Variable global para almacenar el measurement ID
+let MEASUREMENT_ID = '';
+
// Inicializar Google Analytics
export const initializeGoogleAnalytics = measurementId => {
+ // Guardar el measurement ID
+ MEASUREMENT_ID = measurementId;
+
// Crear el script de Google Analytics
const script = document.createElement('script');
script.async = true;
@@ -33,10 +39,10 @@ export const trackEvent = (eventName, eventParams = {}) => {
// Función para rastrear cambios de página
export const trackPageView = pageName => {
- if (window.gtag) {
- window.gtag('config', '', {
+ if (window.gtag && MEASUREMENT_ID) {
+ window.gtag('config', MEASUREMENT_ID, {
page_path: pageName,
- page_title: pageName,
+ page_title: document.title,
});
}
};
From 0f45bc718af21d8e91f4016f968310ac266fdff6 Mon Sep 17 00:00:00 2001
From: FrankDev
Date: Wed, 4 Mar 2026 20:09:05 -0500
Subject: [PATCH 16/18] =?UTF-8?q?fix:=20Corregir=20rastreo=20de=20p=C3=A1g?=
=?UTF-8?q?inas=20en=20SPA=20con=20evento=20page=5Fview=20de=20GA4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/hooks/usePageTracking.js | 10 +++++++---
src/utils/googleAnalytics.js | 10 ++++++----
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/src/hooks/usePageTracking.js b/src/hooks/usePageTracking.js
index 06cba5a..602a301 100644
--- a/src/hooks/usePageTracking.js
+++ b/src/hooks/usePageTracking.js
@@ -6,7 +6,11 @@ export const usePageTracking = () => {
const location = useLocation();
useEffect(() => {
- // Rastrear cada vez que cambia la ruta
- trackPageView(location.pathname);
- }, [location.pathname]);
+ // Pequeño delay para asegurar que gtag esté listo
+ const timeout = setTimeout(() => {
+ trackPageView(location.pathname + location.search);
+ }, 100);
+
+ return () => clearTimeout(timeout);
+ }, [location.pathname, location.search]);
};
diff --git a/src/utils/googleAnalytics.js b/src/utils/googleAnalytics.js
index b3ab265..a10d86c 100644
--- a/src/utils/googleAnalytics.js
+++ b/src/utils/googleAnalytics.js
@@ -22,8 +22,9 @@ export const initializeGoogleAnalytics = measurementId => {
}
gtag('js', new Date());
+ // Desactivar el page_view automático para manejarlo manualmente en la SPA
gtag('config', measurementId, {
- page_path: window.location.pathname,
+ send_page_view: false,
});
// Guardar gtag en window para usarla en toda la app
@@ -37,11 +38,12 @@ export const trackEvent = (eventName, eventParams = {}) => {
}
};
-// Función para rastrear cambios de página
+// Función para rastrear cambios de página (GA4 - evento page_view)
export const trackPageView = pageName => {
- if (window.gtag && MEASUREMENT_ID) {
- window.gtag('config', MEASUREMENT_ID, {
+ if (window.gtag) {
+ window.gtag('event', 'page_view', {
page_path: pageName,
+ page_location: window.location.origin + pageName,
page_title: document.title,
});
}
From 8f446188ff564b3dab120366b4ef3f2ab22f380c Mon Sep 17 00:00:00 2001
From: FrankDev
Date: Wed, 18 Mar 2026 21:22:33 -0500
Subject: [PATCH 17/18] firebase auth, firestore tracking y registro de botones
exitosos
---
.env.example | 11 +
.gitignore | 5 +
index.html | 1 +
package-lock.json | 1576 ++++++++++++++++-
package.json | 2 +
src/App.jsx | 5 +-
.../components/DoubleListControls.jsx | 26 +
.../components/LinearSearchControls.jsx | 7 +
.../queue/components/QueueControls.jsx | 6 +
.../components/SimpleListControls.jsx | 26 +
.../stack/components/StackControls.jsx | 6 +
src/animations/views/AnimationView.jsx | 2 +
src/components/Auth/LoginButton.jsx | 83 +
src/components/Auth/UserProfile.jsx | 115 ++
src/components/HomeAprende.jsx | 2 +
src/components/MobileMenu.jsx | 23 +-
src/components/NavBar.jsx | 19 +-
src/config/firebaseConfig.js | 42 +
src/hooks/useActivity.js | 47 +
src/hooks/useAuth.js | 53 +
src/hooks/usePageActivity.js | 47 +
src/hooks/useSessionClose.js | 55 +
src/quiz/pages/DataStructuresPage.jsx | 2 +
src/quiz/pages/SearchMethodsPage.jsx | 2 +
src/quiz/pages/SortingMethodsPage.jsx | 2 +
src/quiz/views/QuizView.jsx | 7 +
src/services/authService.js | 39 +
src/services/firestoreService.js | 138 ++
src/utils/clickRegistry.js | 21 +
29 files changed, 2304 insertions(+), 66 deletions(-)
create mode 100644 .env.example
create mode 100644 src/components/Auth/LoginButton.jsx
create mode 100644 src/components/Auth/UserProfile.jsx
create mode 100644 src/config/firebaseConfig.js
create mode 100644 src/hooks/useActivity.js
create mode 100644 src/hooks/useAuth.js
create mode 100644 src/hooks/usePageActivity.js
create mode 100644 src/hooks/useSessionClose.js
create mode 100644 src/services/authService.js
create mode 100644 src/services/firestoreService.js
create mode 100644 src/utils/clickRegistry.js
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..b6a17bb
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,11 @@
+# Firebase Configuration
+# Copy this file to .env and fill in your Firebase project credentials.
+# Get these values from: Firebase Console > Project Settings > General > Your apps > SDK setup
+
+VITE_FIREBASE_API_KEY=your_api_key_here
+VITE_FIREBASE_AUTH_DOMAIN=your_project_id.firebaseapp.com
+VITE_FIREBASE_PROJECT_ID=your_project_id
+VITE_FIREBASE_STORAGE_BUCKET=your_project_id.appspot.com
+VITE_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
+VITE_FIREBASE_APP_ID=your_app_id
+VITE_FIREBASE_MEASUREMENT_ID=G-XXXXXXXXXX
diff --git a/.gitignore b/.gitignore
index a547bf3..7ef0b74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,11 @@ dist
dist-ssr
*.local
+# Environment variables — never commit credentials
+.env
+.env.local
+.env.*.local
+
# Editor directories and files
.vscode/*
!.vscode/extensions.json
diff --git a/index.html b/index.html
index e739f7b..432743f 100644
--- a/index.html
+++ b/index.html
@@ -17,5 +17,6 @@
+