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.

FutsalLeague Manager includes a prediction game — called the porra — that lets registered users call the outcome of any upcoming match before it kicks off. Correct predictions earn a point, and a season leaderboard ranks every participant. The game runs entirely within the platform: there is nothing extra to install or configure, and every match page displays live community sentiment as soon as the first vote is cast.
The porra is a powerful engagement tool. When fans have a stake in the result, they follow every match more closely — which increases traffic to match pages and drives community activity throughout the season.

How voting works

Any registered user can cast or change their prediction on a match while it has pendiente status. The three vote options map directly to the possible results.
Vote valueMeaning
localHome team wins
empateDraw
visitanteAway team wins
Votes are stored in the match_votes table with a unique constraint on (match_id, user_id), so submitting a new vote simply replaces the previous one. There is no separate “update vote” endpoint — the same POST /matches/:id/vote call handles both first-time votes and changes.

Voting rules

  • Votes are only accepted while the match status is pendiente. Attempting to vote on an en_curso or finalizado match returns 400 Bad Request.
  • Both teams must be fully assigned to the match. If either home_team_id or away_team_id is null (placeholder teams used in knockout draws), the server rejects the vote with a 400 Bad Request and an explanatory message.
  • A user can change their prediction any number of times before the match starts.
  • Votes from anonymous (unauthenticated) users are not accepted. A valid JWT is required.

Casting a vote

POST /matches/15/vote
Authorization: Bearer <token>
Content-Type: application/json

{
  "vote": "local"
}
A successful response returns the updated voting stats immediately so the UI can refresh the community breakdown without a separate request.
{
  "message": "Voto registrado correctamente",
  "votingStats": {
    "local": 43,
    "draw": 18,
    "away": 27,
    "total": 88
  }
}

Point system

Points are awarded automatically inside the match finalization transaction. No separate job or cron is needed.
  1. When PUT /matches/:id/finish is called, the server determines the actual result: local, visitante, or empate.
  2. All match_votes rows for the match where vote equals the actual result have points_awarded set to 1.
  3. For each user who predicted correctly, the user_points table is upserted — adding 1 point to their running total for the season (ON CONFLICT DO UPDATE SET points = points + 1).
Users who predicted incorrectly, or who did not vote, receive no points and no penalty.
Points are per-season. A user’s total resets when a new active season begins.

Voting stats on every match

Voting stats are included in every match list and match detail response, regardless of the match’s status. This means the UI always has the data it needs without an extra request.
"votingStats": {
  "local": 43,
  "draw": 18,
  "away": 27,
  "total": 88
}
When the authenticated user has already voted on a match, their current selection is returned in the userVote field so the UI can highlight the active choice.
"userVote": "local"

Leaderboard

The prediction leaderboard is available to all visitors — no login required.
GET /statistics/user-ranking?season_id=3
The endpoint returns the top 10 users ranked by points descending, with ties broken alphabetically by username.
[
  { "id": 5,  "username": "futsal_fan_01", "points": 24 },
  { "id": 12, "username": "predictor_pro", "points": 21 },
  { "id": 8,  "username": "golazo",        "points": 19 }
]

Personal prediction history

Logged-in users can review their own voting record for the season.
GET /statistics/user-stats
Authorization: Bearer <token>
The response includes the user’s total points, their global rank, and their last 20 predictions for the season — ordered by match date descending.
{
  "totalPoints": 19,
  "globalRank": 3,
  "history": [
    {
      "match_id": 15,
      "home_team_name": "Rayo Vallecano FS",
      "away_team_name": "Atlético Pinto FS",
      "home_goals": 3,
      "away_goals": 2,
      "status": "finalizado",
      "my_prediction": "local",
      "real_result": "local",
      "points_awarded": 1
    },
    {
      "match_id": 16,
      "home_team_name": "CD Fuenlabrada FS",
      "away_team_name": "FC Alcobendas",
      "home_goals": 5,
      "away_goals": 1,
      "status": "finalizado",
      "my_prediction": "empate",
      "real_result": "local",
      "points_awarded": 0
    },
    {
      "match_id": 18,
      "home_team_name": "SD Leganés FS",
      "away_team_name": "UD Getafe FS",
      "home_goals": null,
      "away_goals": null,
      "status": "pendiente",
      "my_prediction": "visitante",
      "real_result": "pendiente",
      "points_awarded": null
    }
  ]
}
Pending matches appear in the history with real_result: "pendiente" and points_awarded: null. Points are applied retroactively once the match is finalized.

Build docs developers (and LLMs) love