# Licitações — Gaps, Bugs e Divergências

> Levantamento feito por leitura do código da `v6` (e comparação com a `v5`).
> Cada item aponta para o arquivo real onde o problema está.

---

## 1. Bugs e erros conhecidos

### 1.1 Apagar arquivo apaga o arquivo errado (CRÍTICO)

`v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php` — `ajaxDeleteFiles()`:

```php
$licitacao = $this->biddingRepository->getModel()->find($request->get('licitacao'));
$archive = $this->archiveRepository->getModel()->find($request->get('licitacao'));
if ($this->archiveRepository->destroy($request->get('archive_id'))) {
    $this->deleteFile($archive->name, "licitacao/{$licitacao->id}/arquivos/");
```

O `$archive` é buscado com o **id da licitação**, não com o `archive_id`. O
registro em `archives` correto é apagado do banco, mas o arquivo removido do
disco é o de `archives.id == biddings.id`. Consequência: o anexo some da listagem
e continua ocupando espaço no storage (ou, pior, o anexo errado é removido).

### 1.2 `dd()` no repositório mata a requisição (CRÍTICO)

`v6/app/Repositories/Repository.php` — `upInsert()`:

```php
} catch (\Throwable $th) {
    dd($th);
}
```

`dd()` executa `http_response_code(500)`, faz o dump e chama `exit(1)`. O
`try/catch` de `BiddingController@store`/`@update` **nunca** é alcançado para
erros de repositório: o operador vê um dump de exceção em vez da mensagem
“Licitação cadastrada com sucesso.” ou do aviso amigável.

### 1.3 Rota de ajax sem método (erro 500)

`v6/routes/web.php:147`:

```php
Route::any('licitacao-ajax-detalhe/{id}', 'Portal\BiddingsController@ajaxShowDetail')->name('licitacao.showDetail');
```

`Portal\BiddingsController` **não tem** o método `ajaxShowDetail` (métodos
existentes: `__construct`, `index`, `indexShow`, `export`, `gerarTxt`,
`exportTxt`, `paginate`). Qualquer acesso devolve 500. A rota é **morta** — o
que a substitui (`getDetailShow`) é apenas um toggle de jQuery
(`v6/resources/views/portal/layouts/modelo1/licitacao/filtro.blade.php:248`).

### 1.4 `Route::resource` com `show` inexistente (erro 500)

`v6/routes/basepro.php:184` gera `licitacao.show` → `BiddingController@show`,
mas o controller não implementa `show()`. O JS da listagem do CMS gera o botão
de ação com `licitacao/{id}` (`v6/public/assets/js/custom/contents/licitacoes/licitacao/datatable.js`),
apontando para essa rota morta.

### 1.5 Links quebrados nas telas de Tipos de Licitação

`v6/resources/views/prefecture/biddings-types/index.blade.php:36`:
`route('licitacao-anterior', $biddingsType->id)` — **`licitacao-anterior` não é
nome de rota**; é o *slug* do recurso (id 19 na tabela `resources`).
`v6/resources/views/prefecture/biddings-types/create.blade.php:24` repete o erro
no link “Voltar”. Resultado: `RouteNotFoundException` (500) ao abrir a tela de
tipos ou clicar em “Editar”.

### 1.6 Permissões `licitacoes.*` não existem

`BiddingTypeController` e `BiddingModalityController` (e as views
`biddings-types/index.blade.php`, `biddings-modalities/index.blade.php`) usam
`has_access(['licitacoes.index'])`, `['licitacoes.create']`, `['licitacoes.edit']`,
`['licitacoes.delete']`. Na tabela `resources` só existem `licitacao` (id 20) e
`licitacao-anterior` (id 19); o `v6/database/seeds/RolesTableSeeder.php` concede
`licitacao.*` e `licitacao-anterior.*`. Resultado: **para qualquer usuário que
não seja `super-admin`, o acesso às telas de tipos e modalidades é sempre
redirecionado para o dashboard**, e os botões de criar/editar/excluir nunca
aparecem.

### 1.7 Filtro por órgão é exibido mas ignorado

