event

Availability Updated

Un slot de reserva cambió su estado visible de disponibilidad. Evento delta y selectivo: solo se emite al cruzar el límite disponible ↔ no disponible.

EventreservationsDelta, no snapshot

Overview

reservations.availability.updated.v1 representa el estado de disponibilidad de un slot reservable (actividad + sucursal + fecha + hora) visible desde afuera, con independencia del proveedor de reservas.

Es un delta selectivo, no un snapshot de cupos

El evento solo se emite cuando el slot cruza el límite disponible ↔ no disponible. Un cupo que baja de 5 a 4 no genera evento; uno que baja de 1 a 0, sí. Un consumidor no puede usar este topic para llevar la cuenta exacta de cupos.

Cuándo se dispara

El único punto de publicación es AvailabilitySlotEventPublisher.publish_slot_updated(...), invocado desde AutomaticReservationAvailabilityAdapter, que traduce mutaciones de AutomaticBigboxReservation:

SituaciónDirecciónSe publica si…
Reserva creadaquota_consumedel slot quedó agotado
Reserva canceladaquota_freedel slot quedó disponible
Slot liberadoquota_freedídem, desde un snapshot previo
Reserva movida de slotambasun evento por el slot viejo (freed) y otro por el nuevo (consumed)
Cambio de estado con impacto en cuposegún correspondasaved→reserved, reserved→cancelled, …

Los cambios sin impacto en cupo (un comentario, un campo administrativo) no emiten evento.

Reglas de consumo de cupo: reserved siempre consume; lived consume solo si activity.instant_validation; cancelled y saved no consumen.

Schema

Formato Avro, validado con fastavro.

reservations.availability.updated.v1 — Avro
{
"type": "record",
"name": "AvailabilityUpdated",
"namespace": "reservations.availability",
"doc": "Emitido cuando un slot de reserva cruza el limite disponible/no-disponible en bigbox-backend (partner). Representa el estado de disponibilidad visible externamente, con independencia del proveedor (Bigbox, RoomCloud, CoverManager, etc.). Consumido por el servicio search para actualizar la disponibilidad del documento de actividad.",
"fields": [
{
"name": "activity_id",
"type": "int",
"doc": "Id de la actividad afectada."
},
{
"name": "location_id",
"type": "int",
"doc": "Id interno de la location (sucursal). Desambigua la misma (provincia, ciudad) entre sucursales; el consumer no lo expone en su API."
},
{
"name": "province_id",
"type": ["null", "int"],
"default": null
},
{
"name": "city_id",
"type": ["null", "int"],
"default": null
},
{
"name": "date",
"type": "string",
"doc": "Fecha del slot en ISO-8601 (date.isoformat())."
},
{
"name": "time",
"type": "string",
"doc": "Hora del slot en ISO-8601 (time.isoformat())."
},
{
"name": "end_time",
"type": ["null", "string"],
"default": null,
"doc": "Hora de fin del slot en ISO-8601; null si no esta definida. El consumer lo ignora."
},
{
"name": "bookable_until",
"type": ["null", "string"],
"default": null,
"doc": "Ultimo instante en que el slot todavia puede reservarse (hora del turno menos la anticipacion requerida). Datetime naive en hora local del pais del slot. null cuando no se pudo calcular (fail-safe)."
},
{
"name": "source",
"type": "string",
"doc": "Origen del cambio. Hoy siempre 'bigbox' (AutomaticReservationAvailabilityAdapter.SOURCE). El consumer lo ignora."
},
{
"name": "updated_at",
"type": "string",
"doc": "timezone.now().isoformat() de Django (aware, TZ del proyecto) al momento de publicar. El consumer lo ignora."
},
{
"name": "available_quotas",
"type": "int",
"doc": "Cupos restantes tras el cambio. En la practica 0 (el slot se acaba de llenar) o 1 (el slot se acaba de liberar). Nunca negativo. <= 0 hace que el consumer elimine el slot."
},
{
"name": "occurred_at",
"type": "string",
"doc": "ISO-8601 naive en hora local del proceso. Lo inyecta events.event.Event.__init__."
}
]
}
CampoDetalle
available_quotasCupos tras el cambio: en la práctica 0 (se llenó) o 1 (se liberó). <= 0 hace que el consumidor elimine el slot.
date, time, bookable_untilStrings libres. El consumidor les impone max_length=64 porque no confía en el formato.
bookable_untilDatetime naive en hora local del país del slot. Compararlo contra UTC da mal.
end_time, source, updated_at, occurred_atEl consumidor los ignora, pero son parte del contrato: no removerlos sin versionar.
sourcePensado para distinguir proveedores. Hoy siempre bigbox.
Campos deliberadamente ausentes

appointment_id e is_available son internos y nunca viajan en el payload. Está explícito en el código del productor: es intencional, no un olvido.

Consumidores

publishes
event
routes to
routes to
consumes
Viewing
Event
Availability Updated(v1)
alpha-centauri
Service
Bigbox Backend(v1.0.0)
omega-backend+2
Channel
Google Cloud Pub/Subv1.0.0
At least oncepubsub
ecommerzeta
Service
Search(v1.0.0)
ecommerzeta
  • events (1)
  • services (2)
  • channels (2)

search valida contra el modelo AvailabilityEvent y aplica el cambio sobre el array availability del documento de la actividad: upsert del slot si available_quotas > 0, borrado si es <= 0. El province_slug se deriva en el indexado a partir del array province de la actividad — el evento solo manda province_id.

Deuda conocida

  • Sin ordenamiento garantizado: dos eventos del mismo slot en sucesión rápida (consumed → freed) pueden procesarse fuera de orden.
  • search.product.updated reescribe el array availability completo y puede pisar el resultado de este evento.
  • El header owner dice Ecommerce, pero el equipo dueño de este evento es Alpha Centauri. El Owner está configurado a nivel bus, no por evento.