# Solicitações LAI (e-SIC) — Arquitetura

> Versão analisada: `v6` (Laravel 5.5.50 / PHP 7.2.34 / PostgreSQL 10 + Redis + APCu)

---

## 1. Visão geral das camadas

O módulo **não** usa a arquitetura `Route → Controller → Repository → Service →
Model` que o restante da plataforma adota. Não existe repository nem service
dedicado à LAI: tudo está concentrado em dois controllers, um model e uma
trait. Isso é relevante para a reconstrução (ver `06-MIGRACAO-MODERNA.md`).

```
                    ┌──────────────────────── PORTAL (público) ────────────────────────┐
 Formulário Blade   │                                                                │
 (solicitacao-form) │  POST /informacao-solicitada (requested-information)              │
        │           │  POST /informacao-solicitada-ajax (requested-information-ajax)     │
        ▼           │                                                                │
 ┌──────────────────┴──────────────────────────────────────────────────────────┐      │
 │ Portal\InformationController                                            │      │
 │  ├─ store()                      → InformationsRequestRequest (Form)  │      │
 │  ├─ storeRequestInformacaoAjax()  → validator() inline                │      │
 │  ├─ getInformationByProtocol()    → JSON                               │      │
 │  └─ getAnswerByInformation()      → JSON + view renderizada           │      │
 └───────────────┬───────────────────────────────────────────────────────┘      │
                 │  DB::transaction                                              │
                 ▼                                                              │
   Person / Address / Email / Phone  →  informations_requests (mask_request)    │
                 │                                                              │
                 │  Mail::send (prefecture.emails.informations-requests.*)      │
                 └──────────────────────────────────────────────────────────────┘
                                                                                │
 ┌───────────────────────────── CMS (painel) ───────────────────────────────┐  │
 │ menu "Solicitações (LAI)"  →  route('info-request-by-status', 1..4)      │  │
 │                          ▼                                                 │  │
 │ Prefecture\InformationsRequestController                                   │  │
 │  ├─ index()          → (quebrado: view exige $status)                     │  │
 │  ├─ listByStatus()   → prefecture.informations-requests.index            │  │
 │  ├─ statusUpdate()   → status_id + e-mail de prorrogação                  │  │
 │  ├─ show()           → prefecture.informations-requests.show             │  │
 │  ├─ create()/store() → (quebrado: views e rota inexistentes)             │  │
 │  └─ edit/update/destroy() → vazios                                       │  │
 │                          ▼                                                 │  │
 │ Prefecture\AnswerController@store  (POST cms/answer)                      │  │
 │  ├─ Answer::create (status 2 = Atendido | 3 = Indeferido)                │  │
 │  ├─ Archive::create + pivot answer_archives + storeAs("answer/{id}/")     │  │
 │  └─ Mail::send (prefecture.emails.answer.*)                              │  │
 │                          ▼                                                 │  │
 │ Prefeture\DatatableController@get_lai*  → DB::table("em_tramitacao") etc. │  │
 └──────────────────────────────────────────────────────────────────────────┘  │
                                                                                │
 ┌──────────────────────── PORTAL — Relatório Estatístico ───────────────────┐  │
 │ Portal\TransparencyController@relStatitics  → InformationsRequest::      │  │
 │   getByBetweenDate / getByBetweenDateWithAnswer (scopes com whereRaw)     │  │
 │ Portal\TransparencyController@getAnswers → JSON com mask_request          │  │
 │ Portal\TransparencyController@export   → xls / csv / pdf / txt             │  │
 └──────────────────────────────────────────────────────────────────────────┘
```

