netgescon-master/docs/02-architettura-laravel/00-database-comuni-italiani/ANALISI-DATABASE-COMUNI.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

🔄 Versione: 2025.07.19-1644
🎯 Sistema pronto per Git distribuito
2025-07-19 16:44:47 +02:00

19 KiB

NETGESCON - DATABASE COMUNI ITALIANI

📋 OVERVIEW

Integrazione del database completo dei comuni italiani con licenza MIT per gestire indirizzi, calcolo codice fiscale, CAP e dati geografici. Sistema auto-aggiornante collegato alle fonti ISTAT ufficiali.

🗂️ STRUTTURA DATI FORNITA

Tabelle Principali (6 normalizzate + 2 aggregate)

1. gi_nazioni

Scopo: Calcolo codice fiscale per persone nate all'estero

CREATE TABLE gi_nazioni (
    sigla_nazione VARCHAR(10) PRIMARY KEY, -- IT, F, D, USA
    codice_belfiore VARCHAR(4), -- Codice per CF
    denominazione_nazione VARCHAR(255), -- ITALIA, FRANCIA
    denominazione_cittadinanza VARCHAR(255) -- Italiana, Francese
);

2. gi_regioni

Scopo: Classificazione geografica e menù a tendina

CREATE TABLE gi_regioni (
    codice_regione VARCHAR(3) PRIMARY KEY, -- Codice ISTAT
    ripartizione_geografica VARCHAR(50), -- Nord-ovest, Centro, Sud, Isole  
    denominazione_regione VARCHAR(100),
    tipologia_regione VARCHAR(50), -- statuto ordinario/speciale
    numero_province INT,
    numero_comuni INT,
    superficie_kmq DECIMAL(10,2)
);

3. gi_province

Scopo: Unità territoriali sovracomunali

CREATE TABLE gi_province (
    codice_sovracomunale VARCHAR(6) PRIMARY KEY, -- Codice ISTAT
    codice_regione VARCHAR(3), -- FK
    sigla_provincia VARCHAR(2), -- MI, RM, BO
    denominazione_provincia VARCHAR(255),
    tipologia_provincia VARCHAR(100), -- Provincia, Città metropolitana, etc
    numero_comuni INT,
    superficie_kmq DECIMAL(10,2)
);

4. gi_comuni (PRINCIPALE)

Scopo: Tutti i comuni italiani attivi

CREATE TABLE gi_comuni (
    codice_istat VARCHAR(6) PRIMARY KEY, -- Codice ISTAT alfanumerico
    sigla_provincia VARCHAR(2), -- FK
    denominazione_ita_altra VARCHAR(255), -- Bolzano/Bozen
    denominazione_ita VARCHAR(255), -- Bolzano
    denominazione_altra VARCHAR(255), -- Bozen
    flag_capoluogo ENUM('SI','NO'),
    codice_belfiore VARCHAR(4), -- PER CALCOLO CODICE FISCALE ⭐
    lat DECIMAL(10,8), -- Coordinate GPS
    lon DECIMAL(11,8),
    superficie_kmq DECIMAL(10,2),
    codice_sovracomunale VARCHAR(6) -- FK
);

5. gi_comuni_validita (STORICO)

Scopo: Comuni cessati - FONDAMENTALE per CF persone anziane

CREATE TABLE gi_comuni_validita (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    codice_istat VARCHAR(6),
    sigla_provincia VARCHAR(2),
    denominazione_ita VARCHAR(255),
    codice_belfiore VARCHAR(4), -- CHIAVE per CF
    data_inizio_validita DATE,
    data_fine_validita DATE, -- NULL = attivo
    stato_validita ENUM('Attivo','Inattivo')
);

6. gi_cap

Scopo: Relazione Comuni-CAP (molti a molti)

CREATE TABLE gi_cap (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    codice_istat VARCHAR(6), -- FK
    cap VARCHAR(5)
);

Tabelle Aggregate (Per performance)

7. gi_comuni_nazioni_cf (PER CALCOLO CF)

Scopo: Unisce comuni + nazioni per calcolo CF completo