O formulário de filtros do portal tem o campo `contents_id` (“Órgão”) e o
controller o carrega, mas `v6/app/Http/Filter/FilterBiddings.php` **não** tem
nenhuma cláusula para ele. O usuário informa o órgão e recebe o resultado sem
filtro.

### 1.8 Relação `BiddingType::biddings()` aponta para coluna inexistente

`v6/app/Http/Model/Prefecture/BiddingType.php`:

```php
return $this->belongsTo(Bidding::class, 'bidding_type_id', 'id');
```

A coluna real é `bidding_types_id`. Qualquer uso da relação gera erro de SQL.

### 1.9 `biddingJurisdictionals()` com chaves invertidas

`v6/app/Http/Model/Prefecture/Bidding.php` declara
`belongsTo(BiddingJurisdictionals::class, 'id', 'biddings_id')` — o inverso do
que deveria ser. Na view de detalhe do portal
(`v6/resources/views/portal/layouts/modelo1/licitacao/show.blade.php`) isso
funciona por acaso e exibe **apenas um** órgão jurisdicionado, mesmo quando a
licitação tem vários.

### 1.10 Datas quebram quando a hora não é informada

`v6/app/Http/Traits/AppTrait.php` — `toUSDataHourFormat()` faz
`explode(" ", $array[2])[1]`; se `competition_date` chegar como `dd/mm/yyyy` sem
hora, o índice 1 não existe (notice + data incorreta).

### 1.11 Exportação de contratos gera PDF de licitações

O botão “pdf” da listagem de contratos chama `exportar('pdf')`
(`v6/resources/views/portal/layouts/modelo1/licitacao/contratos/filtro.blade.php:159`),
que redireciona para `/exportacao?...&type=pdf`. Essa rota é servida por
`Portal\BiddingsController@index`, que gera o PDF de **licitações**, não de
contratos. O `ContractsExportView` (`v6/app/Exports/ContractsExportView.php`)
existe mas não é acionado por esse caminho.

### 1.12 `verificaModalidades()` não faz nada

`v6/resources/views/prefecture/biddings/form.blade.php` esconde `#dt_certame` e
`#dt_edital` para as modalidades `1, 6, 7, 8` — mas **nenhum elemento com
esses `id`s existe** no formulário (os campos são `competition_date` e
`publication_edital_date`). A regra pretendida (esconder as datas nas
dispensas) nunca foi aplicada.

### 1.13 Coluna `value` da view `licitacao` tem nome enganoso

Na view SQL, `value` é o `row_number()` (numeração da linha) e o valor
monetário está em `valor`. O JS do DataTable
(`v6/public/assets/js/custom/contents/licitacoes/licitacao/datatable.js`) consome
`valor` para a coluna “Valor” e `data_publicacao` para a data — a confusão
obrigou o `runDatatables` a injetar uma coluna extra de índice.

### 1.14 Deslocamento de fuso na data da view

`to_char(b.publication_edital_date::timestamp with time zone, 'DD/MM/YYYY')`
converte para o fuso do servidor. Em UTC, licitações publicadas no fim do dia
podem exibir a data do dia anterior.

### 1.15 Exclusão em lote deixa secretarias órfãs

`BiddingController@deleteByLote` usa `destroyAll()` da classe base
(`v6/app/Repositories/Repository.php`), que **não** executa
`contents()->detach()` — ao contrário de `destroy()`, sobrescrito em
`BiddingRepositoryDefaultImpl`. As linhas de `bidding_jurisdictionals` permanecem.

### 1.16 `str_slug` (v5)

