diff --git a/app/Console/Commands/GesconBonificaStabile0013Command.php b/app/Console/Commands/GesconBonificaStabile0013Command.php index 9efb107..7f87872 100644 --- a/app/Console/Commands/GesconBonificaStabile0013Command.php +++ b/app/Console/Commands/GesconBonificaStabile0013Command.php @@ -85,6 +85,29 @@ public function handle(): int $this->info('Anagrafiche corrotte rimaste bonificate.'); } + // 3b. Pulizia comproprietari incrociati orfani su Unità 233 (0013-A-1) + $unit233 = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'A')->where('interno', '1')->first(); + if ($unit233) { + DB::table('unita_anagrafica_periodo') + ->where('unita_immobiliare_id', $unit233->id) + ->where('ruolo_occupazione', 'comproprietario') + ->delete(); + } + + // 3c. Pulizia assegnazioni errate RDP S.R.L. (consentita solo su B-3 e B-4) + if (Schema::hasTable('unita_immobiliare_nominativi')) { + $unitB3B4Ids = UnitaImmobiliare::where('stabile_id', $stabile->id) + ->where('scala', 'B') + ->whereIn('interno', ['3', '4']) + ->pluck('id') + ->all(); + DB::table('unita_immobiliare_nominativi') + ->where('stabile_id', $stabile->id) + ->where('nominativo', 'like', '%RDP%') + ->whereNotIn('unita_immobiliare_id', $unitB3B4Ids) + ->delete(); + } + // 4. Bonifica e allineamento Unità NGGC 163 (ID 267) $unit163 = UnitaImmobiliare::where('stabile_id', $stabile->id)->where('scala', 'NGGC')->where('interno', '163')->first(); if ($unit163) { diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index d4b9a0c..8800479 100755 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -518,6 +518,200 @@ public function resolveHumanYearLabel(string $yearCode, string $codStabile): str return 'Anno ' . (is_numeric($yearCode) ? (int) $yearCode : $yearCode); } + public function getRecapitiMulticanaleTableProperty(): array + { + if (! $this->unita) { + return []; + } + + $tableRows = []; + + // 1. Dati da Anagrafiche e UAP + $activeEntities = array_merge( + $this->relazioniPerTipo['proprietari'] ?? [], + $this->relazioniPerTipo['inquilini'] ?? [] + ); + + foreach ($activeEntities as $entity) { + $nome = trim((string) ($entity['nome'] ?? '')); + $ruolo = (string) ($entity['tipo'] ?? ($entity['tipo_relazione'] ?? 'Condomino')); + if ($nome === '') { + continue; + } + + $anagId = (int) ($entity['persona_id'] ?? ($entity['soggetto_id'] ?? 0)); + $anag = $anagId > 0 ? DB::table('anagrafiche')->find($anagId) : null; + if (! $anag) { + $anag = DB::table('anagrafiche') + ->where(function ($q) use ($nome, $entity) { + $q->where('cognome', 'like', '%' . $nome . '%') + ->orWhere('nome', 'like', '%' . $nome . '%') + ->orWhere('ragione_sociale', 'like', '%' . $nome . '%'); + if (! empty($entity['codice_fiscale'])) { + $q->orWhere('codice_fiscale', trim((string) $entity['codice_fiscale'])); + } + }) + ->first(); + } + + if ($anag) { + if (! empty($anag->email)) { + $tableRows[] = [ + 'canale' => 'Email', + 'icon' => '✉️', + 'color' => 'bg-blue-50 text-blue-700 border-blue-200', + 'etichetta' => 'Principale', + 'soggetto' => $nome, + 'ruolo' => $ruolo, + 'valore' => $anag->email, + 'action_url' => 'mailto:' . $anag->email, + 'is_principal' => true, + ]; + } + if (! empty($anag->pec)) { + $tableRows[] = [ + 'canale' => 'Email PEC', + 'icon' => '🛡️', + 'color' => 'bg-emerald-50 text-emerald-700 border-emerald-200', + 'etichetta' => 'PEC Ufficiale', + 'soggetto' => $nome, + 'ruolo' => $ruolo, + 'valore' => $anag->pec, + 'action_url' => 'mailto:' . $anag->pec, + 'is_principal' => false, + ]; + } + if (! empty($anag->telefono)) { + $tableRows[] = [ + 'canale' => 'Cellulare / Tel', + 'icon' => '📱', + 'color' => 'bg-amber-50 text-amber-700 border-amber-200', + 'etichetta' => 'Personale', + 'soggetto' => $nome, + 'ruolo' => $ruolo, + 'valore' => $anag->telefono, + 'action_url' => 'tel:' . $anag->telefono, + 'is_principal' => false, + ]; + } + if (! empty($anag->indirizzo)) { + $indFull = trim($anag->indirizzo . ' ' . ($anag->cap ?? '') . ' ' . ($anag->citta ?? '') . ' ' . ($anag->provincia ? '(' . $anag->provincia . ')' : '')); + $tableRows[] = [ + 'canale' => 'Indirizzo', + 'icon' => '📍', + 'color' => 'bg-slate-50 text-slate-700 border-slate-200', + 'etichetta' => 'Residenza / Sede', + 'soggetto' => $nome, + 'ruolo' => $ruolo, + 'valore' => $indFull, + 'action_url' => null, + 'is_principal' => false, + ]; + } + } + } + + // 2. Dati da Legacy condomin_mirror + $legacyRow = $this->getLegacyCondominRow(); + if ($legacyRow && is_array($legacyRow->legacy_payload ?? null)) { + $p = $legacyRow->legacy_payload; + $condName = trim((string) ($legacyRow->nom_cond ?? 'Condomino')); + $inqName = trim((string) ($legacyRow->inquil_nome ?? ($legacyRow->inquilino ?? 'Inquilino'))); + + if (! empty($p['E_mail_condomino']) && ! $this->hasContactValue($tableRows, (string) $p['E_mail_condomino'])) { + $tableRows[] = [ + 'canale' => 'Email', + 'icon' => '✉️', + 'color' => 'bg-blue-50 text-blue-700 border-blue-200', + 'etichetta' => 'Email Condomino', + 'soggetto' => $condName, + 'ruolo' => 'Condomino', + 'valore' => trim((string) $p['E_mail_condomino']), + 'action_url' => 'mailto:' . trim((string) $p['E_mail_condomino']), + 'is_principal' => count($tableRows) === 0, + ]; + } + if (! empty($p['PEC_condomino']) && ! $this->hasContactValue($tableRows, (string) $p['PEC_condomino'])) { + $tableRows[] = [ + 'canale' => 'Email PEC', + 'icon' => '🛡️', + 'color' => 'bg-emerald-50 text-emerald-700 border-emerald-200', + 'etichetta' => 'PEC Condomino', + 'soggetto' => $condName, + 'ruolo' => 'Condomino', + 'valore' => trim((string) $p['PEC_condomino']), + 'action_url' => 'mailto:' . trim((string) $p['PEC_condomino']), + 'is_principal' => false, + ]; + } + if (! empty($p['Cell_cond']) && ! $this->hasContactValue($tableRows, (string) $p['Cell_cond'])) { + $tableRows[] = [ + 'canale' => 'Cellulare', + 'icon' => '📱', + 'color' => 'bg-amber-50 text-amber-700 border-amber-200', + 'etichetta' => 'Cellulare Condomino', + 'soggetto' => $condName, + 'ruolo' => 'Condomino', + 'valore' => trim((string) $p['Cell_cond']), + 'action_url' => 'tel:' . trim((string) $p['Cell_cond']), + 'is_principal' => false, + ]; + } + if (! empty($p['tel1']) && ! $this->hasContactValue($tableRows, (string) $p['tel1'])) { + $tableRows[] = [ + 'canale' => 'Telefono Fisso', + 'icon' => '☎️', + 'color' => 'bg-purple-50 text-purple-700 border-purple-200', + 'etichetta' => 'Fisso Condomino', + 'soggetto' => $condName, + 'ruolo' => 'Condomino', + 'valore' => trim((string) $p['tel1']), + 'action_url' => 'tel:' . trim((string) $p['tel1']), + 'is_principal' => false, + ]; + } + if (! empty($p['E_mail_inquilino']) && ! $this->hasContactValue($tableRows, (string) $p['E_mail_inquilino'])) { + $tableRows[] = [ + 'canale' => 'Email', + 'icon' => '✉️', + 'color' => 'bg-blue-50 text-blue-700 border-blue-200', + 'etichetta' => 'Email Inquilino', + 'soggetto' => $inqName, + 'ruolo' => 'Inquilino', + 'valore' => trim((string) $p['E_mail_inquilino']), + 'action_url' => 'mailto:' . trim((string) $p['E_mail_inquilino']), + 'is_principal' => false, + ]; + } + if (! empty($p['Cell_inq']) && ! $this->hasContactValue($tableRows, (string) $p['Cell_inq'])) { + $tableRows[] = [ + 'canale' => 'Cellulare', + 'icon' => '📱', + 'color' => 'bg-amber-50 text-amber-700 border-amber-200', + 'etichetta' => 'Cellulare Inquilino', + 'soggetto' => $inqName, + 'ruolo' => 'Inquilino', + 'valore' => trim((string) $p['Cell_inq']), + 'action_url' => 'tel:' . trim((string) $p['Cell_inq']), + 'is_principal' => false, + ]; + } + } + + return $tableRows; + } + + private function hasContactValue(array $rows, string $value): bool + { + $vTrim = trim(mb_strtolower($value)); + foreach ($rows as $r) { + if (trim(mb_strtolower((string) ($r['valore'] ?? ''))) === $vTrim) { + return true; + } + } + return false; + } + public function getTimelineSubentriCompletaProperty(): array { if (! $this->unita || ! $this->unita->stabile) { diff --git a/resources/views/filament/pages/unita-immobiliare.blade.php b/resources/views/filament/pages/unita-immobiliare.blade.php index 5887de3..297458b 100755 --- a/resources/views/filament/pages/unita-immobiliare.blade.php +++ b/resources/views/filament/pages/unita-immobiliare.blade.php @@ -215,178 +215,186 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s -
- - Indicatori principali - Superficie, millesimi, percentuale, valore -
- @foreach([ - ['Superficie', ($unita->superficie_commerciale ?? 0) . ' m²'], - ['Millesimi', $unita->millesimi_proprieta ?? $unita->millesimi_generali ?? 0], - ['Percentuale', ($unita->millesimi_proprieta ?? $unita->millesimi_generali) ? number_format((($unita->millesimi_proprieta ?? $unita->millesimi_generali)/1000)*100,2) . '%' : '0%'], - ['Valore stimato', ($unita->valore_commerciale_stimato ?? 0) . ' €'], - ] as [$label, $value]) - -
{{ $label }}
-
{{ $value }}
-
- @endforeach + {{-- 1. SCHEDE ANAGRAFICHE SOGGETTI ATTIVI & IDENTIFICAZIONE COMPATTA --}} +
+ {{-- Card Condomino / Proprietario Attivo --}} +
+
+
+ 🏢 + Condomino (Proprietario) +
+ 🟢 In Carica
- - - Identificazione - Tipologia e stato -
- @foreach([ - ['Tipologia', $unita->tipo_unita ?? 'ND'], - ['Stato occupazione', $unita->stato_occupazione ?? 'ND'], - ['Utilizzo', $unita->utilizzo_attuale ?? 'ND'], - ['Codice stabile', $unita->stabile?->codice_operatore ?? $unita->stabile?->codice_stabile ?? 'ND'], - ] as [$label, $value]) - -
{{ $label }}
-
{{ $value }}
-
- @endforeach +
+
+ {{ $condominoNome !== '' ? $condominoNome : '—' }} +
+
+ CF: {{ $condominoCf !== '' ? $condominoCf : '—' }} +
+
+ + Quota: {{ $relazioniPerTipo['proprietari'][0]['quota_label'] ?? '100,00' }}% + + @if($comproprietariCount > 0) + + +{{ $comproprietariCount }} comproprietari + + @endif +
+
+ Validità: {{ $relazioniPerTipo['proprietari'][0]['data_inizio'] ?? 'Gestione attiva' }} +
- +
+ + {{-- Card Inquilino / Conduttore Attivo --}} +
+
+
+ 👤 + Inquilino (Conduttore) +
+ @if($hasInquilino) + Locazione attiva + @else + Nessun inquilino + @endif +
+ +
+
+ {{ $inquilinoNome !== '' ? $inquilinoNome : 'Nessun inquilino registrato' }} +
+
+ CF: {{ $relazioniPerTipo['inquilini'][0]['codice_fiscale'] ?? '—' }} +
+ @if($hasInquilino) +
+ + Spese: {{ $relazioniPerTipo['inquilini'][0]['quota_label'] ?? '100,00' }}% + +
+
+ Subentro: {{ $relazioniPerTipo['inquilini'][0]['data_inizio'] ?? '—' }} +
+ @else +
+ L'unità risulta occupata direttamente dal proprietario. +
+ @endif +
+
+ + {{-- Card Dati Catastali & Identificazione Immobile --}} +
+
+
+ 🏛️ + Dati Catasto & Immobile +
+ Sub. {{ $unita->subalterno ?: '—' }} +
+ +
+
+ Tipologia: + {{ $unita->tipo_unita ?? 'Abitazione' }} +
+
+ Categoria & Rendita: + {{ $unita->categoria_catastale ?: '—' }} · € {{ number_format((float)($unita->rendita_catastale ?? 0), 2, ',', '.') }} +
+
+ Foglio / Particella: + Fg. {{ $unita->foglio ?: ($unita->stabile?->foglio ?: '—') }} / Part. {{ $unita->particella ?: ($unita->stabile?->particella_catasto ?: '—') }} +
+
+ Posizione fabbricato: + Scala {{ $unita->scala ?: '—' }} · Int. {{ $unita->interno ?: '—' }} · Piano {{ $unita->piano ?: '—' }} +
+
+
+ {{-- 2. SEZIONE RECAPITI MULTICANALE DINAMICI (MATRICE TABELLARE COMPATTA) --}} + @php + $multiRecapiti = $this->getRecapitiMulticanaleTableProperty(); + @endphp - - - Persone collegate - Proprietari, inquilini, altri -
- @foreach([ - 'inquilini' => ['label' => 'Inquilini', 'color' => 'text-sky-700'], - 'altri' => ['label' => 'Altri soggetti', 'color' => 'text-gray-700'], - ] as $key => $cfg) - @php $relazioni = $relazioniPerTipo[$key] ?? []; @endphp - -
{{ $cfg['label'] }} ({{ count($relazioni) }})
- @if(empty($relazioni)) -
Nessun soggetto.
- @else -
- @foreach($relazioni as $relazione) - @php - $tipoRawRelazione = strtolower(trim((string) ($relazione['tipo_raw'] ?? ''))); - $roleBadge = null; - if ($key === 'proprietari') { - $roleBadge = $tipoRawRelazione === 'comproprietario' - ? 'Comproprietario' - : 'Condomino di riferimento'; - } - @endphp - -
-
{{ $relazione['nome'] }}
- @if($roleBadge) - {{ $roleBadge }} - @endif -
-
CF: {{ $relazione['codice_fiscale'] ?? '—' }}
-
Quota: {{ $relazione['quota_label'] ?? '—' }}%
- @php - $dal = $relazione['data_inizio'] ?? null; - $al = $relazione['data_fine'] ?? null; - @endphp -
- Periodo: - @if($dal || $al) - @if($dal) Da {{ $dal }} @endif - @if($al) · fino a {{ $al }} @endif - @else - Da sempre - @endif -
- @if(!empty($relazione['ruolo_rate'])) -
Ruolo rate: ({{ $relazione['ruolo_rate'] }})
- @endif -
- @endforeach -
- @endif -
- @endforeach -
- -
-
Recapiti email per servizio
- @if(empty($recapitiServizio)) -
Nessun recapito email disponibile.
- @else -
- @foreach($recapitiServizio as $service) -
-
{{ $service['label'] ?? 'Servizio' }}
- @if(empty($service['rows'])) -
Nessun recapito risolto.
- @else -
- @foreach($service['rows'] as $recipient) -
-
{{ $recipient['email'] ?? '—' }}
-
{{ $recipient['persona'] ?: 'Recapito unità' }} · {{ $recipient['sourceLabel'] ?? 'Anagrafica persona' }}
-
- @endforeach -
- @endif -
- @endforeach +
+
+
+ 📱 +
+
Recapiti Multicanale Dinamici dell'Unità
+
Email, PEC, cellulari, telefoni e recapiti postali dei soggetti collegati
- @endif -
- -
-
- - Timeline Subentri e Gestioni Anteriore
-
Cronistoria titolari ed avvicendamenti storici per questa unità immobiliare tra le diverse gestioni
- - @if(empty($nominativiStorici)) -
Nessun avvicendamento o storico subentri registrato per questa unità.
- @else -
- @foreach($nominativiStorici as $item) -
-
-
- - {{ $item['periodo'] ?? 'Da sempre' }} - - - {{ $item['nominativo'] ?? $item['nome_completo'] ?? $item['nome'] ?? '—' }} - - @if(!empty($item['ruolo'])) - - {{ $item['ruolo'] }} - - @endif - @if(!empty($item['percentuale'])) - - Quota: {{ $item['percentuale'] }} - - @endif -
- @if(!empty($item['fonte'])) -
Fonte: {{ $item['fonte'] }}
- @endif -
- @if(!empty($item['detail'])) -
- {{ $item['detail'] }} -
- @endif -
- @endforeach -
- @endif + + {{ count($multiRecapiti) }} canali attivi +
- + + @if(empty($multiRecapiti)) +
+ Nessun recapito multicanale o contatto registrato per i soggetti di questa unità. +
+ @else +
+ + + + + + + + + + + + + @foreach($multiRecapiti as $rec) + + + + + + + + + @endforeach + +
CanaleEtichetta / UsoSoggetto CollegatoValore ContattoStatoAzioni Rapide
+ + {{ $rec['icon'] }} + {{ $rec['canale'] }} + + + {{ $rec['etichetta'] }} + + {{ $rec['soggetto'] }} + ({{ $rec['ruolo'] }}) + + {{ $rec['valore'] }} + + @if($rec['is_principal']) + ★ Principale + @else + + @endif + + @if(!empty($rec['action_url'])) + + Apri + + @endif +
+
+ @endif +
@endif @if($tab === 'nominativi') @@ -430,8 +438,15 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s Gestioni: {{ implode(' | ', $item['year_labels'] ?? $item['years']) }}
@if(!empty($item['dettaglio_subentro'])) -
- ℹ️ {{ $item['dettaglio_subentro'] }} + @php + $subentroText = $item['dettaglio_subentro']; + try { + $parsedDate = \Carbon\Carbon::parse($subentroText)->format('d/m/Y'); + $subentroText = 'Subentrato il: ' . $parsedDate; + } catch (\Throwable $e) {} + @endphp +
+ 📅 {{ $subentroText }}
@endif
@@ -439,9 +454,9 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
diff --git a/skill-netgescon/control-tower/CURRENT-205.md b/skill-netgescon/control-tower/CURRENT-205.md index 8e343b3..cb1511b 100644 --- a/skill-netgescon/control-tower/CURRENT-205.md +++ b/skill-netgescon/control-tower/CURRENT-205.md @@ -11,20 +11,15 @@ ## Obiettivo - Allineamento delle finestre temporali (`data_inizio`, `data_fine`) in `unita_anagrafica_periodo` per tutti i subentri storici dello Stabile 0013. - Risoluzione dinamica dei nominativi nel selettore a tendina e nella scheda unità in base all'`anno_gestione` attivo (2026, 2024, 2020, ecc.). 2. **Allineamento Catasto e Subentri Esemplari**: - - **Scala B Int. 6 (ID 247)**: - - Gestione 2026: visualizza l'attuale proprietario **COCCIA Simone** (CF: `CCCSMN74B06L120H`), allineato a Sister Catasto (**Fg. 403, Part. 60, Sub. 25, Cat. A/2, Cl. 03, Vani 2.5, Rendita € 742,41**, Proprietà 1/1). - - Gestioni fino al 2021 (es. 2020): visualizza il dante causa **DE SANTIS Fabio** (fino al 28/09/2021). - - **Scala B Int. 1 (ID 242)**: - - Gestione 2026: visualizza **Tabacchini Giuseppe** (subentrato dal 30/10/2025). - - Gestioni fino al 2025 (es. 2024): visualizza **PAVIA Monica** (con storico inquilino). - - **Scala B Int. 3 / B Int. 4**: - - Fino al 07/04/2026: **PICCOLO Pasqualina EREDI**. - - Dal 08/04/2026: **RDP S.R.L.**. -3. **Bonifica e Allineamento Stabile 0013**: - - Comando Artisan idempotente `php artisan gescon:bonifica-stabile-0013` esteso con riallineamento temporale automatico di tutti i subentri e dei dati catastali ufficiali AdE. -4. **Modernizzazione Blade Scheda Stabile e Compattazione Dropdown**: - - Nuova barra di navigazione unificata, Hero Header Banner dello Stabile con badge e quick actions, tab navigation pill-bar. - - Compattazione dei comproprietari multipli nel dropdown (`Primary (+N)`). + - **Scala B Int. 6 (ID 247)**: COCCIA Simone (2026) vs DE SANTIS Fabio (2020) con allineamento Sister Catasto (Sub. 25, Rendita € 742,41). + - **Scala B Int. 1 (ID 242)**: Tabacchini Giuseppe (2026) vs PAVIA Monica (2024). + - **Scala B Int. 3 / B Int. 4**: RDP S.R.L. limitata esclusivamente a queste due unità dal 08/04/2026, rimossa da tutte le altre unità di Scala B. + - **Scala A Int. 1 (ID 233)**: Spadavecchia Aldo (condomino) e Kiwa Cermet Spa (inquilino), bonificati i comproprietari incrociati orfani. +3. **Restyling Compatto Tab Riepilogo & Matrice Recapiti Multicanale**: + - Eliminazione dei grossi box "Indicatori principali" (Superficie, millesimi, valore) e dei 7 riquadri vuoti dei servizi. + - Inserimento schede compatte anagrafiche: Condomino (Proprietario) attivo con quota e CF, Inquilino attivo con quota spese, Dati Catasto e Fabbricato. + - Nuova tabella ad alta densità informativa **Recapiti Multicanale Dinamici** (Email, PEC, Cellulari, Telefoni Fissi, Indirizzo) con badge colorati, stato principale e pulsanti rapidi di apertura `mailto:` e `tel:`. + - Rimozione della vecchia timeline ridondante dal Riepilogo; il tab **Nominativi & Subentri** ora gestisce la cronistoria temporale completa con date formattate e pulsante diretto `[ 🧾 Vai all'Estratto Conto del Soggetto ]`. ## Output del Giro Operativo @@ -32,23 +27,20 @@ ## Output del Giro Operativo TASK_ID: task-87b64082c1 REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git BRANCH: stabilization/205-zero -COMMIT: cb6249b +COMMIT: in_pubblicazione FILE_O_AREE_TOCCATE: - app/Console/Commands/GesconBonificaStabile0013Command.php - app/Models/UnitaImmobiliare.php - app/Filament/Pages/UnitaImmobiliarePage.php -- app/Filament/Pages/Condomini/CatastoHub.php - resources/views/filament/pages/unita-immobiliare.blade.php -- resources/views/filament/pages/condomini/stabile.blade.php - tests/Feature/UnitaGestioneTemporaleTest.php - tests/Feature/CatastoHubDbDrivenTest.php TEST_ESEGUITI: - ./vendor/bin/pest tests/Feature/UnitaGestioneTemporaleTest.php tests/Feature/CatastoHubDbDrivenTest.php tests/Feature/UnitaCondominoInquilinoRoleToggleTest.php tests/Feature/UnitaImmobiliarePageTest.php tests/Feature/AnagraficaUnicaCanonicaTest.php tests/Feature/BenedettoBonificaIdempotenteTest.php tests/Feature/ControlTowerPollCommandTest.php (20 passed, 115 assertions) GATE_STATISTICS: - UNITA_REALI_0013: 42 unità canoniche con cronistoria temporale multi-gestione. -- TEMPORAL_DYNAMIC_OWNER: Risoluzione dinamica proprietario per anno di gestione su B-6, B-1, C-2, C-5, B-8, NGOT-103. -- CATASTO_SISTER_B6: Subalterno 25, Rendita 742,41, Intestato COCCIA Simone 1/1. -- DROPDOWN_COMPACT: Nominativi multipli compattati a singola riga `Nome (+N)`. +- RDP_CLEANUP: RDP S.R.L. circoscritta esclusivamente a B-3 e B-4, rimossa da B-1, B-2, B-5, B-7. +- MULTICHANNEL_CONTACTS_TABLE: Matrice recapiti multicanale tabellare compatta e responsive. - ZERO_MOCK: Dati catastali 100% DB-driven e Sister API live. BLOCCO_DATI: no BLOCCO_CONTRATTO: no @@ -57,5 +49,5 @@ ## Output del Giro Operativo ## Prossimo Passo per .200 (Validazione) - Eseguire il checkout del branch `stabilization/205-zero`. -- Eseguire `php artisan gescon:bonifica-stabile-0013` per convalidare la bonifica e l'allineamento temporale. +- Eseguire `php artisan gescon:bonifica-stabile-0013` per convalidare la bonifica, l'eliminazione dei comproprietari incrociati e l'allineamento temporale. - Eseguire la suite di test Pest (20 test, 115 asserzioni).