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.

A training session in Blackterz is a complete snapshot of one workout: which routine was used, which exercises were performed, and the exact weight, reps, and completion status of every individual set. Sessions are written to three database tables in a single SQL transaction so a partial write can never occur. Once saved, sessions are permanent — they cannot be edited or deleted — forming a reliable training history.

Starting a Session (Frontend Flow)

The Blackterz frontend pre-fills the training form from the routine’s planned data before the user touches a single input.
1

Load routine exercises

When the user selects a routine, GET /api/rutinas/:id returns the routine with its full nested exercises array. The frontend renders a card per exercise, each with one input row per planned set.
2

Pre-fill from last session

GET /api/sesiones/ultima/:rutina_id is called immediately after loading the routine. If a previous session exists, each set row shows the previous weight and reps in a grey hint span (anterior-valor) — the foundation of progressive overload.
3

User edits and checks sets

The user updates weights and reps freely, checks each set as completed, and can add extra exercises or additional set rows at any time without modifying the base routine.
4

Auto-save draft to localStorage

Every input change, checkbox toggle, and set addition triggers guardarEstadoEntrenamiento(), which serializes the entire session state to localStorage under the key entrenamiento_draft. If the browser is closed or the page is refreshed, the session is fully restored on next load via restaurarEstadoEntrenamiento().
5

Finalize and POST

The user taps Finalizar Entrenamiento. The frontend traverses the DOM, collects only the checked sets, and POSTs to POST /api/sesiones.
Each exercise card has a configurable rest timer. The input accepts 5–300 seconds (default 60 s). As soon as a set is marked complete, the countdown starts automatically. A separate timer runs per card so two exercises can countdown simultaneously.

Recording a Session

POST /api/sesiones is the core write endpoint. The controller extracts usuario_id from the JWT (never from the body) and validates the payload before handing it to the model.

Anti-spoofing

// The controller overwrites any usuario_id the client sent:
datosSesion.usuario_id = req.usuario.usuario_id;
Even if the request body contains a different usuario_id, the model only ever sees the one from the verified JWT.

Request body

rutina_id
number
required
ID of the routine this session is based on.
fecha
string
required
Date of the workout in YYYY-MM-DD format.
notas
string
Optional free-text notes for the overall session.
duracion_minutos
number
Total session duration in minutes, recorded by the in-app stopwatch.
ejercicios
array
required
Array of exercise objects. Must contain at least one item.
ejercicios[].ejercicio_id
number
required
ID of the exercise from the catalog.
ejercicios[].notas
string
Per-exercise notes (e.g. “Keep elbow tucked”).
ejercicios[].series
array
required
Array of set objects. Must contain at least one item.
ejercicios[].series[].numero_serie
number
required
Set number (1-indexed).
ejercicios[].series[].repeticiones
number
required
Repetitions performed in this set.
ejercicios[].series[].peso
number
Weight in kilograms. Defaults to 0 if omitted.
ejercicios[].series[].completada
boolean
Whether the set was marked as completed by the user.

Sample request body

{
  "rutina_id": 3,
  "fecha": "2026-06-15",
  "notas": "Buena sesión, aumenté peso en press",
  "duracion_minutos": 52,
  "ejercicios": [
    {
      "ejercicio_id": 4,
      "notas": "Mantener escápulas retraídas",
      "series": [
        { "numero_serie": 1, "repeticiones": 10, "peso": 60, "completada": true },
        { "numero_serie": 2, "repeticiones": 10, "peso": 62.5, "completada": true },
        { "numero_serie": 3, "repeticiones": 8, "peso": 65, "completada": true }
      ]
    },
    {
      "ejercicio_id": 12,
      "notas": "",
      "series": [
        { "numero_serie": 1, "repeticiones": 12, "peso": 40, "completada": true },
        { "numero_serie": 2, "repeticiones": 10, "peso": 42.5, "completada": false }
      ]
    }
  ]
}
Response (201)
{
  "status": "ok",
  "message": "Sesión guardada exitosamente",
  "data": {
    "sesionId": 18
  }
}

The 3-Table Atomic Transaction

