commit f41a80beec96adb428b2fe493d46d4454435f219 Author: Alain Vasquez Ramirez Date: Wed Apr 23 07:39:18 2025 -0600 avance1 diff --git a/.env b/.env new file mode 100644 index 0000000..9cf1624 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MONGODB_URI=mongodb://127.0.0.1:27017/swimartdb diff --git a/public/atleta.html b/public/atleta.html new file mode 100644 index 0000000..6615f1a --- /dev/null +++ b/public/atleta.html @@ -0,0 +1,15 @@ + + + + + Athlete Dashboard + + + +
+

Bienvenido Atleta 🏊‍♂️

+

Esta es tu zona para consultar tus rutinas y progreso.

+ Cerrar sesión +
+ + diff --git a/public/coach.html b/public/coach.html new file mode 100644 index 0000000..f0292e3 --- /dev/null +++ b/public/coach.html @@ -0,0 +1,109 @@ + + + + + + SwimArt Manager – Crear Rutina + + + + +
+

Crear Rutina – SwimArt Manager

+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + + + + + + + +
+ +
+ +
+ + +
+

Agregar Elementos Técnicos

+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
    +
    + +
    + + +
    +

    Posiciones en la Piscina

    +

    Haz clic en la piscina para posicionar el elemento seleccionado

    + +
    + + +
    + +
    +
    + + + + + diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..3b5f8de --- /dev/null +++ b/public/css/login.css @@ -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); + } \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..124cc05 --- /dev/null +++ b/public/index.html @@ -0,0 +1,35 @@ + + + + + Iniciar Sesión - SwimArt + + + + + + +
    +
    +

    Iniciar Sesión

    +
    +
    + + +
    +
    + + +
    + +
    +
    + ¿No tienes cuenta? Regístrate aquí +
    +
    +
    + + + + + diff --git a/public/js/coach.js b/public/js/coach.js new file mode 100644 index 0000000..43c51c9 --- /dev/null +++ b/public/js/coach.js @@ -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); + } +} diff --git a/public/js/login.js b/public/js/login.js new file mode 100644 index 0000000..fe520d5 --- /dev/null +++ b/public/js/login.js @@ -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.'); + }); \ No newline at end of file diff --git a/public/js/script.js b/public/js/script.js new file mode 100644 index 0000000..ca297fe --- /dev/null +++ b/public/js/script.js @@ -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); + }); + \ No newline at end of file diff --git a/public/register.html b/public/register.html new file mode 100644 index 0000000..427dd7e --- /dev/null +++ b/public/register.html @@ -0,0 +1,58 @@ + + + + + Registro - SwimArt + + + + + + +
    +
    +

    Crear cuenta

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    + ¿Ya tienes cuenta? Inicia sesión +
    +
    +
    + + + + + diff --git a/routes/atletas.js b/routes/atletas.js new file mode 100644 index 0000000..31021db --- /dev/null +++ b/routes/atletas.js @@ -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; diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..d470a5f --- /dev/null +++ b/routes/auth.js @@ -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; diff --git a/routes/routines.js b/routes/routines.js new file mode 100644 index 0000000..4fa4bbb --- /dev/null +++ b/routes/routines.js @@ -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); diff --git a/server.js b/server.js new file mode 100644 index 0000000..4d7b51e --- /dev/null +++ b/server.js @@ -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}`); +});