{
  "id": "migration-01a-cities",
  "title": "Migration 01A — cities",
  "description": "Catálogo local das cidades brasileiras identificadas pelo código IBGE.",
  "type": "migration-reference",
  "status": "implemented",
  "visibility": "public",
  "tags": [
    "sge/migrations",
    "sge/localizacao",
    "sge/ibge"
  ],
  "related": [
    "enum-brazilianstate",
    "migration-01-addresses",
    "migration-22-holidays"
  ],
  "sourceRefs": [
    "https://github.com/sge-suite/sge/blob/master/app/Models/City.php",
    "https://github.com/sge-suite/sge/blob/master/app/Concerns/CityValidationRules.php",
    "https://github.com/sge-suite/sge/blob/master/app/Console/Commands/FetchCities.php",
    "https://github.com/sge-suite/sge/blob/master/database/seeders/CitySeeder.php",
    "https://github.com/sge-suite/sge/blob/master/config/services.php",
    "https://github.com/sge-suite/sge/blob/master/database/data/cities.json"
  ],
  "authors": [],
  "updated": null,
  "diagram": null,
  "body": "> [!success] Estado\n> Implementada. A tabela de referência e o catálogo local estão disponíveis no projeto; a carga é determinística e não depende de serviço externo.\n\n## Contrato\n\n| Coluna | Tipo PostgreSQL | Nulo | Chaves/índices | Regra |\n| --- | --- | --- | --- | --- |\n| `id` | `bigint` | não | `PK` | Identificador interno usado pelas FKs. |\n| `ibge_code` | `char(7)` | não | `UQ` | Código oficial do município. |\n| `name` | `varchar(120)` | não | índice `(state, name)` | Nome oficial usado nos selects e relatórios. |\n| `state` | `char(2)` | não | índice | Sigla da UF validada por [`BrazilianState`](doc:enum-brazilianstate). |\n| `created_at` | `timestamp(0)` | não | — | Inclusão no catálogo. |\n| `updated_at` | `timestamp(0)` | não | — | Última sincronização. |\n\nO catálogo nacional tem 5.571 municípios e é importado localmente. O catálogo conferido está salvo em `database/data/cities.json` e versionado junto com o projeto. A migration `create_cities_table`, o model `City` e o `CitySeeder` estão implementados: o seeder lê esse arquivo e insere/atualiza as cidades de forma idempotente pelo `ibge_code`, sem apagar registros existentes. `CityValidationRules` centraliza código IBGE, nome e UF e é aplicado ao salvar o model, ao validar o catálogo no seeder e ao aceitar respostas do comando de coleta.\n\nNão há chamada à BrasilAPI para consultas de cidades nem para o cálculo de feriados. A API/IBGE pode ser usada para obter ou revisar o arquivo versionado antes de uma nova implantação. A busca opcional por CEP, documentada em [`addresses`](doc:migration-01-addresses), fica para uma etapa futura; quando existir, a cidade continuará sendo resolvida no catálogo local, sem criação de municípios pela resposta externa.\n\n`City` usa Scout/Meilisearch para a busca textual no select de cidade da gestão de campi. O índice contém ID, nome, UF e código IBGE, com `state` filtrável; o filtro de UF é aplicado no mecanismo de busca. Não há consulta inicial para carregar opções. A busca começa com 2 caracteres, após debounce de 500 ms, e retorna até 20 cidades. O nome da cidade já selecionada é preservado na edição. O catálogo no banco continua sendo a fonte da seleção e das FKs; a cidade nunca é um enum nem texto livre. A indexação Eloquent usa fila após commit. Após importar o catálogo existente, execute `scout:import 'App\\Models\\City'` e processe a fila, além de sincronizar as configurações dos índices.\n\nO código IBGE é a identidade da cidade. O `CitySeeder` pode atualizar o nome oficial associado ao mesmo código em uma carga controlada; documentos gerados preservam os valores formatados no snapshot da geração, e endereços históricos continuam apontando para a mesma identidade municipal. Não há enum de cidades nem exclusão lógica como operação normal.\n\n## Geração do catálogo e carga local\n\nO comando Artisan `cities:fetch` consulta a lista de UFs, busca os municípios de cada uma, valida os códigos IBGE e gera o arquivo local. A configuração da URL fica em `services.brasil_api.base_url`; não há URL duplicada dentro do comando.\n\n```bash\nphp artisan cities:fetch\nphp artisan cities:fetch --force\n```\n\nO comando não substitui um catálogo existente sem `--force`. A opção `--output` permite gerar um arquivo temporário ou alternativo para revisão. A gravação do catálogo é atômica: falhas durante a coleta não deixam um JSON parcial no caminho final.\n\nO arquivo `database/data/cities.json` contém somente dados de referência, em formato estável e revisável:\n\n```json\n[\n  {\n    \"ibge_code\": \"4300109\",\n    \"name\": \"Agudo\",\n    \"state\": \"RS\"\n  }\n]\n```\n\nO `CitySeeder` implementado:\n\n- lê o arquivo local sem fazer requisições de rede;\n- valida o código IBGE com sete dígitos, a UF contra `BrazilianState`, o nome não vazio e o limite da coluna usando `CityValidationRules`;\n- insere ou atualiza pelo `ibge_code`, em lotes de 500 registros e dentro de uma transação;\n- rejeita códigos IBGE duplicados no catálogo;\n- nunca apaga cidades automaticamente, porque endereços e feriados podem referenciá-las;\n- é chamado explicitamente pelo seeder principal.\n\nPara carregar ou recarregar o catálogo, use:\n\n```bash\nphp artisan db:seed --class=Database\\\\Seeders\\\\CitySeeder\n```\n\nA obtenção do arquivo ocorre fora do fluxo de seed, durante o desenvolvimento ou em uma sincronização administrativa explícita. Depois de revisado e commitado, qualquer ambiente consegue popular o banco de forma determinística e reproduzível.\n\n## Integridade\n\n- `PRIMARY KEY (id)`;\n- `UNIQUE (ibge_code)`;\n- `INDEX (state)` e `INDEX (state, name)` para filtros por UF e consultas locais por nome;\n- a validação de `state` usa os 27 valores de [`BrazilianState`](doc:enum-brazilianstate);\n- a UF do endereço é obtida por `city_id`, sem campo estadual duplicado; feriados municipais devem pertencer à UF da cidade selecionada.\n\nO model `City` declara `holidays()` para os feriados municipais e `addresses()` para os endereços. `CityFactory` cria fixtures independentes de rede e do catálogo nacional nos testes de backend.\n\n## Checklist\n\n- [x] Criar `create_cities_table` antes de `create_addresses_table`.\n- [x] Criar Model `City` com cast de `state`.\n- [x] Adicionar `City::addresses()` e `CityFactory` para fixtures de backend.\n- [x] Gerar e revisar `database/data/cities.json` com o catálogo nacional pelo comando Artisan.\n- [x] Criar `CitySeeder` idempotente, executável sem rede e seguro para reexecução.\n- [x] Testar migration, carga local, unicidade do código IBGE e filtro por UF/nome.\n- [x] Testar campos inválidos do catálogo e preservar dados existentes quando a validação falha.",
  "sections": [
    {
      "id": "contrato",
      "level": 2,
      "title": "Contrato",
      "text": "| Coluna | Tipo PostgreSQL | Nulo | Chaves/índices | Regra | | --- | --- | --- | --- | --- | | `id` | `bigint` | não | `PK` | Identificador interno usado pelas FKs. | | `ibge_code` | `char(7)` | não | `UQ` | Código oficial do município. | | `name` | `varchar(120)` | não | índice `(state, name)` | Nome oficial usado nos selects e relatórios. | | `state` | `char(2)` | não | índice | Sigla da UF validada por [`BrazilianState`](doc:enum-brazilianstate). | | `created_at` | `timestamp(0)` | não | — | Inclusão no catálogo. | | `updated_at` | `timestamp(0)` | não | — | Última sincronização. |  O catálogo nacional tem 5.571 municípios e é importado localmente. O catálogo conferido está salvo em `database/data/cities.json` e versionado junto com o projeto. A migration `create_cities_table`, o model `City` e o `CitySeeder` estão implementados: o seeder lê esse arquivo e insere/atualiza as cidades de forma idempotente pelo `ibge_code`, sem apagar registros existentes. `CityValidationRules` centraliza código IBGE, nome e UF e é aplicado ao salvar o model, ao validar o catálogo no seeder e ao aceitar respostas do comando de coleta.  Não há chamada à BrasilAPI para consultas de cidades nem para o cálculo de feriados. A API/IBGE pode ser usada para obter ou revisar o arquivo versionado antes de uma nova implantação. A busca opcional por CEP, documentada em [`addresses`](doc:migration-01-addresses), fica para uma etapa futura; quando existir, a cidade continuará sendo resolvida no catálogo local, sem criação de municípios pela resposta externa.  `City` usa Scout/Meilisearch para a busca textual no select de cidade da gestão de campi. O índice contém ID, nome, UF e código IBGE, com `state` filtrável; o filtro de UF é aplicado no mecanismo de busca. Não há consulta inicial para carregar opções. A busca começa com 2 caracteres, após debounce de 500 ms, e retorna até 20 cidades. O nome da cidade já selecionada é preservado na edição. O catálogo no banco continua sendo a fonte da seleção e das FKs; a cidade nunca é um enum nem texto livre. A indexação Eloquent usa fila após commit. Após importar o catálogo existente, execute `scout:import 'App\\Models\\City'` e processe a fila, além de sincronizar as configurações dos índices.  O código IBGE é a identidade da cidade. O `CitySeeder` pode atualizar o nome oficial associado ao mesmo código em uma carga controlada; documentos gerados preservam os valores formatados no snapshot da geração, e endereços históricos continuam apontando para a mesma identidade municipal. Não há enum de cidades nem exclusão lógica como operação normal.",
      "line": 4
    },
    {
      "id": "geracao-do-catalogo-e-carga-local",
      "level": 2,
      "title": "Geração do catálogo e carga local",
      "text": "O comando Artisan `cities:fetch` consulta a lista de UFs, busca os municípios de cada uma, valida os códigos IBGE e gera o arquivo local. A configuração da URL fica em `services.brasil_api.base_url`; não há URL duplicada dentro do comando.   O comando não substitui um catálogo existente sem `--force`. A opção `--output` permite gerar um arquivo temporário ou alternativo para revisão. A gravação do catálogo é atômica: falhas durante a coleta não deixam um JSON parcial no caminho final.  O arquivo `database/data/cities.json` contém somente dados de referência, em formato estável e revisável:   O `CitySeeder` implementado:  - lê o arquivo local sem fazer requisições de rede; - valida o código IBGE com sete dígitos, a UF contra `BrazilianState`, o nome não vazio e o limite da coluna usando `CityValidationRules`; - insere ou atualiza pelo `ibge_code`, em lotes de 500 registros e dentro de uma transação; - rejeita códigos IBGE duplicados no catálogo; - nunca apaga cidades automaticamente, porque endereços e feriados podem referenciá-las; - é chamado explicitamente pelo seeder principal.  Para carregar ou recarregar o catálogo, use:   A obtenção do arquivo ocorre fora do fluxo de seed, durante o desenvolvimento ou em uma sincronização administrativa explícita. Depois de revisado e commitado, qualquer ambiente consegue popular o banco de forma determinística e reproduzível.",
      "line": 23
    },
    {
      "id": "integridade",
      "level": 2,
      "title": "Integridade",
      "text": "- `PRIMARY KEY (id)`; - `UNIQUE (ibge_code)`; - `INDEX (state)` e `INDEX (state, name)` para filtros por UF e consultas locais por nome; - a validação de `state` usa os 27 valores de [`BrazilianState`](doc:enum-brazilianstate); - a UF do endereço é obtida por `city_id`, sem campo estadual duplicado; feriados municipais devem pertencer à UF da cidade selecionada.  O model `City` declara `holidays()` para os feriados municipais e `addresses()` para os endereços. `CityFactory` cria fixtures independentes de rede e do catálogo nacional nos testes de backend.",
      "line": 63
    },
    {
      "id": "checklist",
      "level": 2,
      "title": "Checklist",
      "text": "- [x] Criar `create_cities_table` antes de `create_addresses_table`. - [x] Criar Model `City` com cast de `state`. - [x] Adicionar `City::addresses()` e `CityFactory` para fixtures de backend. - [x] Gerar e revisar `database/data/cities.json` com o catálogo nacional pelo comando Artisan. - [x] Criar `CitySeeder` idempotente, executável sem rede e seguro para reexecução. - [x] Testar migration, carga local, unicidade do código IBGE e filtro por UF/nome. - [x] Testar campos inválidos do catálogo e preservar dados existentes quando a validação falha.",
      "line": 73
    }
  ],
  "sourcePath": "content/migration-01a-cities.md",
  "visuals": [],
  "apiVersion": 1
}