CREATE TABLE gi_comuni_nazioni_cf (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    sigla_provincia VARCHAR(2), -- EE per estero
    denominazione_ita VARCHAR(255), -- Comune o Nazione
    codice_belfiore VARCHAR(4), -- CODICE PER CF ⭐
    data_inizio_validita DATE,
    data_fine_validita DATE
);

8. gi_comuni_cap (PER AUTOCOMPLETAMENTO)

Scopo: Tutti i dati aggregati per ricerca rapida

CREATE TABLE gi_comuni_cap (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    codice_istat VARCHAR(6),
    denominazione_ita_altra VARCHAR(255),
    denominazione_ita VARCHAR(255),
    cap VARCHAR(5),
    sigla_provincia VARCHAR(2),
    denominazione_provincia VARCHAR(255),
    codice_regione VARCHAR(3),
    denominazione_regione VARCHAR(100),
    lat DECIMAL(10,8),
    lon DECIMAL(11,8),
    codice_belfiore VARCHAR(4), -- ⭐ SEMPRE PRESENTE
    flag_capoluogo ENUM('SI','NO')
);

🧮 ALGORITMO CALCOLO CODICE FISCALE

Struttura Codice Fiscale (16 caratteri)

RSSMRA80E15H501T
└─┬─┘└┬┘└┬┘└┬──┘└┬
  │   │  │  │   │
  │   │  │  │   └─ Carattere controllo (T)
  │   │  │  └───── Codice Belfiore comune (H501)
  │   │  └──────── Giorno nascita + 40 se donna (15)
  │   └─────────── Mese nascita (E = Maggio)
  └─────────────── Anno nascita (80 = 1980)

Implementazione Algoritmo PHP

class CodiceFiscaleCalculator 
{
    // Tabelle conversione per algoritmo
    private static $mesi = [
        1 => 'A', 2 => 'B', 3 => 'C', 4 => 'D', 5 => 'E', 6 => 'H',
        7 => 'L', 8 => 'M', 9 => 'P', 10 => 'R', 11 => 'S', 12 => 'T'
    ];
    
    private static $caratteriDispari = [
        '0' => 1, '1' => 0, '2' => 5, '3' => 7, '4' => 9, '5' => 13,
        '6' => 15, '7' => 17, '8' => 19, '9' => 21, 'A' => 1, 'B' => 0,
        'C' => 5, 'D' => 7, 'E' => 9, 'F' => 13, 'G' => 15, 'H' => 17,
        'I' => 19, 'J' => 21, 'K' => 2, 'L' => 4, 'M' => 18, 'N' => 20,
        'O' => 11, 'P' => 3, 'Q' => 6, 'R' => 8, 'S' => 12, 'T' => 14,
        'U' => 16, 'V' => 10, 'W' => 22, 'X' => 25, 'Y' => 24, 'Z' => 23
    ];
    
    private static $caratteriPari = [
        '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5,
        '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 0, 'B' => 1,
        'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7,
        'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
        'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19,
        'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25
    ];
    
    private static $caratteriControllo = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    
    /**
     * Calcola il codice fiscale completo
     */
    public static function calcola($nome, $cognome, $dataNascita, $sesso, $codiceBelfiore) 
    {
        $cf = '';
        
        // 1. Cognome (3 caratteri)
        $cf .= self::calcolaCognome($cognome);
        
        // 2. Nome (3 caratteri)  
        $cf .= self::calcolaNome($nome);
        
        // 3. Anno nascita (2 caratteri)
        $cf .= substr($dataNascita->format('Y'), -2);
        
        // 4. Mese nascita (1 carattere)
        $cf .= self::$mesi[(int)$dataNascita->format('n')];
        
        // 5. Giorno nascita (2 caratteri) + 40 se donna
        $giorno = (int)$dataNascita->format('d');
        if (strtoupper($sesso) === 'F') {
            $giorno += 40;
        }
        $cf .= str_pad($giorno, 2, '0', STR_PAD_LEFT);
        
        // 6. Codice Belfiore (4 caratteri)
        $cf .= strtoupper($codiceBelfiore);
        
        // 7. Carattere di controllo
        $cf .= self::calcolaCarattereControllo($cf);
        
        return $cf;
    }
    
