# Carta de Serviços — Arquitetura

## 1. Visão geral das camadas

O módulo **não usa repositories** nem `Services` (com uma exceção rompida, tratada em §6). O fluxo é o MVC clássico do legado: rota → middleware → controller → model Eloquent → view.

```
┌───────────────────────────── CMS (autenticado) ──────────────────────────────┐
│                                                                              │
│  routes/basepro.php:512 ──require──> routes/service_letter.php                │
│   prefix group 'cms' + before:csrf                prefix 'carta-de-servicos'  │
│   (v6/routes/basepro.php:35)                        + middleware check.logged │
│                                                        │                     │
│                                                        ▼                     │
│                          App\Http\Controllers\Prefecture\ServiceLetter\*        │
│                          (9 controllers)                                      │
│                          + check.route.permission (no construtor)             │
│                                                        │                     │
│                     ┌──────────┴──────────┐                                  │
│                     ▼                     ▼                                  │
│          $request->validate()      Model Eloquent direto                     │
│          (sem Form Request)        App\Prefecture\ServiceLetter\*            │
│                                          │                                   │
│                                          ▼                                   │
│                             PostgreSQL (10 tabelas + pivot)                  │
│                                          │                                   │
│                                          ▼                                   │
│                            views/prefecture/service-letter/**                │
│                            (AdminLTE 1.22 + Bootstrap 3)                    │
└──────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────── PORTAL (público) ───────────────────────────────┐
│                                                                              │
│  routes/web.php:53-56  (grupo middleware check.site.manutention)             │
│                                                        │                     │
│                                                        ▼                     │
│              App\Http\Controllers\Portal\ServiceLetterController              │
│                    index() / show() / view() / pdf()                         │
│                                                        │                     │
│                                          ┌─────────────┴─────────────┐       │
│                                          ▼                           ▼       │
│                              Eloquent + whereHas()            Blade          │
│                              + translate()/ilike (Postgres)  portal/layouts/  │
│                                                           {modelo1..6}/      │
│                                                           carta-servicos/    │
└──────────────────────────────────────────────────────────────────────────────┘
```

## 2. Mapa de classes

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

| Classe | Arquivo | Papel |
|---|---|---|
| `ServiceLetterController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterController.php` | CRUD de serviços (`index`, `create`, `store`, `edit`, `update`, `destroy`, `destroyAll`), alternância de `publicado` (`setStatus`) e `destaque` (`setDestaque`) e CRUD de **responsáveis** (`storeResponsavel`, `updateResponsavel`, `destroyResponsavel`) |
| `ServiceLetterSubjectController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterSubjectController.php` | CRUD de assuntos (categoria, slug, ícone/imagem, ordem) |
| `ServiceLetterChannelController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterChannelController.php` | CRUD de canais de atendimento (JSON) |
| `ServiceLetterStepController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterStepController.php` | CRUD de etapas + reordenação (`updateOrder`) (JSON) |
| `ServiceLetterDocumentController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterDocumentController.php` | CRUD de documentos necessários (JSON) |
| `ServiceLetterAttachmentController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterAttachmentController.php` | Upload/atualização/remoção de anexos (JSON) |
| `ServiceLetterConfigController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterConfigController.php` | Leitura e atualização da configuração singleton do módulo |
| `ServiceLetterPdfController` | `v6/app/Http/Controllers/Prefecture/ServiceLetter/ServiceLetterPdfController.php` | Listagem, geração, download, streaming e exclusão de PDFs |

> **Nota:** o enunciado do_assignment menciona um `ServiceLetterResponsavelController.php` — **não existe**. O CRUD de responsáveis está dentro de `ServiceLetterController` (métodos `storeResponsavel`, `updateResponsavel`, `destroyResponsavel`), exposto pelas rotas `carta-de-servicos.responsaveis.*`.

### 2.2 Controller Portal

| Classe | Arquivo | Papel |
|---|---|---|
| `ServiceLetterController` | `v6/app/Http/Controllers/Portal/ServiceLetterController.php` | `index()` (redirect ao 1º assunto), `show($slug)` (lista filtrada + paginação + AJAX), `view($subjectSlug, $serviceSlug)` (ficha do serviço + contador de visualizações), `pdf()` (download do último PDF gerado) |

### 2.3 Models — `v6/app/Http/Model/Prefecture/ServiceLetter/`

Namespace `App\Prefecture\ServiceLetter` (mapeado por classmap em `v6/composer.json`).

| Classe | Tabela | SoftDeletes | Papel |
|---|---|---|---|
| `ServiceLetter` | `service_letters` | sim | Entidade central; relações com `subjects` (N:N), `channels`, `steps`, `documents`, `attachments`, `responsaveis`, `accessLogs`, `secretaria` |
| `ServiceLetterSubject` | `service_letter_subjects` | sim | Categoria do serviço; `tipo_visual` ∈ {`icone`, `imagem`} |
| `ServiceLetterConfig` | `service_letter_configs` | **não** | Configuração singleton (nome, textos do PDF, tipografia, capa) |
| `ServiceLetterChannel` | `service_letter_channels` | sim | Canal de atendimento |
| `ServiceLetterStep` | `service_letter_steps` | sim | Etapa do fluxo do serviço |
| `ServiceLetterDocument` | `service_letter_documents` | sim | Documento exigido |
| `ServiceLetterAttachment` | `service_letter_attachments` | sim | Anexo em arquivo |
| `ServiceLetterResponsavel` | `service_letter_responsaveis` | sim | Responsável pelo atendimento |
| `ServiceLetterPdf` | `service_letter_pdfs` | sim | Registro de PDF gerado |
| `ServiceLetterAccessLog` | `service_letter_access_logs` | **não** (`$timestamps = false`) | Log de acesso diário — **tabela criada e nunca escrita** (ver `08-GAPS-E-DIVIDAS.md`) |

### 2.4 Views e JavaScript

| Camada | Caminhos |
|---|---|
| Views CMS | `v6/resources/views/prefecture/service-letter/{config,index,pdfs,services,subjects}/` |
| Views Portal | `v6/resources/views/portal/layouts/modelo{1..6}/carta-servicos/{index,list,nav,show,view}.blade.php` |
| JS CMS | `v6/public/assets/js/custom/contents/service-letter/{datatable.js,modulos.js,responsaveis.js,subjects-datatable.js}` |
| JS utilitário | `v6/public/assets/js/custom/utilities/{table.js,deleteAll.js}` (DataTables genérico + exclusão em lote) |

## 3. Fluxo detalhado: cadastro de serviço

```
1. Operador abre /cms/carta-de-servicos/servicos/create
2. ServiceLetterController@create
   - $subjects  = ServiceLetterSubject::where('ativo', true)->orderBy('ordem')  → mapeado para stdClass{id,name}
   - $secretarias = Content::where('category_contents_id', 7)->orderBy('title')   ← categoria "Secretarias"
   - view prefecture.service-letter.services.create
3. POST /cms/carta-de-servicos/servicos  (Form::open, files: true)
4. ServiceLetterController@store
   - validate: nome required|string|max:255
               secretaria_id required|exists:contents,id
               menu_acesso_rapido required|in:cidadao,empresa,servidor
               tipo_exibicao required|in:pagina,link_externo
   - DB::transaction:
       • $data = $request->only([...30 campos...])
       • $data['slug'] = Str::slug($request->nome)
       • flags via $request->has(): acesso_online, acesso_telefone, acesso_presencial,
         gratuito, ativo, publicado, destaque
       • $data['created_by'] = Sentinel::getUser()->id
       • se publicado: $data['publicado_em'] = now()
       • upload de responsavel_foto → storage/service-letter/responsaveis/
       • ServiceLetter::create($data)
       • $serviceLetter->subjects()->sync($request->subjects)   ← pivot
       • activity()->performedOn($serviceLetter)->log('Cadastro de serviço')
   - flash + redirect carta-de-servicos.servicos.index
```

> **Armadilha verificada:** a foto do responsável em `store`/`update` usa `$file->storeAs('service-letter/responsaveis', $name)` **sem o argumento de disco**, enquanto o CRUD de responsáveis (`storeResponsavel`) usa `storeAs(..., 'public')`. Com `FILESYSTEM_DRIVER=public` (padrão em `v6/config/filesystems.php:16`) ambos terminam em `storage/app/public/`, mas o caminho gravado no banco é prefixado com `storage/` — a URL pública montada à mão (`/{{ $arquivo }}`) funciona, porém o `Storage::delete()` no destroy remove por caminho relativo ao disco `public`. Ver `05-REGRAS-E-PORTABILIDADE.md`.

## 4. Fluxo detalhado: etapas (canais, etapas, documentos, anexos, responsáveis)

O operador edita um serviço na tela **Editar Serviço**. As cinco abas renderizam partials Blade com tabela + modal. Todo o CRUD é AJAX contra rotas dedicated sob o prefixo `carta-de-servicos.{canais,etapas,documentos,anexos,responsaveis}`.

```
JS (modulos.js / responsaveis.js)
  └─ FormData + method spoofing (_method=PUT) + X-CSRF-TOKEN
      └─ POST/PUT/DELETE /cms/carta-de-servicos/{recurso}[/{id}]
          └─ Controller dedicado
              ├─ validate() (só no store)
              ├─ Model::create() / ::update() / ::delete()
              ├─ activity()->log(...)
              └─ JSON { success, message, data }
                  └─ JS: toastr + reloadTable(tipo, data, action)
                      └─ substitui <tr data-id="..."> pela linha renderizada
```

