diff --git a/public/catalog/figurasFINA.json b/public/catalog/figurasFINA.json index f10bac7..5701ac8 100644 --- a/public/catalog/figurasFINA.json +++ b/public/catalog/figurasFINA.json @@ -3,54 +3,63 @@ "nombre": "Pez Volador con Giro", "descripcion": "Impulso a vertical desde pike, transición a cola de pez aérea, regreso a vertical y giro de 180°.", "categoria": "1A", + "codigoElemento": "1A", "imagen": "pez-volador-giro.png" }, { "nombre": "Pez Volador", "descripcion": "Impulso a vertical desde pike, transición a cola de pez aérea, regreso a vertical y descenso.", "categoria": "1B", + "codigoElemento": "1B", "imagen": "pez-volador.png" }, { "nombre": "Rodilla Flexionada con Giro Completo", "descripcion": "Vertical con giro completo a rodilla flexionada, otro giro a vertical, apertura 180° a split y walkout.", "categoria": "2A", + "codigoElemento": "2A", "imagen": "rodilla-giro-completo.png" }, { "nombre": "Rodilla Flexionada con Medio Giro", "descripcion": "Vertical con medio giro a rodilla flexionada, otro medio giro a vertical, split y walkout.", "categoria": "2B", + "codigoElemento": "2B", "imagen": "rodilla-medio-giro.png" }, { "nombre": "Fouetté con Giro 720°", "descripcion": "Desde cola de pez, dos giros Fouetté, paso a vertical y giro continuo de 720°.", "categoria": "3A", + "codigoElemento": "3A", "imagen": "fouette-720.png" }, { "nombre": "Fouetté con Giro 360°", "descripcion": "Desde cola de pez, dos giros Fouetté, paso a vertical y giro rápido de 360°.", "categoria": "3B", + "codigoElemento": "3B", "imagen": "fouette-360.png" }, { "nombre": "Mariposa", "descripcion": "Secuencia dinámica con pike, cola de pez, split, giros, arco superficial y salida en layout.", "categoria": "4", + "codigoElemento": "4", "imagen": "mariposa.png" }, { "nombre": "Split con Giro 180°", "descripcion": "Impulso a vertical, split aéreo, giro 180° a rodilla flexionada y descenso vertical.", "categoria": "5A", + "codigoElemento": "5A", "imagen": "split-giro.png" }, { "nombre": "Split a Rodilla Flexionada", "descripcion": "Impulso a vertical, split aéreo, transición a rodilla flexionada y descenso vertical.", "categoria": "5B", + "codigoElemento": "5B", "imagen": "split-rodilla.png" } ] diff --git a/public/css/editorPiscina.css b/public/css/editorPiscina.css index 281ee39..7830f3c 100644 --- a/public/css/editorPiscina.css +++ b/public/css/editorPiscina.css @@ -1,212 +1,4 @@ -/* GENERAL */ -body { - font-family: 'Inter', sans-serif; - background-color: #f9fafb; - color: #212529; - margin: 0; - padding: 0; -} - -/* TARJETAS */ -.card { - border: none; - border-radius: 1rem; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); - background-color: #fff; - overflow: visible; -} - -.card-body { - overflow: visible; -} - -/* FORMULARIOS */ -.form-control, -.form-select { - border-radius: 0.75rem; - font-size: 1rem; - transition: border-color 0.3s ease, box-shadow 0.3s ease; -} - -.form-control:focus, -.form-select:focus { - border-color: #0d6efd; - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15); -} - -/* BOTONES */ -button, -.btn { - border-radius: 0.75rem; - transition: all 0.25s ease; - font-weight: 600; -} - -.btn:hover { - transform: translateY(-1px); - box-shadow: 0 4px 10px rgba(0,0,0,0.06); -} - -#btnAgregarAtleta, -#btnActualizarPiscina, -#btnGuardarFormacion, -#playPauseBtn { - padding: 0.75rem 1rem; - font-size: 1rem; -} - -#playPauseBtn { - background-color: #0d6efd; - color: white; - font-weight: 500; - border: none; - border-radius: 8px; - box-shadow: 0 2px 5px rgba(0,0,0,0.1); -} - -#playPauseBtn:hover { - background-color: #084298; - transform: scale(1.05); -} - -/* MULTIMEDIA */ -#waveform { - width: 100%; - height: 100px; - margin-bottom: 10px; -} - -#audioPlayer { - display: none; -} - -/* PISCINA */ -#piscinaContainer { - background-color: #e6f7ff; - border: 2px dashed #007bff; - padding: 1rem; - border-radius: 0.75rem; - width: fit-content; - height: fit-content; - min-width: 300px; - min-height: 200px; - max-width: 100%; - box-sizing: border-box; -} - -#piscina { - display: block; - width: 100% !important; - height: 100% !important; - min-width: 300px; - min-height: 200px; - background-image: - linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px); - background-size: 45px 45px; - background-position: top left; - position: relative; - overflow: hidden; -} - -/* TIMELINE */ -.timeline-placeholder { - display: flex; - overflow-x: auto; - gap: 10px; - padding: 1rem; - border: 1px solid #ddd; - background: #ffffff; - border-radius: 0.75rem; - box-shadow: 0 1px 4px rgba(0,0,0,0.05); -} - -.timeline-placeholder .step { - min-width: 100px; - padding: 0.5rem; - border: 1px solid #ccc; - background: white; - border-radius: 8px; - text-align: center; - cursor: pointer; - font-size: 0.85rem; - transition: all 0.2s ease; - box-shadow: 1px 1px 3px rgba(0,0,0,0.05); -} - -.timeline-placeholder .step:hover { - background-color: #dbeafe; - font-weight: bold; - transform: scale(1.03); -} - -.step.active { - background-color: #0d6efd !important; - color: white !important; - font-weight: bold; -} - -/* AUTOCOMPLETADO */ -#sugerenciasFigura { - margin-top: 4px; - padding: 6px 0; - background-color: #fff; - border: 1px solid #ccc; - border-radius: 6px; - box-shadow: 0 2px 8px rgba(0,0,0,0.06); - max-height: 160px; - overflow-y: auto; - position: absolute; - width: 100%; - z-index: 10; -} - -#sugerenciasFigura button { - display: block; - width: 100%; - padding: 6px 12px; - font-size: 0.9rem; - border: none; - background: none; - text-align: left; - color: #333; - cursor: pointer; - transition: background 0.2s ease; -} - -#sugerenciasFigura button:hover { - background-color: #edf6ff; - color: #007bff; - font-weight: 500; -} - -/* VISTA PREVIA FIGURA */ -#previewFigura img { - margin-top: 8px; - max-width: 100%; - border: 1px solid #ddd; - border-radius: 6px; - height: auto; -} - -#previewFigura p { - font-size: 0.85rem; - color: #555; - margin-top: 4px; -} - -/* RESPONSIVO */ -@media (max-width: 768px) { - #piscina { - min-height: 300px; - } - - .timeline-placeholder .step { - min-width: 80px; - font-size: 0.75rem; - } -} -/* ---------- ESTILO GLOBAL 2025 ---------- */ +/* ------------------- ESTILO GLOBAL ------------------- */ :root { --color-primary: #0d6efd; --color-success: #198754; @@ -216,16 +8,67 @@ button, --font-family: 'Inter', sans-serif; } -/* Fuentes y cuerpo */ +html { + overflow-y: scroll; + scroll-behavior: smooth; +} + body { font-family: var(--font-family); - font-size: clamp(1rem, 1vw + 0.2rem, 1.125rem); + font-size: clamp(1rem, 1vw + 0.2rem, 1.1rem); line-height: 1.7; background-color: #f9fafb; color: #212529; + margin: 0; + padding: 0; } -/* Botones unificados */ +/* ------------------- NAVBAR ------------------- */ +.navbar { + min-height: 72px; + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +.navbar .nav-link { + font-weight: 500; + font-size: 1rem; + transition: all 0.2s ease; +} + +.navbar .nav-link:hover { + opacity: 0.85; +} + +.navbar * { + box-sizing: border-box; +} + +/* ------------------- TARJETAS ------------------- */ +.card { + border: none; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + background-color: white; + overflow: visible; +} + +/* ------------------- FORMULARIOS ------------------- */ +.form-control, +.form-select { + border-radius: var(--radius-lg); + border: 1px solid #ced4da; + transition: var(--transition-fast); + font-size: 1rem; +} + +.form-control:focus, +.form-select:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.15); +} + +/* ------------------- BOTONES ------------------- */ button, .btn { border-radius: var(--radius-lg); @@ -248,7 +91,6 @@ button, border-color: var(--color-primary); color: #fff; } - .btn-primary:hover { background-color: #084298; border-color: #084298; @@ -259,7 +101,6 @@ button, border-color: var(--color-success); color: white; } - .btn-success:hover { background-color: #145c32; border-color: #145c32; @@ -270,7 +111,6 @@ button, border-color: var(--color-primary); background-color: white; } - .btn-outline-primary:hover { background-color: var(--color-primary); color: white; @@ -281,37 +121,142 @@ button, border-color: #6c757d; background-color: white; } - .btn-outline-secondary:hover { background-color: #6c757d; color: white; } -/* Formulario */ -.form-control, -.form-select { - border-radius: var(--radius-lg); - border: 1px solid #ced4da; - transition: var(--transition-fast); -} - -.form-control:focus, -.form-select:focus { - border-color: var(--color-primary); - box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.15); -} - -/* Tarjetas */ -.card { - border: none; - border-radius: var(--radius-lg); - box-shadow: var(--shadow-md); - background-color: white; -} - -/* Botón desactivado coherente */ button:disabled, .btn:disabled { opacity: 0.65; cursor: not-allowed; } + +/* ------------------- PISCINA ------------------- */ +#piscinaContainer { + background-color: #e6f7ff; + border: 2px dashed #007bff; + padding: 1rem; + border-radius: var(--radius-lg); + width: fit-content; + height: fit-content; + min-width: 300px; + min-height: 200px; + max-width: 100%; + box-sizing: border-box; +} + +#piscina { + display: block; + width: 100% !important; + height: 100% !important; + background-image: + linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px); + background-size: 45px 45px; + background-position: top left; + overflow: hidden; +} + +/* ------------------- TIMELINE ------------------- */ +.timeline-placeholder { + display: flex; + overflow-x: auto; + gap: 10px; + padding: 1rem; + border: 1px solid #ddd; + background: #ffffff; + border-radius: var(--radius-lg); + box-shadow: 0 1px 4px rgba(0,0,0,0.05); +} + +.timeline-placeholder .step { + min-width: 100px; + padding: 0.5rem; + border: 1px solid #ccc; + background: white; + border-radius: 8px; + text-align: center; + cursor: pointer; + font-size: 0.85rem; + transition: all 0.2s ease; + box-shadow: 1px 1px 3px rgba(0,0,0,0.05); +} +.timeline-placeholder .step:hover { + background-color: #dbeafe; + font-weight: bold; + transform: scale(1.03); +} +.step.active { + background-color: #0d6efd !important; + color: white !important; + font-weight: bold; +} + +/* ------------------- AUTOCOMPLETADO ------------------- */ +#sugerenciasFigura { + margin-top: 4px; + padding: 6px 0; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + max-height: 160px; + overflow-y: auto; + position: absolute; + width: 100%; + z-index: 10; +} +#sugerenciasFigura button { + display: block; + width: 100%; + padding: 6px 12px; + font-size: 0.9rem; + border: none; + background: none; + text-align: left; + color: #333; + cursor: pointer; + transition: background 0.2s ease; +} +#sugerenciasFigura button:hover { + background-color: #edf6ff; + color: #007bff; + font-weight: 500; +} + +/* ------------------- VISTA PREVIA FIGURA ------------------- */ +#previewFigura img { + margin-top: 8px; + max-width: 100%; + border: 1px solid #ddd; + border-radius: 6px; + height: auto; +} +#previewFigura p { + font-size: 0.85rem; + color: #555; + margin-top: 4px; +} + +/* ------------------- MULTIMEDIA ------------------- */ +#waveform { + width: 100%; + height: 100px; + margin-bottom: 10px; +} +#audioPlayer { + display: none; +} + +/* ------------------- RESPONSIVO ------------------- */ +@media (max-width: 768px) { + #piscina { + min-height: 280px; + } + + .timeline-placeholder .step { + min-width: 80px; + font-size: 0.75rem; + } +} diff --git a/public/js/login.js b/public/js/login.js index d37e616..a161ee1 100644 --- a/public/js/login.js +++ b/public/js/login.js @@ -20,10 +20,11 @@ document.getElementById('loginForm').addEventListener('submit', async function ( sessionStorage.setItem("role", data.role); if (data.role === "coach") { - window.location.href = "coach.html"; + window.location.href = "equipoDisponibles.html"; } else if (data.role === "athlete") { window.location.href = "atleta.html"; } else { window.location.href = "ventanaPrincipal.html"; } + }); diff --git a/public/js/piscina.js b/public/js/piscina.js index 75051c8..0c99783 100644 --- a/public/js/piscina.js +++ b/public/js/piscina.js @@ -55,11 +55,16 @@ document.addEventListener('DOMContentLoaded', async () => { item.type = 'button'; item.textContent = figura.nombre; item.onclick = () => { - inputFigura.value = figura.nombre; - inputFigura.title = `${figura.descripcion} (${figura.categoria})`; - sugerenciasBox.innerHTML = ''; - mostrarPreview(figura); - }; + 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); }); }); @@ -73,23 +78,54 @@ document.addEventListener('DOMContentLoaded', async () => { ${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); + console.warn('❌ No se pudo cargar el catálogo FINA:', err); } - const tipoPiscinaSelect = document.getElementById('tipoPiscina'); +const tipoPiscinaSelect = document.getElementById('tipoPiscina'); - const medidasPiscina = { - olimpica: { width: 1000, height: 500 }, - semiolimpica: { width: 750, height: 375 }, - fosa: { width: 600, height: 600 } - }; +const medidasPiscina = { + olimpica: { width: 1000, height: 500 }, + semiolimpica: { width: 750, height: 375 }, + fosa: { width: 600, height: 600 } +}; - const tipoSeleccionado = rutina.tipoPiscina || 'olimpica'; - tipoPiscinaSelect.value = tipoSeleccionado; +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; - const piscinaWidth = medidasPiscina[tipoSeleccionado].width; - const 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; @@ -203,12 +239,17 @@ document.addEventListener('DOMContentLoaded', async () => { }); const text = new Konva.Text({ - x: atleta.x - 10, - y: atleta.y - 7, + x: atleta.x, + y: atleta.y, text: atleta.idPersonalizado, fontSize: 12, - fill: 'white' + 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, @@ -276,12 +317,16 @@ document.addEventListener('DOMContentLoaded', async () => { 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] - }); + 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); } @@ -301,12 +346,16 @@ document.addEventListener('DOMContentLoaded', async () => { const start = stage.getPointerPosition(); const atleta = formacionActual[indexAtletaDireccion]; - const linea = new Konva.Line({ - points: [start.x, start.y, start.x, start.y], - stroke: 'black', - strokeWidth: 2, - dash: [4, 4] - }); +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', () => { @@ -452,10 +501,10 @@ document.addEventListener('DOMContentLoaded', async () => { }); if (res.ok) { - alert(editIndex === null ? 'Formación guardada' : 'Formación actualizada'); + alert(editIndex === null ? '✅ Formación guardada' : '✏️ Formación actualizada'); window.location.reload(); } else { - alert('Error al guardar formación'); + alert('❌ Error al guardar formación'); } }); @@ -475,12 +524,12 @@ try { const json = await res.json(); if (res.ok) { - console.log('Formaciones reordenadas'); + console.log('✅ Formaciones reordenadas'); } else { - console.warn('Error al guardar nuevo orden:', json); + console.warn('⚠️ Error al guardar nuevo orden:', json); } } catch (err) { - console.error('Error de red al reordenar:', err); + console.error('❌ Error de red al reordenar:', err); } } @@ -520,7 +569,7 @@ try { btnEditarFormacion.className = 'btn btn-outline-secondary btn-sm'; btnEditarFormacion.innerHTML = '✏ Editar formación'; - btnGuardarFormacion.textContent = 'Guardar Formación'; + btnGuardarFormacion.textContent = '💾 Guardar Formación'; btnGuardarFormacion.classList.remove('btn-warning'); btnGuardarFormacion.classList.add('btn-primary'); @@ -550,7 +599,7 @@ formaciones.forEach((f, i) => { inputFigura.title = ''; btnEditarFormacion.className = 'btn btn-warning btn-sm d-inline-flex align-items-center gap-2'; - btnEditarFormacion.innerHTML = 'En edición activa'; + btnEditarFormacion.innerHTML = '🟡 En edición activa'; btnGuardarFormacion.textContent = 'Actualizar Formación'; btnGuardarFormacion.classList.remove('btn-primary'); @@ -561,41 +610,6 @@ formaciones.forEach((f, i) => { }); -const newWidth = medidasPiscina[tipoPiscinaSelect.value].width; -const newHeight = medidasPiscina[tipoPiscinaSelect.value].height; - - try { - const res = await fetch(`/api/rutinas/${rutinaId}/piscina`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ width: newWidth, height: newHeight }) - }); - - if (!res.ok) throw new Error('No se pudo guardar en la base de datos'); - - stage.width(newWidth); - stage.height(newHeight); - divPiscina.style.width = `${newWidth}px`; - divPiscina.style.height = `${newHeight}px`; - document.getElementById('piscinaContainer').style.width = `${newWidth + 20}px`; - - layer.destroyChildren(); - dibujarCuadricula(layer, newWidth, newHeight); - formacionActual.forEach(a => dibujarAtleta(a)); - - Swal.fire({ - icon: 'success', - title: 'Piscina actualizada', - text: 'El tamaño y la cuadrícula fueron actualizados correctamente.' - }); - } catch (err) { - Swal.fire({ - icon: 'error', - title: 'Error', - text: 'No se pudo actualizar la piscina en la base de datos.' - }); - } - }); // Observador de redimensionamiento para mantener la tarjeta ajustada const piscinaCard = document.querySelector('#piscinaContainer').closest('.card'); @@ -615,6 +629,10 @@ const wave = WaveSurfer.create({ responsive: true }); +if (rutina.musicUrl) { + wave.load(rutina.musicUrl); +} + const playPauseBtn = document.getElementById('playPauseBtn'); playPauseBtn.addEventListener('click', () => { @@ -657,3 +675,5 @@ wave.on('audioprocess', (currentTime) => { } }); }); + +}); diff --git a/public/piscina.html b/public/piscina.html index 181b50a..0b0140e 100644 --- a/public/piscina.html +++ b/public/piscina.html @@ -19,131 +19,137 @@
Inicializar Rutina Equipos Disponibles - Piscina + Piscina
-
- +
-

Nombre de la Rutina

-

Tipo: Cargando...

-

Modalidad: Cargando...

+

Editor de Formación

+
+
Tipo: Cargando...
+
Modalidad: Cargando...
+
- -
- -
-
-
🎯 Agregar atleta
+
+ +
+
+
Agregar Atleta
- - +
+ + +
- - +
- - - - - +
+ + +
+
+ +
+
- - - - - - -
+ +
+ + +
+ +
+
- -
-
-
🏊 Vista de la Piscina
+ +
+
+
Piscina
-
- - -
+
+ + +
- - -
+
-
- +
+
+ + +
- -
-
- - + +
+
Datos de Formación
+
+
+ + +
+ +
+ + +
+ +
+ + +
-
- - -
-
- - + +
+
- -
- + +
+
Reproductor de Audio
+
+
+ +
- -
- - - -
- - -
- -
- -
-

🕒 Línea de tiempo

-
-
- + +