# Ouvidoria — Arquitetura

## 1. Visão geral das camadas

O módulo **não usa repositories**. O fluxo é: rota → middleware → controller → Form Request (no CMS) / validação inline (no portal) → Eloquent direto → view Blade / JSON / e-mail.

```
┌──────────────────────────── CMS (autenticado) ─────────────────────────────┐
│                                                                              │
│  routes/basepro.php:89-105  (dentro do grupo prefix 'cms' + before: csrf)    │
│  + 7 rotas em routes/basepro.php:118-120 (prefix 'ajax')                     │
│  + 6 rotas em routes/basepro.php:463-468 (prefix 'datatable')                 │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────┐        │
│  │ 8 controllers em v6/app/Http/Controllers/Prefecture/            │        │
│  │   OmbudsmanRequestController      (listagem, dados, enviar,     │        │
│  │   OmbudsmanAnswerController       (responder, indeferir, store)│        │
│  │   OmbudsmanSubjectController      (CRUD assuntos)               │        │
│  │   OmbudsmanSectorController       (CRUD setores/órgãos)         │        │
│  │   OmbudsmanManifestationTypeController (CRUD tipos + setStatus) │        │
│  │   OmbudsmanResponseMethodController (CRUD formas de resposta)   │        │
│  │   OmbudsmanOriginController       (CRUD origens)                │        │
│  │   OmbudsmanCannedResponseController (CRUD respostas prontas)    │        │
│  └──────────────────────────────────────────────────────────────────┘        │
│        │                    │                                                  │
│        ▼                    ▼                                                  │
│  Form Requests        Model Eloquent (App\Models\* e App\Prefecture\*)        │
│  (Ombudsman*Request)   → ombudsman_requests, ombudsman_answers,               │
│                          ombudsman_subjects, ombudsman_sectors,                │
│                          ombudsman_origins, ombudsman_response_methods,        │
│                          ombudsman_manifestation_types,                        │
│                          ombudsman_canned_responses,                           │
│                          ombudsman_request_logs, statuses, contents           │
│        │                                                                     │
│        ├──► activity()->log()  (spatie/laravel-activitylog)                    │
│        └──► Mail::send()      (9 templates Blade)                             │
│                                                                              │
│        ▼                                                                     │
│  resources/views/prefecture/ombudsman/**  (AdminLTE 1.22 / Bootstrap 3)       │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────── PORTAL (público) ──────────────────────────────┐
│                                                                              │
│  routes/web.php:23, 73-74, 98-99  (grupo check.site.manutention)            │
│                                                                              │
│  Portal\OmbudsmanController (App\Http\Controllers\Portal)                    │
│    getIndex()      → página da Ouvidoria + formulário                         │
│    store()         → cria a manifestação + 2 e-mails                         │
│    relStatitics()  → relatório estatístico público + exportações             │
│    getOmbudsmanByProtocol() / getAnswerByOmbudsman()                          │
│    getOmbudsmanAnswers() / newMessage()  (contestação)                        │
│                                                                              │
│        ▼                                                                     │
│  resources/views/portal/layouts/modelo{1..6}/                                │
│    informacoes-instituicao/ouvidoria/          (index + form-contact)        │
│    informacoes-instituicao/acesso-rapido/consulta-solicitacao-ouvidoria      │
│    transparencia-fiscal/relatorio-estatistico-ouvidoria/  (index + js)       │
│    transparencia-fiscal/ouvidoria-solicitada.blade.php                        │
└──────────────────────────────────────────────────────────────────────────────┘
```

## 2. Mapa de classes

### 2.1 Controllers CMS — `v6/app/Http/Controllers/Prefecture/`

