# Design — Refactoring Web (PHP + Bootstrap)

> Data: 03/03/2026
> Stato: APPROVATO

---

## Contesto

Il comparativo preventivi, attualmente un'app Python desktop (tkinter + openpyxl),
deve diventare un'applicazione web hostata su server Apache shared (Tophost).
Il progetto Python viene archiviato (non cancellato) e sostituito da una versione
PHP + HTML + JS + Bootstrap.

## Decisioni prese

| Domanda | Decisione |
|---|---|
| Stack | PHP + Bootstrap 5 (CDN) + JS vanilla |
| Libreria Excel | PhpSpreadsheet (Composer locale, vendor/ caricato via FTP) |
| Storage dati | SQLite (file .db, nativo PHP, nessun server DB) |
| File Excel caricati | Conservati originali su disco + dati estratti in SQLite |
| Output | Preview HTML a schermo + download Excel (archiviato su server) |
| URL | Sottocartella di un sito esistente (es. sito.it/comparativo/) |
| Logica Excel | Riscritta interamente in PHP (nessuna dipendenza Python) |

---

## 1. Architettura e struttura cartelle

```
comparativo/                         <- root app (sottocartella del sito)
|-- index.php                        <- login page
|-- .htaccess                        <- protezione cartelle, rewrite
|
|-- config/
|   |-- app.php                      <- costanti (soglia, colori Excel, ecc.)
|   +-- database.php                 <- connessione SQLite (singleton PDO)
|
|-- storage/
|   |-- db/
|   |   +-- comparativo.sqlite       <- database unico
|   |-- uploads/                     <- file Excel caricati
|   |   +-- {comparativo_id}/        <- una sottocartella per comparativo
|   |       |-- capitolato.xlsx
|   |       +-- impresa_brugnotto.xlsx
|   +-- output/                      <- file Excel generati
|       +-- {comparativo_id}/
|           +-- comparativo_20260303.xlsx
|
|-- core/                            <- logica PHP backend
|   |-- auth.php                     <- login, sessioni, verifica ruoli
|   |-- lettore.php                  <- leggiCapitolato(), leggiPreventivo()
|   |-- generatore.php               <- genera(), foglioComparativo(), foglioRiepilogo()
|   +-- logger.php                   <- scrittura log in SQLite
|
|-- api/                             <- endpoint AJAX
|   |-- login.php
|   |-- logout.php
|   |-- comparativi.php              <- CRUD comparativi
|   |-- upload.php                   <- upload file Excel
|   |-- genera.php                   <- avvia generazione
|   |-- download.php                 <- scarica Excel generato
|   |-- utenti.php                   <- gestione utenti (admin/developer)
|   +-- log.php                      <- lettura log (developer)
|
|-- pages/                           <- pagine HTML servite da PHP
|   |-- dashboard.php                <- elenco comparativi + ricerca
|   |-- comparativo.php              <- dettaglio: upload, preview, download
|   |-- utenti.php                   <- gestione utenti
|   +-- log.php                      <- visualizzazione log
|
|-- assets/
|   |-- css/
|   |   +-- style.css                <- stili custom (palette studio)
|   |-- js/
|   |   |-- app.js                   <- navigazione, sessione, utility
|   |   |-- comparativo.js           <- upload, preview, generazione
|   |   +-- utenti.js                <- gestione utenti
|   +-- img/
|       +-- duebytes.png             <- logo DUEBYTES
|
+-- vendor/                          <- PhpSpreadsheet (Composer, FTP)
```

Protezione `.htaccess`: blocca accesso diretto a `storage/`, `config/`, `core/`.

---

## 2. Database SQLite

```sql
CREATE TABLE utenti (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    username        TEXT UNIQUE NOT NULL,
    password        TEXT NOT NULL,                  -- hash bcrypt
    ruolo           TEXT NOT NULL DEFAULT 'utente',  -- developer | admin | utente
    attivo          INTEGER NOT NULL DEFAULT 1,
    creato_da       INTEGER,
    creato_il       TEXT NOT NULL,
    ultimo_accesso  TEXT
);

CREATE TABLE comparativi (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    nome            TEXT NOT NULL,
    soglia          INTEGER NOT NULL DEFAULT 20,
    utente_id       INTEGER NOT NULL,
    creato_il       TEXT NOT NULL,
    aggiornato_il   TEXT NOT NULL,
    stato           TEXT NOT NULL DEFAULT 'bozza',  -- bozza | generato
    FOREIGN KEY (utente_id) REFERENCES utenti(id)
);

CREATE TABLE file_comparativo (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    comparativo_id  INTEGER NOT NULL,
    tipo            TEXT NOT NULL,           -- capitolato | preventivo
    nome_impresa    TEXT,
    nome_file       TEXT NOT NULL,
    percorso        TEXT NOT NULL,
    caricato_il     TEXT NOT NULL,
    voci_prezzate   INTEGER,
    voci_totali     INTEGER,
    FOREIGN KEY (comparativo_id) REFERENCES comparativi(id)
);

CREATE TABLE log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp       TEXT NOT NULL,
    livello         TEXT NOT NULL,           -- info | warning | errore
    utente_id       INTEGER,
    azione          TEXT NOT NULL,
    dettagli        TEXT,
    ip              TEXT
);
```

