📋 Sicily Experience - Development Bible
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
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
Glossario Termini
| Termine | Definizione |
|---|---|
CPT | Custom Post Type - Tipo di contenuto personalizzato in WordPress |
ACF | Advanced Custom Fields - Plugin per campi personalizzati |
PMS | Property Management System - Sistema gestionale per strutture ricettive |
OTA | Online Travel Agency - Agenzia di viaggio online (Booking, Airbnb) |
CIN | Codice Identificativo Nazionale - Obbligatorio per strutture ricettive |
CIR | Codice Identificativo Regionale - Rilasciato dalla Regione Sicilia |
Alloggiati Web | Portale Polizia di Stato per comunicazione ospiti |
Turist@t | Osservatorio Turistico Regione Sicilia per dati ISTAT |
Split Payment | Divisione automatica pagamento tra piattaforma e fornitore |
Cross-Referral | Commissione su vendite generate tra host e business |
Peer Referral | Sistema inviti tra host/business con crediti wallet |
KYC | Know Your Customer - Verifica identità utenti |
GMV | Gross Merchandise Value - Valore totale transazioni |
LTV | Lifetime Value - Valore cliente nel tempo |
Churn Rate | Tasso di abbandono clienti |
Stack Tecnologico
Architettura Generale
🗄️ Database Schema
Il database di Sicily Experience combina le tabelle native di WordPress con tabelle custom ottimizzate per le funzionalità specifiche della piattaforma.
Tabelle WordPress Utilizzate
| Tabella | Utilizzo |
|---|---|
wp_posts | CPT: struttura, unita, business, offerta, evento, guida |
wp_postmeta | Metadati ACF per tutti i CPT |
wp_users | Utenti (turisti, host, business, ambassador, admin) |
wp_usermeta | Metadati utenti e ruoli custom |
wp_terms / wp_term_taxonomy | Tassonomie (tipologie, città, categorie) |
wp_options | Configurazioni globali piattaforma |
Tabelle Custom
Tutte le tabelle custom usano il prefisso se_ (Sicily Experience). Esempio: se_bookings
se_bookings - Prenotazioni
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
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
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
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
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)
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
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
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
| Tabella | Descrizione | Righe Stimate |
|---|---|---|
se_reviews | Recensioni turisti → strutture/business | 10K+ |
se_messages | Messaggi tra utenti | 50K+ |
se_notifications | Notifiche utenti | 100K+ |
se_cancellations | Storico cancellazioni | 1K+ |
se_overbooking_incidents | Incidenti overbooking | 100+ |
se_noshow_reports | Segnalazioni no-show | 500+ |
se_force_majeure_requests | Richieste forza maggiore | 200+ |
se_peer_referrals | Referral tra host/business | 5K+ |
se_cross_referrals | Cross-referral transazioni | 20K+ |
se_impersonation_logs | Log impersonation ambassador | 10K+ |
se_payouts | Payout a host/business/ambassador | 10K+ |
se_calendar_sync | Sync iCal con OTA | 5K+ |
Campi ACF per CPT
CPT: struttura
| Campo | Tipo | Descrizione |
|---|---|---|
tipologia | Select | hotel | b_and_b | casa_vacanza | appartamento | ostello |
modalita_affitto | Select | intera_unita | camere | entrambe |
indirizzo | Group | via, civico, cap, città, provincia |
coordinate | Google Map | Latitudine e longitudine |
descrizione_breve | Textarea | Max 200 caratteri |
descrizione_completa | WYSIWYG | Descrizione estesa |
gallery | Gallery | Immagini struttura |
servizi | Checkbox | WiFi, parcheggio, piscina, etc. |
lingue_parlate | Checkbox | Italiano, inglese, etc. |
orario_checkin | Time Picker | Orario check-in standard |
orario_checkout | Time Picker | Orario check-out standard |
cin | Text | Codice Identificativo Nazionale |
cir | Text | Codice Identificativo Regionale |
payturist_structure_id | Text | ID struttura su Payturist |
payturist_token | Text | Token API Payturist |
stripe_account_id | Text | ID account Stripe Connect |
piano_tariffario | Select | base | standard | premium |
commissione_override | Number | Commissione personalizzata (VIP) |
conferma_manuale | True/False | Richiede conferma prenotazioni |
capacita_max | Number | Solo se modalita = intera_unita |
prezzo_base | Number | Solo se modalita = intera_unita |
regole_prezzo | Repeater | Stagionalità, weekend, etc. |
politica_cancellazione | Select | non_rimborsabile | standard | flessibile | personalizzata |
owner_id | User | Proprietario/gestore |
CPT: unita (Camera/Appartamento)
| Campo | Tipo | Descrizione |
|---|---|---|
struttura_padre | Post Object | Riferimento a CPT struttura |
tipo_unita | Select | camera | appartamento | posto_letto |
tipologia_camera | Select | singola | doppia | tripla | quadrupla | suite | dormitorio |
capacita_min | Number | Minimo ospiti |
capacita_max | Number | Massimo ospiti |
num_letti | Repeater | Tipo letto + quantità |
superficie_mq | Number | Superficie in mq |
prezzo_base | Number | Prezzo per notte |
regole_prezzo | Repeater | Stagionalità, weekend, etc. |
servizi_camera | Checkbox | Bagno privato, AC, TV, etc. |
gallery | Gallery | Foto camera |
descrizione | Textarea | Descrizione unità |
CPT: business
| Campo | Tipo | Descrizione |
|---|---|---|
tipologia | Select | tour_operator | ristorante | noleggio | artigiano | negozio | servizi |
indirizzo | Group | via, civico, cap, città, provincia |
coordinate | Google Map | Latitudine e longitudine |
descrizione | WYSIWYG | Descrizione attività |
gallery | Gallery | Immagini |
orari_apertura | Repeater | Giorno + orario apertura/chiusura |
contatti | Group | Telefono, email, website, social |
stripe_account_id | Text | ID account Stripe Connect |
piano_tariffario | Select | base | standard | premium |
owner_id | User | Proprietario |
Diagramma Relazioni
👑 MOD_00 - Account Speciali & Trial
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
Sistema Trial
Trial Standard (Auto-registrazione)
Trial Ambassador
Configurazioni Trial
| Configurazione | Default | Descrizione |
|---|---|---|
| Durata trial standard Host | 14 giorni | Configurabile da backend |
| Durata trial standard Business | 14 giorni | Configurabile da backend |
| Piano durante trial | Premium | Accesso a tutte le funzionalità |
| Max durata trial Ambassador | 30 giorni | Limite massimo attivabile |
| Max trial attivi per Ambassador | 10 | Contemporaneamente |
| Reminder 7 giorni prima | Attivo | |
| Reminder 3 giorni prima | Attivo | Email + Push |
| Reminder 1 giorno prima | Attivo | Email + Push |
| Reminder scaduto | Attivo | Email + SMS |
Funzioni PHP Principali
/**
* 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
- Creazione account Owner iniziale funziona
- Owner può creare account VIP
- Admin NON può creare account VIP
- VIP ha commissione 0% (o personalizzata)
- Trial standard si attiva alla registrazione
- Trial scade correttamente dopo X giorni
- Reminder vengono inviati nei tempi corretti
- Ambassador può attivare trial per clienti
- Limiti trial Ambassador sono rispettati
- Conversione trial → piano viene tracciata
🔐 MOD_01 - Autenticazione
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
| Ruolo | Slug | Capabilities Principali |
|---|---|---|
| Turista | se_turista | read, se_book, se_review |
| Host | se_host | read, se_manage_struttura, se_view_bookings |
| Business | se_business | read, se_manage_business, se_manage_offerte |
| Promoter | se_promoter | read, se_manage_eventi |
| Ambassador | se_ambassador | read, se_manage_referrals, se_impersonate |
| Admin SE | se_admin | manage_options, se_moderate, se_configure |
Flussi di Registrazione
Registrazione Turista
Registrazione Host/Business
API Endpoints
| Metodo | Endpoint | Descrizione | Auth |
|---|---|---|---|
POST | /wp-json/sicily/v1/auth/register | Registrazione nuovo utente | No |
POST | /wp-json/sicily/v1/auth/login | Login | No |
POST | /wp-json/sicily/v1/auth/logout | Logout | Sì |
POST | /wp-json/sicily/v1/auth/forgot-password | Richiesta reset password | No |
POST | /wp-json/sicily/v1/auth/reset-password | Reset password con token | No |
GET | /wp-json/sicily/v1/auth/me | Dati utente corrente | Sì |
PUT | /wp-json/sicily/v1/auth/me | Aggiorna profilo | Sì |
POST | /wp-json/sicily/v1/auth/verify-email | Verifica email | No |
Codice Riferimento
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
- Registrazione turista con email valida
- Registrazione turista con email già esistente (errore)
- Registrazione host con dati aziendali
- Login con credenziali corrette
- Login con credenziali errate (errore)
- Reset password funzionante
- Verifica email funzionante
- Redirect corretto post-login per ogni ruolo
- Trial attivato automaticamente per host/business
- Referral code processato correttamente
🏠 MOD_02 - Strutture
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
Modalità di Affitto
Custom Post Type: struttura
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
| Metodo | Endpoint | Descrizione |
|---|---|---|
GET | /wp-json/sicily/v1/strutture | Lista strutture (con filtri) |
GET | /wp-json/sicily/v1/strutture/{id} | Dettaglio struttura |
POST | /wp-json/sicily/v1/strutture | Crea 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}/disponibilita | Verifica disponibilità |
GET | /wp-json/sicily/v1/strutture/{id}/prezzi | Calcola prezzi per date |
Test Cases
- Creazione struttura con tutti i campi obbligatori
- Validazione CIN/CIR formato corretto
- Upload gallery immagini
- Selezione servizi disponibili
- Configurazione orari check-in/check-out
- Pubblicazione struttura (stato draft → publish)
- Ricerca strutture per città
- Filtro strutture per tipologia
- Verifica permessi (solo owner può modificare)
- Collegamento corretto con Payturist
📅 MOD_10 - Prenotazioni
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
Flusso Creazione Prenotazione
Calcolo Prezzo
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
| Metodo | Endpoint | Descrizione |
|---|---|---|
POST | /wp-json/sicily/v1/bookings | Crea 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}/cancel | Cancella prenotazione |
POST | /wp-json/sicily/v1/bookings/{id}/confirm | Conferma manuale host |
POST | /wp-json/sicily/v1/bookings/{id}/checkin | Registra check-in |
GET | /wp-json/sicily/v1/bookings/calculate-price | Calcola prezzo |
Test Cases
- Creazione prenotazione con date valide
- Rifiuto prenotazione con date non disponibili
- Rifiuto se sotto minimo notti
- Calcolo prezzo base corretto
- Applicazione regole stagionalità
- Applicazione extra obbligatori
- Calcolo tassa soggiorno per comune
- Applicazione codice sconto
- Conferma manuale entro timeout
- Auto-rifiuto se timeout conferma
- Soft-lock calendario durante checkout (15 min)
- Invio notifiche email corrette
🛏️ MOD_03 - Unità/Camere
Scopo
Gestisce le singole unità abitabili all'interno di una struttura: camere d'hotel, appartamenti, posti letto in dormitori.
Tipi di Unità
Custom Post Type: unita
// 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)
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
- Creazione unità con tutti i campi
- Verifica relazione parent-child con struttura
- Cambio stato unità (attiva/manutenzione)
- Override prezzi rispetto a struttura
- Calcolo capacità totale struttura da somma unità
📅 MOD_04 - Calendario Disponibilità
Scopo
Gestisce disponibilità in tempo reale, blocchi manuali, sincronizzazione iCal con OTA (Booking, Airbnb), soft-lock durante checkout.
Logica Disponibilità
Modalità Camere: Ogni unità ha proprio calendario. Struttura disponibile se almeno 1 unità libera.
Database: se_availability
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
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
Trigger: Utente inizia checkout
Rilascio: Pagamento completato, timeout, o abbandono carrello
API Endpoints
// 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
- Verifica disponibilità per range date
- Blocco manuale singola data
- Blocco range date
- Import iCal da Booking.com
- Import iCal da Airbnb
- Export iCal per OTA
- Soft-lock durante checkout
- Rilascio soft-lock dopo timeout
- Conflitto quando due utenti tentano stesso periodo
💰 MOD_05 - Prezzi e Tariffe
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
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
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
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
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
- Calcolo prezzo base semplice
- Applicazione regola stagionale alta stagione
- Applicazione regola stagionale bassa stagione
- Markup weekend (ven-dom)
- Pricing per occupancy (1 ospite vs 4 ospiti)
- Override prezzo specifico giorno
- Combinazione multiple regole
- Calcolo extra obbligatorio pulizia
- Calcolo extra per notte (es. colazione)
- Calcolo extra per persona
- Tassa soggiorno Taormina
- Tassa soggiorno con max notti
- Esenzione bambini tassa soggiorno
🏪 MOD_06 - Business
Scopo
Gestione attività locali: tour operator, ristoranti, noleggi, artigiani, guide turistiche. Ogni business può pubblicare offerte/esperienze.
Tipologie Business
Custom Post Type: business
// 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)
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
- Registrazione nuovo business
- Attivazione trial automatica
- Compilazione profilo completo
- Upload licenze/certificazioni
- Onboarding Stripe Connect
- Cambio piano abbonamento
- Sospensione per mancato pagamento
🎯 MOD_07 - Offerte e Esperienze
Scopo
Gestione catalogo offerte dei business: esperienze, tour, prodotti, servizi. Sistema di prenotazione con disponibilità e varianti.
Tipologie Offerta
Custom Post Type: offerta
// 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
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
- Creazione esperienza con orari multipli
- Creazione prodotto con varianti
- Gestione disponibilità per data
- Calcolo prezzo con sconti
- Verifica posti disponibili
- Prenotazione che riduce posti
- Blocco prenotazione se esaurito
- Cancellazione che ripristina posti
🎉 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 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
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
- Creazione evento singolo
- Creazione evento multi-giorno
- Pagamento piano pubblicazione
- Approvazione admin
- Scadenza pubblicazione automatica
- Upgrade piano in corso
- Annullamento evento
- Tracking click biglietti
🔎 MOD_09 - Ricerca e Filtri
Scopo
Motore di ricerca unificato per strutture, esperienze, eventi. Filtri avanzati, geolocalizzazione, ordinamento.
Filtri Strutture
// 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
// 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
// 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
Bounding box: Pre-filtro su lat/lng per performance prima del calcolo esatto.
Test Cases
- Ricerca per destinazione testuale
- Ricerca per coordinate GPS
- Filtro per date con disponibilità
- Filtro per prezzo range
- Filtro per servizi multipli (AND)
- Ordinamento per prezzo crescente
- Ordinamento per rating decrescente
- Ordinamento per distanza
- Paginazione corretta
- Aggregazioni per filtri dinamici
🛒 MOD_11 - Carrello e Checkout
Scopo
Carrello unificato per alloggi + esperienze + prodotti. Integrazione WooCommerce solo per checkout/pagamento.
Architettura Carrello
Motivo: Flessibilità per gestire prenotazioni con deposito, split payment, soft-lock calendario.
Database: se_cart
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
Verifica disponibilità → Calcola prezzo → Soft-lock calendario (15 min)
Mostra items → Timer soft-lock → Opzione rimuovi/modifica
Raccolta dati per Payturist (documenti, date nascita)
Stripe checkout → Deposito o totale → Split automatico
Crea prenotazione → Blocca calendario → Invia a Payturist → Email conferma
Test Cases
- Aggiungi alloggio al carrello
- Aggiungi esperienza al carrello
- Carrello misto (alloggio + esperienza)
- Soft-lock attivo durante checkout
- Timeout soft-lock → item rimosso
- Checkout con utente loggato
- Checkout come guest
- Raccolta dati ospiti completa
💳 MOD_12 - Pagamenti
Scopo
Gestione pagamenti via Stripe: depositi, saldi, rimborsi. Integrazione con WooCommerce per checkout UI.
Modalità Pagamento Host
Database: se_payments
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
• 7 giorni prima scadenza saldo
• 3 giorni prima
• Giorno scadenza
• Se non pagato entro 24h: cancellazione automatica, calendario liberato
Test Cases
- Pagamento totale con Stripe
- Pagamento deposito (30%)
- Pagamento saldo a scadenza
- Reminder saldo pendente
- Cancellazione per mancato pagamento saldo
- Rimborso completo
- Rimborso parziale
✂️ MOD_13 - Split Payment
Scopo
Distribuzione automatica pagamenti tra piattaforma e host/business via Stripe Connect.
Flusso Split Payment
€100 → Stripe Platform Account
Commissione 8% = €8 piattaforma | €92 host
Stripe Transfer €92 → Host Connected Account
Stripe Connect Setup
// 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
- Onboarding host su Stripe Connect
- Split corretto per piano Base (12%)
- Split corretto per piano Standard (5%)
- Split corretto per piano Premium (0%)
- Split corretto per VIP (0%)
- Transfer automatico a check-out completato
- Gestione Stripe fees (chi paga)
🏛️ MOD_14 - Integrazione Payturist
Scopo
Automazione completa adempimenti burocratici italiani: Alloggiati Web (Polizia), ISTAT, Tassa di Soggiorno.
Servizi Payturist
Database: se_payturist_submissions
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
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
Genera link check-in online → Invia a ospite via email
Documenti, date nascita, cittadinanza tramite form Payturist
Host conferma arrivo → Trigger invio Alloggiati Web
Entro 24h: Alloggiati Web | Fine mese: ISTAT
Test Cases
- Connessione struttura a Payturist
- Generazione link check-in online
- Ricezione dati ospiti da Payturist
- Invio Alloggiati Web entro 24h
- Gestione errori Alloggiati (dati mancanti)
- Invio dati ISTAT mensile
- Report tassa soggiorno per comune
📖 MOD_15 - Guide Personalizzate
Scopo
Host creano guide personalizzate per i propri ospiti con consigli, ristoranti, esperienze. Cross-referral tracking incluso.
Struttura Guide
// 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
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
- Creazione guida con sezioni multiple
- Inserimento esperienza da piattaforma
- Tracking cross-referral da click guida
- Invio automatico guida con conferma
- Accesso guida con/senza registrazione
- Analytics visite guida
⭐ MOD_16 - Recensioni
Scopo
Sistema recensioni verificate per strutture ed esperienze. Solo chi ha prenotato può recensire. Moderazione automatica + manuale.
Regole Recensioni
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
Database: se_reviews
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
// 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
- Invito a recensire dopo check-out
- Blocco recensione senza prenotazione
- Blocco recensione doppia
- Calcolo media rating entity
- Risposta host a recensione
- Segnalazione recensione inappropriata
- Moderazione automatica parole offensive
- Approvazione/rifiuto admin
💬 MOD_17 - Messaggistica
Scopo
Sistema messaggi interno tra turisti e host/business. Thread per prenotazione, notifiche real-time, moderazione.
Regole Messaggistica
Post-prenotazione: Chat illimitata legata alla prenotazione
Dati sensibili: Mascheramento automatico email/telefono prima della conferma
Database: se_messages
-- 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
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
- Invio messaggio pre-prenotazione
- Limite 5 messaggi/giorno senza booking
- Chat illimitata con booking
- Mascheramento email nel testo
- Mascheramento telefono nel testo
- Notifica nuovo messaggio
- Contatore messaggi non letti
- Archiviazione thread
🔔 MOD_20 - Notifiche
Scopo
Sistema notifiche multi-canale: in-app, email, SMS, push. Preferenze utente, template multilingua, scheduling.
Canali Notifica
Tipi di Notifica
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
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
| Codice | Trigger | Destinatario | Oggetto |
|---|---|---|---|
| 🔐 AUTENTICAZIONE | |||
| AUTH_WELCOME | Registrazione | Utente | Benvenuto su Sicily Experience! |
| AUTH_VERIFY | Verifica email | Utente | Verifica il tuo indirizzo email |
| AUTH_RESET | Reset password | Utente | Reimposta la tua password |
| AUTH_CHANGED | Password cambiata | Utente | Password modificata |
| 📅 PRENOTAZIONI | |||
| BOOK_REQUEST | Nuova richiesta | Host | Nuova richiesta prenotazione |
| BOOK_CONFIRM_GUEST | Confermata | Ospite | Prenotazione confermata #{{code}} |
| BOOK_CONFIRM_HOST | Confermata | Host | Nuova prenotazione confermata |
| BOOK_REMINDER | -3gg check-in | Ospite | Il tuo viaggio si avvicina! |
| BOOK_CHECKIN | Giorno check-in | Ospite | Istruzioni check-in |
| BOOK_CHECKOUT | Giorno check-out | Ospite | Grazie per il soggiorno! |
| BOOK_MODIFIED | Modificata | Entrambi | Prenotazione modificata |
| BOOK_CANCEL_GUEST | Cancellata ospite | Entrambi | Prenotazione cancellata |
| BOOK_CANCEL_HOST | Cancellata host | Ospite | Cancellata dall'host |
| 💳 PAGAMENTI | |||
| PAY_RECEIPT | Pagamento ricevuto | Ospite | Ricevuta #{{code}} |
| PAY_BALANCE_DUE | -7gg saldo | Ospite | Promemoria saldo |
| PAY_REFUND | Rimborso | Ospite | Rimborso emesso |
| PAY_PAYOUT | Payout host | Host | Accredito effettuato |
| PAY_FAILED | Fallito | Ospite | Problema pagamento |
| ⭐ RECENSIONI | |||
| REV_REQUEST | +3gg check-out | Ospite | Com'è stato il soggiorno? |
| REV_RECEIVED | Nuova recensione | Host | Nuova recensione ricevuta |
| REV_RESPONSE | Risposta host | Ospite | L'host ha risposto |
| 💬 MESSAGGI | |||
| MSG_NEW | Nuovo messaggio | Destinatario | Messaggio da {{sender}} |
| 🏠 HOST/PARTNER | |||
| HOST_APPROVED | Approvato | Host | Struttura online! |
| HOST_REJECTED | Rifiutato | Host | Revisione richiesta |
| HOST_SUB_EXPIRE | -30/-7/-1gg | Host | Abbonamento in scadenza |
| HOST_SUB_RENEWED | Rinnovato | Host | Abbonamento rinnovato |
| 🤝 AMBASSADOR | |||
| AMB_TRIAL | Trial attivato | Ambassador | Trial per {{client}} |
| AMB_COMMISSION | Commissione | Ambassador | Commissione maturata |
| AMB_PAYOUT | Payout | Ambassador | Pagamento effettuato |
| ⚠️ ALERT ADMIN | |||
| ALERT_OVERBOOKING | Overbooking | Admin+Host | ⚠️ Overbooking rilevato |
| ALERT_NOSHOW | No-show | Admin | Segnalazione no-show |
| ALERT_DISPUTE | Disputa | Admin | Nuova disputa |
Test Cases
- Invio notifica in-app
- Invio email con template
- Invio SMS via Twilio
- Rispetto preferenze utente
- Rispetto orari quiete
- Scheduling notifica futura
- Mark as read
- Contatore badge non lette
🤝 MOD_18 - Sistema 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
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
Test Cases
- Creazione account ambassador
- Attivazione trial per cliente
- Limite max trial contemporanei
- Conversione trial → abbonamento
- Calcolo commissione prima sottoscrizione
- Calcolo commissione rinnovo
- Calcolo commissione transazione
- Scadenza relazione 24 mesi
- Impersonation con logging
- Request payout
💎 MOD_19 - Referral e Wallet
Scopo
Sistema di peer referral tra host/business + wallet per accumulo crediti utilizzabili per rinnovi o prelievo.
Peer Referral
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
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
Test Cases
- Generazione link referral unico
- Tracking referral da link
- Accredito peer referral su abbonamento
- Accredito peer referral su rinnovo
- Tracking cross-referral da guida
- Accredito cross-referral
- Prelievo wallet PayPal
- Prelievo wallet IBAN
- Uso wallet per rinnovo con sconto 5%
✏️ 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
Ospite seleziona nuove date
Sistema controlla disponibilità
Ricalcolo prezzo e differenza
Addebito/rimborso differenza
Aggiornamento e notifiche
Schema Database
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
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
┌─────────────────────────────────────────────────────────────┐
│ ← 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
Test Cases
- Cambio date con disponibilità → successo
- Cambio date senza disponibilità → errore
- Cambio date < 48h da check-in → bloccato
- Estensione soggiorno → pagamento differenza
- Riduzione soggiorno → rimborso parziale
- Cambio ospiti entro capacità → ricalcolo
- Cambio ospiti oltre capacità → errore
- Upgrade camera → pagamento differenza
- Downgrade camera → richiede approvazione host
- Modifica con saldo da pagare → Stripe charge
- Modifica con rimborso → Stripe refund
- Notifiche inviate correttamente
🎟️ MOD_22 - Coupon & Promozioni
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
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
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
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
┌─────────────────────────────────────────────────────────────┐
│ 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
- 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
- Coupon valido applicato correttamente
- Coupon scaduto rifiutato
- Coupon esaurito rifiutato
- Coupon già usato da utente rifiutato
- Minimo notti non raggiunto → errore
- Minimo importo non raggiunto → errore
- Date blackout → errore
- Coupon struttura specifica su altra struttura → errore
- Sconto percentuale calcolato correttamente
- Sconto fisso con tetto massimo applicato
- Promozione early booking applicata automaticamente
- Promozione long stay cumulata con coupon
- Host crea coupon per propria struttura
- Report utilizzo coupon
❌ 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
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
Utente richiede cancellazione → Calcolo rimborso in base a policy snapshot
Mostra importo rimborso → Utente conferma
Refund automatico su carta originale
Rilascio calendario + Notifiche + Aggiornamento statistiche
Test Cases
- Cancellazione con rimborso 100%
- Cancellazione con rimborso parziale
- Cancellazione senza rimborso
- Refund Stripe corretto
- Rilascio date calendario
- Notifica turista
- Notifica host
- Calcolo commissione su trattenuto
⚖️ MOD_26 - Dispute e Overbooking
Scopo
Gestione situazioni problematiche: overbooking, no-show, forza maggiore, reclami. Workflow risoluzione con penalità.
Gestione Overbooking
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
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
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
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
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
- Segnalazione overbooking da host
- Ricerca alloggio alternativo
- Calcolo compensazione turista
- Applicazione penalità progressiva host
- Segnalazione no-show
- Timeout risposta turista (24h)
- Richiesta forza maggiore con documenti
- Emissione credito 12 mesi
📈 MOD_27 - Analytics
Scopo
Dashboard analytics per host, business, admin. Metriche performance, revenue, conversioni, comparativi.
Metriche Host
Metriche Business
Metriche Piattaforma (Admin)
Database: se_analytics_daily
-- 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
- Calcolo occupancy rate corretto
- Calcolo ADR e RevPAR
- Aggregazione giornaliera automatica
- Dashboard host con grafici
- Comparativo periodo precedente
- Export CSV metriche
- Tracking eventi pagina
🌍 MOD_28 - Multi-lingua
Scopo
Supporto multilingua per frontend turisti: IT, EN, DE, FR, ES. Implementazione custom senza plugin pesanti.
Lingue Supportate
Struttura URL
// 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
// 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
- Switching lingua da menu
- URL corretto per ogni lingua
- Contenuto tradotto correttamente
- Fallback a italiano se traduzione mancante
- SEO: hreflang tags corretti
- Persistenza lingua in sessione
🔍 MOD_29 - SEO e Schema.org
Scopo
Ottimizzazione SEO completa: Schema.org markup, Open Graph, sitemap dinamiche, llms.txt per AI.
Schema.org Markup
{
"@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
Test Cases
- Schema.org validato su Google Rich Results Test
- Open Graph corretto per condivisione social
- Sitemap XML dinamica
- Sitemap per ogni lingua
- Hreflang tags corretti
- llms.txt accessibile
📊 MOD_30 - Export Contabilità
Scopo
Interfaccia backend per commercialista: export dati per Fatture in Cloud (API), Danea Easyfatt (.defxml), Excel/CSV.
Integrazioni Supportate
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
- Export CSV commissioni per mese
- Export CSV payout per mese
- Sync Fatture in Cloud: crea cliente
- Sync Fatture in Cloud: crea fattura
- Export Danea .defxml
- Report tassa soggiorno per comune
📜 Policy - Cancellazioni
Politiche Cancellazione Predefinite
Prezzo scontato, nessun rimborso in caso di cancellazione.
Ideale per: Tariffe promozionali, last minute
- Cancellazione gratuita fino a 5 giorni prima
- 1-5 giorni prima: 50% trattenuto
- Meno di 24 ore o no-show: 100% trattenuto
- 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
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
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
⚠️ 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à)
- Sicily Experience cerca struttura alternativa
- Criteri: stessa zona, stesso livello o superiore
- Tempo massimo ricerca: 4 ore
- Turista può accettare o rifiutare
Step 2: Se Ricollocamento Accettato
- Turista va nella nuova struttura
- Differenza prezzo: a carico dell'host colpevole
- Turista riceve bonus scuse (15% della prenotazione, min €25, max €200)
Step 3: Se Ricollocamento Rifiutato/Impossibile
- Rimborso 100% al turista
- Turista riceve bonus scuse
Penali Host
| Livello | Condizione | Penale |
|---|---|---|
| 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 |
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
Definizione
Circostanze eccezionali e imprevedibili che impediscono il viaggio, non imputabili al turista.
Casi Riconosciuti
Documentazione Richiesta
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
Se il pagamento era già stato trasferito all'host, viene stornato dal prossimo payout.
💳 Policy - Pagamenti
Modalità Pagamento Turista
| Modalità | Deposito | Saldo | Configurabile Host |
|---|---|---|---|
| Pagamento Totale | 100% | - | Sì |
| Deposito 30% | 30% | 70% (7gg prima check-in) | Sì |
| Deposito 50% | 50% | 50% (7gg prima check-in) | Sì |
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
Metodo: Stripe Connect (transfer automatico)
Minimo: €10 (sotto questa soglia, accumula)
Frequenza: Automatico per ogni prenotazione completata
Commissioni su Penali
🛡️ GDPR_01 - Privacy & Compliance
Riferimenti Normativi
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
| 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
┌─────────────────────────────────────────────────────────────┐
│ 🍪 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)
Utente richiede cancellazione da Area Utente
Check prenotazioni attive, saldi pendenti
Email conferma + link verifica
7 giorni per annullare
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)
{
"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
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
Test Cases
- Cookie banner appare a primo accesso
- Scelta cookie salvata e rispettata
- Analytics NON carica senza consenso
- Utente può modificare preferenze cookie
- Export dati genera JSON/CSV completo
- Cancellazione account funziona
- Cooling-off period rispettato
- Dati finanziari mantenuti dopo cancellazione
- Prenotazioni anonimizzate correttamente
- Link Privacy Policy accessibile
- Consenso marketing registrato correttamente
- Opt-out marketing immediato
🔌 Integrazione Payturist
Panoramica
Payturist è il sistema utilizzato dalle strutture ricettive siciliane per gestire gli adempimenti burocratici obbligatori. L'integrazione automatizza completamente questi processi.
Funzionalità Automatizzate
| Adempimento | Destinatario | Frequenza | Automatizzato |
|---|---|---|---|
| Schedine Alloggiati | Polizia di Stato | Entro 24h dal check-in | ✅ |
| Flussi turistici ISTAT | Osservatorio Turistico Sicilia | Giornaliero | ✅ |
| Tassa di soggiorno | Comune | Mensile/Trimestrale | ✅ |
| Dichiarazione Annuale | Agenzia delle Entrate | Annuale | ✅ |
| Modello 21 | Corte dei Conti | Annuale | ✅ |
Configurazione
Per integrare una struttura con Payturist sono necessari:
structure_id- ID struttura su Payturist (nella loro area riservata)token- Token API utente tipo "Booking" (validità 12 mesi)software_id- Da richiedere a [email protected]
API Payturist
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
Architettura
Modello: Direct charges con application fee
Valuta: EUR
Flusso Onboarding Host
API: Stripe\Account::create(['type' => 'express'])
Redirect a Stripe per KYC e dati bancari
account.updated → verifica capabilities attive
Host può ricevere pagamenti
Flusso Pagamento
// 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
📅 Integrazione iCal Sync
Scopo
Sincronizzazione bidirezionale calendari con OTA: Booking.com, Airbnb, Vrbo, altri channel manager.
Import (da OTA)
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 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
Export: Real-time (URL sempre aggiornato)
Cron job: WP-Cron o sistema cron esterno per reliability
📄 Integrazione Fatture in Cloud
Scopo
Sincronizzazione automatica con Fatture in Cloud per contabilità: clienti, fatture, prodotti/servizi.
API Endpoints Utilizzati
Flusso Automatico
Check-out confermato
Dati turista → Fatture in Cloud
Commissione piattaforma verso host
📊 Export Danea Easyfatt
Scopo
Generazione file .defxml compatibile con import Danea Easyfatt per commercialisti che usano questo software.
Formato Export
<?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
- Commissioni piattaforma
- Abbonamenti host/business
- Payout effettuati
- Rimborsi emessi
- Tasse di soggiorno incassate
🗺️ INT-MAPS - Google Maps API
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
// 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
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
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
- Mostrare immagine statica mappa (pre-generata)
- Link a Google Maps esterno
- Coordinate testuali copiabili
- Log errore per alerting
Budget Mensile Stimato
Test Cases
- Mappa carica su pagina struttura
- Marker posizionato correttamente
- InfoWindow con dati struttura
- Mappa ricerca con cluster markers
- Autocomplete indirizzo funzionante
- Geocoding indirizzo → coordinate
- Calcolo distanza da aeroporto
- Fallback se API non disponibile
- Cache geocoding funzionante
- Stili custom applicati
🌴 Frontend Turisti - Overview
Caratteristiche
Struttura Pagine
Design System
Colori
#1a5f7a
#ff6b35
#ffc947
#2ecc71
#e74c3c
Tipografia
- Titoli: Playfair Display (serif, elegante)
- Body: Source Sans Pro (sans-serif, leggibile)
- Codice/Dati: JetBrains Mono (monospace)
🏠 FE-T-01 - Homepage
URL
sicilyexperience.com/ | sicilyexperience.com/{lang}/
Obiettivo
Ispirare, emozionare, convertire. Far sognare la Sicilia e spingere alla ricerca/prenotazione.
Sezioni Pagina
┌─────────────────────────────────────────────────────────────┐
│ 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
- Hero con video/immagine rotante
- Search box con autocomplete destinazioni
- Date picker (check-in/check-out)
- Guest selector
- Carousel destinazioni
- Grid strutture in evidenza
- Grid esperienze
- Trust badges
- Mini calendario eventi
- Slider testimonial
- Form newsletter
SEO
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
URL
/strutture | /strutture/{citta} | /strutture?check_in=...&check_out=...&guests=...
Layout
┌─────────────────────────────────────────────────────────────┐
│ 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à
- Filtri dinamici con contatori
- Ricerca per mappa (Leaflet/Mapbox)
- Toggle vista lista/mappa
- Infinite scroll o paginazione
- Ordinamento (prezzo, rating, distanza, novità)
- Salva ricerca
- Wishlist (cuore)
- URL shareable con parametri
Mobile
🏨 FE-T-03 - Scheda Struttura
URL
/strutture/{citta}/{slug}Esempio:
/strutture/taormina/villa-sul-mare
Sezioni Pagina
┌─────────────────────────────────────────────────────────────┐
│ 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
{
"@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
Pagine
/esperienze- Lista tutte le esperienze/esperienze/{categoria}- Per categoria (tour, degustazioni, avventura)/esperienze/{citta}/{slug}- Scheda singola esperienza
Categorie Esperienze
Scheda Esperienza
- Galleria foto/video
- Descrizione dettagliata
- Cosa include / non include
- Punto di ritrovo con mappa
- Durata e orari disponibili
- Calendario disponibilità
- Selezione data e partecipanti
- Prezzo con varianti
- Recensioni
- Info organizzatore
🎉 FE-T-05 - Eventi
Pagine
/eventi- Calendario eventi/eventi/{citta}- Eventi per città/eventi/{slug}- Scheda singolo evento
Vista Calendario
┌─────────────────────────────────────────────────────────────┐
│ 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
📖 FE-T-06 - Guide Città
URL Patterns
/guide/{citta}/guide/host/{token}/guide/{citta}#sezioneWireframe Pagina Guida
┌─────────────────────────────────────────────────────────────┐
│ 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
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
Test Cases
- Caricamento guida pubblica città
- Caricamento guida host con token valido
- Token invalido → redirect 404 custom
- Navigazione anchor tra sezioni
- Sticky nav appare su scroll
- Click su struttura → tracking referral
- Click su esperienza → tracking referral
- Mappa con filtri POI funzionanti
- Condivisione social
- Stampa/PDF guida
💳 FE-T-07 - Checkout
Flusso Checkout
Verifica dettagli prenotazione, date, ospiti, prezzo
Nome, email, telefono, richieste speciali
Per Payturist: documento, data nascita, nazionalità (per ogni ospite)
Stripe checkout, scelta totale o deposito
Thank you page, email conferma, codice prenotazione
Soft-Lock Timer
Pagina Conferma
- Codice prenotazione prominente
- Riepilogo completo
- Istruzioni check-in
- Contatti host
- Link per gestire prenotazione
- Suggerimenti esperienze nella zona
- Pulsante "Aggiungi al calendario"
👤 FE-T-08 - Area Utente
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
- Stato prenotazione con timeline
- Codice prenotazione
- Dettagli struttura con link
- Date e ospiti
- Breakdown prezzo
- Pagamenti effettuati/pendenti
- Documenti (ricevuta, conferma)
- Azioni: Modifica date, Cancella, Contatta host
- Check-in online (link Payturist)
- Lascia recensione (dopo check-out)
⚠️ FE-T-09 - Pagine Errore
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
┌─────────────────────────────────────────────────────────────┐
│ 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
┌─────────────────────────────────────────────────────────────┐
│ 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
┌─────────────────────────────────────────────────────────────┐
│ │
│ 🔧 │
│ │
│ 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
| 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
- 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
- 404 su URL inesistente
- 404 su struttura cancellata
- 403 su risorsa protetta
- 500 gestito gracefully
- 503 durante manutenzione
- CTA funzionanti su pagine errore
- Ricerca funziona da 404
- Error ID generato e loggato
- Sentry riceve errori 500
🏢 Frontend Partner - Overview
Caratteristiche
Struttura Dashboard
Dashboard Host - Wireframe
🚀 FE-P-01 - Landing & Registrazione
URL
partner.sicilyexperience.com/
Obiettivo
Convincere host e business a registrarsi. Spiegare vantaggi, mostrare piani tariffari, facilitare onboarding.
Sezioni Landing Page
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
Host (struttura) o Business (esperienze)?
Nome, email, password, telefono
Link di conferma
Wizard per completare profilo e prima struttura
🏠 FE-P-02 - Dashboard Host
Menu Laterale
Dashboard Home - Widget
- Arrivi/Partenze oggi
- Revenue periodo
- Occupazione %
- Calendario settimanale mini
- Prenotazioni in attesa di conferma
- Messaggi non letti
- Recensioni da rispondere
- Avvisi (rinnovo abbonamento, documenti scadenza)
Gestione Struttura
- Info generali (nome, descrizione, tipologia)
- Indirizzo e mappa
- Galleria foto (drag & drop, riordina)
- Servizi e amenities
- Regole della casa
- Policy cancellazione
- Codici CIN/CIR
- Connessione Payturist
- Gestione unità/camere
🏪 FE-P-03 - Dashboard Business
Differenze da Host
Menu Specifico Business
Gestione Offerta
- Tipo (esperienza, prodotto, servizio, voucher)
- Titolo e descrizione multilingua
- Galleria foto/video
- Cosa include/non include
- Prezzo e varianti
- Durata e orari
- Punto di ritrovo
- Capacità min/max
- Disponibilità (giorni, date escluse)
- Requisiti (età, difficoltà)
- Policy cancellazione
🤝 FE-P-04 - Dashboard Ambassador
Obiettivo
Permettere agli ambassador di gestire i clienti acquisiti, attivare trial, monitorare commissioni e richiedere payout.
Sezioni Dashboard
Funzionalità Chiave
- Attiva trial per nuovo cliente (max 30gg, max 10 attivi)
- Impersonation: accedi come cliente (con permesso)
- Traccia conversioni trial → pagante
- Visualizza commissioni su abbonamenti e transazioni
- Richiedi payout (minimo €50)
- Genera link referral tracciati
- Report performance mensile
Widget Overview
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ CLIENTI ATTIVI │ │ TRIAL ATTIVI │ │ CONVERSIONI │
│ 23 │ │ 5/10 │ │ 78% │
└────────────────┘ └────────────────┘ └────────────────┘
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ COMM. MESE │ │ COMM. TOTALI │ │ SALDO ATTUALE │
│ €345 │ │ €2.890 │ │ €195 │
└────────────────┘ └────────────────┘ └────────────────┘
📅 FE-P-05 - Calendario
Viste Calendario
Funzionalità
- Vista per struttura o tutte le strutture
- Vista per unità (multi-row)
- Colori per stato (confermato, pendente, bloccato)
- Click su prenotazione → dettagli
- Drag & drop per modificare date
- Blocco manuale date
- Sync status iCal (ultimo import)
- Segnalazione conflitti
Wireframe Timeline
│ 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
Overview Wallet
┌─────────────────────────────────────────────────────────────┐
│ 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
- Link referral personalizzato
- Codice referral
- Statistiche (inviti, registrazioni, conversioni)
- Lista referral con stato
- Commissioni guadagnate per referral
Cross-Referral (per Host)
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
⚙️ Frontend 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
📊 FE-A-01 - Dashboard Admin
KPI Principali
Grafici
- Revenue nel tempo (line chart)
- Prenotazioni per giorno (bar chart)
- Distribuzione per tipologia struttura (pie chart)
- Top città per prenotazioni
- Conversion funnel (visite → ricerche → checkout → conferma)
Alert e Azioni
- Strutture in attesa di approvazione
- Dispute aperte
- Payout pendenti
- Errori Payturist
- Pagamenti falliti
⚙️ FE-A-02 - Configurazioni
Parametri Configurabili
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
🛡️ FE-A-03 - Moderazione
Code di Moderazione
Gestione Dispute
- Lista dispute aperte con priorità
- Dettaglio dispute con timeline
- Comunicazione con le parti
- Azioni: rimborso, credito, penalità, ban
- Chiusura con note
Azioni su Utenti
- Verifica identità
- Sospensione temporanea
- Ban permanente
- Reset password forzato
- Modifica piano/commissioni
- Crea account VIP (solo Owner)
Gestione Forza Maggiore
- Lista richieste pendenti
- Verifica documentazione
- Approva/rifiuta con motivazione
- Emetti credito o rimborso
🚀 Roadmap - Fase 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)
- Setup ambiente sviluppo e staging
- MOD_00 Account speciali & Trial
- MOD_01 Autenticazione (ruoli, login, registrazione)
- MOD_02 Strutture (CPT, ACF, CRUD)
- MOD_03 Unità/Camere
- MOD_28 Multi-lingua (struttura base IT/EN)
- Setup database tabelle custom
Sprint 2: Booking Core (4 settimane)
- MOD_04 Calendario disponibilità
- MOD_05 Prezzi e tariffe (base + stagionalità)
- MOD_09 Ricerca e filtri
- MOD_10 Prenotazioni (creazione, stati, ciclo vita)
- Verifica disponibilità real-time
- Calcolo prezzi con regole
Sprint 3: Pagamenti (3 settimane)
- MOD_11 Carrello & Checkout (WooCommerce)
- MOD_12 Pagamenti Stripe
- MOD_13 Split Payment (Stripe Connect)
- MOD_25 Cancellazioni e rimborsi
- Gestione caparra + saldo
- Email transazionali conferma
Sprint 4: Integrazioni (3 settimane)
- MOD_14 Integrazione Payturist
- MOD_20 Sistema notifiche (email)
- INT_03 Sync iCal base
- Check-in online automatico
- Schedine Alloggiati Web
Sprint 5: Frontend (4 settimane)
- FE_T Homepage turisti
- FE_T Ricerca strutture con mappa
- FE_T Scheda struttura
- FE_T Checkout
- FE_P Landing partner
- FE_P Dashboard host base
- FE_P Calendario prenotazioni
- PWA base
Sprint 6: Test & Launch (2 settimane)
- Test end-to-end completi
- Test pagamenti in sandbox
- Test Payturist in ambiente test
- Performance optimization
- Security audit base
- Backup e monitoring setup
- Soft launch con beta tester
- Go live!
Feature NON in MVP (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
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
PHP Extensions Richieste
🚀 Deployment - Procedura
Ambiente Staging
Scopo: Test prima di ogni deploy in produzione
Database: Copia anonimizzata della produzione
Checklist Pre-Deploy
- Tutti i test passano
- Backup database recente
- Backup files recente
- Changelog aggiornato
- Versione incrementata
- Test in staging completato
Procedura 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
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
RPO (Recovery Point Objective): 1 ora
Procedura: Documentata in runbook interno
📡 Deployment - Monitoring
Strumenti
Allarmi
| Evento | Soglia | Canale |
|---|---|---|
| Site down | 2 check falliti | SMS + Email |
| Response time | > 3 secondi | |
| Error rate | > 5% richieste | Slack + Email |
| Disk space | > 80% usato | |
| SSL expiry | < 14 giorni |
✅ VAL_01 - Validazione Input
Principi Fondamentali
Fail fast: Restituire tutti gli errori insieme, non uno alla volta.
Messaggi chiari: Spiegare cosa è sbagliato e come correggerlo.
Regole Validazione Campi
| Campo | Tipo | Regole | Sanitizzazione | Messaggio Errore |
|---|---|---|---|---|
| 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
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
// 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
- Email valida accettata
- Email invalida rifiutata
- Password troppo corta rifiutata
- CF italiano valido accettato
- P.IVA con checksum errato rifiutata
- Data passata rifiutata per prenotazione
- Prezzo negativo rifiutato
- Errori multipli mostrati insieme
- Sanitizzazione XSS in textarea
- Validazione client + server consistente
♿ A11Y - Accessibilità
Standard di Riferimento
Legge: Conformità L. 4/2004 (Legge Stanca) e Direttiva UE 2016/2102
Tools: WAVE, aXe, Lighthouse Accessibility
Checklist WCAG
- 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
- 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
- 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
<!-- 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
/* 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
- Navigazione completa con solo tastiera
- Test con screen reader (NVDA/VoiceOver)
- Zoom 200% senza scroll orizzontale
- Lighthouse Accessibility score ≥90
- aXe zero critical issues
- Form compilabile senza mouse
- Modal chiudibile con Esc
⚡ CACHE - Caching Strategy
Livelli di Cache
Assets statici (CSS, JS, immagini)
Cloudflare edge caching
HTML pagine (Nginx FastCGI)
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
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
- Cache hit ratio >80% su Redis
- Pagina struttura cachata correttamente
- Disponibilità aggiornata dopo prenotazione
- Utente loggato vede dati fresh
- Invalidazione corretta su update
- No cache su checkout
🔒 SEC_01 - Security Checklist
OWASP Top 10 Mitigation
| 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
# 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
// ❌ 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 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
CSRF Protection
// 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
- 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
- SQL injection test su tutti i parametri
- XSS test su tutti gli output
- CSRF token validato su tutte le form
- Rate limiting funzionante
- Security headers presenti
- HTTPS redirect attivo
- File upload validazione MIME
- Password policy enforced
🔄 CI/CD - Pipeline
Pipeline Overview
Push su branch
PHPStan, ESLint, PHPUnit
Compila assets
Auto su develop
Manual approval
GitHub Actions Workflow
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
#!/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
# 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
- PR trigger CI pipeline
- Lint failure blocca merge
- Test failure blocca merge
- Deploy staging automatico su develop
- Deploy production richiede approval
- Rollback funzionante
- Secrets non esposti in logs
- Coverage report generato
🚀 Roadmap - Fase 2
Timeline: Post-MVP (Q3-Q4 2026)
Dopo il lancio MVP e validazione mercato con le strutture ricettive.
Sprint 7-8: Business & Esperienze (6 settimane)
- MOD_06 Gestione Business
- MOD_07 Offerte ed Esperienze
- FE_T Sezione esperienze
- FE_T Ricerca esperienze
- FE_P Dashboard business
- Carrello misto (alloggio + esperienza)
Sprint 9-10: Community Features (6 settimane)
- MOD_16 Sistema recensioni
- MOD_17 Messaggistica interna
- MOD_15 Guide personalizzate complete
- FE_T Sezione guide città
- FE_P Editor guide per host
Sprint 11-12: Monetization (4 settimane)
- MOD_18 Sistema Ambassador
- MOD_19 Referral & Wallet
- Cross-referral tracking
- FE_P Dashboard ambassador
- FE_P Sezione wallet
Sprint 13-14: Eventi & Promoter (4 settimane)
- MOD_08 Gestione eventi
- FE_T Calendario eventi
- FE_T Scheda evento
- FE_P Dashboard promoter
- Pagamento pubblicazione eventi
📋 Roadmap - Backlog Futuro
Fase 3: Scale (2027)
- App nativa iOS/Android (React Native o Flutter)
- Channel Manager avanzato (API Booking.com, Airbnb)
- Revenue Management (dynamic pricing AI)
- Multi-destinazione (espansione oltre Sicilia)
- B2B: agenzie e tour operator
Features da Valutare
- Instant booking senza conferma host
- Assicurazione viaggio integrata
- Loyalty program turisti
- Concierge AI chatbot
- Video tour virtuali
- Marketplace prodotti locali (e-commerce puro)
- API pubblica per partner
Tech Debt & Improvements
- Migrazione a headless CMS (mantenendo WP backend)
- GraphQL API
- Microservizi per componenti critici
- CDN per media (Cloudflare R2 o Bunny)
- Search engine dedicato (Elasticsearch/Meilisearch)