Files
nis2-agile/public/onboarding.html
T
DevEnv nis2-agileandClaude Opus 4.8 a982797fa2 [FEAT] ARIA voce naturale (proxy TTS nexus-voice-ms) + toggle voce — Fase C
Porting voce TRPG/AllTax (ElevenLabs "Sarah") sull'ARIA chat NIS2:
- AiController::tts() + route POST /api/ai/tts: proxy same-origin a nexus-voice-ms
  (config VOICE_MS_URL=172.21.0.1:4215, voce TTS_VOICE_ID=EXAVITQu4vr4xnSDxMaL,
  eleven_turbo_v2_5). requireAuth, cache MP3 sha256 (sys_get_temp_dir/nis2-tts-cache),
  validazioni (text<=800, voice_id allowlist), stream audio/mpeg, 502 se upstream non-audio.
- common.js: speak(answer) dopo ogni risposta ARIA (voce discreta vol .55, markdown
  strip), toggle 🔊/🔇 nell'header (muto persistito localStorage nis2_aria_voice),
  autoplay best-effort.
Smoke: voice-ms da nis2-app HTTP 200 audio/mpeg 19KB; /api/ai/tts 401 senza auth.
Reachability OK (no allowlist VOX necessaria). Cache-buster common.js -> 20260624g.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:58:05 +02:00

