### Task 2: Il questionario in pagina Sostituisce il prototipo del cliente, che salva nel browser e che il documento stesso dichiara da buttare in produzione. **Files:** - Create: `src/components/longevity/Questionario.tsx`, `src/pages/longevity/questionario.astro`, `src/pages/api/longevity/questionario.ts` - Test: `tests/longevity/questionario-pagina.test.ts` **Interfaces:** - Consumes: `testAttivi` da `registro.ts`; `salvaCompilazione`, `QUEST_VERSION`, `CAMPI_LIBERI` da `questionario.ts`; `codicePerUtente` da `anagrafica.ts` - Produces: `POST /api/longevity/questionario` → `{ sessioneId }` o errore **Le domande vengono dal registro, non dal codice.** La pagina legge `testAttivi` e costruisce i campi da lì: etichetta, tipo, minimo e massimo. Aggiungere una domanda resta una modifica ai dati — è la promessa della spec, e qui va mantenuta. ⚠️ **Tre regole che l'endpoint deve rispettare, e sono di sostanza:** 1. **Il codice cliente si ricava dalla sessione, mai dalla richiesta.** Un codice che arriva dal browser va ignorato. È la regola §8 della spec, e questo è il primo endpoint su cui si può finalmente provare. 2. **Il consenso al trattamento dei dati sanitari non è «compilato», è «dato».** Il prototipo del cliente blocca il salvataggio solo se il campo è vuoto: rispondendo **«No»** i dati vengono salvati lo stesso. È l'esatto contrario di ciò per cui il campo esiste. Qui: se il consenso non è affermativo, **la compilazione non si salva**, e la risposta lo dice chiaramente. 3. **Il consenso va registrato, non solo controllato.** Un consenso è un fatto che un domani va **dimostrato**: chi, quando, a cosa. Salvalo fra le note di profilo con la sua data e la versione del questionario, **anche quando la risposta è no** — il rifiuto è precisamente ciò di cui bisogna poter provare il rispetto. - [ ] **Step 1: Write the failing test** ```ts // tests/longevity/questionario-pagina.test.ts import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { createLongevityDb } from '../../src/lib/longevity/db'; import { seedRegistro } from '../../src/lib/longevity/registro'; import { campiDelQuestionario, salvaDalForm } from '../../src/lib/longevity/vista'; function dbPronto() { const db = createLongevityDb(':memory:'); seedRegistro(db); db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001','F')`).run(); return db; } describe('i campi del questionario vengono dal registro', () => { it('costruisce i campi leggendo il registro, non una lista nel codice', () => { const db = dbPronto(); const campi = campiDelQuestionario(db, '2026-08-22'); expect(campi.length).toBe(20); const alcol = campi.find((c) => c.id === 'q_alcol_life')!; expect(alcol.etichetta).toMatch(/alcoliche/i); expect(alcol.max).toBe(30); }); it('una domanda disattivata sparisce dal modulo senza toccare il codice', () => { const db = dbPronto(); db.prepare(`UPDATE registro_test SET attivo_a = '2026-01-01' WHERE test_id = 'q_sigarette'`).run(); const campi = campiDelQuestionario(db, '2026-08-22'); expect(campi.map((c) => c.id)).not.toContain('q_sigarette'); expect(campi.length).toBe(19); }); }); describe('il salvataggio, e il consenso', () => { const risposte = { q_riposato: 8, q_ore_sonno: 7.5 }; it('salva una compilazione quando il consenso e dato', () => { const db = dbPronto(); const esito = salvaDalForm(db, { client_code: 'ISL-0001', data: '2026-08-22', eta: 35, risposte, consensoSanitario: 'Si', liberi: {}, }); expect(esito.ok).toBe(true); const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number }; expect(n.n).toBe(1); }); it('NON salva se il consenso e rifiutato: "No" non e un consenso', () => { const db = dbPronto(); const esito = salvaDalForm(db, { client_code: 'ISL-0001', data: '2026-08-22', eta: 35, risposte, consensoSanitario: 'No', liberi: {}, }); expect(esito.ok).toBe(false); const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number }; expect(n.n).toBe(0); // niente sessione, niente misure }); it('NON salva se il consenso manca del tutto', () => { const db = dbPronto(); const esito = salvaDalForm(db, { client_code: 'ISL-0001', data: '2026-08-22', eta: 35, risposte, consensoSanitario: '', liberi: {}, }); expect(esito.ok).toBe(false); }); it('registra il consenso anche quando e rifiutato: il rifiuto va dimostrato', () => { const db = dbPronto(); salvaDalForm(db, { client_code: 'ISL-0001', data: '2026-08-22', eta: 35, risposte, consensoSanitario: 'No', liberi: {}, }); const note = db.prepare( `SELECT campo_id, testo FROM profilo_note WHERE campo_id LIKE 'consenso%'` ).all() as { campo_id: string; testo: string }[]; expect(note.length).toBeGreaterThan(0); expect(note[0].testo).toMatch(/No/); }); it('il codice cliente NON puo arrivare da chi chiama la pagina', () => { const src = readFileSync(join(process.cwd(), 'src/pages/api/longevity/questionario.ts'), 'utf8'); // il codice si prende dalla sessione dell'utente, mai dal corpo della richiesta expect(src).toMatch(/codicePerUtente/); expect(src).not.toMatch(/body\.client_code|body\.clientId|corpo\.client_code/); }); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: `npm test -- tests/longevity/questionario-pagina.test.ts` Expected: FAIL — `src/lib/longevity/vista.ts` non esiste - [ ] **Step 3: Write minimal implementation** `vista.ts` espone `campiDelQuestionario(db, alla)` — che legge `testAttivi` e tiene i soli `test_id` che iniziano per `q_` — e `salvaDalForm(db, input)`, che controlla il consenso **prima** di scrivere qualsiasi cosa, registra il consenso fra le note col suo esito e la versione del questionario, e delega a `salvaCompilazione`. L'isola React mostra i quattro blocchi uno per schermata, con avanti e indietro, e invia in fondo. L'endpoint ricava il codice cliente dalla sessione con `codicePerUtente` e passa a `salvaDalForm`. ⚠️ Il consenso **rifiutato va scritto comunque** fra le note: apri la sessione solo se il consenso è affermativo, ma la nota del consenso si scrive in entrambi i casi, legata al cliente. - [ ] **Step 4: Run test to verify it passes** Run: `npm test` — atteso 1 fallito pre-esistente, il resto verde. Poi `npx tsc --noEmit` pulito. - [ ] **Step 5: Commit** ```bash git add src/lib/longevity/vista.ts src/components/longevity/Questionario.tsx \ src/pages/longevity/questionario.astro src/pages/api/longevity/questionario.ts \ tests/longevity/questionario-pagina.test.ts git commit -m "longevity: il questionario in pagina, e il consenso che si puo negare davvero" ``` ---