| Classe | Métodos | Middleware | Papel |
|---|---|---|---|
| `OmbudsmanRequestController` | `index`, `update`, `dados_do_chamado`, `enviar_chamado`, `prorrogar_chamado` | **NÃO registrado** (construtor sobrescrito) | Listagem, detalhe JSON, reenvio/encaminhamento, prorrogação |
| `OmbudsmanAnswerController` | `createByRequest`, `createRejection`, `store` | **herdado** (`check.route.permission`) | Resposta, indeferimento, anexos, e-mail |
| `OmbudsmanSubjectController` | `index`, `create`, `store`, `edit`, `update`, `destroy`, `deleteAll` | **NÃO registrado** | CRUD de assuntos |
| `OmbudsmanSectorController` | `index`, `create` (redireciona), `store`, `edit`, `update`, `destroy`, `deleteAll` | **NÃO registrado** | CRUD de setores/órgãos, com bloqueio de exclusão quando há assuntos vinculados |
| `OmbudsmanManifestationTypeController` | `index`, `create`, `store`, `edit`, `update`, `destroy`, `setStatus`, `deleteAll` | **NÃO registrado** | CRUD de tipos de chamado + alternância de `status` |
| `OmbudsmanResponseMethodController` | `index`, `create`, `store`, `edit`, `update`, `destroy`, `deleteAll` | **NÃO registrado** | CRUD de formas de resposta |
| `OmbudsmanOriginController` | `index`, `create`, `store`, `edit`, `update`, `destroy`, `deleteAll` | **NÃO registrado** | CRUD de origens de chamado |
| `OmbudsmanCannedResponseController` | `index`, `create`, `store`, `edit`, `update`, `destroy`, `respostas_prontas` (JSON), `deleteAll` | **NÃO registrado** | CRUD de respostas prontas + endpoint JSON para o editor |

> **Divergência verificada:** em Laravel 5.5, o middleware de controller é coletado via `$controller->getMiddleware()`, que só é populado quando o construtor da classe base é executado. `Prefecture\Controller::__construct()` (`v6/app/Http/Controllers/Prefecture/Controller.php:20-24`) registra `check.route.permission`. **Apenas `OmbudsmanAnswerController` herda esse construtor** — os outros 7 o sobrescrevem sem chamar `parent::__construct()`. [INFERÊNCIA] as rotas desses 7 controllers **não** recebem `check.route.permission`, que é o que exige login via Sentinel e checa `hasAnyAccess`. Isso difere do padrão dos demais módulos do CMS.

### 2.2 Controller Portal

`v6/app/Http/Controllers/Portal/OmbudsmanController.php` — usa os traits `NewsTrait`, `GetInformationsPrefectureTrait` e `DataMaskTrait`.

| Método | Retorno |
|---|---|
| `getIndex()` | View `portal.layouts.{modelo}.informacoes-instituicao.ouvidoria.index` |
| `store(Request)` | View `portal.layouts.{modelo}.transparencia-fiscal.ouvidoria-solicitada` |
| `relStatitics(Request)` | View `portal.layouts.{modelo}.transparencia-fiscal.relatorio-estatistico-ouvidoria.index` (ou `export()` se `type` preenchido) |
| `getOmbudsmanByProtocol(Request)` | JSON com o chamado |
| `getAnswerByOmbudsman(Request)` | JSON com as respostas |
| `getOmbudsmanAnswers(Request)` | JSON com `{request: {protocol, mask_description, status_id}, answers: [...]}` |
| `newMessage(Request)` | JSON — registra contestação |
| `export($ombudsmanRequests, $type)` | Download xls / csv / pdf / txt |
| `exportTxt(...)` (private) | TXT formatado |

### 2.3 Models — `v6/app/Http/Model/Prefecture/Ombudsman*.php`

> **Atenção ao namespace:** todos declaram `namespace App\Models` **exceto** `OmbudsmanAnswer`, que declara `namespace App\Prefecture`. O autoload é por `classmap` (`v6/composer.json`), que mapeia a classe para o arquivo independentemente do namespace.

