{"openapi":"3.1.0","info":{"title":"API del Torneo Volleyball 2026","description":"\nDatos reales del **Torneo Volleyball 2026**: la liga (7 equipos, 21 partidos,\n63 sets) y la fase final con su podio, servidos como API REST de solo lectura,\nmás un endpoint que recalcula la tabla con resultados hipotéticos.\n\n- La tabla se calcula en cada petición a partir de los parciales de cada set;\n  no hay posiciones guardadas que puedan desincronizarse.\n- Los errores siguen el formato **RFC 9457** (`application/problem+json`).\n- Las respuestas llevan `ETag`: repetir la petición con `If-None-Match`\n  devuelve `304` sin cuerpo.\n- Cada respuesta lleva `X-Request-ID` (o reutiliza el que envíes).\n\nLos datos se exportaron de la base de producción sin jugadores ni ningún otro\ndato personal.\n","contact":{"name":"David Ameth Martínez Sánchez","url":"https://davidameth.dev/"},"license":{"name":"MIT","identifier":"MIT"},"version":"1.0.0"},"paths":{"/v1/teams":{"get":{"tags":["equipos"],"summary":"Listar equipos","description":"Los equipos, en orden de siembra.","operationId":"list_teams_v1_teams_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Team"},"type":"array","title":"Response List Teams V1 Teams Get"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."}}}},"/v1/teams/{team_id}":{"get":{"tags":["equipos"],"summary":"Un equipo","operationId":"get_team_v1_teams__team_id__get","parameters":[{"name":"team_id","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9-]{1,60}$","examples":["aguilas-carmesi"],"title":"Team Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"500":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Internal Server Error"},"404":{"description":"No existe.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/jornadas":{"get":{"tags":["jornadas"],"summary":"Listar jornadas","operationId":"list_jornadas_v1_jornadas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Jornada"},"type":"array","title":"Response List Jornadas V1 Jornadas Get"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."}}}},"/v1/jornadas/{number}":{"get":{"tags":["jornadas"],"summary":"Una jornada","operationId":"get_jornada_v1_jornadas__number__get","parameters":[{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jornada"}}}},"500":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Internal Server Error"},"404":{"description":"No existe.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/matches":{"get":{"tags":["partidos"],"summary":"Buscar partidos","description":"Partidos en orden de jornada y posición.\n\nUn filtro por un equipo o una jornada que no existen devuelve una lista\nvacía, no un 404: es una búsqueda sin resultados.","operationId":"list_matches_v1_matches_get","parameters":[{"name":"jornada","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Solo esa jornada.","title":"Jornada"},"description":"Solo esa jornada."},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[a-z0-9-]{1,60}$","examples":["aguilas-carmesi"]},{"type":"null"}],"description":"Partidos donde juega ese equipo.","title":"Team"},"description":"Partidos donde juega ese equipo."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["pending","in_progress","completed"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchPage"}}}},"500":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Internal Server Error"},"400":{"description":"La petición no se pudo leer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."}}}},"/v1/matches/{match_id}":{"get":{"tags":["partidos"],"summary":"Un partido","operationId":"get_match_v1_matches__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","title":"Match Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Match"}}}},"500":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Internal Server Error"},"404":{"description":"No existe.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/standings":{"get":{"tags":["tabla"],"summary":"Tabla de posiciones","description":"Clasificación calculada en el momento a partir de los parciales de cada set.","operationId":"get_standings_v1_standings_get","parameters":[{"name":"through_jornada","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Tabla tal como quedó al cerrar esa jornada. Sin él, la actual.","title":"Through Jornada"},"description":"Tabla tal como quedó al cerrar esa jornada. Sin él, la actual."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Standings"}}}},"500":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"Internal Server Error"},"404":{"description":"No existe.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"400":{"description":"La petición no se pudo leer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."}}}},"/v1/standings/simulate":{"post":{"tags":["tabla"],"summary":"Simular la tabla","description":"Recalcula la tabla con resultados hipotéticos. No guarda nada.\n\nCada resultado pasa por las mismas reglas que los reales: 3 sets, sets a 25\ncon 2 de ventaja. Un marcador imposible devuelve `422 invalid-score`.","operationId":"simulate_standings_v1_standings_simulate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Simulation"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Standings"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"400":{"description":"La petición no se pudo leer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/final-phase":{"get":{"tags":["fase final"],"summary":"Fase final y podio","description":"Semifinales, partido por el 3.er puesto, final y el podio que sale de ellos.\n\nAl cargar los datos se comprueba que el cuadro cuadre con la liga: los\nsemifinalistas son los cuatro primeros de la tabla, por el 3.er puesto\njuegan los perdedores de semifinal y la final, los ganadores. No suma a\nla tabla de la liga.","operationId":"get_final_phase_v1_final_phase_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinalPhase"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"304":{"description":"Sin cambios desde el `ETag` enviado en `If-None-Match`."}}}},"/health":{"get":{"tags":["sistema"],"summary":"Salud del servicio","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/problems/{slug}":{"get":{"tags":["sistema"],"summary":"Describir un tipo de error","description":"Documenta cada `type` de error que puede devolver la API.","operationId":"describe_problem_problems__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Describe Problem Problems  Slug  Get"}}}},"404":{"description":"No existe.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"La petición no cumple el contrato.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"schemas":{"FinalMatch":{"properties":{"id":{"type":"string","title":"Id","description":"`ff-p{posición}`","examples":["ff-p4"]},"round":{"type":"string","enum":["semifinal_1","semifinal_2","third_place","final"],"title":"Round"},"round_name":{"type":"string","title":"Round Name","examples":["Final"]},"home":{"$ref":"#/components/schemas/TeamRef"},"away":{"$ref":"#/components/schemas/TeamRef"},"winner":{"$ref":"#/components/schemas/TeamRef"}},"additionalProperties":false,"type":"object","required":["id","round","round_name","home","away","winner"],"title":"FinalMatch"},"FinalPhase":{"properties":{"date":{"type":"string","title":"Date","examples":["2026-09-26"]},"city":{"type":"string","title":"City","examples":["Churube"]},"score_recorded":{"type":"boolean","title":"Score Recorded","description":"`false`: la fase final se jugó sin anotar los puntos; de cada partido solo se sabe quién ganó. Por eso no hay sets ni marcador."},"matches":{"items":{"$ref":"#/components/schemas/FinalMatch"},"type":"array","title":"Matches"},"podium":{"$ref":"#/components/schemas/Podium","description":"Sale de la final y del partido por el 3.er puesto, no de la tabla."}},"additionalProperties":false,"type":"object","required":["date","city","score_recorded","matches","podium"],"title":"FinalPhase"},"Health":{"properties":{"status":{"type":"string","const":"ok","title":"Status"},"version":{"type":"string","title":"Version"},"matches_loaded":{"type":"integer","title":"Matches Loaded"}},"additionalProperties":false,"type":"object","required":["status","version","matches_loaded"],"title":"Health"},"Jornada":{"properties":{"number":{"type":"integer","minimum":1.0,"title":"Number","examples":[3]},"name":{"type":"string","title":"Name","examples":["Jornada 3"]},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Fecha ISO 8601 (AAAA-MM-DD).","examples":["2026-07-11"]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","examples":["San Carlos"]},"status":{"type":"string","enum":["pending","in_progress","completed"],"title":"Status","description":"Se deriva de sus partidos."}},"additionalProperties":false,"type":"object","required":["number","name","date","city","status"],"title":"Jornada"},"Match":{"properties":{"id":{"type":"string","title":"Id","description":"`j{jornada}-p{posición}`","examples":["j3-p2"]},"jornada":{"type":"integer","title":"Jornada"},"position":{"type":"integer","title":"Position","description":"Orden dentro de la jornada."},"home":{"$ref":"#/components/schemas/TeamRef"},"away":{"$ref":"#/components/schemas/TeamRef"},"status":{"type":"string","enum":["pending","in_progress","completed"],"title":"Status"},"sets":{"items":{"$ref":"#/components/schemas/SetScore"},"type":"array","title":"Sets"},"result":{"anyOf":[{"$ref":"#/components/schemas/Result"},{"type":"null"}],"description":"Solo en partidos terminados."}},"additionalProperties":false,"type":"object","required":["id","jornada","position","home","away","status","sets","result"],"title":"Match"},"MatchPage":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Match"},"type":"array","title":"Data"},"total":{"type":"integer","title":"Total","description":"Partidos que cumplen el filtro, sin paginar."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["data","total","limit","offset"],"title":"MatchPage"},"Podium":{"properties":{"champion":{"$ref":"#/components/schemas/TeamRef"},"runner_up":{"$ref":"#/components/schemas/TeamRef"},"third":{"$ref":"#/components/schemas/TeamRef"},"fourth":{"$ref":"#/components/schemas/TeamRef"}},"additionalProperties":false,"type":"object","required":["champion","runner_up","third","fourth"],"title":"Podium"},"Problem":{"additionalProperties":true,"description":"Error en formato RFC 9457 (application/problem+json).","properties":{"type":{"examples":["https://api.davidameth.dev/problems/not-found"],"title":"Type","type":"string"},"title":{"examples":["Recurso no encontrado"],"title":"Title","type":"string"},"status":{"examples":[404],"title":"Status","type":"integer"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"examples":["No existe el equipo 'aguilas'."],"title":"Detail"},"instance":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"examples":["/v1/teams/aguilas"],"title":"Instance"}},"required":["type","title","status"],"title":"Problem","type":"object"},"Ratio":{"properties":{"won":{"type":"integer","title":"Won"},"lost":{"type":"integer","title":"Lost"},"ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ratio","description":"won / lost con 3 decimales. `null` si no perdió nada y ganó algo (infinito)."}},"additionalProperties":false,"type":"object","required":["won","lost","ratio"],"title":"Ratio"},"Result":{"properties":{"sets_home":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Sets Home"},"sets_away":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Sets Away"},"winner":{"$ref":"#/components/schemas/TeamRef"}},"additionalProperties":false,"type":"object","required":["sets_home","sets_away","winner"],"title":"Result"},"SetScore":{"properties":{"home":{"type":"integer","maximum":99.0,"minimum":0.0,"title":"Home","examples":[25]},"away":{"type":"integer","maximum":99.0,"minimum":0.0,"title":"Away","examples":[22]}},"additionalProperties":false,"type":"object","required":["home","away"],"title":"SetScore"},"SimResult":{"properties":{"home":{"type":"string","pattern":"^[a-z0-9-]{1,60}$","title":"Home","examples":["aguilas-carmesi"]},"away":{"type":"string","pattern":"^[a-z0-9-]{1,60}$","title":"Away","examples":["aguilas-carmesi"]},"sets":{"items":{"$ref":"#/components/schemas/SetScore"},"type":"array","maxItems":3,"minItems":3,"title":"Sets"}},"additionalProperties":false,"type":"object","required":["home","away","sets"],"title":"SimResult"},"Simulation":{"properties":{"results":{"items":{"$ref":"#/components/schemas/SimResult"},"type":"array","maxItems":200,"title":"Results"},"include_season":{"type":"boolean","title":"Include Season","description":"Si es `true`, los resultados se suman a los reales de la temporada.","default":true}},"additionalProperties":false,"type":"object","required":["results"],"title":"Simulation","examples":[{"include_season":true,"results":[{"away":"aguilas-carmesi","home":"shalom-2","sets":[{"away":20,"home":25},{"away":24,"home":26},{"away":18,"home":25}]}]}]},"StandingRow":{"properties":{"position":{"type":"integer","title":"Position","description":"Dos equipos empatados en todo comparten posición."},"team":{"$ref":"#/components/schemas/TeamRef"},"played":{"type":"integer","title":"Played"},"won":{"type":"integer","title":"Won"},"lost":{"type":"integer","title":"Lost"},"points":{"type":"integer","title":"Points","description":"Puntos FIVB: 3-0 = 3, 2-1 = 2, 1-2 = 1, 0-3 = 0."},"sets":{"$ref":"#/components/schemas/Ratio"},"rally_points":{"$ref":"#/components/schemas/Ratio","description":"Puntos anotados y recibidos en los sets."},"tiebreak":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tiebreak","description":"Por qué va delante o detrás de un equipo con los mismos puntos.","examples":["Empate a 11 pts; desempata ratio de sets"]}},"additionalProperties":false,"type":"object","required":["position","team","played","won","lost","points","sets","rally_points","tiebreak"],"title":"StandingRow"},"Standings":{"properties":{"through_jornada":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Through Jornada","description":"Última jornada contada; `null` si ninguna."},"matches_counted":{"type":"integer","title":"Matches Counted"},"rows":{"items":{"$ref":"#/components/schemas/StandingRow"},"type":"array","title":"Rows"}},"additionalProperties":false,"type":"object","required":["through_jornada","matches_counted","rows"],"title":"Standings"},"Team":{"properties":{"id":{"type":"string","pattern":"^[a-z0-9-]{1,60}$","title":"Id","examples":["aguilas-carmesi"]},"name":{"type":"string","title":"Name","examples":["Águilas Carmesí"]},"seed":{"type":"integer","title":"Seed","description":"Orden de siembra con que empezó la temporada (1 = cabeza)."}},"additionalProperties":false,"type":"object","required":["id","name","seed"],"title":"Team"},"TeamRef":{"properties":{"id":{"type":"string","pattern":"^[a-z0-9-]{1,60}$","title":"Id","examples":["aguilas-carmesi"]},"name":{"type":"string","title":"Name","examples":["Águilas Carmesí"]}},"additionalProperties":false,"type":"object","required":["id","name"],"title":"TeamRef"}}},"tags":[{"name":"equipos","description":"Los 7 equipos de la liga."},{"name":"jornadas","description":"Fechas de juego y su estado."},{"name":"partidos","description":"Resultados set a set, con filtros y paginación."},{"name":"tabla","description":"Clasificación FIVB con desempates explicados."},{"name":"fase final","description":"Semifinales, 3.er puesto, final y podio."},{"name":"sistema","description":"Salud del servicio y catálogo de errores."}]}