## 2. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `App\Http\Controllers\Portal\InformationController` | `v6/app/Http/Controllers/Portal/InformationController.php` | Endpoint público: recebe o pedido, grava, envia e-mails, consulta por protocolo. Estende `App\Http\Controllers\Admin\Controller` (herda `getTemplateActived()`). Usa `DataMaskTrait`. |
| `App\Http\Controllers\Prefecture\InformationsRequestController` | `v6/app/Http/Controllers/Prefecture/InformationsRequestController.php` | Telas do CMS: listagem por status, alteração de status, detalhe. O cabeçalho do arquivo declara: *"Este controller esta em off"*. |
| `App\Http\Controllers\Prefecture\AnswerController` | `v6/app/Http/Controllers/Prefecture/AnswerController.php` | Registro da resposta/indeferimento, upload de anexos, e-mail ao solicitante, atualização do status. |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | DataTables de LAI (`get_lai`, `get_lai_atendidos`, `get_lai_prorrogados`, `get_lai_indeferidos`) sobre vistas SQL. |
| `App\Http\Controllers\Portal\TransparencyController` | `v6/app/Http/Controllers/Portal/TransparencyController.php` | Relatório Estatístico público e exportações da LAI. |
| `App\Prefecture\InformationsRequest` | `v6/app/Http/Model/Prefecture/InformationsRequest.php` | Model Eloquent da tabela `informations_requests`; scopes de prazo, acessores mascarados, relações. |
| `App\Prefecture\Answer` | `v6/app/Http/Model/Prefecture/Answer.php` | Model das respostas; `archives()` (belongsToMany via `answer_archives`). |
| `App\Prefecture\Archive` | `v6/app/Http/Model/Prefecture/Archive.php` | Model dos anexos. |
| `App\Prefecture\Status` | `v6/app/Http/Model/Prefecture/Status.php` | Status (1 Em Tramitação, 2 Atendido, 3 Indeferido, 4 Prorrogado). |
| `App\Admin\Person` / `Address` / `Email` / `Phone` | `v6/app/Http/Model/Admin/Person.php` etc. | Titular do pedido e contatos; gravados a cada solicitação. |
| `App\Http\Requests\InformationsRequestRequest` | `v6/app/Http/Requests/InformationsRequestRequest.php` | Form Request do formulário público (reCAPTCHA, CPF/CNPJ, tamanho do texto). |
| `App\Http\Requests\AnswerRequest` | `v6/app/Http/Requests/AnswerRequest.php` | Valida `information_request_id`, `type` e o texto da resposta/justificativa. |
| `App\Http\Traits\DataMaskTrait` | `v6/app/Http/Traits/DataMaskTrait.php` | Mascaramento determinístico + IA, `maskProtocol()`, `maskName()`. |
| `App\Services\Dashboard\Widgets\LaiWidget` | `v6/app/Services/Dashboard/Widgets/LaiWidget.php` | Widget de pizza com a distribuição por status no dashboard do CMS. |
| `App\Exports\InformationsRequestsExport` | `v6/app/Exports/InformationsRequestsExport.php` | Export `FromView` (xls/csv) a partir da view `exports.informations-requests`. |
| `App\Prefecture\Configuration` | `v6/app/Http/Model/Prefecture/Configuration.php` | Chaves `email`, `email_lai`, `titulo_site`, `layout`, `full_list_requests_disabled`, `relatorio_completolai`, `gemini_api_key`. |
| `App\Library\Helper\appHelpers` (`has_access`) | `v6/app/Library/Helper/appHelpers.php` | Checagem de permissão usada no menu do CMS. |

## 3. Fluxo detalhado: registro de um pedido (portal)

1. O visitante abre a página estática `solicitar-informacao` (slug em `static_pages`).
   `Portal\PortalController@staticShow` redireciona para `acesso-rapido.index`
   (`v6/app/Http/Controllers/Portal/PortalController.php:298`), que em
   `QuickAccessController::getIndexStatic` devolve a view
   `portal.layouts.newsbit.informacoes-instituicao.acesso-rapido.solicitar-informacao`.
2. A view inclui `portal.layouts.$modelo.transparencia.solicitacao-form`, que
   abre o form em `route('requested-information')` e injeta
   `<input type="hidden" name="protocol" value="{{date('YmdHis')}}">` — o
   protocolo é gerado **no HTML**, não no controller.
3. `POST /informacao-solicitada` → `Portal\InformationController@store`
   (`v6/app/Http/Controllers/Portal/InformationController.php:71`).