| Classe | Namespace | `$table` | SoftDeletes | Relações declaradas |
|---|---|---|---|---|
| `OmbudsmanRequest` | `App\Models` | (implícita) `ombudsman_requests` | **Não** (coluna `deleted_at` existe) | `type`, `subject`, `responseMethod`, `origin` (belongsTo); `status` (hasOne `Status`); `ombudsman_answers` (hasMany) |
| `OmbudsmanAnswer` | `App\Prefecture` | `ombudsman_answers` | Sim | `statuses` (belongsTo `Status`); `archive` (hasOne `Archive`); `ombudsmanRequests` (belongsTo `OmbudsmanRequest`); `archives()` **comentado** |
| `OmbudsmanSubject` | `App\Models` | (implícita) | Sim | `ombudsmans()` (hasMany `OmbudsmanRequest`); `sector()` (belongsTo); `secretary()` (belongsTo `Content`); accessor `sector_name` |
| `OmbudsmanSector` | `App\Models` | `ombudsman_sectors` | Sim | `content()` (belongsTo `Content`); `subjects()` (hasMany `OmbudsmanSubject`) |
| `OmbudsmanOrigin` | `App\Models` | (implícita) | **Não** | `ombudsmans()` (hasMany `App\Ombudsman` — **classe inexistente**) |
| `OmbudsmanResponseMethod` | `App\Models` | (implícita) | **Não** | `ombudsmans()` (hasMany `App\Ombudsman` — **classe inexistente**) |
| `OmbudsmanManifestationType` | `App\Models` | (implícita) | **Não** | `ombudsmans()` (hasMany `OmbudsmanRequest`) |
| `OmbudsmanCannedResponse` | `App\Models` | (implícita) | **Não** | `ombudsmans()` (hasMany `OmbudsmanRequest`) |
| `OmbudsmanRequestLogs` | `App\Models` | (implícita) | **Não** | `user()` (belongsTo `App\Admin\User`); `sectorFrom()`/`sectorTo()` (belongsTo `OmbudsmanSector`); `subjectFrom()`/`subjectTo()` (belongsTo `OmbudsmanSubject`) |

> **Relações quebradas verificadas:**
> - `App\Ombudsman` **não existe** como classe (nem em `v6/app/`, nem no `autoload_classmap.php`). As relações `ombudsmans()` de `OmbudsmanOrigin` e `OmbudsmanResponseMethod` apontam para ela.
> - `App\OmbudsmanRequest` **não existe** como classe. As relações `ombudsmans()` de `OmbudsmanSubject` e `OmbudsmanManifestationType` a importam.
> - `OmbudsmanRequestLogs::sectorFrom()/sectorTo()` apontam para `OmbudsmanSector`, mas as FKs no banco referenciam `contents(id)` — divergência model↔banco.

### 2.4 Traits

| Trait | Arquivo | Uso |
|---|---|---|
| `DataMaskTrait` | `v6/app/Http/Traits/DataMaskTrait.php` | `maskTextDeterministic`, `maskTextWithAi` (chamada por API externa), `maskProtocol`, `maskName`, `maskSensitiveDictionary` (privado) |
| `NewsTrait` | `v6/app/Http/Traits/NewsTrait.php` | `getContentNews()` — conteúdo de notícias exibido no portal |
| `GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` | `getStaticPageData($slug)`, `updateClickStaticPage($id)` |

## 3. Fluxo principal — criação da manifestação (portal)

