Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Danielsl4/TFG_DAM_2526/llms.txt

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

Match management in FutsalLeague Manager spans two distinct roles: admins create and schedule matches and manage the trash cycle, while referees (and admins) take control of live officiating through a lock-based concurrency system. When a match is finalized, the platform atomically updates team standings, individual player stats, and prediction points for users who voted correctly — all inside a single database transaction.

Creating a match

Only admins can create matches. Send a POST /matches request with the following fields:
POST /matches
{
  "date": "2026-11-08T19:30:00",
  "homeTeamId": 3,
  "awayTeamId": 7,
  "fieldId": 1,
  "groupId": 2,
  "seasonId": 4,
  "phase": "fase_de_grupos",
  "homePlaceholder": null,
  "awayPlaceholder": null
}
FieldRequiredDescription
dateYesISO 8601 datetime for the match
homeTeamIdNoID of the home team (can be null for knockout rounds)
awayTeamIdNoID of the away team (can be null for knockout rounds)
fieldIdYesID of the playing field
groupIdNoGroup this match belongs to
seasonIdYesSeason this match belongs to
phaseNoCompetition phase (defaults to fase_de_grupos)
homePlaceholderNoText label when home team is not yet determined
awayPlaceholderNoText label when away team is not yet determined
Matches are created with status = 'pendiente' and is_active = true.

Match phases

ValueDescription
fase_de_gruposGroup stage match
octavosRound of sixteen
cuartosQuarter-finals
semisSemi-finals
finalFinal

Knockout round placeholders

For knockout matches where the opponent is not yet known at scheduling time, leave homeTeamId and/or awayTeamId as null and use homePlaceholder / awayPlaceholder (for example "Ganador Grupo A") to display descriptive labels on the calendar. Once the teams are determined, update the assignment with PUT /matches/:id/teams.

Referee workflow

1

Lock the match

Before editing any match data, the referee must acquire an exclusive lock. Only one referee can hold the lock at a time; the lock expires automatically after 2 minutes of inactivity.
POST /matches/88/lock
A successful response:
{ "message": "Bloqueo obtenido/renovado", "success": true }
If another referee already holds the lock and it has not expired, the server returns 409 Conflict with the name of the current lock holder.
2

Set the match to in progress

Update the match status to signal that play has started:
PUT /matches/88/status
{ "status": "en_curso" }
Valid status values are pendiente, en_curso, and finalizado. The lock must be held to change status.
3

Record match events

As events occur during the match, post them to the events endpoint. The lock must be held.
POST /matches/88/events
{
  "type": "gol",
  "playerId": 55,
  "teamSide": "home"
}
FieldValues
typegol, tarjeta_amarilla, tarjeta_roja, penalti_tanda_marcado, penalti_tanda_fallado
playerIdID of the player involved
teamSidehome or away
Recording a gol event increments home_goals or away_goals on the match and adds a goal to the player’s player_stats. Recording tarjeta_amarilla or tarjeta_roja adds the card to both the player’s individual stats and the team’s group stats. Penalty shootout events (penalti_tanda_marcado, penalti_tanda_fallado) update home_penalty_goals / away_penalty_goals but do not affect regular player stats.
4

Remove incorrect events

If an event was recorded by mistake, delete it. The system reverses all stat changes made by the event atomically.
DELETE /matches/88/events/412
The response confirms deletion and the match cache is invalidated automatically.
5

Finalize the match

Finalization closes the match and triggers a cascade of updates in a single transaction:
  • status is set to finalizado
  • Any optional observations text is saved
  • The lock is released
  • matches_played is incremented for every player who appeared in an event
  • team_stats (played, won, drawn, lost, goals_for, goals_against, points) is updated for both teams
  • Prediction votes are evaluated and user_points is updated for users who voted correctly
PUT /matches/88/finish
{ "observations": "Yellow card rescinded after review." }
observations is optional. If the match is already marked finalizado, the request returns an error.
6

Unlock the match

After finalizing (or if you need to release the lock early), call the unlock endpoint:
POST /matches/88/unlock
Only the referee holding the lock, an admin, or a request with ?force=true (admin only) can unlock. The lock is also released automatically when the match is finalized.

Updating team assignments for knockout rounds

Once the teams for a knockout match are determined, fill in their IDs:
PUT /matches/95/teams
{
  "homeTeamId": 3,
  "awayTeamId": 11,
  "homePlaceholder": null,
  "awayPlaceholder": null
}
Setting the real team IDs alongside null placeholders clears any descriptive labels from the calendar. You can also update placeholders without setting team IDs if the matchup is partially confirmed.

Trash and restore system

DELETE /matches/:id performs a soft delete — the match’s is_active flag is set to false and it disappears from all public-facing calendars and standings calculations. The match is not permanently removed from the database.
GET /matches/admin/trash
Returns all soft-deleted matches with their home/away team names, placeholders, and the season they belonged to.
POST /matches/88/restore
Sets is_active back to true. The match reappears in calendars and is included in report queries again.
DELETE /matches/88/permanent
Physically removes the match and all its events from the database. This endpoint only works on matches that are already in the trash (is_active = false). Attempting to permanently delete an active match returns a 400 error — you must soft-delete it first.

Season report export

GET /matches/admin/report returns all finalized, active matches for a season along with every event (goals, cards) attached to each match. This payload is used by the frontend to generate PDF exports.
GET /matches/admin/report?season_id=4
The response is an array of match objects, each containing an events array:
[
  {
    "id": 88,
    "date": "2026-11-08T19:30:00",
    "home_team_name": "Ajax Futsal",
    "away_team_name": "FC Benidorm",
    "home_goals": 3,
    "away_goals": 1,
    "home_penalty_goals": null,
    "away_penalty_goals": null,
    "observations": null,
    "events": [
      { "id": 412, "match_id": 88, "type": "gol", "player_name": "D. Sánchez", "team": "home" },
      { "id": 413, "match_id": 88, "type": "tarjeta_amarilla", "player_name": "J. Pérez", "team": "away" }
    ]
  }
]
Matches with is_active = false are excluded from the report.

Build docs developers (and LLMs) love