# Ouvidoria — Gaps, Dívidas e Riscos

> **Slug:** `Ouvidoria` · **Versão analisada:** `lotep` (Laravel 5.5.50 / PHP 7.4)
>
> Bugs, funcionalidades ausentes, divergências V1→V2, riscos de dados e perguntas que precisam
> de decisão. Severidade: crítica (segurança/perda de dado), alta (fluxo quebrado),
> média (degradação), baixa (code smell).

---

## 1. Bugs e erros conhecidos

| ID | Severidade | Descrição | Onde | Impacto |
|---|---|---|---|---|
| G-01 | **crítica** | Mass assignment no portal: `$r->all()` + `$fillable` com `status`, `protocol`, `is_anonymous`, `metadata` | `OmbudsmanPortalController.php:32-35`; `OmbudsmanRequest.php:73` | cliente escolhe status/protocolo, forja anonimato e grava JSON arbitrário |
| G-02 | **alta** | `updateStatus` não valida nada e usa `$r->input('status', 1)` | `OmbudsmanRequestCmsController.php:71` | POST sem campo reabre a manifestação; `status=99` cria estado inválido |
| G-03 | **alta** | Toda resposta força `status = 2` | `OmbudsmanRequestCmsController.php:88` | impossível indeferir registrando resposta; a V1 distinguia 2 e 3 |
| G-04 | **alta** | `scopeSearch` usa `orWhere` no topo, sem grupo | `OmbudsmanRequest.php:148-154` | busca não combina com outros filtros; risco de vazar soft-deleted |
| G-05 | **alta** | Consentimento LGPD (`user_option`) não é validado nem persistido | `Views/portal/index.blade.php:186-193`; validação `:31`; `$fillable` | cidadão “autoriza” sem registro; não conformidade ADR-019 |
| G-06 | **alta** | IP cru é gravado e exibido no detalhe | `OmbudsmanPortalController.php:34`; `show.blade.php:36` | dado pessoal desnecessário; o importador V1 já usa hash |
| G-07 | **média** | Protocolo gerado no `boot()` do model, sem retry de colisão | `OmbudsmanRequest.php:78-91` | efeito colateral em todo `create()`; colisão de 32 bits/dia vira 500 |
| G-08 | **média** | Resposta e mudança de status fora de transação | `OmbudsmanRequestCmsController.php:87-88` | resposta gravada sem atualização de status |
| G-09 | **média** | Módulo desativado não bloqueia o portal | `V2/Core/Providers/V2ServiceProvider.php:515-518` | cidadão continua enviando manifestação que ninguém verá |
| G-10 | **média** | N+1 na listagem (`$row->type` por linha) | `index.blade.php:33-35` | 20 queries extras por página |
| G-11 | **alta** | Status é inteiro mágico repetido em controller, views e importador | `OmbudsmanRequestCmsController.php:88`; `index.blade.php:6-17`; `show.blade.php:67-72` | estados divergentes e relatórios frágeis |
| G-12 | **alta** | Rota V1 `any /ouvidoria/store` contém `dd($request->all())` e colide com a rota V2 | `routes/web.php:58-59`; `app/Http/Controllers/Portal/OmbudsmanController.php:129` | se a V1 vencer a resolução, o portal despeja o request e morre |
| G-13 | **média** | Anonimato não tem controle na UI e pode contradizer PII | `$fillable` + view sem campo | `is_anonymous=true` com nome/e-mail; rótulo “Anônimo” deriva de `name` |
| G-14 | **baixa** | Respostas sem autor, sem ordenação explícita e sem limite | `show.blade.php:51-58` | ordem indefinida; impossível saber quem respondeu |
| G-15 | **baixa** | `empty($t)` trata `"0"` como vazio; `%`/`_` não escapados | `OmbudsmanRequest.php:150-153` | busca enganosa; curingas injetados |
| G-16 | **baixa** | `exists:` aceita dicionário inativo ou soft-deleted | validação do portal `:31` | manifestação aponta para item desativado |
| G-17 | **baixa** | `OmbudsmanSubject::$fillable` lista `secretary_email`/`responsible`, sem colunas | `OmbudsmanSubject.php:46` | erro SQL se alguém usar os campos |
| G-18 | **baixa** | Código morto no módulo: `scopeActive`/`scopeOrdered` em `OmbudsmanRequest`; `file_url` e `metadata` sem uso no módulo (ambos gravados pelo importador) | `OmbudsmanRequest.php:131-142` | confusão e falso contrato |
| G-19 | **média** | `address` sem validação de tamanho (coluna 500) | validação `:31` | texto maior gera erro 500 de banco |
| G-20 | **baixa** | Busca anuncia “protocolo ou nome”, mas inclui e-mail; não há filtro de status/período apesar do índice e do `filter-card` | `index.blade.php:56-62` | expectativa frustrada e recurso subutilizado |

