document.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; } try { const res = await fetch(`/api/users/${userId}`); const user = await res.json(); if (user?.name) { document.getElementById("nombreUsuarioHeader").textContent = user.name; } let lang = 'es'; // valor por defecto // 1. Determinar idioma if (sessionStorage.getItem('langJustChanged') === 'true') { lang = sessionStorage.getItem('lang') || user.language || 'es'; } else if (sessionStorage.getItem('lang')) { lang = sessionStorage.getItem('lang'); } else if (user.language) { lang = user.language; sessionStorage.setItem('lang', lang); } window.tLang = lang; localStorage.setItem('lang', lang); // opcional si usas localStorage // 🌀 Recarga solo una vez con delay para evitar parpadeo inmediato if (!sessionStorage.getItem('langLoadedOnce')) { sessionStorage.setItem('langLoadedOnce', 'true'); setTimeout(() => location.reload(), 100); // return; } // ✅ Aplicar traducciones después de confirmar idioma const selector = document.getElementById("langSelector"); if (selector) selector.value = lang; aplicarTraducciones(); actualizarTextosDinamicos(); } catch (err) { console.error("❌ Error al obtener datos del usuario:", err); } const rutinaId = new URLSearchParams(window.location.search).get('routineId'); if (!rutinaId) return alert('No se proporcionó ID de rutina.'); const select = document.getElementById('selectAtleta'); const tituloRutina = document.getElementById('tituloRutina'); const tipoRutina = document.getElementById('tipoRutina'); const modalidadRutina = document.getElementById('modalidadRutina'); const inputFigura = document.getElementById('figura'); const btnEditarFormacion = document.getElementById('btnEditarFormacion'); const btnGuardarFormacion = document.getElementById('btnGuardarFormacion'); let editIndex = null; let indexSeleccionado = null; let modoDireccion = false; let indexAtletaDireccion = null; const rutina = await fetch(`/api/rutinas/${rutinaId}`).then(res => res.json()); const modalidad = rutina.modalidad; const audioPlayer = document.getElementById('audioPlayer'); if (rutina.musicUrl) { audioPlayer.src = rutina.musicUrl; } tituloRutina.textContent = rutina.title || rutina.nombreCompetencia; tipoRutina.textContent = rutina.tipoCompetencia; modalidadRutina.textContent = modalidad; const atletas = await fetch('/api/users/athletes').then(res => res.json()); atletas.forEach(a => { const opt = document.createElement('option'); opt.value = JSON.stringify({ id: a._id, nombre: a.name, idPers: a.idPersonalizado }); opt.textContent = `${a.name} (${a.idPersonalizado || 'sin ID'})`; select.appendChild(opt); }); let figuraActual = null; // 🔁 Guardar la figura seleccionada select.addEventListener('change', () => { const datos = JSON.parse(select.value || '{}'); select.dataset.id = datos.id; const idInput = document.getElementById('idPersonalizado'); if (datos.idPers) { idInput.value = datos.idPers; idInput.disabled = true; } else { idInput.value = ''; idInput.disabled = false; } }); let figurasFINA = []; try { const res = await fetch('/catalog/figurasFINA.json'); figurasFINA = await res.json(); const sugerenciasBox = document.getElementById('sugerenciasFigura'); inputFigura.addEventListener('input', () => { const val = inputFigura.value.trim().toLowerCase(); sugerenciasBox.innerHTML = ''; if (val.length < 1) return; const matches = figurasFINA.filter(f => f.nombre.toLowerCase().includes(val)); matches.forEach(figura => { const item = document.createElement('button'); item.type = 'button'; item.className = 'list-group-item list-group-item-action'; item.textContent = figura.nombre; item.onclick = () => { inputFigura.value = figura.nombre; inputFigura.title = `${figura.descripcion} (${figura.categoria})`; sugerenciasBox.innerHTML = ''; figuraActual = figura; // guardamos figura activa mostrarPreview(figura); const codigoInput = document.getElementById("codigoElemento"); if (codigoInput && figura.codigoElemento) { codigoInput.value = figura.codigoElemento; } }; sugerenciasBox.appendChild(item); }); }); inputFigura.addEventListener('blur', () => { setTimeout(() => sugerenciasBox.innerHTML = '', 200); }); function mostrarPreview(figura) { const nombreTrad = window.figurasTraducidas?.[figura.nombre]?.[window.tLang] || figura.nombre; const descripcionTrad = window.descripcionesTraducidas?.[figura.descripcion]?.[window.tLang] || figura.descripcion; // Traducción visual adicional debajo del input const nombreTraducidoVisual = document.getElementById("nombreFiguraTraducido"); if (nombreTraducidoVisual) { nombreTraducidoVisual.textContent = nombreTrad; } document.getElementById('previewFigura').innerHTML = ` ${nombreTrad}

${nombreTrad}
${descripcionTrad} ${figura.categoria}

`; const codigoInput = document.getElementById("codigoElemento"); if (codigoInput && figura.codigoElemento) { codigoInput.value = figura.codigoElemento; } } // Esto es clave: vuelve a mostrar la figura al cambiar idioma document.getElementById('langSelector').addEventListener('change', () => { window.tLang = sessionStorage.getItem('lang') || user.language || 'es'; if (figuraActual) { mostrarPreview(figuraActual); // 🔁 refresca con idioma nuevo } }); } catch (err) { console.warn('❌ No se pudo cargar el catálogo FINA:', err); } const tipoPiscinaSelect = document.getElementById('tipoPiscina'); const medidasPiscina = { olimpica: { width: 1000, height: 500 }, semiolimpica: { width: 750, height: 375 }, fosa: { width: 600, height: 600 } }; let tipoSeleccionado = rutina.tipoPiscina || 'olimpica'; tipoPiscinaSelect.value = tipoSeleccionado; let piscinaWidth = medidasPiscina[tipoSeleccionado].width; let piscinaHeight = medidasPiscina[tipoSeleccionado].height; tipoPiscinaSelect.addEventListener('change', async () => { tipoSeleccionado = tipoPiscinaSelect.value; piscinaWidth = medidasPiscina[tipoSeleccionado].width; piscinaHeight = medidasPiscina[tipoSeleccionado].height; // Actualiza dimensiones visuales stage.width(piscinaWidth); stage.height(piscinaHeight); document.getElementById('piscina').style.width = `${piscinaWidth}px`; document.getElementById('piscina').style.height = `${piscinaHeight}px`; document.getElementById('piscinaContainer').style.width = `${piscinaWidth + 20}px`; // Redibujar cuadrícula layer.destroyChildren(); dibujarCuadricula(layer, piscinaWidth, piscinaHeight); formacionActual.forEach(a => dibujarAtleta(a)); // Guardar en BD try { await fetch(`/api/rutinas/${rutinaId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tipoPiscina: tipoSeleccionado }) }); } catch (err) { console.error('Error al actualizar tipoPiscina:', err); } }); // Escalas de conversión a metros 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`; dibujarCuadricula(layer, piscinaWidth, piscinaHeight); 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 })); } } let formacionActual = []; let atletasKonva = {}; let currentAtleta = null; const maxAtletas = modalidad === 'solo' ? 1 : modalidad === 'duo' ? 2 : 8; document.getElementById('btnAgregarAtleta').addEventListener('click', () => { if (formacionActual.length >= maxAtletas) { alert(`Modalidad "${modalidad}" permite un máximo de ${maxAtletas} atletas.`); return; } const idAtleta = select.dataset.id; const idPersonalizado = document.getElementById('idPersonalizado').value.trim(); // 🚫 Validación para evitar duplicados const yaExiste = formacionActual.some(a => a.atletaId === idAtleta || a.idPersonalizado === idPersonalizado); if (yaExiste) { Swal.fire({ icon: 'warning', title: 'Atleta ya asignado', text: 'Este atleta ya fue agregado a esta formación.', confirmButtonText: 'Entendido' }); return; } const rol = document.getElementById('rolAtleta').value.trim().toLowerCase(); const figura = inputFigura?.value.trim(); const tipoElemento = document.getElementById('tipoElemento').value; const codigoElemento = document.getElementById('codigoElemento').value.trim(); if (!idAtleta || !rol || !idPersonalizado) { alert('Faltan datos del atleta'); return; } currentAtleta = { atletaId: idAtleta, rol, idPersonalizado, figura, tipoElemento, codigoElemento }; alert('Haz clic en la piscina para colocarlo'); }); stage.on('click', () => { if (!currentAtleta || modoDireccion) return; const pos = stage.getPointerPosition(); const metros = convertirAMetros(pos.x, pos.y); console.log(`📏 Posición en metros: (${metros.metrosX}m, ${metros.metrosY}m)`); const atletaObj = { ...currentAtleta, x: pos.x, y: pos.y, grupo: '', direccion: null }; formacionActual.push(atletaObj); dibujarAtleta(atletaObj); currentAtleta = null; document.getElementById('rolAtleta').value = ''; document.getElementById('idPersonalizado').value = ''; inputFigura.value = ''; inputFigura.title = ''; document.getElementById('previewFigura').innerHTML = ''; }); 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, draggable: !modoDireccion }); const text = new Konva.Text({ x: atleta.x, y: atleta.y, text: atleta.idPersonalizado, fontSize: 12, fill: 'white', fontStyle: 'bold', align: 'center' }); text.offsetX(text.width() / 2); text.offsetY(text.height() / 2); const figuraText = new Konva.Text({ x: atleta.x - 25, y: atleta.y + 18, text: atleta.figura || '', fontSize: 10, fill: '#333', fontStyle: 'italic' }); figuraText.listening(false); const coordText = new Konva.Text({ x: atleta.x - 35, y: atleta.y + 30, text: `${metros.metrosX}m, ${metros.metrosY}m`, fontSize: 9, fill: '#666' }); coordText.listening(false); let flecha = null; if (atleta.direccion) { flecha = new Konva.Arrow({ points: [atleta.direccion.x1, atleta.direccion.y1, atleta.direccion.x2, atleta.direccion.y2], stroke: 'black', fill: 'black', strokeWidth: 2, pointerLength: 10, pointerWidth: 10, dash: [4, 4] }); layer.add(flecha); } circle.on('dblclick', () => { const i = formacionActual.findIndex(a => a.idPersonalizado === atleta.idPersonalizado); 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(); const newY = circle.y(); const i = formacionActual.findIndex(a => a.idPersonalizado === atleta.idPersonalizado); if (i !== -1) { formacionActual[i].x = newX; formacionActual[i].y = newY; // Si hay dirección, recalcular puntos if (formacionActual[i].direccion && atletasKonva[atleta.idPersonalizado].flecha) { const dir = formacionActual[i].direccion; const dx = dir.x2 - dir.x1; const dy = dir.y2 - dir.y1; // Actualizar punto de partida y final relativo a nueva posición dir.x1 = newX; dir.y1 = newY; dir.x2 = newX + dx; dir.y2 = newY + dy; atletasKonva[atleta.idPersonalizado].flecha.points([ dir.x1, dir.y1, dir.x2, dir.y2 ]); } } // Actualiza visualmente los textos const nuevosMetros = convertirAMetros(newX, newY); coordText.text(`${nuevosMetros.metrosX}m, ${nuevosMetros.metrosY}m`); coordText.x(newX - 35); coordText.y(newY + 30); text.x(newX - 10); text.y(newY - 7); figuraText.x(newX - 25); figuraText.y(newY + 18); layer.batchDraw(); }); layer.add(circle); layer.add(text); layer.add(figuraText); layer.add(coordText); atletasKonva[atleta.idPersonalizado] = { circle, text, figuraText, coordText, flecha }; layer.draw(); } window.activarModoDireccion = function(index) { modoDireccion = true; indexAtletaDireccion = index; document.getElementById('popoverAtleta')?.remove(); alert('Haz clic para trazar dirección.'); } stage.on('mousedown', () => { if (!modoDireccion || indexAtletaDireccion === null) return; const start = stage.getPointerPosition(); const atleta = formacionActual[indexAtletaDireccion]; const linea = new Konva.Arrow({ points: [start.x, start.y, start.x, start.y], stroke: 'black', fill: 'black', strokeWidth: 2, pointerLength: 10, pointerWidth: 10, dash: [4, 4] }); layer.add(linea); stage.on('mousemove.direccion', () => { const pos = stage.getPointerPosition(); linea.points([start.x, start.y, pos.x, pos.y]); layer.batchDraw(); }); stage.on('mouseup.direccion', () => { const end = stage.getPointerPosition(); atleta.direccion = { x1: start.x, y1: start.y, x2: end.x, y2: end.y }; // Asignar la flecha al atleta visual if (atletasKonva[atleta.idPersonalizado]) { atletasKonva[atleta.idPersonalizado].flecha = linea; } modoDireccion = false; indexAtletaDireccion = null; stage.off('mousemove.direccion'); stage.off('mouseup.direccion'); }); }); function showPopover(x, y, contentHTML) { document.getElementById('popoverAtleta')?.remove(); const popover = document.createElement('div'); popover.id = 'popoverAtleta'; popover.innerHTML = contentHTML; popover.style.position = 'absolute'; popover.style.left = `${x + 50}px`; popover.style.top = `${y + 100}px`; popover.style.zIndex = '1000'; popover.style.background = '#fff'; popover.style.border = '1px solid #ccc'; popover.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)'; popover.style.padding = '10px'; popover.style.borderRadius = '6px'; document.body.appendChild(popover); setTimeout(() => { document.addEventListener('click', function handler(e) { if (!popover.contains(e.target)) { popover.remove(); document.removeEventListener('click', handler); } }); }, 100); } function animarTransicion(nuevaFormacion) { // Limpiar capa y diccionario de atletas previos layer.destroyChildren(); atletasKonva = {}; // Redibujar cuadrícula dibujarCuadricula(layer, piscinaWidth, piscinaHeight); // Dibujar cada atleta nuevaFormacion.forEach(a => { dibujarAtleta(a); }); layer.draw(); } btnGuardarFormacion.addEventListener('click', async () => { const nombre = document.getElementById('nombreFormacion').value.trim(); const notas = document.getElementById('notasFormacion').value.trim(); const duracion = parseInt(document.getElementById('duracionFormacion').value); if (!nombre || formacionActual.length === 0) { alert('Agrega nombre de formación y al menos un atleta'); return; } formacionActual = formacionActual.map(a => { const obj = atletasKonva[a.idPersonalizado]; if (!obj) return a; const x = obj.circle.x(); const y = obj.circle.y(); // Actualizar posición a.x = x; a.y = y; // Si hay flecha, actualiza dirección if (a.direccion && obj.flecha) { const points = obj.flecha.points(); a.direccion = { x1: points[0], y1: points[1], x2: points[2], y2: points[3] }; } return a; }); const body = { nombreColoquial: nombre, notasTacticas: notas, duracion: duracion || 0, atletas: formacionActual.map(a => ({ atletaId: a.atletaId, idPersonalizado: a.idPersonalizado, x: a.x, y: a.y, rol: a.rol, grupo: a.grupo, direccion: a.direccion, figura: a.figura, tipoElemento: a.tipoElemento, codigoElemento: a.codigoElemento, tipoPiscina: tipoPiscinaSelect.value, })) }; // Validación extra: asegura que se esté usando bien el ID de rutina if (!rutinaId) { alert('No se puede guardar porque falta el ID de rutina.'); return; } const method = editIndex === null ? 'POST' : 'PUT'; const endpoint = editIndex === null ? `/api/rutinas/${rutinaId}/formations` : `/api/rutinas/${rutinaId}/formations/${editIndex}`; const res = await fetch(endpoint, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (res.ok) { alert(editIndex === null ? '✅ Formación guardada' : '✏️ Formación actualizada'); window.location.reload(); } else { alert('❌ Error al guardar formación'); } }); const timeline = document.getElementById('lineaTiempo'); new Sortable(timeline, { animation: 150, onEnd: async (evt) => { const nuevaLista = [...timeline.children].map(btn => parseInt(btn.dataset.index)); const nuevasFormaciones = nuevaLista.map(i => formaciones[i]); try { const res = await fetch(`/api/rutinas/${rutinaId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ formaciones: nuevasFormaciones }) }); const json = await res.json(); if (res.ok) { console.log('✅ Formaciones reordenadas'); } else { console.warn('⚠️ Error al guardar nuevo orden:', json); } } catch (err) { console.error('❌ Error de red al reordenar:', err); } } }); 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-secondary 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; indexSeleccionado = parseInt(e.target.dataset.index); const seleccionada = formaciones[indexSeleccionado]; if (!seleccionada) return; document.getElementById('nombreFormacion').value = seleccionada.nombreColoquial || ''; document.getElementById('notasFormacion').value = seleccionada.notasTacticas || ''; formacionActual = seleccionada.atletas.map(a => ({ ...a })); const nuevaFormacion = seleccionada.atletas.map(a => ({ ...a })); animarTransicion(nuevaFormacion); formacionActual = nuevaFormacion; btnEditarFormacion.disabled = false; btnEditarFormacion.className = 'btn btn-outline-secondary btn-sm'; btnEditarFormacion.innerHTML = '✏ Editar formación'; btnGuardarFormacion.textContent = '💾 Guardar Formación'; btnGuardarFormacion.classList.remove('btn-warning'); btnGuardarFormacion.classList.add('btn-primary'); 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; const a = formacionActual[0] || {}; document.getElementById('rolAtleta').value = a.rol || ''; document.getElementById('idPersonalizado').value = a.idPersonalizado || ''; inputFigura.value = a.figura || ''; inputFigura.title = ''; btnEditarFormacion.className = 'btn btn-warning btn-sm d-inline-flex align-items-center gap-2'; btnEditarFormacion.innerHTML = t('editor.edicionActiva'); btnGuardarFormacion.textContent = t('editor.actualizar'); btnGuardarFormacion.classList.remove('btn-primary'); btnGuardarFormacion.classList.add('btn-warning'); } else { 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'); } }); // Observador de redimensionamiento para mantener la tarjeta ajustada const piscinaCard = document.querySelector('#piscinaContainer').closest('.card'); const resizeObserver = new ResizeObserver(() => { if (piscinaCard) { piscinaCard.style.height = 'auto'; } }); 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'); } }); }); }); document.getElementById('langSelector').addEventListener('change', async (e) => { const selectedLang = e.target.value; sessionStorage.setItem('lang', selectedLang); window.tLang = selectedLang; const userId = sessionStorage.getItem("userId"); if (userId) { await fetch(`/api/users/${userId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ language: selectedLang }) }); } // 🟡 Recargar solo 1 vez sessionStorage.setItem('langJustChanged', 'true'); window.location.reload(); }); function logout() { sessionStorage.clear(); localStorage.removeItem('lang'); // ✅ Limpia persistencia de idioma alert("Sesión cerrada"); window.location.href = "../index.html"; } window.logout = logout;