# Licitações — resumo executivo

> **Convenção de caminhos deste dossiê.** Caminhos iniciados por `V2/`, `tests/`,
> `app/` e `public/` são relativos à raiz do projeto analisado
> (`lotep/`, Laravel 5.5 / PHP 7.4). Caminhos iniciados por `src/`, `wiki/adr/`
> e `wiki/plan/` são relativos ao projeto destino (`public-v7/`, Laravel 12 /
> PHP 8.4). Nenhum arquivo de `lotep/` foi alterado.

## 1. Objetivo

Publicar e manter o cadastro oficial de **licitações** do órgão, com o edital
(e demais anexos) disponível para download no portal e com um backoffice simples
para a equipe administrativa. O módulo cobre quatro necessidades:

1. cadastrar a licitação (número, título, objeto, valor, datas, status, local,
   unidade gestora e órgãos participantes);
2. publicar/despublicar no portal por um interruptor (`is_active`);
3. anexar arquivos (edital, termo, ata etc.) com ordenação e título editável;
4. arquivar sem apagar (soft delete), restaurar e excluir em definitivo.

Não há fluxo de disputa, propostas, contratos, homologação de vencedor ou
integração com sistema de compras. O módulo é um **repositório editorial de
editais**, apesar do nome “Licitações”.

## 2. Efeito no produto

| Superfície | Efeito observável |
|---|---|
| CMS | lista paginada (20/página) com busca por número/título/objeto, ordenação e ações de editar, ativar/desativar, arquivar e ver no portal |
| CMS | tela de edição com formulário único, seleção de órgãos, upload/reordenação de anexos |
| CMS | tela de órgãos/secretarias com criação, renomeação, ativação e remoção reversível |
| Portal | `/licitacoes` lista **todas** as licitações ativas com filtro de texto e órgão no navegador |
| Portal | `/licitacao/{slug}` detalha objeto, valor, processo, local, unidade gestora, datas, órgãos, anexos e observação |

## 3. Comportamento e regras que definem o módulo

- **Identidade**: PK UUID v4 em string de 36 caracteres (`v2_uuid()`); não há
  auto-incremento (`V2/Modules/Publicacoes/Licitacoes/Models/Licitacao.php:84-87`). O helper tem nome enganoso:
  gera **v4**, não v7 (fato verificado da plataforma).
- **Slug**: derivado de `number + title` com `Str::slug` na criação
  (`V2/Modules/Publicacoes/Licitacoes/Models/Licitacao.php:119-126`), regerado quando `number`/`title` mudam e o
  slug não foi editado manualmente (`:128-135`). Deve ser único pela indexação
  parcial do banco, mas a geração tem um defeito estrutural (seção 8).
- **Objeto**: sempre texto puro. O mutator aplica
  `strip_tags(html_entity_decode($value))` (`:172-175`), inclusive para valores
  vindos do editor rico; o portal tenta renderizar o campo pelo
  `ContentRichEditorRendererService` e, em falha, exibe o texto escapado.
- **Valor**: `decimal(15,2)` com default `0`, convertido para `float` no model;
  a exibição é condicionada por `show_value` em **todas** as superfícies.
- **Datas**: três campos independentes e opcionais — edital, competição e
  homologação. Não há validação de ordem cronológica.
- **Ativação**: `is_active` é independente de `status_id` e de `deleted_at`.
  Uma licitação “Cancelada” pode estar ativa se o operador assim deixar.
- **Órgãos**: N:N (`v2_licitacao_orgao_licitacao`). O formulário sincroniza a
  lista inteira via `sync()`; um órgão pode ser criado inline por `fetch` sem
  sair da tela de licitação.
- **Anexos**: 1:N em tabela própria (`v2_licitacao_anexos`), com `order`,
  `title` opcional, soft delete e remoção física do arquivo no delete.
- **Allowlist de upload**: MIME declarado precisa estar em
  `ALLOWED_MIME_TYPES` e o conteúdo é conferido por magic bytes — com suporte a
  assinatura em offset (`video/mp4` usa `ftyp` no offset 4).
- **Ciclo de vida**: `destroy` = soft delete; `restore` = desfaz; `forceDelete`
  = remove anexos visíveis do storage e apaga a linha (cascade no resto).

## 4. Estrutura de dados em uma página

