Case study web app — prenotazioni medico

Il telefono squilla.
La segreteria è occupata.
Il paziente non richiama.

Uno studio medico con 4 specialisti perdeva prenotazioni ogni giorno per via di un sistema di gestione agenda completamente manuale: telefonate, foglio Excel, promemoria a voce. Ho costruito un portale di prenotazione online con agenda intelligente, reminder SMS automatici e accesso diretto ai referti — eliminando il collo di bottiglia della segreteria e riducendo i no-show del 62%.

ClienteStudio medico privato
SettoreSanità privata
Anno2024
TipoWeb App · Booking
studiomedico.it/prenota
Studio Medico
AGENDA ONLINE
Online
Cardiologia
Dermatologia
Ortopedia
Neurologia
LUN
MAR
MER
GIO
VEN
Prenotato
Libero
Urgenza
MER 12 GIU · 09:30
LB
Lucia Bianchi
Paziente dal 2022 · ECG urgente
Urgenza
30 min
Reminder automatici
SMS −24h ✓ Inviato
Email −48h ✓ Inviato
SMS −2h ○ In attesa
GESTISCI APPUNTAMENTO

Case study app prenotazioni studio medico

Un foglio Excel come
sistema di prenotazione

Lo studio medico gestiva 4 specialisti, circa 80 appuntamenti alla settimana e una segreteria che passava buona parte della giornata al telefono. La gestione dell'agenda era interamente manuale: una segreteria centralizzata, un foglio Excel condiviso su Google Drive e promemoria scritti a mano.

I problemi erano evidenti: i pazienti non trovavano posto libero fuori dagli orari di ufficio, i no-show superavano il 28% degli appuntamenti per mancanza di promemoria, e i referti venivano consegnati ancora fisicamente o via email non strutturata. Il primario voleva un sistema che lavorasse anche quando la segreteria era chiusa.

Vincolo critico: il sistema doveva integrarsi con il gestionale medico preesistente (Medigest Pro) tramite API, senza sostituzione — il cliente non voleva cambiare il software interno già rodato.

Cliente Studio medico privato, 4 specialisti
Settore Sanità privata · Ambulatorio
Anno 2024 · 10 settimane
Stack principale
React 18 Node.js 20 PostgreSQL Twilio SMS Redis Stripe
Ruolo Full-stack developer · UX design

Prenotazione self-service 24/7

I pazienti devono poter prenotare in autonomia, scegliere specialista, data e ora — anche alle 23:00. Nessuna telefonata necessaria per slot standard.

Reminder SMS + Email automatici

Notifiche a −48h (email) e −24h e −2h (SMS) con link di conferma o disdetta. Ogni disdetta deve liberare lo slot in tempo reale e inviare conferma al paziente.

Portale pazienti e referti digitali

Area riservata per ogni paziente: storico visite, possibilità di scaricare referti in PDF, visualizzazione prossimi appuntamenti e gestione dati personali (GDPR compliant).

Integrazione con Medigest Pro

Sincronizzazione bidirezionale con il gestionale interno: nuove prenotazioni entrano in Medigest Pro, modifiche manuali del medico si riflettono sul portale online in tempo reale.

Pagamento anticipato selettivo

Alcune visite richiedono un acconto al momento della prenotazione (tramite Stripe) per ridurre i no-show. La segreteria deve poter attivare questa opzione per singola tipologia di visita.

Requisiti portale prenotazioni online

Quattro agende, zero conflitti,
un sistema che funziona da solo

Il dominio medico ha vincoli specifici: privacy dei dati (GDPR sanitario), concorrenza sugli slot, affidabilità dei reminder che — se non arrivano — costano denaro reale allo studio. Ogni pezzo del sistema doveva essere robusto per definizione, non a best-effort.

01

Race condition sugli slot

Due pazienti che prenotano lo stesso slot nello stesso momento potevano entrambi ricevere conferma. Con un sistema distribuito e nessun lock, le collisioni erano frequenti — specie nelle ore di punta post-notizia SMS.

02

Reminder affidabili al 100%

Un SMS non consegnato a −24h era una visita persa con slot vuoto. Twilio ha latenze variabili e l'istanza Node.js non poteva gestire timer in-memory: restart del server = reminder persi.

03

Sincronizzazione con Medigest Pro

L'API del gestionale interno era REST ma mal documentata, senza webhook. Le modifiche dei medici (spostamenti, cancellazioni urgenti) dovevano riflettersi sul portale quasi in tempo reale senza un push event.

04

Dati sanitari e GDPR art. 9

I dati medici rientrano nella categoria "dati particolari" del GDPR: consenso esplicito, diritto alla cancellazione, log degli accessi. Il sistema doveva essere privacy-by-design, non un afterthought.

05

UX accessibile e zero apprendimento

Il bacino di pazienti include anziani poco avvezzi al digitale. Il flusso di prenotazione doveva essere completabile in meno di 90 secondi, senza account obbligatorio al primo accesso, con accessibilità WCAG 2.1 AA.

Sfide tecniche agenda medica digitale