---

## 2. Funcionalidades incompletas ou mortas

| Item | Evidência de que está morto/incompleto | Consequência |
|---|---|---|
| F-01 Prazos/SLA/prorrogação | não há coluna, job ou tela; status 4 da V1 não foi portado | ouvidoria sem controle de tempo nem alerta de vencimento |
| F-02 Acompanhamento por protocolo | nenhuma rota pública além do formulário | cidadão perde o protocolo no *flash* e não consulta o andamento |
| F-03 E-mail ao cidadão/setor | nenhum `Mail`/Job no módulo (V1 tinha dois envios) | cidadão não sabe que a manifestação foi recebida nem respondida |
| F-04 Anexos | `file_url` existe, mas só o importador preenche; sem upload nas views | perda de evidência documental que a V1 aceitava |
| F-05 CRUD de dicionários | só seed; nenhuma rota/tela de tipos/assuntos/origens/formas | operador depende de deploy/seed para corrigir um rótulo |
| F-06 Respostas prontas | tabela/CRUD da V1 não portados | retrabalho para respostas repetitivas |
| F-07 Histórico de status | só a trilha genérica de auditoria; sem tela de linha do tempo | operador não vê a evolução na manifestação |
| F-08 Retenção/DSAR/classificação LGPD | nada além do checkbox inerte | descumprimento de ADR-019 (retenção, direitos do titular, DPIA) |
| F-09 Indicadores/exportação | sem dashboard, contagem por tipo/status ou CSV | gestão da ouvidoria cega |
| F-10 Antiabuso além do throttle | sem honeypot, time-trap, captcha ou limite por sessão | *spam* com 10 POSTs/min por IP |
| F-11 `is_anonymous` | coluna sem UI, teste apenas injeta via request | funcionalidade “existe no banco, não no produto” |
| F-12 Exclusão/arquivamento | sem rota; SoftDeletes sem uso | tabela cresce para sempre |
| F-13 `file_url`, `metadata`, `scopeActive`, `scopeOrdered` | sem chamador no módulo (`file_url`/`metadata` só são escritos pelo importador) | dívida de leitura |

---

## 3. Itens divergentes entre versões (V1 → V2)

| Item | V1 | V2 | Motivo (se descobrível) |
|---|---|---|---|
| Protocolo | `OUV-dmY-His` | `OUV-Ymd-{8}` | reduzir colisão e não expor horário |
| Campos obrigatórios | nome/e-mail/telefone/tipo/assunto/forma/origem | só descrição | simplificação |
| Status | 1–4 (inclui Prorrogado) | 1–3 | prorrogação não portada |
| Desfecho da resposta | `type=1` → 2; `type=0` → 3 | sempre 2 | regra perdida |
| Anexos | `archives_id` + upload | nenhum | não portado |
| E-mail | cidadão + admin | nenhum | não portado |
| Dicionários | CRUD no CMS | somente seed | não portado |
| Respostas prontas | CRUD | não existe | não portado |
| Consentimento | `user_option` persistido | checkbox descartado | não portado |
| Endereço | número/bairro/complemento/referência | campo único | simplificação |
| Listagem | DataTables | tabela server-side | reescrita |
| IP | cru | cru (importador usa hash) | dívida mantida |

### 3.1 Removido na versão atual

| Item | Impacto | Resgate necessário? |
|---|---|---|
| Status `4` (Prorrogado) | não há prorrogação de prazo | sim (F-01) |
| E-mail de confirmação/resposta | cidadão sem retorno | sim (F-03) |
| Anexos na resposta | evidência perdida | sim (F-04) |
| CRUD de dicionários e respostas prontas | operação dependente de seed | sim (F-05/F-06) |
| Campos separados de endereço | granularidade de endereço perdida | avaliar |
| `user_option` persistido | consentimento sem prova | sim (G-05) |