```
GET /ouvidoria  → Portal\OmbudsmanController@getIndex
   ├─ $subjects          = OmbudsmanSubject::whereNull('deleted_at')->with('sector')->get()
   ├─ $manifestation_types = OmbudsmanManifestationType::where('status', true)->whereNull('deleted_at')->get()
   ├─ $origins           = OmbudsmanOrigin::whereNull('deleted_at')->get()
   ├─ $response_methods  = OmbudsmanResponseMethod::whereNull('deleted_at')->get()
   ├─ $modalities        = ['identificado','sigiloso','anonimo']
   ├─ $contents          = getStaticPageData('ouvidoria') ?: getStaticPageData('contato')
   │     └─ se nenhum existir → view "admin.errors.conteudo-nao-encontrado"
   ├─ $archives, $view_pdf = Archive da página estática
   └─ updateClickStaticPage($contents->id)

POST /ouvidoria/store  → Portal\OmbudsmanController@store
   ├─ $validated = $this->validated($request)   ← regras inline (não usa OmbudsmanPortalRequest)
   ├─ OmbudsmanRequest:
   │    protocol  = 'OUV-' . now()->format('dmY-His')
   │    anonymous = $request->has('anonymous')
   │    status_id = 1  (Em Tramitação)
   │    ip        = $_SERVER['REMOTE_ADDR']
   │    browser   = $request->header('User-Agent')
   │    token     = Hash::make($request->token)
   │    mask_description = $this->maskTextWithAi($request->description)
   ├─ Mail 1 → prefecture.emails.ombudsman-requests.email_user   (ao cidadão, se houver e-mail)
   ├─ Mail 2 → prefecture.emails.ombudsman-requests.email_admin  (ao e-mail do assunto)
   │    └─ to = OmbudsmanSubject::where('id', $subject_id)->first()->email
   └─ view ouvidoria-solicitada (protocolo + prazo de configurations.response_time)
```

## 4. Fluxo principal — resposta (CMS)

```
GET /cms/chamado-da-ouvidoria/responder/{id}  → OmbudsmanAnswerController@createByRequest
   ├─ $ombudsman_request = OmbudsmanRequest::with('type','subject','responseMethod','origin','status')->find($id)
   ├─ $answers   = OmbudsmanAnswer::where('ombudsman_request_id', $id)->with('archive')->get()
   ├─ $request_logs = OmbudsmanRequestLogs::where(...)->with('user','sectorFrom','sectorTo','subjectFrom','subjectTo')->get()
   └─ view prefecture.ombudsman.request.answer.create

POST /cms/chamado-da-ouvidoria/responder  → OmbudsmanAnswerController@store(OmbudsmanAnswerRequest)
   ├─ type==1 → status=2 (Atendido), texto em `answer`
   │  type==0 → status=3 (Indeferido), texto em `rejected`
   ├─ DB::transaction:
   │    ├─ OmbudsmanAnswer::create([... 'mask_answer' => $this->maskTextWithAi(...)])
   │    ├─ anexos: Archive::create + storeAs("answer/{id}/") + $answer->archives()->attach(...)
   │    ├─ $ombudsmanRequest->status_id = $status; save()
   │    ├─ activity()->log('Chamado de Ouvidoria respondida')
   │    └─ Mail::send(view, [...]) → email_answer_attach | email_answer_no_attach | email_rejected
   └─ redirect rota('chamados-da-ouvidoria.index')
```

## 5. Fluxo do relatório estatístico

```
GET /relatorio-estatistico-ouvidoria?ano=2026[&type=xls|csv|pdf|txt]
   → Portal\OmbudsmanController@relStatitics
      ├─ $ombudsmanRequests = OmbudsmanRequest::with('status')->byDateBetweenMain($ano)->get()
      ├─ se $request->filled('type') → $this->export($ombudsmanRequests, $type)
      ├─ $emTramitacao_apos_prazo = OmbudsmanRequest::getByBetweenDate($ano, 1, 1)->count()
      ├─ $emTramitacao_no_prazo   = OmbudsmanRequest::getByBetweenDate($ano, 1, 2)->count()
      ├─ $prorrogado_apos_prazo   = OmbudsmanRequest::getByBetweenDate($ano, 4, 1)->count()
      ├─ $prorrogado_no_prazo     = OmbudsmanRequest::getByBetweenDate($ano, 4, 2)->count()
      ├─ $indeferido_apos_prazo   = OmbudsmanRequest::getByBetweenDateWithAnswer($ano, 3, 1)->count()
      ├─ $indeferido_no_prazo     = OmbudsmanRequest::getByBetweenDateWithAnswer($ano, 3, 2)->count()
      ├─ $atendido_apos_prazo     = OmbudsmanRequest::getByBetweenDateWithAnswer($ano, 2, 1)->count()
      ├─ $atendido_no_prazo       = OmbudsmanRequest::getByBetweenDateWithAnswer($ano, 2, 2)->count()
      └─ view relatorio-estatistico-ouvidoria.index
```