---

## 3. Autenticazione e ruoli

- Sessioni PHP native (session_start)
- Password: bcrypt (password_hash / password_verify)
- Utente developer creato automaticamente al primo deploy

Gerarchia:
- **developer**: tutto (utenti, admin, log sistema)
- **admin**: gestisce utenti sotto di se (crea, modifica, abilita/disabilita)
- **utente**: usa i comparativi, cambia la propria password

Protezione: ogni pagina include core/auth.php che verifica sessione + ruolo.

---

## 4. Schermate

### 4a. Login
Form username/password. Logo studio in alto. Credits DUEBYTES in basso.

### 4b. Dashboard
- Navbar: nome studio | menu (Comparativi, Utenti [admin+], Log [developer], Logout)
- Barra ricerca con filtro live JS
- Tabella: nome, data, utente, stato, N imprese, azioni
- Bottone "Nuovo comparativo"
- Ordinamento per data decrescente

### 4c. Dettaglio comparativo
1. Info: nome (editabile), soglia anomalie
2. Capitolato: upload .xlsx, voci trovate
3. Preventivi: upload multiplo, nome impresa editabile, rimuovi
4. Genera: bottone -> preview HTML tabella + download Excel
5. Possibilita di aggiungere nuovi preventivi in un secondo momento

### 4d. Gestione utenti (admin/developer)
Tabella + form modale per CRUD.

### 4e. Log (developer)
Tabella con filtri livello/data/utente, paginazione.

### 4f. Footer globale
"Sviluppato da DUEBYTES di Paolo Alberti" + logo + link duebytes.it

---

## 5. Logica core PHP

Traduzione 1:1 da Python a PHP con PhpSpreadsheet:

| Python | PHP | Logica |
|---|---|---|
| leggi_capitolato() | Lettore::leggiCapitolato() | col D numerico -> dati, resto -> testo/vuota |
| estrai_firma_intestazione() | Lettore::estraiFirma() | Cerca '#' in col A |
| trova_riga_intestazione() | Lettore::trovaRigaIntestazione() | '#' poi fallback testuale |
| leggi_preventivo() | Lettore::leggiPreventivo() | col D numerico -> prezzo in col E |
| genera_comparativo() | Generatore::genera() | Coordina lettura + scrittura |
| _genera_foglio_comparativo() | Generatore::foglioComparativo() | Stessi colori e struttura |
| _genera_foglio_riepilogo() | Generatore::foglioRiepilogo() | Stesse sezioni e calcoli |

Struttura dati interna identica: array di righe con tipo, col_a, col_b, col_c, col_d, num_voce.

---

## 6. Sicurezza

- .htaccess blocca storage/, config/, core/
- Upload: validazione estensione (.xlsx/.xls), limite 10MB, nome sanitizzato
- SQL: prepared statements (PDO) ovunque
- XSS: htmlspecialchars() su ogni output
- CSRF: token in sessione su ogni POST
- Sessioni: timeout 2h, rigenerazione ID dopo login

---

## 7. Palette e stile

Bootstrap 5 da CDN + CSS custom:

- Header/navbar: #1F4E79 (blu scuro) con "Calvi Ceschia Vigano / Architetti Associati"
- Accent/bottoni: #4F8EF7 (azzurro)
- Sfondo body: #F5F6FA
- Card/pannelli: #FFFFFF
- Testo: #1A1A2E
- Bordi: #E5E7EB

---

## 8. Credits

Footer e login: logo DUEBYTES (https://www.duebytes.it/assets/img/duebytes.png)
+ "di Paolo Alberti" + link https://www.duebytes.it/

---

## 9. Deploy

1. Sviluppo locale sul NAS con PHP + Composer
2. Composer install per PhpSpreadsheet
3. Upload FTP su Tophost (incluso vendor/)
4. Creare storage/db/ con permessi scrittura
5. Primo accesso crea il database e l'utente developer