Lock ottimistico, job queue
e un portale che dorme, ma lavora

01 / 03

Prenotazione atomica con lock ottimistico su PostgreSQL

Per eliminare le race condition ho usato un approccio a lock ottimistico: ogni slot ha una colonna version incrementale. La prenotazione aggiorna lo slot solo se la versione corrisponde a quella letta — se nel frattempo qualcun altro ha prenotato, la transazione fallisce con un retry guidato. Questo elimina i deadlock tipici dei lock pessimistici e scala bene sotto carico.

Il flusso al back-end è una singola transazione PostgreSQL: leggo lo slot, verifico disponibilità, aggiorno con WHERE version = $expected, se le righe aggiornate sono 0 rilancio SlotConflictError al client che mostra un messaggio e propone gli slot vicini liberi.

// Node.js · PostgreSQL · lock ottimistico su slot agenda async function bookSlot(slotId, patientId, paymentIntentId) { const client = await pool.connect(); try { await client.query('BEGIN'); // lettura slot con lock di riga const { rows } = await client.query( `SELECT id, version, status FROM slots WHERE id = $1 AND status = 'available' FOR UPDATE SKIP LOCKED`, [slotId] ); if (rows.length === 0) { await client.query('ROLLBACK'); throw new SlotConflictError('Slot non più disponibile'); } // prenotazione atomica await client.query( `UPDATE slots SET status='booked', patient_id=$1, payment_intent=$2, version=version+1 WHERE id=$3`, [patientId, paymentIntentId, slotId] ); // accoda i reminder nella job queue await scheduleReminders(client, slotId, patientId); await client.query('COMMIT'); } catch (err) { await client.query('ROLLBACK'); throw err; } finally { client.release(); } }

02 / 03

Job queue persistente per reminder affidabili

I reminder non vengono schedulati con setTimeout ma come job persistenti su BullMQ + Redis. Ogni prenotazione aggiunge 3 job con delay calcolato: −48h email, −24h SMS, −2h SMS. Se il server si riavvia, i job sopravvivono su Redis e vengono processati al momento giusto. Twilio riceve la richiesta solo al momento dell'invio, con retry automatico in caso di errore di rete.

