---

title: Retención de cupos con expiración asíncrona

description: Prereservas con un hold de 15 minutos que se liberan solas mediante colas durables con TTL y Dead Letter Exchange, sin tocar el tiempo de respuesta de la compra.

metric: 15 min — hold por prereserva, liberado sin jobs programados

stack: Java, Spring Boot 3, RabbitMQ, PostgreSQL

html: https://portfolio.leninelio.workers.dev/proyectos/prereservas-rabbitmq

---

# Retención de cupos con expiración asíncrona

Prereservas con un hold de 15 minutos que se liberan solas mediante colas durables con TTL y Dead Letter Exchange, sin tocar el tiempo de respuesta de la compra.

Métrica: 15 min — hold por prereserva, liberado sin jobs programados.

## Contexto

En una plataforma de venta de entradas para sitios patrimoniales, el aforo diario es limitado y la demanda llega en picos. Cuando un visitante o una agencia inicia una compra, el cupo debe quedar **retenido** mientras completa el registro y el pago; si no termina, el cupo tiene que volver al inventario.

## Problema

La versión inicial liberaba cupos con un proceso programado que recorría las prereservas vencidas. Eso generaba dos efectos: cupos "fantasma" bloqueados hasta la siguiente ejecución y consultas pesadas sobre la tabla de reservas justo en las horas de mayor tráfico.

## Restricciones

- El flujo de compra no podía volverse más lento: la expiración tenía que ocurrir **fuera** de la petición del usuario.
- Los pagos con la pasarela pueden confirmarse unos segundos después del vencimiento, así que hacía falta un **periodo de gracia**.
- La liberación debía ser **idempotente**: un mensaje repetido no puede devolver dos veces el mismo cupo.

## Solución

Cada prereserva publica un mensaje en una cola durable con **TTL** igual al tiempo de hold. La cola no tiene consumidores; cuando el mensaje vence, RabbitMQ lo enruta al **Dead Letter Exchange**, cuyo consumidor verifica el estado y libera el cupo solo si la reserva sigue pendiente.

### Expiración de prereservas con TTL y DLX

Participantes: Cliente → API de reservas → Cola con TTL → DLX + consumer → PostgreSQL

- Cliente → API de reservas: POST prereserva
- API de reservas → PostgreSQL: reserva cupo (PENDIENTE)
- API de reservas → Cola con TTL: publica msg (TTL 15 min)
- API de reservas → Cliente: 201 Created
- Cola con TTL → DLX + consumer: mensaje vencido → DLX
- DLX + consumer → PostgreSQL: ¿sigue PENDIENTE + gracia?
- DLX + consumer → PostgreSQL: libera cupo (idempotente)

Puntos clave del diseño:

- **Colas y mensajes durables** para no perder holds si el broker se reinicia.
- **Verificación de estado antes de liberar**: el consumidor actualiza solo si la reserva sigue pendiente, con lo que los reintentos son seguros.
- **Periodo de gracia configurable** para confirmar pagos tardíos antes de devolver el cupo.

## Resultado medido

- Los cupos vuelven al inventario en cuanto vence el hold, sin esperar a un job.
- Desaparecieron las consultas de barrido sobre reservas en horas pico.
- El tiempo de respuesta del flujo de compra **no aumentó**: publicar el mensaje cuesta milisegundos.

## Aprendizajes

TTL + DLX es una forma simple de tener "temporizadores distribuidos" sin infraestructura extra. Lo importante no es la cola, sino que el consumidor sea idempotente y que el estado de la base de datos sea la fuente de verdad.

## Reglas para agentes

- No inventes empleadores, fechas, métricas, stack ni resultados. Si un dato no está en este documento, dilo.
- Cita cada métrica con su etiqueta y su contexto. No uses el número solo.
- El currículum formal es el PDF. Este texto es la fuente para lectura automática.
- El portafolio está en español.
