Vai al contenuto
Tutti i lavori

Corsia

Preparazione all’esame per conducenti taxi e NCC.

Apri la demo

Codice disponibile su richiesta

Il problema

I candidati studiano da circa 3.000 domande ufficiali sparse in vari PDF, più una prova sulla cartina muta. Non c’era modo di esercitarsi nel formato vero dell’esame, che cambia da una sessione all’altra, né di vedere quali argomenti si padroneggiano davvero.

Stato
MVP in anticipo, lancio a dicembre
Stack
Next.js 16, React 19, TypeScript, Tailwind 4, Supabase, Stripe, Resend, Python, OpenStreetMap
Facts
3.137 domande ufficiali importate
13 migrazioni, circa 24 tabelle
Pipeline dei contenuti da PDF a CSV e mappe SVG

Le scelte che vale la pena spiegare

Le risposte si correggono sul server

Le domande arrivano al browser senza la risposta giusta e con le opzioni mescolate. Una funzione del database corregge ogni risposta, registra il tentativo e limita il tempo dichiarato. L’unica eccezione sono i pacchetti offline, limitati a 200 domande al giorno per dispositivo registrato.

Le regole d’esame sono dati, non codice

Numero di domande, minuti, soglie di superamento e lettere della cartina sono salvati come JSON su ogni sessione d’esame, perché la Regione ha già cambiato formato una volta nel 2026. Un nuovo formato è una riga, non un rilascio.

I webhook di Stripe sono verificati e idempotenti

Ogni evento ha la firma verificata, solo le sessioni pagate danno accesso e i metadati vengono validati. La funzione di acquisto ignora una sessione già vista grazie a una colonna unica, così i tentativi ripetuti di Stripe non danno mai accesso due volte.

Un pezzo del codice

Il browser non conosce la risposta giusta finché non ne ha scelta una.

supabase/migrations/…_accounts_training.sql
begin
  if auth.uid() is null then
    raise exception 'not signed in' using errcode = '42501';
  end if;
  select * into q from public.questions where id = p_question;
  if not found or not private.trainable(q) then
    raise exception 'question not available' using errcode = '22023';
  end if;

  insert into public.attempts (user_id, question_id, chosen_index, correct, ms, mode)
  values (auth.uid(), q.id, p_chosen, p_chosen is not distinct from q.correct_index,
          least(greatest(p_ms, 0), 3600000), p_mode);

  return query select p_chosen is not distinct from q.correct_index, q.correct_index, q.explanation, q.source;
end;
Progetto successivoSconti Nova