`v5/app/Http/Model/Prefecture/LicitanteSancionado.php` usa `str_slug()`, helper
removido no Laravel 6+ (e descontinuado no 5.5). Ao migrar, substituir por
`Str::slug()`.

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| `GET /licitacao-ajax-detalhe/{id}` | Rota morta (1.3). |
| `GET /cms/licitacao/{id}` | Rota morta (1.4). |
| `v6/resources/views/prefecture/biddings/show.blade.php` | View genérica “Funcionalidade”, herdada de outro módulo, sem nenhuma referência a licitação. |
| Bloco `Rule::unique` do `BiddingRequest` | Comentado; a unicidade depende do controller. |
| Bloco de obrigatoriedade de datas do `BiddingRequest` | Comentado; `publication_edital_date` e `competition_date` são opcionais. |
| Filtro `contents_id` do portal | Renderizado, sem efeito (1.7). |
| `BiddingType::biddings()` | Relação quebrada (1.8). |
| `$codigo = Request::segment(3);` no `BiddingRequest` | Variável atribuída e nunca usada. |
| `propostas()` / `proposals()` | Duas relações para a mesma tabela (`propostas` não é eager-loaded e gera N+1 em `gerarTxt`). |
| `BiddingsExport` / `ContractsExportView` | `ContractsExportView` não é acionado pelo botão pdf da listagem de contratos (1.11). |
| Configuração `licitacoes_old` | A chave não existe na base de desenvolvimento; o link “Ir para licitações até 2018” nunca aparece sem cadastrá-la. |
| Layout `default` do portal | Não possui a pasta `licitacao/`; se `configurations.layout` for `default`, a listagem pública quebra. |
| `has_access` vs. `Sentinel::hasAnyAccess` | `has_access()` sempre verifica **qualquer** acesso do array (`v6/app/Library/Helper/appHelpers.php:37-39`); a intenção de "qualquer um destes" é discutível. |

## 3. Itens existentes na v5 e removidos na v6

| Item | v5 | v6 |
|---|---|---|
| Coluna `contracts.agent` (“Fiscal do Contrato”) | existe: migration `v5/database/migrations/2025_06_07_004403_add_agent_column_to_contracts_table.php`, campo no formulário `v5/resources/views/prefecture/contracts/form.blade.php:106`, persistência em `ContractController@store`/`@update`, filtro no portal | **removida** (sem migration, sem coluna, sem campo, sem filtro) |
| Ordenação da listagem de contratos | extrai dígitos do número com `CAST(NULLIF(substring(number from '...'), '') AS INTEGER) DESC NULLS LAST` | `orderBy('publication_date','desc')` |
| Ordenação da listagem pública de licitações | extrai dígitos do `number` | `orderByRaw('homologation_date IS NULL, homologation_date DESC, publication_edital_date DESC')` |
| Módulo **Licitantes Sancionados** | completo: `v5/app/Http/Controllers/Prefecture/LicitanteSancionadoController.php`, `v5/app/Http/Controllers/Portal/LicitantesSancionadosController.php`, `v5/app/Http/Model/Prefecture/LicitanteSancionado.php`, views `v5/resources/views/prefecture/licitantes-sancionados/`, rotas em `v5/routes/basepro.php:346-353` e `v5/routes/web.php:157-159`, migrations `v5/database/migrations/2026_05_26_000000_create_licitantes_sancionados_table.php`, `..._000001_create_view_licitantes_sancionados.php` (view `licitantes_sancionados_view`), `..._000002_add_slug_to_licitantes_sancionados_table.php` | **inexistente** |

Observação sobre o módulo de licitantes sancionados: ele é **funcional** na v5
(tabela, view SQL, CRUD no CMS, listagem pública com slug), e não existe na v6.
Como é parte da família “transparência de licitações”, precisa de decisão
explícita: migrar para a v7 ou manter no sistema legado.

## 4. Itens existentes na v6 e removidos na v5 (nenhum relevante)

Nenhum item do módulo de licitações foi-exclusive da v6. O que há é o oposto
(3.1). As demais diferenças entre `v5/routes/web.php` e `v6/routes/web.php` e
entre `v5/routes/basepro.php` e `v6/routes/basepro.php` dizem respeito a outros
módulos (VIAP, Emendas Parlamentares, Dashboard, Selos, Requerimentos, Carta de
Serviços, Pauta do Dia, TV Câmara, Ajustes de ordem).

## 5. Mapa v5 -> v6 (módulo licitações)

