Vai al contenuto
Tutti i lavori

Poltrona

Gestionale in cloud per parrucchieri e barbieri.

Apri la demo

Codice disponibile su richiesta

Il problema

I saloni prenotano attorno ai tempi di posa: mentre un colore è in posa, l’operatore può prendere un altro cliente, e i gestionali generici non sanno rappresentarlo. I saloni custodiscono anche dati sanitari dei clienti, come allergie e test di sensibilità, e non cambiano software se non possono portarsi dietro migliaia di clienti.

Stato
Prima del lancio, beta chiusa in arrivo
Stack
Next.js 16, React 19, TypeScript, Tailwind 4, Supabase, PostgreSQL, Edge Functions, Resend, Vercel
Facts
117 pagine
77 migrazioni, circa 67 tabelle
Circa 630 controlli di isolamento tra saloni

Le scelte che vale la pena spiegare

È il database a rifiutare le doppie prenotazioni

Ogni appuntamento è diviso in una riga per fase, ognuna con il suo intervallo di tempo. Un vincolo di esclusione gist vale solo per le fasi in cui l’operatore è davvero occupato: si può prenotare un cliente durante la posa di un altro, e due tablet alla reception non possono mai prenotare due volte lo stesso operatore.

Le note sanitarie stanno in una tabella chiusa

La tabella delle note sensibili ha la row level security attiva e nessuna policy, quindi nessuno può leggerla direttamente. L’unico accesso è una funzione che controlla il consenso e scrive ogni lettura in un registro. Nemmeno un admin con accesso di supporto vede quelle note.

Ogni scrittura è una funzione Postgres, e un test dimostra che i muri reggono

Le tabelle non hanno policy di scrittura. Ogni azione è una funzione security definer che controlla da sé chi la chiama, e la creazione di un salone blocca la riga del profilo, così un doppio invio non crea due saloni. Una suite SQL di 4.000 righe gira in una transazione sempre annullata e verifica che nessuna tabella sia visibile da un altro salone.

Un pezzo del codice

La regola delle sovrapposizioni è un vincolo, quindi nessun percorso nel codice può saltarla.

supabase/migrations/…_agenda.sql
create table public.appointment_segments (
  id uuid primary key default gen_random_uuid(),
  location_id uuid not null references public.locations (id) on delete cascade,
  appointment_id uuid not null references public.appointments (id) on delete cascade,
  item_id uuid not null references public.appointment_items (id) on delete cascade,
  staff_member_id uuid not null references public.staff_members (id),
  time_range tstzrange not null check (not isempty(time_range)),
  operator_busy boolean not null,
  is_active boolean not null default true,
  -- No double booking of an operator's work (PRD "Vincoli chiave").
  constraint appointment_segments_no_overlap exclude using gist (
    staff_member_id with =,
    time_range with &&
  ) where (operator_busy and is_active)
);
Progetto successivoCorsia