Files
clienthub/.planning/ROADMAP.md
T
simone 5177a3700a feat(offers): ciclo di vita dei servizi ricorrenti (v2.4 Phase 13)
Un retainer, una volta assegnato, non si poteva fermare: project_offers
aveva solo start_date e il forecast sommava il canone a ogni mese
dell'orizzonte da li in poi, per sempre. Un cliente che disdiceva
continuava a gonfiare il forecast a 12 mesi e a vedersi l'abbonamento
attivo nel portale.

- migration 0016 (gia applicata a prod): project_offers.status
  (attivo|sospeso|cessato, CHECK) + end_date. Additiva pura, default
  'attivo' cosi le righe esistenti conservano il comportamento di prima
- forecast: i retainer si fermano a end_date, sospesi e cessati escono.
  getOffersSoldBreakdown NON filtra per stato: e uno storico di vendita,
  escludere le cessate riscriverebbe il passato
- offersAcceptedTotal esclude le cessate (default del piano pagamenti)
- admin: comandi Sospendi/Riattiva/Cessa + data fine nella tab Offerte,
  solo per i ricorrenti. setProjectOfferLifecycle valida con Zod e filtra
  anche per project_id, cosi un id arbitrario non tocca altri progetti
- portale: "Attivo dal", "fino al", badge In pausa, "Canone mensile"
  invece di "Prezzo finale"; le cessate non arrivano al client
- fix: un retainer sospeso continuava a intestare i pagamenti "Totale
  Pagamento Mensile" e a sovrascrivere l'importo

Igiene nello stesso giro:
- STATUS.md riscritto: era fermo al 22 giugno e diceva che node/docker non
  sono disponibili sul server e che le migrazioni si applicano da locale
  con uno script postgres.js — il contrario della procedura reale
- rimossi ChatSection/CommentList/CommentForm, senza importatori (308 righe)
- overrides postcss>=8.5.18 e sharp>=0.35.0: 3 CVE high transitive di Next
  senza fix upstream. npm audit ora pulito, build verde

Verifica: forecast controllato sui dati veri in 5 scenari (baseline
invariata, end_date, sospeso, cessato, ripristino); portale verificato nei
4 stati; tab admin verificata con Playwright sul build di produzione
(i comandi non compaiono sulle una tantum). Dati di test ripuliti,
tabelle protette invariate 4/5/11/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:18:28 +02:00