| Tabela | Papel | Chave |
|---|---|---|
| `v2_licitacoes` | registro principal | `id` (uuid) |
| `v2_licitacao_status` | situações (“Aberta”, “Finalizada”, “Cancelada”) | `id` (uuid), `name` único |
| `v2_licitacao_anexos` | arquivos por licitação, ordenáveis | `id` (uuid), FK `licitacao_id` cascade |
| `v2_licitacao_orgaos` | cadastro de órgãos/secretarias | `id` (uuid), `name` |
| `v2_licitacao_orgao_licitacao` | pivô N:N | PK composta (`licitacao_id`, `orgao_id`) |

Campos jsonb de `v2_licitacoes`: `jurisdicionados` (array, default `[]`),
`arquivos` (array, default `[]`, **legado do importador**) e `metadata` (objeto
livre). `v1_id` guarda a PK original da V1 e tem índice único parcial; o trio
`v1_id`/`imported_at`/`status_id` foi criado para o importador legado.
`v2_modalidades_licitacao` e `v2_tipos_licitacao` existiram, mas foram
arquivadas em `backup_*` (`V2/Modules/Publicacoes/Licitacoes/Migrations/2026_08_31_000012_drop_modalidade_tipo_from_v2_licitacoes_table.php:33-41`).

## 5. Como o usuário interage

**Operador (CMS).** Entra em `/cms/v2/licitacoes`, busca e ordena a lista,
cria uma licitação pelo botão “Nova Licitação”, preenche o formulário, escolhe
órgãos (podendo criar um novo em modal), salva e, na edição, anexa arquivos.
Anexos podem ser renomeados, reordenados (setas ou posição numérica) e
removidos. Na lista, o interruptor ativa/desativa no portal; o botão de arquivo
move para “Arquivadas”, onde há restaurar e excluir em definitivo.

**Cidadão (portal).** Abre `/licitacoes`, digita um termo e/ou escolhe um órgão;
a filtragem é instantânea e local. Clica em “Detalhes” e vê a página por slug,
com o objeto em HTML rico e os anexos em uma lista lateral.

## 6. Particularidades técnicas

- Módulo auto-descoberto em `V2/Modules/Publicacoes/Licitacoes`
  (`V2/Modules/Publicacoes/Licitacoes/ModuleProvider.php` devolve título, ícone, posição 20, grupo “Atos
  Oficiais”, rota inicial e padrão de menu ativo).
- Views no namespace `v2-licitacoes::`; CMS usa o layout `v2-cms::layouts.app`
  e portal usa `v2-portal::app-layout`.
- Rotas CMS em `V2/Modules/Publicacoes/Licitacoes/Routes/cms.php` sob `cms/v2/licitacoes`, com
  `v2.authenticate` + `v2.permission:licitacoes,read`; ações de escrita
  acrescentam `v2.permission:licitacoes,write`.
- Permissões no formato legado de dois níveis (`read`/`write`), resolvidas por
  `v2_can('licitacoes', ...)` e aliased para a chave canônica `Licitacoes`
  (`V2/Core/Services/PermissionService.php:35`). Bypass para
  `config('v2.support.email')` e `super-admin`.
- Auditoria em duas camadas: `V2AuditLogger::activity()` para mutações do
  registro e anexos; `CmsActionLogger::info()` para navegação/telemetria de
  ações do CMS.
- `LicitacaoDataSource` (Estatísticas) é acoplado por leitura direta em
  `v2_licitacoes` + `v2_licitacao_status`, com métricas total/ativas/por
  status/por ano/recentes.
- O importador `V2/Modules/Support/MigracaoLegada/Services/LicitacoesLegacyImporter.php`
  grava `v1_id`, `status_id`, `jurisdicionados` e `arquivos` no destino.

## 7. Como testar (visão geral)

- Feature CMS: `tests/Feature/V2/Modules/Publicacoes/Licitacoes/LicitacaoCmsControllerTest.php`
  (11 métodos: views, store, slug, toggle, soft delete, restore, 403, upload).
- Feature portal: `tests/Feature/V2/Modules/Publicacoes/Licitacoes/LicitacaoPortalControllerTest.php` (5 métodos: listagem,
  filtro de ativas, detalhe por slug, 404 para inativa e inexistente).
- Unit serviço de anexos: `tests/Unit/V2/Modules/Publicacoes/Licitacoes/LicitacaoAttachmentServiceTest.php` (20 métodos:
  MIME, magic bytes de PDF/MP3/MP4/WAV, ordenação, soft delete, apresentação).
- Unit regressão do model: `tests/Unit/V2/Modules/Publicacoes/Licitacoes/LicitacaoModelRegressionTest.php` (20 métodos:
  `strip_tags`, entidades, slug único, escopos, UUID, legado, soft delete).
