# ClientHub (IAMCAVALLI) — Status _Ultimo aggiornamento: 2026-08-08_ Questo è **l'unico documento narrativo** del progetto: a che punto siamo, cosa manca, cosa abbiamo imparato. `.planning/STATE.md` è il digest che leggono i comandi `/gsd-*` — non raddoppia questo file, ci rimanda. ## Stato attuale In produzione su `hub.iamcavalli.net` (Coolify/Hetzner, deploy automatico su push a `main` via Gitea). Build verde, `npm audit` pulito. Milestone **v2.4 "Post-vendita"** — Phase 13 e Phase 26 consegnate e in produzione. Nessun lavoro in sospeso non committato. | Milestone | Fasi | Stato | |---|---|---| | v2.4 Post-vendita | 13, 26 | 🔨 in corso — consegnato tutto ciò che era pianificato | | v2.3 Email & Accesso | 23–25 | ✅ shipped 2026-07-29, verificata E2E | | v2.2 Sales Loop | 18–22 | ✅ shipped 2026-06-20 | | v2.1 Offer Studio + CRM | 11, 12, 14 | ✅ chiusa per reset 2026-06-19 | | v1.0 + v2.0 | 1–10 | ✅ shipped giugno 2026 | ## Fatto ### v2.4 — Post-vendita - **[Phase 13, prod 2026-08-01] Ciclo di vita dei servizi ricorrenti.** `project_offers.status` (attivo/sospeso/cessato) + `end_date` (migr. 0016). Prima un retainer non poteva finire e il forecast lo sommava a ogni mese in eterno. Comandi Sospendi/Riattiva/Cessa nella tab Offerte; il cliente vede stato, "attivo dal" e canone mensile. Dettaglio: [`.planning/phases/13-…/13-SUMMARY.md`](.planning/phases/13-ciclo-vita-servizi-ricorrenti/13-SUMMARY.md) - **[Phase 26, prod 2026-08-08] Anteprima admin del portale + toggle password.** `?preview=1` più una sessione Auth.js valida apre il portale di un cliente in **sola lettura**, senza passare dal gate OTP. Deviazione consapevole dal vincolo LOCKED #4, annotata in `CLAUDE.md`. Dettaglio: [`.planning/phases/26-…/26-SUMMARY.md`](.planning/phases/26-anteprima-admin-e-login/26-SUMMARY.md) ### v2.3 — Email & Accesso - **Gate OTP sul portale cliente**: whitelist email per cliente (`client_emails`), codice a 6 cifre via Resend, sessione firmata 90 giorni, revoca in blocco dall'admin (`clients.sessions_valid_from`). Migr. 0015. Verificato E2E in produzione. Archivio: [`v2.3-ROADMAP.md`](.planning/milestones/v2.3-ROADMAP.md) ### Prima di v2.3 - **[2026-07] Audit di sicurezza chiuso**: 4 vulnerabilità risolte e deployate, slug clienti ruotati a 12 char CSPRNG, `INTERNAL_SECRET` e `ADMIN_PASSWORD` configurati su Coolify. Report in [`.planning/security/`](.planning/security/). - **Design system "Quiet Luxury"**: dashboard, liste (Clienti/Offerte/Catalogo/ Preventivi/Progetti), Conversazioni, Impostazioni, Pipeline+Kanban, dettaglio Lead e portale cliente base sono a token semantici e dual-theme. - **Tassonomie centralizzate** in Impostazioni (modello Notion, pool persistenti `src/lib/taxonomy.ts`). - **Lead → Cliente**: `clients.email/phone` + `leads.archived` (migr. 0011). `convertLeadToClient` riusa `createClientCore`, porta i transcript, archivia il lead mantenendo "won". - **Offerta → Fasi/Task**: `importOfferIntoProject` crea fasi raggruppando i servizi del tier per `services.fase`. - **Offerte (modello + UI)**: `offer_macros.offer_type` ('una_tantum'|'retainer') + toggle "Modalità" nell'editor (migr. 0012). Tab Offerte a 2 step. ## Da fare - [ ] **Whitelist portale**: dei 4 clienti solo uno ha email autorizzate. Chi ha la whitelist vuota **non entra nel proprio portale** — si popola da `/admin/clients/` → "Accessi al portale", poi va reinviato il link. - [ ] **Fasi/Task dall'offerta** funzionano solo se i servizi hanno il campo **Fase** valorizzato nel Catalogo (altrimenti finiscono in "Generale"). - [ ] **Debito design (DEBT-01)**: **~40 file, ~450 occorrenze** di palette Tailwind raw e hex literal invece dei token semantici. I cluster: `/admin/projects/[id]` e i suoi tab (~182), `/admin/offers/[id]/edit` (~79), `/admin/clients/[id]` (~59), tutto `/quote/[token]` (~48, ed è rivolto al cliente), `ChatPanel` (37), più `ui/dialog.tsx` che propaga il look vecchio a ogni modale. *Esclusi perché legittimi:* `AdminSidebar` (eccezione brand documentata), `src/lib/mailer.ts` (HTML email), i colori di stato di `StatusBadge` (sanzionati dal design system, hanno già le varianti `dark:`). - [ ] Micro legacy "Mantenimento" senza tier: valutare se rimuoverlo/normalizzarlo. - [ ] **Backlog**: canoni mensili tracciabili (RET-06 — serve una tabella nuova), PROP-03 (Stripe sul deck), PROP-04 (auto-provisioning al "Vinto"), SEND-01/02 (invio preventivo via email — il mailer è già pronto). Elenco completo in [`.planning/REQUIREMENTS.md`](.planning/REQUIREMENTS.md). ## Lezioni operative Cose imparate a caro prezzo. Non sono documentazione di feature: sono trappole in cui si ricasca. - **Il gate OTP non va nel layout.** Prima implementazione: gate in `client/[token]/layout.tsx` che rendeva `` al posto di `{children}`. **Non protegge nulla.** Nell'App Router il segmento `page` è renderizzato in parallelo al layout: la dashboard spariva a schermo ma fasi, task e pagamenti restavano leggibili nel payload RSC dell'HTML (46.907 byte → 17.594 dopo il fix). Il gate sta in cima alla `page`, prima di ogni query, via `getClientGate()`. **Ogni nuova route sotto `/client/[token]/` deve fare lo stesso.** - **Ricreare il dominio su Resend rigenera la chiave DKIM.** Se il dominio torna `failed`, non fidarsi di valori DKIM annotati in passato: rileggerli da `GET /domains` e confrontarli con `dig +short TXT resend._domainkey.iamcavalli.net @8.8.8.8`. - **`.env.local` non è allineato a produzione.** `ADMIN_PASSWORD` e `NEXTAUTH_SECRET` sono stati ruotati il 2026-07-28 **solo su Coolify**. Per costruire firme o hash validi in prod vanno letti da Coolify, non da `.env.local`. - **L'API Coolify rifiuta `is_build_time`** con 422 sul POST a `/api/v1/applications//envs`: mandare solo `key`, `value`, `is_preview`. - **Playwright non funziona contro `npm run dev`**: la CSP blocca `eval` e i client component non si idratano. Serve il build di produzione. ## Note tecniche - **Il DB di `.env.local` È la produzione** (178.104.27.55). Non esiste un database di sviluppo separato: qualunque cosa si esegua in locale scrive su dati reali. Verificare i conteggi delle tabelle protette prima e dopo ogni prova. - **Migrazioni**: SQL scritto a mano in `src/db/migrations/` (`drizzle-kit generate` è rotto — vanno tenuti in sync `schema.ts` e l'SQL). Si applicano **da locale via SSH + docker exec**, senza tunnel — procedura completa in `CLAUDE.md`. Il tunnel `ssh -f -N -L 54321:localhost:54321` serve solo per puntare il tooling locale (es. `npx tsx`) al DB di prod. - **Ordine di deploy con schema**: applicare la migrazione a prod **prima** del push (il deploy fa girare subito il codice nuovo). - **Coolify API**: credenziali in `~/.coolify.env` (formato `export VAR=…`, va sorgentato). App uuid `xsksow44g4kcoo8wocsgkscc`. - **`overrides` in package.json** forzano `postcss >= 8.5.18` e `sharp >= 0.35.0`: le versioni che Next si porta dietro hanno CVE high e non c'è fix upstream. Se un aggiornamento di Next rompe qualcosa, è il primo posto dove guardare. - `offer_micros` non ha `created_at` (no "tier più vecchio" affidabile). - **Debito tecnico non bloccante**: tabelle legacy `service_catalog` / `offer_services` / `offer_micro_services` come deadweight; `createService` / `serviceSchema` dead code in `src/app/admin/catalog/actions.ts`. ## File chiave | File | Scopo | |---|---| | src/proxy.ts | Middleware (Next 16 lo chiama `proxy`, non `middleware`) | | src/lib/client-gate.ts | Gate OTP — va chiamato in cima a ogni page sotto `/client/[token]/` | | src/lib/otp.ts, client-session.ts | Codici OTP e cookie di sessione 90gg | | src/lib/mailer.ts | Unico punto di invio email (Resend) | | src/lib/forecast-queries.ts | Forecast 12 mesi — rispetta stato e `end_date` dei retainer | | src/lib/taxonomy.ts | Pool tassonomie (Impostazioni) | | src/app/admin/projects/project-actions.ts | `importOfferIntoProject`, `setProjectOfferLifecycle`, piani pagamento | | src/components/admin/tabs/OffersTab.tsx | Tab Offerte + comandi ciclo di vita | | src/lib/admin-queries.ts / client-view.ts | I due layer separati: admin vs proiezioni client-safe | | src/db/migrations/ | 0011 (email/phone), 0012 (offer_type), 0015 (OTP), 0016 (ciclo di vita) | ## Dove sta il resto | Cosa | Dove | |---|---| | Regole per Claude, procedure deploy/DB, vincoli LOCKED | `CLAUDE.md` | | Digest di stato per i comandi `/gsd-*` | `.planning/STATE.md` | | Requisiti e backlog della milestone corrente | `.planning/REQUIREMENTS.md` | | Roadmap e storico milestone | `.planning/ROADMAP.md`, `.planning/MILESTONES.md` | | Archivi delle milestone chiuse | `.planning/milestones/` | | Fasi della milestone in corso | `.planning/phases/` | | Report dell'audit di sicurezza (chiuso) | `.planning/security/` | | Design system e mock per pagina | `design-reference/` |