v1.0.0

📋 Sicily Experience - Development Bible

DOC_OVERVIEW_001
ℹ️ Informazioni Documento

Versione: 1.0.0 | Ultimo aggiornamento: Gennaio 2026 | Autore: Team Sicily Experience

Questo documento rappresenta la guida completa e definitiva per lo sviluppo della piattaforma Sicily Experience. Ogni sezione è esportabile singolarmente per facilitare il lavoro di sviluppo modulare.

Vision & Obiettivi

🎯
Vision
Piattaforma turistica siciliana all-in-one: alloggi + esperienze + prodotti locali, con gestione automatizzata di tutti gli adempimenti burocratici.
🏆
Mission
Diventare il punto di riferimento per il turismo in Sicilia, offrendo valore sia ai turisti che agli operatori locali.
💡
Differenziazione
Unica piattaforma che automatizza completamente la burocrazia (Alloggiati Web, ISTAT, Tassa soggiorno) tramite Payturist.

Target Utenti

Tipo Chi sono Cosa cercano Valore offerto
🏠 Host Proprietari strutture ricettive (Hotel, B&B, Case Vacanza, Ostelli) Gestionale semplice, burocrazia zero, visibilità PMS integrato con automazione totale adempimenti
🏪 Business Tour operator, ristoranti, artigiani, noleggi Canale vendita, clienti qualificati Marketplace con turisti già in zona
📅 Promoter Organizzatori eventi, enti locali Visibilità eventi Pubblicazione eventi nelle guide turistiche
🤝 Ambassador Agenti territoriali, commerciali Guadagno da referral Sistema commissioni su clienti portati
🌴 Turista Visitatori Sicilia (italiani e stranieri) Esperienza completa, prenotazione facile One-stop-shop per vacanza siciliana

Revenue Streams

┌─────────────────────────────────────────────────────────────────────────────┐ │ MODELLO DI REVENUE │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1️⃣ ABBONAMENTI 2️⃣ COMMISSIONI │ │ ├── Host: €0-79/mese ├── Su prenotazioni alloggi: 8-12% │ │ ├── Business: €0-79/mese ├── Su vendita esperienze: 10-15% │ │ └── In base al piano └── Su prodotti: 12-15% │ │ │ │ 3️⃣ PUBBLICAZIONE EVENTI 4️⃣ CROSS-REFERRAL │ │ ├── Singolo evento: €25 ├── Host guadagna su esperienze │ │ ├── Pack 5 eventi: €99 │ acquistate dai propri ospiti │ │ └── Abbonamento: €149/mese └── Business guadagna su alloggi │ │ prenotati dai propri clienti │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

Glossario Termini

📖 Termini Tecnici e Abbreviazioni
TermineDefinizione
CPTCustom Post Type - Tipo di contenuto personalizzato in WordPress
ACFAdvanced Custom Fields - Plugin per campi personalizzati
PMSProperty Management System - Sistema gestionale per strutture ricettive
OTAOnline Travel Agency - Agenzia di viaggio online (Booking, Airbnb)
CINCodice Identificativo Nazionale - Obbligatorio per strutture ricettive
CIRCodice Identificativo Regionale - Rilasciato dalla Regione Sicilia
Alloggiati WebPortale Polizia di Stato per comunicazione ospiti
Turist@tOsservatorio Turistico Regione Sicilia per dati ISTAT
Split PaymentDivisione automatica pagamento tra piattaforma e fornitore
Cross-ReferralCommissione su vendite generate tra host e business
Peer ReferralSistema inviti tra host/business con crediti wallet
KYCKnow Your Customer - Verifica identità utenti
GMVGross Merchandise Value - Valore totale transazioni
LTVLifetime Value - Valore cliente nel tempo
Churn RateTasso di abbandono clienti

Stack Tecnologico

┌─────────────────────────────────────────────────────────────────────────────┐ │ STACK TECNOLOGICO │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ BACKEND │ │ ├── WordPress 6.x (CMS + Framework) │ │ ├── WooCommerce (solo checkout e pagamenti) │ │ ├── ACF Pro (campi personalizzati) │ │ ├── PHP 8.1+ (codice custom) │ │ └── MySQL 8.0 (database) │ │ │ │ FRONTEND │ │ ├── HTML5 / CSS3 (Tailwind o custom) │ │ ├── JavaScript (Vanilla + Alpine.js) │ │ ├── Tema WordPress custom │ │ └── PWA (Progressive Web App) │ │ │ │ INTEGRAZIONI │ │ ├── Stripe Connect (pagamenti split) │ │ ├── Payturist API (burocrazia) │ │ ├── Brevo/SendGrid (email) │ │ ├── Twilio (SMS) │ │ ├── Fatture in Cloud API (contabilità) │ │ └── iCal (sync calendari) │ │ │ │ INFRASTRUTTURA │ │ ├── Server: VPS Linux (Ubuntu 22.04) │ │ ├── Web Server: Nginx + PHP-FPM │ │ ├── Cache: Redis │ │ ├── CDN: Cloudflare / BunnyCDN │ │ ├── SSL: Let's Encrypt │ │ └── Backup: Automatizzati giornalieri │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

Architettura Generale

┌─────────────────────────────────────────────────────────────────────────────┐ │ ARCHITETTURA SISTEMA │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ │ │ │ BROWSER │ │ │ │ (Turista/ │ │ │ │ Partner/Admin) │ │ │ └────────┬────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ CLOUDFLARE │ │ │ │ (CDN + WAF) │ │ │ └────────┬────────┘ │ │ │ │ │ ┌─────────────────────────────┼─────────────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────────────┐ ┌─────────────────┐ │ │ │ FRONTEND A │ │ FRONTEND B │ │ FRONTEND C │ │ │ │ TURISTI │ │ PARTNER │ │ ADMIN │ │ │ │ │ │ │ │ │ │ │ │ sicilyexper- │ │ partner.sicilyexper- │ │ /wp-admin + │ │ │ │ ience.com │ │ ience.com │ │ custom pages │ │ │ │ │ │ │ │ │ │ │ │ • Homepage │ │ • Dashboard Host │ │ • Dashboard │ │ │ │ • Ricerca │ │ • Dashboard Business │ │ • Moderazione │ │ │ │ • Booking │ │ • Dashboard Ambassador │ │ • Config │ │ │ │ • Checkout │ │ • Calendario │ │ • Report │ │ │ │ • Guide │ │ • Analytics │ │ • Export │ │ │ │ • Account │ │ • Wallet │ │ │ │ │ │ │ │ │ │ │ │ │ │ Mobile-first │ │ Desktop-first │ │ Desktop only │ │ │ │ Multi-lingua │ │ Italiano │ │ Italiano │ │ │ └────────┬────────┘ └────────────┬────────────┘ └────────┬────────┘ │ │ │ │ │ │ │ └─────────────────────────┼─────────────────────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ WORDPRESS │ │ │ │ + CUSTOM │ │ │ │ CODE │ │ │ └────────┬────────┘ │ │ │ │ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ MySQL │ │ WooCommerce │ │ REST API │ │ │ │ (Database) │ │ (Pagamenti) │ │ (Custom) │ │ │ └─────────────────┘ └─────────────────────┘ └─────────────────────┘ │ │ │ │ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ STRIPE │ │ PAYTURIST │ │ FATTURE IN CLOUD │ │ │ │ CONNECT │ │ API │ │ API │ │ │ │ │ │ │ │ │ │ │ │ • Pagamenti │ │ • Check-in │ │ • Fatturazione │ │ │ │ • Split │ │ • Alloggiati Web │ │ • Clienti │ │ │ │ • Payout │ │ • ISTAT │ │ • Report │ │ │ │ │ │ • Tassa soggiorno │ │ │ │ │ └─────────────────┘ └─────────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

🗄️ Database Schema

DOC_DATABASE_001

Il database di Sicily Experience combina le tabelle native di WordPress con tabelle custom ottimizzate per le funzionalità specifiche della piattaforma.

Tabelle WordPress Utilizzate

TabellaUtilizzo
wp_postsCPT: struttura, unita, business, offerta, evento, guida
wp_postmetaMetadati ACF per tutti i CPT
wp_usersUtenti (turisti, host, business, ambassador, admin)
wp_usermetaMetadati utenti e ruoli custom
wp_terms / wp_term_taxonomyTassonomie (tipologie, città, categorie)
wp_optionsConfigurazioni globali piattaforma

Tabelle Custom

⚠️ Convenzione Naming

Tutte le tabelle custom usano il prefisso se_ (Sicily Experience). Esempio: se_bookings

se_bookings - Prenotazioni

📋 SCHEMA: se_bookings
id BIGINT UNSIGNED PK ID auto-incrementale
booking_code VARCHAR(20) UNIQUE Codice prenotazione (SE-2026-XXXXX)
booking_type ENUM 'struttura_intera' | 'unita'
struttura_id BIGINT UNSIGNED FK Riferimento a CPT struttura
unita_id BIGINT UNSIGNED FK NULL Riferimento a CPT unita (se tipo = unita)
check_in DATE Data check-in
check_out DATE Data check-out
nights INT UNSIGNED Numero notti
adults INT UNSIGNED Numero adulti
children INT UNSIGNED Numero bambini
infants INT UNSIGNED Numero neonati
turista_id BIGINT UNSIGNED FK NULL ID utente (NULL se guest)
guest_email VARCHAR(255) Email ospite
guest_name VARCHAR(255) Nome ospite
guest_phone VARCHAR(50) NULL Telefono ospite
subtotal DECIMAL(10,2) Subtotale (prezzo base)
extras_total DECIMAL(10,2) Totale extra e supplementi
taxes_total DECIMAL(10,2) Totale tasse (soggiorno)
discount_total DECIMAL(10,2) Totale sconti
total DECIMAL(10,2) Totale finale
payment_type ENUM 'full' | 'deposit'
deposit_amount DECIMAL(10,2) NULL Importo caparra
balance_amount DECIMAL(10,2) NULL Importo saldo
balance_due_date DATE NULL Scadenza saldo
commission_rate DECIMAL(5,2) % commissione applicata
commission_amount DECIMAL(10,2) Importo commissione
host_payout DECIMAL(10,2) Importo da girare all'host
status ENUM pending_confirmation | pending_payment | confirmed | checked_in | completed | cancelled_guest | cancelled_host | noshow | refunded
cancellation_policy_snapshot JSON Copia policy al momento prenotazione
wc_order_id BIGINT UNSIGNED NULL ID ordine WooCommerce
payturist_sent BOOLEAN Dati inviati a Payturist
created_at DATETIME Data creazione
updated_at DATETIME Data ultimo aggiornamento
📋 SQL Completo - se_bookings
se_bookings.sql SQL
CREATE TABLE se_bookings (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_code VARCHAR(20) UNIQUE NOT NULL,
    
    -- Tipo prenotazione
    booking_type ENUM('struttura_intera', 'unita') NOT NULL,
    struttura_id BIGINT UNSIGNED NOT NULL,
    unita_id BIGINT UNSIGNED NULL,
    
    -- Date
    check_in DATE NOT NULL,
    check_out DATE NOT NULL,
    nights INT UNSIGNED NOT NULL,
    
    -- Ospiti
    adults INT UNSIGNED DEFAULT 1,
    children INT UNSIGNED DEFAULT 0,
    infants INT UNSIGNED DEFAULT 0,
    
    -- Cliente
    turista_id BIGINT UNSIGNED NULL,
    guest_email VARCHAR(255) NOT NULL,
    guest_phone VARCHAR(50) NULL,
    guest_name VARCHAR(255) NOT NULL,
    guest_notes TEXT NULL,
    
    -- Importi
    subtotal DECIMAL(10,2) NOT NULL,
    extras_total DECIMAL(10,2) DEFAULT 0,
    taxes_total DECIMAL(10,2) DEFAULT 0,
    discount_total DECIMAL(10,2) DEFAULT 0,
    total DECIMAL(10,2) NOT NULL,
    
    -- Pagamento
    payment_type ENUM('full', 'deposit') NOT NULL,
    deposit_amount DECIMAL(10,2) NULL,
    deposit_paid_at DATETIME NULL,
    balance_amount DECIMAL(10,2) NULL,
    balance_due_date DATE NULL,
    balance_paid_at DATETIME NULL,
    
    -- Commissioni
    commission_rate DECIMAL(5,2) NOT NULL,
    commission_amount DECIMAL(10,2) NOT NULL,
    host_payout DECIMAL(10,2) NOT NULL,
    
    -- Referral
    cross_referral_source_type ENUM('host', 'business') NULL,
    cross_referral_source_id BIGINT UNSIGNED NULL,
    ambassador_id BIGINT UNSIGNED NULL,
    
    -- Policy
    cancellation_policy_id BIGINT UNSIGNED NOT NULL,
    cancellation_policy_snapshot JSON NOT NULL,
    
    -- Stato
    status ENUM(
        'pending_confirmation',
        'pending_payment',
        'confirmed',
        'pending_balance',
        'checked_in',
        'completed',
        'cancelled_guest',
        'cancelled_host',
        'noshow',
        'refunded'
    ) DEFAULT 'pending_payment',
    
    -- Conferma manuale
    requires_confirmation BOOLEAN DEFAULT FALSE,
    confirmation_deadline DATETIME NULL,
    confirmed_at DATETIME NULL,
    rejection_reason TEXT NULL,
    
    -- WooCommerce
    wc_order_id BIGINT UNSIGNED NULL,
    wc_order_deposit_id BIGINT UNSIGNED NULL,
    wc_order_balance_id BIGINT UNSIGNED NULL,
    
    -- Payturist
    payturist_sent BOOLEAN DEFAULT FALSE,
    payturist_sent_at DATETIME NULL,
    payturist_response JSON NULL,
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    cancelled_at DATETIME NULL,
    
    -- Indexes
    INDEX idx_struttura (struttura_id),
    INDEX idx_unita (unita_id),
    INDEX idx_turista (turista_id),
    INDEX idx_dates (check_in, check_out),
    INDEX idx_status (status),
    INDEX idx_created (created_at)
);

se_booking_guests - Ospiti Prenotazione

📋 Schema se_booking_guests
se_booking_guests.sql SQL
CREATE TABLE se_booking_guests (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    
    -- Anagrafica
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    birth_date DATE NOT NULL,
    birth_place VARCHAR(255) NOT NULL,
    birth_country VARCHAR(2) NOT NULL,
    citizenship VARCHAR(2) NOT NULL,
    gender ENUM('M', 'F') NOT NULL,
    
    -- Documento
    document_type ENUM('CI', 'PASSPORT', 'DRIVING_LICENSE', 'OTHER') NOT NULL,
    document_number VARCHAR(50) NOT NULL,
    document_issued_by VARCHAR(255) NOT NULL,
    document_issue_date DATE NOT NULL,
    document_expiry_date DATE NULL,
    
    -- Ruolo
    is_primary BOOLEAN DEFAULT FALSE,
    guest_type ENUM('adult', 'child', 'infant') NOT NULL,
    
    -- Payturist
    payturist_guest_id VARCHAR(100) NULL,
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id) ON DELETE CASCADE
);

se_wallet - Portafoglio Utenti

📋 Schema se_wallet
se_wallet.sql SQL
CREATE TABLE se_wallet (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    user_id BIGINT UNSIGNED NOT NULL,
    user_type ENUM('host', 'business') NOT NULL,
    
    -- Saldi
    saldo_disponibile DECIMAL(10,2) DEFAULT 0,
    saldo_in_attesa DECIMAL(10,2) DEFAULT 0,
    
    -- Storico
    totale_guadagnato DECIMAL(10,2) DEFAULT 0,
    totale_prelevato DECIMAL(10,2) DEFAULT 0,
    totale_usato_rinnovi DECIMAL(10,2) DEFAULT 0,
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_user (user_id, user_type)
);

se_wallet_transactions - Movimenti Wallet

📋 Schema se_wallet_transactions
se_wallet_transactions.sql SQL
CREATE TABLE se_wallet_transactions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    wallet_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo transazione
    tipo ENUM(
        'referral_host',
        'referral_business', 
        'cross_referral',
        'prelievo',
        'rinnovo',
        'bonus',
        'storno'
    ) NOT NULL,
    
    -- Importo (positivo = entrata, negativo = uscita)
    importo DECIMAL(10,2) NOT NULL,
    descrizione VARCHAR(500) NOT NULL,
    
    -- Riferimenti
    riferimento_id BIGINT UNSIGNED NULL,
    riferimento_tipo VARCHAR(50) NULL,
    
    -- Stato
    stato ENUM('pending', 'completed', 'failed', 'cancelled') DEFAULT 'completed',
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    processed_at DATETIME NULL,
    
    FOREIGN KEY (wallet_id) REFERENCES se_wallet(id)
);

se_trials - Gestione Trial

📋 Schema se_trials
se_trials.sql SQL
CREATE TABLE se_trials (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- Chi è in trial
    user_id BIGINT UNSIGNED NOT NULL,
    user_type ENUM('host', 'business') NOT NULL,
    
    -- Chi ha attivato il trial
    activated_by_type ENUM('self', 'ambassador', 'admin', 'owner') NOT NULL,
    activated_by_id BIGINT UNSIGNED NULL,
    
    -- Durata
    started_at DATETIME NOT NULL,
    expires_at DATETIME NOT NULL,
    duration_days INT NOT NULL,
    
    -- Piano durante trial
    trial_plan ENUM('standard', 'premium') DEFAULT 'premium',
    
    -- Stato
    status ENUM(
        'active',
        'converted',
        'expired',
        'extended',
        'cancelled'
    ) DEFAULT 'active',
    
    -- Conversione
    converted_at DATETIME NULL,
    converted_to_plan VARCHAR(50) NULL,
    converted_order_id BIGINT UNSIGNED NULL,
    
    -- Reminder
    reminder_7days_sent BOOLEAN DEFAULT FALSE,
    reminder_3days_sent BOOLEAN DEFAULT FALSE,
    reminder_1day_sent BOOLEAN DEFAULT FALSE,
    reminder_expired_sent BOOLEAN DEFAULT FALSE,
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_user (user_id, user_type),
    INDEX idx_status (status),
    INDEX idx_expires (expires_at)
);

se_special_accounts - Account Speciali (Owner/VIP)

📋 Schema se_special_accounts
se_special_accounts.sql SQL
CREATE TABLE se_special_accounts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    user_id BIGINT UNSIGNED NOT NULL UNIQUE,
    
    -- Tipo account speciale
    account_type ENUM('owner', 'vip') NOT NULL,
    
    -- Configurazione
    commission_override DECIMAL(5,2) DEFAULT 0,
    plan_override VARCHAR(50) DEFAULT 'premium',
    
    -- Durata
    valid_from DATETIME NOT NULL,
    valid_until DATETIME NULL,  -- NULL = lifetime
    
    -- Chi lo ha creato
    created_by BIGINT UNSIGNED NULL,
    
    -- Note
    internal_notes TEXT NULL,
    
    -- Stato
    is_active BOOLEAN DEFAULT TRUE,
    
    -- Timestamps
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP
);

se_ambassador_commissions - Commissioni Ambassador

📋 Schema se_ambassador_commissions
se_ambassador_commissions.sql SQL
CREATE TABLE se_ambassador_commissions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    ambassador_id BIGINT UNSIGNED NOT NULL,
    cliente_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo commissione
    tipo ENUM('abbonamento', 'transazione', 'rinnovo') NOT NULL,
    
    -- Importi
    importo_base DECIMAL(10,2) NOT NULL,
    percentuale_applicata DECIMAL(5,2) NOT NULL,
    commissione_calcolata DECIMAL(10,2) NOT NULL,
    
    -- Stato
    stato ENUM('maturata', 'in_attesa', 'pagata', 'annullata') DEFAULT 'maturata',
    
    -- Riferimenti
    riferimento_ordine BIGINT UNSIGNED NULL,
    
    -- Timestamps
    data_maturazione DATETIME NOT NULL,
    data_pagamento DATETIME NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_ambassador (ambassador_id),
    INDEX idx_stato (stato)
);

se_platform_config - Configurazioni Piattaforma

📋 Schema se_platform_config
se_platform_config.sql SQL
CREATE TABLE se_platform_config (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    config_key VARCHAR(100) UNIQUE NOT NULL,
    config_value TEXT NOT NULL,
    config_type ENUM('percentage', 'currency', 'integer', 'boolean', 'json', 'string') NOT NULL,
    category VARCHAR(50) NOT NULL,
    
    description TEXT NULL,
    
    -- Audit
    updated_by BIGINT UNSIGNED NULL,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    history JSON NULL,
    
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_category (category)
);

Altre Tabelle Custom

TabellaDescrizioneRighe Stimate
se_reviewsRecensioni turisti → strutture/business10K+
se_messagesMessaggi tra utenti50K+
se_notificationsNotifiche utenti100K+
se_cancellationsStorico cancellazioni1K+
se_overbooking_incidentsIncidenti overbooking100+
se_noshow_reportsSegnalazioni no-show500+
se_force_majeure_requestsRichieste forza maggiore200+
se_peer_referralsReferral tra host/business5K+
se_cross_referralsCross-referral transazioni20K+
se_impersonation_logsLog impersonation ambassador10K+
se_payoutsPayout a host/business/ambassador10K+
se_calendar_syncSync iCal con OTA5K+

Campi ACF per CPT

CPT: struttura

🏠 Campi ACF - Struttura
CampoTipoDescrizione
tipologiaSelecthotel | b_and_b | casa_vacanza | appartamento | ostello
modalita_affittoSelectintera_unita | camere | entrambe
indirizzoGroupvia, civico, cap, città, provincia
coordinateGoogle MapLatitudine e longitudine
descrizione_breveTextareaMax 200 caratteri
descrizione_completaWYSIWYGDescrizione estesa
galleryGalleryImmagini struttura
serviziCheckboxWiFi, parcheggio, piscina, etc.
lingue_parlateCheckboxItaliano, inglese, etc.
orario_checkinTime PickerOrario check-in standard
orario_checkoutTime PickerOrario check-out standard
cinTextCodice Identificativo Nazionale
cirTextCodice Identificativo Regionale
payturist_structure_idTextID struttura su Payturist
payturist_tokenTextToken API Payturist
stripe_account_idTextID account Stripe Connect
piano_tariffarioSelectbase | standard | premium
commissione_overrideNumberCommissione personalizzata (VIP)
conferma_manualeTrue/FalseRichiede conferma prenotazioni
capacita_maxNumberSolo se modalita = intera_unita
prezzo_baseNumberSolo se modalita = intera_unita
regole_prezzoRepeaterStagionalità, weekend, etc.
politica_cancellazioneSelectnon_rimborsabile | standard | flessibile | personalizzata
owner_idUserProprietario/gestore

CPT: unita (Camera/Appartamento)

🛏️ Campi ACF - Unità
CampoTipoDescrizione
struttura_padrePost ObjectRiferimento a CPT struttura
tipo_unitaSelectcamera | appartamento | posto_letto
tipologia_cameraSelectsingola | doppia | tripla | quadrupla | suite | dormitorio
capacita_minNumberMinimo ospiti
capacita_maxNumberMassimo ospiti
num_lettiRepeaterTipo letto + quantità
superficie_mqNumberSuperficie in mq
prezzo_baseNumberPrezzo per notte
regole_prezzoRepeaterStagionalità, weekend, etc.
servizi_cameraCheckboxBagno privato, AC, TV, etc.
galleryGalleryFoto camera
descrizioneTextareaDescrizione unità

CPT: business

🏪 Campi ACF - Business
CampoTipoDescrizione
tipologiaSelecttour_operator | ristorante | noleggio | artigiano | negozio | servizi
indirizzoGroupvia, civico, cap, città, provincia
coordinateGoogle MapLatitudine e longitudine
descrizioneWYSIWYGDescrizione attività
galleryGalleryImmagini
orari_aperturaRepeaterGiorno + orario apertura/chiusura
contattiGroupTelefono, email, website, social
stripe_account_idTextID account Stripe Connect
piano_tariffarioSelectbase | standard | premium
owner_idUserProprietario

Diagramma Relazioni

┌─────────────────────────────────────────────────────────────────────────────┐ │ DIAGRAMMA ER SEMPLIFICATO │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ wp_users │ │ struttura │ │ business │ │ │ │ (utenti) │ │ (CPT) │ │ (CPT) │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ 1:N │ 1:N │ 1:N │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ se_wallet │ │ unita │ │ offerta │ │ │ │ │ │ (CPT) │ │ (CPT) │ │ │ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ 1:N │ 1:N │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ se_bookings │ │ se_bookings │ │ │ │ (alloggi) │ │ (esperienze)│ │ │ └──────┬──────┘ └─────────────┘ │ │ │ │ │ │ 1:N │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │se_booking_ │ │ │ │ guests │ │ │ └─────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ se_trials │◄──────│ wp_users │──────►│se_ambassador│ │ │ │ │ │ │ │_commissions │ │ │ └─────────────┘ └──────┬──────┘ └─────────────┘ │ │ │ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ se_special_ │ │ │ │ accounts │ │ │ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

👑 MOD_00 - Account Speciali & Trial

MOD_00_ACCOUNT_TRIAL
ℹ️ Panoramica Modulo

Stato: Da sviluppare

Priorità: 🔴 Alta (MVP)

Dipendenze: MOD_01 Autenticazione

Descrizione

Gestisce i ruoli speciali (Owner, VIP) e il sistema di trial per nuovi utenti. Permette al proprietario della piattaforma di avere accesso gratuito lifetime e di creare account VIP per parenti/partner strategici.

Gerarchia Ruoli

┌─────────────────────────────────────────────────────────────────────────────┐ │ GERARCHIA RUOLI │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 👑 OWNER (Proprietario piattaforma) │ │ ├── Accesso totale a tutto │ │ ├── Strutture/Business personali: LIFETIME GRATIS │ │ ├── Può creare account VIP per chiunque │ │ ├── Configura TUTTE le impostazioni │ │ └── Non può essere rimosso o declassato │ │ │ │ ⭐ VIP (Parenti, amici, partner strategici) │ │ ├── Strutture/Business: LIFETIME GRATIS │ │ ├── Creato solo da OWNER │ │ ├── Nessuna commissione (o commissione ridotta) │ │ └── Badge nascosto (non visibile pubblicamente) │ │ │ │ 🛡️ ADMIN │ │ ├── Gestisce piattaforma │ │ ├── NON può creare VIP │ │ ├── NON può modificare OWNER │ │ └── Può configurare trial standard │ │ │ │ 🤝 AMBASSADOR │ │ ├── Porta clienti │ │ ├── Può attivare TRIAL per i suoi clienti │ │ ├── Max giorni trial: configurato da OWNER/ADMIN │ │ └── Max trial attivi contemporaneamente: configurabile │ │ │ │ 🏠 HOST / 🏪 BUSINESS / 📅 PROMOTER │ │ ├── Account standard │ │ ├── Piani: Trial → Base → Standard → Premium │ │ └── Soggetti a commissioni normali │ │ │ │ 🌴 TURISTA │ │ └── Utente finale, prenota e paga │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

Sistema Trial

Trial Standard (Auto-registrazione)

Nuovo utente si registra Sceglie "Prova gratuita" Piano PREMIUM per X giorni Reminder scadenza Converte o Downgrade

Trial Ambassador

