Skip to main content

POST /v2/match — Actualizar partido (v2)

Lambda: MatchPostV2
Handler: STACK_TORNEOS/services/match/post_v2.py
Autenticación: JWT Cognito (Authorization: Bearer <token>)
CORS: Habilitado (MatchV2Options en /v2/match OPTIONS)


Query Parameters

ParámetroTipoRequeridoDescripción
matchIdstringID del fixture a actualizar

Descripción

Permite actualizar un partido de forma parcial o total. El front puede enviar sólo los campos que cambiaron, sin necesidad de reenviar el estado completo.
Todos los casos de entrada son combinables en un mismo body (excepto donde se indique).


Casos de entrada


Caso 1 — Actualizar UN solo jugador

Ideal para registrar un gol, tarjeta o sanción en tiempo real sin enviar todo el equipo.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"team_id": 15358,
"player": {
"id": 34038,
"goal": 2,
"yellow_card": 1,
"red_card": 0,
"assistence": 1,
"position": 9,
"penalty_round": 0,
"penalty_status": "no-penalty"
}
}

Campos del jugador:

CampoTipoDescripción
idintegerID del userprofile. Si no viene, se resuelve por first_name + last_name
first_namestringRequerido solo si no hay id
last_namestringRequerido solo si no hay id
goalintegerGoles marcados por el jugador
assistenceintegerAsistencias
yellow_cardintegerTarjetas amarillas
red_cardintegerTarjetas rojas
positionintegerDorsal o posición
penalty_roundintegerGoles en definición por penales
penalty_statusstring"goal" | "no-penalty" | "miss"

⚠️ El total de goles del equipo se recalcula automáticamente desde la DB al hacer el upsert. No es necesario enviar los datos de los demás jugadores.


Caso 2 — Actualizar N jugadores de un equipo

Cuando se cierra la carga de stats de un equipo completo (sin necesidad de enviar el otro equipo).

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"team_id": 15358,
"players": [
{ "id": 34038, "goal": 2, "yellow_card": 0, "red_card": 0, "assistence": 1, "position": 9 },
{ "id": 34039, "goal": 1, "yellow_card": 1, "red_card": 0, "assistence": 0, "position": 7 },
{ "id": 34059, "goal": 0, "yellow_card": 0, "red_card": 1, "assistence": 0, "position": 4 }
]
}

Caso 3 — Actualizar ambos equipos (compatibilidad v1)

Equivalente al contrato original. Soportado para no romper integraciones existentes.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"team_1": {
"team_id": 15358,
"players": [
{ "id": 34038, "goal": 2, "assistence": 1, "yellow_card": 0, "red_card": 0, "position": 9 },
{ "id": 34039, "goal": 0, "assistence": 0, "yellow_card": 1, "red_card": 0, "position": 7 }
]
},
"team_2": {
"team_id": 15350,
"players": [
{ "id": 33761, "goal": 1, "assistence": 0, "yellow_card": 0, "red_card": 0, "position": 11 }
]
}
}

Caso 4 — Partido con definición por penales

Se activa cuando al menos un jugador tiene penalty_round > 0.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"team_1": {
"team_id": 15358,
"players": [
{ "id": 34038, "goal": 1, "penalty_round": 1, "penalty_status": "goal" },
{ "id": 34039, "goal": 0, "penalty_round": 0, "penalty_status": "miss" }
]
},
"team_2": {
"team_id": 15350,
"players": [
{ "id": 33761, "goal": 1, "penalty_round": 1, "penalty_status": "goal" },
{ "id": 34161, "goal": 0, "penalty_round": 1, "penalty_status": "miss" }
]
}
}

El campo penalty_round = TRUE y los totales team_1_penalty / team_2_penalty se guardan en el fixture automáticamente.


Caso 5 — Jugador sin cuenta registrada (manual)

Cuando el jugador no tiene cuenta en la plataforma, se crea automáticamente.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"team_id": 15358,
"player": {
"first_name": "Diego",
"last_name": "García",
"goal": 1,
"yellow_card": 0,
"red_card": 0,
"assistence": 0,
"position": 10
}
}

Si no existe en userprofile por first_name + last_name, se crea el perfil en PostgreSQL, se agrega a DynamoDB (Profiles{ENV}) y se vincula al equipo (team_userprofile).


Caso 6 — Actualizar firmas y foto del árbitro

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"signature_1": "data:image/png;base64,iVBORw0KGgoAAAANS...",
"signature_2": "data:image/png;base64,iVBORw0KGgoAAAANS...",
"referee_image": "data:image/png;base64,iVBORw0KGgoAAAANS..."
}
CampoDescripción
signature_1Firma del capitán del equipo 1 (base64)
signature_2Firma del capitán del equipo 2 (base64)
referee_imageFoto/firma del árbitro (base64)

Caso 7 — Actualizar comentario y/o suspensión

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"comment": "Partido suspendido por lluvia",
"suspended": true
}
CampoTipoDescripción
commentstringComentario libre sobre el partido
suspendedbooleantrue si el partido fue suspendido

Caso 8 — Cerrar partido (finished)

Marca el partido como jugado. Dispara la lógica de avance de fase en playoffs.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"finished": true
}

