diff --git a/public/js/atleta.js b/public/js/atleta.js
index d421c8f..2617e28 100644
--- a/public/js/atleta.js
+++ b/public/js/atleta.js
@@ -1,64 +1,65 @@
-document.addEventListener("DOMContentLoaded", () => {
+document.addEventListener("DOMContentLoaded", async () => {
const atletaId = sessionStorage.getItem("userId");
+ const contenedor = document.getElementById("rutinas-list");
if (!atletaId) {
- document.getElementById("rutinas-list").innerHTML =
- "
No se encontró tu sesión. Inicia sesión nuevamente.
";
+ contenedor.innerHTML = "No se encontró tu sesión. Inicia sesión nuevamente.
";
return;
}
- fetch(`/api/rutinas/atleta/${atletaId}`)
- .then(res => {
- if (!res.ok) {
- throw new Error("Error al obtener rutinas");
- }
- return res.json();
- })
- .then(rutinas => {
- const contenedor = document.getElementById("rutinas-list");
- if (rutinas.length === 0) {
- contenedor.innerHTML = "No tienes rutinas asignadas todavía.
";
- return;
- }
- rutinas.forEach(rutina => {
- const card = document.createElement("div");
- card.className = "card my-3";
- card.innerHTML = `
-
-
${rutina.title}
-
${rutina.nombreCompetencia || "Sin descripción"}
-
-
- `;
- contenedor.appendChild(card);
- });
- })
- .catch(err => {
- console.error(err);
- document.getElementById("rutinas-list").innerHTML =
- "Error al cargar tus rutinas.
";
+ try {
+ const res = await fetch(`/api/rutinas/atleta/${atletaId}/formaciones`);
+ if (!res.ok) throw new Error("Error al obtener formaciones del atleta");
+
+ const formaciones = await res.json();
+ if (formaciones.length === 0) {
+ contenedor.innerHTML = "No estás asignado a ninguna formación aún.
";
+ return;
+ }
+
+ formaciones.forEach(f => {
+ const card = document.createElement("div");
+ card.className = "card text-start my-3 shadow-sm";
+
+ card.innerHTML = `
+
+
${f.rutinaNombre || "Rutina sin nombre"}
+
Formación: ${f.nombreColoquial || "(sin nombre)"}
+
+ Duración: ${f.duracion || "?"}s
+ Notas: ${f.notasTacticas || "Sin notas"}
+ Rol: ${f.atleta.rol || "N/A"} |
+ ID: ${f.atleta.idPersonalizado || "N/A"} |
+ Figura: ${f.atleta.figura || "—"}
+
+
+
+ `;
+ contenedor.appendChild(card);
});
+ } catch (err) {
+ console.error("❌ Error al obtener formaciones:", err);
+ contenedor.innerHTML = "Error al cargar tus asignaciones.
";
+ }
});
-function verRutina(id) {
- window.location.href = `simulador.html?routineId=${id}`;
+function verSimulador(rutinaId, index) {
+ window.location.href = `simulador.html?routineId=${rutinaId}&formationIndex=${index}`;
}
function logout() {
- sessionStorage.clear();
+ sessionStorage.clear();
alert("Sesión cerrada");
- window.location.href = "../index.html";
+ window.location.href = "../index.html";
}
-// Mostrar nombre del usuario logueado en el header
-window.addEventListener('DOMContentLoaded', async () => {
+// Mostrar nombre de usuario
+window.addEventListener("DOMContentLoaded", async () => {
const userId = sessionStorage.getItem("userId");
- if (!userId) {
- alert("Sesión expirada, inicia sesión de nuevo.");
- window.location.href = "index.html";
- return;
- }
+ if (!userId) return;
try {
const res = await fetch(`/api/users/${userId}`);
@@ -66,9 +67,8 @@ window.addEventListener('DOMContentLoaded', async () => {
if (user?.name) {
document.getElementById("nombreUsuarioHeader").textContent = user.name;
- document.getElementById("nombreUsuarioDropdown").textContent = "Coach " + user.name;
}
} catch (err) {
- console.error("❌ Error al obtener datos del usuario:", err);
+ console.error("❌ Error al obtener usuario:", err);
}
-});
\ No newline at end of file
+});
diff --git a/public/js/piscina.js b/public/js/piscina.js
index 6adce4c..480e929 100644
--- a/public/js/piscina.js
+++ b/public/js/piscina.js
@@ -298,15 +298,32 @@ tipoPiscinaSelect.addEventListener('change', async () => {
circle.on('dblclick', () => {
const i = formacionActual.findIndex(a => a.idPersonalizado === atleta.idPersonalizado);
- const html = `
-
-
${atleta.figura || 'Sin figura'}
-
-
-
- `;
+const html = `
+
+
${atleta.figura || 'Sin figura'}
+
+
+
+
+`;
+
showPopover(circle.x(), circle.y(), html);
});
+window.eliminarDireccion = function(index) {
+ const atleta = formacionActual[index];
+ if (!atleta) return;
+
+ atleta.direccion = null;
+
+ const visual = atletasKonva[atleta.idPersonalizado];
+ if (visual?.flecha) {
+ visual.flecha.destroy();
+ visual.flecha = null;
+ layer.draw();
+ }
+
+ document.getElementById('popoverAtleta')?.remove();
+};
circle.on('dragend', () => {
const newX = circle.x();
@@ -644,6 +661,40 @@ formaciones.forEach((f, i) => {
alert('Selecciona una formación desde la línea de tiempo primero.');
}
});
+const btnEliminarFormacion = document.getElementById('btnEliminarFormacion');
+
+btnEliminarFormacion.addEventListener('click', async () => {
+ if (indexSeleccionado === null || !formaciones[indexSeleccionado]) {
+ return alert('Selecciona una formación primero');
+ }
+
+ const confirm = await Swal.fire({
+ title: '¿Eliminar formación?',
+ text: 'Esta acción no se puede deshacer',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonText: 'Sí, eliminar',
+ cancelButtonText: 'Cancelar'
+ });
+
+ if (!confirm.isConfirmed) return;
+
+ try {
+ const res = await fetch(`/api/rutinas/${rutinaId}/formations/${indexSeleccionado}`, {
+ method: 'DELETE'
+ });
+
+ if (res.ok) {
+ Swal.fire('Eliminada', 'La formación ha sido eliminada.', 'success');
+ window.location.reload();
+ } else {
+ Swal.fire('Error', 'No se pudo eliminar la formación.', 'error');
+ }
+ } catch (err) {
+ console.error(err);
+ Swal.fire('Error', 'Ocurrió un error de red.', 'error');
+ }
+});
diff --git a/public/js/simulador.js b/public/js/simulador.js
index e28d7ca..7ed49fe 100644
--- a/public/js/simulador.js
+++ b/public/js/simulador.js
@@ -1,11 +1,16 @@
document.addEventListener("DOMContentLoaded", async () => {
const rutinaId = new URLSearchParams(window.location.search).get("routineId");
- if (!rutinaId) return alert("No se proporcionó ID de rutina.");
+ const formationIndex = parseInt(new URLSearchParams(window.location.search).get("formationIndex"));
+
+ if (!rutinaId || isNaN(formationIndex)) {
+ return alert("No se proporcionó ID de rutina o índice de formación.");
+ }
const tituloRutina = document.getElementById("tituloRutina");
const tipoRutina = document.getElementById("tipoRutina");
const modalidadRutina = document.getElementById("modalidadRutina");
const audioPlayer = document.getElementById("audioPlayer");
+ const playBtn = document.getElementById("playPauseBtn");
const timeline = document.getElementById("lineaTiempo");
const rutina = await fetch(`/api/rutinas/${rutinaId}`).then(res => res.json());
@@ -14,8 +19,20 @@ document.addEventListener("DOMContentLoaded", async () => {
tituloRutina.textContent = rutina.title || rutina.nombreCompetencia;
tipoRutina.textContent = rutina.tipoCompetencia;
modalidadRutina.textContent = modalidad;
+
if (rutina.musicUrl) {
audioPlayer.src = rutina.musicUrl;
+ playBtn.onclick = () => {
+ if (audioPlayer.paused) {
+ audioPlayer.play();
+ playBtn.textContent = "⏸ Pausar";
+ } else {
+ audioPlayer.pause();
+ playBtn.textContent = "▶ Reproducir";
+ }
+ };
+ } else {
+ playBtn.style.display = "none";
}
const piscinaWidth = rutina.piscina?.width || 850;
@@ -41,6 +58,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const divPiscina = document.getElementById("piscina");
divPiscina.style.width = `${piscinaWidth}px`;
divPiscina.style.height = `${piscinaHeight}px`;
+ divPiscina.style.background = "#d4f0ff"; // Azul claro
document.getElementById("piscinaContainer").style.width = `${piscinaWidth + 20}px`;
function dibujarCuadricula(layer, ancho, alto, tamano = 45) {
@@ -79,38 +97,38 @@ document.addEventListener("DOMContentLoaded", async () => {
x: atleta.x - 10,
y: atleta.y - 7,
text: atleta.idPersonalizado,
- fontSize: 12,
+ fontSize: 14,
+ fontStyle: 'bold',
fill: 'white'
});
const figuraText = new Konva.Text({
- x: atleta.x - 25,
- y: atleta.y + 18,
+ x: atleta.x - 30,
+ y: atleta.y + 20,
text: atleta.figura || '',
- fontSize: 10,
+ fontSize: 11,
fill: '#333',
fontStyle: 'italic'
});
const coordText = new Konva.Text({
- x: atleta.x - 35,
- y: atleta.y + 30,
+ x: atleta.x - 38,
+ y: atleta.y + 34,
text: `${metros.metrosX}m, ${metros.metrosY}m`,
- fontSize: 9,
+ fontSize: 10,
fill: '#666'
});
- layer.add(circle);
- layer.add(text);
- layer.add(figuraText);
- layer.add(coordText);
+ layer.add(circle, text, figuraText, coordText);
if (atleta.direccion) {
const dir = new Konva.Line({
points: [atleta.direccion.x1, atleta.direccion.y1, atleta.direccion.x2, atleta.direccion.y2],
stroke: 'black',
strokeWidth: 2,
- dash: [4, 4]
+ dash: [4, 4],
+ pointerLength: 10,
+ pointerWidth: 10
});
layer.add(dir);
}
@@ -119,27 +137,21 @@ document.addEventListener("DOMContentLoaded", async () => {
}
const formaciones = await fetch(`/api/rutinas/${rutinaId}/formations`).then(res => res.json());
+ const formacion = formaciones[formationIndex];
- if (Array.isArray(formaciones)) {
- formaciones.forEach((f, i) => {
- const block = document.createElement("button");
- block.className = "btn btn-outline-primary btn-sm me-2 step";
- block.textContent = `${f.nombreColoquial || `Formación ${i + 1}`} (${f.duracion || '?'}s)`;
- block.dataset.index = i;
- block.title = f.notasTacticas || '';
- timeline.appendChild(block);
- });
+ if (!formacion) {
+ alert("La formación solicitada no existe.");
+ return;
}
- timeline.addEventListener("click", (e) => {
- if (!e.target.classList.contains("step")) return;
+ // Pintar solo esta formación
+ formacion.atletas.forEach(dibujarAtleta);
- const index = parseInt(e.target.dataset.index);
- const formacion = formaciones[index];
- if (!formacion) return;
-
- layer.destroyChildren();
- dibujarCuadricula(layer, stage.width(), stage.height());
- formacion.atletas.forEach(a => dibujarAtleta(a));
- });
+ // Mostrar título visual
+ const block = document.createElement("button");
+ block.className = "btn btn-outline-primary btn-sm me-2 step";
+ block.textContent = `${formacion.nombreColoquial || `Formación ${formationIndex + 1}`} (${formacion.duracion || '?'}s)`;
+ block.dataset.index = formationIndex;
+ block.title = formacion.notasTacticas || '';
+ timeline.appendChild(block);
});
diff --git a/public/piscina.html b/public/piscina.html
index 9a39aa3..8400270 100644
--- a/public/piscina.html
+++ b/public/piscina.html
@@ -81,39 +81,41 @@
+
+
+
Piscina
-
-
-
Piscina
+
+
+
+
-
-
-
-
+
-
+
+
+
+
-
+
+
-
-
-
+
+
+
+
+
Datos de Formación
diff --git a/public/simulador.html b/public/simulador.html
index 9357b4f..a17fd69 100644
--- a/public/simulador.html
+++ b/public/simulador.html
@@ -49,18 +49,24 @@
-
Cargando rutina...
-
|
+
Cargando rutina...
+
+ |
+
+
-
+
+
+
+
-
+
+
+
Línea de Tiempo
+
-
Línea de Tiempo
-
-
diff --git a/routes/routines.js b/routes/routines.js
index a2fff8c..2ecd796 100644
--- a/routes/routines.js
+++ b/routes/routines.js
@@ -258,11 +258,97 @@ router.get('/atleta/:id', async (req, res) => {
const rutinas = await Routine.find({ participantes: atletaId })
.populate('participantes', 'name')
.populate('formaciones.atletas.atletaId', 'name');
- res.json(rutinas);
+
+ // Filtrar solo rutinas donde realmente aparece en alguna formación
+ const filtradas = rutinas.filter(rutina =>
+ rutina.formaciones.some(f =>
+ f.atletas.some(a => a.atletaId?.toString() === atletaId)
+ )
+ );
+
+ res.json(filtradas);
} catch (err) {
console.error(err);
res.status(500).json({ mensaje: "Error al obtener rutinas del atleta" });
}
});
+// DELETE eliminar una formación específica por índice
+router.delete('/:id/formations/:index', async (req, res) => {
+ const { id, index } = req.params;
+
+ if (!mongoose.Types.ObjectId.isValid(id)) {
+ return res.status(400).json({ message: 'ID de rutina inválido' });
+ }
+
+ const i = parseInt(index);
+ if (isNaN(i) || i < 0) {
+ return res.status(400).json({ message: 'Índice de formación inválido' });
+ }
+
+ try {
+ const rutina = await Routine.findById(id);
+ if (!rutina) {
+ return res.status(404).json({ message: 'Rutina no encontrada' });
+ }
+
+ if (i >= rutina.formaciones.length) {
+ return res.status(400).json({ message: 'Índice fuera de rango' });
+ }
+
+ rutina.formaciones.splice(i, 1);
+ await rutina.save();
+
+ const nuevosParticipantes = new Set();
+ rutina.formaciones.forEach(f => {
+ f.atletas.forEach(a => {
+ if (a.atletaId) nuevosParticipantes.add(a.atletaId.toString());
+ });
+ });
+rutina.participantes = [...nuevosParticipantes];
+
+ res.status(200).json({ message: 'Formación eliminada exitosamente' });
+ } catch (error) {
+ console.error('❌ Error al eliminar formación:', error);
+ res.status(500).json({ message: 'Error del servidor', error });
+ }
+});
+router.get('/atleta/:id/formaciones', async (req, res) => {
+ const atletaId = req.params.id;
+ try {
+ const rutinas = await Routine.find({ participantes: atletaId });
+
+ const resultado = [];
+
+ for (const rutina of rutinas) {
+ const formacionesAtleta = rutina.formaciones
+ .map((f, i) => {
+ const match = f.atletas.find(a => a.atletaId?.toString() === atletaId);
+ return match
+ ? {
+ rutinaId: rutina._id,
+ rutinaNombre: rutina.title || rutina.nombreCompetencia,
+ index: i,
+ nombreColoquial: f.nombreColoquial,
+ notasTacticas: f.notasTacticas,
+ duracion: f.duracion,
+ atleta: match
+ }
+ : null;
+ })
+ .filter(Boolean);
+
+ if (formacionesAtleta.length > 0) {
+ resultado.push(...formacionesAtleta);
+ }
+ }
+
+ res.json(resultado);
+ } catch (err) {
+ console.error(err);
+ res.status(500).json({ mensaje: "Error al obtener formaciones del atleta" });
+ }
+});
+
+
module.exports = router;