When we published the post about Lueira back in March, I described it at a high level: a booking and management platform built for mountain sports schools — skiing, kayaking, canyoning, climbing, all the activities where a "product" is really a person, a piece of gear and a slot on the calendar happening at the same time. On the site we describe the feature simply: "Motor de reservas" — an online store that sells activities, courses and rentals, connected automatically to the calendar, stock and instructors. That sentence hides most of the hard engineering. I want to go through what actually makes this different from booking a hotel room, and roughly how we approached it.
1. A booking is not one resource, it's several
A hotel room booking engine only really has one axis to worry about: is this room free on these dates. A booking engine for a ski or kayak school has to satisfy several independent constraints at once, and all of them have to hold true for the exact same booking:
- An instructor ("monitor") who is available at that time, qualified for that activity, and speaking the language the customer needs.
- Equipment in the right size and quantity — a wetsuit in a size, a kayak, a pair of skis — that isn't already assigned to someone else during that window.
- A time slot that doesn't overlap with anything else that instructor, or that equipment, is already committed to.
- Group capacity: a class might take four students, a private lesson takes one.
None of these is "the" resource being booked — the booking is really the intersection of all of them. So instead of modeling a calendar with slots you check off, we ended up modeling a bookable slot as a function: given an activity, a date and a duration, which combinations of instructor + equipment + capacity are simultaneously free. That function is what the search on the storefront actually calls, and it's also what the backoffice calendar calls when a staff member drags a booking around by hand.
2. The race for the last spot
Once you accept that a "bookable slot" is a set of resources being checked together, you run into the classic problem of any inventory system: two people trying to buy the same last unit at the same time. The naive version of a booking flow — check availability, then insert the booking a few requests later — has an obvious race: two customers can both pass the availability check before either booking is written, and both walk away thinking they booked the last kayak.
We handle this with a transaction that locks exactly the rows a booking depends on before deciding whether it can proceed:
-- simplified example, not actual production code
BEGIN;
SELECT id, available_qty
FROM equipment_stock
WHERE product_id = $1 AND size = $2
FOR UPDATE;
SELECT id
FROM instructor_slots
WHERE instructor_id = $3
AND slot_start = $4
AND slot_end = $5
AND booked = false
FOR UPDATE;
UPDATE equipment_stock
SET available_qty = available_qty - 1
WHERE product_id = $1 AND size = $2 AND available_qty > 0;
UPDATE instructor_slots
SET booked = true
WHERE instructor_id = $3 AND slot_start = $4 AND slot_end = $5 AND booked = false;
INSERT INTO bookings (customer_id, product_id, instructor_id, slot_start, slot_end)
VALUES ($6, $1, $3, $4, $5);
COMMIT;
The exact mechanism matters less than the principle: the check and the reservation of a resource have to be one atomic operation, not two separate steps a request can be interrupted between.
3. Keeping the storefront honest in real time
The public booking page and the backoffice a school's staff uses cannot be allowed to show two different realities. If an instructor blocks off tomorrow morning in the backoffice, that slot has to disappear from the online store immediately — not after a cache refreshes. The catalog — descriptions, prices, photos — can be cached aggressively, because it barely changes. Availability can't.
The one place we do allow a customer-facing view to lag reality on purpose is checkout. Rather than re-running the full search after every field they fill in, we put a short hold on the specific resources once they start checkout: the slot, the equipment, the price at that moment, reserved for them for a few minutes.
4. What we'd still change
Holds are a blunt instrument — a customer who gets distracted for ten minutes loses their spot even though nobody "took" it maliciously, and tuning the hold window is a genuine tradeoff between conversion and fairness. We don't yet have a waitlist for full classes. And group bookings, where part of a party has one constraint and the rest have another, still need more graceful handling.
That's roughly the shape of the problem "Motor de reservas" has to solve every time someone books a course, a rental or an activity through Lueira. It's less about clever code and more about being honest about what a single booking actually depends on.