Os escopos `getByBetweenDate` e `getByBetweenDateWithAnswer` implementam a classificação "no prazo" vs "após o prazo" com **20 dias hardcoded**:

```php
->whereRaw('EXTRACT(DAY FROM now() - '.$values['fieldExtract'].') '.$values['comparation'].' 20')
```

Detalhamento em `10-FLUXO-DO-CHAMADO.md`.

## 6. Dependências externas

| Pacote (`v6/composer.json`) | Uso no módulo |
|---|---|
| `laravel/framework` 5.5.* | Base |
| `cartalyst/sentinel` 2.0.* | Autenticação e permissões |
| `spatie/laravel-activitylog` ^2.8 | Auditoria (`activity()->performedOn(...)->log(...)`) |
| `laralabs/toaster` ^2.0 | Flash messages |
| `laravelcollective/html` ^5.5 | `Form::` helpers |
| `laravellegends/pt-br-validator` ^5.1 | Mensagens de validação em pt-BR |
| `maatwebsite/excel` ^3.1 | Exportação xls/csv (`OmbudsmanRequestsExport`) |
| `dompdf/dompdf` + `barryvdh/laravel-dompdf` | Exportação PDF (`PDF::loadView('exports.ombudsman-requests')`) |
| `greggilbert/recaptcha` ^2.2 | Captcha no formulário do portal (`{!! Recaptcha::render() !!}`) — a validação do token está **comentada** no controller |
| `diglactic/laravel-breadcrumbs` ^4.0 | Breadcrumbs `ouvidoria` e `static-ouvidoria` (`v6/routes/breadcrumbs.php:18-21,42-45`) |
| `swiftmailer/swiftmailer` ^6.0 | Envio dos 9 templates de e-mail |

## 7. Dependências internas

| Depende de | Uso |
|---|---|
| `contents` (`category_contents` slug `secretarias`) | Origem dos setores na v5; na v6, `ombudsman_sectors.content_id` referencia `contents` |
| `statuses` | Tabela de status compartilhada (7 status); FK `ombudsman_requests.status_id` |
| `archives` | Anexos das respostas e arquivos da página estática |
| `configurations` | `email`, `titulo_site`, `response_time`, `full_list_requests_disabled`, `layout` |
| `static_pages` | Página institucional da Ouvidoria (slug `ouvidoria` ou `contato`) e do relatório (slug `relatorio-estatistico-ouvidoria`) |
| `users` | `ombudsman_request_logs.user_id` |
| `App\Prefecture\Content` | Secretarias |

## 8. Diagrama de dependência entre models

```
OmbudsmanSector ──hasMany──► OmbudsmanSubject ──belongsTo──► OmbudsmanSector
       │                             │
       │                             │  (secretary_id → contents, legado)
       │                             ▼
       │                      OmbudsmanRequest ──belongsTo──► OmbudsmanManifestationType
       │                             │   ├──belongsTo──► OmbudsmanSubject
       │                             │   ├──belongsTo──► OmbudsmanResponseMethod
       │                             │   ├──belongsTo──► OmbudsmanOrigin
       │                             │   ├──hasOne─────► Status          (statuses)
       │                             │   ├──hasMany───► OmbudsmanAnswer ──hasOne──► Archive
       │                             │   └──belongsTo─► App\Models\OmbudsmanRequestLogs
       │                             │        ├──belongsTo──► User
       │                             │        ├──belongsTo──► OmbudsmanSector (sectorFrom/To)
       │                             │        └──belongsTo──► OmbudsmanSubject (subjectFrom/To)
       ▼
  Content (contents)  ◄──belongsTo── OmbudsmanSector (content_id)
```
