# ClientHub (IAMCAVALLI) — Status _Ultimo aggiornamento: 2026-08-01_ ## Stato attuale In prod su Coolify (Gitea → deploy automatico su push a `main`). Build verde, `npm audit` pulito. Milestone **v2.3 "Email & Accesso" chiusa e in produzione** dal 2026-07-29: il portale cliente non è più apribile col solo link. In corso **v2.4 Phase 13** — ciclo di vita dei servizi ricorrenti. --- ## Fatto (recente, cumulativo) - **[v2.4, in corso] Ciclo di vita dei servizi ricorrenti**: `project_offers.status` (attivo/sospeso/cessato) + `end_date` (migr. 0016, in prod). Prima un retainer non poteva finire e il forecast lo sommava a ogni mese in eterno. Comandi Sospendi/Riattiva/Cessa nella tab Offerte del progetto; il cliente vede stato, "attivo dal" e canone mensile. - **[v2.3] 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. - **[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-*.md`. - **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 (Offerta → Tier con prezzo). ## Da fare - [ ] **Whitelist portale**: dei 4 clienti solo alcuni hanno email autorizzate. Chi ha la whitelist vuota non entra nel proprio portale — si popola da `/admin/clients/` → "Accessi al portale". - [ ] **Fasi/Task dall'offerta** funzionano solo se i servizi hanno il campo **Fase** valorizzato nel Catalogo (altrimenti finiscono in "Generale"). - [ ] Micro legacy "Mantenimento" senza tier: valutare se rimuoverlo/normalizzarlo. - [ ] **Debito design**: 11 pagine ancora a palette raw invece che a token semantici. Le più pesanti: `/admin/projects/[id]` (~140 occorrenze fra i suoi tab), `/admin/offers/[id]/edit`, e tutto `/quote/[token]` (~40, ed è rivolto al cliente). Anche `ui/dialog.tsx`, che propaga il look vecchio a ogni modale. - [ ] **Backlog v2.5**: canoni mensili tracciabili (agosto pagato / settembre no) — serve una tabella nuova, `payments` è protetta e la sua riscalatura è pensata per i piani una tantum. Più PROP-03 (Stripe sul deck), PROP-04 (auto-provisioning al "Vinto"), SEND-01/02 (invio preventivo via email — il mailer è già pronto). ## 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: `cat src/db/migrations/NNNN.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk psql -U clienthub -d clienthub -v ON_ERROR_STOP=1 --single-transaction"` Il tunnel `ssh -f -N -L 54321:localhost:54321` serve solo per puntare il tooling locale (es. `npx tsx`) al DB di prod, riscrivendo l'host di `DATABASE_URL` a `127.0.0.1:54321`. - **Ordine di deploy con schema**: applicare la migrazione a prod **prima** del push (il deploy fa girare subito il codice nuovo). - **`NEXTAUTH_SECRET` locale ≠ produzione**: per costruire firme o hash validi in prod va letto da Coolify, non da `.env.local`. - **Coolify API**: credenziali in `~/.coolify.env` (formato `export VAR=…`, va sorgentato). App uuid `xsksow44g4kcoo8wocsgkscc`. Il POST su `/api/v1/applications//envs` rifiuta il campo `is_build_time` con 422. - **`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). ## 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) |