document.addEventListener('DOMContentLoaded', async () => { 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 = a._id; opt.textContent = a.name; select.appendChild(opt); }); 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.textContent = figura.nombre; item.onclick = () => { inputFigura.value = figura.nombre; inputFigura.title = `${figura.descripcion} (${figura.categoria})`; sugerenciasBox.innerHTML = ''; 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) { document.getElementById('previewFigura').innerHTML = ` ${figura.nombre}

${figura.descripcion} ${figura.categoria}

`; const codigoInput = document.getElementById("codigoElemento"); if (codigoInput && figura.codigoElemento) { codigoInput.value = figura.codigoElemento; } } } 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.value; const rol = document.getElementById('rolAtleta').value.trim().toLowerCase(); const idPersonalizado = document.getElementById('idPersonalizado').value.trim(); 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); circle.on('dblclick', () => { const i = formacionActual.findIndex(a => a.idPersonalizado === atleta.idPersonalizado); const html = `
${atleta.figura || 'Sin figura'}
`; showPopover(circle.x(), circle.y(), html); }); 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; } 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); atletasKonva[atleta.idPersonalizado] = { circle, text, figuraText, coordText }; layer.add(text); layer.add(figuraText); layer.add(coordText); if (atleta.direccion) { 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(dir); } 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 }; 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) { 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(); const duracion = parseInt(document.getElementById('duracionFormacion').value); if (!nombre || formacionActual.length === 0) { alert('Agrega nombre de formación y al menos un atleta'); return; } const body = { nombreColoquial: nombre, notasTacticas: notas, duracion: duracion || 0, atletas: formacionActual.map(a => ({ atletaId: a.atletaId._id || 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, })) }; 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 = '🟡 En edición activa'; btnGuardarFormacion.textContent = 'Actualizar Formación'; btnGuardarFormacion.classList.remove('btn-primary'); btnGuardarFormacion.classList.add('btn-warning'); } else { alert('Selecciona una formación desde la línea de tiempo primero.'); } }); // 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'); } }); }); });