1007 lines
68 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Onboarding - NIS2 Agile</title>
<!-- Bootstrap Italia v2.18.1 — self-hostato da /vendor (MAI CDN). Variante "-bi" della onboarding.html. -->
<link rel="stylesheet" href="vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
<style>
body { background: #f5f6f7; }
.wizard-page { min-height: 100vh; display: flex; flex-direction: column; }
.wizard-header { background: #fff; border-bottom: 1px solid #d4d8db; padding: 16px 32px;
display: flex; align-items: center; gap: 12px; }
.wizard-header-logo { width: 36px; height: 36px; background: #06c; border-radius: 6px;
display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.wizard-header-logo svg { width: 20px; height: 20px; fill: #fff; }
.wizard-header-text h1 { font-size: 1.125rem; font-weight: 700; color: #17324d; margin: 0; }
.wizard-header-text span { font-size: .7rem; color: #5a6772; text-transform: uppercase; letter-spacing: .05em; }
.wizard-stepper { background: #fff; border-bottom: 1px solid #d4d8db; padding: 20px 32px; }
.stepper { display: flex; align-items: center; justify-content: center; max-width: 800px; margin: 0 auto; }
.stepper-step { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
.stepper-number { width: 32px; height: 32px; border-radius: 50%; background: #e6e9ed; color: #5a6772;
display: flex; align-items: center; justify-content: center; font-size: .8rem; font-weight: 700;
flex-shrink: 0; transition: all .2s; }
.stepper-label { font-size: .8rem; font-weight: 500; color: #5a6772; transition: color .2s; }
.stepper-step.active .stepper-number { background: #06c; color: #fff; box-shadow: 0 0 0 4px rgba(0,102,204,.15); }
.stepper-step.active .stepper-label { color: #06c; font-weight: 600; }
.stepper-step.completed .stepper-number { background: #008758; color: #fff; }
.stepper-step.completed .stepper-label { color: #008758; }
.stepper-connector { width: 40px; height: 2px; background: #d4d8db; margin: 0 8px; flex-shrink: 0; transition: background .2s; }
.stepper-connector.completed { background: #008758; }
.wizard-body { flex: 1; display: flex; justify-content: center; padding: 40px 24px 60px; }
.wizard-content { width: 100%; max-width: 800px; }
.wizard-step { display: none; }
.wizard-step.active { display: block; }
.wizard-step-title { font-size: 1.5rem; font-weight: 700; color: #17324d; margin-bottom: 8px; }
.wizard-step-subtitle { font-size: .9375rem; color: #5a6772; margin-bottom: 32px; line-height: 1.6; }
.form-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 20px; }
.form-grid-2 .form-group-full { grid-column: 1 / -1; }
.profile-form-wrapper { max-width: 520px; margin: 0 auto; }
.wizard-loading { display: none; text-align: center; padding: 48px 24px; }
.wizard-loading.active { display: block; }
.wizard-loading-text { font-size: 1rem; font-weight: 600; color: #17324d; margin-bottom: 4px; }
.wizard-loading-subtext { font-size: .8125rem; color: #5a6772; }
.data-source-banner { display: none; }
.data-source-banner.visible { display: block; }
/* Classification result (BI palette) */
.classification-result { padding: 32px; border-radius: 8px; border: 2px solid #d4d8db; text-align: center; margin-bottom: 24px; }
.classification-result.essential { border-color: #06c; background: #e6f0ff; }
.classification-result.important { border-color: #e58a00; background: #fff5e6; }
.classification-result.not-applicable { border-color: #d4d8db; background: #f5f6f7; }
.classification-result.voluntary { border-color: #06c; background: #e6f0ff; }
.classification-result-icon { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; }
.classification-result.essential .classification-result-icon { background: #06c; color: #fff; }
.classification-result.important .classification-result-icon { background: #e58a00; color: #fff; }
.classification-result.not-applicable .classification-result-icon { background: #b1b9c0; color: #fff; }
.classification-result.voluntary .classification-result-icon { background: #06c; color: #fff; }
.classification-result-icon svg { width: 32px; height: 32px; fill: currentColor; }
.classification-result-label { font-size: 1.5rem; font-weight: 800; margin-bottom: 8px; }
.classification-result.essential .classification-result-label { color: #06c; }
.classification-result.important .classification-result-label { color: #e58a00; }
.classification-result.not-applicable .classification-result-label { color: #5a6772; }
.classification-result.voluntary .classification-result-label { color: #06c; }
.classification-result-desc { font-size: .875rem; color: #5a6772; line-height: 1.6; max-width: 600px; margin: 0 auto; }
.voluntary-section { display: none; margin-bottom: 24px; }
.voluntary-section.visible { display: block; }
.voluntary-card { border: 2px solid #b3d1ff; border-radius: 8px; padding: 24px; display: flex; align-items: flex-start; gap: 16px; background: #fff; }
.voluntary-card.checked { border-color: #06c; background: #e6f0ff; }
.voluntary-card input[type="checkbox"] { width: 20px; height: 20px; flex-shrink: 0; margin-top: 2px; }
.voluntary-card h4 { font-size: .9375rem; font-weight: 700; color: #17324d; margin-bottom: 6px; }
.voluntary-card p { font-size: .8125rem; color: #5a6772; line-height: 1.6; margin: 0; }
.summary-item-label { font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: #5a6772; margin-bottom: 4px; }
.summary-item-value { font-size: .9375rem; font-weight: 500; color: #17324d; }
.summary-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.wizard-actions { display: flex; justify-content: space-between; align-items: center; padding-top: 32px; border-top: 1px solid #d4d8db; margin-top: 32px; }
.wizard-actions-right { display: flex; gap: 12px; }
.auto-filled .form-control, .auto-filled .form-select, .auto-filled select { border-color: #008758; background: #f0faf4; }
.auto-fill-badge { display: inline-flex; align-items: center; padding: 2px 8px; margin-left: 8px; font-size: .65rem; font-weight: 600; color: #008758; background: #d6f0e2; border-radius: 100px; text-transform: uppercase; vertical-align: middle; }
.ico { width: 1.1em; height: 1.1em; fill: currentColor; vertical-align: -.18em; }
@media (max-width: 768px) {
.wizard-header { padding: 12px 16px; }
.wizard-stepper { padding: 16px; overflow-x: auto; }
.stepper { min-width: 600px; }
.stepper-label { display: none; }
.wizard-body { padding: 24px 16px 40px; }
.form-grid-2, .summary-grid { grid-template-columns: 1fr; }
.wizard-step-title { font-size: 1.25rem; }
}
</style>
<!-- PWA:start -->
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#0066CC">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="NIS2 Agile">
<meta name="application-name" content="NIS2 Agile">
<script src="/js/pwa.js?v=20260614" defer></script>
<!-- PWA:end -->
</head>
<body>
<div class="wizard-page">
<!-- Header -->
<div class="wizard-header">
<div class="wizard-header-logo">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/>
<path d="M10 12.5l-2-2-1.41 1.41L10 15.32l5.41-5.41L14 8.5l-4 4z"/>
</svg>
</div>
<div class="wizard-header-text">
<h1>NIS2 Agile</h1>
<span>Configurazione Iniziale</span>
</div>
</div>
<!-- Stepper -->
<div class="wizard-stepper">
<div class="stepper" id="stepper">
<div class="stepper-step active" data-step="1">
<div class="stepper-number">1</div>
<span class="stepper-label">Partita IVA</span>
</div>
<div class="stepper-connector" data-after="1"></div>
<div class="stepper-step" data-step="2">
<div class="stepper-number">2</div>
<span class="stepper-label">Dati Aziendali</span>
</div>
<div class="stepper-connector" data-after="2"></div>
<div class="stepper-step" data-step="3">
<div class="stepper-number">3</div>
<span class="stepper-label">Il Tuo Profilo</span>
</div>
<div class="stepper-connector" data-after="3"></div>
<div class="stepper-step" data-step="4">
<div class="stepper-number">4</div>
<span class="stepper-label">Classificazione</span>
</div>
</div>
</div>
<!-- Wizard Body -->
<div class="wizard-body">
<div class="wizard-content">
<!-- ═══ STEP 1: Partita IVA ═══ -->
<div class="wizard-step active" id="step-1">
<h2 class="wizard-step-title">Inserisci la Partita IVA dell'azienda</h2>
<p class="wizard-step-subtitle">Inserisci la Partita IVA dell'azienda da configurare. I dati verranno recuperati automaticamente dal registro delle imprese.</p>
<div class="card card-bg has-bkg-grey shadow" style="max-width:520px; margin:0 auto;">
<div class="card-body">
<div class="form-group">
<label class="active" for="s1-vat">Partita IVA <span aria-hidden="true">*</span></label>
<div style="display:flex; gap:.5rem; align-items:stretch;">
<span style="display:flex;align-items:center;padding:0 12px;background:#e6e9ed;border:1px solid #b1b9c0;border-radius:4px;font-weight:600;color:#5a6772;">IT</span>
<input type="text" id="s1-vat" class="form-control" placeholder="12345678901" maxlength="11"
style="flex:1;" inputmode="numeric" oninput="this.value=this.value.replace(/\D/g,'')">
</div>
<small class="form-text">11 cifre numeriche senza prefisso IT</small>
</div>
<div id="s1-result" class="d-none" role="status"></div>
<button class="btn btn-primary w-100" id="s1-fetch-btn" onclick="step1FetchCompany()">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-search"></use></svg>
Cerca Azienda
</button>
</div>
</div>
<div class="wizard-loading" id="s1-loading" aria-live="polite">
<div class="progress-spinner progress-spinner-active size-sm mb-2"><span class="visually-hidden">Caricamento...</span></div>
<div class="wizard-loading-text">Ricerca in corso...</div>
<div class="wizard-loading-subtext">Interrogazione registro imprese</div>
</div>
<div style="text-align:center; margin-top:1.5rem;">
<button class="btn btn-link" onclick="step1ManualEntry()">
Non trovi l'azienda? Inserisci manualmente
</button>
</div>
<div class="wizard-actions" style="margin-top:2rem;">
<div></div>
<div class="wizard-actions-right">
<button class="btn btn-primary" id="s1-continue-btn" onclick="goToStep(2)" style="display:none;">
Continua
<svg class="ico ms-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-right"></use></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 2: Dati Aziendali ═══ -->
<div class="wizard-step" id="step-2">
<h2 class="wizard-step-title">Verifica e Completa i Dati Aziendali</h2>
<p class="wizard-step-subtitle">Controlla i dati qui sotto e completa le informazioni mancanti.</p>
<div class="data-source-banner alert alert-success" id="data-source-banner" role="status">
<span id="data-source-text"></span>
</div>
<div class="card card-bg has-bkg-grey shadow">
<div class="card-body">
<div class="form-grid-2">
<div class="form-group form-group-full">
<label class="active" for="company-name">Ragione Sociale <span aria-hidden="true">*</span></label>
<input type="text" id="company-name" class="form-control" placeholder="Es. Acme S.r.l." required>
</div>
<div class="form-group">
<label class="active" for="vat-number">Partita IVA</label>
<input type="text" id="vat-number" class="form-control" placeholder="IT12345678901" maxlength="16">
</div>
<div class="form-group">
<label class="active" for="fiscal-code">Codice Fiscale</label>
<input type="text" id="fiscal-code" class="form-control" placeholder="Es. 12345678901" maxlength="16">
</div>
<div class="form-group form-group-full">
<label class="active" for="address">Indirizzo</label>
<input type="text" id="address" class="form-control" placeholder="Via Roma, 1">
</div>
<div class="form-group">
<label class="active" for="city">Citta'</label>
<input type="text" id="city" class="form-control" placeholder="Es. Milano">
</div>
<div class="form-group">
<label class="active" for="website">Sito Web</label>
<input type="text" id="website" class="form-control" placeholder="https://www.esempio.it">
</div>
<div class="form-group">
<label class="active" for="company-email">Email Aziendale</label>
<input type="email" id="company-email" class="form-control" placeholder="info@azienda.it">
</div>
<div class="form-group">
<label class="active" for="company-phone">Telefono Aziendale</label>
<input type="text" id="company-phone" class="form-control" placeholder="+39 02 1234567">
</div>
<div class="form-group form-group-full">
<label class="active" for="sector">Settore NIS2 <span aria-hidden="true">*</span></label>
<select id="sector" class="form-select select-pdnd" required>
<option value="">-- Seleziona settore --</option>
<optgroup label="Settori ad Alta Criticita' (Allegato I)">
<option value="energy_electricity">Energia - Elettricita'</option>
<option value="energy_district_heating">Energia - Teleriscaldamento</option>
<option value="energy_oil">Energia - Petrolio</option>
<option value="energy_gas">Energia - Gas</option>
<option value="energy_hydrogen">Energia - Idrogeno</option>
<option value="transport_air">Trasporti - Aereo</option>
<option value="transport_rail">Trasporti - Ferroviario</option>
<option value="transport_water">Trasporti - Marittimo/Fluviale</option>
<option value="transport_road">Trasporti - Stradale</option>
<option value="banking">Banche</option>
<option value="financial_markets">Infrastrutture Mercati Finanziari</option>
<option value="health">Sanita'</option>
<option value="drinking_water">Acqua Potabile</option>
<option value="waste_water">Acque Reflue</option>
<option value="digital_infrastructure">Infrastruttura Digitale</option>
<option value="ict_service_management">Gestione Servizi ICT (B2B)</option>
<option value="public_administration">Pubblica Amministrazione</option>
<option value="space">Spazio</option>
</optgroup>
<optgroup label="Altri Settori Critici (Allegato II)">
<option value="postal_courier">Servizi Postali e Corrieri</option>
<option value="waste_management">Gestione Rifiuti</option>
<option value="chemicals">Fabbricazione Prodotti Chimici</option>
<option value="food">Produzione e Distribuzione Alimentare</option>
<option value="manufacturing_medical">Fabbricazione - Dispositivi Medici</option>
<option value="manufacturing_computers">Fabbricazione - Computer/Elettronica</option>
<option value="manufacturing_electrical">Fabbricazione - Apparecchiature Elettriche</option>
<option value="manufacturing_machinery">Fabbricazione - Macchinari</option>
<option value="manufacturing_vehicles">Fabbricazione - Autoveicoli</option>
<option value="manufacturing_transport">Fabbricazione - Altri Mezzi di Trasporto</option>
<option value="digital_providers">Fornitori Servizi Digitali</option>
<option value="research">Organizzazioni di Ricerca</option>
</optgroup>
<optgroup label="Altro">
<option value="other">Altro Settore</option>
</optgroup>
</select>
</div>
<div class="form-group">
<label class="active" for="employee-count">Numero Dipendenti <span aria-hidden="true">*</span></label>
<input type="number" id="employee-count" class="form-control" placeholder="Es. 150" min="1" required>
</div>
<div class="form-group">
<label class="active" for="annual-turnover">Fatturato Annuo EUR <span aria-hidden="true">*</span></label>
<input type="number" id="annual-turnover" class="form-control" placeholder="Es. 15000000" min="0" required>
</div>
<div class="form-group">
<label class="active" for="total-balance">Totale di bilancio EUR</label>
<input type="number" id="total-balance" class="form-control" placeholder="Es. 12000000 (facoltativo)" min="0">
<small class="form-text text-muted">Terzo criterio dimensionale (Racc. 2003/361/CE): grande impresa se &gt; 43M&euro; di totale di bilancio.</small>
</div>
<div class="form-group">
<label class="active" for="is-autonomous">Struttura dell'impresa</label>
<select id="is-autonomous" class="form-select select-pdnd">
<option value="1">Impresa autonoma</option>
<option value="0">Parte di un gruppo (imprese collegate/associate)</option>
</select>
<small class="form-text text-muted">Se parte di un gruppo, le soglie vanno calcolate in modo consolidato (art. 6 Racc. 2003/361/CE).</small>
</div>
<div class="form-group">
<label class="active" for="size-independent-category">Categoria speciale NIS2 (a prescindere dalla dimensione)</label>
<select id="size-independent-category" class="form-select select-pdnd">
<option value="none">Nessuna delle seguenti</option>
<option value="dns">Fornitore di servizi DNS</option>
<option value="tld">Gestore di registri TLD</option>
<option value="ixp">Fornitore IXP (Internet Exchange Point)</option>
<option value="trust_qualified">Prestatore di servizi fiduciari qualificati</option>
<option value="trust_unqualified">Prestatore di servizi fiduciari non qualificati</option>
<option value="ecomm_public_network">Fornitore di reti pubbliche di comunicazione elettronica</option>
<option value="ecomm_public_service">Fornitore di servizi di comunicazione elettronica accessibili al pubblico</option>
<option value="domain_registrar">Fornitore di servizi di registrazione nomi di dominio</option>
<option value="pa_central">Pubblica Amministrazione centrale</option>
<option value="pa_regional_local">Pubblica Amministrazione regionale/locale</option>
</select>
<small class="form-text text-muted">Questi soggetti rientrano nell'ambito a prescindere dalle soglie (art. 3, comma 5, D.Lgs. 138/2024).</small>
</div>
<div class="form-group">
<label class="active" for="mission">Mission / contesto organizzativo</label>
<textarea id="mission" class="form-control" rows="2" placeholder="Obiettivi, servizi critici e dipendenze principali (facoltativo)"></textarea>
<small class="form-text text-muted">Alimenta il contesto organizzativo e il registro dei sistemi rilevanti (misura GV.OC).</small>
</div>
<div class="form-group mb-0">
<div class="form-check">
<input type="checkbox" id="has-code-of-ethics" class="form-check-input">
<label class="form-check-label" for="has-code-of-ethics">L'organizzazione ha adottato un codice etico</label>
</div>
<small class="form-text text-muted">Facoltativo &mdash; buona prassi di governance, non richiesto dalla NIS2.</small>
</div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-outline-primary" onclick="goToStep(1)">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-left"></use></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary" onclick="submitStep3()">
Continua
<svg class="ico ms-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-right"></use></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 3: Il Tuo Profilo ═══ -->
<div class="wizard-step" id="step-3">
<h2 class="wizard-step-title">Completa il tuo profilo</h2>
<p class="wizard-step-subtitle">Inserisci le informazioni sul tuo ruolo in azienda.</p>
<div class="profile-form-wrapper">
<div class="card card-bg has-bkg-grey shadow">
<div class="card-body">
<div class="form-group">
<label class="active" for="full-name">Nome Completo</label>
<input type="text" id="full-name" class="form-control" placeholder="Mario Rossi">
</div>
<div class="form-group">
<label class="active" for="role">Ruolo in Azienda <span aria-hidden="true">*</span></label>
<select id="role" class="form-select select-pdnd" required>
<option value="">-- Seleziona ruolo --</option>
<option value="ceo">CEO/Amministratore Delegato</option>
<option value="cto">CTO/Direttore Tecnico</option>
<option value="ciso">CISO/Responsabile Sicurezza</option>
<option value="dpo">DPO/Data Protection Officer</option>
<option value="compliance_manager">Compliance Manager</option>
<option value="it_manager">IT Manager</option>
<option value="legal">Responsabile Legale</option>
<option value="board_member">Membro CDA</option>
<option value="consultant">Consulente Esterno</option>
<option value="other">Altro</option>
</select>
</div>
<div class="form-group mb-0">
<label class="active" for="user-phone">Telefono</label>
<input type="text" id="user-phone" class="form-control" placeholder="+39 333 1234567">
</div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-outline-primary" onclick="goToStep(2)">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-left"></use></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary" onclick="submitStep4()">
Continua
<svg class="ico ms-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-right"></use></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 4: Classificazione NIS2 e Riepilogo ═══ -->
<div class="wizard-step" id="step-4">
<h2 class="wizard-step-title">Classificazione NIS2</h2>
<p class="wizard-step-subtitle">In base ai dati forniti, ecco la classificazione della tua organizzazione secondo la Direttiva NIS2.</p>
<div class="classification-result" id="classification-result">
<div class="classification-result-icon" id="classification-icon">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/></svg>
</div>
<div class="classification-result-label" id="classification-label"></div>
<div class="classification-result-desc" id="classification-desc"></div>
</div>
<ul class="mb-4" id="classification-obligations" style="display:none"></ul>
<div class="card card-bg has-bkg-grey shadow mb-4" id="subthreshold-block">
<div class="card-header bg-white"><h3 class="h6 mb-0">Sei sotto le soglie dimensionali? — criteri qualitativi (art. 3, comma 9, D.Lgs. 138/2024)</h3></div>
<div class="card-body">
<p class="small text-muted mb-3">Compila solo se la tua organizzazione <strong>non supera</strong> le soglie dimensionali: anche un soggetto sotto-soglia può essere individuato dall'Autorità (ACN) se ricorre uno di questi criteri.</p>
<div class="form-check"><input type="checkbox" id="crit-sole" class="form-check-input"><label class="form-check-label" for="crit-sole">Siamo l'<strong>unico fornitore nazionale</strong> di un servizio essenziale per attività sociali o economiche fondamentali <em>(lett. b)</em></label></div>
<div class="form-check"><input type="checkbox" id="crit-impact" class="form-check-input"><label class="form-check-label" for="crit-impact">Un'interruzione del nostro servizio avrebbe <strong>impatto significativo su sicurezza, incolumità o salute pubblica</strong> <em>(lett. c)</em></label></div>
<div class="form-check"><input type="checkbox" id="crit-systemic" class="form-check-input"><label class="form-check-label" for="crit-systemic">Un'interruzione comporterebbe un <strong>rischio sistemico significativo</strong>, anche transfrontaliero <em>(lett. d)</em></label></div>
<button class="btn btn-sm btn-outline-primary mt-3" type="button" onclick="runClassificationAndSummary()">Ricalcola classificazione</button>
</div>
</div>
<div class="alert alert-info" id="classification-disclaimer" role="alert" style="font-size:.85rem;display:none"></div>
<div class="voluntary-section" id="voluntary-section">
<div class="voluntary-card" id="voluntary-card">
<input type="checkbox" id="voluntary-checkbox" onchange="onVoluntaryChange(this.checked)" aria-label="Adesione volontaria alla NIS2">
<div>
<h4>Adesione Volontaria alla NIS2</h4>
<p>
Anche se la tua organizzazione non rientra nell'ambito di applicazione obbligatorio della
Direttiva NIS2, puoi scegliere di aderire volontariamente adottando le misure di sicurezza
previste dalla normativa. Questo consente di migliorare la postura di cybersicurezza e di
prepararsi a possibili future designazioni.
</p>
</div>
</div>
</div>
<div class="card card-bg has-bkg-grey shadow mb-4">
<div class="card-header bg-white"><h3 class="h6 mb-0">Riepilogo Dati Aziendali</h3></div>
<div class="card-body">
<div class="summary-grid">
<div class="summary-item"><div class="summary-item-label">Ragione Sociale</div><div class="summary-item-value" id="sum-company">-</div></div>
<div class="summary-item"><div class="summary-item-label">Partita IVA</div><div class="summary-item-value" id="sum-vat">-</div></div>
<div class="summary-item"><div class="summary-item-label">Settore</div><div class="summary-item-value" id="sum-sector">-</div></div>
<div class="summary-item"><div class="summary-item-label">Tipologia Soggetto</div><div class="summary-item-value" id="sum-entity-type">-</div></div>
<div class="summary-item"><div class="summary-item-label">Dipendenti</div><div class="summary-item-value" id="sum-employees">-</div></div>
<div class="summary-item"><div class="summary-item-label">Fatturato Annuo</div><div class="summary-item-value" id="sum-turnover">-</div></div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-outline-primary" onclick="goToStep(3)">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-arrow-left"></use></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary" id="complete-btn" onclick="completeOnboarding()">
<svg class="ico me-1" aria-hidden="true"><use href="vendor/bootstrap-italia/dist/svg/sprites.svg#it-check-circle"></use></svg>
Completa e Vai alla Dashboard
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script>
if (window.bootstrap && bootstrap.loadFonts) { bootstrap.loadFonts('vendor/bootstrap-italia/dist/fonts'); }
</script>
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
<script src="js/api.js?v=20260631"></script>
<script src="js/common.js?v=20260624g"></script>
<script>
// ── Auth check ──────────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
// ── Extend API with onboarding methods ──────────────────────────
api.uploadVisura = async function(file) {
const formData = new FormData();
formData.append('visura', file);
const response = await fetch(this.baseUrl + '/onboarding/upload-visura', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + this.token,
'X-Organization-Id': this.orgId || ''
},
body: formData
});
return response.json();
};
api.fetchCompany = function(vatNumber) {
return this.post('/onboarding/fetch-company', { vat_number: vatNumber });
};
api.completeOnboarding = function(data) {
return this.post('/onboarding/complete', data);
};
// ── Wizard State ────────────────────────────────────────────────
const wizardState = {
currentStep: 1,
method: null,
dataSource: null,
visuraFile: null,
company: {
name: '', vat_number: '', fiscal_code: '', address: '', city: '',
website: '', email: '', phone: '', sector: '', employee_count: '', annual_turnover: '',
total_balance: '', is_autonomous: 1, size_independent_category: 'none',
crit_sole_supplier: 0, crit_public_impact: 0, crit_systemic_risk: 0,
mission: '', has_code_of_ethics: 0
},
profile: { full_name: '', role: '', phone: '' },
classification: null,
autoFilledFields: new Set(),
voluntaryCompliance: false
};
function parseEmployeesRange(range) {
if (!range) return '';
if (typeof range === 'number') return range;
const str = String(range).trim();
if (str.endsWith('+')) return parseInt(str);
const parts = str.split('-');
if (parts.length === 2) return parseInt(parts[0]);
return parseInt(str) || '';
}
// ── Step 1: P.IVA Fetch ─────────────────────────────────────────
async function step1FetchCompany() {
const vat = document.getElementById('s1-vat').value.trim();
if (!vat || vat.length !== 11) {
showNotification('Inserisci una Partita IVA valida (11 cifre numeriche).', 'warning');
return;
}
const btn = document.getElementById('s1-fetch-btn');
const loading = document.getElementById('s1-loading');
const resultDiv = document.getElementById('s1-result');
btn.disabled = true;
loading.classList.add('active');
resultDiv.classList.add('d-none');
try {
const result = await api.fetchCompany(vat);
if (result.success && result.data) {
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = vat;
wizardState.company.fiscal_code = d.fiscal_code || d.codice_fiscale || '';
wizardState.company.address = d.address || d.indirizzo || '';
wizardState.company.city = d.city || d.citta || '';
wizardState.company.website = d.website || d.sito_web || '';
wizardState.company.email = d.email || d.pec || '';
wizardState.company.phone = d.phone || d.telefono || '';
wizardState.company.sector = d.suggested_sector || d.sector || '';
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
wizardState.company.annual_turnover = '';
wizardState.dataSource = 'Dati precompilati da CertiSource';
wizardState.autoFilledFields.clear();
if (wizardState.company.name) wizardState.autoFilledFields.add('company-name');
if (wizardState.company.vat_number) wizardState.autoFilledFields.add('vat-number');
if (wizardState.company.fiscal_code) wizardState.autoFilledFields.add('fiscal-code');
if (wizardState.company.address) wizardState.autoFilledFields.add('address');
if (wizardState.company.city) wizardState.autoFilledFields.add('city');
if (wizardState.company.email) wizardState.autoFilledFields.add('company-email');
if (wizardState.company.phone) wizardState.autoFilledFields.add('company-phone');
if (wizardState.company.sector) wizardState.autoFilledFields.add('sector');
if (wizardState.company.employee_count) wizardState.autoFilledFields.add('employee-count');
resultDiv.className = 'alert alert-success';
resultDiv.innerHTML = '<strong>✓ ' + (wizardState.company.name || 'Azienda trovata') + '</strong><br><small>' + (d.address || d.city || '') + '</small>';
document.getElementById('s1-continue-btn').style.display = 'inline-flex';
showNotification('Azienda trovata: ' + (wizardState.company.name || vat), 'success');
} else {
resultDiv.className = 'alert alert-warning';
resultDiv.textContent = 'Azienda non trovata. Puoi inserire i dati manualmente.';
step1ManualEntry(vat);
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
} finally {
btn.disabled = false;
loading.classList.remove('active');
}
}
function step1ManualEntry(vat) {
wizardState.company.vat_number = vat || document.getElementById('s1-vat').value.trim();
wizardState.dataSource = null;
wizardState.autoFilledFields.clear();
document.getElementById('s1-continue-btn').style.display = 'inline-flex';
showNotification('Inserisci i dati aziendali manualmente.', 'info');
}
// ── Step navigation ─────────────────────────────────────────────
function goToStep(step) {
wizardState.currentStep = step;
document.querySelectorAll('.wizard-step').forEach(s => s.classList.remove('active'));
const targetStep = document.getElementById('step-' + step);
if (targetStep) targetStep.classList.add('active');
if (step === 2) populateCompanyForm();
if (step === 3) loadUserProfile();
if (step === 4) runClassificationAndSummary();
updateStepper(step);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function updateStepper(currentStep) {
document.querySelectorAll('.stepper-step').forEach(el => {
const s = parseInt(el.dataset.step);
el.classList.remove('active', 'completed');
const numEl = el.querySelector('.stepper-number');
if (s < currentStep) {
el.classList.add('completed');
numEl.innerHTML = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
} else if (s === currentStep) {
el.classList.add('active');
numEl.textContent = s;
} else {
numEl.textContent = s;
}
});
document.querySelectorAll('.stepper-connector').forEach(el => {
const afterStep = parseInt(el.dataset.after);
el.classList.toggle('completed', afterStep < currentStep);
});
}
// ── Visura (legacy, kept for API completeness) ──
function handleVisuraFile(file) {
if (file.type !== 'application/pdf') { showNotification('Il file deve essere in formato PDF.', 'warning'); return; }
if (file.size > 10 * 1024 * 1024) { showNotification('Il file non deve superare i 10 MB.', 'warning'); return; }
wizardState.visuraFile = file;
}
async function analyzeVisura() {
if (!wizardState.visuraFile) return;
try {
const result = await api.uploadVisura(wizardState.visuraFile);
if (result.success && result.data) {
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = d.vat_number || d.partita_iva || '';
wizardState.company.fiscal_code = d.fiscal_code || d.codice_fiscale || '';
wizardState.company.address = d.address || d.indirizzo || '';
wizardState.company.city = d.city || d.citta || '';
wizardState.company.website = d.website || d.sito_web || '';
wizardState.company.email = d.email || d.pec || '';
wizardState.company.phone = d.phone || d.telefono || '';
wizardState.company.sector = d.suggested_sector || d.sector || '';
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
wizardState.company.annual_turnover = '';
wizardState.dataSource = 'Dati precompilati dalla visura camerale';
showNotification('Visura analizzata con successo!', 'success');
setTimeout(() => goToStep(2), 600);
} else {
showNotification(result.message || 'Errore nell\'analisi della visura.', 'error');
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
}
}
async function fetchCompany() {
const vatInput = document.getElementById('cs-vat');
if (!vatInput) return;
const vat = vatInput.value.trim();
if (!vat) { showNotification('Inserisci la Partita IVA.', 'warning'); return; }
try {
const result = await api.fetchCompany(vat);
if (result.success && result.data) {
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = d.vat_number || d.partita_iva || vat;
wizardState.company.sector = d.suggested_sector || d.sector || '';
wizardState.dataSource = 'Dati recuperati da CertiSource';
showNotification('Dati aziendali recuperati con successo!', 'success');
setTimeout(() => goToStep(2), 600);
} else {
showNotification(result.message || 'Impossibile recuperare i dati. Verifica la Partita IVA.', 'error');
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
}
}
// ── Step 2: Company Form ────────────────────────────────────────
function populateCompanyForm() {
const c = wizardState.company;
document.getElementById('company-name').value = c.name || '';
document.getElementById('vat-number').value = c.vat_number || '';
document.getElementById('fiscal-code').value = c.fiscal_code || '';
document.getElementById('address').value = c.address || '';
document.getElementById('city').value = c.city || '';
document.getElementById('website').value = c.website || '';
document.getElementById('company-email').value = c.email || '';
document.getElementById('company-phone').value = c.phone || '';
document.getElementById('employee-count').value = c.employee_count || '';
document.getElementById('annual-turnover').value = c.annual_turnover || '';
if (c.sector) {
const sectorSelect = document.getElementById('sector');
if (sectorSelect.querySelector('option[value="' + c.sector + '"]')) sectorSelect.value = c.sector;
}
const banner = document.getElementById('data-source-banner');
const bannerText = document.getElementById('data-source-text');
if (wizardState.dataSource) {
bannerText.textContent = wizardState.dataSource;
banner.classList.add('visible');
} else {
banner.classList.remove('visible');
}
const autoFields = wizardState.autoFilledFields;
document.querySelectorAll('#step-2 .form-control, #step-2 .form-select').forEach(el => {
const wrapper = el.closest('.form-group');
if (!wrapper) return;
wrapper.classList.remove('auto-filled');
const existing = wrapper.querySelector('.auto-fill-badge');
if (existing) existing.remove();
if (autoFields.has(el.id)) {
wrapper.classList.add('auto-filled');
const badge = document.createElement('span');
badge.className = 'auto-fill-badge';
badge.textContent = wizardState.dataSource ? 'auto-compilato' : 'da visura';
const label = wrapper.querySelector('label');
if (label) label.appendChild(badge);
}
});
}
function submitStep3() {
const name = document.getElementById('company-name').value.trim();
const sector = document.getElementById('sector').value;
const employees = document.getElementById('employee-count').value;
const turnover = document.getElementById('annual-turnover').value;
if (!name) { showNotification('Inserisci la ragione sociale.', 'warning'); document.getElementById('company-name').focus(); return; }
if (!sector) { showNotification('Seleziona un settore NIS2.', 'warning'); document.getElementById('sector').focus(); return; }
if (!employees || parseInt(employees) <= 0) { showNotification('Inserisci il numero di dipendenti.', 'warning'); document.getElementById('employee-count').focus(); return; }
if (!turnover || parseInt(turnover) < 0) { showNotification('Inserisci il fatturato annuo.', 'warning'); document.getElementById('annual-turnover').focus(); return; }
wizardState.company.name = name;
wizardState.company.vat_number = document.getElementById('vat-number').value.trim();
wizardState.company.fiscal_code = document.getElementById('fiscal-code').value.trim();
wizardState.company.address = document.getElementById('address').value.trim();
wizardState.company.city = document.getElementById('city').value.trim();
wizardState.company.website = document.getElementById('website').value.trim();
wizardState.company.email = document.getElementById('company-email').value.trim();
wizardState.company.phone = document.getElementById('company-phone').value.trim();
wizardState.company.sector = sector;
wizardState.company.employee_count = parseInt(employees);
wizardState.company.annual_turnover = parseInt(turnover);
wizardState.company.total_balance = parseInt(document.getElementById('total-balance').value) || 0;
wizardState.company.is_autonomous = parseInt(document.getElementById('is-autonomous').value);
wizardState.company.size_independent_category = document.getElementById('size-independent-category').value;
wizardState.company.mission = document.getElementById('mission').value.trim();
wizardState.company.has_code_of_ethics = document.getElementById('has-code-of-ethics').checked ? 1 : 0;
goToStep(3);
}
// ── Step 3: User Profile ────────────────────────────────────────
async function loadUserProfile() {
try {
const result = await api.getMe();
if (result.success && result.data) {
const nameInput = document.getElementById('full-name');
if (!nameInput.value && result.data.full_name) {
nameInput.value = result.data.full_name;
wizardState.profile.full_name = result.data.full_name;
}
}
} catch (e) { /* ignore */ }
}
function submitStep4() {
const role = document.getElementById('role').value;
if (!role) { showNotification('Seleziona il tuo ruolo in azienda.', 'warning'); document.getElementById('role').focus(); return; }
wizardState.profile.full_name = document.getElementById('full-name').value.trim();
wizardState.profile.role = role;
wizardState.profile.phone = document.getElementById('user-phone').value.trim();
goToStep(4);
}
// ── Step 4: Classification & Summary ────────────────────────────
function classifyLocally(sector, employees, turnover) {
const annexI = [
'energy_electricity', 'energy_district_heating', 'energy_oil', 'energy_gas',
'energy_hydrogen', 'transport_air', 'transport_rail', 'transport_water',
'transport_road', 'banking', 'financial_markets', 'health', 'drinking_water',
'waste_water', 'digital_infrastructure', 'ict_service_management',
'public_administration', 'space'
];
const annexII = [
'postal_courier', 'waste_management', 'chemicals', 'food',
'manufacturing_medical', 'manufacturing_computers', 'manufacturing_electrical',
'manufacturing_machinery', 'manufacturing_vehicles', 'manufacturing_transport',
'digital_providers', 'research'
];
const isAnnexI = annexI.includes(sector);
const isAnnexII = annexII.includes(sector);
const isLarge = employees >= 250 || turnover >= 50000000;
const isMedium = employees >= 50 || turnover >= 10000000;
if (isAnnexI && isLarge) {
return { classification: 'essential', label: 'Soggetto Essenziale',
description: 'La vostra organizzazione rientra tra i soggetti essenziali ai sensi della Direttiva NIS2, in quanto opera in un settore ad alta criticita\' (Allegato I) e supera le soglie dimensionali per la classificazione come grande impresa. Siete tenuti al rispetto completo degli obblighi di sicurezza con vigilanza ex ante.' };
} else if ((isAnnexI || isAnnexII) && isMedium) {
return { classification: 'important', label: 'Soggetto Importante',
description: 'La vostra organizzazione rientra tra i soggetti importanti ai sensi della Direttiva NIS2. Siete tenuti a rispettare gli obblighi di sicurezza e di notifica degli incidenti, con un regime di vigilanza ex post.' };
} else if (sector === 'other' || (!isAnnexI && !isAnnexII)) {
return { classification: 'not_applicable', label: 'Non Applicabile',
description: 'In base ai dati forniti, la vostra organizzazione non sembra rientrare nell\'ambito di applicazione della Direttiva NIS2. Consigliamo comunque di adottare misure di cybersicurezza adeguate e di verificare con le autorita\' competenti.' };
} else {
return { classification: 'not_applicable', label: 'Sotto le Soglie',
description: 'La vostra organizzazione opera in un settore coperto dalla NIS2 ma non raggiunge le soglie dimensionali minime (50 dipendenti o 10M EUR di fatturato). Potreste comunque essere designati dalle autorita\' nazionali.' };
}
}
async function runClassificationAndSummary() {
const c = wizardState.company;
const employees = parseInt(c.employee_count) || 0;
const turnover = parseInt(c.annual_turnover) || 0;
// criteri qualitativi sotto-soglia (art. 3 c. 9) letti dai checkbox dello step 4
c.crit_sole_supplier = (document.getElementById('crit-sole') || {}).checked ? 1 : 0;
c.crit_public_impact = (document.getElementById('crit-impact') || {}).checked ? 1 : 0;
c.crit_systemic_risk = (document.getElementById('crit-systemic') || {}).checked ? 1 : 0;
// Classificazione v2 lato server (single source of truth: Nis2ClassificationService)
let result = null;
try {
const resp = await api.post('/organizations/classify', {
sector: c.sector, employee_count: employees, annual_turnover_eur: turnover,
total_balance_eur: parseInt(c.total_balance) || 0,
is_autonomous: c.is_autonomous, size_independent_category: c.size_independent_category,
crit_sole_supplier: c.crit_sole_supplier, crit_public_impact: c.crit_public_impact, crit_systemic_risk: c.crit_systemic_risk
});
if (resp && resp.success && resp.data) {
const d = resp.data;
result = { classification: d.entity_type, label: d.label, description: d.explanation,
designation_basis: d.designation_basis, sub_threshold_candidate: !!d.sub_threshold_candidate,
disclaimer: d.disclaimer || '', obligations: d.obligations || [] };
}
} catch (e) { /* fallback locale sotto */ }
if (!result) {
const r = classifyLocally(c.sector, employees, turnover);
result = { classification: r.classification, label: r.label, description: r.description, obligations: [], disclaimer: '', sub_threshold_candidate: false };
}
wizardState.classification = result;
// Adesione volontaria: visibile solo per soggetti non in ambito
wizardState.voluntaryCompliance = false;
const voluntaryCheckbox = document.getElementById('voluntary-checkbox');
if (voluntaryCheckbox) voluntaryCheckbox.checked = false;
const voluntaryCard = document.getElementById('voluntary-card');
if (voluntaryCard) voluntaryCard.classList.remove('checked');
const voluntarySection = document.getElementById('voluntary-section');
if (result.classification === 'not_applicable') voluntarySection.classList.add('visible');
else voluntarySection.classList.remove('visible');
const container = document.getElementById('classification-result');
const labelEl = document.getElementById('classification-label');
const descEl = document.getElementById('classification-desc');
const iconEl = document.getElementById('classification-icon');
container.className = 'classification-result';
if (result.classification === 'essential') {
container.classList.add('essential');
iconEl.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/><path d="M10 12.5l-2-2-1.41 1.41L10 15.32l5.41-5.41L14 8.5l-4 4z"/></svg>';
} else if (result.classification === 'important') {
container.classList.add('important');
iconEl.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/></svg>';
} else {
container.classList.add('not-applicable');
iconEl.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
}
// Etichetta + badge "autovalutazione preliminare"
labelEl.textContent = (result.label || '') + ' — autovalutazione preliminare';
descEl.textContent = result.description || '';
// Obblighi (dal server, citano il D.Lgs. 138/2024)
const obEl = document.getElementById('classification-obligations');
if (obEl) {
if (result.obligations && result.obligations.length) {
obEl.innerHTML = result.obligations.map(o => '<li>' + String(o).replace(/[<>]/g, '') + '</li>').join('');
obEl.style.display = '';
} else { obEl.style.display = 'none'; obEl.innerHTML = ''; }
}
// Disclaimer ACN (non vincolante)
const disEl = document.getElementById('classification-disclaimer');
if (disEl) {
if (result.disclaimer) { disEl.textContent = result.disclaimer; disEl.style.display = ''; }
else { disEl.style.display = 'none'; }
}
const sectorSelect = document.getElementById('sector');
const sectorOption = sectorSelect.querySelector('option[value="' + c.sector + '"]');
const sectorText = sectorOption ? sectorOption.textContent : c.sector;
document.getElementById('sum-company').textContent = c.name || '-';
document.getElementById('sum-vat').textContent = c.vat_number || '-';
document.getElementById('sum-sector').textContent = sectorText || '-';
document.getElementById('sum-entity-type').textContent = result.label || '-';
document.getElementById('sum-employees').textContent = employees ? employees.toLocaleString('it-IT') : '-';
document.getElementById('sum-turnover').textContent = turnover ? '€ ' + turnover.toLocaleString('it-IT') : '-';
}
function onVoluntaryChange(checked) {
wizardState.voluntaryCompliance = checked;
const card = document.getElementById('voluntary-card');
const container = document.getElementById('classification-result');
const labelEl = document.getElementById('classification-label');
const sumEntityType = document.getElementById('sum-entity-type');
card.classList.toggle('checked', checked);
if (checked) {
container.className = 'classification-result voluntary';
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/><path d="M10 12.5l-2-2-1.41 1.41L10 15.32l5.41-5.41L14 8.5l-4 4z"/></svg>';
labelEl.textContent = 'Adesione Volontaria';
document.getElementById('classification-desc').textContent = 'La tua organizzazione aderisce volontariamente alla Direttiva NIS2, adottando le misure di sicurezza previste dalla normativa per migliorare la propria postura di cybersicurezza.';
sumEntityType.textContent = 'Adesione Volontaria';
} else {
container.className = 'classification-result not-applicable';
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
labelEl.textContent = wizardState.classification.label;
document.getElementById('classification-desc').textContent = wizardState.classification.description;
sumEntityType.textContent = wizardState.classification.label;
}
}
// ── Complete Onboarding ─────────────────────────────────────────
async function completeOnboarding() {
const btn = document.getElementById('complete-btn');
btn.disabled = true;
btn.textContent = 'Completamento in corso...';
const c = wizardState.company;
const p = wizardState.profile;
const payload = {
name: c.name, vat_number: c.vat_number, fiscal_code: c.fiscal_code,
address: c.address, city: c.city, website: c.website,
contact_email: c.email, contact_phone: c.phone, sector: c.sector,
employee_count: parseInt(c.employee_count) || 0,
annual_turnover_eur: parseInt(c.annual_turnover) || 0,
total_balance_eur: parseInt(c.total_balance) || 0,
is_autonomous: c.is_autonomous,
size_independent_category: c.size_independent_category,
crit_sole_supplier: c.crit_sole_supplier,
crit_public_impact: c.crit_public_impact,
crit_systemic_risk: c.crit_systemic_risk,
mission: c.mission,
has_code_of_ethics: c.has_code_of_ethics,
voluntary_compliance: wizardState.voluntaryCompliance ? 1 : 0,
full_name: p.full_name, phone: p.phone, country: 'IT',
};
try {
const result = await api.completeOnboarding(payload);
if (result.success && result.data) {
const orgId = result.data.id || result.data.organization_id;
if (orgId) api.setOrganization(orgId);
showNotification('Configurazione completata con successo!', 'success');
setTimeout(() => {
const dest = api.isConsultant() ? 'companies.html' : 'dashboard.html';
window.location.href = dest;
}, 1000);
} else {
showNotification(result.message || 'Errore nel completamento.', 'error');
btn.disabled = false;
btn.textContent = 'Completa e Vai alla Dashboard';
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
btn.disabled = false;
btn.textContent = 'Completa e Vai alla Dashboard';
}
}
// ── Allow P.IVA input to submit with Enter ───────────────────────
document.getElementById('s1-vat').addEventListener('keypress', (e) => {
if (e.key === 'Enter') { e.preventDefault(); step1FetchCompany(); }
});
</script>
</body>
</html>