# demo — audioguide video delle applicazioni GTS

Questo motore registra un browser vero mentre percorre un'applicazione GTS, e ne
produce un **video con commento parlato**: cartelli di passo, voce sintetica italiana
sincronizzata con le immagini, screenshot.

Non sa nulla di alcuna applicazione: apre il browser, entra senza login, mette i
cartelli, misura i tempi, sintetizza la voce e la monta in sincrono. Una guida nuova è
quindi **un solo file** con le frasi, i dati di prova e la sequenza dei passi.

Le sei guide di `gtserp` (`gtserp/tools/demo_*.js`) sono il campionario da cui copiare.

> **Non serve a chi non registra guide.** Sono file inerti: nessuna applicazione li
> include, non toccano il framework, e chi clona la shared si porta 64 kB in più e
> nient'altro. I prerequisiti pesanti (Playwright, Chromium, Piper, il modello vocale)
> **non** stanno nel repository e si installano solo su chi vuole registrare.

---

## 1. Preparare la macchina (una volta sola)

```bash
cd /opt/gts/shared/demo
./prepara.sh
```

Installa e verifica quello che serve: Node e Playwright con il browser Chromium,
`ffmpeg` per il montaggio, Piper per la voce, e il modello vocale italiano.
Con `--controlla` dice soltanto cosa manca, senza installare nulla.

Il **modello vocale** (61 MB) non sta in git — è un binario già compresso, e in git ogni
aggiornamento aggiungerebbe una copia intera alla storia per sempre. Si scarica a parte:

```bash
./scarica_voce.sh
```

Ingombro complessivo sulla macchina, fuori dal repository: circa 250 MB fra browser,
libreria e modello vocale.

---

## 2. Scrivere una guida

```bash
cp /opt/gts/shared/demo/esempio.js /opt/gts/<app>/tools/demo_qualcosa.js
```

Il file ha tre parti, e **solo queste tre** vanno scritte:

```javascript
const { avvia } = require('/opt/gts/shared/demo/motore');

const D = { … };                    // i dati di prova

const NARRAZIONE = { 1: "…", … };   // una frase per passo

async function percorso(h) {        // la sequenza delle schermate
    await h.vaiA('clienti.php');
    await h.titolo('Creare un cliente', 'Anagrafiche › Clienti');
    await h.premi('Inserisci');
    await h.scrivi('CODICE', D.cliente.codice);
    await h.premi('Registra');
}

avvia({ titolo: '…', video: 'qualcosa', narrazione: NARRAZIONE }, percorso);
```

## 3. Registrare

```bash
cd /opt/gts/<app>/tools
node demo_qualcosa.js                              # il video muto e i tempi
node /opt/gts/shared/demo/aggiungi_voce.js qualcosa # ci monta la voce
/opt/gts/shared/demo/verifica_voce.sh qualcosa      # controlla le sovrapposizioni
```

Con `--lento` le pause sono più lunghe, per un video più leggibile.

Il video finisce in `docs/` dell'applicazione, oppure dove indica `GTS_DOCS`.

---

## I comandi disponibili

Non serve conoscere Playwright. L'oggetto `h` passato al percorso offre:

### Navigazione e struttura del racconto

| Comando | Cosa fa |
|---|---|
| `h.vaiA('pagina.php')` | apre una pagina e attende che sia pronta |
| `h.titolo(testo, sottotitolo)` | annuncia un passo: cartello, screenshot, frase del commento |
| `h.attendi(ms)` | pausa esplicita |
| `h.log(testo)` | annota una riga nel resoconto della corsa |

### Compilare le maschere

| Comando | Cosa fa |
|---|---|
| `h.scrivi(id, valore)` | compila un campo |
| `h.scegli(id, testo)` | sceglie in una tendina, cercando per codice o descrizione |
| `h.accendi(id)` | attiva un interruttore |
| `h.impostaRicerca(id, valore)` | valorizza un campo con la lente (`hb_search_generic`) |
| `h.scriviInLinea(id, valore)` | campo a modifica in linea (`hb_edit_inline`) |
| `h.idArticolo/idCliente/idFornitore(codice)` | ricava l'id interno da un codice |

### Premere

