avances reunion
This commit is contained in:
parent
adc5c49f69
commit
c63a8cfa4a
|
@ -9,7 +9,11 @@
|
|||
<div class="container mt-5">
|
||||
<h1 class="text-success">Bienvenido Atleta 🏊♂️</h1>
|
||||
<p>Esta es tu zona para consultar tus rutinas y progreso.</p>
|
||||
<div id="contenedor-rutinas" class="row mt-4 justify-content-center"></div>
|
||||
<a href="index.html" class="btn btn-outline-success mt-3">Cerrar sesión</a>
|
||||
<div id="rutinas-list"></div>
|
||||
|
||||
</div>
|
||||
<script src="js/atleta.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -19,6 +19,16 @@ body {
|
|||
display: block;
|
||||
}
|
||||
|
||||
/* Multimedia */
|
||||
#waveform {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#audioPlayer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Contenedor de la piscina */
|
||||
#piscinaContainer {
|
||||
background-color: #e6f7ff;
|
||||
|
@ -130,6 +140,31 @@ body {
|
|||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#playPauseBtn {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
#playPauseBtn:hover {
|
||||
background-color: #084298;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.step.active {
|
||||
background-color: #0d6efd !important;
|
||||
color: white !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Responsivo */
|
||||
@media (max-width: 768px) {
|
||||
#piscina {
|
||||
|
|
|
@ -28,13 +28,13 @@
|
|||
|
||||
<script>
|
||||
async function loadRoutines() {
|
||||
const response = await fetch('/routines');
|
||||
const response = await fetch('/api/rutinas');
|
||||
const routines = await response.json();
|
||||
const routinesList = document.getElementById('routinesList');
|
||||
routinesList.innerHTML = '';
|
||||
|
||||
for (const routine of routines) {
|
||||
const formaciones = await fetch(`/routines/${routine._id}/formations`).then(res => res.json());
|
||||
const formaciones = await fetch(`/api/rutinas/${routine._id}/formations`).then(res => res.json());
|
||||
const ultimaFormacion = formaciones?.at(-1);
|
||||
|
||||
const atletas = ultimaFormacion?.atletas || [];
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="container d-flex justify-content-center align-items-center" style="min-height: 100vh;">
|
||||
<div class="card shadow p-4" style="width: 100%; max-width: 400px;">
|
||||
<h3 class="text-center mb-4">Iniciar Sesión</h3>
|
||||
<form action="/auth/login" method="POST">
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Correo electrónico</label>
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="Ingresar correo" required>
|
||||
|
@ -31,5 +31,6 @@
|
|||
|
||||
<!-- Bootstrap JS (opcional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/login.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const atletaId = sessionStorage.getItem("userId");
|
||||
|
||||
if (!atletaId) {
|
||||
document.getElementById("rutinas-list").innerHTML =
|
||||
"<p>No se encontró tu sesión. Inicia sesión nuevamente.</p>";
|
||||
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 = "<p>No tienes rutinas asignadas todavía.</p>";
|
||||
return;
|
||||
}
|
||||
rutinas.forEach(rutina => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "card my-3";
|
||||
card.innerHTML = `
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${rutina.title}</h5>
|
||||
<p class="card-text">${rutina.nombreCompetencia || "Sin descripción"}</p>
|
||||
<button class="btn btn-success" onclick="verRutina('${rutina._id}')">Ver rutina</button>
|
||||
</div>
|
||||
`;
|
||||
contenedor.appendChild(card);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
document.getElementById("rutinas-list").innerHTML =
|
||||
"<p>Error al cargar tus rutinas.</p>";
|
||||
});
|
||||
});
|
||||
|
||||
function verRutina(id) {
|
||||
window.location.href = `simulador.html?routineId=${id}`;
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ document.getElementById('rutinaForm').addEventListener('submit', async function
|
|||
formData.append('music', musicFile);
|
||||
|
||||
try {
|
||||
const uploadRes = await fetch('/routines/upload/music', {
|
||||
const uploadRes = await fetch('/api/rutinas/upload/music', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ document.getElementById('rutinaForm').addEventListener('submit', async function
|
|||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('/routines', {
|
||||
const res = await fetch('/api/rutinas', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(routine)
|
||||
|
|
|
@ -1,9 +1,29 @@
|
|||
document.getElementById('loginForm').addEventListener('submit', function (e) {
|
||||
document.getElementById('loginForm').addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
alert('Inicio de sesión simulado.');
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
const res = await fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
document.getElementById('registerForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
alert('Registro exitoso simulado.');
|
||||
if (!res.ok) {
|
||||
alert("Credenciales incorrectas.");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
sessionStorage.setItem("userId", data.userId);
|
||||
sessionStorage.setItem("role", data.role);
|
||||
|
||||
if (data.role === "coach") {
|
||||
window.location.href = "coach.html";
|
||||
} else if (data.role === "athlete") {
|
||||
window.location.href = "atleta.html";
|
||||
} else {
|
||||
window.location.href = "ventanaPrincipal.html";
|
||||
}
|
||||
});
|
|
@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
let modoDireccion = false;
|
||||
let indexAtletaDireccion = null;
|
||||
|
||||
const rutina = await fetch(`/routines/${rutinaId}`).then(res => res.json());
|
||||
const rutina = await fetch(`/api/rutinas/${rutinaId}`).then(res => res.json());
|
||||
const modalidad = rutina.modalidad;
|
||||
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
|
@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
tipoRutina.textContent = rutina.tipoCompetencia;
|
||||
modalidadRutina.textContent = modalidad;
|
||||
|
||||
const atletas = await fetch('/users/athletes').then(res => res.json());
|
||||
const atletas = await fetch('/api/users/athletes').then(res => res.json());
|
||||
atletas.forEach(a => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = a._id;
|
||||
|
@ -123,6 +123,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}
|
||||
|
||||
let formacionActual = [];
|
||||
let atletasKonva = {};
|
||||
let currentAtleta = null;
|
||||
const maxAtletas = modalidad === 'solo' ? 1 : modalidad === 'duo' ? 2 : 8;
|
||||
|
||||
|
@ -252,6 +253,12 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
});
|
||||
|
||||
layer.add(circle);
|
||||
atletasKonva[atleta.idPersonalizado] = {
|
||||
circle,
|
||||
text,
|
||||
figuraText,
|
||||
coordText
|
||||
};
|
||||
layer.add(text);
|
||||
layer.add(figuraText);
|
||||
layer.add(coordText);
|
||||
|
@ -338,6 +345,58 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function animarTransicion(nuevaFormacion) {
|
||||
nuevaFormacion.forEach(a => {
|
||||
const obj = atletasKonva[a.idPersonalizado];
|
||||
if (obj) {
|
||||
const { circle, text, figuraText, coordText } = obj;
|
||||
|
||||
// Animar círculo
|
||||
new Konva.Tween({
|
||||
node: circle,
|
||||
duration: 1.0,
|
||||
x: a.x,
|
||||
y: a.y,
|
||||
easing: Konva.Easings.EaseInOut,
|
||||
}).play();
|
||||
|
||||
// Mover textos en paralelo
|
||||
new Konva.Tween({
|
||||
node: text,
|
||||
duration: 1.0,
|
||||
x: a.x - 10,
|
||||
y: a.y - 7,
|
||||
easing: Konva.Easings.EaseInOut,
|
||||
}).play();
|
||||
|
||||
new Konva.Tween({
|
||||
node: figuraText,
|
||||
duration: 1.0,
|
||||
x: a.x - 25,
|
||||
y: a.y + 18,
|
||||
easing: Konva.Easings.EaseInOut,
|
||||
}).play();
|
||||
|
||||
new Konva.Tween({
|
||||
node: coordText,
|
||||
duration: 1.0,
|
||||
x: a.x - 35,
|
||||
y: a.y + 30,
|
||||
easing: Konva.Easings.EaseInOut,
|
||||
onUpdate: () => {
|
||||
const metros = convertirAMetros(a.x, a.y);
|
||||
coordText.text(`${metros.metrosX}m, ${metros.metrosY}m`);
|
||||
}
|
||||
}).play();
|
||||
|
||||
} else {
|
||||
dibujarAtleta(a);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
btnGuardarFormacion.addEventListener('click', async () => {
|
||||
const nombre = document.getElementById('nombreFormacion').value.trim();
|
||||
const notas = document.getElementById('notasFormacion').value.trim();
|
||||
|
@ -368,8 +427,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
const method = editIndex === null ? 'POST' : 'PUT';
|
||||
const endpoint = editIndex === null
|
||||
? `/routines/${rutinaId}/formations`
|
||||
: `/routines/${rutinaId}/formations/${editIndex}`;
|
||||
? `/api/rutinas/${rutinaId}/formations`
|
||||
: `/api/rutinas/${rutinaId}/formations/${editIndex}`;
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method,
|
||||
|
@ -393,7 +452,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
const nuevasFormaciones = nuevaLista.map(i => formaciones[i]);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/routines/${rutinaId}`, {
|
||||
const res = await fetch(`/api/rutinas/${rutinaId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ formaciones: nuevasFormaciones })
|
||||
|
@ -412,7 +471,7 @@ try {
|
|||
}
|
||||
});
|
||||
|
||||
const formaciones = await fetch(`/routines/${rutinaId}/formations`).then(res => res.json());
|
||||
const formaciones = await fetch(`/api/rutinas/${rutinaId}/formations`).then(res => res.json());
|
||||
|
||||
if (Array.isArray(formaciones)) {
|
||||
formaciones.forEach((f, i) => {
|
||||
|
@ -437,9 +496,10 @@ try {
|
|||
|
||||
formacionActual = seleccionada.atletas.map(a => ({ ...a }));
|
||||
|
||||
layer.destroyChildren();
|
||||
dibujarCuadricula(layer, stage.width(), stage.height());
|
||||
formacionActual.forEach(a => dibujarAtleta(a));
|
||||
const nuevaFormacion = seleccionada.atletas.map(a => ({ ...a }));
|
||||
animarTransicion(nuevaFormacion);
|
||||
formacionActual = nuevaFormacion;
|
||||
|
||||
|
||||
btnEditarFormacion.disabled = false;
|
||||
btnEditarFormacion.className = 'btn btn-outline-secondary btn-sm';
|
||||
|
@ -452,6 +512,19 @@ try {
|
|||
editIndex = null;
|
||||
});
|
||||
|
||||
const syncPuntos = [];
|
||||
let tiempoAcumulado = 0;
|
||||
|
||||
formaciones.forEach((f, i) => {
|
||||
syncPuntos.push({
|
||||
index: i,
|
||||
inicio: tiempoAcumulado,
|
||||
fin: tiempoAcumulado + (f.duracion || 0)
|
||||
});
|
||||
tiempoAcumulado += (f.duracion || 0);
|
||||
});
|
||||
|
||||
|
||||
btnEditarFormacion.addEventListener('click', () => {
|
||||
if (indexSeleccionado !== null) {
|
||||
editIndex = indexSeleccionado;
|
||||
|
@ -485,7 +558,7 @@ try {
|
|||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/routines/${rutinaId}/piscina`, {
|
||||
const res = await fetch(`/api/rutinas/${rutinaId}/piscina`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ width: newWidth, height: newHeight })
|
||||
|
@ -525,4 +598,61 @@ try {
|
|||
}
|
||||
});
|
||||
resizeObserver.observe(document.getElementById('piscina'));
|
||||
|
||||
// Crear WaveSurfer
|
||||
const wave = WaveSurfer.create({
|
||||
container: '#waveform',
|
||||
waveColor: '#A8DBA8',
|
||||
progressColor: '#3B8686',
|
||||
height: 100,
|
||||
responsive: true
|
||||
});
|
||||
|
||||
if (rutina.musicUrl) {
|
||||
wave.load(rutina.musicUrl);
|
||||
}
|
||||
|
||||
const playPauseBtn = document.getElementById('playPauseBtn');
|
||||
|
||||
playPauseBtn.addEventListener('click', () => {
|
||||
wave.playPause();
|
||||
});
|
||||
|
||||
wave.on('play', () => {
|
||||
playPauseBtn.textContent = '⏸︎ Pausar';
|
||||
});
|
||||
|
||||
wave.on('pause', () => {
|
||||
playPauseBtn.textContent = '▶ Reproducir';
|
||||
});
|
||||
|
||||
wave.on('finish', () => {
|
||||
playPauseBtn.textContent = '▶ Reproducir';
|
||||
});
|
||||
|
||||
wave.on('audioprocess', (currentTime) => {
|
||||
syncPuntos.forEach(punto => {
|
||||
const btn = document.querySelector(`.step[data-index="${punto.index}"]`);
|
||||
if (!btn) return;
|
||||
|
||||
if (currentTime >= punto.inicio && currentTime < punto.fin) {
|
||||
btn.classList.add('active');
|
||||
|
||||
if (indexSeleccionado !== punto.index) {
|
||||
indexSeleccionado = punto.index;
|
||||
const seleccionada = formaciones[punto.index];
|
||||
if (seleccionada) {
|
||||
formacionActual = seleccionada.atletas.map(a => ({ ...a }));
|
||||
const nuevaFormacion = seleccionada.atletas.map(a => ({ ...a }));
|
||||
animarTransicion(nuevaFormacion);
|
||||
formacionActual = nuevaFormacion;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const rutinaId = new URLSearchParams(window.location.search).get("routineId");
|
||||
if (!rutinaId) return alert("No se proporcionó ID de rutina.");
|
||||
|
||||
const tituloRutina = document.getElementById("tituloRutina");
|
||||
const tipoRutina = document.getElementById("tipoRutina");
|
||||
const modalidadRutina = document.getElementById("modalidadRutina");
|
||||
const audioPlayer = document.getElementById("audioPlayer");
|
||||
const timeline = document.getElementById("lineaTiempo");
|
||||
|
||||
const rutina = await fetch(`/api/rutinas/${rutinaId}`).then(res => res.json());
|
||||
const modalidad = rutina.modalidad;
|
||||
|
||||
tituloRutina.textContent = rutina.title || rutina.nombreCompetencia;
|
||||
tipoRutina.textContent = rutina.tipoCompetencia;
|
||||
modalidadRutina.textContent = modalidad;
|
||||
if (rutina.musicUrl) {
|
||||
audioPlayer.src = rutina.musicUrl;
|
||||
}
|
||||
|
||||
const piscinaWidth = rutina.piscina?.width || 850;
|
||||
const piscinaHeight = rutina.piscina?.height || 400;
|
||||
const escalaX = 25 / piscinaWidth;
|
||||
const escalaY = 20 / piscinaHeight;
|
||||
|
||||
function convertirAMetros(pxX, pxY) {
|
||||
return {
|
||||
metrosX: (pxX * escalaX).toFixed(2),
|
||||
metrosY: (pxY * escalaY).toFixed(2)
|
||||
};
|
||||
}
|
||||
|
||||
const stage = new Konva.Stage({
|
||||
container: "piscina",
|
||||
width: piscinaWidth,
|
||||
height: piscinaHeight
|
||||
});
|
||||
const layer = new Konva.Layer();
|
||||
stage.add(layer);
|
||||
|
||||
const divPiscina = document.getElementById("piscina");
|
||||
divPiscina.style.width = `${piscinaWidth}px`;
|
||||
divPiscina.style.height = `${piscinaHeight}px`;
|
||||
document.getElementById("piscinaContainer").style.width = `${piscinaWidth + 20}px`;
|
||||
|
||||
function dibujarCuadricula(layer, ancho, alto, tamano = 45) {
|
||||
for (let x = 0; x <= ancho; x += tamano) {
|
||||
layer.add(new Konva.Line({
|
||||
points: [x, 0, x, alto],
|
||||
stroke: '#cceeff',
|
||||
strokeWidth: 1
|
||||
}));
|
||||
}
|
||||
for (let y = 0; y <= alto; y += tamano) {
|
||||
layer.add(new Konva.Line({
|
||||
points: [0, y, ancho, y],
|
||||
stroke: '#cceeff',
|
||||
strokeWidth: 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
dibujarCuadricula(layer, piscinaWidth, piscinaHeight);
|
||||
|
||||
function dibujarAtleta(atleta) {
|
||||
const color = atleta.rol === 'volador' ? 'purple' : atleta.rol === 'pilar' ? 'blue' : 'red';
|
||||
const metros = convertirAMetros(atleta.x, atleta.y);
|
||||
|
||||
const circle = new Konva.Circle({
|
||||
x: atleta.x,
|
||||
y: atleta.y,
|
||||
radius: 15,
|
||||
fill: color,
|
||||
stroke: 'white',
|
||||
strokeWidth: 2
|
||||
});
|
||||
|
||||
const text = new Konva.Text({
|
||||
x: atleta.x - 10,
|
||||
y: atleta.y - 7,
|
||||
text: atleta.idPersonalizado,
|
||||
fontSize: 12,
|
||||
fill: 'white'
|
||||
});
|
||||
|
||||
const figuraText = new Konva.Text({
|
||||
x: atleta.x - 25,
|
||||
y: atleta.y + 18,
|
||||
text: atleta.figura || '',
|
||||
fontSize: 10,
|
||||
fill: '#333',
|
||||
fontStyle: 'italic'
|
||||
});
|
||||
|
||||
const coordText = new Konva.Text({
|
||||
x: atleta.x - 35,
|
||||
y: atleta.y + 30,
|
||||
text: `${metros.metrosX}m, ${metros.metrosY}m`,
|
||||
fontSize: 9,
|
||||
fill: '#666'
|
||||
});
|
||||
|
||||
layer.add(circle);
|
||||
layer.add(text);
|
||||
layer.add(figuraText);
|
||||
layer.add(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]
|
||||
});
|
||||
layer.add(dir);
|
||||
}
|
||||
|
||||
layer.draw();
|
||||
}
|
||||
|
||||
const formaciones = await fetch(`/api/rutinas/${rutinaId}/formations`).then(res => res.json());
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
timeline.addEventListener("click", (e) => {
|
||||
if (!e.target.classList.contains("step")) return;
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
|
@ -7,6 +7,7 @@
|
|||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/editorPiscina.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/wavesurfer.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/konva@9.2.0/konva.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
</head>
|
||||
|
@ -129,17 +130,19 @@
|
|||
<button id="btnGuardarFormacion" class="btn btn-primary btn-lg px-5">💾 Guardar Formación</button>
|
||||
</div>
|
||||
|
||||
<div id="waveform"></div>
|
||||
<button id="playPauseBtn">▶ Reproducir</button>
|
||||
|
||||
|
||||
<hr class="my-5" />
|
||||
|
||||
<!-- Reproductor de música -->
|
||||
<!-- Línea de música anterior, sin wave -->
|
||||
<section class="mb-4">
|
||||
<h5 class="fw-semibold">🎵 Música de la rutina</h5>
|
||||
<audio id="audioPlayer" class="w-100" controls>
|
||||
Tu navegador no soporta audio.
|
||||
</audio>
|
||||
</section>
|
||||
|
||||
<!-- Línea de tiempo -->
|
||||
<section>
|
||||
<h4 class="mb-3 fw-semibold">🕒 Línea de tiempo</h4>
|
||||
<div id="lineaTiempo" class="timeline-placeholder d-flex flex-wrap gap-2"></div>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Simulador de Rutina</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-4">
|
||||
<h2 id="tituloRutina" class="text-center text-primary mb-3">Cargando rutina...</h2>
|
||||
<p class="text-center"><span id="tipoRutina" class="badge bg-info"></span> | <span id="modalidadRutina" class="badge bg-secondary"></span></p>
|
||||
|
||||
<audio id="audioPlayer" controls class="d-block mx-auto mb-4"></audio>
|
||||
|
||||
<div id="piscinaContainer" class="mx-auto mb-4">
|
||||
<div id="piscina" class="border bg-white" style="margin: 0 auto;"></div>
|
||||
</div>
|
||||
|
||||
<h5 class="mt-3">Línea de Tiempo</h5>
|
||||
<div id="lineaTiempo" class="d-flex flex-wrap gap-2"></div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<a href="atleta.html" class="btn btn-outline-secondary">Volver al Panel</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.12/konva.min.js"></script>
|
||||
<script src="js/simulador.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -54,15 +54,11 @@ router.post('/login', async (req, res) => {
|
|||
const valid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!valid) return res.status(401).send('Contraseña incorrecta');
|
||||
|
||||
// Redirección por rol
|
||||
if (user.role === 'coach') {
|
||||
return res.redirect('/coach.html');
|
||||
} else if (user.role === 'athlete') {
|
||||
return res.redirect('/atleta.html');
|
||||
} else {
|
||||
return res.redirect('/ventanaPrincipal.html'); // fallback
|
||||
}
|
||||
|
||||
res.json({
|
||||
userId: user._id,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error en login:', error);
|
||||
res.status(500).send('Error interno del servidor');
|
||||
|
@ -70,4 +66,5 @@ router.post('/login', async (req, res) => {
|
|||
});
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -117,13 +117,24 @@ router.post('/:id/formations', async (req, res) => {
|
|||
if (!rutina) return res.status(404).json({ error: 'Rutina no encontrada' });
|
||||
|
||||
rutina.formaciones.push(req.body);
|
||||
|
||||
const nuevosAtletas = req.body.atletas.map(a => a.atletaId);
|
||||
|
||||
nuevosAtletas.forEach(idAtleta => {
|
||||
if (!rutina.participantes.includes(idAtleta.toString())) {
|
||||
rutina.participantes.push(idAtleta);
|
||||
}
|
||||
});
|
||||
|
||||
await rutina.save();
|
||||
res.status(200).json({ message: 'Formación agregada exitosamente' });
|
||||
} catch (error) {
|
||||
console.error('Error al guardar formación:', error);
|
||||
res.status(500).json({ error: 'Error al guardar formación', details: error });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// GET formaciones de rutina
|
||||
router.get('/:id/formations', async (req, res) => {
|
||||
try {
|
||||
|
@ -206,6 +217,19 @@ router.post('/upload/music', upload.single('music'), (req, res) => {
|
|||
res.status(200).json({ url: fileUrl });
|
||||
});
|
||||
|
||||
// GET rutinas asignadas a un atleta
|
||||
router.get('/atleta/:id', async (req, res) => {
|
||||
const atletaId = req.params.id;
|
||||
try {
|
||||
const rutinas = await Routine.find({ participantes: atletaId })
|
||||
.populate('participantes', 'name')
|
||||
.populate('formaciones.atletas.atletaId', 'name');
|
||||
res.json(rutinas);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ mensaje: "Error al obtener rutinas del atleta" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config();
|
||||
|
||||
const uri = process.env.MONGO_URI;
|
||||
const client = new MongoClient(uri);
|
||||
|
||||
|
@ -12,6 +14,7 @@ router.get('/athletes', async (req, res) => {
|
|||
.find({ role: 'athlete' })
|
||||
.project({ _id: 1, name: 1 })
|
||||
.toArray();
|
||||
|
||||
res.json(athletes);
|
||||
} catch (err) {
|
||||
console.error('Error al obtener atletas:', err);
|
||||
|
|
|
@ -15,8 +15,8 @@ const routineRoutes = require('./routes/routines');
|
|||
const userRoutes = require('./routes/users');
|
||||
|
||||
app.use('/auth', authRoutes);
|
||||
app.use('/routines', routineRoutes);
|
||||
app.use('/users', userRoutes);
|
||||
app.use('/api/rutinas', routineRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/catalog', express.static(path.join(__dirname, 'catalog')));
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue