[{"data":1,"prerenderedAt":335},["ShallowReactive",2],{"work-it-ospitio":3},{"id":4,"title":5,"body":6,"category":312,"description":98,"extension":313,"featured":314,"links":315,"meta":316,"navigation":314,"order":317,"path":318,"period":319,"role":320,"seo":321,"slug":322,"stack":323,"stem":332,"subtitle":333,"summary":315,"__hash__":334},"projects_it\u002Fprojects\u002Fit\u002Fospitio.md","Ospitio",{"type":7,"value":8,"toc":303},"minimark",[9,14,18,44,47,51,63,67,85,91,101,116,120,126,166,184,205,215,228,238,242,285,289,300],[10,11,13],"h2",{"id":12},"il-problema","Il problema",[15,16,17],"p",{},"Ogni struttura ricettiva italiana deve compilare due adempimenti obbligatori per ogni ospite:",[19,20,21,38],"ol",{},[22,23,24,28,29,32,33,37],"li",{},[25,26,27],"strong",{},"Alloggiati Web"," — servizio della Polizia di Stato. Le schedine degli ospiti vanno trasmesse entro 24 ore dal check-in. L'unico canale ufficiale è un'interfaccia SOAP che accetta file TXT con record a ",[25,30,31],{},"larghezza fissa di 168 caratteri",". 15 colonne, ognuna con padding specifico (50 per il cognome, 30 per il nome, 2 per la sigla provincia, 9 per il codice ISTAT del comune di nascita). Un carattere fuori posto = schedina scartata con messaggio ",[34,35,36],"code",{},"SCHEDINA_CAMPO_NON_CORRETTO",".",[22,39,40,43],{},[25,41,42],{},"Paytourist"," — piattaforma comunale per la gestione dell'imposta di soggiorno. API REST, endpoint diverso per provincia (Bari, Roma, Firenze, ognuna con la propria istanza), token struttura con validità 36 mesi.",[15,45,46],{},"Un gestore tipico inserisce manualmente gli stessi dati in tre sistemi (il proprio gestionale + Alloggiati + Paytourist), sbaglia una volta ogni 20 soggiorni, perde 15-30 minuti per prenotazione e accumula rischio di sanzioni sulle scadenze.",[10,48,50],{"id":49},"perché-lho-costruito","Perché l'ho costruito",[15,52,53,54,57,58,62],{},"Gestisco un piccolo B&B. Ho vissuto il problema per mesi prima di decidere che l'automazione era l'unica strada sostenibile. ",[25,55,56],{},"Ogni scelta di prodotto è stata presa contro un caso d'uso reale vissuto in prima persona"," — non contro una ",[59,60,61],"em",{},"persona"," inventata in workshop.",[10,64,66],{"id":65},"architettura","Architettura",[15,68,69,72,73,76,77,80,81,84],{},[25,70,71],{},"Frontend"," — Nuxt 3 SPA + Nuxt UI + TypeScript, build SSG su hosting statico. Niente store Pinia: ",[34,74,75],{},"useState()"," nativo di Nuxt + 11 composables che incapsulano le chiamate API. Un singolo ",[34,78,79],{},"useApi()"," wrappa ",[34,82,83],{},"$fetch"," con iniezione automatica del bearer token e dell'header di contesto utente.",[15,86,87,90],{},[25,88,89],{},"Backend"," — NestJS + Prisma + PostgreSQL, deploy containerizzato. 14 moduli di dominio:",[92,93,99],"pre",{"className":94,"code":96,"language":97,"meta":98},[95],"language-text","auth              ← JWT + registration \u002F login \u002F password reset\nusers             ← user management\npermissions       ← RBAC granulare\nproperties        ← strutture + credenziali delle integrazioni\nrooms             ← camere + mapping verso la piattaforma fiscale\nguests            ← anagrafica ospiti\nguest-documents   ← documenti identità per ospite\nstays             ← soggiorni\nstay-guests       ← pivot soggiorno↔ospite con ruolo\nexports           ← generatore TXT 168-char per il portale di pubblica sicurezza\nalloggiati-web    ← client SOAP del portale di pubblica sicurezza\npaytourist        ← client REST dell'imposta di soggiorno\ncheckin-intake    ← ingresso webhook per moduli di self check-in\naudit             ← log delle modifiche\n","text","",[34,100,96],{"__ignoreMap":98},[15,102,103,104,107,108,111,112,115],{},"Tre guard trasversali: ",[34,105,106],{},"JwtAuthGuard"," globale per il controllo del token, ",[34,109,110],{},"PermissionsGuard"," per i permessi granulari, ",[34,113,114],{},"ImpersonationInterceptor"," che permette a un admin — solo se autorizzato — di operare nel contesto di un altro utente tramite un canale dedicato.",[10,117,119],{"id":118},"decisioni-tecniche-chiave","Decisioni tecniche chiave",[15,121,122,125],{},[25,123,124],{},"1. SOAP dove non vorresti mai vederlo."," La Polizia di Stato espone solo SOAP. Client minimale scritto a mano, nessuna libreria pesante. Gestisce:",[127,128,129,146,149],"ul",{},[22,130,131,134,135,138,139,138,142,145],{},[34,132,133],{},"GenerateToken"," con credenziali ",[34,136,137],{},"user"," \u002F ",[34,140,141],{},"password",[34,143,144],{},"wsKey"," della struttura",[22,147,148],{},"cache in memoria del token (validità ~1h) con refresh automatico",[22,150,151,152,155,156,159,160,155,163,165],{},"due coppie di metodi in base al tipo di utente: ",[34,153,154],{},"Send()","\u002F",[34,157,158],{},"Test()"," per utenti standard, ",[34,161,162],{},"GestioneAppartamenti_Send()",[34,164,158],{}," per property manager con più appartamenti",[15,167,168,171,172,175,176,179,180,183],{},[25,169,170],{},"2. Formatter a larghezza fissa come cittadino di prima classe."," ",[34,173,174],{},"alloggiati.formatter.ts"," mappa ",[34,177,178],{},"Guest + GuestDocument + Stay + StayGuestRole"," → riga di 168 caratteri con padding specifico per ogni colonna. Separato da validator e exporter (single responsibility). Il ",[34,181,182],{},"StayGuestRole"," (SINGLE \u002F FAMILY_HEAD \u002F FAMILY_MEMBER \u002F GROUP_HEAD \u002F GROUP_MEMBER) determina il codice \"Tipo Alloggiato\" (16–20) — regola non banale perché dipende dalla composizione del gruppo.",[15,185,186,189,190,193,194,193,197,200,201,204],{},[25,187,188],{},"3. Multi-tenancy leggera."," Un'istanza backend, N utenti, ognuno con le proprie strutture. Ogni tabella operativa (",[34,191,192],{},"Guest",", ",[34,195,196],{},"Stay",[34,198,199],{},"Export",", …) ha FK verso ",[34,202,203],{},"ownerId",": tutti i service filtrano per owner. Più semplice di uno schema-per-tenant, più sicuro del trust cieco nell'app.",[15,206,207,210,211,214],{},[25,208,209],{},"4. Impersonation al posto delle password condivise."," Per assistere un utente, posso entrare nel suo account tramite un flusso dedicato, gated da un permesso specifico. Niente più ",[59,212,213],{},"\"dammi la password un secondo\"",". Ogni azione in impersonation viene loggata con operatore e utente target.",[15,216,217,220,221,224,225,227],{},[25,218,219],{},"5. Test mode come gesto di rispetto per l'utente."," Prima di inviare le schedine alla Polizia di Stato, l'utente può cliccare ",[25,222,223],{},"Test",": il backend chiama il metodo SOAP ",[34,226,158],{}," che valida senza committare. Gli errori tornano per-schedina, con il codice originale e una traduzione leggibile. Zero sorprese dopo il Send.",[15,229,230,233,234,237],{},[25,231,232],{},"6. Webhook kiosk-to-stay."," Un modulo esterno di self check-in può creare ",[34,235,236],{},"Guest + GuestDocument + Stay"," tramite un endpoint webhook autenticato. Quando l'ospite compila il form alla porta d'ingresso, i dati arrivano già strutturati nel backend — pronti per essere inviati al portale di pubblica sicurezza.",[10,239,241],{"id":240},"numeri","Numeri",[127,243,244,251,261,267,273,279],{},[22,245,246,247,250],{},"~",[25,248,249],{},"12.000 LOC"," totali (6,7k frontend · 5,3k backend)",[22,252,253,256,257,260],{},[25,254,255],{},"14 modelli"," Prisma · ",[25,258,259],{},"14 moduli"," NestJS",[22,262,263,266],{},[25,264,265],{},"15 colonne fixed-width"," nel formato Alloggiati, con lookup contro le tabelle ISTAT per comuni e stati",[22,268,269,272],{},[25,270,271],{},"2 integrazioni regolatorie"," con semantiche opposte (SOAP XML fixed-width vs REST JSON)",[22,274,275,278],{},[25,276,277],{},"5 permessi granulari"," mappati a endpoint specifici",[22,280,281,284],{},[25,282,283],{},"1 webhook kiosk"," che chiude il loop dal front-desk al backend in un passaggio",[10,286,288],{"id":287},"lezioni-che-mi-porto-dietro","Lezioni che mi porto dietro",[15,290,291,292,295,296,299],{},"Lavorare su dominio pubblico-burocratico italiano insegna una cosa: l'assunzione di ",[59,293,294],{},"\"API decenti\""," non tiene. L'astrazione vale solo se la scriviamo noi. Il valore per l'utente finale è proporzionale a quanta complessità regolatoria il software riesce a nascondere — e questa scala ",[25,297,298],{},"moltiplicativamente",", non additivamente, rispetto alla quantità di codice.",[15,301,302],{},"Costruire software per un problema che hai in prima persona ha un vantaggio cheat: sai già dove l'utente si arrende. Non serve user research, serve solo non dimenticare quel momento.",{"title":98,"searchDepth":304,"depth":304,"links":305},2,[306,307,308,309,310,311],{"id":12,"depth":304,"text":13},{"id":49,"depth":304,"text":50},{"id":65,"depth":304,"text":66},{"id":118,"depth":304,"text":119},{"id":240,"depth":304,"text":241},{"id":287,"depth":304,"text":288},"work","md",true,null,{},1,"\u002Fprojects\u002Fit\u002Fospitio","2026 → in corso","Fondatore · full-stack",{"title":5,"description":98},"ospitio",[324,325,326,327,328,329,330,331],"Nuxt 3","Nuxt UI","NestJS","Prisma","PostgreSQL","TypeScript","SOAP","REST","projects\u002Fit\u002Fospitio","Il gestionale per strutture turistiche italiane che automatizza due adempimenti obbligatori — Alloggiati Web e Paytourist — in un'unica piattaforma.","A3HCdhspti9fhRZXHSXPuZOLOgMUDGkVh8VMLZnXTmw",1781346783286]