| Comando | Cosa fa |
|---|---|
| `h.premi(testo)` | pulsante con quel testo, nel pannello in cima |
| `h.premiTitolo(titolo)` | pulsante con la sola icona (l'etichetta sta nel `title`) |
| `h.premiIcona(nome, riga)` | icona di riga in griglia, es. `'mdi:pencil'` |
| `h.premiSelettore(css)` | qualunque elemento, per selettore |
| `h.chiudiMessaggio()` | chiude info e conferme — **si ferma sugli errori** |
| `h.conferma()` | conferma un `$$.confirm` |
| `h.compilaMessageBox({campo: valore})` | riempie e conferma un MessageBox |

### Mostrare l'interfaccia

| Comando | Cosa fa |
|---|---|
| `h.evidenzia(css)` | contorna un elemento |
| `h.evidenziaBarra([nomi])` | illustra a uno a uno i pulsanti in alto a destra |
| `h.evidenziaReadonly()` | evidenzia i campi in sola lettura del pannello |
| `h.apriMenu(area)` / `h.apriTendina(id)` / `h.apriCalendario(id)` | apre e mostra |
| `h.ordinaColonna(testo)` | ordina la griglia per quella colonna |
| `h.ctrlClickIntestazione()` | apre il riquadro di esportazione |

---

## Come sono scritte le frasi

Sono la parte che nessun programma può generare: percorrere un'applicazione è
automatizzabile, capire che cosa vale la pena spiegare no. Sei regole ricavate
facendo le prime sei guide.

**Scrivere l'italiano con gli accenti veri.** `perché`, `già`, `così`, `è`, `più`, non
`perche'`, `gia'`. La voce sintetica legge la forma con l'apostrofo come una parola
tronca: misurato, `perche'` dura 0,27 secondi contro 0,55 di `perché` — metà del tempo,
quindi metà delle sillabe. Si sente, ed è il difetto che rende la voce "artificiale".

**Dire il perché, non ripetere il titolo.** Il titolo si legge già a schermo. La frase
deve aggiungere quello che non si vede: perché quel campo esiste, cosa succederebbe
sbagliandolo, dove riemergerà quel dato più avanti.

**I numeri in lettere.** "quattrocentocinquanta euro", non "450 €".

**Rileggerle tutte di seguito prima di registrare.** Stanno in una tabella unica in cima
al file proprio per questo: è l'unico modo per accorgersi se il commento suona legnoso o
ripete sempre le stesse formule.

**Non affermare ciò che non si vede.** Se la frase dice che l'ordine resta parziale, il
video deve mostrarlo. È già capitato di scrivere il comportamento atteso invece di
quello reale, e un video che spiega una cosa inesistente è peggio di nessun video.

**Le griglie devono essere popolate.** Parlare di permessi mentre la lista è vuota non
insegna nulla: se la pagina ha filtri obbligatori (società, applicazione) vanno scelti
prima, e i dati devono esistere.

---

## Il ritmo e le sovrapposizioni

Ogni passo attende **metà** del tempo di lettura della propria frase, poi agisce: la
spiegazione prosegue mentre l'operazione è in corso, così non c'è tempo morto fra il
concetto e ciò che lo illustra.

La garanzia contro le voci sovrapposte è un'altra: **il passo successivo non comincia
finché la frase precedente non è finita**. Non basta sperare che le azioni durino
abbastanza — è già successo, ed è il difetto più fastidioso di un video parlato.

La durata si stima a **16 caratteri al secondo più otto decimi di respiro**. Misurata
frase per frase, la voce va da 17,9 a 20,8 caratteri al secondo: usare la media
significherebbe sbagliare per difetto proprio sulle frasi lette più lentamente, che sono
quelle che poi si accavallano.

**`verifica_voce.sh` è il controllo che conta.** `aggiungi_voce.js` confronta le durate
previste — è un controllo sulle intenzioni. `verifica_voce.sh` misura invece il livello
sonoro nella frazione di secondo che precede ogni frase sull'**audio prodotto**: se lì
c'è ancora voce, la precedente non era finita. Va eseguito dopo ogni registrazione.

---

## La sessione applicativa

Il motore crea un file temporaneo `_demo_session.php` nella cartella dell'applicazione,
lo usa per entrare senza passare dal login, e **lo rimuove al termine anche in caso di
errore**. Apre una sessione senza autenticazione: non deve finire né in un repository né
su un server raggiungibile.

Se una registrazione viene interrotta a forza (`kill -9`), verificare che non sia
rimasto.

Quel file chiama `$INTL->init()`, come fa il lanciatore dopo il login. Senza, la sessione
resta priva di lingua e formato data, `bootstrap-datepicker` solleva *"Invalid date
format."* e `gvSetupPage()` si interrompe a metà: le tendine restano liste grezze con
tutte le voci in vista. È un sintomo che sembra estetico e nasce da una pagina
inizializzata a metà.

---

## Pilotare un'altra applicazione

Una guida può mostrare un'applicazione e depositare il video altrove — è così che la
guida su `hq` finisce fra quelle di `gtserp`, dove la pagina delle guide le elenca tutte
insieme:

```javascript
avvia({
    video:  'amministrazione',
    base:   'http://localhost/hq',
    appdir: '/opt/gts/hq',
    …
}, percorso);
```

Oppure dall'ambiente: `GTS_BASE`, `GTS_APPDIR`, `GTS_DOCS`, `GTS_VOCE`.

---

## Distribuire i filmati

I video non stanno in git: sono decine di MB l'uno, il `.webm` è già compresso — quindi
git non fa delta e ogni rifacimento aggiungerebbe una copia intera alla storia — e sono
documentazione di prodotto, identica su ogni installazione.

In `gtserp/tools` ci sono `guide_pubblica.sh` e `guide_scarica.sh`, che li spostano via
FTP e verificano le dimensioni a trasferimento concluso. Sono un buon punto di partenza
da copiare per un'altra applicazione.
