Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Blackterz2/Proyecto_5to_Semestre/llms.txt

Use this file to discover all available pages before exploring further.

The MySQL database is named fitness_app (created in an earlier project and reused here). It holds ten tables that cover user accounts, workout routines, exercise catalogs, and training session records. The schema has evolved through a series of incremental migration files — each one tracked in the docs/ directory — rather than being recreated from scratch.
The default fallback in db.js points to a database named blackterz, but the real database is fitness_app. Always confirm your .env file sets DB_NAME=fitness_app.

Tables

ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
nombreVARCHAR(50)NOT NULLDisplay name
emailVARCHAR(50)UNIQUE, NOT NULLLogin identifier
passwordVARCHAR(60)NOT NULLbcrypt hash ($2b$10$...)
activoTINYINT(1)DEFAULT 10 = soft-deleted account
avatar_urlVARCHAR(255)NULLPath to uploaded photo, e.g. /images/avatars/avatar-5.jpg
nivel_experienciaENUMNULLPrincipiante / Intermedio / Avanzado
peso_actualDECIMAL(5,2)NULLBody weight in kg
estatura_cmINTNULLHeight in centimetres
sexoENUMNULLMasculino / Femenino / Otro
onboarding_completadoTINYINT(1)DEFAULT 0Set to 1 after first-run form
created_atTIMESTAMP
updated_atTIMESTAMP
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
usuario_idBIGINTFK → usuarios.idOwner — set from JWT, never from request body
nombreVARCHAR(50)NOT NULL
descripcionTEXTNULL
activaTINYINT(1)DEFAULT 10 = soft-deleted routine
es_recomendadaTINYINT(1)DEFAULT 0Seed routines visible to all users
created_atTIMESTAMP
updated_atTIMESTAMP
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
nombreVARCHAR(50)NOT NULLSpanish name, e.g. Press de banca
descripcionTEXTNULL
categoriaENUMfuerza / cardio / flexibilidad
imagen_urlTEXTNULLFilename, e.g. bench-press.avif
gif_urlTEXTNULLAnimation URL (added in later migration)
created_atTIMESTAMP
updated_atTIMESTAMP
The catalog is populated by seed.sql and contains 64 exercises across barbell, bodyweight, dumbbell, machine, and cable categories.
ColumnTypeConstraints
idBIGINTPK, AUTO_INCREMENT
nombreVARCHAR(50)UNIQUE
The 15 seeded groups are: Hombros, Tríceps, Pecho, Aductores, Antebrazos, Bíceps, Core, Cuádriceps, Espalda Baja, Espalda Media, Flexores Cadera, Gemelos, Glúteos, Isquiotibiales, Trampas.
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
ejercicio_idBIGINTFK → ejercicios.id
grupo_muscular_idBIGINTFK → grupos_musculares.id
tipoENUMprincipal or secundario
Each exercise can have multiple principal and secundario muscle groups. The seed produces 188 rows total.
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
rutina_idBIGINTFK → rutinas.id
ejercicio_idBIGINTFK → ejercicios.id
ordenINTDisplay position within the routine
seriesINTPlanned sets
repeticionesINTPlanned reps
pesoDECIMAL(6,2)Planned weight in kg
tiempo_descansoINTNULLRest timer in seconds between sets
created_atTIMESTAMP
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
usuario_idBIGINTFK → usuarios.idTaken from JWT, never from body
rutina_idBIGINTFK → rutinas.idCan be NULL if routine is later soft-deleted
fechaDATENOT NULLTraining date
duracion_minutosINTNULLSet when the user finalizes the session
notasTEXTNULLOptional session note
created_atTIMESTAMP
updated_atTIMESTAMP
The estado column (pending/completed/cancelled) was removed in migracion-drop-estado.sql. The system only persists fully completed sessions, making estado redundant.
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
sesion_idBIGINTFK → sesiones_entrenamiento.id
ejercicio_idBIGINTFK → ejercicios.id
ordenINTOrdering within the session
notasTEXTNULLPer-exercise notes entered during training
created_atTIMESTAMP
Four columns were removed from this table in migracion-dropear-columnas.sql: series_planificadas, repeticiones_planificadas, peso_planificado, and completado. These were redundant with data already tracked in sesion_series and ejercicios_rutinas. Two additional columns (duracion_segundos, tiempo_descanso) were removed from the sesion_series table in the same migration.
ColumnTypeConstraintsNotes
idBIGINTPK, AUTO_INCREMENT
sesion_ejercicio_idBIGINTFK → sesion_ejercicios.id
numero_serieINTNOT NULL1-based set number
repeticionesINTActual reps performed
pesoDECIMAL(6,2)Actual weight in kg
completadaTINYINT(1)DEFAULT 1Checkbox state; powers volume calculations
created_atTIMESTAMP
ColumnTypeConstraintsNotes
idINTPK, AUTO_INCREMENT
usuario_idBIGINTFK → usuarios.id
tokenVARCHAR(64)UNIQUE64-char hex string generated by crypto.randomBytes(32) — stored as plain hex, not hashed
expira_enDATETIMEToken expiry timestamp
usadoTINYINT(1)DEFAULT 0Set to 1 after redemption (single-use)
created_atTIMESTAMP