    /**
     * Calcola le prime 3 consonanti del cognome
     */
    private static function calcolaCognome($cognome) 
    {
        return self::estraiCaratteri($cognome, 3);
    }
    
    /**
     * Calcola caratteri nome (regole particolari)
     */
    private static function calcolaNome($nome) 
    {
        $consonanti = self::estraiConsonanti($nome);
        $vocali = self::estraiVocali($nome);
        
        // Se consonanti >= 4, prendi 1°, 3°, 4°
        if (strlen($consonanti) >= 4) {
            return substr($consonanti, 0, 1) . substr($consonanti, 2, 2);
        }
        
        // Altrimenti segui regola standard
        return self::estraiCaratteri($nome, 3);
    }
    
    /**
     * Estrae caratteri secondo regole CF
     */
    private static function estraiCaratteri($stringa, $lunghezza) 
    {
        $stringa = strtoupper(preg_replace('/[^A-Za-z]/', '', $stringa));
        
        $consonanti = self::estraiConsonanti($stringa);
        $vocali = self::estraiVocali($stringa);
        
        $risultato = $consonanti . $vocali;
        
        // Pad con X se necessario
        return str_pad(substr($risultato, 0, $lunghezza), $lunghezza, 'X');
    }
    
    /**
     * Estrae consonanti
     */
    private static function estraiConsonanti($stringa) 
    {
        return preg_replace('/[AEIOU]/', '', strtoupper($stringa));
    }
    
    /**
     * Estrae vocali
     */
    private static function estraiVocali($stringa) 
    {
        return preg_replace('/[^AEIOU]/', '', strtoupper($stringa));
    }
    
    /**
     * Calcola carattere di controllo
     */
    private static function calcolaCarattereControllo($codice15Caratteri) 
    {
        $somma = 0;
        
        for ($i = 0; $i < 15; $i++) {
            $carattere = $codice15Caratteri[$i];
            
            if ($i % 2 === 0) { // Posizione dispari (0, 2, 4, ...)
                $somma += self::$caratteriDispari[$carattere];
            } else { // Posizione pari (1, 3, 5, ...)
                $somma += self::$caratteriPari[$carattere];
            }
        }
        
        $resto = $somma % 26;
        return self::$caratteriControllo[$resto];
    }
    
    /**
     * Valida un codice fiscale esistente
     */
    public static function valida($codiceFiscale, $nome = null, $cognome = null, $dataNascita = null, $sesso = null, $codiceBelfiore = null) 
    {
        // Verifica lunghezza
        if (strlen($codiceFiscale) !== 16) {
            return false;
        }
        
        // Verifica carattere controllo
        $primi15 = substr($codiceFiscale, 0, 15);
        $carattereControllo = substr($codiceFiscale, 15, 1);
        
        if (self::calcolaCarattereControllo($primi15) !== $carattereControllo) {
            return false;
        }
        
        // Se sono forniti i dati anagrafici, verifica coerenza
        if ($nome && $cognome && $dataNascita && $sesso && $codiceBelfiore) {
            $cfCalcolato = self::calcola($nome, $cognome, $dataNascita, $sesso, $codiceBelfiore);
            return $codiceFiscale === $cfCalcolato;
        }
        
        return true;
    }
    
    /**
     * Trova codice Belfiore per comune e data nascita
     */
    public static function trovaCodiceBelfiore($comuneId, $dataNascita) 
    {
        // Cerca in gi_comuni_validita per gestire comuni cessati
        $comune = DB::table('gi_comuni_validita')
            ->where('codice_istat', $comuneId)
            ->where(function($query) use ($dataNascita) {
                $query->where('data_inizio_validita', '<=', $dataNascita)
                      ->where(function($q) use ($dataNascita) {
                          $q->whereNull('data_fine_validita')
                            ->orWhere('data_fine_validita', '>=', $dataNascita);
                      });
            })
            ->first();
            
        return $comune ? $comune->codice_belfiore : null;
    }
}

