Matches are the central entity of every FutsalManager season. An admin schedules each fixture, a referee locks it when play begins and records every goal, card, and penalty shootout kick, and the system updates scores, standings, and porra points the moment the referee finalises the result. This page explains the full data model, the match lifecycle, real-time update mechanics, and the fan voting feature.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 data model
Every match record carries the following fields:| Field | Type | Description |
|---|---|---|
date | timestamp | Scheduled kick-off date and time |
homeTeam / awayTeam | object | Team ID, name, and logo URL |
homeTeamPlaceholder / awayTeamPlaceholder | string | Label used for knockout slots before teams are known (e.g. “Winner Group A”) |
field | object | Venue ID, name, and location |
group | string | Group name when the match is in the group stage |
phase | string | Competition phase (see below) |
status | string | Current lifecycle state (see below) |
homeGoals / awayGoals | integer | Regulation-time score |
homePenaltyGoals / awayPenaltyGoals | integer | Penalty shootout goals (knockout only) |
observations | string | Referee notes added at or after the final whistle |
Competition phases
fase_de_grupos — Group stage
fase_de_grupos — Group stage
The default phase. Matches are assigned to a group and count toward the league table. Results update
team_stats and feed the standings algorithm.octavos — Round of 16
octavos — Round of 16
First knockout round. Teams may be set by placeholder until group stage results determine qualifiers.
cuartos — Quarter-finals
cuartos — Quarter-finals
Second knockout round.
semis — Semi-finals
semis — Semi-finals
Third knockout round.
final — Final
final — Final
The championship match. Penalty shootout fields are available for all knockout-phase matches that end level.
Only
fase_de_grupos matches affect the standings table. Knockout matches do not contribute points to team_stats.Match lifecycle
A match moves through three statuses. Only referees and admins can advance the status viaPUT /matches/:id/status.
pendiente — Scheduled
The match exists in the calendar. Fans can browse the fixture and cast a porra vote. The score fields are
null. No events can be recorded yet.en_curso — In progress
A referee has locked the match (
POST /matches/:id/lock) and set status to en_curso. Goals, cards, and penalty shootout kicks are now accepted via POST /matches/:id/events. Voting is closed — the API rejects new vote submissions while the match is live.finalizado — Finished
The referee calls
PUT /matches/:id/finish. The backend runs a single database transaction that:- Sets
status = 'finalizado' - Increments
matches_playedfor every player who appeared in an event - Upserts
team_stats(points, won, drawn, lost, goals for/against) - Awards porra points to users who predicted the correct result
- Releases the referee lock
invalidateMatchCache() and updateGlobalLastActivity() are called so clients polling the last-activity endpoint know to refresh.Match events
Referees submit individual events during a live match. Each event is tied to a player and a side (home or away).
| Event type | Effect |
|---|---|
gol | Increments home_goals or away_goals on the match; increments goals in player_stats |
tarjeta_amarilla | Increments yellow_cards in player_stats and team_stats |
tarjeta_roja | Increments red_cards in player_stats and team_stats |
penalti_tanda_marcado | Increments home_penalty_goals or away_penalty_goals; does not affect individual stats |
penalti_tanda_fallado | Recorded for audit purposes only; no stat counters change |
Real-time updates
The Angular frontend pollsGET /matches/last-activity to detect changes without holding a WebSocket connection open. The backend stores a Unix timestamp in Redis and updates it via updateGlobalLastActivity() after every event write, status change, or match finalisation. When the client sees a newer timestamp it re-fetches the affected match or list.
Match list and detail responses are cached in Redis with a configurable TTL (CACHE_TTL_MS). invalidateMatchCache(id) deletes the keys for the affected match and the global list so the next request hits PostgreSQL and repopulates the cache.
Thundering herd protection
Concurrent requests for the same uncached resource are deduplicated using an in-memoryinflightRequests map. When the first request for a cache key starts a database query it stores the resulting Promise in inflightRequests[cacheKey]. All subsequent concurrent requests for the same key await that same Promise instead of firing duplicate queries. The Promise is removed from the map in the finally block regardless of success or failure.
matches.js
Match locking
Before a referee can record events, they must acquire a lock withPOST /matches/:id/lock. The lock is stored as locked_by (user ID) and locked_at (timestamp) on the match row. A lock expires automatically after 2 minutes of inactivity and can be acquired by another referee. Admins can force-release any lock with POST /matches/:id/unlock?force=true.
The verifyMatchLock middleware enforces the lock on all event and status mutation routes — requests from a user who does not hold the current lock are rejected with 403.
Match voting (porra)
Authenticated users can predict the outcome of anypendiente match before it starts. The three valid votes are local (home win), empate (draw), and visitante (away win).
Cast a vote
POST /matches/:id/vote with { "vote": "local" | "empate" | "visitante" }. A second vote from the same user overwrites the first via ON CONFLICT DO UPDATE.Points awarded
When a match finalises, users whose vote matches the actual result receive 1 point added to
users.points.Leaderboard
The global user ranking is exposed at
GET /statistics/user-ranking, sorted by points DESC.Votes are only accepted while
status = 'pendiente'. The API returns 400 if you attempt to vote on a match that has already started or finished, or on a fixture where either team is not yet confirmed.Related pages
List and filter matches
Query parameters, response shape, and season filtering for the match calendar.
Match detail endpoint
Full match object including events list and voting statistics.
Referee management endpoints
Lock, unlock, add events, change status, and finalize matches.
League standings
How finalized match results feed into the group-stage table.