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 calculates group standings automatically the moment a match is finalized. There is no manual recalculation step — standings, tie-breaking, and the best fourth-place ranking are all derived on demand from the team_stats table and cached for one hour. Results are accurate and consistent across every client as soon as the referee confirms the final whistle.

How standings are calculated

Each team earns points from their group-stage results using the standard three-point system:
  • Win — 3 points
  • Draw — 1 point
  • Loss — 0 points
When two or more teams are level on points, the following criteria are applied in order until the tie is broken.

Tie-breaking order

  1. Head-to-head points — Points earned only in the matches played between the tied teams (the mini-league). Applied first for all ties.
  2. Overall goal difference — Goals scored minus goals conceded across all group matches.
  3. Goals scored — Total goals for across all group matches.
  4. Fair-play points — Calculated as yellow_cards + (red_cards × 3). The team with the lower score ranks higher.
  5. Goals against — Fewer goals conceded is better.
If all five criteria are equal, teams are ranked by their database ID (deterministic but arbitrary).
When exactly two teams are tied on points, head-to-head results come first, followed by overall goal difference, goals scored, fair-play points, and goals against. When three or more teams are tied, the mini-league points from matches among all the tied teams are compared first, and the remaining criteria apply to the group as a whole.

Best fourth-place system

Tournament brackets that advance four teams from each group require a way to compare fourth-placed teams across groups that may have different sizes. FutsalLeague Manager handles this automatically. When a group has more teams than the smallest group in the competition, the fourth-place team’s statistics are adjusted before the cross-group comparison:
  1. The result between the fourth-place team and the bottom-placed team in their group is identified.
  2. That match’s goals, points, and games-played contribution are subtracted from the fourth-place team’s totals, producing an adjusted row with the same number of matches as fourth-place teams from smaller groups.
  3. All adjusted fourth-place rows are then sorted using the same tie-breaking criteria described above.
The team with the best adjusted record becomes bestFourthId and advances alongside the three automatic qualifiers from each group.

Season filtering

Pass season_id as a query parameter to retrieve standings for any historical season.
GET /standings?season_id=3
If season_id is omitted, the endpoint uses the currently active season (is_active = true).

Redis caching

Standings are cached in Redis for 1 hour under the key standings (or standings:<season_id> for historical queries). The cache is invalidated automatically when PUT /matches/:id/finish is called, so the next request after a finalization always reflects the updated table.

Response structure

{
  "groups": {
    "Grupo A": [
      {
        "id": 3,
        "team_name": "Rayo Vallecano FS",
        "team_logo": "https://res.cloudinary.com/example/rayo.webp",
        "points": 12,
        "played": 5,
        "won": 4,
        "drawn": 0,
        "lost": 1,
        "goals_for": 18,
        "goals_against": 7,
        "goal_difference": 11,
        "yellow_cards": 3,
        "red_cards": 0
      },
      {
        "id": 7,
        "team_name": "Atlético Pinto FS",
        "team_logo": "https://res.cloudinary.com/example/atletico.webp",
        "points": 9,
        "played": 5,
        "won": 3,
        "drawn": 0,
        "lost": 2,
        "goals_for": 11,
        "goals_against": 9,
        "goal_difference": 2,
        "yellow_cards": 6,
        "red_cards": 1
      }
    ],
    "Grupo B": [
      {
        "id": 11,
        "team_name": "CD Fuenlabrada FS",
        "team_logo": "https://res.cloudinary.com/example/fuenlabrada.webp",
        "points": 15,
        "played": 5,
        "won": 5,
        "drawn": 0,
        "lost": 0,
        "goals_for": 22,
        "goals_against": 4,
        "goal_difference": 18,
        "yellow_cards": 1,
        "red_cards": 0
      }
    ]
  },
  "bestFourthId": 14,
  "fourthPlacesRanking": [
    {
      "id": 14,
      "team_name": "UD Getafe FS",
      "points": 4,
      "played": 4,
      "goals_for": 7,
      "goals_against": 6,
      "goal_difference": 1
    },
    {
      "id": 21,
      "team_name": "SD Leganés FS",
      "points": 3,
      "played": 4,
      "goals_for": 5,
      "goals_against": 5,
      "goal_difference": 0
    }
  ]
}
Teams that have not yet played any group-stage matches do not appear in team_stats and will be absent from the standings response until their first match is finalized.

Build docs developers (and LLMs) love