# Ouvidoria — Arquitetura

> **Slug:** `Ouvidoria` · **Versão analisada:** `lotep` (Laravel 5.5.50 / PHP 7.4)
>
> Camadas, classes e fluxo das duas superfícies. Todo caminho é relativo à raiz do
> repositório `lotep/` e existe no código.

---

## 1. Fluxo da requisição

### 1.1 Portal (público)

```mermaid
sequenceDiagram
    participant C as Cidadão
    participant R as Rota web
    participant M as Middleware
    participant P as OmbudsmanPortalController
    participant D as OmbudsmanRequest
    participant V as Blade v2-ouvidoria::portal
    participant DB as PostgreSQL

    C->>R: GET /ouvidoria
    R->>M: web + MaintenanceMiddleware
    M->>P: index()
    P->>DB: 4 consultas (dicionários ativos/ordenados)
    P->>V: view(types, subjects, origins, methods)
    V-->>C: formulário

    C->>R: POST /ouvidoria/store
    R->>M: web + throttle:10,1
    M->>P: store(Request)
    P->>P: $r->validate([...]) inline
    P->>P: $data = $r->all(); id = v2_uuid(); ip = $r->ip()
    P->>D: Model::create($data)  (fillable filtra)
    D->>D: boot creating(): protocol = OUV-Ymd-<8 do UUID>
    D->>DB: INSERT v2_ombudsman_requests
    P-->>C: redirect()->back() com flash do protocolo
```

### 1.2 CMS (backoffice)

```mermaid
sequenceDiagram
    participant U as Operador
    participant R as Rota cms
    participant A as CmsAuthenticate
    participant P as V2PermissionMiddleware
    participant F as ModuleAccessMiddleware
    participant C as OmbudsmanRequestCmsController
    participant DB as PostgreSQL
    participant L as Audit/CmsLog
    participant V as Blade v2-cms

    U->>R: GET /cms/v2/ouvidoria/requests
    R->>A: autentica (Sentinel)
    A->>P: resolve módulo por active_pattern e nível
    P->>F: module.active:Ouvidoria
    F->>C: index(Request)
    C->>C: v2_can('ouvidoria','read') ou abort(403)
    C->>DB: search($t)->orderBy($sort,$dir)->paginate(20)
    C->>L: cmsLog->info('ouvidoria','index',...)
    C->>V: view(requests, search, sort, dir)
    V-->>U: tabela paginada
```

---

## 2. Rotas principais

| Método | URI | Nome | Controller@action | Middleware | Papel |
|---|---|---|---|---|---|
| GET | `/ouvidoria` | `v2.ouvidoria.portal.index` | `Portal\OmbudsmanPortalController@index` | `web`, `MaintenanceMiddleware` | formulário |
| POST | `/ouvidoria/store` | `v2.ouvidoria.portal.store` | `Portal\OmbudsmanPortalController@store` | `web`, `MaintenanceMiddleware`, `throttle:10,1` | cria manifestação |
| GET | `/cms/v2/ouvidoria/requests` | `v2.ouvidoria.requests.index` | `Admin\OmbudsmanRequestCmsController@index` | `web`, `module.active:Ouvidoria`, `v2.authenticate`, `v2.permission:ouvidoria,read` | lista |
| GET | `/cms/v2/ouvidoria/requests/{id}` | `v2.ouvidoria.requests.show` | `Admin\OmbudsmanRequestCmsController@show` | idem | detalhe |
| POST | `/cms/v2/ouvidoria/requests/{id}/status` | `v2.ouvidoria.requests.status` | `Admin\OmbudsmanRequestCmsController@updateStatus` | idem + `v2.permission:ouvidoria,write` | muda status |
| POST | `/cms/v2/ouvidoria/requests/{id}/answer` | `v2.ouvidoria.requests.answer` | `Admin\OmbudsmanRequestCmsController@answer` | idem + `v2.permission:ouvidoria,write` | responde |

Fontes: `V2/Modules/Servicos/Ouvidoria/Routes/web.php:5-6` e
`V2/Modules/Servicos/Ouvidoria/Routes/cms.php:5-10`.

O grupo CMS é registrado pelo `V2ServiceProvider` com o middleware
`module.active:<ModuleName>`, onde `<ModuleName>` é o nome do diretório (`Ouvidoria`):

