La saga de compra de TicketFlow. Sin solutions.md hasta entregar.
Ejercicio 1 — El mapa fuerte/eventual
- Clasifica TODAS las operaciones del checkout: disponibilidad, reserva+asientos, cobro, email de confirmación, webhooks salientes, dashboard de ventas, informe RGPD (23). Fuerte o eventual, y la garantía de convergencia de las eventuales.
- Busca en tu código el pecado contrario: ¿algún email/cobro en el request crítico que deba moverse, o alguna "consulta eventual" que el usuario espera? Documenta el finding.
Ejercicio 2 — La tabla PurchaseSaga
- Crea el modelo
PurchaseSaga(saga_id UUID, reserva FK, intent_id, estado, intentos, created_at, updated_at)con la máquina de estados (00b) comoTextChoicesy transiciones validadas en el modelo. - Escribe el orquestador
iniciar_compra(user, event, seats): TX local (reserva) + fila saga PENDING + comando de cobro con on_commit (29). Test: el pago falla ANTES de encolarse si la TX de reserva hace rollback. - Test de reanudación: mata el "proceso" (simula excepción tras el cobro) y relanza
reanudar_sagas()— la fila PENDING con intent_id se retoma, no se duplica el cobro (idempotencia de la pasarela fake, 14).
Ejercicio 3 — El UNKNOWN
- Implementa el manejo del timeout:
except GatewayTimeout → Outcome.UNKNOWN, conreconciliar(intent_id)que haceGET /charges/{intent}a la pasarela fake. Tres casos en test: cobró (→ CONFIRMED), declinó (→ compensar), sigue UNKNOWN (→ reintento con backoff). - Define los plazos: máximo de reintentos de reconciliación y plazo de compensación. ¿Qué pasa al expirar el plazo con la saga aún UNKNOWN? Escríbelo como runbook de 4 pasos (el estado final es humano, no automático).
- Rompe a propósito: compensa en ciego ante un timeout SIN reconciliar y demostrando con el fake que el cliente paga dos veces (cargo + reembolso de un cargo que existió... con el asiento liberado). Pega el test rojo como advertencia.
Ejercicio 4 — Las compensaciones
- Implementa
compensar(saga): reembolso (ledger con entrada inversa, 08) + liberar asientos +ReservationCancelledal outbox + email. Idempotente: dos llamadas → un reembolso. - Test del orden inverso: saga con 3 pasos ejecutados (cobro, reserva, email) — ¿en qué orden compensa tu código y por qué? Escríbelo como assertion del test (el email fake NO se "desenvía": documenta qué pasa con los efectos no compensables).
- El job diario (31):
perseguir_compensaciones_pendientes()— lascompensation_pendingcon más de 24 h → alerta. Test con FakeClock.
Ejercicio 5 — Coreografía periférica
- Los consumidores del stream (30) que reaccionan a
PaymentSucceeded: emails, analytics. Escribe el consumidor de analytics que construye "ventas por día" SIN tocar el flujo de compra. ¿Qué pasa si analytics está caído 2 h? (respuesta: converge por PEL/reclamo — verifícalo). - Escribe el test de la historia completa (la E2E de la 35 adelantada): compra feliz → CONFIRMED + 1 email + 1 evento webhook; compra con declinación → compensada + email de fallo; compra con timeout-cobró → reconciliada a CONFIRMED.
Entrega
Pega el mapa, el modelo saga, los tres casos del UNKNOWN y el test E2E de la saga. Después: Lección 33 — Pruebas unitarias bien hechas (módulo 7).