π« [BUG] 500 INTERNAL_ERROR al reanudar torneo pausado β POST /scheduling/{id}/resume
SEVERIDAD: Alta β el torneo queda atascado en 'paused' sin poder reanudarse desde la UI.
ENDPOINT: POST /scheduling/{tournamentId}/resume (body vacΓo)
TORNEO: 65d1ecb9-78b2-4f45-83e8-4f9388daec86
TRACE ID: 3328f374-7436-4cff-92c5-e1ceeb18c0dd
β οΈ FRAMING: el backend es NestJS detrΓ‘s de Traefik (NO n8n; el dominio n8n.srvβ¦ se
comparte por routing). Logs en el contenedor tennis-management-backend (pino/JSON).
ββββββββββββββββββββββββββββββββββββββββββββββ
ANΓLISIS (verificado en develop, cΓ³digo actual)
ββββββββββββββββββββββββββββββββββββββββββββββ
El 500 es code:"INTERNAL_ERROR" = una EXCEPCIΓN CRUDA no controlada, y estΓ‘ en el
REFLOW de resume. Descartes con evidencia:
- pausedAt SΓ se persiste al pausar (scheduling.service.ts:2233); y resume ya valida
!paused_at con AppError('TOURNAMENT_PAUSE_INCONSISTENT',500) (2289-2294) β cΓ³digo
propio, NO el genΓ©rico. β No es "pausedAt nulo".
- Idempotencia ya cubierta: status != 'paused' β 409 TOURNAMENT_NOT_PAUSED (2283-2288).
- GlobalExceptionFilter mapea AppErrorβcΓ³digo; P2002β409 UNIQUE_CONSTRAINT_VIOLATION;
P2003β400; P2023β400; y TODO lo demΓ‘s β INTERNAL_ERROR 500 (filter:238).
β Por descarte, el error es un Prisma no mapeado (P2010 raw query failed / P2025β¦) o
un error JS puro (Invalid Date, null deref), dentro del bloque if (delayMinutes>0)
(scheduling.service.ts:2305-2453).
- resume es 1 transacciΓ³n atΓ³mica β al reventar hace ROLLBACK β el torneo queda 'paused'.
"Una vez funcionΓ³, ahora no" = el 1er resume commiteΓ³ y cambiΓ³ el estado de los
partidos; ese estado nuevo revienta el reflow. ES DEPENDIENTE DEL ESTADO (no del request).
CANDIDATOS (confirmar con el stack):
a) Escrituras del reflow (2359-2387): match_assignments.create con
scheduled_start_at:new Date(next.startsAt). Si el planner devuelve next con fecha
invΓ‘lida/undefined en algΓΊn borde β Prisma error no mapeado β 500.
(Doble-reserva de slot darΓa 409 UNIQUE_CONSTRAINT_VIOLATION, NO 500 β no es eso.)
b) buildBulkReschedulePlan (2324) + helpers con $queryRaw (afectados/candidatos/regrid):
raw query que falla por el estado β P2010 β INTERNAL_ERROR.
c) NO perseguir el Γ‘ngulo "zona horaria/hoy en UTC": fallarΓa siempre, no es
state-dependent. todayStart usa buildDateTimeInTimezone con la tz del venue.
ββββββββββββββββββββββββββββββββββββββββββββββ
PASO 1 (obligatorio) β CONSEGUIR EL STACK
ββββββββββββββββββββββββββββββββββββββββββββββ
El filtro loguea la excepciΓ³n COMPLETA con stack junto al traceId
(global-exception.filter.ts:222-234, campo err, nivel pino 50).
tail de errores + reproducir el 500 en la UI (el torneo estaba 'paused'):
docker logs -f --tail 0 tennis-management-backend 2>&1 | grep '"level":50'
(jq opcional): ... | jq -r '.err.stack // .err.message'
β οΈ Cada deploy hace up --force-recreate y reinicia logs β capturar traceId fresco.
El stack apunta a la lΓnea exacta.
ββββββββββββββββββββββββββββββββββββββββββββββ
FIX REQUERIDO
ββββββββββββββββββββββββββββββββββββββββββββββ
A) Causa raΓz exacta (vΓa stack) y corregir la lΓnea que revienta.
B) Blindar el reflow: envolver las escrituras del reflow; ante un fallo del reflow,
devolver un cΓ³digo manejable (p.ej. RESUME_REFLOW_FAILED, 409/422) en vez de 500;
guardar defensivamente contra next con fecha invΓ‘lida y slot_ids inexistentes.
Los partidos no colocables deben degradar a 'draft' (comportamiento ya existente),
nunca tumbar toda la operaciΓ³n con INTERNAL_ERROR.
C) NO re-implementar: pausedAt persistido + idempotencia 409 ya estΓ‘n.
CRITERIOS DE ACEPTACIΓN
- /resume responde 200 (o un cΓ³digo de dominio manejable), nunca INTERNAL_ERROR.
- Un reflow sin slots disponibles degrada a 'draft' / devuelve cΓ³digo claro.
- El torneo queda siempre en estado consistente (reanudado o pausado vΓ‘lido).
DESATASCAR EL TORNEO (ya se puede hacer manualmente; ver SQL entregado por el PM):
UPDATE tenis.tournaments
SET status_slug=COALESCE(pre_pause_status_slug,'active'), paused_at=NULL,
pre_pause_status_slug=NULL
WHERE id='65d1ecb9-78b2-4f45-83e8-4f9388daec86';
DATO PARA EL FRONT: si se define RESUME_REFLOW_FAILED (u otro code), avisar para mapearlo
a un toast (hoy ya manejan 409 β "El torneo no estΓ‘ en pausa").
π« [BUG] 500 INTERNAL_ERROR al reanudar torneo pausado β POST /scheduling/{id}/resume
SEVERIDAD: Alta β el torneo queda atascado en 'paused' sin poder reanudarse desde la UI.
ENDPOINT: POST /scheduling/{tournamentId}/resume (body vacΓo)
TORNEO: 65d1ecb9-78b2-4f45-83e8-4f9388daec86
TRACE ID: 3328f374-7436-4cff-92c5-e1ceeb18c0dd
comparte por routing). Logs en el contenedor
tennis-management-backend(pino/JSON).ββββββββββββββββββββββββββββββββββββββββββββββ
ANΓLISIS (verificado en develop, cΓ³digo actual)
ββββββββββββββββββββββββββββββββββββββββββββββ
El 500 es code:"INTERNAL_ERROR" = una EXCEPCIΓN CRUDA no controlada, y estΓ‘ en el
REFLOW de resume. Descartes con evidencia:
!paused_at con AppError('TOURNAMENT_PAUSE_INCONSISTENT',500) (2289-2294) β cΓ³digo
propio, NO el genΓ©rico. β No es "pausedAt nulo".
P2003β400; P2023β400; y TODO lo demΓ‘s β INTERNAL_ERROR 500 (filter:238).
β Por descarte, el error es un Prisma no mapeado (P2010 raw query failed / P2025β¦) o
un error JS puro (Invalid Date, null deref), dentro del bloque
if (delayMinutes>0)(scheduling.service.ts:2305-2453).
"Una vez funcionΓ³, ahora no" = el 1er resume commiteΓ³ y cambiΓ³ el estado de los
partidos; ese estado nuevo revienta el reflow. ES DEPENDIENTE DEL ESTADO (no del request).
CANDIDATOS (confirmar con el stack):
a) Escrituras del reflow (2359-2387): match_assignments.create con
scheduled_start_at:new Date(next.startsAt). Si el planner devuelve
nextcon fechainvΓ‘lida/undefined en algΓΊn borde β Prisma error no mapeado β 500.
(Doble-reserva de slot darΓa 409 UNIQUE_CONSTRAINT_VIOLATION, NO 500 β no es eso.)
b) buildBulkReschedulePlan (2324) + helpers con $queryRaw (afectados/candidatos/regrid):
raw query que falla por el estado β P2010 β INTERNAL_ERROR.
c) NO perseguir el Γ‘ngulo "zona horaria/hoy en UTC": fallarΓa siempre, no es
state-dependent. todayStart usa buildDateTimeInTimezone con la tz del venue.
ββββββββββββββββββββββββββββββββββββββββββββββ
PASO 1 (obligatorio) β CONSEGUIR EL STACK
ββββββββββββββββββββββββββββββββββββββββββββββ
El filtro loguea la excepciΓ³n COMPLETA con stack junto al traceId
(global-exception.filter.ts:222-234, campo
err, nivel pino 50).tail de errores + reproducir el 500 en la UI (el torneo estaba 'paused'):
docker logs -f --tail 0 tennis-management-backend 2>&1 | grep '"level":50'
(jq opcional): ... | jq -r '.err.stack // .err.message'
El stack apunta a la lΓnea exacta.
ββββββββββββββββββββββββββββββββββββββββββββββ
FIX REQUERIDO
ββββββββββββββββββββββββββββββββββββββββββββββ
A) Causa raΓz exacta (vΓa stack) y corregir la lΓnea que revienta.
B) Blindar el reflow: envolver las escrituras del reflow; ante un fallo del reflow,
devolver un cΓ³digo manejable (p.ej. RESUME_REFLOW_FAILED, 409/422) en vez de 500;
guardar defensivamente contra
nextcon fecha invΓ‘lida y slot_ids inexistentes.Los partidos no colocables deben degradar a 'draft' (comportamiento ya existente),
nunca tumbar toda la operaciΓ³n con INTERNAL_ERROR.
C) NO re-implementar: pausedAt persistido + idempotencia 409 ya estΓ‘n.
CRITERIOS DE ACEPTACIΓN
DESATASCAR EL TORNEO (ya se puede hacer manualmente; ver SQL entregado por el PM):
UPDATE tenis.tournaments
SET status_slug=COALESCE(pre_pause_status_slug,'active'), paused_at=NULL,
pre_pause_status_slug=NULL
WHERE id='65d1ecb9-78b2-4f45-83e8-4f9388daec86';
DATO PARA EL FRONT: si se define RESUME_REFLOW_FAILED (u otro code), avisar para mapearlo
a un toast (hoy ya manejan 409 β "El torneo no estΓ‘ en pausa").