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.
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.
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ón | Dirección | Se publica si… |
|---|---|---|
| Reserva creada | quota_consumed | el slot quedó agotado |
| Reserva cancelada | quota_freed | el slot quedó disponible |
| Slot liberado | quota_freed | ídem, desde un snapshot previo |
| Reserva movida de slot | ambas | un evento por el slot viejo (freed) y otro por el nuevo (consumed) |
| Cambio de estado con impacto en cupo | según corresponda | saved→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.
{ "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__." } ]}| Campo | Detalle |
|---|---|
available_quotas | Cupos 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_until | Strings libres. El consumidor les impone max_length=64 porque no confía en el formato. |
bookable_until | Datetime naive en hora local del país del slot. Compararlo contra UTC da mal. |
end_time, source, updated_at, occurred_at | El consumidor los ignora, pero son parte del contrato: no removerlos sin versionar. |
source | Pensado para distinguir proveedores. Hoy siempre bigbox. |
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
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.updatedreescribe el arrayavailabilitycompleto y puede pisar el resultado de este evento.- El header
ownerdiceEcommerce, pero el equipo dueño de este evento es Alpha Centauri. ElOwnerestá configurado a nivel bus, no por evento.