🔄 SISTEMA AUTO-AGGIORNAMENTO

Fonti Ufficiali

  • ISTAT: Dataset comuni italiani
  • Agenzia Entrate: Codici Belfiore
  • Poste Italiane: Database CAP

Procedura Aggiornamento Automatico

class AggiornamentoComuni
{
    /**
     * Download e import automatico da ISTAT
     */
    public function aggiornaDatasetCompleto() 
    {
        Log::info('Inizio aggiornamento dataset comuni');
        
        // 1. Download file ufficiali
        $dataset = $this->downloadDatasetISTAT();
        
        // 2. Parsing e validazione
        $datiValidati = $this->validaEparsaDati($dataset);
        
        // 3. Confronto con dati esistenti
        $differenze = $this->confrontaConEsistente($datiValidati);
        
        // 4. Applicazione modifiche
        if ($differenze['nuovi'] || $differenze['modificati'] || $differenze['cessati']) {
            $this->applicaModifiche($differenze);
            $this->inviaReportAggiornamento($differenze);
        }
        
        Log::info('Aggiornamento completato', ['differenze' => $differenze]);
    }
    
    /**
     * Aggiornamento incrementale (solo nuovi/modificati)
     */
    public function aggiornamentoIncrementale() 
    {
        // Check solo comuni modificati nell'ultimo mese
        $ultimoAggiornamento = Setting::get('ultimo_aggiornamento_comuni');
        
        // Logica di confronto e import selettivo
    }
}

Scheduling Automatico

// In app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Aggiornamento completo mensile
    $schedule->command('comuni:aggiorna-completo')
             ->monthlyOn(1, '02:00')
             ->emailOutputOnFailure('admin@netgescon.it');
             
    // Check incrementale settimanale  
    $schedule->command('comuni:aggiorna-incrementale')
             ->weeklyOn(1, '03:00');
}

🏗️ INTEGRAZIONE IN NETGESCON

Tabelle NetGesCon da Modificare

-- Modifica tabella persone per FK a comuni
ALTER TABLE persone 
ADD COLUMN luogo_nascita_comune_id VARCHAR(6),
ADD COLUMN residenza_comune_id VARCHAR(6),
ADD COLUMN domicilio_comune_id VARCHAR(6),
ADD COLUMN corrispondenza_comune_id VARCHAR(6),
ADD FOREIGN KEY fk_nascita_comune (luogo_nascita_comune_id) REFERENCES gi_comuni(codice_istat),
ADD FOREIGN KEY fk_residenza_comune (residenza_comune_id) REFERENCES gi_comuni(codice_istat);

-- Modifica tabella stabili
ALTER TABLE stabili
ADD COLUMN comune_id VARCHAR(6),
ADD FOREIGN KEY fk_stabile_comune (comune_id) REFERENCES gi_comuni(codice_istat);

Helper Class per Comune

class ComuneHelper 
{
    /**
     * Ricerca comuni con autocompletamento
     */
    public static function ricercaComuni($query, $limit = 10) 
    {
        return DB::table('gi_comuni_cap')
            ->where('denominazione_ita', 'LIKE', "%$query%")
            ->orWhere('cap', 'LIKE', "$query%")
            ->orWhere('sigla_provincia', '=', strtoupper($query))
            ->limit($limit)
            ->get();
    }
    
    /**
     * Dati completi comune per codice ISTAT
     */
    public static function datiComune($codiceIstat) 
    {
        return DB::table('gi_comuni_cap')
            ->where('codice_istat', $codiceIstat)
            ->first();
    }
    
    /**
     * Calcola distanza tra due comuni
     */
    public static function distanzaComuni($comune1Id, $comune2Id) 
    {
        $c1 = self::datiComune($comune1Id);
        $c2 = self::datiComune($comune2Id);
        
        return self::calcolaDistanzaGPS($c1->lat, $c1->lon, $c2->lat, $c2->lon);
    }
    