Detalhamento por recurso em `09-WORKFLOW-ETAPAS-E-CANAIS.md`.

## 5. Fluxo detalhado: leitura no portal

```
GET /carta-de-servicos
  → config = ServiceLetterConfig::first()
  → subjects = ServiceLetterSubject::where('ativo', true)->orderBy('nome')->get()
  → se houver subjects: redirect route('carta-servicos.show', $subjects->first()->slug)
  → senão: view .../carta-servicos.index

GET /carta-de-servicos/{slugAssunto}
  → $services = ServiceLetter::where('ativo',true)->where('publicado',true)
        ->whereHas('subjects', fn => where('service_letter_subjects.id', $subject->id))
        ->with(['subjects','secretaria'])
  → filtro texto:  whereRaw("translate(lower(nome), 'àáâãäéèëêíìïîóòõöôúùüûçÇ',
                                        'aaaaaeeeeiiiiooooouuuucc') ilike ?", ["%{$texto}%"])
       (idem para descricao_curta, em OR)
  → filtro secretaria: where('secretaria_id', ...)
  → orderBy('nome')->paginate(12)
  → se $request->ajax(): Response::json(view(...)->render())   ← fragmento HTML, não JSON de dados
  → senão: view .../carta-servicos.show

GET /carta-de-servicos/{slugAssunto}/{slugServico}
  → ServiceLetter::where('slug',$slug)->where('ativo',true)->where('publicado',true)
        ->with(['subjects','secretaria','channels','steps','documents','responsaveis'])->first()
  → $service->increment('visualizacoes')
  → view .../carta-servicos.view
```

## 6. Dependências externas

| Pacote (`v6/composer.json`) | Uso no módulo |
|---|---|
| `laravel/framework` 5.5.* | Base (Eloquent, Validation, Blade, Filesystem) |
| `yajra/laravel-datatables` ^1.5 | Listagens DataTable do CMS (`v6/app/Http/Controllers/Prefecture/DatatableController.php:492-502`) |
| `spatie/laravel-activitylog` ^2.8 | Trilha de auditoria (`activity()->performedOn(...)->log(...)`) em todos os controllers CMS |
| `laralabs/toaster` ^2.0 | Notificações `flash(...)->success()` / `->error()` |
| `laravelcollective/html` ^5.5 | `Form::open` / `Form::close` nos blades |
| `laravellegends/pt-br-validator` ^5.1 | Mensagens de validação em português |
| `diglactic/laravel-breadcrumbs` ^4.0 | Breadcrumbs `carta-servicos`, `carta-servicos-show`, `carta-servicos-view` (`v6/routes/breadcrumbs.php:463-483`) |
| `cartalyst/sentinel` 2.0.* | Autenticação (`Sentinel::getUser()`), permissões (`check.route.permission`) e roles (`super-admin`) |
| `dompdf/dompdf` + `barryvdh/laravel-dompdf` | Usados pelo `PDF::loadView(...)` do portal; **não** pelo gerador da Carta de Serviços (que está ausente) |
| `tecnickcom/tcpdf` ^6.3, `setasign/fpdi` ^2.3 | Usados pelo `FPdfService` (`v6/app/Services/FPdfService.php`) do Diário Oficial — **não** são usados por este módulo |

## 7. Dependências internas (acoplamentos)

| Depende de | Onde | Tipo |
|---|---|---|
| `contents` (secretarias) | `SecretariaController@create/edit`, `ServiceLetter.secretaria` | FK `service_letters.secretaria_id → contents.id` |
| `category_contents` | filtro `category_contents_id = 7` | ID **hardcoded** (categoria "Secretarias") |
| `modulos` / `menu_orders` / `resources` | migration `2026_01_20_160719` | Registro do módulo e do item de menu |
| `users` | `created_by` / `updated_by` / `gerado_por` | FK |
| `statuses` | — | não usado por este módulo |
| `Configuration` (`layout`) | `getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39`) | Escolhe o template do portal |

## 8. Diagrama de dependência entre models

```
                       ServiceLetterSubject
                              ▲
                              │ belongsToMany (pivot service_letters_subjects_rel)
                              │
        ┌─────────────────────┴──────────────────────┐
        │                                            │
   ServiceLetter  ──belongsTo──►  Content (secretaria_id)
        │
        ├──hasMany──► ServiceLetterChannel   (orderBy ordem)
        ├──hasMany──► ServiceLetterStep      (orderBy ordem)
        ├──hasMany──► ServiceLetterDocument  (orderBy ordem)
        ├──hasMany──► ServiceLetterAttachment(orderBy ordem)
        ├──hasMany──► ServiceLetterResponsavel (orderBy ordem)
        ├──hasMany──► ServiceLetterAccessLog  (nunca usada)
        └──hasMany──► ServiceLetterPdf (via ServiceLetterConfig.config_id)
```