### 3.2 Novo na versão atual

| Item | Substitui o quê? |
|---|---|
| Tabelas `v2_ombudsman_*` com UUID | PKs inteiras da V1 |
| Dicionários planos (`is_active`) | views e tabelas da V1 |
| Importação idempotente por `v1_id` | importação ad hoc |
| Auditoria `V2AuditLogger`/`CmsActionLogger` | `activity()` da V1 |
| Permissão `ouvidoria` no RBAC V2 | permissões por rota da V1 |
| `throttle:10,1` | captcha desativado da V1 |

---

## 4. Mapa entre versões

| Aspecto | V1 (`app/Http/...`, `ombudsman_*`) | V2 (`V2/Modules/Servicos/Ouvidoria`) | V7 (proposto) |
|---|---|---|---|
| Rotas | `any /ouvidoria`, `any /ouvidoria/store`; CMS em `routes/basepro.php` | `GET /ouvidoria`, `POST /ouvidoria/store`; CMS `cms/v2/ouvidoria/requests*` | `ouvidoria.portal.*` e `ouvidoria.cms.*` |
| Tabelas | `ombudsman_requests/answers/*` + 5 views | `v2_ombudsman_requests/answers/*` | `ouvidoria.solicitacoes/respostas/*` |
| Controller | `Portal\OmbudsmanController`, `Prefecture\Ombudsman*Controller` | 2 controllers | controllers invocáveis + actions |
| Views | layouts do portal por template + AdminLTE | `v2-cms::*` / `v2-portal::*` | `<x-layout::portal>` / `<x-cms::*>` |
| Permissões | rota + `check.route.permission` | `ouvidoria.read/write` | `ouvidoria.ver/editar/arquivar` |
| Protocolo | `OUV-dmY-His` | `OUV-Ymd-{8}` | mesmo formato do V2 |
| Status | 1–4 | 1–3 | enum aberta/respondida/indeferida/arquivada |
| PII | nome/e-mail/telefone/endereço/IP crus | idem + IP cru | consentimento + `ip_hash` + retenção |
| Anexos | `archives` | nenhum | `Services\Media` |
| E-mail | sim | não | job + Mail |

---

## 5. Riscos de dados na migração

| Risco | Tabela afetada | Como verificar antes de migrar | Plano de rollback |
|---|---|---|---|
| Protocolos em formatos diferentes (`PROT-*`, `OUV-dmY-His`, `OUV-Ymd-*`) | `v2_ombudsman_requests.protocol` | `SELECT protocol, count(*) FROM ... GROUP BY 1 HAVING count(*) > 1;` e `SELECT protocol FROM ... WHERE protocol !~ '^OUV-';` | manter a coluna como texto; não normalizar em massa |
| Status V1 colapsado (Indeferido → 2) | `status` | comparar `metadata.legado.status_nome` com `status` | reprocessar a partir da V1 com novo mapa |
| PII importada em claro | nome/e-mail/telefone/endereço | inventário de campos + `information_schema` | anonimização em massa por `RetentionTarget` |
| IP cru em registros anteriores à importação | `ip` | `SELECT count(*) FROM ... WHERE ip IS NOT NULL;` | substituir por `ip_hash` e zerar a coluna |
| Registros soft-deleted na V1 não importados | origem | `SELECT count(*) FROM ombudsman_requests WHERE deleted_at IS NOT NULL;` | decidir se entram como arquivados |
| Respostas com arquivo físico ausente | `answers.file_url` | relatório do importador (`dryRun`) | manter resposta sem anexo, como o importador já faz |
| `v1_id` duplicado entre bases | unique parcial | `SELECT v1_id, count(*) ... HAVING count(*) > 1;` | reimportar por chave composta |
| Fuso/timestamp sem timezone | `created_at`/`imported_at` | comparar `now()` com os valores | migrar para `timestamptz` com `AT TIME ZONE` explícito |
| Descrição acima de 10 000 caracteres (limite proposto no V7) | `descricao` | `SELECT max(length(description)) ...;` | truncar com registro ou manter `text` sem limite |