The model guardarSesionCompleta uses pool.getConnection() + beginTransaction / commit / rollback to guarantee that all three writes succeed or none of them persist.
const connection = await pool.getConnection();
try {
  await connection.beginTransaction();

  // 1. INSERT into sesiones_entrenamiento
  const [resultadoSesion] = await connection.execute(
    `INSERT INTO sesiones_entrenamiento (usuario_id, rutina_id, fecha, notas, duracion_minutos)
     VALUES (?, ?, ?, ?, ?)`,
    [datosSesion.usuario_id, datosSesion.rutina_id, datosSesion.fecha,
     datosSesion.notas || null, datosSesion.duracion_minutos || null]
  );
  const sesionId = resultadoSesion.insertId;

  for (const ejercicio of datosSesion.ejercicios) {
    // 2. INSERT into sesion_ejercicios
    const [resultadoEjercicio] = await connection.execute(
      `INSERT INTO sesion_ejercicios (sesion_id, ejercicio_id, notas) VALUES (?, ?, ?)`,
      [sesionId, ejercicio.ejercicio_id, ejercicio.notas || null]
    );
    const sesionEjercicioId = resultadoEjercicio.insertId;

    for (const serie of ejercicio.series) {
      // 3. INSERT into sesion_series
      await connection.execute(
        `INSERT INTO sesion_series (sesion_ejercicio_id, numero_serie, repeticiones, peso, completada)
         VALUES (?, ?, ?, ?, ?)`,
        [sesionEjercicioId, serie.numero_serie, serie.repeticiones,
         serie.peso || 0, serie.completada ? 1 : 0]
      );
    }
  }

  await connection.commit();
  return { sesionId };

} catch (error) {
  await connection.rollback();
  throw error;
} finally {
  connection.release(); // Always return the connection to the pool
}

sesiones_entrenamiento

One row per workout. Stores usuario_id, rutina_id, fecha, notas, and duracion_minutos.

sesion_ejercicios

One row per exercise performed. Foreign key to sesiones_entrenamiento. Stores per-exercise notas.

sesion_series

One row per set. Foreign key to sesion_ejercicios. Stores numero_serie, repeticiones, peso, and completada.

Session History

GET /api/sesiones returns all training sessions for the authenticated user, ordered by date descending. This powers the history table on the profile page.
curl http://localhost:3000/api/sesiones \
  -H "Authorization: Bearer <token>"
{
  "status": "ok",
  "data": [
    {
      "id": 18,
      "fecha": "2026-06-15T00:00:00.000Z",
      "notas": "Buena sesión",
      "duracion_minutos": 52,
      "created_at": "2026-06-15T21:30:00.000Z",
      "rutina_nombre": "Push Day",
      "volumen_total_kg": 1855,
      "total_series": 5
    }
  ]
}
volumen_total_kg
number
Sum of peso × repeticiones for all completed sets in the session. Used for the weekly volume chart.
total_series
number
Count of completed sets across all exercises in the session.

Session Detail

GET /api/sesiones/:id returns the full hierarchical detail of a single session — all exercises and all sets. If the session does not belong to the authenticated user, the server returns 404 (not 403) to avoid revealing that the session ID exists.
curl http://localhost:3000/api/sesiones/18 \
  -H "Authorization: Bearer <token>"

Last Session for Progressive Overload

GET /api/sesiones/ultima/:rutina_id finds the most recent session for a given routine and returns the series grouped by ejercicio_id. The frontend uses this to pre-fill weight inputs and show grey hint values before a new workout begins.
curl http://localhost:3000/api/sesiones/ultima/3 \
  -H "Authorization: Bearer <token>"
{
  "status": "ok",
  "data": {
    "sesionId": 17,
    "ejercicios": {
      "4": [
        { "peso": "60.00", "repeticiones": 10 },
        { "peso": "62.50", "repeticiones": 10 },
        { "peso": "65.00", "repeticiones": 8 }
      ],
      "12": [
        { "peso": "40.00", "repeticiones": 12 }
      ]
    }
  }
}
If no previous session exists for the routine, data is null and a message field explains why — the frontend silently skips the hint injection.

API Reference — Sessions

View full request/response schemas, error codes, and live playground for all /api/sesiones endpoints.

Build docs developers (and LLMs) love