avance1
This commit is contained in:
commit
f41a80beec
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Athlete Dashboard</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-light text-center">
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-success">Bienvenido Atleta 🏊♂️</h1>
|
||||
<p>Esta es tu zona para consultar tus rutinas y progreso.</p>
|
||||
<a href="index.html" class="btn btn-outline-success mt-3">Cerrar sesión</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>SwimArt Manager – Crear 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 py-5">
|
||||
<h1 class="mb-4 text-center">Crear Rutina – SwimArt Manager</h1>
|
||||
|
||||
<!-- Formulario general -->
|
||||
<form id="routineForm" class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="title" class="form-label">Título de la rutina</label>
|
||||
<input type="text" class="form-control" id="title" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="duration" class="form-label">Duración (segundos)</label>
|
||||
<input type="number" class="form-control" id="duration" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="language" class="form-label">Idioma</label>
|
||||
<select class="form-select" id="language">
|
||||
<option value="es">Español</option>
|
||||
<option value="en">Inglés</option>
|
||||
<option value="fr">Francés</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="music" class="form-label">Subir música (mp3)</label>
|
||||
<input type="file" class="form-control" id="music" accept="audio/mp3">
|
||||
</div>
|
||||
|
||||
<label>Nombre de la competencia:</label>
|
||||
<input type="text" id="nombreCompetencia" name="nombreCompetencia" required>
|
||||
|
||||
<label>Tipo de competencia:</label>
|
||||
<select id="tipoCompetencia" name="tipoCompetencia">
|
||||
<option value="libre">Libre</option>
|
||||
<option value="técnica">Técnica</option>
|
||||
</select>
|
||||
|
||||
<label>Modalidad:</label>
|
||||
<select id="modalidad" name="modalidad">
|
||||
<option value="solo">Solo</option>
|
||||
<option value="duo">Dúo</option>
|
||||
<option value="equipo">Equipo (4 a 8)</option>
|
||||
</select>
|
||||
|
||||
<label>Selecciona atletas participantes:</label>
|
||||
<div id="listaAtletas"></div> <!-- Aquí se insertarán checkboxes desde JS -->
|
||||
|
||||
</form>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Elementos técnicos -->
|
||||
<div class="mb-4">
|
||||
<h4>Agregar Elementos Técnicos</h4>
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" id="elementSelect">
|
||||
<option value="T3.2">T3.2 – TRE Inversión</option>
|
||||
<option value="A1.1">A1.1 – Acro Lanzamiento</option>
|
||||
<option value="H5.3">H5.3 – Híbrido Triple</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<input type="number" class="form-control" id="start" step="0.5" min="0" placeholder="Inicio (s)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<input type="number" class="form-control" id="durationElem" step="0.5" min="0" placeholder="Duración (s)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary w-100" type="button" onclick="addElement()">Agregar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="mt-3 list-group" id="elementListPreview"></ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Diagrama de piscina -->
|
||||
<div class="mb-4">
|
||||
<h4>Posiciones en la Piscina</h4>
|
||||
<p>Haz clic en la piscina para posicionar el elemento seleccionado</p>
|
||||
<canvas id="poolCanvas" width="800" height="400" style="border: 2px solid #007bff;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Guardar -->
|
||||
<div class="text-end">
|
||||
<button class="btn btn-success btn-lg" onclick="saveRoutine()">Guardar Rutina</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/coach.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
body {
|
||||
background: linear-gradient(to right, #6dd5fa, #2980b9);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.2);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Iniciar Sesión - SwimArt</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap CSS -->
|
||||
<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 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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="Ingresar contraseña" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Ingresar</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<small>¿No tienes cuenta? <a href="register.html">Regístrate aquí</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS (opcional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,140 @@
|
|||
let elements = [];
|
||||
|
||||
const canvas = document.getElementById('poolCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let currentElement = null;
|
||||
|
||||
// Dibujar piscina
|
||||
function drawPool() {
|
||||
ctx.fillStyle = '#b3e0f2';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(canvas.width / 2, 0);
|
||||
ctx.lineTo(canvas.width / 2, canvas.height);
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.setLineDash([5, 5]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
drawPool();
|
||||
|
||||
// Clic en piscina para ubicar elemento
|
||||
canvas.addEventListener('click', (e) => {
|
||||
if (!currentElement) return;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / canvas.width) * 25;
|
||||
const y = ((e.clientY - rect.top) / canvas.height) * 20;
|
||||
|
||||
currentElement.position = { x: parseFloat(x.toFixed(2)), y: parseFloat(y.toFixed(2)) };
|
||||
drawCircle(x, y);
|
||||
});
|
||||
|
||||
function drawCircle(x, y) {
|
||||
const px = (x / 25) * canvas.width;
|
||||
const py = (y / 20) * canvas.height;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 6, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Agregar elemento técnico
|
||||
function addElement() {
|
||||
const code = document.getElementById('elementSelect').value;
|
||||
const start = parseFloat(document.getElementById('start').value);
|
||||
const duration = parseFloat(document.getElementById('durationElem').value);
|
||||
|
||||
if (isNaN(start) || isNaN(duration)) {
|
||||
alert('Completa tiempo de inicio y duración correctamente.');
|
||||
return;
|
||||
}
|
||||
|
||||
const element = {
|
||||
code,
|
||||
startTime: start,
|
||||
duration,
|
||||
position: null
|
||||
};
|
||||
|
||||
currentElement = element;
|
||||
elements.push(element);
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item';
|
||||
li.textContent = `${code} desde ${start}s, duración ${duration}s (haz clic en piscina para posicionar)`;
|
||||
document.getElementById('elementListPreview').appendChild(li);
|
||||
}
|
||||
|
||||
// Cargar atletas al iniciar
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
const res = await fetch('/api/atletas'); // Debe estar implementado en backend
|
||||
const atletas = await res.json();
|
||||
const lista = document.getElementById('listaAtletas');
|
||||
|
||||
atletas.forEach(atleta => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.name = 'participantes';
|
||||
checkbox.value = atleta._id;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.textContent = atleta.nombre || atleta.nombreCompleto || atleta.email;
|
||||
|
||||
container.appendChild(checkbox);
|
||||
container.appendChild(label);
|
||||
lista.appendChild(container);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error cargando atletas:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Guardar rutina en Mongo
|
||||
async function saveRoutine() {
|
||||
const title = document.getElementById('title').value;
|
||||
const duration = parseInt(document.getElementById('duration').value);
|
||||
const language = document.getElementById('language').value;
|
||||
|
||||
const nombreCompetencia = document.getElementById('nombreCompetencia').value;
|
||||
const tipoCompetencia = document.getElementById('tipoCompetencia').value;
|
||||
const modalidad = document.getElementById('modalidad').value;
|
||||
const participantes = Array.from(document.querySelectorAll('input[name="participantes"]:checked')).map(el => el.value);
|
||||
|
||||
if (!title || !duration || elements.length === 0 || !nombreCompetencia || !tipoCompetencia || !modalidad) {
|
||||
alert('Por favor completa todos los campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
const routine = {
|
||||
title,
|
||||
duration,
|
||||
language,
|
||||
createdBy: "coach-id-ejemplo",
|
||||
musicUrl: "",
|
||||
elements,
|
||||
nombreCompetencia,
|
||||
tipoCompetencia,
|
||||
modalidad,
|
||||
participantes
|
||||
};
|
||||
|
||||
const res = await fetch('/api/routines', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(routine)
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
if (res.ok) {
|
||||
alert('✅ Rutina guardada exitosamente');
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('❌ Error: ' + result.error);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
document.getElementById('loginForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
alert('Inicio de sesión simulado.');
|
||||
});
|
||||
|
||||
document.getElementById('registerForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
alert('Registro exitoso simulado.');
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
document.getElementById('userForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const data = {
|
||||
name: document.getElementById('name').value,
|
||||
email: document.getElementById('email').value,
|
||||
role: document.getElementById('role').value,
|
||||
passwordHash: 'test123'
|
||||
};
|
||||
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
alert(result.message || result.error);
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Registro - SwimArt</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap CSS -->
|
||||
<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 d-flex justify-content-center align-items-center" style="min-height: 100vh;">
|
||||
<div class="card shadow p-4" style="width: 100%; max-width: 500px;">
|
||||
<h3 class="text-center mb-4">Crear cuenta</h3>
|
||||
<form action="/auth/register" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre completo</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="Ingresar nombre" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nombre de usuario</label>
|
||||
<input type="text" class="form-control" id="username" name="username" placeholder="Ingresar usuario" required>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="Ingresar contraseña" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Rol</label>
|
||||
<select class="form-select" id="role" name="role" required>
|
||||
<option value="coach">Coach</option>
|
||||
<option value="athlete">Atleta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="language" class="form-label">Idioma</label>
|
||||
<select class="form-select" id="language" name="language">
|
||||
<option value="es">Español</option>
|
||||
<option value="en">Inglés</option>
|
||||
<option value="fr">Francés</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100">Registrarse</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<small>¿Ya tienes cuenta? <a href="index.html">Inicia sesión</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,14 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Atleta = require('../models/atleta'); // Asegúrate de tener este modelo
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const atletas = await Atleta.find();
|
||||
res.json(atletas);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,73 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const mongoose = require('mongoose');
|
||||
const bcrypt = require('bcrypt');
|
||||
require('dotenv').config();
|
||||
|
||||
// === Conexión a MongoDB ===
|
||||
mongoose.connect(process.env.MONGODB_URI, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
}).then(() => console.log('[auth.js] Conectado a MongoDB'))
|
||||
.catch(err => console.error(' [auth.js] Error de conexión:', err));
|
||||
|
||||
// === Modelo de usuario ===
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: String,
|
||||
username: { type: String, unique: true, required: true },
|
||||
email: { type: String, unique: true, required: true },
|
||||
passwordHash: String,
|
||||
role: { type: String, enum: ['coach', 'athlete'], required: true },
|
||||
language: { type: String, enum: ['es', 'en', 'fr'], default: 'es' },
|
||||
createdAt: { type: Date, default: Date.now }
|
||||
});
|
||||
|
||||
const User = mongoose.model('User', userSchema);
|
||||
|
||||
// === Ruta: Registro de usuario ===
|
||||
router.post('/register', async (req, res) => {
|
||||
const { name, username, email, password, role, language } = req.body;
|
||||
|
||||
try {
|
||||
const existing = await User.findOne({ email });
|
||||
if (existing) return res.status(400).send('Correo ya registrado');
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const user = new User({ name, username, email, passwordHash, role, language });
|
||||
await user.save();
|
||||
|
||||
res.redirect('/index.html');
|
||||
} catch (error) {
|
||||
console.error('Error en registro:', error);
|
||||
res.status(500).send('Error interno del servidor');
|
||||
}
|
||||
});
|
||||
|
||||
// === Ruta: Login de usuario ===
|
||||
router.post('/login', async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
try {
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) return res.status(401).send('Correo no registrado');
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error en login:', error);
|
||||
res.status(500).send('Error interno del servidor');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,27 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const routineSchema = new mongoose.Schema({
|
||||
title: String,
|
||||
createdBy: String, // En producción usar ObjectId + ref
|
||||
language: { type: String, enum: ['es', 'en', 'fr'], default: 'es' },
|
||||
duration: Number,
|
||||
musicUrl: String,
|
||||
nombreCompetencia: String,
|
||||
tipoCompetencia: { type: String, enum: ['libre', 'técnica'], default: 'libre' },
|
||||
modalidad: { type: String, enum: ['solo', 'duo', 'equipo'], default: 'solo' },
|
||||
participantes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Atleta' }],
|
||||
elements: [
|
||||
{
|
||||
code: String,
|
||||
startTime: Number,
|
||||
duration: Number,
|
||||
position: {
|
||||
x: Number,
|
||||
y: Number
|
||||
}
|
||||
}
|
||||
],
|
||||
createdAt: { type: Date, default: Date.now }
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Routine', routineSchema);
|
|
@ -0,0 +1,25 @@
|
|||
const express = require('express');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware para parsear formularios y JSON
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Servir archivos estáticos (HTML, CSS, JS)
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Rutas separadas
|
||||
const authRoutes = require('./routes/auth');
|
||||
const routineRoutes = require('./routes/routines');
|
||||
|
||||
app.use('/auth', authRoutes); // login, register
|
||||
app.use('/routines', routineRoutes); // rutinas
|
||||
|
||||
// Servidor en marcha
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Servidor corriendo en http://localhost:${PORT}`);
|
||||
});
|
Loading…
Reference in New Issue