---

## 6. Dívida técnica

| Item | Tipo | Esforço para corrigir | Risco de não corrigir |
|---|---|---|---|
| Mass assignment | segurança | baixo (DTO + `validated()`) | **crítico** |
| Status mágico | modelagem | médio (enum + check) | alto |
| Consentimento inerte | conformidade | médio (coluna + validação + aviso versionado) | alto |
| IP cru | conformidade | baixo (`ip_hash`) | alto |
| Busca sem grupo/índice | performance/correção | baixo/médio | alto |
| Sem transação na resposta | consistência | baixo | médio |
| Protocolo no `boot()` | testabilidade | baixo (action) | médio |
| N+1 | performance | baixo (`with('type')`) | médio |
| HTML em PHP | manutenção/CSP | médio (componentes) | médio |
| Views duplicadas de status | manutenção | baixo | baixo |
| Dicionários sem CRUD | operação | médio | médio |
| Sem prazos/acompanhamento/e-mail | produto | alto | alto |
| Sem retenção/DSAR | conformidade | alto | alto |

---

## 7. Riscos de segurança

| Risco | Explorável por | Mitigação |
|---|---|---|
| Forjar `status`/`protocol`/`is_anonymous` | qualquer visitante do portal | G-01: DTO + `validated()` + `fillable` mínimo |
| Poluir `metadata` (jsonb) com payload grande | visitante (10/min por IP) | limite de tamanho/estrutura; remover `metadata` do portal |
| Erro 500 por `address` > 500 ou protocolo colidente | visitante | validação e retry |
| Squatting de protocolo (pré-preencher protocolos válidos) | visitante | protocolo gerado só no servidor e ignorado do request |
| Exposição de PII a qualquer perfil `read` | operador com leitura | separar `ver` de `ver_pii` ou registrar acesso; ADR-019/035 |
| XSS via closures que emitem `{!! !!}` | operador que editar a view | componentes Blade com escape |
| Enumeração de manifestações | não há rota pública de consulta no V2 | no V7, consulta exige protocolo + segundo fator e rate limit |
| `dd()` no `store` da V1 | visitante se a rota V1 vencer | remover a rota V1 ou o `dd()` |
| Logs com PII | log de aplicação | `pii_redact()` (ADR-024) |

---

## 8. Perguntas em aberto

| # | Pergunta | Quem decide | Impacto da resposta |
|---|---|---|---|
| 1 | A resposta deve poder indeferir (desfecho 3) ou o indeferimento é só mudança de status? | Ouvidoria | define o fluxo e o enum |
| 2 | O consentimento é obrigatório para toda manifestação ou só para retorno por e-mail? | DPO/jurídico | define `aceite_lgpd` e o registro |
| 3 | Qual o prazo de resposta e ele varia por tipo/assunto? | Ouvidoria | define `prazo_resposta_em` e o alerta |
| 4 | O cidadão pode acompanhar só com o protocolo ou precisa de e-mail/telefone? | Ouvidoria/segurança | define a rota e o nível de exposição |
| 5 | Quem pode ver o IP e a PII: todo perfil de leitura ou um perfil restrito? | DPO | define permissão/policy |
| 6 | Os dicionários serão editáveis no CMS ou fixos no código? | Produto | define telas e migrations |
| 7 | Qual o prazo de retenção e o que se preserva após anonimizar? | DPO/jurídico | define `RetentionTarget` |
| 8 | A ouvidoria precisa de e-mail ao setor responsável (assunto → secretaria)? | Ouvidoria | reintroduz `responsavel_email` |
| 9 | Os registros importados precisam manter o protocolo V1 para consulta? | Ouvidoria | define normalização e busca |
| 10 | Ouvidoria e e-SIC serão um único módulo ou dois? | Arquitetura | evita duplicar prazos/recursos da LAI |

---

## 9. Checklist de aceite

- [ ] O `00-RESUMO.md` é autossuficiente.
- [ ] Toda afirmação tem `arquivo:linha`.
- [ ] Todo caminho citado existe.
- [ ] O mapa entre versões está completo.
- [ ] Os bugs críticos têm teste de regressão em `07-TESTES.md`.
- [ ] A migração moderna é escrevível sem abrir o legado.
