Mini-case · Direct client · in production
Cleo Spa
Inventory, public catalog and bookings for a beauty salon in Arequipa. The tool the staff uses every day — not a showcase.
- problem
- A salon needs to know what stock it has, who moved it and what can be booked, without any of that depending on someone's memory.
- role
- Direct client: database, operations panel and public catalog. In production.
- scope
- Immutable stock ledger with FEFO batches · default-deny RLS on 16 tables · three operational roles (owner, cashier, stylist).
- result
insert-only ledger
corrections = adjustments
3 roles
permissions in DB and app
Next.js 16 · React 19 · TypeScript · Tailwind v4 · Supabase · Zod · Vitest
The product, unretouched



Three decisions that hold up the rest
Stock isn't edited, it's recorded
Every movement goes in as a new row with a signed quantity, location, date and author. A correction does not rewrite the past: it goes in as one more adjustment. The balance of any product is the sum of its ledger, so the question “who moved this and when” always has an answer.
FEFO batches, and the warning before expiry
Stock is tracked by batch and outgoing movements consume the one that expires first. The panel opens with two alerts — below minimum and batches about to expire — because expired stock gets found late or not at all.
Three roles, the same boundary in two layers
Owner, cashier and stylist. Permissions live in the database with default-deny RLS on 16 tables, and the application repeats that boundary: the interface does not offer what the database is going to reject. Having the rule at the bottom is what makes it hold even when the UI gets it wrong.
What it left behind
This is where the starter kit came from that took Ronatello to production in 12 days: the same skeleton of roles, RLS and CI, already argued through once.
Still pending
The migrations are versioned and idempotent, but they're still applied with a tool outside the repository: a clean environment can't be spun up from scratch. It's the first thing I'd close before the project changes hands.