4. `InformationsRequestRequest` valida: `request` obrigatório 20..4000 chars,
   `email_address` obrigatório e e-mail válido, `protocol` único e numérico,
   `g-recaptcha-response` obrigatório, `person-type` obrigatório/booleano e,
   conforme o tipo, `cpf`/`rg`/`name` (Pessoa Física) ou `cnpj`/`social_name`
   (Pessoa Jurídica).
5. Dentro de `DB::transaction`: cria `Person` (PF ou PJ), `Address`, `Email`,
   `Phone` e `InformationsRequest` com `status_id = 1`, `ip` de
   `$_SERVER['REMOTE_ADDR']`, `browser` de `$_SERVER['HTTP_USER_AGENT']` e
   `mask_request` calculado por `MaskRequestWithGeminiAi()`.
6. Envia dois e-mails: `prefecture.emails.informations-requests.email_user`
   (para o solicitante, com o protocolo) e
   `prefecture.emails.informations-requests.email_admin` (para `email_lai` ou,
   se vazio, para `email`).
7. Renderiza `portal.layouts.$template.transparencia-fiscal.informacao-solicitada`
   com `success = true`.

> Observação: a variante **ajax** (`storeRequestInformacaoAjax`) duplica quase
> todo o bloco de gravação, com validação inline diferente (sem reCAPTCHA,
> `email_address` opcional) — ver `05-REGRAS-E-PORTABILIDADE.md`.

## 4. Fluxo detalhado: triagem e resposta (CMS)

1. `GET cms/solicitacoes/lai/{status}` → `InformationsRequestController@listByStatus`.
   O controller converte o número em slug textual (`1` → `em-tramitacao`,
   `2` → `atendidos`, `3` → `indeferidos`, `4` → `prorrogados`), registra
   `menuClicks('lai')` e um log de atividade (`spatie/laravel-activitylog`).
2. A view `prefecture.informations-requests.index` carrega
   `assets/js/custom/contents/solicitacao-lai/{status}/datatables.js`.
3. O JS chama `runDatatables(status, colunas)`
   (`v6/public/assets/js/custom/utilities/table.js`), que faz
   `GET /cms/datatable/{status}`.
4. `Prefecture\DatatableController@get_lai*` lê a **vista SQL** correspondente
   (`DB::table("em_tramitacao")`, etc.) e devolve JSON para a DataTable.
5. Botões da tabela:
   - "Responder" / "Nova resposta" / "Visualizar" → `GET /cms/respostas-lai/{id}`
     (rota `respostas-lai.show`) → `InformationsRequestController@show`, que
     carrega `Answer::with('archive')->where('informations_request_id', $id)`.
   - "Prorrogar" → `GET /cms/solicitacoes/{id}/lai/4` →
     `InformationsRequestController@statusUpdate`, que grava `status_id = 4` e
     dispara o e-mail `prefecture.emails.answer.email_to_extend`.
6. Formulário de resposta: `prefecture.answers.form-answer` (`type = 1`) e
   `prefecture.answers.form-rejected` (`type = 0`), ambos em `POST cms/answer`
   (`answer.store`).
7. `Prefecture\AnswerController@store` cria a `Answer`, sobe os anexos para
   `storage/app/public/answer/{answer_id}/`, vincula via `answer_archives`,
   atualiza `informations_requests.status_id` para 2 (Atendido) ou 3
   (Indeferido) e envia o e-mail correspondente
   (`email_answer_attach`, `email_answer_no_attach` ou `email_rejected`).

## 5. Consultas públicas por protocolo

| Endpoint | Controller | Comportamento |
|---|---|---|
| `POST cms/ajax/get/information/by/protocol` | `Portal\InformationController@getInformationByProtocol` | Devolve o model `InformationsRequest` **completo** (com `request` original, `ip`, `browser` e a relação `person`) em JSON 200, ou JSON vazio com status 404. Verificado em execução: protocolo inexistente → HTTP 404 e corpo `{}`. |
| `POST cms/ajax/get/answer/by/information` | `Portal\InformationController@getAnswerByInformation` | Devolve a lista de respostas com o HTML da lista de arquivos já renderizado. Verificado em execução: protocolo inexistente → erro 500 (`Trying to get property 'id' of non-object` em `InformationController.php:44`). |
| `POST /AjaxGetanswers` (`get.answers`) | `Portal\TransparencyController@getAnswers` | Endpoint do modal do Relatório Estatístico; devolve `protocol` mascarado, `mask_request` e as respostas. |

