Case Study · 2024
Travel Agency Management Platform
A multi-tenant SaaS platform for travel agencies in Patagonia, Argentina. Manages the full reservation lifecycle across isolated agency tenants: services and packages, multi-currency payments, quotations, pricing catalogs, resource scheduling and passenger manifests. Built over 8 months; I graduated with it as my university thesis project with excellent feedback, and the platform later reached full production-ready status.







01 — Overview
Travel agencies in Patagonia were running operations across WhatsApp chats used as databases, paper notes and spreadsheets, often all three for a single reservation.
Passenger contact details got lost between tools. Special requirements disappeared between the phone call and the booking. Excursions got overbooked because no one had a shared view of capacity, and unpaid debts went unchased because tracking partial payments across spreadsheets was slow enough that following up on stragglers didn't happen.
A contact from university whose family owned an agency brought the problem and an early app concept to me. His original plan was to build it in FlutterFlow. However, I proposed switching to a proper web stack, as the requirements were too complex for a no-code tool, and I took technical direction from there. He led client-facing work and requirements alignment for the first month, then stayed on through the build as manual QA under my guidance and supervision — catching bugs, validating features and suggesting UI changes; I taught him Cursor so he could prototype his own ideas. A simplified version of the system became my university final project, with which I graduated with excellent feedback. After graduating I extended it further; the platform reached production-ready status but was not commercially deployed as the commercial partner exited after development was complete.
02 — What I Built
Reservations & Pricing
The reservation workflow: where a scattered operation becomes one.
Before a booking exists, agency staff have already done the groundwork across dedicated modules: defined their services in the Services module, grouped them into Packages, registered third-party providers in Operators, and built up a record of clients in Passengers. Everything loaded into those modules feeds directly into the reservation form.
Entering a passenger's document number pulls their full profile; selecting a date pulls the correct price tier for that date; choosing a third-party provider auto-fills their commission rate — all while a running summary updates with subtotals and totals in real-time. Data loss across workflow tools was one of the main problems agencies were reporting, so I extended this autofill pattern throughout the product: wherever possible, inputs pull from what's already in the system rather than asking staff to retype, and all the available data is leveraged to show relevant information.
The pricing system went through a full rebuild after conversations with agencies. The first version had a flat price per service with an option to add a second price. When testing, agencies explained they use something more layered in their operations: prices that change by season, adult/child/student tier breakdowns, private bookings priced by passenger count range, packages priced either as a single total or broken down per service with modified per-service rates, operator commissions on top of all of that. I rebuilt the pricing system around those realities after the initial assumptions turned out to be wrong. Additionally, given the persistent inflation in Argentina, agencies also expressed the need to reprice their entire catalog regularly, so I built a bulk adjustment tool into the pricing catalog module that lets staff select any combination of services and apply a percentage or flat increase across all of them at once rather than editing each entry individually, as seen in the picture below.


Operational Flow
Designed to match how agency staff think about their operations.
Agency staff think about their work in terms of what services are running, when, and who is involved in each one. That mental model is chronological, so the natural entry point built in the system is the calendar: it gives staff a quick read of everything coming up and the state of each service at a glance, across month, week, day and agenda views. Clicking any service opens the assignment view, where the actual day-to-day work happens:
Passengers start in an unassigned pool and are dragged onto resource slots (vehicles, guides, equipment), each showing its capacity. An auto-assign button distributes passengers by capacity across transportations. If a resource is already committed to another service that day, the platform shows a warning rather than a hard block, because the truck might be done with the morning service before the afternoon one needs it.
Passengers who exceed available capacity move to a third-party pool and get assigned to an external operator. When I looked into how agencies handle overbooking, I found that this is standard practice in the region: agencies routinely hand off excess passengers to a competitor that offers the same activity and keep a commission. Passengers often never know this happened. Building a hard capacity ceiling would have been the wrong model for how the industry actually works.


Passenger Manifest
From the assignment view, one button leads to the customizable passenger manifest for that service run.
A hiking day needs emergency contacts and document numbers; an airport transfer needs flight details and accommodation addresses. Users set the columns they need for that service and export the manifest to Excel to take into the field.
It's also the communication hub: bulk email to all passengers on that service, automatic cancellation notifications and a warning before any bulk send if passengers are missing email addresses. Communications with clients was one of the main pain points that agencies wanted to solve, and the manifest is where that happens in the product.

Payment Processing
Agencies had no reliable way to see what was owed on a reservation at any given moment; partial payments scattered across spreadsheets meant chasing unpaid debts didn't happen.
To fix that, the platform makes every financial relationship on a reservation visible in one place, tracking payments separately for passengers, operators and suppliers.
The platform handles multiple currencies with exchange rates that change frequently due to inflation; each payment record stores the rate at the moment it was recorded so historical amounts never drift. Payments can also go through Mercado Pago (Argentina's dominant payment processor): the platform generates a payment link that can be sent by email or copied for manual delivery, and updates the reservation automatically via webhook when the payment is confirmed or rejected.


03 — Technical Decisions
Each agency's ID is embedded in the user's JWT at login. Database RLS policies check that claim on every query; tenants are isolated at the database layer with no application-level filtering needed. The Express server creates a per-request Supabase client from the user's Bearer token, so RLS applies to server-side operations as well.
The alternative was a separate quotation table with its own form and save logic, duplicating the most complex part of the application. Instead, quotations are reservations with a different status. This reuses the entire form, validation, PDF generation and list view without maintaining a parallel system. The tradeoff is that wherever reservation and quotation behavior diverges (display, validation and available actions), there's a status check in the code. Given the form was already built and working, that was cheaper than maintaining a parallel system for quotations.
04 — Design Decisions
Whatever the system already knows, it uses, so users never have to go looking for information across modules, tools or papers.
The users were agency staff ranging from their 30s to their 60s, coming from paper records and legacy systems. That demographic made adoption risk the constraint that shaped the rest of the design: it had to be simple and intuitive, without anything that needed learning or training. If friction is not minimized at this scale of prior habit, it could cause staff to fall back to what they know and not adopt the product.
Interconnection was one of the solutions to this problem: passenger details entered once are available in every reservation that follows; service prices configured in the catalog flow into totals automatically; converting a quotation carries all passengers, services and prices forward with no re-entry.
Additionally, given that most users had never used anything beyond spreadsheets, I included an in-app guided tour and a PDF manual as a reference for the rollout.
05 — Stack