```php
// V2/Core/Providers/V2ServiceProvider.php:521-524
$this->groupWithDomain(['web', 'module.active:' . $moduleName], function () use ($cmsRoutes, $controllerNamespace) {
    Route::namespace($controllerNamespace)->group($cmsRoutes);
});
```

As rotas web recebem apenas `web` + `MaintenanceMiddleware`
(`V2/Core/Providers/V2ServiceProvider.php:515-518`): **a desativação do módulo não bloqueia o
portal** — o `POST /ouvidoria/store` continua aceitando manifestações com o módulo desligado.

---

## 3. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `OmbudsmanPortalController` | `V2/Modules/Servicos/Ouvidoria/Controllers/Portal/OmbudsmanPortalController.php` | carrega dicionários e cria a manifestação |
| `OmbudsmanRequestCmsController` | `V2/Modules/Servicos/Ouvidoria/Controllers/Admin/OmbudsmanRequestCmsController.php` | lista, mostra, altera status e responde |
| `OmbudsmanRequest` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanRequest.php` | manifestação; gera `id` e `protocol` no `creating`; `search` |
| `OmbudsmanAnswer` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanAnswer.php` | resposta textual (`file_url` gravado pelo importador) |
| `OmbudsmanManifestationType` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanManifestationType.php` | dicionário tipo; `active()`/`ordered()` |
| `OmbudsmanSubject` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanSubject.php` | dicionário assunto; `fillable` divergente da tabela |
| `OmbudsmanOrigin` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanOrigin.php` | dicionário origem |
| `OmbudsmanResponseMethod` | `V2/Modules/Servicos/Ouvidoria/Models/OmbudsmanResponseMethod.php` | dicionário forma de resposta |
| `ModuleProvider` | `V2/Modules/Servicos/Ouvidoria/ModuleProvider.php` | manifesto do módulo para o menu |
| `OuvidoriaDefaultSeeder` | `V2/Modules/Servicos/Ouvidoria/Seeds/OuvidoriaDefaultSeeder.php` | dicionários iniciais |
| `OuvidoriaLegacyImporter` | `V2/Modules/Support/MigracaoLegada/Services/OuvidoriaLegacyImporter.php` | importa a V1 (fora do módulo) |
| `BaseController` | `V2/Core/Controllers/BaseController.php` | base abstrata com `ValidatesRequests` e `logActivity` |
| `V2AuditLogger` | `V2/Core/Services/V2AuditLogger.php` | trilha de auditoria (`activity()`) |
| `CmsActionLogger` | `V2/Core/Logging/CmsActionLogger.php` | envelope de log do CMS (`info()`) |

Não existem: Form Request, Service, Repository, Policy, API Resource, Job, Event/Listener,
Command ou Observer neste módulo.

---

## 4. Middleware aplicado

| Middleware | Onde é registrado | Efeito observável |
|---|---|---|
| `web` | grupo do `V2ServiceProvider` | sessão, CSRF, cookies; o `csrf_field()` dos formulários depende dele |
| `MaintenanceMiddleware` | `mapModuleRoutes` para `V2/Modules/Servicos/Ouvidoria/Routes/web.php` | modo manutenção do portal |
| `module.active:Ouvidoria` | `mapModuleRoutes` para `V2/Modules/Servicos/Ouvidoria/Routes/cms.php` | consulta `v2_module_flags`; módulo inativo → `abort(404)`. O branch de redirect do middleware compara `getPrefix()` com `'cms'`, que não casa com o prefixo real `cms/v2/ouvidoria` |
| `v2.authenticate` | grupo do arquivo `V2/Modules/Servicos/Ouvidoria/Routes/cms.php` | `CmsAuthenticate`: sem sessão Sentinel → redirect ao login |
| `v2.permission` | grupo/rotas do `V2/Modules/Servicos/Ouvidoria/Routes/cms.php` | resolve o módulo pelo `active_pattern`; `none` → dashboard; `read` em POST → redirect/403 |
| `throttle:10,1` | somente `POST /ouvidoria/store` | 10 tentativas por minuto por IP; excedeu → 429 |
| `v2_can()` (não é middleware) | corpo dos 4 actions do CMS | segunda checagem; falha → `abort(403)` |

Detalhes que mudam o comportamento:

- `V2PermissionMiddleware::handle($request, Closure $next)` **não declara parâmetros**, embora as
  rotas passem `:ouvidoria,read` / `:ouvidoria,write`. O PHP aceita os argumentos extras e o
  middleware os ignora (`V2/Core/Middleware/V2PermissionMiddleware.php:22`). A decisão real vem
  do `active_pattern` do `ModuleProvider` e do método HTTP.
- O `active_pattern` é `cms/v2/ouvidoria*` (`V2/Modules/Servicos/Ouvidoria/ModuleProvider.php:14`); o portal não casa com ele,
  portanto nem passa pela checagem de permissão.
- `ModuleAccessMiddleware` só é anexado às rotas CMS pelo provider; não há `module.active` no
  `V2/Modules/Servicos/Ouvidoria/Routes/web.php` (rotas públicas), as rotas CMS ficam em `Routes/cms.php` do módulo.

---

## 5. Dependências externas

| Pacote | Versão | Para que é usado aqui |
|---|---|---|
| `laravel/framework` | 5.5.50 | Eloquent, validação, Blade, paginação, redirect |
| `cartalyst/sentinel` | (legado) | autenticação do CMS via `v2.authenticate` / `v2_can` |
| Bootstrap 5 + Bootstrap Icons | via layout do CMS/portal | classes `badge`, `card`, `bi-*` |
| PostgreSQL | 10+ | `jsonb`, `ILIKE`, FK, índice único parcial |

Nenhuma biblioteca específica de ouvidoria, fila, e-mail ou anexo é usada no V2.

---

## 6. Acoplamento com outros módulos

| Módulo | Tipo de acoplamento | Onde acontece |
|---|---|---|
| `Support/MigracaoLegada` | escrita nas tabelas do módulo | `OuvidoriaLegacyImporter` grava `v2_ombudsman_requests`, `v2_ombudsman_answers` e dicionários |
| `Institucional/BasicPage` | link de LGPD | `route('v2.portal.lgpd')` na view do portal (`Views/portal/index.blade.php:191`) |
| `Core` (layouts) | views e partials | `v2-cms::layouts.app`, `v2-cms::partials.{breadcrumb,filter-card,table}`, `v2-portal::app-layout` |
| `Core` (serviços) | auditoria/log/permissão | `V2AuditLogger`, `CmsActionLogger`, `v2_can`, `v2_uuid` |
| Legado V1 | colisão de URL | `routes/web.php:58-59` registra `any /ouvidoria` e `any /ouvidoria/store` além das rotas V2 |

---

## 7. Pontos de entrada alternativos

- **Tarefas agendadas (cron/queue):** nenhuma.
- **Comandos artisan:** nenhum no módulo; a importação é acionada pelo módulo de migração legada.
- **Webhooks / callbacks:** nenhum.
- **Jobs / eventos:** nenhum.
- **Importação:** `OuvidoriaLegacyImporter::extract()/map()/load()/dryRun()` executada pelo
  `LegacyImportService` do módulo de migração.
- **Seed:** `OuvidoriaDefaultSeeder::run()` chamado por `V2/Seeds/V2EnvironmentSeeder.php:39`.

---

## 8. Observações de arquitetura

- O módulo é **thin controller**: toda a regra está no controller, no model ou na migration.
  Não há camada onde testar o fluxo sem subir o framework.
- O portal e o CMS compartilham o mesmo model, mas **não** compartilham validação, formatação
  nem constantes de status.
- O protocolo é um efeito colateral do `boot()` do model; qualquer `create()` (inclusive o
  importador, que passa `protocol` explicitamente) atravessa esse hook.
- O `ModuleProvider` declara `position => 71` e `route => v2.ouvidoria.requests.index`; o
  `SidebarComposer` consome esse manifesto e o `active_pattern`.
- O grupo declarado no arquivo é `Serviços`, mas o `V2ServiceProvider` sobrescreve com o nome
  do diretório (`Servicos`) antes de publicar `v2.modules.configs`
  (`V2/Core/Providers/V2ServiceProvider.php:104-107`).
