# Unidade Gestora — Regras de Negócio e Portabilidade

## 1. Regras não óbvias

| # | Regra | Onde está | Efeito observável |
|---|---|---|---|
| U-R1 | `name` e `code` são **obrigatórios** e limitados a **120 caracteres**. | `v6/app/Http/Requests/ManagementUnitRequest.php:29-37` | O formulário não grava sem os dois campos. |
| U-R2 | Na **criação**, `name` e `code` devem ser únicos em `management_units`. | mesmo arquivo, linhas 30-31 | Mensagens "O nome já está em uso." / "O código já está em uso.". |
| U-R3 | Na **atualização**, a unicidade **não é verificada** (a regra aponta para outra tabela). | mesmo arquivo, linhas 33-34 | É possível salvar duas unidades com o mesmo nome ou código (bug U1). |
| U-R4 | A exclusão é **lógica** (`SoftDeletes`): a linha recebe `deleted_at` e some da listagem (a view SQL filtra `deleted_at IS NULL`). | `v6/app/Http/Model/Prefecture/ManagementUnit.php:12`, `v6/database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php:19` | O registro continua no banco e, se tiver `biddings`, a FK permanece satisfeita. |
| U-R5 | A listagem do CMS **não mostra o `code`**. | view SQL `unidade_gestora` (só `value`, `nome`, `id`) | O operador precisa abrir a edição para ver o código. |
| U-R6 | A ordenação da listagem é por **nome ascendente** (`runDatatables('unidade-gestora', collums, true, 'ASC', null, true)`), apesar de a view ser criada com `created_at DESC`. | `v6/public/assets/js/custom/contents/licitacoes/unidades-gestoras/datatable.js` | A coluna "Nome" é a ordenável. |
| U-R7 | Toda abertura de tela incrementa o contador de cliques do slug **`licitacao`** (não `unidade-gestora`). | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:63,73` | O "Dashboard de módulos" atribui as visitas ao módulo de Licitações (bug U2). |
| U-R8 | A exclusão em lote registra um log de atividade com o texto **"Exclusão de uniade gestora"**. | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:36` | Grafia incorreta no log (bug U3). |
| U-R9 | O `code` não tem formato nem tipo definido: o seed usa `'1'` e `'2'` (texto). | `v6/database/seeds/ManagementUnitTableSeeder.php` | O campo é livre; a prefeitura pode usar qualquer convenção. |
| U-R10 | `biddings.management_units_id` é **NOT NULL**: toda licitação precisa de uma unidade. | `v6/database/migrations/2018_04_02_181804_create_biddings_table.php:27` e dump do banco | O `select` do formulário de licitação é obrigatório (`BiddingRequest`). |
| U-R11 | O módulo está **desabilitado** no menu do CMS (`modulos.status = false`). | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php:322-327` e banco | As telas continuam acessíveis por URL, mas o módulo não aparece na navegação nem na matriz de permissões. |
| U-R12 | A exclusão em lote responde `true`/`false` (JSON cru); a exclusão individual responde `{"success": "Unidade Gestora deletada com sucesso."}` / `{"erro": "Ocorreu um erro ao tentar salvar no banco de dados."}`. | `v6/app/Repositories/Repository.php:106,109` e `ManagementUnitController@destroy` | Contratos de resposta diferentes para ações equivalentes. |

## 2. Acoplamentos

### 2.1 Dependências de outros módulos

| Módulo | Dependência |
|---|---|
| **Licitações** | `biddings.management_units_id` (FK `NOT NULL`); o `select` do formulário de licitação é preenchido por `ManagementUnit::get(['name','id'])` (`v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php:74,136`). Ver `../licitacoes/`. |
| **Licitações Antigas** | `biddings` também é consultada por esse módulo; a FK é a mesma. |
| **Dashboard / módulos** | `modulos.status` e `menu_orders.clicks` alimentam o painel de acessos. |
| **Perfis e permissões** | `resources.slug = 'unidade-gestora'` + `modulos.modulo_id = 50` montam a matriz de permissões. |

### 2.2 O módulo **é** dependido por

- `Prefecture\Bidding\BiddingController` (select do formulário e log de exclusão).
- `Prefecture\Bidding\BiddingRequest` (validação obrigatória).
- `App\Http\Model\Prefecture\Bidding` (`fillable` e relação `managementUnits()`).
- `Portal\BiddingsController` (filtro/exibição por unidade nas licitações públicas).
- `v6/app/Http/Controllers/Prefecture/DatatableController@get_unidade_gestora`.

## 3. Armadilhas técnicas confirmadas

| # | Armadilha | Evidência | Impacto |
|---|---|---|---|
| A-U1 | **Regra de unicidade da atualização aponta para a tabela errada** (`unique:persons,cpf,{id}`). | `v6/app/Http/Requests/ManagementUnitRequest.php:33-34` | Duplicidade de `name`/`code` permitida; dependência de uma tabela sem relação. |
| A-U2 | **Contador de menu errado**: `menuClicks('licitacao')` nas 3 telas do módulo. | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:63,73` (e `create`) | Estatísticas de acesso incorretas; não existe `menu_orders` com slug `unidade-gestora`. |
| A-U3 | **Rota `show` sem método**: `Route::resource` gera `unidade-gestora.show`, o controller não implementa `show`. | `v6/routes/basepro.php:181` + controller | GET `/cms/unidade-gestora/{id}` -> erro fatal. |
| A-U4 | **View `show.blade.php` órfã** usando `$functionality`. | `v6/resources/views/prefecture/management-units/show.blade.php` | Código morto de outro módulo (parece copiado de "Funcionalidade"). |
| A-U5 | **`update()` não trata id inexistente**: `ManagementUnit::find($id)` pode devolver `null` e `->update()` em `null` estoura. | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:117-121` | Erro 500 em `PUT /cms/unidade-gestora/999999`. |
| A-U6 | **`destroy()` também não trata id inexistente**. | mesmo controller, linhas 141-145 | Erro 500 em `DELETE /cms/unidade-gestora/999999`. |
| A-U7 | **`implode()` com `toDelete` não-array** no `deleteByLote` — `TypeError` não é capturado por `catch (Exception)`. | `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php:35` | Erro fatal (500) em requisição forjada. |
| A-U8 | **Heurística de permissão do middleware abre `create/store/edit/update/destroy`** para qualquer usuário logado. | `v6/app/Http/Middleware/CheckRoutePermission.php:44-52` | Escalada de privilégio: qualquer operador pode criar/editar/excluir. |
| A-U9 | **`Form::open` aponta para `unidade-gestora.index`** no formulário de criação. | `v6/resources/views/prefecture/management-units/create.blade.php:12` | Funciona só por acaso (POST na mesma URI = `store`). |
| A-U10 | **Atributo `maxlength` não é emitido** (chave `maxlenght` x `maxlen`). | `v6/resources/views/prefecture/management-units/form.blade.php` vs `v6/resources/views/components/inputs/input.blade.php:47` | Limite de 120 só validado no servidor. |
| A-U11 | **`structureUpInsert()` retorna `[]`** — migrar `store`/`update` para `upInsert()` criaria registros vazios. | `v6/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php:22-25` | Armadilha para quem quiser "usar o repository". |
| A-U12 | **Relacionamentos Eloquent invertidos** (`belongsTo` de 1:N e `hasOne` de N:1). | `v6/app/Http/Model/Prefecture/ManagementUnit.php:26-29` e `v6/app/Http/Model/Prefecture/Bidding.php:111-115` | Consultas por relação retornam dados errados. |
| A-U13 | **Seed apaga fisicamente** a tabela (`DB::table('management_units')->delete()`), ignorando `SoftDeletes` e a FK de `biddings`. | `v6/database/seeds/ManagementUnitTableSeeder.php:12` | Rodar o seed em produção apaga as unidades e falha se houver licitações. |
| A-U14 | **`->nullable(true)` na FK de `biddings` é no-op** no Laravel 5.5, deixando a coluna `NOT NULL`. | `v6/database/migrations/2018_04_02_181804_create_biddings_table.php:36-38` vs `v6/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php:57-88` | A intenção de permitir licitação sem unidade não foi efetivada. |
| A-U15 | **URLs relativas nos botões do DataTable** (`unidade-gestora/{id}/edit`). | `v6/public/assets/js/custom/contents/licitacoes/unidades-gestoras/datatable.js` | Frágil: depende da URL base da listagem. |
| A-U16 | **`IManagementUnitRepository` vazia** — a arquitetura de repositórios não é aplicada. | `v6/app/Repositories/Contracts/IManagementUnitRepository.php` | Falsa camada: o controller injeta e quase não usa. |
| A-U17 | **Namespace do model desalinhado da pasta** (`App\Prefecture` em `app/Http/Model/Prefecture/`). | `v6/app/Http/Model/Prefecture/ManagementUnit.php:3` | Confuso para quem procurar a classe pelo caminho do arquivo. |
| A-U18 | **`$table->engine = 'InnoDB'`** em migration PostgreSQL. | `v6/database/migrations/2018_04_02_181802_create_management_units_table.php:17` | Ruído herdado do MySQL. |
| A-U19 | **Coluna `code` sem índice unique no banco** — a unicidade existe só na aplicação (e é quebrada na atualização). | dump de `management_units` | Condição de corrida: dois operators criando o mesmo `code` ao mesmo tempo. |
| A-U20 | **Coluna homônima em outro domínio**: `emendas_parlamentares.unidade_gestora_id` aponta para `emendas_parlamentares_categories`, não para `management_units`. | `v6/database/migrations/2026_03_13_102500_create_emendas_parlamentares_table.php:32,47` | Risco de confusão em migrações de dados. |

## 4. Código duplicado / copiado

| Trecho | Origem | Observação |
|---|---|---|
| `ManagementUnitRequest` | `v5/app/Http/Requests/ManagementUnitRequest.php` e `v6/app/Http/Requests/ManagementUnitRequest.php` | **Byte-idênticos**; o bug U1 está nas duas versões. |
| `ManagementUnitController` | `v5/app/Http/Controllers/Prefecture/ManagementUnitController.php` e `v6/app/Http/Controllers/Prefecture/ManagementUnitController.php` | **Byte-idênticos**. |
| `ManagementUnit` (model) | `v5/app/Http/Model/Prefecture/ManagementUnit.php` e `v6/app/Http/Model/Prefecture/ManagementUnit.php` | **Byte-idênticos**. |
| `ManagementUnitRepositoryDefaultImpl` | `v5/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php` e `v6/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php` | **Byte-idênticos**. |
| `ManagementUnitTableSeeder` | `v5/database/seeds/ManagementUnitTableSeeder.php` e `v6/database/seeds/ManagementUnitTableSeeder.php` | **Byte-idênticos**. |
| `create_management_units_table` | `v5/database/migrations/2018_04_02_181802_create_management_units_table.php` e `v6/database/migrations/2018_04_02_181802_create_management_units_table.php` | **Byte-idênticos**. |
| `create_view_to_unidade_gestora` | `v5/database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php` e `v6/database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php` | **Byte-idênticos**. |
| `prefecture/management-units/*.blade.php` | `v5/resources/views/prefecture/management-units/` e `v6/resources/views/prefecture/management-units/` | **Byte-idênticos** (5 arquivos). |
| `show.blade.php` | herdado do módulo "Funcionalidade" | Usa `$functionality` — não pertence a este módulo. |
| `menuClicks('licitacao')` | copiado de `Prefecture\Bidding\BiddingController@index` | Não foi adaptado ao módulo. |
| `deleteByLote` | copiado de outros controllers com `deleteByLote` | O texto do log foi copiado junto ("uniade gestora"). |

## 5. Onde a v6 divergiu da v5

**Não houve divergência funcional.** Todos os arquivos do módulo são
**byte-idênticos** entre `v5/` e `v6/`:

| Arquivo | Comando de comparação | Resultado |
|---|---|---|
| `app/Http/Controllers/Prefecture/ManagementUnitController.php` | `diff -q v5/app/Http/Controllers/Prefecture/ManagementUnitController.php v6/app/Http/Controllers/Prefecture/ManagementUnitController.php` | idêntico |
| `app/Http/Model/Prefecture/ManagementUnit.php` | `diff -q v5/app/Http/Model/Prefecture/ManagementUnit.php v6/app/Http/Model/Prefecture/ManagementUnit.php` | idêntico |
| `app/Http/Requests/ManagementUnitRequest.php` | `diff -q v5/app/Http/Requests/ManagementUnitRequest.php v6/app/Http/Requests/ManagementUnitRequest.php` | idêntico |
| `app/Repositories/Contracts/IManagementUnitRepository.php` | `diff -q v5/app/Repositories/Contracts/IManagementUnitRepository.php v6/app/Repositories/Contracts/IManagementUnitRepository.php` | idêntico |
| `app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php` | `diff -q v5/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php v6/app/Repositories/Eloquent/Prefecture/ManagementUnit/ManagementUnitRepositoryDefaultImpl.php` | idêntico |
| `database/migrations/2018_04_02_181802_create_management_units_table.php` | `diff -q v5/database/migrations/2018_04_02_181802_create_management_units_table.php v6/database/migrations/2018_04_02_181802_create_management_units_table.php` | idêntico |
| `database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php` | `diff -q v5/database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php v6/database/migrations/2023_04_11_101345_create_view_to_unidade_gestora.php` | idêntico |
| `database/seeds/ManagementUnitTableSeeder.php` | `diff -q v5/database/seeds/ManagementUnitTableSeeder.php v6/database/seeds/ManagementUnitTableSeeder.php` | idêntico |
| `resources/views/prefecture/management-units/*.blade.php` (5 arquivos) | `diff -q v5/resources/views/prefecture/management-units/index.blade.php v6/resources/views/prefecture/management-units/index.blade.php` (idem para `create`, `edit`, `form`, `show`) | idênticos |

O que mudou **no ambiente** (e não no módulo):

| Item | v5 | v6 |
|---|---|---|
| Registro em `modulos` | a tabela `modulos` não existia | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` cria o registro `id = 50` com `status = 0` |
| `resources.modulo_id` | coluna inexistente | `v6/database/seeds/ResourcesTableSeeder.php:171-177` vincula o recurso ao módulo 50 |
| `menu_orders` | não existia | a tabela existe, mas **sem** linha para `unidade-gestora` |
| Permissões do papel | — | `v6/database/seeds/RolesTableSeeder.php:62` concede `unidade-gestora.*` ao papel `administrador` |

> O nome do **namespace** do model é `App\Prefecture` **nas duas versões** — a
> mudança de namespace sugerida pela comparação de pastas não ocorreu: tanto
> `v5/app/Http/Model/Prefecture/ManagementUnit.php` quanto
> `v6/app/Http/Model/Prefecture/ManagementUnit.php` declaram
> `namespace App\Prefecture;`. O autoload por classmap
> (`v6/composer.json:53-57`) é o que torna isso possível.

## 6. Riscos de migração de dados

| Risco | Detalhe | Mitigação |
|---|---|---|
| Unicidade não garantida no banco | Não há índice unique em `name`/`code`; pode haver duplicados já gravados. | Antes de criar o índice unique na v7,identificar duplicatas e decidir qual registro é o canônico. |
| Exclusão física do seed | `ManagementUnitTableSeeder` faz `delete()` físico e quebraria a FK de `biddings`. | Trocar por `updateOrCreate`/upsert na v7; nunca rodar o seed em produção. |
| `biddings` órfãs | Unidades em soft delete continuam referenciadas por licitações (`management_units_id` `NOT NULL`, FK `NO ACTION`). | Definir a política: manter a unidade (soft delete) ou exigir substituição antes de excluir. |
| Coluna homônima | `emendas_parlamentares.unidade_gestora_id` é outro domínio. | Não unificar as duas taxonomias sem decisão do negócio. |
| `code` livre | Não há padronização; pode haver códigos em formatos diferentes entre municípios. | Migrar para um `enum`/tabela de referência só após mapear os valores em uso. |
| View SQL `unidade_gestora` | Se a v7 criar a tabela com nomes diferentes, a view e o `DatatableController` precisam ser adaptados. | Substituir a view por uma query Eloquent paginada (ver `06-MIGRACAO-MODERNA.md`). |