| Elemento | v5 | v6 | Situação |
|---|---|---|---|
| `BiddingController` (CMS) | `v5/app/Http/Controllers/Prefecture/Bidding/BiddingController.php` | `v6/app/Http/Controllers/Prefecture/Bidding/BiddingController.php` | idêntico |
| `BiddingTypeController` | `v5/app/Http/Controllers/Prefecture/Bidding/BiddingTypeController.php` | `v6/app/Http/Controllers/Prefecture/Bidding/BiddingTypeController.php` | idêntico |
| `BiddingModalityController` | `v5/app/Http/Controllers/Prefecture/Bidding/BiddingModalityController.php` | `v6/app/Http/Controllers/Prefecture/Bidding/BiddingModalityController.php` | idêntico |
| Model `Bidding` | `v5/app/Http/Model/Prefecture/Bidding.php` | `v6/app/Http/Model/Prefecture/Bidding.php` | idêntico |
| Models `BiddingType` / `BiddingModality` / `BiddingHasArchive` / `BiddingJurisdictionals` | idem | idem | idênticos |
| Model `Contract` | `v5/app/Http/Model/Prefecture/Contract.php` (com `'agent'` no `$fillable`) | `v6/app/Http/Model/Prefecture/Contract.php` (sem `agent`) | **divergente** |
| `Portal\BiddingsController` | `v5/app/Http/Controllers/Portal/BiddingsController.php` (ordenação por dígitos do número) | `v6/app/Http/Controllers/Portal/BiddingsController.php` (ordenação por homologação/publicação) | **divergente** |
| `Portal\ContractController` | `v5/app/Http/Controllers/Portal/ContractController.php` (filtro `agent`, ordenação por número) | `v6/app/Http/Controllers/Portal/ContractController.php` | **divergente** |
| Requests `BiddingRequest` / `BiddingTypeRequest` / `BiddingModalityRequest` | idem | idem | idênticos |
| `BiddingRepositoryDefaultImpl` | `v5/app/Repositories/Eloquent/Prefecture/Bidding/BiddingRepositoryDefaultImpl.php` | `v6/app/Repositories/Eloquent/Prefecture/Bidding/BiddingRepositoryDefaultImpl.php` | idêntico |
| `FilterBiddings` | `v5/app/Http/Filter/FilterBiddings.php` | `v6/app/Http/Filter/FilterBiddings.php` | idêntico |
| `BiddingsExport` | `v5/app/Exports/BiddingsExport.php` | `v6/app/Exports/BiddingsExport.php` | idêntico |
| Migrations do módulo | `2018_04_02_181800`, `2018_04_02_181801`, `2018_04_02_181804`, `2018_04_10_113409`, `2018_09_24_153635`, `2019_10_30_172701`, `2022_04_18_103059`, `2023_03_01_113315`, `2023_04_06_100010`, `2024_02_08_134940`, `2025_03_07_015102` | as mesmas | idênticas |
| Seeds `BiddingTypeTableSeeder` / `BiddingModalityTableSeeder` / `NewBiddingModalities` | idem | idem | idênticos |
| View SQL `licitacao` | `v5/database/migrations/2023_04_06_100010_create_view_to_licitacao.php` | `v6/database/migrations/2023_04_06_100010_create_view_to_licitacao.php` | idêntica |
| Coluna `biddings.is_show` | presente | presente | igual |
| Coluna `biddings.local` | presente | presente | igual |
| Coluna `contracts.bidding_id` (FK `set null`) | presente (`v5/database/migrations/2025_03_07_015102_add_biddings_foreing_key_to_contracts_table.php`) | presente | igual |
| Modalidade `Credenciamento` | migration presente | migration presente | igual |
| Coluna `contracts.agent` | presente | **ausente** | **removida** |
| Módulo Licitantes Sancionados | presente | **ausente** | **removido** |
| Rotas do portal de licitações | `v5/routes/web.php:119-121`, `149-151`, `166` | `v6/routes/web.php:146-148`, `183-185`, `188` | equivalentes |

## 6. Riscos de dados (migração de schema para a v7)

