docs: task cancellati e date dei pagamenti — STATUS, STATE, design system

STATUS.md: perché la pill sta su "Cancellata" e non su "Fatto" (sono entrambi
barrati, e scambiarli significa credere consegnato ciò che non esiste), perché
`due_date` è nullable, e le due verifiche che restano a mano — il riquadro
"Prossimo pagamento" non compare finché nessuna rata ha una scadenza, e due
`paid_at` storici valgono il primo del mese perché li ha scritti il vecchio
selettore a mese.

DESIGN-SYSTEM.md: la quinta forma, la regola della colonna Kanban che si
nasconde solo da vuota, e la spec del box pagamenti.

STATE.md: due decisioni nuove, e resta sotto le 100 righe accorpando le righe
di tabella della stessa settimana.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:51:51 +02:00
parent fe767899b9
commit 4403b39e63
3 changed files with 108 additions and 17 deletions
+74
View File
@@ -303,6 +303,80 @@ e «In revisione» (2) convivono, su 11 task, quindi l'unica dove si vede se ora
distinguono davvero. Da guardare anche col tema scuro: è lì che l'icona «In corso» prima
non aveva varianti.
### Task cancellati e date dei pagamenti (2026-08-22)
Due buchi che si vedevano solo aprendo il portale dalla parte del cliente.
**Un task tolto dal lavoro non aveva dove stare.** L'admin poteva solo cancellarlo dal DB
— e allora il cliente non capiva perché una voce che aveva letto la settimana prima non
c'era più — oppure lasciarlo lì a fingersi «Da fare» per sempre. Ora `cancelled` è il
quinto stato: **X dentro il cerchio, titolo barrato**, e la pill «Cancellata».
È l'unico stato *chiuso* che porta la pill, e non è un'incoerenza: «Fatto» e «Cancellata»
sono **entrambi barrati**, e scambiarli significa credere consegnato qualcosa che non
esiste. La forma da sola non basta a coprire quel rischio.
Il lavoro vero, di nuovo, non era il nuovo stato ma i denominatori. Un task cancellato esce
da **tutti**: percentuale di fase, percentuale globale, board delle consegne, riepilogo
admin — attraverso un solo `countsTowardProgress()` in `src/lib/task-status.ts`, non cinque
`!== "cancelled"` sparsi in giro. Contarlo terrebbe la fase sotto il 100% per un lavoro che
nessuno farà mai; contarlo come fatto racconterebbe una consegna mai avvenuta.
`recomputePhaseStatus` lo ignora allo stesso modo: senza, cancellare l'ultima voce di una
fase la lasciava «In corso» per sempre. Se restano **solo** cancellati la fase torna «Da
iniziare» — è degenere, ma «Completata» al cliente racconterebbe una consegna che non c'è
stata.
Nel Kanban cliente la colonna «Cancellate» compare **solo se ha dentro qualcosa**, così le
quattro che raccontano il lavoro si tengono la larghezza — ma mai se è piena, quindi nessun
task sparisce dalla board: era esattamente il bug che aveva fatto nascere `TASK_STATUSES`.
Nell'admin la colonna c'è sempre, ed è così che si cancella un task: trascinandocelo.
**Il box pagamenti non sapeva dire quando si paga.** `payments` aveva `paid_at` — quando una
rata è stata incassata — e nient'altro: nessun campo per la scadenza. Il portale non poteva
rispondere alla domanda più ovvia del cliente, e non c'era niente su cui agganciare il
promemoria via email. **Migration `0023`**, additiva: `payments.due_date`, nullable senza
default, più un indice parziale su cui girerà la query del promemoria («le non saldate in
scadenza entro N giorni», che l'indice `(project_id, sort_order)` non copre).
Nullable è una scelta: una rata senza data concordata è normale, e un default inventerebbe
una scadenza che nessuno ha pattuito. **Il portale mostra la data solo se c'è.**
Cosa vede il cliente:
- In cima al box, **«Prossimo pagamento»**: data lunga e conto alla rovescia in parole —
«tra 12 giorni», «domani», «oggi». Se è passata, il riquadro diventa rosso e dice
«Pagamento scaduto — scaduto da 3 giorni». Un arretrato batte sempre la rata del mese
prossimo: se c'è, *quello* è il prossimo pagamento.
- Su ogni riga, la sua data: **«Scade il…»** se aperta, **«Pagato il…»** se saldata.
- **Nessun importo per riga.** LOCKED #2 resta dov'era: le date non sono cifre.
I giorni si contano in `src/lib/payment-dates.ts`, sui **giorni civili a Roma** e non sugli
istanti: il container gira a UTC, e «manca una settimana» non deve cambiare risposta a
seconda del fuso di chi renderizza. È lo stesso modulo che userà il promemoria email —
scritto ora proprio perché la mail e il portale non si contraddicano su quanti giorni
mancano. Le date si salvano a **mezzogiorno UTC**, non a mezzanotte: a mezzanotte UTC il
giorno civile a Roma è già quello dopo, e la data tornerebbe indietro di un giorno appena
riletta.
Lato admin ogni rata ha ora il campo **Scadenza**, e «Incassato nel mese» è diventato
**«Incassato il»** — precisione al giorno, che le analytics non notano perché raggruppano
con `extract(month from paid_at)`. Il warning sul cambio schema rate conta anche le
scadenze: una rata «da saldare» con una data è già sotto gli occhi del cliente, e
sparirebbe in silenzio.
**In produzione dal 2026-08-22** (`fe76789`). Migration `0023` applicata a prod **prima**
del push, come da procedura: `ALTER TABLE` + `CREATE INDEX`, 12 righe in tabella, nessuna
toccata. Build, typecheck e lint verdi — lint invariato, nessun problema nuovo nei file
toccati.
⚠️ **Due cose da fare a mano.** (1) Il portale non l'ho visto a schermo: vale la stessa
verifica `?preview=1` della sezione precedente, e finché nessuna rata ha una `due_date` il
riquadro «Prossimo pagamento» **non compare per definizione** — va messa una scadenza dal
tab Pagamenti per vederlo. (2) Due `paid_at` storici valgono il **primo del mese**
(2026-03-01 e 2026-01-01): li ha scritti così il vecchio selettore a mese, e ora il cliente
li legge come «Pagato il 1 mar 2026». Se il giorno vero era un altro, si corregge dal nuovo
campo.
### v2.5 — Audit (Phases 2730), in pausa
Il servizio di analisi sito (tre livelli: **Radiografia / Prima-Dopo / Rotta**) diventa