Entity Relationships

usuarios 1 ──────────────── N rutinas
usuarios 1 ──────────────── N sesiones_entrenamiento
usuarios 1 ──────────────── N password_resets

rutinas  1 ──────────────── N ejercicios_rutinas ──── N ← ejercicios
rutinas  1 ──────────────── N sesiones_entrenamiento

ejercicios 1 ──────────────── N ejercicios_grupos_musculares ──── N ← grupos_musculares
ejercicios 1 ──────────────── N sesion_ejercicios

sesiones_entrenamiento 1 ─── N sesion_ejercicios
sesion_ejercicios      1 ─── N sesion_series

The Soft-Delete Pattern

Two tables use a boolean flag instead of a DELETE statement, so that training history is never orphaned:
TableColumnEffect when 0 / FALSE
usuariosactivoUser cannot log in; account is hidden but data persists
rutinasactivaRoutine disappears from the user’s list; historical sessions linked to it remain
// src/models/usuarioModel.js — soft-deletes an account
async function desactivarUsuario(usuarioId) {
  const [resultado] = await pool.execute(
    `UPDATE usuarios SET activo = FALSE WHERE id = ?`,
    [usuarioId]
  );
  return resultado.affectedRows > 0;
}
// src/models/rutinaModel.js — soft-deletes a routine
async function desactivarRutina(rutinaId, usuarioId) {
  const sql = 'UPDATE rutinas SET activa = FALSE WHERE id = ? AND usuario_id = ?';
  const [result] = await pool.execute(sql, [rutinaId, usuarioId]);
  return result.affectedRows;
}
Always filter activa = TRUE when querying routines, and activo = TRUE when listing users or validating login. Omitting these filters surfaces deleted records.

The JOIN Pattern: Routine with Exercises

Fetching a routine with its full exercise list requires joining three tables in a single query. The result is tabular — one row per exercise — and is then restructured in JavaScript into a nested object.
-- src/models/rutinaModel.js — obtenerRutinaConEjercicios()
SELECT
  r.id          AS rutina_id,
  r.nombre      AS rutina_nombre,
  r.descripcion AS rutina_descripcion,
  e.id          AS ejercicio_id,
  e.nombre      AS ejercicio_nombre,
  e.descripcion AS ejercicio_descripcion,
  e.categoria   AS ejercicio_categoria,
  e.imagen_url  AS ejercicio_imagen_url,
  e.gif_url     AS ejercicio_gif_url,
  er.orden,
  er.series,
  er.repeticiones,
  er.peso
FROM rutinas r
LEFT JOIN ejercicios_rutinas er ON r.id = er.rutina_id
LEFT JOIN ejercicios e ON er.ejercicio_id = e.id
WHERE r.id = ? AND r.usuario_id = ? AND r.activa = TRUE
ORDER BY er.orden ASC
LEFT JOIN is used so that a routine with zero exercises still returns a result (with ejercicio_id = NULL) rather than disappearing from the query entirely. The JavaScript restructuring step checks for null and produces an empty ejercicios: [] array:
// Same file — tabular rows → nested object
const rutina = {
  id:          rows[0].rutina_id,
  nombre:      rows[0].rutina_nombre,
  descripcion: rows[0].rutina_descripcion,
  ejercicios:  [],
};

for (const fila of rows) {
  if (fila.ejercicio_id !== null) {
    rutina.ejercicios.push({
      id:           fila.ejercicio_id,
      nombre:       fila.ejercicio_nombre,
      categoria:    fila.ejercicio_categoria,
      imagen_url:   fila.ejercicio_imagen_url,
      gif_url:      fila.ejercicio_gif_url,
      orden:        fila.orden,
      series:       fila.series,
      repeticiones: fila.repeticiones,
      peso:         fila.peso,
    });
  }
}

Migration History

Schema changes are tracked as individual .sql files under docs/. Run them in chronological order on any fresh database after applying the base schema.
FileChange
migracion-activo.sqlALTER TABLE usuarios ADD COLUMN activo BOOLEAN DEFAULT TRUE — enables soft-delete for accounts
migracion-avatar.sqlALTER TABLE usuarios ADD COLUMN avatar_url VARCHAR(255) DEFAULT NULL — stores path to multer-uploaded photo
migracion-hito17.sqlAdds nivel_experiencia, peso_actual, estatura_cm, and onboarding_completado to usuarios for the onboarding flow
migracion-hito17-parte2.sqlAdds sexo ENUM('Masculino','Femenino','Otro') to usuarios

Build docs developers (and LLMs) love