Ambassador crea invito Cliente riceve email Si registra con trial attivo Ambassador tracciato Commissione se converte

Configurazioni Trial

ConfigurazioneDefaultDescrizione
Durata trial standard Host14 giorniConfigurabile da backend
Durata trial standard Business14 giorniConfigurabile da backend
Piano durante trialPremiumAccesso a tutte le funzionalità
Max durata trial Ambassador30 giorniLimite massimo attivabile
Max trial attivi per Ambassador10Contemporaneamente
Reminder 7 giorni primaAttivoEmail
Reminder 3 giorni primaAttivoEmail + Push
Reminder 1 giorno primaAttivoEmail + Push
Reminder scadutoAttivoEmail + SMS

Funzioni PHP Principali

includes/special-accounts.php PHP
/**
 * Verifica se un utente è Owner della piattaforma
 */
function sicily_is_owner($user_id) {
    global $wpdb;
    $result = $wpdb->get_var($wpdb->prepare("
        SELECT account_type FROM se_special_accounts
        WHERE user_id = %d AND account_type = 'owner' AND is_active = 1
    ", $user_id));
    return $result === 'owner';
}

/**
 * Verifica se un utente è VIP (include Owner)
 */
function sicily_is_vip($user_id) {
    global $wpdb;
    $result = $wpdb->get_var($wpdb->prepare("
        SELECT account_type FROM se_special_accounts
        WHERE user_id = %d AND is_active = 1
        AND (valid_until IS NULL OR valid_until > NOW())
    ", $user_id));
    return in_array($result, ['owner', 'vip']);
}

/**
 * Ottiene la commissione effettiva per un utente
 */
function sicily_get_user_commission_rate($user_id, $default_rate) {
    global $wpdb;
    $override = $wpdb->get_var($wpdb->prepare("
        SELECT commission_override FROM se_special_accounts
        WHERE user_id = %d AND is_active = 1
        AND (valid_until IS NULL OR valid_until > NOW())
    ", $user_id));
    
    return $override !== null ? floatval($override) : $default_rate;
}

/**
 * Crea account VIP (solo Owner può chiamare)
 */
function sicily_create_vip_account($owner_id, $user_email, $user_type, $options = []) {
    // Verifica che chi chiama sia Owner
    if (!sicily_is_owner($owner_id)) {
        return new WP_Error('unauthorized', 'Solo l\'owner può creare account VIP');
    }
    
    // Crea o trova utente
    $user = get_user_by('email', $user_email);
    if (!$user) {
        $user_id = wp_create_user($user_email, wp_generate_password(), $user_email);
        if (is_wp_error($user_id)) return $user_id;
    } else {
        $user_id = $user->ID;
    }
    
    // Inserisci account speciale
    global $wpdb;
    $wpdb->insert('se_special_accounts', [
        'user_id' => $user_id,
        'account_type' => 'vip',
        'commission_override' => $options['commission'] ?? 0,
        'plan_override' => $options['plan'] ?? 'premium',
        'valid_from' => current_time('mysql'),
        'valid_until' => $options['expires'] ?? null,
        'created_by' => $owner_id,
        'internal_notes' => $options['notes'] ?? '',
        'is_active' => 1
    ]);
    
    // Invia email invito se richiesto
    if (!empty($options['send_email'])) {
        sicily_send_vip_welcome_email($user_id, $options['message'] ?? '');
    }
    
    return $user_id;
}

/**
 * Verifica se utente ha trial attivo
 */
function sicily_get_active_trial($user_id, $user_type) {
    global $wpdb;
    return $wpdb->get_row($wpdb->prepare("
        SELECT * FROM se_trials
        WHERE user_id = %d AND user_type = %s
        AND status = 'active' AND expires_at > NOW()
    ", $user_id, $user_type));
}

/**
 * Ambassador attiva trial per cliente
 */
function sicily_ambassador_create_trial($ambassador_id, $client_email, $client_type, $days) {
    // Verifica limiti
    $config = sicily_get_config('trial_ambassador_config');
    $max_days = $config['max_duration'] ?? 30;
    $max_active = $config['max_active'] ?? 10;
    
    if ($days > $max_days) {
        return new WP_Error('max_days', "Massimo {$max_days} giorni consentiti");
    }
    
    // Conta trial attivi ambassador
    global $wpdb;
    $active = $wpdb->get_var($wpdb->prepare("
        SELECT COUNT(*) FROM se_trials
        WHERE activated_by_type = 'ambassador' AND activated_by_id = %d AND status = 'active'
    ", $ambassador_id));
    
    if ($active >= $max_active) {
        return new WP_Error('max_active', "Hai già {$max_active} trial attivi");
    }
    
    // Crea invito
    $invite_code = wp_generate_password(20, false);
    $wpdb->insert('se_trial_invites', [
        'email' => $client_email,
        'user_type' => $client_type,
        'duration_days' => $days,
        'invite_code' => $invite_code,
        'ambassador_id' => $ambassador_id,
        'expires_at' => date('Y-m-d H:i:s', strtotime('+7 days')),
        'created_at' => current_time('mysql')
    ]);
    
    // Invia email
    sicily_send_trial_invite_email($client_email, $invite_code, $ambassador_id, $days);
    
    return ['success' => true, 'invite_code' => $invite_code];
}

Test Cases

🔐 MOD_01 - Autenticazione

MOD_01_AUTH
ℹ️ Panoramica Modulo

Stato: Da sviluppare

Priorità: 🔴 Alta (MVP - Sprint 1)

Dipendenze: Nessuna (modulo base)

Descrizione

Gestisce registrazione, login, logout, recupero password e ruoli utente per tutti i tipi di utenti della piattaforma.

Ruoli WordPress Custom

RuoloSlugCapabilities Principali
Turistase_turistaread, se_book, se_review
Hostse_hostread, se_manage_struttura, se_view_bookings
Businessse_businessread, se_manage_business, se_manage_offerte
Promoterse_promoterread, se_manage_eventi
Ambassadorse_ambassadorread, se_manage_referrals, se_impersonate
Admin SEse_adminmanage_options, se_moderate, se_configure

Flussi di Registrazione

Registrazione Turista

Form registrazione Email + Password Verifica email Account attivo

Registrazione Host/Business

Landing partner Scelta tipo (Host/Business) Dati aziendali Documenti (CIN/CIR) Scelta piano/Trial Verifica admin Attivo

API Endpoints

MetodoEndpointDescrizioneAuth
POST/wp-json/sicily/v1/auth/registerRegistrazione nuovo utenteNo
POST/wp-json/sicily/v1/auth/loginLoginNo
POST/wp-json/sicily/v1/auth/logoutLogout
POST/wp-json/sicily/v1/auth/forgot-passwordRichiesta reset passwordNo
POST/wp-json/sicily/v1/auth/reset-passwordReset password con tokenNo
GET/wp-json/sicily/v1/auth/meDati utente corrente
PUT/wp-json/sicily/v1/auth/meAggiorna profilo
POST/wp-json/sicily/v1/auth/verify-emailVerifica emailNo

Codice Riferimento

includes/class-sicily-auth.php PHP
class Sicily_Auth {
    
    /**
     * Registra nuovo utente
     */
    public static function register($data) {
        // Validazione
        if (empty($data['email']) || !is_email($data['email'])) {
            return new WP_Error('invalid_email', 'Email non valida');
        }
        
        if (email_exists($data['email'])) {
            return new WP_Error('email_exists', 'Email già registrata');
        }
        
        if (strlen($data['password']) < 8) {
            return new WP_Error('weak_password', 'Password deve essere almeno 8 caratteri');
        }
        
        // Determina ruolo
        $role = $data['user_type'] ?? 'se_turista';
        $allowed_roles = ['se_turista', 'se_host', 'se_business', 'se_promoter'];
        if (!in_array($role, $allowed_roles)) {
            $role = 'se_turista';
        }
        
        // Crea utente
        $user_id = wp_create_user(
            $data['email'], 
            $data['password'], 
            $data['email']
        );
        
        if (is_wp_error($user_id)) {
            return $user_id;
        }
        
        // Assegna ruolo
        $user = new WP_User($user_id);
        $user->set_role($role);
        
        // Salva meta
        update_user_meta($user_id, 'first_name', $data['first_name'] ?? '');
        update_user_meta($user_id, 'last_name', $data['last_name'] ?? '');
        update_user_meta($user_id, 'se_phone', $data['phone'] ?? '');
        update_user_meta($user_id, 'se_email_verified', 0);
        
        // Gestisci trial per host/business
        if (in_array($role, ['se_host', 'se_business'])) {
            self::activate_trial($user_id, str_replace('se_', '', $role));
        }
        
        // Invia email verifica
        self::send_verification_email($user_id);
        
        // Gestisci referral code
        if (!empty($data['ref_code'])) {
            self::process_referral($user_id, $data['ref_code']);
        }
        
        return $user_id;
    }
    
    /**
     * Attiva trial per nuovo utente
     */
    private static function activate_trial($user_id, $user_type) {
        $config = sicily_get_config('trial_config');
        $duration = $config['duration_' . $user_type] ?? 14;
        
        global $wpdb;
        $wpdb->insert('se_trials', [
            'user_id' => $user_id,
            'user_type' => $user_type,
            'activated_by_type' => 'self',
            'started_at' => current_time('mysql'),
            'expires_at' => date('Y-m-d H:i:s', strtotime("+{$duration} days")),
            'duration_days' => $duration,
            'trial_plan' => 'premium',
            'status' => 'active',
            'created_at' => current_time('mysql')
        ]);
    }
    
    /**
     * Login utente
     */
    public static function login($email, $password, $remember = false) {
        $user = wp_authenticate($email, $password);
        
        if (is_wp_error($user)) {
            return new WP_Error('invalid_credentials', 'Credenziali non valide');
        }
        
        // Imposta cookie
        wp_set_auth_cookie($user->ID, $remember);
        wp_set_current_user($user->ID);
        
        // Log login
        update_user_meta($user->ID, 'se_last_login', current_time('mysql'));
        
        return [
            'success' => true,
            'user' => self::get_user_data($user->ID),
            'redirect' => self::get_redirect_url($user)
        ];
    }
    
    /**
     * Ottieni URL redirect post-login
     */
    private static function get_redirect_url($user) {
        if (in_array('se_host', $user->roles)) {
            return '/partner/dashboard/';
        }
        if (in_array('se_business', $user->roles)) {
            return '/partner/dashboard/';
        }
        if (in_array('se_ambassador', $user->roles)) {
            return '/partner/ambassador/';
        }
        if (in_array('administrator', $user->roles) || in_array('se_admin', $user->roles)) {
            return '/wp-admin/';
        }
        return '/account/';
    }
}

Test Cases

🏠 MOD_02 - Strutture

MOD_02_STRUTTURE
ℹ️ Panoramica Modulo

Stato: Da sviluppare

Priorità: 🔴 Alta (MVP - Sprint 1)

Dipendenze: MOD_01 Autenticazione

Descrizione

Gestisce le strutture ricettive (Hotel, B&B, Case Vacanza, Ostelli). Include creazione, modifica, pubblicazione e gestione delle impostazioni.

Tipologie Strutture

🏨
Hotel
Struttura con multiple camere. Modalità affitto: camere singole.
🏡
B&B
Bed & Breakfast con camere. Può affittare singole camere o intero.
🏖️
Casa Vacanza
Affitto intera unità. Calendario unico, prezzo per alloggio.
🏢
Appartamento
Simile a casa vacanza. Affitto intera unità.
🛏️
Ostello
Camere multiple + posti letto in dormitorio.

Modalità di Affitto

┌─────────────────────────────────────────────────────────────────────────────┐ │ MODALITÀ DI AFFITTO │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ INTERA UNITÀ (casa_vacanza, appartamento) CAMERE (hotel, b&b, ostello)│ │ ─────────────────────────────────────── ────────────────────────────│ │ │ │ • 1 struttura = 1 calendario • 1 struttura = N calendari │ │ • 1 prezzo per tutto • N prezzi (per camera) │ │ • Capacità: max X ospiti • Capacità: per camera │ │ • Prenotazione blocca tutto • Prenotazione blocca camera│ │ │ │ Esempio calendario: Esempio planning: │ │ ┌────────────────────┐ ┌────────────────────────┐ │ │ │ Gen 2026 │ │ L M M G V │ │ │ │ 1-5: Occupato │ │ Cam.101 ██ ██ ░░ ░░ ░░│ │ │ │ 6-10: Libero │ │ Cam.102 ░░ ░░ ██ ██ ██│ │ │ │ 11-15: Occupato │ │ Cam.201 ██ ██ ██ ░░ ░░│ │ │ └────────────────────┘ └────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

Custom Post Type: struttura

includes/cpt/cpt-struttura.php PHP
function sicily_register_cpt_struttura() {
    $labels = [
        'name' => 'Strutture',
        'singular_name' => 'Struttura',
        'add_new' => 'Aggiungi Struttura',
        'add_new_item' => 'Aggiungi Nuova Struttura',
        'edit_item' => 'Modifica Struttura',
        'view_item' => 'Visualizza Struttura',
        'search_items' => 'Cerca Strutture',
        'not_found' => 'Nessuna struttura trovata',
    ];
    
    $args = [
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_rest' => true,
        'query_var' => true,
        'rewrite' => ['slug' => 'strutture', 'with_front' => false],
        'capability_type' => 'post',
        'has_archive' => true,
        'hierarchical' => false,
        'menu_position' => 5,
        'menu_icon' => 'dashicons-building',
        'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
        'taxonomies' => ['se_citta', 'se_servizi_struttura'],
    ];
    
    register_post_type('struttura', $args);
}
add_action('init', 'sicily_register_cpt_struttura');

API Endpoints

MetodoEndpointDescrizione
GET/wp-json/sicily/v1/struttureLista strutture (con filtri)
GET/wp-json/sicily/v1/strutture/{id}Dettaglio struttura
POST/wp-json/sicily/v1/struttureCrea struttura (auth host)
PUT/wp-json/sicily/v1/strutture/{id}Aggiorna struttura
DELETE/wp-json/sicily/v1/strutture/{id}Elimina struttura
GET/wp-json/sicily/v1/strutture/{id}/disponibilitaVerifica disponibilità
GET/wp-json/sicily/v1/strutture/{id}/prezziCalcola prezzi per date

Test Cases

📅 MOD_10 - Prenotazioni

MOD_10_PRENOTAZIONI
ℹ️ Panoramica Modulo

Stato: Da sviluppare

Priorità: 🔴 Alta (MVP - Sprint 2)

Dipendenze: MOD_02, MOD_03, MOD_04, MOD_05

Descrizione

Cuore del sistema. Gestisce la creazione, modifica, cancellazione e ciclo di vita delle prenotazioni per alloggi.

Stati Prenotazione

┌─────────────────────────────────────────────────────────────────────────────┐ │ CICLO DI VITA PRENOTAZIONE │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ pending_confirmation│ │ │ │ (se conferma manuale)│ │ │ └──────────┬──────────┘ │ │ │ host conferma │ │ ▼ │ │ ┌─────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │ CREATA │──────────►│ pending_payment │──────────►│ confirmed │ │ │ └─────────────┘ avvia └─────────────────┘ paga └──────┬──────┘ │ │ checkout │ │ │ │ │ │ ┌────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌───────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │ │cancelled_guest│ │ pending_balance │ │ checked_in │ │ │ │ │ │ (se caparra) │ │ │ │ │ └───────────────┘ └────────┬────────┘ └──────┬──────┘ │ │ │ paga saldo │ │ │ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ confirmed │ │ completed │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ STATI FINALI NEGATIVI: │ │ • cancelled_guest - Cancellata dal turista │ │ • cancelled_host - Cancellata dall'host │ │ • noshow - Turista non si è presentato │ │ • refunded - Rimborsata │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

Flusso Creazione Prenotazione

1. Selezione date 2. Verifica disponibilità 3. Calcolo prezzo 4. Dati ospiti 5. Checkout 6. Pagamento 7. Conferma

Calcolo Prezzo

includes/bookings/class-sicily-price-calculator.php PHP
class Sicily_Price_Calculator {
    
    private $struttura_id;
    private $unita_id;
    private $check_in;
    private $check_out;
    private $adults;
    private $children;
    
    public function calculate() {
        $nights = $this->get_nights();
        $base_price = $this->get_base_price_per_night();
        
        $breakdown = [
            'nights' => $nights,
            'base_per_night' => $base_price,
            'subtotal' => 0,
            'extras' => [],
            'taxes' => [],
            'discounts' => [],
            'total' => 0
        ];
        
        // Calcola prezzo per ogni notte (può variare)
        $date = new DateTime($this->check_in);
        for ($i = 0; $i < $nights; $i++) {
            $night_price = $this->get_price_for_date($date->format('Y-m-d'));
            $breakdown['subtotal'] += $night_price;
            $date->modify('+1 day');
        }
        
        // Aggiungi extra obbligatori
        $extras = $this->get_extras();
        foreach ($extras as $extra) {
            if ($extra['obbligatorio']) {
                $amount = $this->calculate_extra_amount($extra);
                $breakdown['extras'][] = [
                    'name' => $extra['nome'],
                    'amount' => $amount
                ];
            }
        }
        
        // Calcola tassa di soggiorno
        $tax = $this->calculate_tourist_tax();
        if ($tax > 0) {
            $breakdown['taxes'][] = [
                'name' => 'Tassa di soggiorno',
                'amount' => $tax
            ];
        }
        
        // Applica sconti
        $discounts = $this->apply_discounts();
        $breakdown['discounts'] = $discounts;
        
        // Totale
        $breakdown['total'] = $breakdown['subtotal'] 
            + array_sum(array_column($breakdown['extras'], 'amount'))
            + array_sum(array_column($breakdown['taxes'], 'amount'))
            - array_sum(array_column($breakdown['discounts'], 'amount'));
        
        return $breakdown;
    }
    
    private function get_price_for_date($date) {
        $base = $this->get_base_price_per_night();
        $rules = get_field('regole_prezzo', $this->unita_id ?: $this->struttura_id);
        
        foreach ($rules as $rule) {
            if ($this->rule_applies($rule, $date)) {
                if ($rule['tipo_valore'] === 'percentuale') {
                    $base = $base * (1 + $rule['valore'] / 100);
                } else {
                    $base = $rule['valore'];
                }
            }
        }
        
        return $base;
    }
    
    private function calculate_tourist_tax() {
        // Ottieni comune dalla struttura
        $citta = get_field('indirizzo', $this->struttura_id)['citta'];
        
        // Ottieni tariffa dal comune
        $tariffa = sicily_get_tourist_tax_rate($citta, $this->struttura_id);
        
        // Calcola (per persona per notte, max X notti)
        $nights = min($this->get_nights(), $tariffa['max_nights'] ?? 10);
        $persons = $this->adults; // Bambini spesso esenti
        
        return $tariffa['amount'] * $nights * $persons;
    }
}

API Endpoints

MetodoEndpointDescrizione
POST/wp-json/sicily/v1/bookingsCrea prenotazione
GET/wp-json/sicily/v1/bookings/{id}Dettaglio prenotazione
PATCH/wp-json/sicily/v1/bookings/{id}Aggiorna prenotazione
POST/wp-json/sicily/v1/bookings/{id}/cancelCancella prenotazione
POST/wp-json/sicily/v1/bookings/{id}/confirmConferma manuale host
POST/wp-json/sicily/v1/bookings/{id}/checkinRegistra check-in
GET/wp-json/sicily/v1/bookings/calculate-priceCalcola prezzo

Test Cases

🛏️ MOD_03 - Unità/Camere

MOD_03_UNITA

Scopo

Gestisce le singole unità abitabili all'interno di una struttura: camere d'hotel, appartamenti, posti letto in dormitori.

Tipi di Unità

camera
Camera standard hotel/B&B
appartamento
Appartamento indipendente
suite
Suite di lusso
posto_letto
Letto in dormitorio condiviso

Custom Post Type: unita

📋 ACF Fields
// ACF Field Group: Unità
[
    'struttura_id'       => 'relationship:struttura',  // Parent structure
    'tipo_unita'         => 'select',                   // camera|appartamento|suite|posto_letto
    'nome_unita'         => 'text',                     // es. "Camera Etna", "Suite Panoramica"
    'codice_interno'     => 'text',                     // es. "101", "A1"
    
    // Capacità
    'posti_letto'        => 'number',                   // Capacità standard
    'posti_letto_max'    => 'number',                   // Max con letti aggiunti
    'letti_singoli'      => 'number',
    'letti_matrimoniali' => 'number',
    'divani_letto'       => 'number',
    
    // Dimensioni
    'superficie_mq'      => 'number',
    'piano'              => 'text',
    'vista'              => 'select',                   // mare|montagna|giardino|cortile|strada
    
    // Servizi specifici unità
    'servizi_unita'      => 'checkbox[]',               // bagno_privato|aria_condizionata|tv|minibar|cassaforte|balcone|terrazza
    'accessibilita'      => 'true_false',               // Accessibile disabili
    
    // Media
    'galleria_unita'     => 'gallery',
    'planimetria'        => 'image',
    
    // Prezzi (override struttura)
    'prezzo_base_notte'  => 'number',                   // Se diverso da struttura
    'usa_prezzi_struttura' => 'true_false',             // Eredita prezzi parent
    
    // Stato
    'stato_unita'        => 'select',                   // attiva|manutenzione|disattivata
    'note_interne'       => 'textarea'
]

Database: se_units (Cache/Denormalizzazione)

📋 Schema SQL
CREATE TABLE se_units (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,           -- wp_posts.ID
    struttura_id BIGINT UNSIGNED NOT NULL,      -- Parent structure
    tipo_unita ENUM('camera','appartamento','suite','posto_letto') NOT NULL,
    nome VARCHAR(200) NOT NULL,
    codice_interno VARCHAR(50),
    posti_letto TINYINT UNSIGNED NOT NULL DEFAULT 2,
    posti_letto_max TINYINT UNSIGNED,
    prezzo_base DECIMAL(10,2),
    stato ENUM('attiva','manutenzione','disattivata') DEFAULT 'attiva',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_post (post_id),
    KEY idx_struttura (struttura_id),
    KEY idx_stato (stato),
    KEY idx_tipo (tipo_unita),
    FOREIGN KEY (struttura_id) REFERENCES se_structures(id) ON DELETE CASCADE
) ENGINE=InnoDB;

Test Cases

📅 MOD_04 - Calendario Disponibilità

MOD_04_CALENDARIO

Scopo

Gestisce disponibilità in tempo reale, blocchi manuali, sincronizzazione iCal con OTA (Booking, Airbnb), soft-lock durante checkout.

Logica Disponibilità

Modalità Intera Unità: Un solo calendario per tutta la struttura. Se occupata = non disponibile.
Modalità Camere: Ogni unità ha proprio calendario. Struttura disponibile se almeno 1 unità libera.

Database: se_availability

📋 Schema SQL
CREATE TABLE se_availability (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    entity_type ENUM('struttura','unita') NOT NULL,
    entity_id BIGINT UNSIGNED NOT NULL,         -- struttura_id o unita_id
    data DATE NOT NULL,
    
    -- Stato disponibilità
    stato ENUM('disponibile','occupato','bloccato','soft_lock') DEFAULT 'disponibile',
    
    -- Riferimenti
    booking_id BIGINT UNSIGNED,                  -- se occupato da prenotazione
    source ENUM('manual','booking','ical_import','soft_lock') DEFAULT 'manual',
    ical_event_uid VARCHAR(255),                 -- UID evento iCal importato
    
    -- Soft-lock info
    soft_lock_expires_at TIMESTAMP,
    soft_lock_session_id VARCHAR(100),
    
    -- Prezzi giornalieri (override)
    prezzo_override DECIMAL(10,2),
    minimo_notti_override TINYINT UNSIGNED,
    
    -- Metadata
    note VARCHAR(500),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_entity_date (entity_type, entity_id, data),
    KEY idx_stato (stato),
    KEY idx_booking (booking_id),
    KEY idx_soft_lock (soft_lock_expires_at)
) ENGINE=InnoDB;

iCal Sync

📋 Database: se_calendar_sync
CREATE TABLE se_calendar_sync (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    entity_type ENUM('struttura','unita') NOT NULL,
    entity_id BIGINT UNSIGNED NOT NULL,
    
    -- iCal URLs
    ical_import_url VARCHAR(500),               -- URL da importare (Booking, Airbnb)
    ical_export_token VARCHAR(64),              -- Token per export URL
    
    -- Sync status
    last_import_at TIMESTAMP,
    last_import_status ENUM('success','error','pending'),
    last_import_error TEXT,
    events_imported INT UNSIGNED DEFAULT 0,
    
    -- Settings
    sync_enabled BOOLEAN DEFAULT TRUE,
    sync_interval_hours TINYINT UNSIGNED DEFAULT 6,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    KEY idx_entity (entity_type, entity_id),
    KEY idx_next_sync (sync_enabled, last_import_at)
) ENGINE=InnoDB;

Soft-Lock Logic

Durata: 15 minuti durante checkout
Trigger: Utente inizia checkout
Rilascio: Pagamento completato, timeout, o abbandono carrello

API Endpoints

📋 REST API
// GET /wp-json/se/v1/availability/{entity_type}/{entity_id}
// Query params: from (date), to (date)
// Response: array of dates with availability status

// POST /wp-json/se/v1/availability/check
// Body: { entity_type, entity_id, check_in, check_out, guests }
// Response: { available: bool, price: number, conflicts: [] }

// POST /wp-json/se/v1/availability/soft-lock
// Body: { entity_type, entity_id, check_in, check_out, session_id }
// Response: { locked: bool, expires_at: timestamp }

// DELETE /wp-json/se/v1/availability/soft-lock/{session_id}
// Releases soft-lock

Test Cases

💰 MOD_05 - Prezzi e Tariffe

MOD_05_PREZZI

Scopo

Engine completo per calcolo prezzi: base + stagionalità + weekend + occupancy + minimo notti + extra + tassa soggiorno.

Struttura Prezzi

Componente Descrizione Priorità
Prezzo Base Prezzo notte standard 1 (base)
Regola Stagionale Aumento/sconto % per periodo 2
Weekend Markup Aumento venerdì-domenica 3
Occupancy Pricing Prezzo per numero ospiti 4
Override Giornaliero Prezzo specifico data 5 (max)

Database: se_pricing_rules

📋 Schema SQL
CREATE TABLE se_pricing_rules (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    entity_type ENUM('struttura','unita') NOT NULL,
    entity_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo regola
    rule_type ENUM('seasonal','weekend','occupancy','last_minute','early_bird','length_of_stay') NOT NULL,
    nome_regola VARCHAR(100),
    
    -- Periodo applicazione
    data_inizio DATE,
    data_fine DATE,
    giorni_settimana SET('lun','mar','mer','gio','ven','sab','dom'),
    
    -- Modifica prezzo
    modifica_tipo ENUM('percentuale','fisso','prezzo_assoluto') NOT NULL,
    modifica_valore DECIMAL(10,2) NOT NULL,       -- +20 (%), +50 (€), o 150 (assoluto)
    
    -- Condizioni aggiuntive
    minimo_notti TINYINT UNSIGNED,
    massimo_notti TINYINT UNSIGNED,
    minimo_ospiti TINYINT UNSIGNED,
    massimo_ospiti TINYINT UNSIGNED,
    giorni_anticipo_min INT,                       -- Per early bird
    giorni_anticipo_max INT,                       -- Per last minute
    
    -- Priorità e stato
    priorita TINYINT UNSIGNED DEFAULT 10,
    attiva BOOLEAN DEFAULT TRUE,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    KEY idx_entity (entity_type, entity_id),
    KEY idx_periodo (data_inizio, data_fine),
    KEY idx_attiva (attiva)
) ENGINE=InnoDB;

Extra e Supplementi

📋 Database: se_extras
CREATE TABLE se_extras (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    entity_type ENUM('struttura','unita') NOT NULL,
    entity_id BIGINT UNSIGNED NOT NULL,
    
    nome VARCHAR(100) NOT NULL,
    nome_en VARCHAR(100),
    descrizione TEXT,
    
    -- Pricing
    prezzo DECIMAL(10,2) NOT NULL,
    tipo_prezzo ENUM('per_soggiorno','per_notte','per_persona','per_persona_notte') NOT NULL,
    
    -- Obbligatorietà
    obbligatorio BOOLEAN DEFAULT FALSE,
    incluso_nel_prezzo BOOLEAN DEFAULT FALSE,     -- Già incluso, mostrato per trasparenza
    
    -- Condizioni
    minimo_notti TINYINT UNSIGNED,
    solo_per_gruppi BOOLEAN DEFAULT FALSE,        -- Solo se ospiti > X
    
    -- Categoria
    categoria ENUM('pulizia','biancheria','animali','bambini','parcheggio','colazione','altro') NOT NULL,
    
    attivo BOOLEAN DEFAULT TRUE,
    ordine TINYINT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_entity (entity_type, entity_id),
    KEY idx_categoria (categoria)
) ENGINE=InnoDB;

Tassa di Soggiorno

📋 Database: se_tourist_tax
CREATE TABLE se_tourist_tax (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    comune VARCHAR(100) NOT NULL,
    provincia CHAR(2) NOT NULL,
    
    -- Importi per categoria struttura
    importo_hotel_5stelle DECIMAL(5,2),
    importo_hotel_4stelle DECIMAL(5,2),
    importo_hotel_3stelle DECIMAL(5,2),
    importo_hotel_1_2stelle DECIMAL(5,2),
    importo_bb DECIMAL(5,2),
    importo_casa_vacanza DECIMAL(5,2),
    importo_ostello DECIMAL(5,2),
    importo_campeggio DECIMAL(5,2),
    
    -- Regole
    max_notti TINYINT UNSIGNED DEFAULT 7,         -- Max notti tassabili
    esenti_sotto_anni TINYINT UNSIGNED DEFAULT 10,
    esenti_residenti BOOLEAN DEFAULT TRUE,
    
    -- Validità
    valido_da DATE NOT NULL,
    valido_a DATE,
    
    note TEXT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_comune_validita (comune, valido_da),
    KEY idx_provincia (provincia)
) ENGINE=InnoDB;

Algoritmo Calcolo Prezzo

📋 Pseudocode
function calcolaPrezzoTotale(struttura, checkIn, checkOut, ospiti) {
    let totale = 0;
    let notti = diffGiorni(checkIn, checkOut);
    
    for (let data = checkIn; data < checkOut; data = addGiorni(data, 1)) {
        // 1. Prezzo base
        let prezzoNotte = struttura.prezzo_base;
        
        // 2. Override giornaliero (priorità massima)
        let override = getOverride(struttura, data);
        if (override) {
            prezzoNotte = override.prezzo;
        } else {
            // 3. Applica regole in ordine priorità
            let regole = getRegoleAttive(struttura, data, ospiti, notti);
            regole.sort((a, b) => a.priorita - b.priorita);
            
            for (let regola of regole) {
                prezzoNotte = applicaRegola(prezzoNotte, regola);
            }
        }
        
        totale += prezzoNotte;
    }
    
    // 4. Aggiungi extra obbligatori
    let extras = getExtrasObbligatori(struttura);
    for (let extra of extras) {
        totale += calcolaExtra(extra, notti, ospiti);
    }
    
    // 5. Calcola tassa soggiorno
    let tassa = calcolaTassaSoggiorno(struttura, ospiti, notti);
    
    return {
        subtotale: totale,
        tassa_soggiorno: tassa,
        totale: totale + tassa,
        dettaglio_notti: [...],
        dettaglio_extras: [...]
    };
}

Test Cases

🏪 MOD_06 - Business

MOD_06_BUSINESS

Scopo

Gestione attività locali: tour operator, ristoranti, noleggi, artigiani, guide turistiche. Ogni business può pubblicare offerte/esperienze.

Tipologie Business

tour_operator
Escursioni, tour guidati, gite in barca
ristorante
Ristoranti, trattorie, street food
noleggio
Auto, moto, bici, barche, attrezzature
artigiano
Ceramiche, tessuti, prodotti tipici
guida
Guide turistiche autorizzate
wellness
Spa, centri benessere, massaggi
trasporto
Transfer, NCC, taxi
altro
Altre attività turistiche

Custom Post Type: business

📋 ACF Fields
// ACF Field Group: Business
[
    'user_id'            => 'user',                  // Proprietario account
    'tipo_business'      => 'select',                // Tipologia (vedi sopra)
    'ragione_sociale'    => 'text',
    'partita_iva'        => 'text',
    'codice_fiscale'     => 'text',
    
    // Contatti
    'indirizzo'          => 'text',
    'citta'              => 'text',
    'cap'                => 'text',
    'provincia'          => 'select',
    'telefono'           => 'text',
    'email'              => 'email',
    'website'            => 'url',
    
    // Geolocalizzazione
    'coordinate'         => 'google_map',
    'zona_operativa'     => 'checkbox[]',            // Comuni/zone servite
    
    // Presentazione
    'nome_commerciale'   => 'text',
    'descrizione_it'     => 'wysiwyg',
    'descrizione_en'     => 'wysiwyg',
    'descrizione_de'     => 'wysiwyg',
    'logo'               => 'image',
    'galleria'           => 'gallery',
    'video_presentazione'=> 'url',
    
    // Certificazioni
    'licenze'            => 'repeater' => [
        'tipo_licenza'    => 'text',
        'numero'          => 'text',
        'scadenza'        => 'date',
        'documento'       => 'file'
    ],
    
    // Orari
    'orari_apertura'     => 'repeater' => [
        'giorno'          => 'select',
        'aperto'          => 'true_false',
        'ora_apertura'    => 'time',
        'ora_chiusura'    => 'time',
        'pausa_pranzo'    => 'true_false',
        'pausa_da'        => 'time',
        'pausa_a'         => 'time'
    ],
    
    // Pagamenti accettati
    'metodi_pagamento'   => 'checkbox[]',            // contanti|carta|bonifico|satispay
    
    // Stripe Connect
    'stripe_account_id'  => 'text',
    'stripe_onboarding_complete' => 'true_false',
    
    // Piano e stato
    'piano_abbonamento'  => 'select',                // base|standard|premium
    'stato_account'      => 'select',                // attivo|sospeso|in_revisione
    'data_iscrizione'    => 'date',
    'trial_expires_at'   => 'date'
]

Database: se_businesses (Cache)

📋 Schema SQL
CREATE TABLE se_businesses (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    
    tipo_business ENUM('tour_operator','ristorante','noleggio','artigiano','guida','wellness','trasporto','altro') NOT NULL,
    nome_commerciale VARCHAR(200) NOT NULL,
    ragione_sociale VARCHAR(200),
    partita_iva VARCHAR(20),
    
    -- Location
    citta VARCHAR(100),
    provincia CHAR(2),
    lat DECIMAL(10,8),
    lng DECIMAL(11,8),
    
    -- Stripe
    stripe_account_id VARCHAR(50),
    stripe_onboarding_complete BOOLEAN DEFAULT FALSE,
    
    -- Piano
    piano ENUM('base','standard','premium') DEFAULT 'base',
    commissione_percentuale DECIMAL(4,2) DEFAULT 12.00,
    
    -- Stato
    stato ENUM('attivo','sospeso','in_revisione','trial') DEFAULT 'trial',
    trial_expires_at DATE,
    
    -- Stats cache
    rating_medio DECIMAL(2,1) DEFAULT 0,
    numero_recensioni INT UNSIGNED DEFAULT 0,
    offerte_attive INT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_post (post_id),
    KEY idx_user (user_id),
    KEY idx_tipo (tipo_business),
    KEY idx_citta (citta),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Test Cases

🎯 MOD_07 - Offerte e Esperienze

MOD_07_OFFERTE

Scopo

Gestione catalogo offerte dei business: esperienze, tour, prodotti, servizi. Sistema di prenotazione con disponibilità e varianti.

Tipologie Offerta

esperienza
Tour, escursioni, attività con data/ora specifica
prodotto
Prodotti fisici (ceramiche, alimentari, artigianato)
servizio
Transfer, noleggio, guide private
voucher
Buoni regalo, degustazioni

Custom Post Type: offerta

📋 ACF Fields
// ACF Field Group: Offerta
[
    'business_id'        => 'relationship:business',
    'tipo_offerta'       => 'select',                // esperienza|prodotto|servizio|voucher
    'categoria'          => 'taxonomy:categoria_offerta',
    
    // Contenuto multilingua
    'titolo_it'          => 'text',
    'titolo_en'          => 'text',
    'titolo_de'          => 'text',
    'descrizione_it'     => 'wysiwyg',
    'descrizione_en'     => 'wysiwyg',
    'descrizione_de'     => 'wysiwyg',
    'cosa_include'       => 'repeater',
    'cosa_non_include'   => 'repeater',
    'punto_ritrovo'      => 'text',
    'coordinate_ritrovo' => 'google_map',
    
    // Media
    'immagine_principale'=> 'image',
    'galleria'           => 'gallery',
    'video'              => 'url',
    
    // Pricing
    'prezzo_base'        => 'number',
    'prezzo_scontato'    => 'number',
    'sconto_percentuale' => 'number',
    'tipo_prezzo'        => 'select',                // per_persona|per_gruppo|fisso
    'prezzo_bambini'     => 'number',
    'eta_bambino_max'    => 'number',
    
    // Varianti (es. mezza giornata, giornata intera)
    'varianti'           => 'repeater' => [
        'nome_variante'   => 'text',
        'prezzo_variante' => 'number',
        'durata_minuti'   => 'number'
    ],
    
    // Capacità e disponibilità
    'posti_min'          => 'number',
    'posti_max'          => 'number',
    'durata_minuti'      => 'number',
    'preavviso_ore'      => 'number',                // Anticipo minimo prenotazione
    
    // Calendario (per esperienze)
    'disponibilita_tipo' => 'select',                // sempre|giorni_settimana|date_specifiche
    'giorni_disponibili' => 'checkbox[]',
    'orari_partenza'     => 'repeater' => [
        'ora'             => 'time'
    ],
    'date_escluse'       => 'repeater' => [
        'data'            => 'date'
    ],
    'stagionalita'       => 'repeater' => [
        'data_inizio'     => 'date',
        'data_fine'       => 'date',
        'attivo'          => 'true_false'
    ],
    
    // Requisiti
    'eta_minima'         => 'number',
    'difficolta'         => 'select',                // facile|media|difficile
    'accessibilita'      => 'true_false',
    'requisiti_fisici'   => 'textarea',
    'cosa_portare'       => 'textarea',
    
    // Policy
    'cancellazione_gratuita_ore' => 'number',
    'policy_cancellazione' => 'select',
    
    // Stato
    'stato'              => 'select',                // bozza|attiva|sospesa|esaurita
    'in_evidenza'        => 'true_false',
    'ordine'             => 'number'
]

Database: se_offers

📋 Schema SQL
CREATE TABLE se_offers (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,
    business_id BIGINT UNSIGNED NOT NULL,
    
    tipo ENUM('esperienza','prodotto','servizio','voucher') NOT NULL,
    titolo VARCHAR(200) NOT NULL,
    
    -- Pricing
    prezzo_base DECIMAL(10,2) NOT NULL,
    prezzo_scontato DECIMAL(10,2),
    tipo_prezzo ENUM('per_persona','per_gruppo','fisso') DEFAULT 'per_persona',
    
    -- Capacità
    posti_min TINYINT UNSIGNED DEFAULT 1,
    posti_max SMALLINT UNSIGNED,
    durata_minuti SMALLINT UNSIGNED,
    
    -- Location
    citta VARCHAR(100),
    lat DECIMAL(10,8),
    lng DECIMAL(11,8),
    
    -- Stato
    stato ENUM('bozza','attiva','sospesa','esaurita') DEFAULT 'bozza',
    in_evidenza BOOLEAN DEFAULT FALSE,
    
    -- Stats
    rating_medio DECIMAL(2,1) DEFAULT 0,
    numero_recensioni INT UNSIGNED DEFAULT 0,
    prenotazioni_totali INT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_post (post_id),
    FOREIGN KEY (business_id) REFERENCES se_businesses(id) ON DELETE CASCADE,
    KEY idx_tipo (tipo),
    KEY idx_citta (citta),
    KEY idx_stato (stato),
    KEY idx_prezzo (prezzo_base)
) ENGINE=InnoDB;

-- Disponibilità specifica per data/ora
CREATE TABLE se_offer_availability (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    offer_id BIGINT UNSIGNED NOT NULL,
    
    data DATE NOT NULL,
    ora_inizio TIME,
    ora_fine TIME,
    
    posti_totali SMALLINT UNSIGNED NOT NULL,
    posti_prenotati SMALLINT UNSIGNED DEFAULT 0,
    posti_disponibili SMALLINT UNSIGNED GENERATED ALWAYS AS (posti_totali - posti_prenotati) STORED,
    
    prezzo_override DECIMAL(10,2),
    stato ENUM('disponibile','esaurito','cancellato') DEFAULT 'disponibile',
    
    FOREIGN KEY (offer_id) REFERENCES se_offers(id) ON DELETE CASCADE,
    UNIQUE KEY uk_offer_slot (offer_id, data, ora_inizio),
    KEY idx_data (data),
    KEY idx_disponibili (posti_disponibili)
) ENGINE=InnoDB;

Test Cases

🎉 MOD_08 - Eventi

MOD_08_EVENTI

Scopo

Gestione eventi locali: feste patronali, sagre, concerti, mostre. Pubblicazione a pagamento con piani diversi.

Piani Pubblicazione Eventi

Piano Prezzo Durata Features
Base €25 30 giorni Listing base, 3 foto
Standard €49 60 giorni + In evidenza categoria, 10 foto
Premium €99 90 giorni + Homepage, social share, galleria illimitata
Sponsor €149 90 giorni + Banner dedicato, newsletter, priority

Custom Post Type: evento

📋 ACF Fields
// ACF Field Group: Evento
[
    'promoter_id'        => 'user',                  // Chi pubblica
    'organizzatore'      => 'text',                  // Nome organizzatore
    'contatto_email'     => 'email',
    'contatto_telefono'  => 'text',
    'website_evento'     => 'url',
    
    // Contenuto
    'titolo_it'          => 'text',
    'titolo_en'          => 'text',
    'descrizione_it'     => 'wysiwyg',
    'descrizione_en'     => 'wysiwyg',
    'categoria'          => 'taxonomy:categoria_evento',  // sagra|concerto|mostra|festa|sport|teatro
    
    // Date e orari
    'data_inizio'        => 'date',
    'data_fine'          => 'date',
    'orario_inizio'      => 'time',
    'orario_fine'        => 'time',
    'ricorrenza'         => 'select',                // singolo|giornaliero|settimanale
    'date_multiple'      => 'repeater' => [
        'data'            => 'date',
        'orario'          => 'text'
    ],
    
    // Location
    'luogo_nome'         => 'text',
    'indirizzo'          => 'text',
    'citta'              => 'text',
    'provincia'          => 'select',
    'coordinate'         => 'google_map',
    
    // Media
    'immagine_copertina' => 'image',
    'galleria'           => 'gallery',
    'video'              => 'url',
    'locandina_pdf'      => 'file',
    
    // Biglietti (opzionale - link esterno)
    'biglietti_disponibili' => 'true_false',
    'prezzo_biglietto'   => 'text',                  // "Gratuito", "€10-25", etc.
    'link_biglietti'     => 'url',
    
    // Piano pubblicazione
    'piano_pubblicazione'=> 'select',                // base|standard|premium|sponsor
    'pubblicazione_pagata' => 'true_false',
    'pubblicazione_scadenza' => 'date',
    'in_evidenza'        => 'true_false',
    'in_homepage'        => 'true_false',
    
    // Stato
    'stato'              => 'select'                 // bozza|in_revisione|pubblicato|scaduto|annullato
]

Database: se_events

📋 Schema SQL
CREATE TABLE se_events (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    post_id BIGINT UNSIGNED NOT NULL,
    promoter_id BIGINT UNSIGNED NOT NULL,
    
    titolo VARCHAR(200) NOT NULL,
    organizzatore VARCHAR(200),
    
    -- Date
    data_inizio DATE NOT NULL,
    data_fine DATE,
    orario_inizio TIME,
    
    -- Location
    luogo_nome VARCHAR(200),
    citta VARCHAR(100),
    provincia CHAR(2),
    lat DECIMAL(10,8),
    lng DECIMAL(11,8),
    
    -- Categoria
    categoria VARCHAR(50),
    
    -- Pubblicazione
    piano ENUM('base','standard','premium','sponsor') DEFAULT 'base',
    pubblicazione_scadenza DATE,
    in_evidenza BOOLEAN DEFAULT FALSE,
    in_homepage BOOLEAN DEFAULT FALSE,
    
    -- Stato
    stato ENUM('bozza','in_revisione','pubblicato','scaduto','annullato') DEFAULT 'bozza',
    
    -- Stats
    visualizzazioni INT UNSIGNED DEFAULT 0,
    click_biglietti INT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_post (post_id),
    KEY idx_promoter (promoter_id),
    KEY idx_date (data_inizio, data_fine),
    KEY idx_citta (citta),
    KEY idx_stato (stato),
    KEY idx_evidenza (in_evidenza, in_homepage)
) ENGINE=InnoDB;

Test Cases

🔎 MOD_09 - Ricerca e Filtri

MOD_09_RICERCA

Scopo

Motore di ricerca unificato per strutture, esperienze, eventi. Filtri avanzati, geolocalizzazione, ordinamento.

Filtri Strutture

📋 Parametri Ricerca
// Filtri disponibili per ricerca strutture
{
    // Location
    destinazione: string,          // Città, zona, o "vicino a me"
    lat: number,
    lng: number,
    raggio_km: number,             // Per ricerca geografica
    
    // Date
    check_in: date,
    check_out: date,
    flessibile: boolean,           // ±3 giorni
    
    // Ospiti
    adulti: number,
    bambini: number,
    neonati: number,
    animali: boolean,
    
    // Tipologia
    tipo_struttura: string[],      // hotel|bb|casa_vacanza|ostello
    tipo_alloggio: string[],       // intero|camera_privata|camera_condivisa
    
    // Prezzo
    prezzo_min: number,
    prezzo_max: number,
    prezzo_per: 'notte'|'totale',
    
    // Servizi
    servizi: string[],             // wifi|piscina|parcheggio|aria_condizionata|...
    
    // Caratteristiche
    stelle_min: number,
    rating_min: number,
    prenotazione_istantanea: boolean,
    cancellazione_gratuita: boolean,
    
    // Accessibilità
    accessibile_disabili: boolean,
    
    // Ordinamento
    ordina_per: 'prezzo'|'rating'|'distanza'|'popolarita'|'novita',
    ordine: 'asc'|'desc',
    
    // Paginazione
    pagina: number,
    per_pagina: number             // Default 20, max 50
}

Filtri Esperienze

📋 Parametri Ricerca
// Filtri disponibili per ricerca esperienze
{
    // Location
    destinazione: string,
    lat: number,
    lng: number,
    raggio_km: number,
    
    // Data
    data: date,
    data_da: date,
    data_a: date,
    
    // Partecipanti
    persone: number,
    
    // Categoria
    categoria: string[],           // tour|degustazione|avventura|cultura|natura|...
    tipo: string[],                // esperienza|prodotto|servizio
    
    // Prezzo
    prezzo_min: number,
    prezzo_max: number,
    
    // Durata
    durata_min: number,            // minuti
    durata_max: number,
    
    // Caratteristiche
    difficolta: string[],          // facile|media|difficile
    lingua: string[],              // it|en|de|fr
    accessibile: boolean,
    bambini_ammessi: boolean,
    
    // Rating
    rating_min: number,
    
    // Ordinamento
    ordina_per: 'prezzo'|'rating'|'durata'|'popolarita',
    ordine: 'asc'|'desc'
}

API Endpoint

📋 REST API
// GET /wp-json/se/v1/search/strutture
// GET /wp-json/se/v1/search/esperienze
// GET /wp-json/se/v1/search/eventi

// Response structure
{
    "success": true,
    "data": {
        "results": [...],
        "total": 156,
        "pagina": 1,
        "per_pagina": 20,
        "pagine_totali": 8,
        "filtri_applicati": {...},
        "aggregazioni": {
            "tipologie": [
                {"valore": "hotel", "count": 45},
                {"valore": "bb", "count": 67}
            ],
            "prezzi": {
                "min": 35,
                "max": 450,
                "media": 120
            },
            "servizi": [...]
        }
    }
}

Geolocalizzazione

Ricerca per distanza: Usa formula Haversine per calcolo distanza in km.
Bounding box: Pre-filtro su lat/lng per performance prima del calcolo esatto.

Test Cases

🛒 MOD_11 - Carrello e Checkout

MOD_11_CARRELLO

Scopo

Carrello unificato per alloggi + esperienze + prodotti. Integrazione WooCommerce solo per checkout/pagamento.

Architettura Carrello

Approccio: Carrello custom (non WooCommerce cart) che converte in ordine WC solo al momento del pagamento.
Motivo: Flessibilità per gestire prenotazioni con deposito, split payment, soft-lock calendario.

Database: se_cart

📋 Schema SQL
CREATE TABLE se_cart (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    session_id VARCHAR(100) NOT NULL,            -- Per utenti non loggati
    user_id BIGINT UNSIGNED,                      -- Per utenti loggati
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    expires_at TIMESTAMP,                         -- Pulizia carrelli abbandonati
    
    UNIQUE KEY uk_session (session_id),
    KEY idx_user (user_id),
    KEY idx_expires (expires_at)
) ENGINE=InnoDB;

CREATE TABLE se_cart_items (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cart_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo item
    item_type ENUM('alloggio','esperienza','prodotto','evento') NOT NULL,
    item_id BIGINT UNSIGNED NOT NULL,             -- struttura_id, offerta_id, etc.
    
    -- Dettagli alloggio
    unita_id BIGINT UNSIGNED,                     -- Se prenotazione camera specifica
    check_in DATE,
    check_out DATE,
    ospiti_adulti TINYINT UNSIGNED,
    ospiti_bambini TINYINT UNSIGNED,
    
    -- Dettagli esperienza/prodotto
    data_esperienza DATE,
    ora_esperienza TIME,
    quantita INT UNSIGNED DEFAULT 1,
    variante_id BIGINT UNSIGNED,
    
    -- Pricing snapshot
    prezzo_unitario DECIMAL(10,2) NOT NULL,
    prezzo_totale DECIMAL(10,2) NOT NULL,
    extras_json JSON,                             -- Extra selezionati
    tassa_soggiorno DECIMAL(10,2) DEFAULT 0,
    
    -- Soft-lock
    soft_lock_id BIGINT UNSIGNED,
    soft_lock_expires_at TIMESTAMP,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (cart_id) REFERENCES se_cart(id) ON DELETE CASCADE,
    KEY idx_soft_lock (soft_lock_expires_at)
) ENGINE=InnoDB;

Flusso Checkout

1
Aggiungi al Carrello

Verifica disponibilità → Calcola prezzo → Soft-lock calendario (15 min)

2
Pagina Carrello

Mostra items → Timer soft-lock → Opzione rimuovi/modifica

3
Dati Ospiti

Raccolta dati per Payturist (documenti, date nascita)

4
Pagamento

Stripe checkout → Deposito o totale → Split automatico

5
Conferma

Crea prenotazione → Blocca calendario → Invia a Payturist → Email conferma

Test Cases

💳 MOD_12 - Pagamenti

MOD_12_PAGAMENTI

Scopo

Gestione pagamenti via Stripe: depositi, saldi, rimborsi. Integrazione con WooCommerce per checkout UI.

Modalità Pagamento Host

Pagamento Totale
100% al momento della prenotazione
Deposito + Saldo
20-50% subito, resto X giorni prima check-in

Database: se_payments

📋 Schema SQL
CREATE TABLE se_payments (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo pagamento
    tipo ENUM('deposito','saldo','totale','rimborso') NOT NULL,
    
    -- Importi
    importo_lordo DECIMAL(10,2) NOT NULL,         -- Pagato dal turista
    importo_commissione DECIMAL(10,2) NOT NULL,   -- Commissione piattaforma
    importo_netto DECIMAL(10,2) NOT NULL,         -- Per host/business
    importo_tassa_soggiorno DECIMAL(10,2) DEFAULT 0,
    
    -- Stripe
    stripe_payment_intent_id VARCHAR(100),
    stripe_charge_id VARCHAR(100),
    stripe_transfer_id VARCHAR(100),              -- Transfer to connected account
    
    -- Stato
    stato ENUM('pending','processing','completed','failed','refunded','partially_refunded') DEFAULT 'pending',
    
    -- Scadenza (per saldo)
    scadenza DATE,
    reminder_sent_at TIMESTAMP,
    
    -- Metadata
    metadata JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_stato (stato),
    KEY idx_scadenza (scadenza),
    KEY idx_stripe_pi (stripe_payment_intent_id)
) ENGINE=InnoDB;

Gestione Saldo in Sospeso

Reminder automatici:
• 7 giorni prima scadenza saldo
• 3 giorni prima
• Giorno scadenza
• Se non pagato entro 24h: cancellazione automatica, calendario liberato

Test Cases

✂️ MOD_13 - Split Payment

MOD_13_SPLIT

Scopo

Distribuzione automatica pagamenti tra piattaforma e host/business via Stripe Connect.

Flusso Split Payment

1
Pagamento Turista

€100 → Stripe Platform Account

2
Calcolo Split

Commissione 8% = €8 piattaforma | €92 host

3
Transfer Automatico

Stripe Transfer €92 → Host Connected Account

Stripe Connect Setup

📋 Configurazione
// Host onboarding
$account = \Stripe\Account::create([
    'type' => 'express',
    'country' => 'IT',
    'email' => $host_email,
    'capabilities' => [
        'card_payments' => ['requested' => true],
        'transfers' => ['requested' => true],
    ],
    'business_type' => 'individual', // or 'company'
    'metadata' => [
        'host_id' => $host_id,
        'platform' => 'sicily_experience'
    ]
]);

// Save stripe_account_id to host profile
update_user_meta($host_id, 'stripe_account_id', $account->id);

// Generate onboarding link
$account_link = \Stripe\AccountLink::create([
    'account' => $account->id,
    'refresh_url' => site_url('/partner/stripe-connect-refresh'),
    'return_url' => site_url('/partner/stripe-connect-complete'),
    'type' => 'account_onboarding',
]);

Calcolo Commissioni per Piano

Piano Canone Commissione Su €1000
Base €0/mese 12% €880 host | €120 platform
Standard €29/mese 5% €950 host | €50 platform
Premium €79/mese 0% €1000 host | €0 platform
VIP €0 lifetime 0% €1000 host | €0 platform

Test Cases

🏛️ MOD_14 - Integrazione Payturist

MOD_14_PAYTURIST

Scopo

Automazione completa adempimenti burocratici italiani: Alloggiati Web (Polizia), ISTAT, Tassa di Soggiorno.

Servizi Payturist

Alloggiati Web
Comunicazione schedine Polizia entro 24h dal check-in
ISTAT / OTS
Trasmissione dati Osservatorio Turistico Sicilia
Tassa Soggiorno
Calcolo e reportistica per comune
Check-in Online
Link per ospiti per inserire documenti

Database: se_payturist_submissions

📋 Schema SQL
CREATE TABLE se_payturist_submissions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo submission
    tipo ENUM('alloggiati','istat','tassa_soggiorno') NOT NULL,
    
    -- Dati inviati
    payload_json JSON NOT NULL,
    
    -- Risposta Payturist
    payturist_response_id VARCHAR(100),
    stato ENUM('pending','submitted','accepted','rejected','error') DEFAULT 'pending',
    error_message TEXT,
    
    -- Timing
    submitted_at TIMESTAMP,
    response_at TIMESTAMP,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_stato (stato),
    KEY idx_tipo (tipo)
) ENGINE=InnoDB;

API Integration

📋 Payturist API Calls
class PayturistService {
    private $base_url = 'https://api.payturist.com/v1';
    private $software_id;
    
    public function submitAlloggiati($booking, $guests) {
        $payload = [
            'structure_id' => $booking->struttura->payturist_id,
            'token' => $booking->struttura->payturist_token,
            'check_in' => $booking->check_in,
            'check_out' => $booking->check_out,
            'guests' => array_map(function($guest) {
                return [
                    'tipo' => $guest->is_primary ? 'ospite_singolo' : 'familiare',
                    'cognome' => $guest->cognome,
                    'nome' => $guest->nome,
                    'sesso' => $guest->sesso,
                    'data_nascita' => $guest->data_nascita,
                    'luogo_nascita' => $guest->luogo_nascita,
                    'provincia_nascita' => $guest->provincia_nascita,
                    'stato_nascita' => $guest->stato_nascita,
                    'cittadinanza' => $guest->cittadinanza,
                    'tipo_documento' => $guest->tipo_documento,
                    'numero_documento' => $guest->numero_documento,
                    'rilasciato_da' => $guest->rilasciato_da,
                ];
            }, $guests)
        ];
        
        return $this->post('/alloggiati/submit', $payload);
    }
    
    public function generateCheckInLink($booking) {
        // Returns URL for guest to fill document data
        return $this->post('/checkin/generate-link', [
            'structure_id' => $booking->struttura->payturist_id,
            'booking_ref' => $booking->codice,
            'guest_email' => $booking->email,
            'check_in' => $booking->check_in,
            'adults' => $booking->ospiti_adulti,
            'children' => $booking->ospiti_bambini,
        ]);
    }
}

Workflow Automatico

1
Prenotazione Confermata

Genera link check-in online → Invia a ospite via email

2
Ospite Compila Dati

Documenti, date nascita, cittadinanza tramite form Payturist

3
Check-in Effettuato

Host conferma arrivo → Trigger invio Alloggiati Web

4
Invio Automatico

Entro 24h: Alloggiati Web | Fine mese: ISTAT

Test Cases

📖 MOD_15 - Guide Personalizzate

MOD_15_GUIDE

Scopo

Host creano guide personalizzate per i propri ospiti con consigli, ristoranti, esperienze. Cross-referral tracking incluso.

Struttura Guide

📋 Custom Post Type: guida
// ACF Fields: Guida
[
    'host_id'           => 'user',              // Autore guida
    'struttura_id'      => 'relationship',      // Struttura associata
    'titolo'            => 'text',
    'descrizione'       => 'wysiwyg',
    
    // Sezioni
    'sezioni'           => 'repeater' => [
        'titolo_sezione' => 'text',
        'tipo'           => 'select',           // info|ristoranti|esperienze|luoghi|trasporti
        'contenuto'      => 'wysiwyg',
        'items'          => 'repeater' => [     // Per liste
            'nome'        => 'text',
            'descrizione' => 'textarea',
            'indirizzo'   => 'text',
            'maps_url'    => 'url',
            'offerta_id'  => 'relationship',    // Link a esperienza piattaforma (cross-referral)
        ]
    ],
    
    // Settings
    'richiede_registrazione' => 'true_false',   // Ospite deve registrarsi per vedere
    'invia_automaticamente'  => 'true_false',   // Invia con conferma prenotazione
    'lingue_disponibili'     => 'checkbox',     // it|en|de|fr|es
    
    // Tracking
    'token_accesso'          => 'text',         // Token unico per URL
    'visite'                 => 'number',
]

Cross-Referral nelle Guide

Meccanismo:
1. Host inserisce esperienza piattaforma nella guida
2. Ospite clicca e prenota esperienza
3. Host guadagna % cross-referral (3-5% in base al piano)
4. Tracking via cookie + session + booking reference

Test Cases

MOD_16 - Recensioni

MOD_16_RECENSIONI

Scopo

Sistema recensioni verificate per strutture ed esperienze. Solo chi ha prenotato può recensire. Moderazione automatica + manuale.

Regole Recensioni

Chi può recensire: Solo utenti con prenotazione completata (check-out effettuato)
Quando: Da 1 giorno a 30 giorni dopo check-out
Limite: Una recensione per prenotazione
Modifica: Possibile entro 7 giorni dalla pubblicazione

Criteri di Valutazione

Strutture
Pulizia, Posizione, Comfort, Rapporto qualità/prezzo, Staff, Servizi
Esperienze
Organizzazione, Guida, Valore, Corrispondenza descrizione

Database: se_reviews

📋 Schema SQL
CREATE TABLE se_reviews (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- Riferimenti
    entity_type ENUM('struttura','offerta','business') NOT NULL,
    entity_id BIGINT UNSIGNED NOT NULL,
    booking_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Valutazioni (1-5)
    rating_generale TINYINT UNSIGNED NOT NULL,
    rating_pulizia TINYINT UNSIGNED,
    rating_posizione TINYINT UNSIGNED,
    rating_comfort TINYINT UNSIGNED,
    rating_rapporto_qualita TINYINT UNSIGNED,
    rating_staff TINYINT UNSIGNED,
    rating_servizi TINYINT UNSIGNED,
    
    -- Contenuto
    titolo VARCHAR(200),
    testo TEXT,
    lingua CHAR(2) DEFAULT 'it',
    
    -- Risposta host/business
    risposta TEXT,
    risposta_data TIMESTAMP,
    
    -- Media
    foto JSON,                                    -- Array di URL foto
    
    -- Moderazione
    stato ENUM('pending','approved','rejected','flagged') DEFAULT 'pending',
    motivo_rifiuto VARCHAR(255),
    moderato_da BIGINT UNSIGNED,
    moderato_at TIMESTAMP,
    
    -- Flag utenti
    segnalazioni INT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_booking (booking_id),           -- Una recensione per prenotazione
    FOREIGN KEY (user_id) REFERENCES wp_users(ID),
    KEY idx_entity (entity_type, entity_id),
    KEY idx_stato (stato),
    KEY idx_rating (rating_generale)
) ENGINE=InnoDB;

Moderazione Automatica

📋 Regole Auto-Flag
// Recensione viene flaggata automaticamente se:
const autoFlagRules = {
    // Contenuto sospetto
    parole_offensive: true,           // Lista blacklist
    link_esterni: true,               // URL nel testo
    email_telefono: true,             // Dati di contatto
    
    // Pattern sospetti
    troppo_corta: 10,                 // Meno di 10 caratteri
    troppo_lunga: 5000,               // Più di 5000 caratteri
    tutto_maiuscolo: 0.8,             // >80% maiuscolo
    
    // Anomalie
    rating_estremo_senza_testo: true, // 1 o 5 stelle senza commento
    primo_review_host: true,          // Prima recensione per host nuovo
    
    // Tempistiche
    troppo_veloce: 60,                // Meno di 60 secondi dopo check-out
};

Test Cases

💬 MOD_17 - Messaggistica

MOD_17_MESSAGGI

Scopo

Sistema messaggi interno tra turisti e host/business. Thread per prenotazione, notifiche real-time, moderazione.

Regole Messaggistica

Pre-prenotazione: Turista può contattare host per info (max 5 messaggi/giorno senza prenotazione)
Post-prenotazione: Chat illimitata legata alla prenotazione
Dati sensibili: Mascheramento automatico email/telefono prima della conferma

Database: se_messages

📋 Schema SQL
-- Thread di conversazione
CREATE TABLE se_message_threads (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- Partecipanti
    user_1_id BIGINT UNSIGNED NOT NULL,           -- Turista
    user_2_id BIGINT UNSIGNED NOT NULL,           -- Host/Business
    
    -- Riferimento (opzionale)
    entity_type ENUM('struttura','offerta','booking') NULL,
    entity_id BIGINT UNSIGNED,
    booking_id BIGINT UNSIGNED,
    
    -- Stato
    stato ENUM('attivo','archiviato','bloccato') DEFAULT 'attivo',
    
    -- Cache ultimo messaggio
    ultimo_messaggio_at TIMESTAMP,
    ultimo_messaggio_preview VARCHAR(100),
    
    -- Contatori non letti
    user_1_unread INT UNSIGNED DEFAULT 0,
    user_2_unread INT UNSIGNED DEFAULT 0,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_users_entity (user_1_id, user_2_id, entity_type, entity_id),
    KEY idx_user1 (user_1_id),
    KEY idx_user2 (user_2_id),
    KEY idx_booking (booking_id),
    KEY idx_ultimo (ultimo_messaggio_at)
) ENGINE=InnoDB;

-- Singoli messaggi
CREATE TABLE se_messages (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    thread_id BIGINT UNSIGNED NOT NULL,
    sender_id BIGINT UNSIGNED NOT NULL,
    
    -- Contenuto
    messaggio TEXT NOT NULL,
    messaggio_originale TEXT,                     -- Prima del mascheramento
    
    -- Allegati
    allegati JSON,                                -- Array di {tipo, url, nome}
    
    -- Stato
    letto_at TIMESTAMP,
    
    -- Moderazione
    mascherato BOOLEAN DEFAULT FALSE,             -- Conteneva dati sensibili
    flagged BOOLEAN DEFAULT FALSE,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (thread_id) REFERENCES se_message_threads(id) ON DELETE CASCADE,
    KEY idx_thread (thread_id),
    KEY idx_sender (sender_id),
    KEY idx_created (created_at)
) ENGINE=InnoDB;

Mascheramento Dati Sensibili

📋 Regex Mascheramento
function mascheraDatiSensibili($testo, $prenotazioneConfermata = false) {
    if ($prenotazioneConfermata) {
        return $testo; // Nessun mascheramento dopo conferma
    }
    
    // Email
    $testo = preg_replace(
        '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/',
        '[email nascosta]',
        $testo
    );
    
    // Telefono italiano
    $testo = preg_replace(
        '/(\+39)?[\s.-]?\d{2,4}[\s.-]?\d{5,8}/',
        '[telefono nascosto]',
        $testo
    );
    
    // URL
    $testo = preg_replace(
        '/(https?:\/\/[^\s]+)/',
        '[link rimosso]',
        $testo
    );
    
    return $testo;
}

Test Cases

🔔 MOD_20 - Notifiche

MOD_20_NOTIFICHE

Scopo

Sistema notifiche multi-canale: in-app, email, SMS, push. Preferenze utente, template multilingua, scheduling.

Canali Notifica

In-App
Notifiche nel pannello utente, sempre attive
Email
Via Brevo/SendGrid, disattivabili per tipo
SMS
Via Twilio, solo critiche (conferme, pagamenti)
Push
PWA push notifications, opt-in

Tipi di Notifica

📋 Catalogo Notifiche
const NOTIFICATION_TYPES = [
    // Prenotazioni - Turista
    'booking_confirmed'      => ['email', 'sms', 'push'],
    'booking_cancelled'      => ['email', 'sms'],
    'booking_reminder_7d'    => ['email'],
    'booking_reminder_1d'    => ['email', 'push'],
    'checkin_instructions'   => ['email'],
    'checkout_reminder'      => ['email', 'push'],
    'review_invitation'      => ['email'],
    'balance_due_reminder'   => ['email', 'sms'],
    
    // Prenotazioni - Host
    'new_booking_request'    => ['email', 'sms', 'push'],
    'booking_paid'           => ['email', 'push'],
    'guest_cancelled'        => ['email', 'sms'],
    'guest_checkin'          => ['push'],
    'new_review'             => ['email', 'push'],
    
    // Messaggi
    'new_message'            => ['email', 'push'],
    
    // Pagamenti
    'payment_received'       => ['email'],
    'payout_sent'            => ['email'],
    'payment_failed'         => ['email', 'sms'],
    
    // Account
    'trial_expiring_7d'      => ['email'],
    'trial_expiring_3d'      => ['email'],
    'trial_expiring_1d'      => ['email', 'sms'],
    'trial_expired'          => ['email', 'sms'],
    'subscription_renewed'   => ['email'],
    'subscription_failed'    => ['email', 'sms'],
    
    // Wallet
    'referral_earned'        => ['email', 'push'],
    'withdrawal_completed'   => ['email'],
    
    // Sistema
    'account_suspended'      => ['email', 'sms'],
    'document_expiring'      => ['email'],
];

Database: se_notifications

📋 Schema SQL
CREATE TABLE se_notifications (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo
    tipo VARCHAR(50) NOT NULL,
    
    -- Contenuto
    titolo VARCHAR(200) NOT NULL,
    messaggio TEXT NOT NULL,
    link VARCHAR(500),
    icona VARCHAR(50),
    
    -- Riferimento
    entity_type VARCHAR(50),
    entity_id BIGINT UNSIGNED,
    
    -- Stato
    letta BOOLEAN DEFAULT FALSE,
    letta_at TIMESTAMP,
    
    -- Canali inviati
    sent_email BOOLEAN DEFAULT FALSE,
    sent_email_at TIMESTAMP,
    sent_sms BOOLEAN DEFAULT FALSE,
    sent_sms_at TIMESTAMP,
    sent_push BOOLEAN DEFAULT FALSE,
    sent_push_at TIMESTAMP,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE,
    KEY idx_user_letta (user_id, letta),
    KEY idx_tipo (tipo),
    KEY idx_created (created_at)
) ENGINE=InnoDB;

-- Preferenze notifiche utente
CREATE TABLE se_notification_preferences (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Canali globali
    email_enabled BOOLEAN DEFAULT TRUE,
    sms_enabled BOOLEAN DEFAULT TRUE,
    push_enabled BOOLEAN DEFAULT FALSE,
    
    -- Override per tipo (JSON: {tipo: {email: bool, sms: bool, push: bool}})
    preferences_override JSON,
    
    -- Orari quiete
    quiet_hours_enabled BOOLEAN DEFAULT FALSE,
    quiet_hours_start TIME DEFAULT '22:00:00',
    quiet_hours_end TIME DEFAULT '08:00:00',
    
    -- Lingua preferita
    lingua CHAR(2) DEFAULT 'it',
    
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_user (user_id),
    FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE
) ENGINE=InnoDB;

Lista Completa Template Email

📋 Catalogo Email Transazionali
Codice Trigger Destinatario Oggetto
🔐 AUTENTICAZIONE
AUTH_WELCOMERegistrazioneUtenteBenvenuto su Sicily Experience!
AUTH_VERIFYVerifica emailUtenteVerifica il tuo indirizzo email
AUTH_RESETReset passwordUtenteReimposta la tua password
AUTH_CHANGEDPassword cambiataUtentePassword modificata
📅 PRENOTAZIONI
BOOK_REQUESTNuova richiestaHostNuova richiesta prenotazione
BOOK_CONFIRM_GUESTConfermataOspitePrenotazione confermata #{{code}}
BOOK_CONFIRM_HOSTConfermataHostNuova prenotazione confermata
BOOK_REMINDER-3gg check-inOspiteIl tuo viaggio si avvicina!
BOOK_CHECKINGiorno check-inOspiteIstruzioni check-in
BOOK_CHECKOUTGiorno check-outOspiteGrazie per il soggiorno!
BOOK_MODIFIEDModificataEntrambiPrenotazione modificata
BOOK_CANCEL_GUESTCancellata ospiteEntrambiPrenotazione cancellata
BOOK_CANCEL_HOSTCancellata hostOspiteCancellata dall'host
💳 PAGAMENTI
PAY_RECEIPTPagamento ricevutoOspiteRicevuta #{{code}}
PAY_BALANCE_DUE-7gg saldoOspitePromemoria saldo
PAY_REFUNDRimborsoOspiteRimborso emesso
PAY_PAYOUTPayout hostHostAccredito effettuato
PAY_FAILEDFallitoOspiteProblema pagamento
⭐ RECENSIONI
REV_REQUEST+3gg check-outOspiteCom'è stato il soggiorno?
REV_RECEIVEDNuova recensioneHostNuova recensione ricevuta
REV_RESPONSERisposta hostOspiteL'host ha risposto
💬 MESSAGGI
MSG_NEWNuovo messaggioDestinatarioMessaggio da {{sender}}
🏠 HOST/PARTNER
HOST_APPROVEDApprovatoHostStruttura online!
HOST_REJECTEDRifiutatoHostRevisione richiesta
HOST_SUB_EXPIRE-30/-7/-1ggHostAbbonamento in scadenza
HOST_SUB_RENEWEDRinnovatoHostAbbonamento rinnovato
🤝 AMBASSADOR
AMB_TRIALTrial attivatoAmbassadorTrial per {{client}}
AMB_COMMISSIONCommissioneAmbassadorCommissione maturata
AMB_PAYOUTPayoutAmbassadorPagamento effettuato
⚠️ ALERT ADMIN
ALERT_OVERBOOKINGOverbookingAdmin+Host⚠️ Overbooking rilevato
ALERT_NOSHOWNo-showAdminSegnalazione no-show
ALERT_DISPUTEDisputaAdminNuova disputa

Test Cases

🤝 MOD_18 - Sistema Ambassador

MOD_18_AMBASSADOR

Scopo

Agenti territoriali che acquisiscono clienti (host/business), attivano trial, guadagnano commissioni su abbonamenti e transazioni.

Commissioni Ambassador

Tipo Commissione Durata
Prima sottoscrizione 20% Una tantum
Rinnovi 10% 24 mesi
Transazioni cliente 2% 24 mesi

Database: se_ambassador_commissions

📋 Schema SQL
CREATE TABLE se_ambassador_commissions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ambassador_id BIGINT UNSIGNED NOT NULL,
    client_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo commissione
    tipo ENUM('first_subscription','renewal','transaction') NOT NULL,
    
    -- Importi
    importo_base DECIMAL(10,2) NOT NULL,          -- Valore transazione/abbonamento
    percentuale DECIMAL(5,2) NOT NULL,            -- % applicata
    importo_commissione DECIMAL(10,2) NOT NULL,   -- Commissione guadagnata
    
    -- Riferimento
    reference_type ENUM('subscription','booking','order') NOT NULL,
    reference_id BIGINT UNSIGNED NOT NULL,
    
    -- Stato
    stato ENUM('pending','approved','paid','cancelled') DEFAULT 'pending',
    
    -- Scadenza relazione
    client_expires_at DATE,                       -- Quando scade la relazione 24 mesi
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    paid_at TIMESTAMP,
    
    FOREIGN KEY (ambassador_id) REFERENCES wp_users(ID),
    FOREIGN KEY (client_id) REFERENCES wp_users(ID),
    KEY idx_ambassador (ambassador_id),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Funzionalità Ambassador

Attivazione Trial
Max 30 giorni, max 10 trial attivi contemporanei
Impersonation
Accesso account cliente (con permesso) per setup
Dashboard
Trial attivi, conversioni, commissioni, payout
Payout
Minimo €50, mensile o su richiesta

Test Cases

💎 MOD_19 - Referral e Wallet

MOD_19_WALLET

Scopo

Sistema di peer referral tra host/business + wallet per accumulo crediti utilizzabili per rinnovi o prelievo.

Peer Referral

Meccanismo:
Host A invita Host B → Host B si abbona →
Host A guadagna 10% prima sottoscrizione + 5% rinnovi (12 mesi)

Cross-Referral

Piano Ospite prenota esperienza Cliente prenota alloggio
Base 0% 0%
Standard 3% 3%
Premium 5% 5%

Database: se_wallet

📋 Schema SQL
CREATE TABLE se_wallet (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Saldi
    saldo_disponibile DECIMAL(10,2) DEFAULT 0,    -- Prelevabile
    saldo_pendente DECIMAL(10,2) DEFAULT 0,       -- In attesa conferma
    
    -- Totali storici
    totale_guadagnato DECIMAL(10,2) DEFAULT 0,
    totale_prelevato DECIMAL(10,2) DEFAULT 0,
    totale_usato_rinnovi DECIMAL(10,2) DEFAULT 0,
    
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_user (user_id),
    FOREIGN KEY (user_id) REFERENCES wp_users(ID)
) ENGINE=InnoDB;

CREATE TABLE se_wallet_transactions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    wallet_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo transazione
    tipo ENUM('peer_referral','cross_referral','ambassador','bonus','withdrawal','renewal_payment') NOT NULL,
    
    -- Importi
    importo DECIMAL(10,2) NOT NULL,               -- + credito, - debito
    saldo_dopo DECIMAL(10,2) NOT NULL,
    
    -- Riferimento
    reference_type VARCHAR(50),
    reference_id BIGINT UNSIGNED,
    descrizione VARCHAR(255),
    
    -- Stato
    stato ENUM('pending','completed','cancelled') DEFAULT 'completed',
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (wallet_id) REFERENCES se_wallet(id),
    KEY idx_tipo (tipo),
    KEY idx_created (created_at)
) ENGINE=InnoDB;

Utilizzo Wallet

Prelievo
PayPal/IBAN/Stripe, minimo €25
Rinnovo Abbonamento
Usa crediti + 5% sconto extra

Test Cases

✏️ MOD_21 - Modifica Prenotazione

MOD_21_MODIFICA_PRENOTAZIONE

Scopo

Gestione delle modifiche a prenotazioni esistenti: cambio date, numero ospiti, unità/camera, e relative implicazioni su prezzo, disponibilità e pagamenti.

Tipi di Modifica Consentiti

Tipo Modifica Quando Permesso Penale Approvazione Host
Cambio date (stessa durata) Fino a 48h prima check-in Nessuna se disponibile Automatica se disponibile
Estensione soggiorno Sempre (se disponibile) Nessuna Automatica
Riduzione soggiorno Secondo policy cancellazione Come da policy Automatica
Cambio numero ospiti Entro capacità unità Ricalcolo prezzo Automatica
Upgrade camera/unità Se disponibile Differenza prezzo Automatica
Downgrade camera/unità Fino a 7gg prima Rimborso parziale (80%) Richiesta host

Flusso Modifica Date

1
Richiesta

Ospite seleziona nuove date

2
Verifica

Sistema controlla disponibilità

3
Calcolo

Ricalcolo prezzo e differenza

4
Pagamento

Addebito/rimborso differenza

5
Conferma

Aggiornamento e notifiche

Schema Database

📋 Tabella se_booking_modifications
CREATE TABLE se_booking_modifications (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo modifica
    tipo ENUM('date_change', 'extend', 'reduce', 'guests', 'upgrade', 'downgrade') NOT NULL,
    
    -- Valori originali (JSON)
    original_values JSON NOT NULL,
    -- Nuovi valori (JSON)
    new_values JSON NOT NULL,
    
    -- Differenza economica
    price_difference DECIMAL(10,2) DEFAULT 0,
    
    -- Stato
    stato ENUM('pending', 'approved', 'rejected', 'completed', 'cancelled') DEFAULT 'pending',
    
    -- Pagamento associato (se differenza > 0)
    payment_id BIGINT UNSIGNED,
    refund_id BIGINT UNSIGNED,
    
    -- Approvazione
    requires_host_approval BOOLEAN DEFAULT FALSE,
    approved_by BIGINT UNSIGNED,
    approved_at TIMESTAMP,
    rejection_reason TEXT,
    
    -- Metadata
    requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    completed_at TIMESTAMP,
    notes TEXT,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    FOREIGN KEY (user_id) REFERENCES wp_users(ID),
    KEY idx_booking (booking_id),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Calcolo Differenza Prezzo

📋 Logica PHP
class SE_Booking_Modification {
    
    public function calculate_price_difference($booking_id, $new_params) {
        $booking = $this->get_booking($booking_id);
        $original_total = $booking->total_paid;
        
        // Calcola nuovo prezzo con stesse regole booking originale
        $new_price = SE_Pricing::calculate([
            'unit_id' => $new_params['unit_id'] ?? $booking->unit_id,
            'check_in' => $new_params['check_in'] ?? $booking->check_in,
            'check_out' => $new_params['check_out'] ?? $booking->check_out,
            'guests' => $new_params['guests'] ?? $booking->guests,
        ]);
        
        // Applica eventuali penali per riduzione
        if ($this->is_reduction($booking, $new_params)) {
            $penalty = $this->calculate_penalty($booking, $new_params);
            $new_price['penalty'] = $penalty;
        }
        
        return [
            'original_total' => $original_total,
            'new_total' => $new_price['total'],
            'difference' => $new_price['total'] - $original_total,
            'penalty' => $new_price['penalty'] ?? 0,
            'to_pay' => max(0, $new_price['total'] - $original_total),
            'to_refund' => max(0, $original_total - $new_price['total'] - ($new_price['penalty'] ?? 0)),
        ];
    }
    
    public function can_modify($booking_id, $modification_type) {
        $booking = $this->get_booking($booking_id);
        $hours_until_checkin = $this->hours_until($booking->check_in);
        
        $rules = [
            'date_change' => $hours_until_checkin >= 48,
            'extend' => true, // Sempre se disponibile
            'reduce' => $hours_until_checkin >= 24,
            'guests' => $hours_until_checkin >= 24,
            'upgrade' => $hours_until_checkin >= 24,
            'downgrade' => $hours_until_checkin >= 168, // 7 giorni
        ];
        
        return $rules[$modification_type] ?? false;
    }
}

Wireframe UI Modifica

📋 Schermata Modifica Prenotazione
┌─────────────────────────────────────────────────────────────┐
│  ← Torna alla prenotazione                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  MODIFICA PRENOTAZIONE #SE-2024-1234                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ PRENOTAZIONE ATTUALE                                │   │
│  │ 📅 15-20 Luglio 2024 (5 notti)                     │   │
│  │ 👥 2 adulti                                         │   │
│  │ 🏠 Camera Deluxe Vista Mare                        │   │
│  │ 💰 €750,00 (pagato)                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ COSA VUOI MODIFICARE?                               │   │
│  │                                                      │   │
│  │ ○ Cambiare le date                                  │   │
│  │ ○ Aggiungere notti                                  │   │
│  │ ○ Ridurre il soggiorno                              │   │
│  │ ○ Modificare numero ospiti                          │   │
│  │ ○ Cambiare camera/appartamento                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  [Se selezionato "Cambiare le date":]                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ NUOVE DATE                                          │   │
│  │ Check-in:  [18 Luglio 2024  📅]                    │   │
│  │ Check-out: [23 Luglio 2024  📅]                    │   │
│  │                                                      │   │
│  │ ✅ Disponibile per le date selezionate             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ RIEPILOGO MODIFICA                                  │   │
│  │                                                      │   │
│  │ Prezzo originale:          €750,00                  │   │
│  │ Nuovo prezzo:              €780,00                  │   │
│  │ ─────────────────────────────────                   │   │
│  │ Differenza da pagare:      €30,00                   │   │
│  │                                                      │   │
│  │ [💳 Paga e Conferma Modifica]                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Notifiche

Richiesta modifica
Email a host (se richiede approvazione)
Modifica approvata
Email a ospite con nuovo riepilogo
Modifica rifiutata
Email a ospite con motivazione
Pagamento differenza
Ricevuta a ospite, notifica a host

Test Cases

🎟️ MOD_22 - Coupon & Promozioni

MOD_22_COUPON_PROMO

Scopo

Sistema completo per la gestione di codici sconto, promozioni automatiche, e offerte speciali applicabili a prenotazioni di strutture ed esperienze.

Tipi di Sconto

Tipo Descrizione Esempio
Percentuale Sconto % sul totale ESTATE20 → -20%
Fisso Importo fisso di sconto WELCOME50 → -€50
Prima notte gratis Sconto pari a 1 notte FIRSTFREE → -1 notte
Notte extra gratis Prenota X, paga X-1 7X6 → 7 notti, paghi 6
Upgrade gratuito Camera superiore stesso prezzo UPGRADE → camera +1 livello
Esperienza inclusa Esperienza gratuita con prenotazione WINETOUR → tour vino gratis

Schema Database

📋 Tabelle Coupon
CREATE TABLE se_coupons (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- Identificazione
    code VARCHAR(50) NOT NULL UNIQUE,
    nome VARCHAR(100) NOT NULL,
    descrizione TEXT,
    
    -- Tipo sconto
    tipo ENUM('percentage', 'fixed', 'first_night', 'extra_night', 'upgrade', 'free_experience') NOT NULL,
    valore DECIMAL(10,2) NOT NULL, -- % o importo fisso
    
    -- Condizioni di applicabilità
    min_nights INT DEFAULT 1,
    min_amount DECIMAL(10,2) DEFAULT 0,
    max_discount DECIMAL(10,2), -- Tetto massimo sconto
    
    -- Validità temporale
    valid_from DATE,
    valid_to DATE,
    
    -- Date prenotazione (blackout)
    booking_date_from DATE, -- Valido per check-in da
    booking_date_to DATE,   -- Valido per check-in fino a
    excluded_dates JSON,    -- Date escluse ["2024-08-15", "2024-12-31"]
    
    -- Limiti utilizzo
    max_uses INT, -- Totale utilizzi consentiti
    max_uses_per_user INT DEFAULT 1,
    current_uses INT DEFAULT 0,
    
    -- Restrizioni
    applicable_to ENUM('all', 'structures', 'experiences', 'specific') DEFAULT 'all',
    structure_ids JSON, -- [1, 5, 10] se applicable_to = 'specific'
    experience_ids JSON,
    user_ids JSON, -- Coupon riservati a utenti specifici
    
    -- Chi l'ha creato
    created_by BIGINT UNSIGNED,
    created_by_type ENUM('admin', 'host', 'business') DEFAULT 'admin',
    
    -- Stato
    attivo BOOLEAN DEFAULT TRUE,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    KEY idx_code (code),
    KEY idx_valid (valid_from, valid_to, attivo),
    KEY idx_created_by (created_by, created_by_type)
) ENGINE=InnoDB;

CREATE TABLE se_coupon_usage (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    coupon_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    booking_id BIGINT UNSIGNED NOT NULL,
    
    discount_applied DECIMAL(10,2) NOT NULL,
    
    used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (coupon_id) REFERENCES se_coupons(id),
    FOREIGN KEY (user_id) REFERENCES wp_users(ID),
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_coupon_user (coupon_id, user_id)
) ENGINE=InnoDB;

Logica Validazione Coupon

📋 Classe SE_Coupon
class SE_Coupon {
    
    public function validate($code, $booking_params, $user_id) {
        $coupon = $this->get_by_code($code);
        
        if (!$coupon) {
            return ['valid' => false, 'error' => 'Codice non valido'];
        }
        
        // Verifica attivo
        if (!$coupon->attivo) {
            return ['valid' => false, 'error' => 'Codice non più attivo'];
        }
        
        // Verifica date validità coupon
        $today = date('Y-m-d');
        if ($coupon->valid_from && $today < $coupon->valid_from) {
            return ['valid' => false, 'error' => 'Codice non ancora valido'];
        }
        if ($coupon->valid_to && $today > $coupon->valid_to) {
            return ['valid' => false, 'error' => 'Codice scaduto'];
        }
        
        // Verifica date prenotazione
        $check_in = $booking_params['check_in'];
        if ($coupon->booking_date_from && $check_in < $coupon->booking_date_from) {
            return ['valid' => false, 'error' => 'Non valido per queste date'];
        }
        if ($coupon->booking_date_to && $check_in > $coupon->booking_date_to) {
            return ['valid' => false, 'error' => 'Non valido per queste date'];
        }
        
        // Verifica date escluse (blackout)
        $excluded = json_decode($coupon->excluded_dates, true) ?? [];
        if (in_array($check_in, $excluded)) {
            return ['valid' => false, 'error' => 'Non valido per la data selezionata'];
        }
        
        // Verifica minimo notti
        $nights = $this->calculate_nights($booking_params);
        if ($nights < $coupon->min_nights) {
            return ['valid' => false, 'error' => "Minimo {$coupon->min_nights} notti"];
        }
        
        // Verifica minimo importo
        if ($booking_params['subtotal'] < $coupon->min_amount) {
            return ['valid' => false, 'error' => "Minimo €{$coupon->min_amount}"];
        }
        
        // Verifica utilizzi totali
        if ($coupon->max_uses && $coupon->current_uses >= $coupon->max_uses) {
            return ['valid' => false, 'error' => 'Codice esaurito'];
        }
        
        // Verifica utilizzi per utente
        $user_uses = $this->count_user_uses($coupon->id, $user_id);
        if ($user_uses >= $coupon->max_uses_per_user) {
            return ['valid' => false, 'error' => 'Hai già utilizzato questo codice'];
        }
        
        // Verifica applicabilità (struttura/esperienza)
        if (!$this->is_applicable($coupon, $booking_params)) {
            return ['valid' => false, 'error' => 'Non applicabile a questa prenotazione'];
        }
        
        // Calcola sconto
        $discount = $this->calculate_discount($coupon, $booking_params);
        
        return [
            'valid' => true,
            'coupon' => $coupon,
            'discount' => $discount,
            'message' => "Sconto di €{$discount} applicato!"
        ];
    }
    
    private function calculate_discount($coupon, $booking_params) {
        $subtotal = $booking_params['subtotal'];
        
        switch ($coupon->tipo) {
            case 'percentage':
                $discount = $subtotal * ($coupon->valore / 100);
                break;
            case 'fixed':
                $discount = $coupon->valore;
                break;
            case 'first_night':
                $discount = $booking_params['price_per_night'];
                break;
            case 'extra_night':
                $nights = $this->calculate_nights($booking_params);
                $discount = $subtotal / $nights; // Una notte gratis
                break;
            default:
                $discount = 0;
        }
        
        // Applica tetto massimo
        if ($coupon->max_discount && $discount > $coupon->max_discount) {
            $discount = $coupon->max_discount;
        }
        
        // Non può superare il subtotale
        return min($discount, $subtotal);
    }
}

Promozioni Automatiche

Promozioni applicate automaticamente senza codice:

Early Booking: -10% se prenoti 60+ giorni prima
Last Minute: -15% se check-in entro 3 giorni
Long Stay: -5% per 7+ notti, -10% per 14+ notti, -15% per 30+ notti
Repeat Guest: -5% se già prenotato stessa struttura
Compleanno: -10% se check-in include data compleanno utente

Dashboard Admin Coupon

📋 Wireframe Gestione Coupon
┌─────────────────────────────────────────────────────────────┐
│  GESTIONE COUPON                          [+ Nuovo Coupon]  │
├─────────────────────────────────────────────────────────────┤
│  Filtri: [Tutti ▼] [Attivi ▼] [Scaduti ▼]    🔍 Cerca...   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ ESTATE20           -20%        Strutture            │   │
│  │ Valido: 1 Giu - 31 Ago 2024   Utilizzi: 45/100     │   │
│  │ ● Attivo                       [Modifica] [Disattiva]│   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ WELCOME50          -€50        Tutti                │   │
│  │ Valido: sempre                Utilizzi: 12/∞       │   │
│  │ ● Attivo                       [Modifica] [Disattiva]│   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ FIRSTFREE          1° notte    Strutture specifiche │   │
│  │ Valido: fino 31 Dic 2024      Utilizzi: 8/20       │   │
│  │ ○ Scaduto                      [Modifica] [Riattiva] │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Pagina 1 di 3   [←] [1] [2] [3] [→]                       │
└─────────────────────────────────────────────────────────────┘

Coupon Host

Gli host possono creare coupon per le proprie strutture:
- Solo tipo: percentage, fixed (max 30%)
- Solo per le proprie strutture
- Max 5 coupon attivi contemporaneamente
- Devono essere approvati da admin (opzionale)
- Lo sconto viene detratto dalla quota host, non dalla commissione piattaforma

Test Cases

MOD_25 - Cancellazioni

MOD_25_CANCELLAZIONI

Scopo

Gestione completa ciclo cancellazione: calcolo rimborso, processamento Stripe, rilascio calendario, notifiche, statistiche.

Policy Predefinite

Policy Giorni prima Rimborso
Non Rimborsabile Qualsiasi 0%
Standard >5 giorni 100%
2-5 giorni 50%
<2 giorni 0%
Flessibile >14 giorni 100%
7-14 giorni 70%
2-7 giorni 50%
<2 giorni 0%

Database: se_cancellations

📋 Schema SQL
CREATE TABLE se_cancellations (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    
    -- Chi cancella
    richiesto_da ENUM('turista','host','admin','sistema') NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Motivo
    motivo_categoria ENUM('cambio_piani','emergenza','insoddisfazione','overbooking','forza_maggiore','altro') NOT NULL,
    motivo_dettaglio TEXT,
    
    -- Policy applicata (snapshot al momento della prenotazione)
    policy_nome VARCHAR(50) NOT NULL,
    policy_snapshot JSON NOT NULL,
    giorni_anticipo INT NOT NULL,
    
    -- Calcoli
    importo_pagato DECIMAL(10,2) NOT NULL,
    percentuale_rimborso DECIMAL(5,2) NOT NULL,
    importo_rimborso DECIMAL(10,2) NOT NULL,
    importo_trattenuto DECIMAL(10,2) NOT NULL,
    
    -- Commissioni
    commissione_su_trattenuto DECIMAL(10,2) DEFAULT 0,
    importo_host DECIMAL(10,2) NOT NULL,          -- Quanto va all'host
    
    -- Stripe
    stripe_refund_id VARCHAR(100),
    stripe_refund_status ENUM('pending','succeeded','failed') DEFAULT 'pending',
    
    -- Stato
    stato ENUM('richiesta','approvata','rifiutata','processata','completata') DEFAULT 'richiesta',
    
    -- Timestamps
    richiesto_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    approvato_at TIMESTAMP,
    processato_at TIMESTAMP,
    completato_at TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_booking (booking_id),
    KEY idx_stato (stato),
    KEY idx_richiesto (richiesto_at)
) ENGINE=InnoDB;

Flusso Cancellazione

1
Richiesta

Utente richiede cancellazione → Calcolo rimborso in base a policy snapshot

2
Conferma

Mostra importo rimborso → Utente conferma

3
Rimborso Stripe

Refund automatico su carta originale

4
Aggiornamenti

Rilascio calendario + Notifiche + Aggiornamento statistiche

Test Cases

⚖️ MOD_26 - Dispute e Overbooking

MOD_26_DISPUTE

Scopo

Gestione situazioni problematiche: overbooking, no-show, forza maggiore, reclami. Workflow risoluzione con penalità.

Gestione Overbooking

Definizione: Host non può ospitare turista con prenotazione confermata
Azione piattaforma: Trova alloggio alternativo entro 4 ore
Compensazione turista: 15% del valore prenotazione (min €25, max €200)

Penalità Overbooking Host

Occorrenza Penalità
1° overbooking Warning + differenza costo ricollocazione
2° overbooking Sospensione 7 giorni + €50 + differenza
3° overbooking Sospensione 30 giorni + €150 + downgrade piano
4° overbooking Ban permanente

Database: se_overbooking_incidents

📋 Schema SQL
CREATE TABLE se_overbooking_incidents (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    host_id BIGINT UNSIGNED NOT NULL,
    
    -- Segnalazione
    segnalato_da ENUM('host','turista','sistema') NOT NULL,
    motivo TEXT,
    
    -- Ricollocazione
    ricollocazione_tentata BOOLEAN DEFAULT FALSE,
    ricollocazione_riuscita BOOLEAN,
    struttura_alternativa_id BIGINT UNSIGNED,
    differenza_costo DECIMAL(10,2),
    
    -- Compensazioni
    bonus_turista DECIMAL(10,2),                  -- 15% min €25 max €200
    costo_totale_host DECIMAL(10,2),              -- Differenza + bonus + penalità
    
    -- Penalità host
    penalita_applicata DECIMAL(10,2),
    sospensione_giorni INT UNSIGNED,
    occorrenza_numero TINYINT UNSIGNED NOT NULL,  -- 1°, 2°, 3°, etc.
    
    -- Stato
    stato ENUM('aperto','in_gestione','risolto','escalation') DEFAULT 'aperto',
    risolto_da BIGINT UNSIGNED,
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    risolto_at TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_host (host_id),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Gestione No-Show

Segnalazione: Host segnala mancato arrivo
Verifica: Turista ha 24h per rispondere
Se confermato: 100% addebitato (come cancellazione last-minute)
Recidiva: 1°=warning, 2°=badge visibile, 3°=prepagamento 100%, 5°=ban

Database: se_noshow_reports

📋 Schema SQL
CREATE TABLE se_noshow_reports (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    turista_id BIGINT UNSIGNED NOT NULL,
    host_id BIGINT UNSIGNED NOT NULL,
    
    -- Segnalazione
    segnalato_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    note_host TEXT,
    
    -- Risposta turista
    risposta_turista TEXT,
    risposta_at TIMESTAMP,
    scadenza_risposta TIMESTAMP,                  -- +24h da segnalazione
    
    -- Esito
    confermato BOOLEAN,
    confermato_da ENUM('turista','timeout','admin'),
    
    -- Penalità
    importo_addebitato DECIMAL(10,2),
    occorrenza_numero TINYINT UNSIGNED NOT NULL,
    penalita_applicata VARCHAR(50),               -- warning|badge|prepagamento|ban
    
    -- Stato
    stato ENUM('segnalato','in_attesa_risposta','confermato','contestato','risolto') DEFAULT 'segnalato',
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    risolto_at TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_turista (turista_id),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Forza Maggiore

📋 Database: se_force_majeure_requests
CREATE TABLE se_force_majeure_requests (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    booking_id BIGINT UNSIGNED NOT NULL,
    user_id BIGINT UNSIGNED NOT NULL,
    
    -- Tipo
    tipo ENUM('medica','lutto','disastro','governo','trasporti','altro') NOT NULL,
    descrizione TEXT NOT NULL,
    
    -- Documentazione
    documenti JSON,                               -- Array di file uploadati
    documenti_verificati BOOLEAN DEFAULT FALSE,
    
    -- Richiesta
    opzione_richiesta ENUM('credito','cambio_data','rimborso_parziale') NOT NULL,
    
    -- Decisione
    decisione ENUM('approvata','rifiutata','parziale'),
    decisione_note TEXT,
    decisione_da BIGINT UNSIGNED,
    decisione_at TIMESTAMP,
    
    -- Esecuzione
    credito_emesso DECIMAL(10,2),
    credito_scadenza DATE,                        -- +12 mesi
    rimborso_emesso DECIMAL(10,2),
    nuova_data_check_in DATE,
    
    -- Stato
    stato ENUM('richiesta','in_revisione','approvata','rifiutata','eseguita') DEFAULT 'richiesta',
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (booking_id) REFERENCES se_bookings(id),
    KEY idx_user (user_id),
    KEY idx_stato (stato)
) ENGINE=InnoDB;

Test Cases

📈 MOD_27 - Analytics

MOD_27_ANALYTICS

Scopo

Dashboard analytics per host, business, admin. Metriche performance, revenue, conversioni, comparativi.

Metriche Host

Occupancy Rate
% notti prenotate / notti disponibili
ADR
Average Daily Rate - prezzo medio notte
RevPAR
Revenue Per Available Room
Booking Lead Time
Giorni medi tra prenotazione e check-in
Cancellation Rate
% prenotazioni cancellate
Review Score
Rating medio e trend

Metriche Business

Conversion Rate
Visite pagina / prenotazioni
Average Order Value
Valore medio ordine
Repeat Customers
% clienti che tornano
Top Products
Offerte più vendute

Metriche Piattaforma (Admin)

GMV
Gross Merchandise Value totale
Take Rate
% commissioni su GMV
Active Users
MAU, DAU, nuovi utenti
Host Churn
% host che non rinnovano
Trial Conversion
% trial che convertono in paid
Support Tickets
Volume e tempo risoluzione

Database: se_analytics_daily

📋 Schema SQL
-- Aggregazioni giornaliere pre-calcolate
CREATE TABLE se_analytics_daily (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    data DATE NOT NULL,
    
    -- Scope
    scope_type ENUM('platform','host','business') NOT NULL,
    scope_id BIGINT UNSIGNED,                     -- NULL per platform
    
    -- Metriche
    metriche JSON NOT NULL,
    /*
    Esempio JSON per host:
    {
        "prenotazioni_nuove": 5,
        "prenotazioni_confermate": 4,
        "prenotazioni_cancellate": 1,
        "notti_prenotate": 12,
        "notti_disponibili": 30,
        "revenue_lordo": 1200.00,
        "revenue_netto": 1080.00,
        "commissioni": 120.00,
        "rating_medio": 4.5,
        "recensioni_nuove": 2,
        "messaggi_ricevuti": 8,
        "tempo_risposta_medio_minuti": 45,
        "visualizzazioni_pagina": 234,
        "click_prenota": 15
    }
    */
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY uk_data_scope (data, scope_type, scope_id),
    KEY idx_data (data),
    KEY idx_scope (scope_type, scope_id)
) ENGINE=InnoDB;

-- Eventi tracking (raw)
CREATE TABLE se_analytics_events (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- Identificazione
    session_id VARCHAR(100),
    user_id BIGINT UNSIGNED,
    
    -- Evento
    evento VARCHAR(50) NOT NULL,                  -- page_view|click|booking_start|booking_complete|...
    
    -- Contesto
    entity_type VARCHAR(50),
    entity_id BIGINT UNSIGNED,
    
    -- Dati aggiuntivi
    dati JSON,
    
    -- Request info
    ip_hash VARCHAR(64),                          -- Hash per privacy
    user_agent VARCHAR(255),
    referrer VARCHAR(500),
    
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    KEY idx_evento (evento),
    KEY idx_entity (entity_type, entity_id),
    KEY idx_created (created_at),
    KEY idx_session (session_id)
) ENGINE=InnoDB;

Test Cases

🌍 MOD_28 - Multi-lingua

MOD_28_I18N

Scopo

Supporto multilingua per frontend turisti: IT, EN, DE, FR, ES. Implementazione custom senza plugin pesanti.

Lingue Supportate

🇮🇹 Italiano
Lingua default, completa
🇬🇧 English
Priorità alta, mercato UK/US
🇩🇪 Deutsch
Priorità alta, mercato DACH
🇫🇷 Français
Priorità media
🇪🇸 Español
Priorità media

Struttura URL

📋 URL Patterns
// Homepage
sicilyexperience.com/           → Italiano (default)
sicilyexperience.com/en/        → English
sicilyexperience.com/de/        → Deutsch

// Strutture
sicilyexperience.com/strutture/             → IT
sicilyexperience.com/en/accommodations/     → EN
sicilyexperience.com/de/unterkuenfte/       → DE

// Esperienze
sicilyexperience.com/esperienze/            → IT
sicilyexperience.com/en/experiences/        → EN
sicilyexperience.com/de/erlebnisse/         → DE

// Dettaglio (slug tradotto)
sicilyexperience.com/strutture/villa-sul-mare-taormina/
sicilyexperience.com/en/accommodations/seaside-villa-taormina/

ACF Multi-lingua

📋 Campo Esempio
// Approccio 1: Campi separati per lingua
'titolo_it' => 'text',
'titolo_en' => 'text',
'titolo_de' => 'text',
'titolo_fr' => 'text',
'titolo_es' => 'text',

'descrizione_it' => 'wysiwyg',
'descrizione_en' => 'wysiwyg',
// etc.

// Helper function
function get_translated_field($field, $post_id = null, $lang = null) {
    $lang = $lang ?: get_current_language();
    $value = get_field($field . '_' . $lang, $post_id);
    
    // Fallback to Italian if empty
    if (empty($value)) {
        $value = get_field($field . '_it', $post_id);
    }
    
    return $value;
}

Test Cases

🔍 MOD_29 - SEO e Schema.org

MOD_29_SEO

Scopo

Ottimizzazione SEO completa: Schema.org markup, Open Graph, sitemap dinamiche, llms.txt per AI.

Schema.org Markup

📋 LodgingBusiness Schema
{
  "@context": "https://schema.org",
  "@type": "LodgingBusiness",
  "name": "Villa Sul Mare Taormina",
  "description": "Splendida villa con vista mare...",
  "image": ["https://...jpg"],
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Via Roma 123",
    "addressLocality": "Taormina",
    "addressRegion": "ME",
    "postalCode": "98039",
    "addressCountry": "IT"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 37.8516,
    "longitude": 15.2853
  },
  "priceRange": "€€€",
  "starRating": {
    "@type": "Rating",
    "ratingValue": "4"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.8",
    "reviewCount": "127"
  },
  "amenityFeature": [
    {"@type": "LocationFeatureSpecification", "name": "WiFi", "value": true},
    {"@type": "LocationFeatureSpecification", "name": "Piscina", "value": true}
  ]
}

llms.txt

File per AI: /llms.txt contiene informazioni strutturate per citazione da parte di AI (ChatGPT, Claude, etc.)

Test Cases

📊 MOD_30 - Export Contabilità

MOD_30_EXPORT

Scopo

Interfaccia backend per commercialista: export dati per Fatture in Cloud (API), Danea Easyfatt (.defxml), Excel/CSV.

Integrazioni Supportate

Fatture in Cloud
Sync API diretto: clienti, prodotti, fatture
Danea Easyfatt
Export file .defxml per import
Excel/CSV
Export generico per qualsiasi software

Dati Esportabili

Dato Descrizione Formato
Commissioni Commissioni piattaforma per periodo CSV, API
Payout Host Trasferimenti a host/business CSV, API
Abbonamenti Sottoscrizioni e rinnovi CSV, API
Transazioni Tutte le transazioni Stripe CSV, API
Tasse Soggiorno Report per comune CSV, PDF

Test Cases

📜 Policy - Cancellazioni

POLICY_CANCELLAZIONI

Politiche Cancellazione Predefinite

🔴
Non Rimborsabile

Prezzo scontato, nessun rimborso in caso di cancellazione.

Ideale per: Tariffe promozionali, last minute

🟡
Standard (5 giorni)
  • Cancellazione gratuita fino a 5 giorni prima
  • 1-5 giorni prima: 50% trattenuto
  • Meno di 24 ore o no-show: 100% trattenuto
🟢
Flessibile (14 giorni)
  • Cancellazione gratuita fino a 14 giorni prima
  • 7-14 giorni: 30% trattenuto
  • 1-7 giorni: 50% trattenuto
  • Meno di 24 ore o no-show: 100% trattenuto

Logica Calcolo Rimborso

includes/policies/class-sicily-cancellation.php PHP
class Sicily_Cancellation {
    
    /**
     * Calcola rimborso per cancellazione
     */
    public static function calculate_refund($booking_id) {
        $booking = sicily_get_booking($booking_id);
        $policy = json_decode($booking->cancellation_policy_snapshot, true);
        
        $days_until_checkin = self::days_until($booking->check_in);
        $total_paid = $booking->deposit_amount ?? $booking->total;
        
        $refund_percentage = 0;
        
        // Trova fascia applicabile
        foreach ($policy['fasce'] as $fascia) {
            if ($days_until_checkin >= $fascia['giorni_min']) {
                $refund_percentage = $fascia['rimborso_percentuale'];
                break;
            }
        }
        
        $refund_amount = $total_paid * ($refund_percentage / 100);
        $penalty_amount = $total_paid - $refund_amount;
        
        // Calcola commissione su penalty
        $commission_on_penalty = $penalty_amount * ($booking->commission_rate / 100);
        $host_receives = $penalty_amount - $commission_on_penalty;
        
        return [
            'refund_percentage' => $refund_percentage,
            'refund_amount' => $refund_amount,
            'penalty_amount' => $penalty_amount,
            'commission_amount' => $commission_on_penalty,
            'host_receives' => $host_receives,
            'policy_name' => $policy['nome']
        ];
    }
    
    /**
     * Esegue cancellazione
     */
    public static function process_cancellation($booking_id, $cancelled_by = 'guest') {
        global $wpdb;
        
        $refund = self::calculate_refund($booking_id);
        $booking = sicily_get_booking($booking_id);
        
        // Aggiorna stato
        $wpdb->update('se_bookings', [
            'status' => 'cancelled_' . $cancelled_by,
            'cancelled_at' => current_time('mysql')
        ], ['id' => $booking_id]);
        
        // Registra cancellazione
        $wpdb->insert('se_cancellations', [
            'booking_id' => $booking_id,
            'cancelled_by' => $cancelled_by,
            'refund_amount' => $refund['refund_amount'],
            'penalty_amount' => $refund['penalty_amount'],
            'policy_applied' => $refund['policy_name'],
            'created_at' => current_time('mysql')
        ]);
        
        // Processa rimborso se dovuto
        if ($refund['refund_amount'] > 0) {
            sicily_process_refund($booking->wc_order_id, $refund['refund_amount']);
        }
        
        // Sblocca calendario
        sicily_release_calendar($booking_id);
        
        // Invia notifiche
        sicily_send_cancellation_notifications($booking_id, $refund);
        
        return $refund;
    }
}

🚫 Policy - No-Show

POLICY_NOSHOW

Definizione

Il no-show si verifica quando il turista non si presenta al check-in E non ha cancellato la prenotazione.

Procedura Gestione No-Show

┌─────────────────────────────────────────────────────────────────────────────┐ │ PROCEDURA NO-SHOW │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. SEGNALAZIONE (entro 24h dal check-in previsto) │ │ ├── Host segnala no-show dalla dashboard │ │ └── Sistema registra orario segnalazione │ │ │ │ 2. NOTIFICA AL TURISTA │ │ ├── Email: "Non ti sei presentato. Hai 24h per rispondere" │ │ └── Push notification se abilitata │ │ │ │ 3. RISPOSTA TURISTA (entro 24h) │ │ ├── "Sono in ritardo, arrivo" → Prenotazione resta attiva │ │ ├── "C'è stato un errore" → Verifica e decisione │ │ └── Nessuna risposta → No-show confermato │ │ │ │ 4. SE NO-SHOW CONFERMATO │ │ ├── Si applica politica cancellazione (ultima fascia = 100%) │ │ ├── Host riceve: importo - commissione piattaforma │ │ ├── Piattaforma trattiene: commissione normale │ │ └── Turista riceve: €0 │ │ │ │ 5. SEGNALAZIONE TURISTA RECIDIVO │ │ ├── 1° no-show: Warning │ │ ├── 2° no-show: Badge "attenzione" visibile agli host │ │ ├── 3° no-show: Pagamento anticipato 100% obbligatorio │ │ └── 5° no-show: Ban dalla piattaforma │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

⚠️ Policy - Overbooking

POLICY_OVERBOOKING

Definizione

L'overbooking si verifica quando l'host conferma una prenotazione ma la camera/struttura non è effettivamente disponibile.

Procedura Gestione

Step 1: Ricollocamento (Priorità)

Step 2: Se Ricollocamento Accettato

Step 3: Se Ricollocamento Rifiutato/Impossibile

Penali Host

LivelloCondizionePenale
Livello 1 Primo overbooking Warning + email educativa + paga differenza e bonus
Livello 2 Secondo (entro 12 mesi) Sospensione 7gg + €50 penale + badge "Attenzione" 30gg
Livello 3 Terzo (entro 12 mesi) Sospensione 30gg + €150 penale + downgrade piano
Livello 4 Quarto+ Ban permanente + recensione pubblica
⚠️ Trattenuta Forzata

Tutti i costi (differenza ricollocamento, bonus turista, penali) vengono trattenuti automaticamente dal prossimo payout dell'host. Se il payout è insufficiente, il debito viene scalato dai payout successivi.

🌪️ Policy - Forza Maggiore

POLICY_FORZA_MAGGIORE

Definizione

Circostanze eccezionali e imprevedibili che impediscono il viaggio, non imputabili al turista.

Casi Riconosciuti

🏥 Emergenza Medica
Ricovero, malattia grave documentata, COVID positivo
⚰️ Lutto
Decesso familiare di primo/secondo grado
🌊 Disastro Naturale
Terremoto, alluvione, eruzione vulcanica nella zona
🏛️ Provvedimento Governativo
Lockdown, chiusura frontiere, divieti di viaggio
✈️ Problemi Trasporto
Cancellazione volo (non ritardo), sciopero trasporti

Documentazione Richiesta

Termine: Entro 14 giorni dall'evento
Documenti: Certificato medico, certificato decesso, comunicazione compagnia aerea, DPCM/ordinanze, foto danni
Validazione: Admin verifica autenticità

Opzioni di Rimborso

Opzione Valore Validità Note
Credito Piattaforma 100% 12 mesi Opzione preferita, sempre disponibile
Cambio Data 100% Soggetto a disponibilità Stesso host, stessa struttura
Rimborso Parziale 50% Immediato Solo casi eccezionali, decisione admin

Impatto su Host

L'host non viene penalizzato per cancellazioni per forza maggiore.
Se il pagamento era già stato trasferito all'host, viene stornato dal prossimo payout.

💳 Policy - Pagamenti

POLICY_PAGAMENTI

Modalità Pagamento Turista

Modalità Deposito Saldo Configurabile Host
Pagamento Totale 100% -
Deposito 30% 30% 70% (7gg prima check-in)
Deposito 50% 50% 50% (7gg prima check-in)

Piani Tariffari Host

Piano Canone Commissione Cross-Referral Features
Base €0/mese 12% 0% Listing base, 10 foto, 1 struttura
Standard €29/mese 5% 3% + iCal sync, guide, analytics base
Premium €79/mese 0% 5% + Multi-struttura, priority support, analytics avanzate

Payout Host

Timing: 24h dopo check-in confermato
Metodo: Stripe Connect (transfer automatico)
Minimo: €10 (sotto questa soglia, accumula)
Frequenza: Automatico per ogni prenotazione completata

Commissioni su Penali

Quando il turista cancella e paga una penale, la piattaforma trattiene la stessa % di commissione prevista dal piano host anche sull'importo trattenuto.

🛡️ GDPR_01 - Privacy & Compliance

GDPR_01_PRIVACY

Riferimenti Normativi

GDPR: Regolamento UE 2016/679
Codice Privacy: D.Lgs. 196/2003 (modificato D.Lgs. 101/2018)
Cookie Law: Direttiva ePrivacy 2002/58/CE
Garante: Linee guida cookie e tracciatori (10 giugno 2021)

Basi Giuridiche Trattamento

Dato Base Giuridica Retention
Dati prenotazione (nome, email, telefono) Esecuzione contratto (Art. 6.1.b) 10 anni (obblighi fiscali)
Dati documento identità (Payturist) Obbligo legale (Art. 6.1.c) 5 anni (Questura)
Dati pagamento (Stripe) Esecuzione contratto 10 anni (obblighi fiscali)
Email marketing Consenso (Art. 6.1.a) Fino a revoca
Cookie analytics Consenso 26 mesi max
Log di sistema Legittimo interesse (Art. 6.1.f) 6 mesi
Recensioni Consenso Indefinito (anonimizzabile)

Cookie Banner

📋 Categorie Cookie
Categoria Cookie Scopo Consenso
Necessari session_id, csrf_token, cookie_consent Funzionamento sito Non richiesto
Funzionali lingua, valuta, ricerche_recenti Preferenze utente Consenso
Analytics _ga, _gid, _gat Statistiche anonime Consenso
Marketing _fbp, fr (Meta), ads (Google) Pubblicità personalizzata Consenso esplicito

Wireframe Cookie Banner

📋 Layout Banner
┌─────────────────────────────────────────────────────────────┐
│  🍪 Utilizziamo i cookie                                    │
│                                                             │
│  Questo sito utilizza cookie tecnici necessari e, con il   │
│  tuo consenso, cookie di analisi e marketing.              │
│                                                             │
│  [Personalizza]  [Rifiuta tutti]  [Accetta tutti]          │
│                                                             │
│  Leggi la nostra [Privacy Policy] e [Cookie Policy]        │
└─────────────────────────────────────────────────────────────┘

[Se clicca "Personalizza":]
┌─────────────────────────────────────────────────────────────┐
│  Gestisci preferenze cookie                          [✕]   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ☑️ Necessari (sempre attivi)                              │
│     Cookie essenziali per il funzionamento del sito        │
│                                                             │
│  ☐ Funzionali                                              │
│     Ricordano le tue preferenze (lingua, ricerche)         │
│                                                             │
│  ☐ Analytics                                               │
│     Ci aiutano a capire come usi il sito (anonimi)         │
│                                                             │
│  ☐ Marketing                                               │
│     Mostrano pubblicità pertinenti ai tuoi interessi       │
│                                                             │
│  [Salva preferenze]                                        │
└─────────────────────────────────────────────────────────────┘

Diritti dell'Interessato

Diritto Articolo GDPR Implementazione Tempo Risposta
Accesso Art. 15 Export dati da Area Utente 30 giorni
Rettifica Art. 16 Modifica profilo self-service Immediato
Cancellazione Art. 17 Richiesta da Area Utente 30 giorni
Portabilità Art. 20 Download JSON/CSV 30 giorni
Opposizione Art. 21 Opt-out marketing Immediato

Cancellazione Account (Art. 17)

📋 Procedura Cancellazione
1
Richiesta

Utente richiede cancellazione da Area Utente

2
Verifica

Check prenotazioni attive, saldi pendenti

3
Conferma

Email conferma + link verifica

4
Cooling-off

7 giorni per annullare

5
Esecuzione

Anonimizzazione/eliminazione dati

Cosa viene eliminato:

  • Email, telefono, indirizzo
  • Foto profilo
  • Preferenze e ricerche salvate
  • Wishlist
  • Messaggi (contenuto)

Cosa viene anonimizzato (conservato):

  • Prenotazioni → "Utente Cancellato #12345"
  • Recensioni → "Ospite verificato"
  • Transazioni finanziarie (obbligo fiscale 10 anni)

Blocchi alla cancellazione:

  • Prenotazioni future attive → deve cancellarle prima
  • Saldo wallet > 0 → deve prelevare prima
  • Dispute aperte → deve risolvere prima
  • Host con strutture attive → deve disattivarle prima

Export Dati (Art. 20)

📋 Dati Esportabili
{
  "export_date": "2024-07-15T10:30:00Z",
  "user": {
    "email": "mario.rossi@email.com",
    "nome": "Mario",
    "cognome": "Rossi",
    "telefono": "+39 333 1234567",
    "data_registrazione": "2023-01-15"
  },
  "prenotazioni": [
    {
      "codice": "SE-2024-1234",
      "struttura": "Villa Vista Mare",
      "check_in": "2024-06-10",
      "check_out": "2024-06-15",
      "totale": 750.00,
      "stato": "completata"
    }
  ],
  "recensioni": [
    {
      "struttura": "Villa Vista Mare",
      "data": "2024-06-18",
      "voto": 5,
      "testo": "Fantastico soggiorno..."
    }
  ],
  "messaggi": [
    {
      "data": "2024-06-05",
      "con": "Host Villa Vista Mare",
      "contenuto": "Buongiorno, a che ora..."
    }
  ],
  "preferenze": {
    "lingua": "it",
    "valuta": "EUR",
    "notifiche_email": true
  }
}

Data Breach Procedure

In caso di violazione dati personali:
1. Notifica al Garante entro 72 ore (se rischio per interessati)
2. Documentazione interna immediata
3. Notifica agli interessati se rischio elevato
4. Analisi causa e remediation
5. Aggiornamento misure di sicurezza

Contatto DPO: dpo@sicilyexperience.com

Registro Trattamenti

Il Registro dei Trattamenti (Art. 30) è mantenuto come documento separato e contiene: finalità, categorie interessati, categorie dati, destinatari, trasferimenti extra-UE, termini cancellazione, misure sicurezza.

Test Cases

🔌 Integrazione Payturist

INT_PAYTURIST

Panoramica

Payturist è il sistema utilizzato dalle strutture ricettive siciliane per gestire gli adempimenti burocratici obbligatori. L'integrazione automatizza completamente questi processi.

Funzionalità Automatizzate

AdempimentoDestinatarioFrequenzaAutomatizzato
Schedine AlloggiatiPolizia di StatoEntro 24h dal check-in
Flussi turistici ISTATOsservatorio Turistico SiciliaGiornaliero
Tassa di soggiornoComuneMensile/Trimestrale
Dichiarazione AnnualeAgenzia delle EntrateAnnuale
Modello 21Corte dei ContiAnnuale

Configurazione

Per integrare una struttura con Payturist sono necessari:

API Payturist

includes/integrations/class-sicily-payturist.php PHP
class Sicily_Payturist {
    
    private $api_base = 'https://api.paytourist.com/v1';
    private $software_id;
    
    public function __construct() {
        $this->software_id = get_option('sicily_payturist_software_id');
    }
    
    /**
     * Invia prenotazione a Payturist
     */
    public function send_reservation($booking_id) {
        $booking = sicily_get_booking($booking_id);
        $struttura = get_post($booking->struttura_id);
        
        $structure_id = get_field('payturist_structure_id', $struttura->ID);
        $token = get_field('payturist_token', $struttura->ID);
        
        if (!$structure_id || !$token) {
            return new WP_Error('not_configured', 'Payturist non configurato per questa struttura');
        }
        
        // Prepara dati ospiti
        $guests = $this->prepare_guests_data($booking_id);
        
        $payload = [
            'operation' => 'INSERT',
            'software_id' => $this->software_id,
            'structure_id' => $structure_id,
            'reservation' => [
                'external_id' => $booking->booking_code,
                'check_in' => $booking->check_in,
                'check_out' => $booking->check_out,
                'room_type' => $this->get_room_type($booking),
                'guests' => $guests
            ]
        ];
        
        $response = wp_remote_post($this->api_base . '/reservations', [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode($payload),
            'timeout' => 30
        ]);
        
        if (is_wp_error($response)) {
            $this->log_error($booking_id, $response->get_error_message());
            return $response;
        }
        
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        // Aggiorna booking
        global $wpdb;
        $wpdb->update('se_bookings', [
            'payturist_sent' => 1,
            'payturist_sent_at' => current_time('mysql'),
            'payturist_response' => json_encode($body)
        ], ['id' => $booking_id]);
        
        return $body;
    }
    
    /**
     * Prepara dati ospiti nel formato Payturist
     */
    private function prepare_guests_data($booking_id) {
        global $wpdb;
        $guests = $wpdb->get_results($wpdb->prepare("
            SELECT * FROM se_booking_guests WHERE booking_id = %d
        ", $booking_id));
        
        $formatted = [];
        foreach ($guests as $guest) {
            $formatted[] = [
                'first_name' => $guest->first_name,
                'last_name' => $guest->last_name,
                'birth_date' => $guest->birth_date,
                'birth_place' => $guest->birth_place,
                'birth_country' => $guest->birth_country,
                'citizenship' => $guest->citizenship,
                'gender' => $guest->gender,
                'document' => [
                    'type' => $guest->document_type,
                    'number' => $guest->document_number,
                    'issued_by' => $guest->document_issued_by,
                    'issue_date' => $guest->document_issue_date
                ],
                'is_primary' => $guest->is_primary
            ];
        }
        
        return $formatted;
    }
}

💳 Integrazione Stripe Connect

INT_STRIPE

Architettura

Tipo: Stripe Connect con Express Accounts
Modello: Direct charges con application fee
Valuta: EUR

Flusso Onboarding Host

1
Crea Account

API: Stripe\Account::create(['type' => 'express'])

2
Onboarding Link

Redirect a Stripe per KYC e dati bancari

3
Webhook

account.updated → verifica capabilities attive

4
Pronto

Host può ricevere pagamenti

Flusso Pagamento

📋 Code Example
// Crea PaymentIntent con split
$paymentIntent = \Stripe\PaymentIntent::create([
    'amount' => $amount_cents,
    'currency' => 'eur',
    'payment_method_types' => ['card'],
    'application_fee_amount' => $commission_cents,
    'transfer_data' => [
        'destination' => $host_stripe_account_id,
    ],
    'metadata' => [
        'booking_id' => $booking_id,
        'platform' => 'sicily_experience'
    ]
]);

Webhooks Gestiti

payment_intent.succeeded
Conferma prenotazione
payment_intent.payment_failed
Notifica errore, retry
charge.refunded
Aggiorna stato cancellazione
account.updated
Verifica onboarding completo

📅 Integrazione iCal Sync

INT_ICAL

Scopo

Sincronizzazione bidirezionale calendari con OTA: Booking.com, Airbnb, Vrbo, altri channel manager.

Import (da OTA)

📋 Flusso Import
class iCalImporter {
    public function import($struttura_id) {
        $sync = $this->get_sync_config($struttura_id);
        if (!$sync->ical_import_url) return;
        
        // Fetch iCal
        $response = wp_remote_get($sync->ical_import_url);
        $ical_content = wp_remote_retrieve_body($response);
        
        // Parse
        $calendar = new ICal($ical_content);
        $events = $calendar->events();
        
        foreach ($events as $event) {
            $start = date('Y-m-d', strtotime($event->dtstart));
            $end = date('Y-m-d', strtotime($event->dtend));
            
            // Blocca date nel nostro calendario
            $this->block_dates($struttura_id, $start, $end, [
                'source' => 'ical_import',
                'ical_event_uid' => $event->uid,
                'note' => $event->summary ?? 'Prenotazione esterna'
            ]);
        }
        
        // Update sync status
        $this->update_sync_status($sync->id, 'success');
    }
}

Export (verso OTA)

📋 URL Export
// URL pubblico per export
https://sicilyexperience.com/ical/export/{token}

// Contenuto generato
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sicily Experience//Booking Calendar//IT
X-WR-CALNAME:Villa Taormina - Sicily Experience
BEGIN:VEVENT
UID:[email protected]
DTSTART:20260715
DTEND:20260720
SUMMARY:Prenotato - Sicily Experience
DESCRIPTION:Prenotazione confermata
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR

Frequenza Sync

Import: Ogni 6 ore (configurabile 1-24h)
Export: Real-time (URL sempre aggiornato)
Cron job: WP-Cron o sistema cron esterno per reliability

📄 Integrazione Fatture in Cloud

INT_FATTURE_CLOUD

Scopo

Sincronizzazione automatica con Fatture in Cloud per contabilità: clienti, fatture, prodotti/servizi.

API Endpoints Utilizzati

POST /clients
Crea/aggiorna anagrafica cliente
POST /products
Crea prodotti/servizi
POST /issued_documents
Crea fattura/ricevuta
GET /issued_documents
Recupera documenti emessi

Flusso Automatico

1
Prenotazione Completata

Check-out confermato

2
Crea/Aggiorna Cliente

Dati turista → Fatture in Cloud

3
Genera Fattura

Commissione piattaforma verso host

📊 Export Danea Easyfatt

INT_DANEA

Scopo

Generazione file .defxml compatibile con import Danea Easyfatt per commercialisti che usano questo software.

Formato Export

📋 Struttura .defxml
<?xml version="1.0" encoding="UTF-8"?>
<EasyfattDocuments AppVersion="2" Creator="SicilyExperience">
  <Documents>
    <Document>
      <DocumentType>F</DocumentType>
      <Date>2026-07-15</Date>
      <Number>2026/001</Number>
      <CustomerCode>CLI001</CustomerCode>
      <CustomerName>Mario Rossi</CustomerName>
      <CustomerFiscalCode>RSSMRA80A01H501U</CustomerFiscalCode>
      <Rows>
        <Row>
          <Code>COMM-STD</Code>
          <Description>Commissione prenotazione #12345</Description>
          <Qty>1</Qty>
          <Price>50.00</Price>
          <VatCode>22</VatCode>
        </Row>
      </Rows>
      <Total>61.00</Total>
    </Document>
  </Documents>
</EasyfattDocuments>

Dati Esportabili

🗺️ INT-MAPS - Google Maps API

INT_GOOGLE_MAPS

Scopo

Integrazione con Google Maps Platform per visualizzazione mappe, geocoding, calcolo distanze e directions nelle pagine strutture, esperienze e guide.

API Utilizzate

API Utilizzo Costo stimato
Maps JavaScript API Mappe interattive su pagine struttura, ricerca, guide $7 / 1000 loads
Geocoding API Conversione indirizzo → coordinate (inserimento struttura) $5 / 1000 requests
Places API Autocomplete indirizzo, dettagli POI $17 / 1000 requests
Distance Matrix API Calcolo distanza/tempo da aeroporto, stazione $5 / 1000 elements
Static Maps API Immagini mappa per email, PDF $2 / 1000 requests

Configurazione

📋 Setup API Key
// wp-config.php
define('GOOGLE_MAPS_API_KEY', getenv('GOOGLE_MAPS_API_KEY'));

// Restrizioni API Key (Google Cloud Console):
// - HTTP referrers: 
//   - sicilyexperience.com/*
//   - *.sicilyexperience.com/*
//   - localhost:* (solo development)
// - API restrictions: Solo API elencate sopra

// Caricare script con API key
function se_enqueue_google_maps() {
    if (is_page_template(['search', 'property', 'guide'])) {
        wp_enqueue_script(
            'google-maps',
            'https://maps.googleapis.com/maps/api/js?key=' . GOOGLE_MAPS_API_KEY . '&libraries=places&callback=initMap',
            [],
            null,
            true
        );
    }
}
add_action('wp_enqueue_scripts', 'se_enqueue_google_maps');

Componente Mappa Riutilizzabile

📋 JavaScript Component
class SEMap {
    constructor(containerId, options = {}) {
        this.container = document.getElementById(containerId);
        this.markers = [];
        this.options = {
            zoom: options.zoom || 12,
            center: options.center || { lat: 37.5, lng: 14.0 }, // Centro Sicilia
            styles: this.getCustomStyles(),
            ...options
        };
        this.init();
    }
    
    init() {
        this.map = new google.maps.Map(this.container, this.options);
        this.infoWindow = new google.maps.InfoWindow();
    }
    
    addMarker(location, options = {}) {
        const marker = new google.maps.Marker({
            position: location,
            map: this.map,
            icon: options.icon || this.getDefaultIcon(options.type),
            title: options.title,
        });
        
        if (options.infoContent) {
            marker.addListener('click', () => {
                this.infoWindow.setContent(options.infoContent);
                this.infoWindow.open(this.map, marker);
            });
        }
        
        this.markers.push(marker);
        return marker;
    }
    
    fitBounds() {
        if (this.markers.length === 0) return;
        
        const bounds = new google.maps.LatLngBounds();
        this.markers.forEach(m => bounds.extend(m.getPosition()));
        this.map.fitBounds(bounds);
    }
    
    getDefaultIcon(type) {
        const icons = {
            structure: '/assets/icons/marker-house.png',
            experience: '/assets/icons/marker-star.png',
            restaurant: '/assets/icons/marker-fork.png',
            attraction: '/assets/icons/marker-camera.png',
        };
        return icons[type] || null;
    }
    
    getCustomStyles() {
        // Stile custom per match brand Sicily Experience
        return [
            { featureType: 'water', stylers: [{ color: '#1a5f7a' }] },
            { featureType: 'landscape', stylers: [{ color: '#f5f5f5' }] },
            // ... altri stili
        ];
    }
}

// Utilizzo
const map = new SEMap('property-map', {
    center: { lat: 37.8516, lng: 15.2853 }, // Taormina
    zoom: 15
});

map.addMarker(
    { lat: 37.8516, lng: 15.2853 },
    {
        type: 'structure',
        title: 'Villa Vista Mare',
        infoContent: '<div class="map-popup"><strong>Villa Vista Mare</strong><br>Da €120/notte</div>'
    }
);

Geocoding Backend

📋 PHP Geocoding Helper
class SE_Geocoding {
    
    public static function addressToCoordinates($address) {
        $cache_key = 'geocode_' . md5($address);
        $cached = get_transient($cache_key);
        
        if ($cached !== false) {
            return $cached;
        }
        
        $url = add_query_arg([
            'address' => urlencode($address),
            'key' => GOOGLE_MAPS_API_KEY,
        ], 'https://maps.googleapis.com/maps/api/geocode/json');
        
        $response = wp_remote_get($url);
        
        if (is_wp_error($response)) {
            return null;
        }
        
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($body['status'] !== 'OK') {
            return null;
        }
        
        $result = [
            'lat' => $body['results'][0]['geometry']['location']['lat'],
            'lng' => $body['results'][0]['geometry']['location']['lng'],
            'formatted_address' => $body['results'][0]['formatted_address'],
        ];
        
        // Cache per 30 giorni (indirizzi non cambiano)
        set_transient($cache_key, $result, 30 * DAY_IN_SECONDS);
        
        return $result;
    }
    
    public static function calculateDistance($origin, $destination) {
        $url = add_query_arg([
            'origins' => "{$origin['lat']},{$origin['lng']}",
            'destinations' => "{$destination['lat']},{$destination['lng']}",
            'key' => GOOGLE_MAPS_API_KEY,
        ], 'https://maps.googleapis.com/maps/api/distancematrix/json');
        
        $response = wp_remote_get($url);
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($body['status'] === 'OK') {
            return [
                'distance_km' => $body['rows'][0]['elements'][0]['distance']['value'] / 1000,
                'duration_min' => $body['rows'][0]['elements'][0]['duration']['value'] / 60,
            ];
        }
        
        return null;
    }
}

Fallback senza Maps

Se Google Maps non disponibile (quota esaurita, errore API):
- Mostrare immagine statica mappa (pre-generata)
- Link a Google Maps esterno
- Coordinate testuali copiabili
- Log errore per alerting

Budget Mensile Stimato

Maps loads (10k/mese)
~$70
Geocoding (500/mese)
~$2.50
Places (2k/mese)
~$34
Distance (1k/mese)
~$5
TOTALE
~$110/mese

Test Cases

🌴 Frontend Turisti - Overview

FE_TURISTI_OVERVIEW

Caratteristiche

📱
Mobile-First
70%+ del traffico sarà mobile. Design ottimizzato per touch e schermi piccoli.
🌍
Multi-Lingua
IT, EN, DE, FR, ES. Traduzione automatica + revisione manuale.
🎨
Emozionale
Immagini grandi, video, colori mediterranei. Far sognare la Sicilia.
Veloce
LCP < 3 secondi. Lazy loading, ottimizzazione immagini, CDN.

Struttura Pagine

/ → Homepage /strutture → Ricerca strutture /strutture/{citta} → Strutture filtrate per città /strutture/{citta}/{slug} → Scheda singola struttura /esperienze → Ricerca esperienze /esperienze/{categoria} → Esperienze per categoria /esperienze/{citta}/{slug} → Scheda singola esperienza /eventi → Calendario eventi /eventi/{citta} → Eventi per città /guide/{citta} → Guida città /checkout → Processo checkout /account → Area utente /account/prenotazioni → Le mie prenotazioni /account/wishlist → Preferiti /account/messaggi → Messaggi

Design System

Colori

Primary
#1a5f7a
Secondary
#ff6b35
Accent
#ffc947
Success
#2ecc71
Danger
#e74c3c

Tipografia

🏠 FE-T-01 - Homepage

FE_TURISTI_HOMEPAGE

URL

sicilyexperience.com/ | sicilyexperience.com/{lang}/

Obiettivo

Ispirare, emozionare, convertire. Far sognare la Sicilia e spingere alla ricerca/prenotazione.

Sezioni Pagina

📋 Wireframe Struttura
┌─────────────────────────────────────────────────────────────┐
│  HEADER                                                      │
│  [Logo] [Strutture] [Esperienze] [Eventi] [🌐 IT▼] [Login]  │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  HERO SECTION (Full viewport, video/immagine background)    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                                                      │    │
│  │         "Scopri la vera Sicilia"                    │    │
│  │                                                      │    │
│  │  ┌─────────────────────────────────────────────┐   │    │
│  │  │ 🔍 Dove vuoi andare? │ 📅 Date │ 👥 2 │ 🔎 │   │    │
│  │  └─────────────────────────────────────────────┘   │    │
│  │                                                      │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  DESTINAZIONI POPOLARI                                      │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐    │
│  │Taormina│ │Palermo │ │Siracusa│ │Cefalù  │ │Catania │    │
│  │  📷    │ │  📷    │ │  📷    │ │  📷    │ │  📷    │    │
│  │85 str. │ │120 str.│ │67 str. │ │45 str. │ │92 str. │    │
│  └────────┘ └────────┘ └────────┘ └────────┘ └────────┘    │
├─────────────────────────────────────────────────────────────┤
│  STRUTTURE IN EVIDENZA                          [Vedi tutte]│
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │     📷       │ │     📷       │ │     📷       │        │
│  │ Villa Etna   │ │ B&B Centro   │ │ Casa Mare    │        │
│  │ Taormina     │ │ Palermo      │ │ Siracusa     │        │
│  │ ⭐4.9 €120/n │ │ ⭐4.7 €85/n  │ │ ⭐4.8 €95/n  │        │
│  └──────────────┘ └──────────────┘ └──────────────┘        │
├─────────────────────────────────────────────────────────────┤
│  ESPERIENZE IMPERDIBILI                         [Vedi tutte]│
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │     📷       │ │     📷       │ │     📷       │        │
│  │ Tour Etna    │ │ Lezione cucina│ │ Giro barca  │        │
│  │ da €65/pers  │ │ da €89/pers  │ │ da €120/pers│        │
│  └──────────────┘ └──────────────┘ └──────────────┘        │
├─────────────────────────────────────────────────────────────┤
│  PERCHÉ SICILY EXPERIENCE                                   │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐          │
│  │ ✅      │ │ 🔒      │ │ 💬      │ │ 🎁      │          │
│  │Verificati│ │Pagamenti│ │Supporto │ │ Senza   │          │
│  │         │ │ sicuri  │ │ locale  │ │commissioni│         │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘          │
├─────────────────────────────────────────────────────────────┤
│  EVENTI IN ARRIVO                                           │
│  [Calendario mini con prossimi 5 eventi]                    │
├─────────────────────────────────────────────────────────────┤
│  TESTIMONIAL                                                │
│  "Vacanza perfetta..." - Marco, Milano ⭐⭐⭐⭐⭐           │
├─────────────────────────────────────────────────────────────┤
│  NEWSLETTER                                                 │
│  [Email________________] [Iscriviti]                        │
├─────────────────────────────────────────────────────────────┤
│  FOOTER                                                     │
│  [Links] [Social] [Contatti] [Legal] [© 2026]              │
└─────────────────────────────────────────────────────────────┘

Componenti

SEO

Title: Sicily Experience - Alloggi, Esperienze e Eventi in Sicilia
H1: Scopri la vera Sicilia
Meta: Prenota alloggi verificati, esperienze autentiche e scopri gli eventi in Sicilia. Tutto in un'unica piattaforma.

🔍 FE-T-02 - Ricerca Strutture

FE_TURISTI_RICERCA_STRUTTURE

URL

/strutture | /strutture/{citta} | /strutture?check_in=...&check_out=...&guests=...

Layout

📋 Wireframe
┌─────────────────────────────────────────────────────────────┐
│  HEADER                                                      │
├─────────────────────────────────────────────────────────────┤
│  SEARCH BAR (sticky)                                        │
│  [Destinazione▼] [Check-in] [Check-out] [Ospiti▼] [🔍]     │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────────────┐  ┌────────────────────────────┐  │
│  │  FILTRI (sidebar)    │  │  RISULTATI                 │  │
│  │                      │  │                            │  │
│  │  Prezzo              │  │  156 strutture trovate     │  │
│  │  [━━━●━━━━━━━━━━━]   │  │  Ordina: [Consigliati ▼]   │  │
│  │  €30 - €500          │  │                            │  │
│  │                      │  │  ┌────────────────────────┐│  │
│  │  Tipologia           │  │  │  📷📷📷               ││  │
│  │  ☑ Hotel             │  │  │  Villa Sul Mare       ││  │
│  │  ☑ B&B               │  │  │  Taormina • 2 camere  ││  │
│  │  ☑ Casa vacanza      │  │  │  ⭐4.9 (127) • WiFi 🏊││  │
│  │  ☐ Ostello           │  │  │  €145/notte    [Vedi]││  │
│  │                      │  │  └────────────────────────┘│  │
│  │  Servizi             │  │                            │  │
│  │  ☐ Piscina           │  │  ┌────────────────────────┐│  │
│  │  ☐ WiFi              │  │  │  ...                   ││  │
│  │  ☐ Parcheggio        │  │  └────────────────────────┘│  │
│  │  ☐ Aria condizionata │  │                            │  │
│  │  ☐ Animali ammessi   │  │  [Carica altri risultati] │  │
│  │                      │  │                            │  │
│  │  Valutazione minima  │  │                            │  │
│  │  ⭐⭐⭐⭐ e oltre     │  │                            │  │
│  │                      │  │                            │  │
│  │  [Cancella filtri]   │  │                            │  │
│  └──────────────────────┘  └────────────────────────────┘  │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                    MAPPA                             │    │
│  │         (toggle view lista/mappa)                    │    │
│  │    📍 📍   📍                                        │    │
│  │       📍        📍                                   │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Funzionalità

Mobile

Su mobile: filtri in bottom sheet, mappa a schermo intero con FAB per tornare a lista.

🏨 FE-T-03 - Scheda Struttura

FE_TURISTI_SCHEDA_STRUTTURA

URL

/strutture/{citta}/{slug}
Esempio: /strutture/taormina/villa-sul-mare

Sezioni Pagina

📋 Wireframe
┌─────────────────────────────────────────────────────────────┐
│  BREADCRUMB: Home > Strutture > Taormina > Villa Sul Mare   │
├─────────────────────────────────────────────────────────────┤
│  GALLERIA FOTO (lightbox, swipe mobile)                     │
│  ┌─────────────────────────────┐ ┌─────┐ ┌─────┐           │
│  │                             │ │     │ │     │           │
│  │      FOTO PRINCIPALE        │ │  2  │ │  3  │           │
│  │                             │ │     │ │     │           │
│  │                             │ ├─────┤ ├─────┤           │
│  │                             │ │  4  │ │+12  │           │
│  └─────────────────────────────┘ └─────┘ └─────┘           │
├─────────────────────────────────────────────────────────────┤
│  ┌────────────────────────────────┐ ┌──────────────────┐   │
│  │  CONTENUTO                     │ │ BOOKING BOX      │   │
│  │                                │ │ (sticky sidebar) │   │
│  │  Villa Sul Mare         ❤️ 📤 │ │                  │   │
│  │  ⭐4.9 (127 recensioni)       │ │ €145 /notte      │   │
│  │  📍 Taormina, ME              │ │                  │   │
│  │                                │ │ [Check-in    ]   │   │
│  │  🏠 Intera villa • 4 camere   │ │ [Check-out   ]   │   │
│  │  👥 8 ospiti • 🛏️ 4 letti    │ │ [Ospiti: 2 ▼]   │   │
│  │                                │ │                  │   │
│  │  ─────────────────────────    │ │ Subtotale  €435  │   │
│  │                                │ │ Pulizia     €50  │   │
│  │  DESCRIZIONE                   │ │ Tassa sogg. €12  │   │
│  │  Lorem ipsum dolor sit amet... │ │ ──────────────── │   │
│  │  [Mostra tutto]                │ │ Totale     €497  │   │
│  │                                │ │                  │   │
│  │  ─────────────────────────    │ │ [  PRENOTA  ]    │   │
│  │                                │ │                  │   │
│  │  SERVIZI                       │ │ ✓ Cancellazione  │   │
│  │  ✓ WiFi  ✓ Piscina            │ │   gratuita 5gg   │   │
│  │  ✓ Parcheggio  ✓ A/C          │ │                  │   │
│  │  ✓ Lavatrice  ✓ Cucina        │ │ 💬 Contatta host │   │
│  │                                │ └──────────────────┘   │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  CAMERE DISPONIBILI            │                        │
│  │  (se modalità camere)          │                        │
│  │  ┌────────────────────────┐   │                        │
│  │  │ Camera Etna  €85/notte │   │                        │
│  │  │ 2 persone, bagno privato│   │                        │
│  │  │ [Seleziona]            │   │                        │
│  │  └────────────────────────┘   │                        │
│  │                                │                        │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  CALENDARIO DISPONIBILITÀ     │                        │
│  │  [Calendario interattivo]      │                        │
│  │                                │                        │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  POSIZIONE                     │                        │
│  │  [Mappa con pin]               │                        │
│  │  Come arrivare...              │                        │
│  │                                │                        │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  REGOLE DELLA CASA             │                        │
│  │  • Check-in: 15:00-20:00      │                        │
│  │  • Check-out: entro 10:00     │                        │
│  │  • No feste                    │                        │
│  │  • Animali ammessi             │                        │
│  │                                │                        │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  RECENSIONI                    │                        │
│  │  ⭐4.9 media • 127 recensioni │                        │
│  │  Pulizia ████████░░ 4.8       │                        │
│  │  Posizione █████████░ 5.0     │                        │
│  │  ...                           │                        │
│  │                                │                        │
│  │  [Lista recensioni]            │                        │
│  │                                │                        │
│  │  ─────────────────────────    │                        │
│  │                                │                        │
│  │  HOST                          │                        │
│  │  👤 Mario - Host dal 2020     │                        │
│  │  ⭐4.9 • 45 strutture         │                        │
│  │  Risponde in ~2 ore            │                        │
│  │                                │                        │
│  └────────────────────────────────┘                        │
├─────────────────────────────────────────────────────────────┤
│  STRUTTURE SIMILI                                           │
│  [Carousel]                                                 │
└─────────────────────────────────────────────────────────────┘

Schema.org

📋 JSON-LD
{
  "@context": "https://schema.org",
  "@type": "LodgingBusiness",
  "name": "Villa Sul Mare",
  "image": ["https://..."],
  "address": {...},
  "geo": {"@type": "GeoCoordinates", "latitude": 37.85, "longitude": 15.28},
  "priceRange": "€€€",
  "aggregateRating": {"@type": "AggregateRating", "ratingValue": 4.9, "reviewCount": 127},
  "amenityFeature": [...]
}

🎯 FE-T-04 - Esperienze

FE_TURISTI_ESPERIENZE

Pagine

Categorie Esperienze

🌋 Tour & Escursioni
Etna, isole, borghi, trekking
🍷 Food & Wine
Degustazioni, cooking class, cantine
⛵ Mare & Sport
Gite barca, diving, kayak, vela
🏛️ Cultura & Arte
Visite guidate, musei, siti archeologici
💆 Wellness
Spa, terme, massaggi
🎭 Eventi & Spettacoli
Teatro, concerti, tradizioni

Scheda Esperienza

🎉 FE-T-05 - Eventi

FE_TURISTI_EVENTI

Pagine

Vista Calendario

📋 Wireframe
┌─────────────────────────────────────────────────────────────┐
│  EVENTI IN SICILIA                                          │
├─────────────────────────────────────────────────────────────┤
│  [Città: Tutte ▼] [Categoria: Tutte ▼] [Mese: Luglio ▼]    │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐              │
│  │ Lun │ Mar │ Mer │ Gio │ Ven │ Sab │ Dom │              │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤              │
│  │  1  │  2  │  3  │  4  │  5  │  6  │  7  │              │
│  │     │ 🎭  │     │     │ 🎵  │ 🎪  │ 🎪  │              │
│  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤              │
│  │  8  │  9  │ 10  │ 11  │ 12  │ 13  │ 14  │              │
│  │     │     │     │     │     │ 🍷  │ 🍷  │              │
│  └─────┴─────┴─────┴─────┴─────┴─────┴─────┘              │
│                                                              │
│  EVENTI IN EVIDENZA                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🎭 Taormina Arte - Festival Teatro                   │   │
│  │    15-30 Luglio • Teatro Antico, Taormina           │   │
│  │    [Scopri di più]                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                              │
│  LISTA EVENTI                                               │
│  [Card eventi con data, luogo, categoria]                   │
└─────────────────────────────────────────────────────────────┘

Categorie Eventi

🎭 Cultura
Teatro, mostre, festival
🎵 Musica
Concerti, festival musicali
🍷 Sagre & Food
Sagre, feste del vino, street food
⛪ Tradizioni
Feste patronali, processioni
🏃 Sport
Gare, tornei, eventi sportivi

📖 FE-T-06 - Guide Città

FE_TURISTI_GUIDE

URL Patterns

/guide/{citta}
Guida pubblica città (es. /guide/taormina)
/guide/host/{token}
Guida personalizzata host (link unico)
/guide/{citta}#sezione
Anchor a sezione specifica

Wireframe Pagina Guida

📋 Layout Desktop
┌─────────────────────────────────────────────────────────────┐
│  HEADER                                                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  HERO IMAGE (Full width, parallax)                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                                                      │    │
│  │              📷 TAORMINA                             │    │
│  │         "La perla del Mediterraneo"                  │    │
│  │                                                      │    │
│  │    ☀️ 25°C  •  🏖️ Mare  •  🏛️ Cultura              │    │
│  │                                                      │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  QUICK NAV (sticky on scroll)                               │
│  [Cosa vedere] [Dove mangiare] [Dove dormire] [Esperienze] │
│  [Come arrivare] [Consigli]                                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  INTRO                                                       │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ Taormina è una delle destinazioni più iconiche...   │    │
│  │ [Leggi tutto]                                        │    │
│  │                                                      │    │
│  │ 📊 INFO RAPIDE                                       │    │
│  │ ├── Popolazione: 10.800                             │    │
│  │ ├── Altitudine: 204m s.l.m.                         │    │
│  │ ├── Provincia: Messina                              │    │
│  │ └── Periodo migliore: Apr-Giu, Set-Ott             │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  COSA VEDERE                                    [Vedi tutti] │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │     📷       │ │     📷       │ │     📷       │        │
│  │ Teatro Antico│ │ Isola Bella  │ │ Corso Umberto│        │
│  │ ⭐ Must see  │ │ 🏖️ Spiaggia │ │ 🛍️ Shopping │        │
│  │ ~2 ore       │ │ ~4 ore       │ │ ~2 ore       │        │
│  └──────────────┘ └──────────────┘ └──────────────┘        │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  DOVE MANGIARE                                  [Vedi tutti] │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 🍝 Ristorante Da Nino    ⭐4.8   €€€   Pesce       │    │
│  │ 🍕 Pizzeria Bella Vista  ⭐4.6   €     Pizza       │    │
│  │ 🍷 Osteria del Teatro    ⭐4.9   €€€€  Fine dining │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  DOVE DORMIRE                                   [Vedi tutti] │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │     📷       │ │     📷       │ │     📷       │        │
│  │ Villa Etna   │ │ B&B Centro   │ │ Hotel Lux    │        │
│  │ da €120/notte│ │ da €85/notte │ │ da €200/notte│        │
│  │    [Prenota] │ │    [Prenota] │ │    [Prenota] │        │
│  └──────────────┘ └──────────────┘ └──────────────┘        │
│  * Link con tracking cross-referral se da guida host       │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  ESPERIENZE DA FARE                             [Vedi tutte] │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │     📷       │ │     📷       │ │     📷       │        │
│  │ Tour Etna    │ │ Giro in barca│ │ Cooking class│        │
│  │ da €65/pers  │ │ da €45/pers  │ │ da €89/pers  │        │
│  │    [Prenota] │ │    [Prenota] │ │    [Prenota] │        │
│  └──────────────┘ └──────────────┘ └──────────────┘        │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  MAPPA INTERATTIVA                                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                                                      │    │
│  │     🏛️ Teatro        🏖️ Isola Bella               │    │
│  │           📍                📍                       │    │
│  │                   📍 Centro                         │    │
│  │        📍 Ristoranti    📍 Hotel                    │    │
│  │                                                      │    │
│  │  [Filtri: ☑️ Attrazioni ☑️ Ristoranti ☑️ Hotel]    │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  COME ARRIVARE                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ ✈️ AEREO                                            │    │
│  │    Catania Fontanarossa (CTA) - 50 km, ~1 ora      │    │
│  │                                                      │    │
│  │ 🚂 TRENO                                            │    │
│  │    Stazione Taormina-Giardini + bus/funivia        │    │
│  │                                                      │    │
│  │ 🚗 AUTO                                             │    │
│  │    A18 Messina-Catania, uscita Taormina            │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  METEO & QUANDO ANDARE                                      │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic   │    │
│  │  ░░░ ░░░ ██░ ███ ███ ███ ██░ ██░ ███ ███ ██░ ░░░   │    │
│  │                                                      │    │
│  │  ███ Ideale  ██░ Buono  ░░░ Bassa stagione         │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│  CONSIGLI PRATICI                                           │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ 💡 Evita agosto: troppo affollato e caro           │    │
│  │ 💡 Porta scarpe comode: strade in salita           │    │
│  │ 💡 Prenota ristoranti in anticipo in alta stagione │    │
│  │ 💡 La funivia è il modo migliore per Isola Bella   │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Guide Personalizzate Host

Come funziona:
1. Host crea guida nel suo dashboard selezionando contenuti
2. Aggiunge raccomandazioni personali per ogni sezione
3. Inserisce esperienze dalla piattaforma (attiva cross-referral)
4. Genera link unico con token tracciabile
5. Link inviato automaticamente con email conferma prenotazione
6. Tracking visite e conversioni visibile nel dashboard host

Differenze Guida Pubblica vs Host

Feature Guida Pubblica Guida Host
Contenuto Editoriale piattaforma Personalizzato dall'host
Accesso Pubblico, indicizzato SEO Link privato con token
Cross-referral No Sì (3-5% su prenotazioni)
Analytics Solo admin Visibili all'host
Branding Sicily Experience "Consigliato da [Nome Host]"

Stati UI

🔄 Loading
Skeleton loader per ogni sezione, hero con blur placeholder
📭 Empty
"Nessun ristorante ancora recensito" + CTA suggerisci
❌ Error
Toast non bloccante, retry automatico per mappa
📴 Offline
Contenuto cached PWA, badge "Offline" su mappa

Test Cases

💳 FE-T-07 - Checkout

FE_TURISTI_CHECKOUT

Flusso Checkout

1
Riepilogo

Verifica dettagli prenotazione, date, ospiti, prezzo

2
Dati Ospite

Nome, email, telefono, richieste speciali

3
Dati Documenti

Per Payturist: documento, data nascita, nazionalità (per ogni ospite)

4
Pagamento

Stripe checkout, scelta totale o deposito

5
Conferma

Thank you page, email conferma, codice prenotazione

Soft-Lock Timer

Durante il checkout, le date sono bloccate per 15 minuti. Un timer visibile mostra il tempo rimanente. Allo scadere, le date vengono rilasciate.

Pagina Conferma

👤 FE-T-08 - Area Utente

FE_TURISTI_ACCOUNT

Sezioni Account

URL Sezione Contenuto
/account Dashboard Overview, prossime prenotazioni, notifiche
/account/prenotazioni Le mie prenotazioni Lista prenotazioni (attive, passate, cancellate)
/account/prenotazioni/{id} Dettaglio prenotazione Info complete, modifica, cancella, contatta host
/account/wishlist Preferiti Strutture ed esperienze salvate
/account/messaggi Messaggi Conversazioni con host/business
/account/recensioni Le mie recensioni Recensioni lasciate e da lasciare
/account/profilo Profilo Dati personali, foto, preferenze
/account/sicurezza Sicurezza Password, 2FA, sessioni attive
/account/notifiche Notifiche Preferenze email/push/sms

Dettaglio Prenotazione

⚠️ FE-T-09 - Pagine Errore

FE_TURISTI_ERRORI

Pagine Errore Standard

Codice Nome Quando Azioni Utente
404 Pagina non trovata URL inesistente, struttura rimossa Ricerca, Homepage, Contatti
403 Accesso negato Risorsa protetta, permessi insufficienti Login, Homepage
500 Errore server Bug, database down Riprova, Contatti, Status page
503 Manutenzione Deploy, manutenzione programmata Info durata, Status page

Wireframe 404

📋 Layout Pagina 404
┌─────────────────────────────────────────────────────────────┐
│  HEADER (standard)                                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      🏝️                                     │
│                                                             │
│              Oops! Pagina non trovata                       │
│                                                             │
│    La pagina che cerchi potrebbe essere stata spostata     │
│            o non esiste più. Ma non preoccuparti,          │
│              la Sicilia ti aspetta ancora!                  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  🔍 Cerca la tua destinazione                       │   │
│  │  [                                        ] [Cerca] │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│         [🏠 Torna alla Homepage]    [📞 Contattaci]        │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  DESTINAZIONI POPOLARI                                      │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│  │ Taormina │ │ Palermo  │ │ Siracusa │ │ Cefalù   │      │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘      │
├─────────────────────────────────────────────────────────────┤
│  FOOTER                                                     │
└─────────────────────────────────────────────────────────────┘

Wireframe 500

📋 Layout Pagina 500
┌─────────────────────────────────────────────────────────────┐
│  HEADER (minimal - solo logo)                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      ⚠️                                     │
│                                                             │
│              Qualcosa è andato storto                       │
│                                                             │
│      Stiamo lavorando per risolvere il problema.           │
│              Riprova tra qualche minuto.                    │
│                                                             │
│              [🔄 Riprova]   [🏠 Homepage]                   │
│                                                             │
│  ─────────────────────────────────────────────────────────  │
│                                                             │
│    📧 Se il problema persiste, contattaci:                 │
│       support@sicilyexperience.com                          │
│                                                             │
│    📊 Stato servizi: status.sicilyexperience.com           │
│                                                             │
│  ─────────────────────────────────────────────────────────  │
│                                                             │
│    Codice errore: {{error_id}}                             │
│    (comunicalo al supporto per assistenza più rapida)      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Wireframe Manutenzione

📋 Layout Pagina 503
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│                     🔧                                      │
│                                                             │
│            Sicily Experience in manutenzione                │
│                                                             │
│       Stiamo migliorando il servizio per te.               │
│         Torneremo online a breve!                           │
│                                                             │
│         ┌─────────────────────────────┐                    │
│         │  Tempo stimato: ~30 minuti  │                    │
│         │  Inizio: 02:00              │                    │
│         │  Fine prevista: 02:30       │                    │
│         └─────────────────────────────┘                    │
│                                                             │
│    📧 Per urgenze: emergency@sicilyexperience.com          │
│                                                             │
│    ────────────────────────────────────                    │
│                                                             │
│    Seguici per aggiornamenti:                              │
│    [Twitter] [Facebook] [Instagram]                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Empty States

📋 Catalogo Empty States
Contesto Icona Messaggio CTA
Ricerca senza risultati 🔍 Nessuna struttura trovata per questi criteri [Modifica filtri] [Vedi tutte]
Wishlist vuota ❤️ Non hai ancora salvato nessuna struttura [Esplora strutture]
Prenotazioni vuote 📅 Non hai prenotazioni. Inizia a esplorare! [Cerca destinazione]
Messaggi vuoti 💬 Nessun messaggio. Prenota per contattare un host! [Esplora strutture]
Recensioni da scrivere vuote Nessuna recensione in sospeso -
Notifiche vuote 🔔 Nessuna notifica -
Host: Nessuna prenotazione 📊 Nessuna prenotazione ancora. Ottimizza il tuo annuncio! [Migliora annuncio]
Host: Calendario vuoto 📅 Nessuna prenotazione per questo periodo [Imposta promozione]

Logging Errori

Ogni errore 500 deve:
- Generare ID univoco (visibile all'utente)
- Loggare stack trace completo su Sentry
- Notificare team dev via Slack se critico
- Salvare contesto utente (anonimizzato) per debug

Test Cases

🏢 Frontend Partner - Overview

FE_PARTNER_OVERVIEW

Caratteristiche

🖥️
Desktop-First
60%+ uso da desktop. Layout con sidebar, tabelle dati, form complessi.
📊
Data-Driven
Dashboard con KPI, grafici, analytics. Decisioni informate.
Efficiente
Azioni frequenti sempre visibili. Shortcuts, bulk actions.
🎓
Guidato
Onboarding step-by-step. Help contestuale. Complessità progressiva.

Struttura Dashboard

/partner → Landing vendita /partner/registrazione → Onboarding /partner/login → Login /partner/dashboard → Dashboard principale ├── /calendario → Vista calendario ├── /prenotazioni → Gestione prenotazioni ├── /struttura → Gestione struttura │ ├── /profilo → Dati struttura │ ├── /camere → Gestione camere │ ├── /prezzi → Tariffe │ └── /foto → Gallery ├── /guida → Editor guida personalizzata ├── /messaggi → Chat con ospiti ├── /recensioni → Gestione recensioni ├── /wallet → Saldo e movimenti ├── /referral → Invita e guadagna ├── /analytics → Report e statistiche └── /impostazioni → Configurazioni

Dashboard Host - Wireframe

┌─────────────────────────────────────────────────────────────────────────────┐ │ Sicily Experience Partner 👤 Mario ▼ │ ├────────────┬────────────────────────────────────────────────────────────────┤ │ │ │ │ 🏠 Home │ Buongiorno Mario! 👋 │ │ │ │ │ 📅 Calen. │ ┌────────────────────────────────────────────────────────┐ │ │ │ │ OGGI: 3 arrivi · 2 partenze · €450 revenue │ │ │ 📋 Preno. │ └────────────────────────────────────────────────────────┘ │ │ │ │ │ 🏨 Strutt.│ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ │ OCCUPAZIONE 7GG │ │ REVENUE MESE │ │ │ 📖 Guida │ │ ████████░░░░ 67% │ │ €3.450 (+12% vs mese scorso)│ │ │ │ └─────────────────────┘ └─────────────────────────────┘ │ │ 💬 Msg (3)│ │ │ │ PROSSIMI ARRIVI AZIONI RICHIESTE │ │ ⭐ Recens.│ ┌─────────────────────────────┐ ┌───────────────────┐ │ │ │ │ Rossi - Oggi 15:00 │ │ ⚠️ 3 msg non letti│ │ │ 💰 Wallet │ │ Bianchi - Oggi 18:00 │ │ ⚠️ 1 recensione │ │ │ │ │ Verdi - Domani 14:00 │ │ ⚠️ Rinnovo 5gg │ │ │ 📊 Stats │ └─────────────────────────────┘ └───────────────────┘ │ │ │ │ │ ⚙️ Imposta│ CALENDARIO SETTIMANALE │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ ─────────│ │ L M M G V S D │ │ │ ❓ Aiuto │ │ Cam101 ██ ██ ░░ ░░ ██ ██ ██ │ │ │ │ │ Cam102 ░░ ░░ ██ ██ ██ ░░ ░░ │ │ │ │ │ Suite ░░ ░░ ░░ ░░ ░░ ██ ██ │ │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────┴────────────────────────────────────────────────────────────────┘

🚀 FE-P-01 - Landing & Registrazione

FE_PARTNER_LANDING

URL

partner.sicilyexperience.com/

Obiettivo

Convincere host e business a registrarsi. Spiegare vantaggi, mostrare piani tariffari, facilitare onboarding.

Sezioni Landing Page

📋 Struttura
1. HERO
   - Headline: "Fai crescere la tua attività con Sicily Experience"
   - Subheadline: "Raggiungi migliaia di turisti, automatizza la burocrazia"
   - CTA: [Inizia Gratis] [Scopri di più]

2. PROBLEMA/SOLUZIONE
   - I problemi degli host oggi (burocrazia, visibilità, pagamenti)
   - Come li risolviamo

3. FEATURE PRINCIPALI
   - Automazione Payturist (Alloggiati, ISTAT, Tassa soggiorno)
   - Pagamenti sicuri e garantiti
   - Visibilità su turisti internazionali
   - Dashboard gestionale completa
   - Cross-referral per guadagni extra

4. COME FUNZIONA
   - Step 1: Registrati gratis
   - Step 2: Aggiungi la tua struttura
   - Step 3: Ricevi prenotazioni

5. PIANI TARIFFARI
   - Tabella comparativa Base/Standard/Premium
   - Trial 14 giorni gratuito

6. TESTIMONIANZE
   - Quote da host esistenti

7. FAQ

8. CTA FINALE
   - [Registrati Ora - È Gratis]

Form Registrazione

1
Tipo Account

Host (struttura) o Business (esperienze)?

2
Dati Personali

Nome, email, password, telefono

3
Verifica Email

Link di conferma

4
Onboarding Guidato

Wizard per completare profilo e prima struttura

🏠 FE-P-02 - Dashboard Host

FE_PARTNER_DASHBOARD_HOST

Menu Laterale

🏠 Home
Dashboard overview
📅 Calendario
Vista calendario prenotazioni
📋 Prenotazioni
Lista prenotazioni
🏨 Strutture
Gestione strutture e unità
💰 Tariffe
Prezzi e regole stagionali
📖 Guide
Guide personalizzate ospiti
💬 Messaggi
Chat con ospiti
⭐ Recensioni
Recensioni ricevute
💎 Wallet
Crediti e referral
📊 Analytics
Statistiche e report
⚙️ Impostazioni
Profilo, abbonamento, Stripe

Dashboard Home - Widget

Gestione Struttura

🏪 FE-P-03 - Dashboard Business

FE_PARTNER_DASHBOARD_BUSINESS

Differenze da Host

Il dashboard business è simile a quello host ma focalizzato sulla gestione di offerte/esperienze invece di strutture e unità.

Menu Specifico Business

🎯 Offerte
Catalogo esperienze/prodotti
📅 Calendario
Disponibilità per data/ora
📦 Ordini
Prenotazioni esperienze
👥 Clienti
Database clienti

Gestione Offerta

🤝 FE-P-04 - Dashboard Ambassador

FE_PARTNER_DASHBOARD_AMBASSADOR

Obiettivo

Permettere agli ambassador di gestire i clienti acquisiti, attivare trial, monitorare commissioni e richiedere payout.

Sezioni Dashboard

📊 Overview
KPI, commissioni mese, conversioni
👥 Clienti
Lista clienti acquisiti
🎁 Trial
Attivazione trial, status
💰 Commissioni
Storico commissioni
💳 Payout
Richiedi pagamento
🔗 Link Referral
Link personalizzati

Funzionalità Chiave

Widget Overview

📋 Metriche
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ CLIENTI ATTIVI │ │ TRIAL ATTIVI   │ │ CONVERSIONI    │
│      23        │ │       5/10     │ │      78%       │
└────────────────┘ └────────────────┘ └────────────────┘

┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ COMM. MESE     │ │ COMM. TOTALI   │ │ SALDO ATTUALE  │
│    €345        │ │    €2.890      │ │    €195        │
└────────────────┘ └────────────────┘ └────────────────┘

📅 FE-P-05 - Calendario

FE_PARTNER_CALENDARIO

Viste Calendario

Mese
Vista classica mensile
Settimana
Dettaglio settimanale
Timeline
Gantt-style per multi-unità

Funzionalità

Wireframe Timeline

📋 Vista Multi-Unità
         │ 1  2  3  4  5  6  7  8  9  10 11 12 13 14 │
─────────┼────────────────────────────────────────────┤
Camera 1 │ ██ ██ ██ ██ ░░ ░░ ░░ ██ ██ ██ ██ ██ ░░ ░░ │
         │    Rossi        │      Bianchi            │
─────────┼────────────────────────────────────────────┤
Camera 2 │ ░░ ░░ ██ ██ ██ ██ ██ ░░ ░░ ░░ ██ ██ ██ ██ │
         │      Verdi            │    Neri           │
─────────┼────────────────────────────────────────────┤
Suite    │ ░░ ░░ ░░ ░░ ░░ ██ ██ ██ ██ ░░ ░░ ░░ ░░ ░░ │
         │               │ Russo  │                  │
─────────┴────────────────────────────────────────────┘

Legenda: ██ Occupato │ ░░ Disponibile │ ▓▓ Bloccato

💎 FE-P-06 - Wallet & Referral

FE_PARTNER_WALLET

Overview Wallet

📋 Layout
┌─────────────────────────────────────────────────────────────┐
│  IL TUO WALLET                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────┐  ┌─────────────────┐                   │
│  │ SALDO DISPONIBILE│  │ IN ARRIVO       │                   │
│  │     €127,50      │  │     €45,00      │                   │
│  │  [Preleva]       │  │  (entro 7gg)    │                   │
│  └─────────────────┘  └─────────────────┘                   │
│                                                              │
│  GUADAGNI TOTALI                                            │
│  ┌────────────────────────────────────────────────────────┐ │
│  │ Peer Referral:     €234,00                             │ │
│  │ Cross-Referral:    €89,50                              │ │
│  │ Bonus:             €25,00                              │ │
│  │ ─────────────────────────────────────────────────────  │ │
│  │ Totale guadagnato: €348,50                             │ │
│  │ Totale prelevato:  €221,00                             │ │
│  └────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  USA IL TUO CREDITO                                         │
│                                                              │
│  ○ Preleva su conto (min €25)                               │
│  ○ Rinnova abbonamento (sconto extra 5%)                    │
│                                                              │
│  [Continua]                                                  │
└─────────────────────────────────────────────────────────────┘

Sezione Referral

Cross-Referral (per Host)

Come funziona:
1. Inserisci esperienze nella tua guida personalizzata
2. L'ospite prenota un'esperienza dalla guida
3. Guadagni 3-5% sul valore dell'esperienza
4. Il credito appare nel wallet dopo il completamento

Prelievo

Minimo
€25
Metodi
PayPal, Bonifico IBAN, Stripe
Tempistiche
3-5 giorni lavorativi

⚙️ Frontend Admin - Overview

FE_ADMIN_OVERVIEW

Accesso

sicilyexperience.com/wp-admin/ + pagine custom in /admin/

Ruoli Admin

Ruolo Permessi
Owner Accesso completo, crea VIP, modifica Owner
Admin Tutto tranne creare VIP e modificare Owner
Moderator Approvazione contenuti, gestione dispute
Support Solo lettura, risposta ticket

Menu Admin Custom

📊 Dashboard
KPI piattaforma, grafici
👥 Utenti
Gestione tutti gli utenti
🏨 Strutture
Moderazione strutture
📋 Prenotazioni
Tutte le prenotazioni
💰 Finanza
Transazioni, payout, commissioni
⚠️ Dispute
Overbooking, no-show, reclami
⚙️ Configurazioni
Parametri piattaforma
📤 Export
Export contabilità

📊 FE-A-01 - Dashboard Admin

FE_ADMIN_DASHBOARD

KPI Principali

GMV
Gross Merchandise Value (oggi, settimana, mese)
Revenue
Commissioni incassate
Prenotazioni
Nuove, confermate, completate
Utenti
Nuovi, attivi, churn
Strutture
Attive, in revisione, sospese
Trial
Attivi, conversioni, scaduti

Grafici

Alert e Azioni

⚙️ FE-A-02 - Configurazioni

FE_ADMIN_CONFIG

Parametri Configurabili

📋 Lista Completa

Commissioni

  • Commissione alloggi per piano (Base/Standard/Premium)
  • Commissione esperienze per piano
  • Commissione prodotti
  • Commissione eventi

Referral

  • % peer referral prima sottoscrizione
  • % peer referral rinnovi
  • Durata peer referral (mesi)
  • % cross-referral per piano

Ambassador

  • % commissione prima sottoscrizione
  • % commissione rinnovi
  • % commissione transazioni
  • Durata relazione (mesi)
  • Max trial contemporanei
  • Max durata trial (giorni)

Trial

  • Durata trial standard (giorni)
  • Piano durante trial

Payout

  • Minimo payout host/business
  • Minimo payout ambassador
  • Minimo prelievo wallet

Policy

  • Timeout conferma manuale (ore)
  • Soft-lock checkout (minuti)
  • Giorni anticipo saldo
  • Penalità overbooking

History Tracking

Ogni modifica di configurazione viene loggata con: admin_id, timestamp, valore precedente, nuovo valore. Possibilità di rollback.

🛡️ FE-A-03 - Moderazione

FE_ADMIN_MODERAZIONE

Code di Moderazione

🏨 Strutture
Nuove strutture da approvare
🎯 Offerte
Nuove esperienze da approvare
🎉 Eventi
Eventi da pubblicare
⭐ Recensioni
Recensioni flaggate
💬 Messaggi
Messaggi segnalati
📷 Media
Foto/video segnalati

Gestione Dispute

Azioni su Utenti

Gestione Forza Maggiore

🚀 Roadmap - Fase MVP

ROADMAP_MVP

Obiettivo MVP

Lanciare una versione funzionante della piattaforma con le feature essenziali per validare il modello di business prima della stagione turistica estiva 2026.

Timeline Stimata

Sprint 1: Fondamenta 4 settimane
Sprint 2: Booking Core 4 settimane
Sprint 3: Pagamenti 3 settimane
Sprint 4: Integrazioni 3 settimane
Sprint 5: Frontend 4 settimane
Sprint 6: Test & Launch 2 settimane

Sprint 1: Fondamenta (4 settimane)

Sprint 2: Booking Core (4 settimane)

Sprint 3: Pagamenti (3 settimane)

Sprint 4: Integrazioni (3 settimane)

Sprint 5: Frontend (4 settimane)

Sprint 6: Test & Launch (2 settimane)

Feature NON in MVP (Fase 2)

⏳ Rimandate a Fase 2
  • Business e esperienze
  • Eventi
  • Guide personalizzate complete
  • Sistema recensioni
  • Messaggistica interna
  • Ambassador e referral
  • Wallet
  • Analytics avanzati
  • Channel manager (oltre iCal)
  • App nativa

🖥️ Deployment - Requisiti Server

DEPLOY_REQUISITI

Server Specifications

Componente Minimo Raccomandato
CPU 2 vCPU 4 vCPU
RAM 4 GB 8 GB
Storage 50 GB SSD 100 GB NVMe SSD
Banda 1 TB/mese Illimitata

Software Stack

OS
Ubuntu 22.04 LTS
Web Server
Nginx 1.24+
PHP
PHP 8.1+ con FPM
Database
MySQL 8.0 o MariaDB 10.6+
Cache
Redis 7+
SSL
Let's Encrypt (auto-renewal)

PHP Extensions Richieste

curl, gd, intl, mbstring, mysqli, openssl, redis, soap, xml, zip, imagick

🚀 Deployment - Procedura

DEPLOY_PROCEDURA

Ambiente Staging

URL: staging.sicilyexperience.com
Scopo: Test prima di ogni deploy in produzione
Database: Copia anonimizzata della produzione

Checklist Pre-Deploy

Procedura Deploy

📋 Script Deploy
#!/bin/bash
# deploy.sh

# 1. Maintenance mode
wp maintenance-mode activate

# 2. Pull latest code
git pull origin main

# 3. Install dependencies
composer install --no-dev --optimize-autoloader

# 4. Run migrations
wp se migrate:run

# 5. Clear caches
wp cache flush
wp transient delete --all
redis-cli FLUSHDB

# 6. Optimize
wp rewrite flush
wp se optimize:images

# 7. Disable maintenance
wp maintenance-mode deactivate

# 8. Verify
curl -s https://sicilyexperience.com/health | grep "ok"

echo "Deploy completato!"

💾 Deployment - Backup Strategy

DEPLOY_BACKUP

Frequenza Backup

Tipo Frequenza Retention Storage
Database completo Ogni 6 ore 30 giorni S3 + locale
Database incrementale Ogni ora 7 giorni Locale
Files (uploads) Giornaliero 30 giorni S3
Configurazioni Ad ogni modifica 90 giorni Git + S3

Disaster Recovery

RTO (Recovery Time Objective): 4 ore
RPO (Recovery Point Objective): 1 ora
Procedura: Documentata in runbook interno

📡 Deployment - Monitoring

DEPLOY_MONITORING

Strumenti

Uptime
UptimeRobot - check ogni 5 min
Errors
Sentry - error tracking
APM
Query Monitor + custom logging
Server
Netdata - metriche server

Allarmi

Evento Soglia Canale
Site down 2 check falliti SMS + Email
Response time > 3 secondi Email
Error rate > 5% richieste Slack + Email
Disk space > 80% usato Email
SSL expiry < 14 giorni Email

VAL_01 - Validazione Input

VAL_01_VALIDAZIONE

Principi Fondamentali

Mai fidarsi del client: Validazione client-side per UX, ma SEMPRE ri-validare server-side.
Fail fast: Restituire tutti gli errori insieme, non uno alla volta.
Messaggi chiari: Spiegare cosa è sbagliato e come correggerlo.

Regole Validazione Campi

📋 Tabella Regole Complete
Campo Tipo Regole Sanitizzazione Messaggio Errore
email email required, valid email, max 255 sanitize_email(), lowercase Inserisci un indirizzo email valido
password password required, min 8, 1 maiuscola, 1 numero Nessuna (hash dopo) Min 8 caratteri, una maiuscola e un numero
telefono tel optional, pattern italiano Rimuovi spazi, solo numeri e + Formato: +39 333 1234567
codice_fiscale text required (host), 16 chars, pattern CF uppercase, trim Codice fiscale non valido
partita_iva text optional, 11 cifre, check digit Solo numeri Partita IVA non valida
iban text optional, IBAN format, checksum uppercase, rimuovi spazi IBAN non valido
prezzo number required, min 0, max 99999.99 floatval(), round 2 Inserisci un prezzo valido
data date required, Y-m-d, non passata DateTime::createFromFormat Data non valida o nel passato
cap text required, 5 cifre Solo numeri CAP deve essere di 5 cifre
descrizione textarea required, min 50, max 5000 wp_kses_post() Tra 50 e 5000 caratteri

Classe Validatore PHP

📋 SE_Validator Class
class SE_Validator {
    private $errors = [];
    private $data = [];
    
    public function __construct(array $data) {
        $this->data = $data;
    }
    
    public function required(string $field, string $msg = null): self {
        if (empty($this->data[$field])) {
            $this->errors[$field][] = $msg ?? "Campo obbligatorio";
        }
        return $this;
    }
    
    public function email(string $field): self {
        if (!empty($this->data[$field]) && !is_email($this->data[$field])) {
            $this->errors[$field][] = "Email non valida";
        }
        return $this;
    }
    
    public function min(string $field, int $length): self {
        if (!empty($this->data[$field]) && strlen($this->data[$field]) < $length) {
            $this->errors[$field][] = "Minimo {$length} caratteri";
        }
        return $this;
    }
    
    public function max(string $field, int $length): self {
        if (!empty($this->data[$field]) && strlen($this->data[$field]) > $length) {
            $this->errors[$field][] = "Massimo {$length} caratteri";
        }
        return $this;
    }
    
    public function codice_fiscale(string $field): self {
        if (!empty($this->data[$field])) {
            $cf = strtoupper(trim($this->data[$field]));
            $pattern = '/^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$/';
            if (!preg_match($pattern, $cf)) {
                $this->errors[$field][] = "Codice fiscale non valido";
            }
        }
        return $this;
    }
    
    public function partita_iva(string $field): self {
        if (!empty($this->data[$field])) {
            $piva = preg_replace('/[^0-9]/', '', $this->data[$field]);
            if (strlen($piva) !== 11) {
                $this->errors[$field][] = "Partita IVA non valida";
            }
        }
        return $this;
    }
    
    public function future_date(string $field): self {
        if (!empty($this->data[$field])) {
            $date = new DateTime($this->data[$field]);
            if ($date < new DateTime('today')) {
                $this->errors[$field][] = "Data non può essere nel passato";
            }
        }
        return $this;
    }
    
    public function passes(): bool { return empty($this->errors); }
    public function fails(): bool { return !$this->passes(); }
    public function errors(): array { return $this->errors; }
}

// Utilizzo
$validator = new SE_Validator($_POST);
$validator
    ->required('email')->email('email')
    ->required('nome')->min('nome', 2)->max('nome', 100)
    ->required('check_in')->future_date('check_in');

if ($validator->fails()) {
    wp_send_json_error(['errors' => $validator->errors()], 422);
}

Validazione JavaScript Client-Side

📋 Real-time Validation
// HTML: <input data-validate="required|email|max:255">
document.querySelectorAll('input[data-validate]').forEach(input => {
    input.addEventListener('blur', () => validateField(input));
});

function validateField(input) {
    const rules = input.dataset.validate.split('|');
    const value = input.value.trim();
    let error = null;
    
    for (const rule of rules) {
        const [name, param] = rule.split(':');
        switch(name) {
            case 'required':
                if (!value) error = 'Campo obbligatorio';
                break;
            case 'email':
                if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
                    error = 'Email non valida';
                break;
            case 'min':
                if (value.length < parseInt(param))
                    error = `Minimo ${param} caratteri`;
                break;
            case 'max':
                if (value.length > parseInt(param))
                    error = `Massimo ${param} caratteri`;
                break;
        }
        if (error) break;
    }
    
    showFieldError(input, error);
    return !error;
}

Test Cases

A11Y - Accessibilità

A11Y_ACCESSIBILITA

Standard di Riferimento

Target: WCAG 2.1 Level AA
Legge: Conformità L. 4/2004 (Legge Stanca) e Direttiva UE 2016/2102
Tools: WAVE, aXe, Lighthouse Accessibility

Checklist WCAG

📋 Perceivable (Percepibile)
  • Tutte le immagini hanno alt text descrittivo
  • Video hanno sottotitoli
  • Contrasto minimo 4.5:1 per testo normale
  • Contrasto minimo 3:1 per testo grande (>18px bold)
  • Contenuto comprensibile senza colore
  • Testo ridimensionabile al 200% senza perdita
📋 Operable (Utilizzabile)
  • Tutto navigabile da tastiera (Tab, Enter, Esc)
  • Focus visibile su tutti gli elementi interattivi
  • Skip link "Vai al contenuto" come primo elemento
  • Nessun contenuto lampeggiante >3 volte/secondo
  • Timeout estendibili (es. soft-lock checkout)
  • Breadcrumb e navigazione consistente
📋 Understandable (Comprensibile)
  • Lingua pagina dichiarata (lang="it")
  • Label form associate a input (for/id)
  • Errori form identificati e descritti
  • Link descrittivi (no "clicca qui")
  • Navigazione consistente tra pagine

Pattern ARIA

📋 Esempi Implementazione
<!-- Skip Link -->
<a href="#main-content" class="skip-link">
    Vai al contenuto principale
</a>

<!-- Form con errore -->
<div class="form-group">
    <label for="email">Email *</label>
    <input type="email" id="email"
           aria-required="true"
           aria-invalid="true"
           aria-describedby="email-error">
    <span id="email-error" role="alert">
        Inserisci un'email valida
    </span>
</div>

<!-- Modal accessibile -->
<div role="dialog" 
     aria-modal="true" 
     aria-labelledby="modal-title">
    <h2 id="modal-title">Conferma prenotazione</h2>
    <button>Conferma</button>
    <button>Annulla</button>
</div>

<!-- Loading state -->
<button aria-busy="true" aria-live="polite">
    Caricamento...
</button>

Focus Styles CSS

📋 CSS Focus Management
/* Focus visible per keyboard users */
:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}

/* Rimuovi outline per mouse users */
:focus:not(:focus-visible) {
    outline: none;
}

/* Skip link */
.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: var(--primary);
    color: white;
    padding: 8px 16px;
    z-index: 10000;
}

.skip-link:focus {
    top: 0;
}

Contrasto Colori

Elemento Foreground Background Ratio Status
Body text #343a40 #ffffff 10.5:1 AAA
Primary button #ffffff #1a5f7a 5.2:1 AA
Error text #e74c3c #ffffff 4.6:1 AA

Test Cases

CACHE - Caching Strategy

CACHE_STRATEGY

Livelli di Cache

1
Browser Cache

Assets statici (CSS, JS, immagini)

2
CDN Cache

Cloudflare edge caching

3
Page Cache

HTML pagine (Nginx FastCGI)

4
Object Cache

Redis per query DB

TTL per Tipo di Dato

Dato Cache Key TTL Invalidation
Lista strutture (ricerca) se_search_{hash} 5 min Nuova prenotazione
Scheda struttura se_struttura_{id} 1 ora Modifica struttura
Disponibilità se_avail_{id}_{month} 1 min Prenotazione/blocco
Prezzi calcolati se_price_{id}_{dates} 5 min Modifica tariffe
Recensioni se_reviews_{id} 15 min Nuova recensione
Config piattaforma se_config 24 ore Modifica admin

Implementazione Redis

📋 SE_Cache Helper
class SE_Cache {
    
    // Get or set cache
    public static function remember(string $key, int $ttl, callable $cb) {
        $cached = wp_cache_get($key, 'se_cache');
        
        if ($cached !== false) {
            return $cached;
        }
        
        $value = $cb();
        wp_cache_set($key, $value, 'se_cache', $ttl);
        return $value;
    }
    
    // Generate cache key with params hash
    public static function key(string $prefix, array $params = []): string {
        return empty($params) 
            ? $prefix 
            : $prefix . '_' . md5(serialize($params));
    }
    
    // Flush by pattern
    public static function forget(string $pattern) {
        // Redis SCAN + DEL
    }
}

// Utilizzo
$struttura = SE_Cache::remember(
    SE_Cache::key('struttura', ['id' => 123]),
    HOUR_IN_SECONDS,
    fn() => $this->fetch_struttura(123)
);

// Invalidation hook
add_action('se_struttura_updated', function($id) {
    SE_Cache::forget("se_struttura_{$id}");
});

Pagine da NON Cachare

  • /checkout/* (dinamico, soft-lock)
  • /account/* (dati personali)
  • /wp-admin/*
  • Utenti con cookie se_logged_in
  • Risultati ricerca con query string

Test Cases

🔒 SEC_01 - Security Checklist

SEC_01_SECURITY

OWASP Top 10 Mitigation

📋 Checklist OWASP
Vulnerabilità Mitigazione Implementazione
A01: Broken Access Control Capability checks current_user_can()
A02: Cryptographic Failures HTTPS, password hashing wp_hash_password(), SSL
A03: Injection Prepared statements $wpdb->prepare()
A05: Security Misconfiguration Headers, wp-config hardened Security headers Nginx
A07: Auth Failures Rate limiting, 2FA Login lockout plugin

Security Headers

📋 Nginx Configuration
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Content Security Policy
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://js.stripe.com;
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
    img-src 'self' data: https:;
    font-src 'self' https://fonts.gstatic.com;
    connect-src 'self' https://api.stripe.com;
    frame-src https://js.stripe.com;
" always;

# HSTS
add_header Strict-Transport-Security "max-age=31536000" always;

SQL Injection Prevention

📋 Esempi Corretti
// ❌ SBAGLIATO - SQL Injection vulnerabile
$results = $wpdb->get_results(
    "SELECT * FROM se_bookings WHERE id = " . $_GET['id']
);

// ✅ CORRETTO - Prepared statement
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM se_bookings WHERE id = %d",
        intval($_GET['id'])
    )
);

XSS Prevention

📋 Output Escaping
// Output in HTML
echo esc_html($user_input);

// Output in attributi
echo '<input value="' . esc_attr($input) . '">';

// Output in URL
echo '<a href="' . esc_url($url) . '">';

// Output in JS
echo 'var data = ' . wp_json_encode($data) . ';';

// Rich text (tag sicuri)
echo wp_kses_post($content);

Rate Limiting

Login
5 tentativi / 5 minuti
Password Reset
3 / ora
API Request
100 / minuto
Booking Create
10 / ora

CSRF Protection

📋 Nonce Implementation
// Form
<form method="post">
    <?php wp_nonce_field('se_booking', 'se_nonce'); ?>
</form>

// Verifica
if (!wp_verify_nonce($_POST['se_nonce'] ?? '', 'se_booking')) {
    wp_send_json_error(['message' => 'Security check failed'], 403);
}

// AJAX
wp_localize_script('se-main', 'seAjax', [
    'nonce' => wp_create_nonce('se_ajax_nonce')
]);

File Upload Security

Regole upload:
- Whitelist: jpg, jpeg, png, gif, pdf
- Max size: 5MB immagini, 10MB PDF
- Verifica MIME type reale
- Rinomina con hash univoco
- Store fuori da web root

Test Cases

🔄 CI/CD - Pipeline

CI_CD_PIPELINE

Pipeline Overview

1
Commit

Push su branch

2
Lint & Test

PHPStan, ESLint, PHPUnit

3
Build

Compila assets

4
Deploy Staging

Auto su develop

5
Deploy Prod

Manual approval

GitHub Actions Workflow

📋 .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
      - run: composer install
      - run: vendor/bin/phpstan analyse --level=6
      - run: npm ci && npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: test_db
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
      - run: composer install
      - run: vendor/bin/phpunit --coverage-clover coverage.xml
      - uses: codecov/codecov-action@v3

  deploy-staging:
    needs: test
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.STAGING_HOST }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/staging
            git pull
            composer install --no-dev
            wp cache flush

  deploy-production:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PROD_HOST }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/production
            wp maintenance-mode activate
            git pull
            composer install --no-dev
            wp cache flush
            wp maintenance-mode deactivate

Branch Strategy

Branch Scopo Deploy Protezione
main Produzione Auto → Prod PR + approval + CI
develop Staging Auto → Staging PR + CI
feature/* Nuove feature Nessuno Nessuna
hotfix/* Fix urgenti Manuale Nessuna

Rollback Procedure

📋 Script Rollback
#!/bin/bash
# rollback.sh

DEPLOY_DIR="/var/www/production"
BACKUP_DIR="/var/backups/deploys"

echo "🔄 Rollback iniziato..."

# Maintenance mode
wp maintenance-mode activate --path=$DEPLOY_DIR

# Find last backup
BACKUP=$(ls -t $BACKUP_DIR/*.tar.gz | head -1)

# Restore
cd $DEPLOY_DIR
tar -xzf $BACKUP

# Clear cache
wp cache flush --path=$DEPLOY_DIR

# Exit maintenance
wp maintenance-mode deactivate --path=$DEPLOY_DIR

echo "✅ Rollback completato!"

Environment Variables

📋 .env.example
# Database
DB_NAME=sicily_experience
DB_USER=
DB_PASSWORD=
DB_HOST=localhost

# WordPress
WP_DEBUG=false

# Stripe
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Payturist
PAYTURIST_API_KEY=
PAYTURIST_ENV=sandbox

# Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# Sentry
SENTRY_DSN=

Test Cases

🚀 Roadmap - Fase 2

ROADMAP_FASE2

Timeline: Post-MVP (Q3-Q4 2026)

Dopo il lancio MVP e validazione mercato con le strutture ricettive.

Sprint 7-8: Business & Esperienze (6 settimane)

Sprint 9-10: Community Features (6 settimane)

Sprint 11-12: Monetization (4 settimane)

Sprint 13-14: Eventi & Promoter (4 settimane)

📋 Roadmap - Backlog Futuro

ROADMAP_BACKLOG

Fase 3: Scale (2027)

Features da Valutare

Tech Debt & Improvements

KPI Target Fase 3

Strutture attive
500+
Business attivi
200+
Prenotazioni/mese
2.000+
GMV annuo
€5M+