- Comando exato: `vendor/bin/phpunit --no-coverage --filter Licitacao`.
  Não há e2e Playwright específico de Licitações no repositório.

## 8. Riscos que a reconstrução precisa resolver

1. **Portal sem paginação**: `LicitacaoPortalController::index()` executa
   `->get()` com `status`, `orgaos` e `anexos` carregados
   (`V2/Modules/Publicacoes/Licitacoes/Controllers/Portal/LicitacaoPortalController.php:28-31`). Cresce de forma
   linear em memória e tempo de resposta.
2. **Slug com `static::$oldSlug` inexistente**: a propriedade estática nunca é
   definida em nenhum ponto do repositório (`V2/Modules/Publicacoes/Licitacoes/Models/Licitacao.php:143-166`);
   como a consulta de unicidade inclui a própria linha, editar número/título
   pode gerar `…-1` sem necessidade.
3. **Duas verdades para anexos**: o jsonb `arquivos` (legado) e a tabela
   `v2_licitacao_anexos` coexistem; nada sincroniza os dois, e o portal só lê a
   tabela.
4. **Force delete parcial**: `forceDelete()` itera `$licitacao->anexos`, que
   ignora anexos já soft-deleted; os arquivos físicos desses anexos ficam
   órfãos no storage.
5. **Filtros do CMS decorativos**: a view oferece `orgao` e `status`
   (`V2/Modules/Publicacoes/Licitacoes/Views/cms/index.blade.php:113-139`), mas o controller lê apenas `search`,
   `sort` e `dir` (`V2/Modules/Publicacoes/Licitacoes/Controllers/Admin/LicitacaoCmsController.php:46-51`).
6. **N+1 na listagem**: `anexos` é eager-loaded, mas a view chama
   `$row->anexos->count()` (ok) e o model expõe `has_anexos` executando
   `anexos()->count()` (não usado nas views atuais); a tela de edição chama
   `$licitacao->anexos()->count()` dentro do laço de anexos
   (`V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments.blade.php:33`).
7. **Autorização duplicada e parcial**: rotas exigem `read`, controllers repetem
   `v2_can`; `LicitacaoOrgaoController` não audita nenhuma mutação e não usa
   `LicitacaoRequest`.

## 9. Migração para a base moderna (resumo)

O destino V7 não deve portar o módulo como está. O caminho recomendado:

- schema próprio (`licitacoes.*`) com UUIDv7 nativo, `timestamptz`, `jsonb` só
  para dado de domínio (nunca HTML), status como tabela de referência ou enum
  controlado, e anexos em tabela com FK `cascade`;
- `object` em **AST TipTap** (`jsonb`) e renderização pelo `RichText` (ADR-028);
- anexos via serviço `Media`/disco S3 (ADR-026/ADR-030), não `Storage::disk('public')`;
- portal paginado e filtrável no servidor, com `ETag`/`304` (ADR-022);
- RBAC de quatro ações `{licitacoes}.{ver|editar|aprovar|arquivar}` (ADR-033) e
  autorização no controller por `Permission::authorize`;
- SEO por `@graph` no `<head>` (ADR-027) e `sitemap` com as licitações ativas;
- sem importador automático da V1: conteúdo migra à mão (ADR-042), e o jsonb
  `arquivos` legado não vira segunda fonte de verdade.

O plano detalhado está em [`06-MIGRACAO-MODERNA.md`](06-MIGRACAO-MODERNA.md) e o
julgamento de aproveitamento em
[`09-APROVEITAMENTO-V7.md`](09-APROVEITAMENTO-V7.md).

## 10. Índice desta pasta

| Arquivo | Conteúdo |
|---|---|
| `00-RESUMO.md` | este texto |
| `01-ARQUITETURA.md` | fluxo de requisição, classes, dependências |
| `02-MODELAGEM.md` | tabelas, colunas, relações, migrations, seeds |
| `03-ROTAS-E-CONTROLADORES.md` | rotas, middleware, validação, permissões |
| `04-VIEWS-E-UX.md` | blades, JS, UX do CMS e do portal |
| `05-REGRAS-E-PORTABILIDADE.md` | regras, acoplamentos, armadilhas |
| `06-MIGRACAO-MODERNA.md` | schema e código-alvo no V7 |
| `07-TESTES.md` | cobertura atual, casos e fixtures |
| `08-GAPS-E-DIVIDAS.md` | defeitos, lacunas, riscos e perguntas |
| `09-APROVEITAMENTO-V7.md` | o que portar, adaptar e rejeitar, com ADR |
