Skip to content
Case study · 16 / 19← All work

La Trencería

A branded web app for an afro-braiding studio in Mar del Plata: online booking, a shop, paid courses and loyalty points, installable on the phone.

Role
Full build for a client
Stack
Next.js 16, React 19, TypeScript, Prisma + Postgres, Auth.js (NextAuth v5), Mercado Pago, Tailwind CSS 4
Home
HomeA Postgres lock per stylist stops double bookings
(01) The problem

Katia ran the studio from Instagram and WhatsApp. Every booking was a chat, the same questions about prices and duration came every day, and no-shows cost her hours she could not fill. She wanted to sell recorded courses and hair products, and had no place to do it. She needed one app with her brand that she could run alone, from her phone, without asking a developer to change a text.

My roleI built the whole product, from the data model and payments to the admin screens she uses every day.
(02) ResultsCounted in the code
50screens across the site, the customer account and the admin panel
43Prisma models behind bookings, shop, courses and loyalty
62site texts and photos the owner edits from the admin, with no deploy
(03) Architecture

Three decisions that shaped it.

  1. 01

    A Postgres lock per stylist stops double bookings

    Why. Checking a slot and then saving it leaves a gap where two people get the same hour. The booking runs in one transaction that first takes an advisory lock on the stylist, then looks for any overlap and only then creates the appointment. The second request waits and gets a clear 409. Loyalty redemptions use the same pattern per customer, so two taps can't spend the same points twice.

    Trade-off. All bookings for one stylist go one at a time. With one studio that costs nothing, but it would not scale to a marketplace. The lock lives in raw SQL, outside what Prisma models, so it has to be kept in mind on every new write path.

  2. 02

    Static public pages, purged by tag when the admin saves

    Why. The site runs on a free hosting plan, so the public pages are built static and their reads are cached under six tags: home, services, products, gallery, courses and reviews. Each admin save purges only its tag, with immediate expiry, so Katia sees her change on the next load. Before, one save invalidated the whole layout tree.

    Trade-off. Every admin route has to purge the right tag; a missing call means a stale page and nothing fails. There are 41 of those calls in 27 files. Immediate expiry also means one full render per save.

  3. 03

    Texts and messages are settings, with defaults in code

    Why. The home page copy, photos, testimonials and the nine WhatsApp and email templates live as key-value rows. The code ships a default for each key and uses the stored value when there is one. Katia rewrites the confirmation message or swaps the hero photo herself, and an empty database still renders a complete site.

    Trade-off. Values are plain strings with no history, so a bad edit can't be undone from the panel. The set of fields is still fixed in code: a new section on the home page needs a developer.

(04) GalleryKeep scrolling →
Home
Home · The public home. The titles, texts and the three photos come from the admin panel.
Booking on the phone
Booking on the phone · Step two of four. Taken and blocked hours show crossed out, from the same rules the admin sets.
Calendar
Calendar · The week in the admin, with sample data. Yellow appointments are still waiting for a payment or a confirmation.
Message templates
Message templates · The WhatsApp and email texts for confirmation, reminder and cancellation, with variables the owner can move around.
Loyalty points
Loyalty points · A customer's balance and rewards, with a sample account. Visits, reviews and referrals add points.
(05) What I'd do next
01Release unpaid bookings on time. A booking waiting for a Mercado Pago payment holds its slot until the webhook reports a cancellation; a scheduled job should free it when the payment link expires.
02Use one overlap rule in the slot picker and in the booking transaction. The picker only checks where an existing appointment starts, so it can offer an hour that the server then rejects with a 409.
03Make the service worker cache the shell and the customer's next appointment. Today it only handles push notifications, so the installed app shows nothing offline.
Next project · 17 / 19
Noelia Viscelli →A coach's sales pages she edits herself, plus a course platform
Case study · 16 / 19

La Trencería

A branded web app for an afro-braiding studio in Mar del Plata: online booking, a shop, paid courses and loyalty points, installable on the phone.

Role
Full build for a client
Stack
Next.js 16, React 19, TypeScript, Prisma + Postgres, Auth.js (NextAuth v5), Mercado Pago, Tailwind CSS 4
La Trencería
(01) The problem

Katia ran the studio from Instagram and WhatsApp. Every booking was a chat, the same questions about prices and duration came every day, and no-shows cost her hours she could not fill. She wanted to sell recorded courses and hair products, and had no place to do it. She needed one app with her brand that she could run alone, from her phone, without asking a developer to change a text.

My roleI built the whole product, from the data model and payments to the admin screens she uses every day.
(02) Results
50screens across the site, the customer account and the admin panel
43Prisma models behind bookings, shop, courses and loyalty
62site texts and photos the owner edits from the admin, with no deploy
(03) Architecture

Three decisions that shaped it.

01

A Postgres lock per stylist stops double bookings

Why. Checking a slot and then saving it leaves a gap where two people get the same hour. The booking runs in one transaction that first takes an advisory lock on the stylist, then looks for any overlap and only then creates the appointment. The second request waits and gets a clear 409. Loyalty redemptions use the same pattern per customer, so two taps can't spend the same points twice.

Trade-off. All bookings for one stylist go one at a time. With one studio that costs nothing, but it would not scale to a marketplace. The lock lives in raw SQL, outside what Prisma models, so it has to be kept in mind on every new write path.

02

Static public pages, purged by tag when the admin saves

Why. The site runs on a free hosting plan, so the public pages are built static and their reads are cached under six tags: home, services, products, gallery, courses and reviews. Each admin save purges only its tag, with immediate expiry, so Katia sees her change on the next load. Before, one save invalidated the whole layout tree.

Trade-off. Every admin route has to purge the right tag; a missing call means a stale page and nothing fails. There are 41 of those calls in 27 files. Immediate expiry also means one full render per save.

03

Texts and messages are settings, with defaults in code

Why. The home page copy, photos, testimonials and the nine WhatsApp and email templates live as key-value rows. The code ships a default for each key and uses the stored value when there is one. Katia rewrites the confirmation message or swaps the hero photo herself, and an empty database still renders a complete site.

Trade-off. Values are plain strings with no history, so a bad edit can't be undone from the panel. The set of fields is still fixed in code: a new section on the home page needs a developer.

(04) Gallery
Home
Home · The public home. The titles, texts and the three photos come from the admin panel.
Booking on the phone
Booking on the phone · Step two of four. Taken and blocked hours show crossed out, from the same rules the admin sets.
Calendar
Calendar · The week in the admin, with sample data. Yellow appointments are still waiting for a payment or a confirmation.
Message templates
Message templates · The WhatsApp and email texts for confirmation, reminder and cancellation, with variables the owner can move around.
Loyalty points
Loyalty points · A customer's balance and rewards, with a sample account. Visits, reviews and referrals add points.
(05) What I'd do next
01Release unpaid bookings on time. A booking waiting for a Mercado Pago payment holds its slot until the webhook reports a cancellation; a scheduled job should free it when the payment link expires.
02Use one overlap rule in the slot picker and in the booking transaction. The picker only checks where an existing appointment starts, so it can offer an hour that the server then rejects with a 409.
03Make the service worker cache the shell and the customer's next appointment. Today it only handles push notifications, so the installed app shows nothing offline.
Next projectNoelia Viscelli →
Nahuel SantillánEN