| Risco | Detalhe | Mitigação |
|---|---|---|
| `value` em `double precision` | `biddings.value` e `contracts.value` são `double`; valores monetários grandes perdem precisão. | `ALTER TABLE ... ALTER COLUMN value TYPE numeric(14,2) USING value::numeric(14,2);` |
| `publication_edital_date` NULL |-many registros podem ter a data nula (a obrigatoriedade foi removida em 2019). | Manter nullable; index parcial (`WHERE publication_edital_date IS NOT NULL`) para a ordenação. |
| `is_show` NULL | coluna nullable, nunca preenchida em licitações antigas. | `UPDATE biddings SET is_show = false WHERE is_show IS NULL;` e `NOT NULL DEFAULT false`. |
| `contracts.bidding_id` × `contracts.proposals_id` | `bidding_id` é `bigint` apontando para `biddings.id` (`integer`); além disso há contratos com as duas FKs, só com uma, ou com nenhuma. | Padronizar o tipo, criar índice em `bidding_id` e decidir a regra (contrato **deve** apontar para uma licitação?). |
| View `licitacao` | `CREATE OR REPLACE VIEW` falha se a assinatura (colunas) mudar. | Recriar a view (`DROP VIEW` + `CREATE VIEW`) em migration, ou eliminá-la (opção B em `06-MIGRACAO-MODERNA.md`). |
| Pivot `biddings_has_archives` | tem `deleted_at` mas o model não usa `SoftDeletes` — linhas “removidas” ainda aparecem. | Filtrar `deleted_at IS NULL` explicitamente ou remover a coluna. |
| Coluna `agent` da v5 | Se a base de origem for a v5, a coluna existe e precisa ser **reintroduzida** (ou descartada com decisão do negócio). | Migration `Schema::table('contracts', fn ($t) => $t->string('agent')->nullable());`. |
| Orfandade por `INNER JOIN` na view | Licitações com FK órfã (modalidade/tipo/situário removidos logicamente) desaparecem do CMS sem aviso. | `LEFT JOIN` na view nova + relatório de órfãs antes do deploy. |
| Soft delete inconsistente | `bidding_jurisdictionals` não tem `deleted_at`, mas o model importa `SoftDeletes`. | Alinhar migration e model. |

## 7. Perguntas em aberto para o negócio

1. **“Fiscal do Contrato” (`contracts.agent`)**: a v6 removeu o campo. Deve ser
   reintroduzido na v7? A fiscalização de contrato é exigência legal
   (Lei 8.666/93, art. 67).
2. **Módulo Licitantes Sancionados**: migrar da v5 para a v7 ou manter no legado?
3. **Unicidade do número da licitação**: “número + modalidade” (índice único) ou
   “número + modalidade + secretarias” (regra de negócio)? Há casos reais de
   números repetidos?
4. **Obrigatoriedade de datas**: `publication_edital_date` e `competition_date`
   devem ser obrigatórias para licitações que exigem certame?
5. **Valor estimado**: `show_value` deve ser o padrão ou uma decisão por
   licitação? Deve haver justificativa registrada quando o valor é ocultado?
6. **`is_show`**: o rótulo “Show Artístico” no CMS está errado. O campo deve ser
   renomeado (ex.: “Exibir no filtro de shows”) ou a semântica deve ser revista?
7. **Ordenação da listagem pública**: vale a ordenação da v6 (homologação +
   publicação) ou a da v5 (maior número)?
8. **Filtro por órgão**: confirmar que deve filtrar (hoje é ignorado).
9. **Tamanho e tipos de arquivo** aceitos para o edital: hoje não há limite.
10. **Prazo de retenção / republicação**: licitações com `homologation_date`
    antigo continuam públicas indefinidamente?
11. **Exclusão**: o que fazer com os contratos vinculados quando a licitação é
    excluída (hoje ficam órfãos com `bidding_id = null`)?
12. **View `licitacao`**: manter a view SQL no banco ou eliminá-la em favor de
    um `ApiResource`?

## 8. Resumo de severidade

| Severidade | Itens |
|---|---|
| Crítica | 1.1 (apaga arquivo errado), 1.2 (`dd()` mata a requisição), 1.3/1.4 (rotas com 500), 1.5 (links com 500) |
| Alta | 1.6 (tipos/modalidades inacessíveis), 1.7 (filtro ignorado), 1.11 (PDF errado), perda de `agent` e do módulo de licitantes sancionados |
| Média | 1.8, 1.9, 1.10, 1.12, 1.15, 1.16 |
| Baixa | 1.13, 1.14, view genérica `show.blade.php`, layout `default` sem pasta de licitações |