Acciones que se ejecutan:

  1. UPDATE fixture SET playered = TRUE, date_finished = now() (idempotente: solo si aún no estaba jugado)
  2. Si el torneo estaba en preparation → pasa a in_progress
  3. Verifica si todos los partidos de la fase están jugados → actualiza fase y notificaciones de avance de playoffs

Caso 9 — Combinación de campos en un solo request

Se pueden combinar campos simples con datos de jugadores y el cierre en un mismo request.

POST /v2/match?matchId=35071
Authorization: Bearer <token>

{
"comment": "Gran partido",
"signature_1": "data:image/png;base64,iVBORw0...",
"team_id": 15358,
"players": [
{ "id": 34038, "goal": 2, "assistence": 1, "yellow_card": 0, "red_card": 0, "position": 9 },
{ "id": 34039, "goal": 0, "assistence": 0, "yellow_card": 1, "red_card": 0, "position": 7 }
],
"finished": true
}

Respuesta exitosa

HTTP 201

{
"error": false,
"data": {
"match": 35071,
"suspended": false,
"comment": "Gran partido",
"footballfield": "Cancha 2",
"id_league": null,
"tournament_name": "Liga Verano 2026",
"tournament_id": 3639,
"playoff_name": "Semi-final",
"tournament_friendly": false,
"playered": true,
"signature_1": "https://cdn.statics.dev.mas10.ar/fixture_signature_1/fileCXYV27MTJK.png",
"signature_2": "https://cdn.statics.dev.mas10.ar/fixture_signature_2/fileID082DGG61.png",
"referee_image": "",
"date_played": "2025-11-23 00:00",
"phase": "Semi-final",
"playoff": true,
"penalty_round": false,
"team_1_goal": 3,
"team_2_goal": 1,
"team_1_penalty": 0,
"team_2_penalty": 0,
"team_1": {
"team_id": 15358,
"username": "equipo2",
"team_name": "ASTON BIRRA",
"avatar": "https://cdn.statics.dev.mas10.ar/team_avatar/file_team1.png",
"avatar_50": "https://cdn.statics.dev.mas10.ar/team_avatar/file_team1_50x75.png",
"avatar_100": "https://cdn.statics.dev.mas10.ar/team_avatar/file_team1_100x100.png",
"goals": 3,
"topic_name": "team-15358Equipo2",
"players": [
{
"id": 34038,
"username": null,
"first_name": "Diego",
"last_name": "Teru",
"full_name": "Teru Diego",
"goal": 2,
"assistence": 1,
"yellow_card": 0,
"red_card": 0,
"position": 9,
"avatar": null,
"avatar_50": null,
"avatar_100": null,
"medicalreport": null,
"blocked_user": false,
"motive_blocked": null,
"penalty_round": null,
"penalty_status": null,
"validated": null,
"rejection": null,
"reason_rejection": null,
"id_league": null
}
]
},
"team_2": {
"team_id": 15350,
"username": "loscuatrodesiempre",
"team_name": "LOS CUATRO DE SIEMPRE",
"avatar": null,
"avatar_50": null,
"avatar_100": null,
"goal": 1,
"topic_name": "team-15350CUATROPERFILLIRICUS665",
"players": [
{
"id": 33761,
"username": null,
"first_name": "MANUAL",
"last_name": "LOQUESETECANTE II",
"full_name": "LOQUESETECANTE II MANUAL",
"goal": 1,
"assistence": 0,
"yellow_card": 0,
"red_card": 0,
"position": 0,
"avatar": null,
"avatar_50": null,
"avatar_100": null,
"medicalreport": null,
"blocked_user": false,
"motive_blocked": null,
"penalty_round": null,
"penalty_status": null,
"validated": null,
"rejection": null,
"reason_rejection": null,
"id_league": null
}
]
}
}
}

Respuestas de error

401 — Token inválido o expirado

{ "error": true, "message": "User not found" }

500 — Error interno (DB, DynamoDB, etc.)

El handler captura excepciones individuales por sección (jugadores, Postgres, DynamoDB) y las loguea sin abortar el request completo. Solo falla con 500 si el error es en la capa de autenticación o de respuesta final.


Headers de respuesta CORS

Incluidos automáticamente por locales.get_response_standar() y locales.get_response_options():

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization,X-Language
Access-Control-Allow-Methods: OPTIONS,POST,GET,PUT,DELETE

Notas de implementación

AspectoComportamiento
Idempotenciafinished=true es idempotente: WHERE playered=FALSE evita doble procesamiento
Totales de golesSe recalculan con SUM(goal) desde fixture_info en DB, no desde el payload
Jugador sin cuentaSi no trae id, se resuelve o crea por first_name + last_name
Persistencia dualToda actualización va a PostgreSQL (fixture, fixture_info) y DynamoDB (cache)
NotasIA / PushEliminados del flujo síncrono en v2 (~240ms de mejora en finished=true)
Timeout15 segundos (vs 30s en v1)

Comparativa de performance (warm)

Casov1v2
Firma sola~208ms~60ms
finished: true~579ms~130-160ms
Comentario solo~24ms~15ms
Un jugador (gol)~200ms~50ms