> As duas primeiras rotas vivem sob o prefixo `/cms/ajax/`, mas o controller é
> do namespace `Portal` e **não** usa `check.logged`; o `CheckRoutePermission`
> só é aplicado por `Prefecture\Controller::__construct`. Resultado: são
> acessíveis por qualquer visitante, desde que o protocolo seja conhecido.

## 6. Dependências externas (composer)

| Pacote | Uso no módulo |
|---|---|
| `cartalyst/sentinel` | Sessão e roles do CMS; `Sentinel::getUser()` nos logs de atividade. |
| `yajra/laravel-datatables` | `DataTables::of(...)` nas listagens do CMS. |
| `maatwebsite/excel` | `InformationsRequestsExport` (xls/csv) do Relatório Estatístico. |
| `barryvdh/laravel-dompdf` | Export PDF do Relatório Estatístico. |
| `greggilbert/recaptcha` | `Recaptcha::render()` no formulário e regra `recaptcha` no Form Request. |
| `laravellegends/pt-br-validator` | Regras `cpf` e `cnpj` usadas no `InformationsRequestRequest`. |
| `spatie/laravel-activitylog` | `activity()->performedOn(...)->log(...)` nas transições. |
| `guzzlehttp/guzzle` | Chamada HTTP à API Gemini dentro de `DataMaskTrait::maskTextWithAi`. |
| `diglactic/laravel-breadcrumbs` | Breadcrumb `static-transparencia` nas páginas do portal. |

## 7. Pontos de acoplamento

- **Dashboard:** `LaiWidget` (`v6/app/Services/Dashboard/Widgets/LaiWidget.php`)
  consulta `InformationsRequest` diretamente; o widget é registrado para o
  módulo `solicitacoes-lai`.
- **Menu do CMS:** `App\Providers\MenuServiceProvider` monta o submenu "Solicitações
  (LAI)" quando o `MenuOrder` de slug `lai` é encontrado
  (`v6/app/Providers/MenuServiceProvider.php:72`).
- **Barra superior do CMS:** `v6/resources/views/vendor/adminlte/page.blade.php:161`
  exibe o menu quando `has_access(['lai.index'])`.
- **Relatório Estatístico:** `Portal\TransparencyController@relStatitics` usa os
  scopes `getByBetweenDate` / `getByBetweenDateWithAnswer` do mesmo model.
- **Portal da Transparência:** as páginas do relatório ficam sob
  `transparencia-fiscal` e compartilham as configurações `layout` e
  `full_list_requests_disabled`.
- **Acessibilidade e LGPD:** o módulo depende de `DataMaskTrait`, compartilhado
  com a Ouvidoria (`v6/app/Http/Traits/DataMaskTrait.php`).

## 8. Diagrama de sequência resumido (resposta do operador)

```
Operador        Browser        Answers/InformationsRequestController   AnswerController   DB        Mail
   |               |                    |                                  |              |         |
   |--clica Responder----------------->|                                  |              |         |
   |               |--GET /cms/respostas-lai/{id}-->|                    |              |         |
   |               |                    |--find(InformationsRequest)----->|              |         |
   |               |<--HTML com form-answer/form-rejected--------------|              |         |
   |--preenche e submete POST /cms/answer------------------------------------------------->|         |
   |               |                    |                    |--AnswerRequest->|          |         |
   |               |                    |                    |                |--DB::transaction-------->|
   |               |                    |                    |                |  create answers        |
   |               |                    |                    |                |  storeAs answer/{id}/   |
   |               |                    |                    |                |  update status_id      |
   |               |                    |                    |                |------------------->|    |
   |               |                    |                    |<--Mail::send (email_answer_*)------|    |
   |<--redirect cms/solicitacoes/lai/{status}---------------|                |              |         |
```
