# Task 4 Report — Script di generazione PDF (Turso+R2+Puppeteer)

## Cosa implementato

### `app/scripts/pdf/template.ts`
Porting fedele di `pdf-prototype/template.js`. Unica modifica funzionale: rimossa la lettura
da filesystem (`fileToDataUri`) dentro il template. Le immagini arrivano come `allegato.dataUri`
(base64 già pronto), e il logo Alleanza arriva come `data.logoUri` nel payload passato a
`renderHtml(data)`. Layout, CSS, sezioni 1-6, matrice rischio: identici al prototipo approvato.
Adattati `require` → `import`, `module.exports` → `export function renderHtml(data)`.

### `app/scripts/pdf/data.ts`
Query di `pdf-prototype/load.js` portate su `@libsql/client` (async/await).
- `resolveDvrAgenziaId` gestisce le gemelle (legge `dvr_condiviso_da_id`, fallback a sé).
- Tutte le query sul contenuto DVR (pericoli, valutazioni, misure, incendio, attività) usano
  `dvrAgenziaId` (la sede master), mentre anagrafica e allegati usano `agenziaId` proprio.
- Tipo `AllegatoRow` con campo opzionale `dataUri?: string` precaricata da generate.ts.
- Stessa forma di ritorno di load.js (campi identici, tipi async).

### `app/scripts/pdf/generate.ts`
Orchestratore dual-mode:

**Modalità R2 (prod/CI):** scarica allegati da R2 (key = `storage_path` senza prefisso
`storage/`), genera PDF con Puppeteer, carica su R2 (`pdf/<codice_fisico>_DVR.pdf`), upsert
`dvr_pdf` stato=ready con `data_hash` via `hashFingerprintInput(loadFingerprintInput(...))`.

**Modalità locale (no R2):** legge allegati da `storage/<storage_path>` nel repo locale,
scrive il PDF in `pdf-output/<codice_fisico>_DVR.pdf` (gitignored), upsert `dvr_pdf` lo stesso
(così si può verificare il flusso completo senza credenziali R2).

Rilevamento dual-mode: tutte e 4 le env `R2_*` presenti → R2; altrimenti → locale.

Il logo Alleanza (`app/static/logo-alleanza.png`) viene letto da filesystem una volta all'avvio
e passato al template come `logoUri` base64.

**Opzione Puppeteer headless:** `headless: true` (la vecchia stringa `'new'` è stata rimossa
in Puppeteer 24.x).

## Scelte

| Decisione | Motivazione |
|---|---|
| Dual-mode immagini (R2 vs locale) | Permette test senza credenziali R2; CI rimane identico |
| Logo nel payload dati | Evita dipendenza dal filesystem dentro template.ts |
| `headless: true` invece di `'new'` | Puppeteer 24.x ha rimosso `'new'` |
| `pdf-output/` gitignored | PDF locali di test non vanno in repo |
| `import.meta.dirname` per REPO_ROOT | ESM nativo in tsx (package type=module) |

## Storage path format riscontrato

`storage/agenzie/<codice_fisico>/NN-imageN.ext`

R2 key = stessa stringa senza il prefisso `storage/` → `agenzie/<codice_fisico>/NN-imageN.ext`

## Verifica locale

Comando:
```
cd app
DATABASE_URL='file:../import-tools/db/dev.db' npx tsx scripts/pdf/generate.ts 1
```

Output:
```
[generate] R2 non configurato → modalità locale
OK 1 → locale:/.../pdf-output/22399_DVR.pdf (3877784 byte)
```

- PDF prodotto: 3.877.784 byte (~3,8 MB), inizia con `%PDF` (verificato con xxd).
- Riga `dvr_pdf` locale: `agenzia_id=1 | stato=ready | data_hash=7f8cbc46...| r2_key=pdf/22399_DVR.pdf | generato_il=2026-06-24T16:27:26.551Z`

## Verifica build Vercel

`cd app && npm run build` → OK (21.98s). Nessun riferimento a `puppeteer` o `scripts/pdf`
in `src/routes/` o `src/lib/` (verificato con grep). I warning presenti sono pre-esistenti
(libsql native binding / ws optionals — non introdotti da questo task).

## File creati/modificati

| File | Operazione |
|---|---|
| `app/scripts/pdf/template.ts` | Creato — porting template.js |
| `app/scripts/pdf/data.ts` | Creato — porting load.js async |
| `app/scripts/pdf/generate.ts` | Creato — orchestratore dual-mode |
| `app/package.json` | Modificato — aggiunto `"pdf:gen"` script + `puppeteer`/`tsx` devDeps |
| `app/package-lock.json` | Aggiornato da npm install |
| `.gitignore` | Modificato — aggiunto `pdf-output/` |

## Commit

`0f9aab0` — "PDF: script di generazione Chromium (Turso+R2) per Action e seed"

## Concern

- **Nessuno critico.** Build OK, PDF valido, dvr_pdf aggiornato.
- I warning Svelte (`state_referenced_locally` in ValutazioneCard.svelte) sono pre-esistenti,
  non introdotti da questo task.
- Lo Step 6 reale (prod/R2) non è stato eseguito per mancanza di credenziali, come da istruzioni.