157 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Roadmap: ClientHub
## Milestones
-**v1.0 Client Portal & Offer System** — Phases 16 (shipped 2026-06-10) — [archive](milestones/v1.0-ROADMAP.md)
-**v2.0 Business Operations Suite** — Phases 710 (shipped 2026-06-13) — [archive](milestones/v2.0-ROADMAP.md)
-**v2.1 Offer Studio + CRM** — Phases 1114 parziale (chiuso 2026-06-19, reset → v2.2)
-**v2.2 Sales Loop** — Phases 1822 (shipped 2026-06-20) — [archive](milestones/v2.2-ROADMAP.md)
-**v2.3 Email & Accesso** — Phases 2325 (shipped 2026-07-29)
- 🔨 **v2.4 Post-vendita** — Phase 13 (in corso)
## Phases
<details>
<summary>✅ v1.0 + v2.0 + v2.1 (Phases 117) — SHIPPED / CHIUSE</summary>
Vedi archivi:
- `milestones/v1.0-ROADMAP.md` — Phases 16
- `milestones/v2.0-ROADMAP.md` — Phases 710
- Phases 11, 12, 14 — Offer Studio + CRM Attio (shipped in prod)
- Phases 13, 15, 16, 17 — congelate/abbandonate/ri-scopate in v2.2
</details>
<details>
<summary>✅ v2.2 Sales Loop (Phases 1822) — SHIPPED 2026-06-20</summary>
- [x] Phase 18: Cleanup & Consolidamento (3/3 plans) — completed 2026-06-19
- [x] Phase 19: Pipeline CRM Kanban (1/1 plan) — completed 2026-06-19
- [x] Phase 20: Knowledge Base Cliente (3/3 plans) — completed 2026-06-20
- [x] Phase 21: Agente AI — generazione preventivo (1/1 plan) — completed 2026-06-20
- [x] Phase 22: Pagina pubblica preventivo (1/1 plan, PUB-03 deferred) — completed 2026-06-20
Archivio completo: [milestones/v2.2-ROADMAP.md](milestones/v2.2-ROADMAP.md)
</details>
### 🔨 v2.3 — Email & Accesso (Phases 2325)
- [x] **Phase 23: Resend Setup** — SDK Resend + `src/lib/mailer.ts` + template OTP *(l'invio preventivo è stato rinviato a v2.4 il 2026-07-28)*
- [x] **Phase 24: Schema + Whitelist Admin** — Tabelle `client_emails` e `otp_codes`, admin UI gestione whitelist + revoca sessioni
- [x] **Phase 25: OTP Gate + Sessione** — Gate OTP completo, sessione **90gg**, rate limiting, no enumeration
**Shipped 2026-07-29** (commit `27da969`), verificata end-to-end su `hub.iamcavalli.net`. SEND-01/02 rinviati a backlog.
### 🔨 v2.4 — Post-vendita (Phase 13)
- [ ] **Phase 13: Ciclo di vita dei servizi ricorrenti**`project_offers.status` + `end_date` (migr. 0016), forecast che si ferma davvero, comandi Sospendi/Riattiva/Cessa nella tab Offerte, stato dell'abbonamento visibile al cliente.
Fuori scope di questo giro, rimandato: tracciamento dei canoni mese per mese (serve una tabella nuova — `payments` è protetta e la sua riscalatura è pensata per i piani una tantum).
## Phase Details
### Phase 23: Resend Setup + Invio Preventivo
**Goal**: Admin può inviare il link `/preventivo/[slug]` via email con un click dall'admin UI
**Depends on**: Nothing — primo uso di Resend, nessuna dipendenza DB
**Requirements**: SEND-01, SEND-02
**Success Criteria** (what must be TRUE):
1. Admin fa click su "Invia preventivo" nel dettaglio lead/preventivo e l'email parte senza uscire dall'app
2. Il destinatario riceve un'email in italiano con nome cliente e link cliccabile al deck pubblico
3. L'invio usa Resend con le variabili d'ambiente configurate su Coolify (`RESEND_API_KEY`, `RESEND_FROM`)
4. In caso di errore Resend, l'admin vede un messaggio di errore chiaro nell'UI (non un crash silenzioso)
**Plans**: TBD
**UI hint**: yes
### Phase 24: Schema + Whitelist Admin
**Goal**: Admin può gestire la whitelist email di ogni cliente, con le tabelle DB pronte per l'OTP gate
**Depends on**: Phase 23 (Resend SDK già installato e variabili d'ambiente configurate)
**Requirements**: OTP-01
**Success Criteria** (what must be TRUE):
1. Admin può aggiungere una o più email alla whitelist di un cliente dalla pagina dettaglio cliente
2. Admin può rimuovere un'email dalla whitelist di un cliente
3. Le tabelle `client_emails` e `otp_codes` esistono in produzione (migration additive applicata via SSH prima del codice)
4. La migration non tocca nessuna delle tabelle protette (`clients`, `projects`, `payments`, `phases`)
**Plans**: TBD
**UI hint**: yes
### Phase 25: OTP Gate + Sessione
**Goal**: Il portale `/client/[token]/*` richiede verifica OTP email prima di mostrare la dashboard
**Depends on**: Phase 24 (tabelle `client_emails` e `otp_codes` in prod)
**Requirements**: OTP-02, OTP-03, OTP-04, OTP-05, OTP-06, OTP-07
**Success Criteria** (what must be TRUE):
1. Cliente senza sessione OTP valida vede una schermata "inserisci la tua email" al posto della dashboard
2. Inserita un'email in whitelist, il cliente riceve il codice OTP via Resend; inserendo il codice corretto ottiene accesso con cookie valido 30 giorni
3. Un'email non in whitelist non riceve OTP — il messaggio d'errore mostrato è identico a quello per email valide (no enumeration)
4. Un codice OTP non utilizzato entro 15 minuti viene rifiutato; il cliente deve richiederne uno nuovo
5. Tentativi ripetuti sugli endpoint OTP vengono bloccati dal rate limiter (no brute force)
**Plans**: TBD
## Progress
| Phase | Milestone | Plans | Status | Completed |
|-------|-----------|-------|--------|-----------|
| 16. Foundation → UX Overhaul | v1.0 | 24/24 | ✅ Done | 2026-06-10 |
| 710. Unified Catalog → CRM Pipeline | v2.0 | 12/12 | ✅ Done | 2026-06-13 |
| 11. Catalog Database-View UX | v2.1 | 4/4 | ✅ Done | 2026-06-13 |
| 12. Offer Editor Tier A/B/C | v2.1 | 5/5 | ✅ Done | 2026-06-18 |
| 13. Workspace Servizi Attivi | v2.1 | — | ❌ Congelata | — |
| 14. CRM Attio-style & Fix | v2.1 | 3/3 | ✅ Done | 2026-06-14 |
| 15. Dashboard Revenue Stats | v2.1 | — | ❌ Abbandonata | — |
| 1617. Proposal AI originale | v2.1 | — | ❌ Ri-scopata in v2.2 | — |
| 18. Cleanup & Consolidamento | v2.2 | 3/3 | ✅ Done | 2026-06-19 |
| 19. Pipeline CRM Kanban | v2.2 | 1/1 | ✅ Done | 2026-06-19 |
| 20. Knowledge Base Cliente | v2.2 | 3/3 | ✅ Done | 2026-06-20 |
| 21. Agente AI Preventivo | v2.2 | 1/1 | ✅ Done | 2026-06-20 |
| 22. Pagina Pubblica + Deck | v2.2 | 1/1 | ✅ Done | 2026-06-20 |
| 23. Resend Setup | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
| 24. Schema + Whitelist Admin | v2.3 | 1/1 | ✅ Done | 2026-07-28 |
| 25. OTP Gate + Sessione | v2.3 | 1/1 | ✅ Done | 2026-07-29 |
| 13. Ciclo di vita servizi ricorrenti | v2.4 | 1/1 | 🔨 In corso | — |
---
## Requirement Coverage (v2.3)
| Requirement | Phase |
|-------------|-------|
| SEND-01 | Phase 23 |
| SEND-02 | Phase 23 |
| OTP-01 | Phase 24 |
| OTP-02 | Phase 25 |
| OTP-03 | Phase 25 |
| OTP-04 | Phase 25 |
| OTP-05 | Phase 25 |
| OTP-06 | Phase 25 |
| OTP-07 | Phase 25 |
**Mapped: 9/9. No orphans.**
---
## Dependency Chain (v2.3)
```
Phase 23 (Resend config + SDK)
└── Phase 24 (schema DB additive: client_emails + otp_codes)
└── Phase 25 (OTP gate + sessione cookie 30gg)
```
Phase 23 first: Resend SDK e variabili d'ambiente sono infrastruttura condivisa con Phase 25 (email OTP).
Phase 24 before Phase 25: le tabelle `client_emails` e `otp_codes` devono essere in prod (via SSH migration) prima del gate.
---
## Implementation Notes (v2.3)
**Migration constraint:** `client_emails` e `otp_codes` sono nuove tabelle — migration additiva pura. SQL a mano (drizzle-kit generate rotto da Phase 8). Applicare via SSH tunnel PRIMA di pushare il codice dipendente.
**OTP middleware layer:** Il token middleware esistente (proxy.ts → `/api/internal/validate-token`) rimane invariato. Il gate OTP è uno strato aggiuntivo dopo la validazione del token, non un suo rimpiazzo.
**Resend shared infra:** La stessa istanza Resend client e le stesse variabili d'ambiente (`RESEND_API_KEY`, `RESEND_FROM`) servono sia Phase 23 (email preventivo) sia Phase 25 (email OTP). Configurare una volta in Phase 23, riusare in Phase 25.
**Security invariants (Phase 25, come consegnata):** rate limiting su entrambi gli endpoint OTP; risposta identica per email in whitelist e non; OTP 6 cifre, scade 15 minuti, monouso (`consumed_at` al primo uso), max 5 tentativi; cookie HttpOnly + Secure + SameSite=Lax, **MaxAge 90 giorni** (non 30: modificato il 2026-07-28, compensato dalla revoca admin), per-cliente. Il gate sta in cima alla `page`, non nel layout — vedi la lezione in `STATE.md`.
---
*Roadmap created: 2026-06-21 — v2.3 Email & Accesso*