Files
clienthub/STATUS.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

5.3 KiB

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/<id> → "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/<uuid>/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)