    /**
     * Trova comuni limitrofi
     */
    public static function comuniLimitrofi($comuneId, $raggioKm = 25) 
    {
        $comune = self::datiComune($comuneId);
        
        // Query con calcolo distanza GPS
        return DB::table('gi_comuni_cap')
            ->select('*')
            ->selectRaw("
                ( 6371 * acos( cos( radians(?) ) 
                * cos( radians( lat ) ) 
                * cos( radians( lon ) - radians(?) ) 
                + sin( radians(?) ) 
                * sin( radians( lat ) ) ) ) AS distanza_km
            ", [$comune->lat, $comune->lon, $comune->lat])
            ->having('distanza_km', '<', $raggioKm)
            ->orderBy('distanza_km')
            ->get();
    }
}

📱 COMPONENTI UI

Autocompletamento Indirizzo

<!-- Component: address-autocomplete.blade.php -->
<div class="form-group">
    <label for="comune">Comune</label>
    <input type="text" id="comune-search" class="form-control" 
           placeholder="Digita nome comune o CAP..." autocomplete="off">
    <input type="hidden" id="comune-id" name="comune_id">
    
    <div id="comuni-dropdown" class="dropdown-menu" style="display:none;">
        <!-- Risultati via AJAX -->
    </div>
</div>

<script>
$('#comune-search').on('input', function() {
    const query = $(this).val();
    
    if (query.length >= 2) {
        $.ajax({
            url: '/api/comuni/search',
            data: { q: query },
            success: function(comuni) {
                let html = '';
                comuni.forEach(comune => {
                    html += `
                        <a href="#" class="dropdown-item comune-option" 
                           data-id="${comune.codice_istat}"
                           data-denominazione="${comune.denominazione_ita}"
                           data-cap="${comune.cap}"
                           data-provincia="${comune.sigla_provincia}">
                            ${comune.denominazione_ita} (${comune.cap}) - ${comune.sigla_provincia}
                        </a>
                    `;
                });
                $('#comuni-dropdown').html(html).show();
            }
        });
    }
});

$(document).on('click', '.comune-option', function(e) {
    e.preventDefault();
    const $this = $(this);
    
    $('#comune-search').val($this.data('denominazione'));
    $('#comune-id').val($this.data('id'));
    $('#cap').val($this.data('cap')); // Auto-compila CAP
    $('#provincia').val($this.data('provincia')); // Auto-compila Provincia
    $('#comuni-dropdown').hide();
});
</script>

📊 UTILIZZI IN NETGESCON

1. Calcolo Codice Fiscale

  • Input persona → Verifica CF automatico
  • Persone anziane → Cerca comune validità storica
  • Stranieri → Gestione nazioni estere

2. Gestione Indirizzi

  • Autocompletamento indirizzi completi
  • Validazione CAP-Comune
  • Geolocalizzazione stabili

3. Reportistica Geografica

  • Distribuzione condomini per area
  • Analisi bacino utenza amministratori
  • Ricerca fornitori zona

4. Comunicazioni Legali

  • Indirizzi conformi normative
  • CAP sempre corretti
  • Certificazioni conformità dati

🚀 ROADMAP INTEGRAZIONE

Fase 1 - Import Database (Sprint 1)

  • Download dataset MIT completo
  • Import tabelle in NetGesCon
  • Setup auto-aggiornamento mensile
  • Test algoritmo codice fiscale

Fase 2 - Integrazione Base (Sprint 2)

  • Modifica tabelle esistenti con FK
  • Helper class ComuneHelper
  • API endpoint ricerca comuni
  • Component autocompletamento

Fase 3 - Validazioni (Sprint 3)

  • Validazione CF all'inserimento persona
  • Controllo coerenza CAP-Comune
  • Import massivo con validazione
  • Dashboard errori dati

Fase 4 - Funzionalità Avanzate (Sprint 4)

  • Geolocalizzazione e mappe
  • Ricerca per prossimità
  • Analytics geografiche
  • Export dati conformi

Data Analisi: 14/07/2025
Stato: PRONTO PER IMPLEMENTAZIONE
Priorità: ALTA - Richiesto per anagrafica e CF