La disdetta via SMS (il paziente risponde "DISDICI" all'SMS) attiva un webhook Twilio che cancella il job di conferma, libera lo slot in PostgreSQL e invia una notifica email allo studio — tutto senza intervento umano.

BullMQ Dashboard · Reminder Queue
REMINDER QUEUE
● 3 attivi 847 completati
reminder:sms:−24h
Bianchi L. · slot 12/06 09:30
in 22h 14m
delayed
reminder:email:−48h
Rossi M. · slot 13/06 11:00
in 46h 02m
delayed
reminder:sms:−2h
Ferrari G. · slot 11/06 14:00
completato 09:47
completed

03 / 03

Portale pazienti e polling intelligente su Medigest Pro

Il portale pazienti è un'SPA React con autenticazione tramite magic link via email: nessuna password da ricordare, massima frizione zero al primo accesso. Ogni paziente vede il proprio storico visite, può scaricare i referti PDF caricati dal medico e gestire le prenotazioni attive.

La sincronizzazione con Medigest Pro — che non offre webhook — avviene tramite un worker di polling ogni 60 secondi che confronta il digest delle agende con lo stato interno. Le modifiche rilevate (spostamenti, cancellazioni, nuove disponibilità) vengono applicate sul database e propagate ai client React tramite Server-Sent Events — aggiornamento in tempo reale senza WebSocket, più semplice da gestire dietro reverse proxy.

Processo sviluppo app prenotazioni React

10 settimane, tre attori,
un sistema che va da solo

Il progetto ha coinvolto tre figure: il primario (decisore e domain expert), la segreteria (principale user dell'agenda) e i pazienti (user finale del portale). Ho tenuto sessioni di osservazione con la segreteria prima di scrivere una riga di codice — capire il flusso reale era più utile di qualsiasi documento di requisiti.

Settimana 1–2

Discovery e analisi del dominio

Osservazione del flusso di lavoro della segreteria, mappatura dei tipi di visita e dei vincoli di agenda per specialista, analisi dell'API Medigest Pro. Definizione dello schema dati e dei requisiti GDPR.

Output: schema DB, wireframe portale, piano GDPR

Settimana 3–4

Backend core: slot, lock e job queue

Implementazione del modello agenda su PostgreSQL, lock ottimistico con FOR UPDATE SKIP LOCKED, integrazione BullMQ + Redis, primo worker reminder Twilio. Test di carico con 50 prenotazioni simultanee.

Output: API REST stabile, 0 race condition in test

Settimana 5–6

Frontend portale pazienti e agenda

SPA React con magic link auth, flusso di prenotazione in 3 step (specialista → slot → conferma), area riservata con storico visite e download referti. Focus su accessibilità WCAG 2.1 AA e usabilità per utenti non tecnici.

Output: portale testato con 5 pazienti reali in user test

Settimana 7–8

Integrazione Medigest + Stripe + reminder pipeline

Worker di polling Medigest Pro con diff engine, sync bidirezionale, Server-Sent Events per aggiornamenti real-time sul frontend. Integrazione Stripe per acconti selettivi. Test end-to-end del ciclo disdetta via SMS.

Output: pipeline reminder testata con 200 job simulati

Settimana 9–10

UAT, GDPR audit e go-live progressivo

Sessioni con segreteria e primario, fix UX sulla gestione urgenze, audit GDPR con consulente legale dello studio. Go-live in 3 fasi: cardiologia → dermatologia → tutti gli specialisti. Monitoring attivo nelle prime 72 ore.

Output: 0 criticità nei primi 7 giorni di produzione

Soluzione: lock ottimistico e reminder SMS

React + Node.js al centro.
Ogni scelta giustificata.

React 18 + TypeScript
Node.js 20 + Express
PostgreSQL 16
Redis 7 + BullMQ
Twilio SMS
Stripe Payments
Nodemailer + SES
AWS S3 (referti PDF)
Vite
Jest + Supertest
Zod (validazione)
date-fns (gestione orari)

Perché magic link e non password?

Il bacino di pazienti include persone anziane o poco avvezze al digitale. Una password significa "ho dimenticato la password" come primo punto di contatto con il portale. Il magic link — un link monouso via email valido 15 minuti — elimina questo problema: se hai accesso all'email, hai accesso al portale. Per chi prenota per la prima volta non serve nemmeno registrarsi: i dati vengono raccolti durante la prenotazione stessa.

Perché SSE e non WebSocket?

I WebSocket richiedono un'infrastruttura dedicata (sticky sessions, supporto proxy) che complicava il deploy su un hosting condiviso già in uso dallo studio. Server-Sent Events funzionano su HTTP standard, sono supportati nativamente dai browser moderni e coprono il caso d'uso reale: aggiornamenti monodirezionali dal server al client (slot liberato, appuntamento spostato). Meno infrastruttura, stesso risultato percepito.

Learnings sviluppo app prenotazioni sanitarie

Prenotazioni online +400%.
La segreteria ha smesso di essere un collo di bottiglia.

Dati rilevati nei 6 mesi successivi al go-live completo, confrontati con lo stesso periodo dell'anno precedente.

+400%
Prenotazioni online
Da 8% a 41% del totale
−62%
Tasso no-show
Da 28% a 10,6% grazie ai reminder
−70%
Chiamate in segreteria
Solo urgenze e casi complessi
34%
Prenotazioni fuori orario
Dopo le 18:00 o nel weekend
4,8/5
Soddisfazione pazienti
Survey post-visita · 140 risposte
0
Doppi booking
Lock ottimistico · 6 mesi di produzione

«Prima passavamo metà mattina a rispondere al telefono per le prenotazioni. Adesso gestiamo solo le cose che richiedono davvero la nostra attenzione. La segreteria fa finalmente il lavoro per cui è qualificata.»

— Segretaria dello studio, 3 mesi dopo il go-live

Risultati app prenotazioni: +400% online

Cosa porterei
nel prossimo progetto.

Osservare prima di progettare vale doppio nel dominio medico. Le sessioni di osservazione in segreteria hanno rivelato un workflow reale completamente diverso da quello descritto nei requisiti iniziali. Il medico "sposta" gli slot in un modo che nessun documento aveva catturato — e che avrebbe rotto l'agenda digitale se non l'avessi visto di persona.

I job di reminder vanno testati con simulazioni temporali, non con clock reali. Durante lo sviluppo ho perso due giorni aspettando che i job si attivassero nei momenti giusti. Da quel momento uso sistematicamente il time mocking in test: avanzo il clock di 24 ore in pochi millisecondi e verifico che il job si attivi. Test più veloci, copertura più alta.

Il magic link ha eliminato il 90% delle richieste di supporto all'onboarding. Avevo previsto una FAQ per "come mi registro" e "ho perso la password". Non ne ho avuto bisogno. Per gli utenti digitalmente fragili, abbassare la barriera d'ingresso è più importante di qualsiasi funzionalità avanzata.

Il GDPR sanitario va progettato dentro l'architettura, non aggiunto alla fine. Inserire i log degli accessi ai referti, il consenso esplicito, la retention policy e il diritto alla cancellazione fin dallo schema del database ha richiesto 2 giorni extra all'inizio. Aggiungerli dopo avrebbe richiesto una riscrittura parziale.

Il go-live progressivo per specialista ha salvato la reputazione del sistema. Avviando con la sola cardiologia abbiamo intercettato tre edge case dell'agenda (visite di follow-up con durata variabile, slot bloccati per riunioni interne) che non erano emersi in UAT. Se fossimo andati live con tutti e quattro gli specialisti insieme, quei bug avrebbero colpito 4× gli utenti nelle prime 48 ore critiche.

Hai un processo che si basa ancora su telefonate ed Excel?

Agende, prenotazioni, reminder automatici — se il flusso esiste già, si può digitalizzare senza stravolgere tutto.