# Carta de Serviços — Workflow de Etapas e Canais (ponta a ponta)

> Este documento detalha o ciclo completo do sub-recursos "Etapas" e "Canais de Atendimento" dentro do serviço, que é o núcleo operacional do módulo.

## 1. Visão geral do workflow

```
┌─── CMS ─────────────────────────────────────────────────────────────────────┐
│                                                                            │
│  (1) Operador cria o serviço                                              │
│      POST /cms/carta-de-servicos/servicos                                  │
│      → ServiceLetter::create()  → service_letters                          │
│                                                                            │
│  (2) Operador abre a aba "Etapas" na tela de edição                       │
│      GET /cms/carta-de-servicos/servicos/{id}/edit                         │
│      → ServiceLetterController@edit                                        │
│        with(['subjects','channels','steps','documents',                    │
│              'attachments','responsaveis'])                                │
│      → partials/steps.blade.php renderiza a tabela inicial                │
│                                                                            │
│  (3) Operador clica "Adicionar Etapa"                                      │
│      → JS resetFormEtapa() + abre #modal_etapa                            │
│      → preenche numero, titulo, descricao, prazo, ordem, ativo             │
│                                                                            │
│  (4) Submit do modal                                                       │
│      POST /cms/carta-de-servicos/etapas   (ou PUT .../etapas/{id})          │
│      → ServiceLetterStepController@store/@update                           │
│        validate: service_letter_id|exists                                   │
│                  titulo|required|string|max:255                             │
│                  numero|required|integer                                    │
│        → ServiceLetterStep::create()/update()                               │
│        → activity log 'Cadastro de etapa' / 'Atualização de etapa'         │
│        → JSON { success, message, data }                                    │
│                                                                            │
│  (5) JS reloadTable('etapa', data, action)                                 │
│      → renderEtapaRow(data) → <tr data-id="..."> substituído/acrescentado  │
│                                                                            │
│  (6) (idem para Canais, Documentos, Anexos, Responsáveis)                  │
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘

┌─── PORTAL ─────────────────────────────────────────────────────────────────┐
│                                                                            │
│  (7) Cidadão abre /carta-de-servicos/{assunto}/{servico}                   │
│      → Portal\ServiceLetterController@view                                 │
│        with(['subjects','secretaria','channels','steps',                  │
│              'documents','responsaveis'])                                  │
│      → $service->increment('visualizacoes')                                │
│      → view.blade.php: card "Etapas" com $service->steps                   │
│          .where('ativo', true)->sortBy('numero')                           │
│        → card "Canais de Atendimento" com $service->channels               │
│          .where('ativo', true)                                             │
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘
```

## 2. Etapas — modelo de dados

| Campo | Origem | Uso no CMS | Uso no portal |
|---|---|---|---|
| `id` | serial | chave da linha (`data-id`) | — |
| `service_letter_id` | FK | invisível (injetado pelo Blade) | — |
| `numero` | digitado pelo operador (JS sugere `length + 1`) | **coluna exibida** | **ordenação e numeração** (`sortBy('numero')`) |
| `titulo` | digitado | coluna exibida | exibido em negrito: `"{numero}. {titulo}"` |
| `descricao` | digitado | truncado em 80 chars (`Str::limit`) | exibido abaixo do título |
| `prazo` | texto livre (ex.: "5 dias úteis") | coluna exibida | badge "Prazo: {prazo}" |
| `ordem` | digitado | ordenação da relação Eloquent | — (o portal usa `numero`) |
| `ativo` | checkbox | badge Ativo/Inativo | **filtra** — etapas inativas não aparecem |
| `created_at`/`updated_at`/`deleted_at` | automático | — | — |

### 2.1 Contrato HTTP do recurso de etapas

**Criar** — `POST /cms/carta-de-servicos/etapas`

```json
{
  "service_letter_id": 12,
  "numero": 1,
  "titulo": "Preencher requerimento",
  "descricao": "Formulário disponível no portal",
  "prazo": "1 dia útil",
  "ordem": 0,
  "ativo": "true"
}
```

Resposta 200:

```json
{
  "success": true,
  "message": "Etapa cadastrada com sucesso.",
  "data": { "id": 87, "numero": 1, "titulo": "Preencher requerimento", "...": "..." }
}
```

Erro de validação (Laravel `ValidationException`): HTTP 422 com `{"message": "...", "errors": {"titulo": ["..."]}}`. O JS `showErrors()` trata `xhr.responseJSON.errors` (formato **objeto**, não array) e o `submitForm` só ramifica para `xhr.status === 422` — **demais erros** caem no `toastr.error` genérico.

**Atualizar** — `PUT /cms/carta-de-servicos/etapas/{id}` — **sem validação** no controller. O corpo aceita `numero`, `titulo`, `descricao`, `prazo`, `ordem`, `ativo`.

**Excluir** — `DELETE /cms/carta-de-servicos/etapas/{id}` — soft delete. Resposta `{"success": true, "message": "Etapa excluída com sucesso."}`.

**Reordenar** — `POST /cms/carta-de-servicos/etapas/order`

```json
{ "sortable_data": [ { "id": 87, "order": 1 }, { "id": 88, "order": 2 } ] }
```

```php
foreach ($request->sortable_data as $item) {
    $this->step->where('id', $item['id'])->update(['ordem' => $item['order']]);
}
```

> **Rota órfã verificada:** nenhum JavaScript do módulo chama `/etapas/order`. O `modulos.js` **não** implementa sortable/drag-and-drop. A rota funciona, mas não há UI. Se for usada, atualiza `ordem` — que **não** é a ordem exibida no portal (que usa `numero`).

### 2.2 Fluxo do modal de etapa no front-end

```js
// v6/public/assets/js/custom/contents/service-letter/modulos.js
$('#btn_add_etapa').on('click', function() {
    resetFormEtapa();
    $('#modal_etapa_title').text('Adicionar Etapa');
    $('#etapa_numero').val($('#lista_etapas tr[data-id]').length + 1);   // sugere o próximo número
    $('#modal_etapa').modal('show');
});

$('#form_etapa').on('submit', function(e) {
    e.preventDefault();
    var formData = new FormData(this);
    var id = $('#etapa_id').val();
    var url = id ? '/cms/carta-de-servicos/etapas/' + id : '/cms/carta-de-servicos/etapas';
    if (id) formData.append('_method', 'PUT');       // method spoofing
    formData.set('ativo', $('#etapa_ativo').is(':checked') ? 'true' : 'false');
    submitForm(url, formData, 'etapa', id ? 'update' : 'add');
});
```

> **Bug de semântica verificado:** `formData.set('ativo', 'false')` envia a **string** `"false"`, que é uma string não-vazia → `$request->has('ativo')` retorna **true**. O controller faz `$data['ativo'] = $request->has('ativo')` — portanto a etapa é **sempre** criada/Atualizada como **ativa**, mesmo que o operador desmarque "Ativo". O mesmo bug vale para canais, documentos, anexos e (parcialmente) responsáveis. Consequência: **não é possível desativar uma etapa pela interface**.

### 2.3 Renderização da etapa no portal

`v6/resources/views/portal/layouts/modelo{1..6}/carta-servicos/view.blade.php`:

```blade
@if ($service->steps && $service->steps->where('ativo', true)->count() > 0)
    <div class="card mb-3">
        <div class="card-header ">
            <i class="fas fa-list-ol"></i> Etapas
        </div>
        <ul class="list-group list-group-flush">
            @foreach ($service->steps->where('ativo', true)->sortBy('numero') as $step)
                <li class="list-group-item">
                    <strong>{{ $step->numero }}. {{ $step->titulo }}</strong>
                    @if ($step->prazo)
                        <span class="badge bg-warning text-dark ms-2">Prazo: {{ $step->prazo }}</span>
                    @endif
                    @if ($step->descricao)
                        <p class="mb-0 mt-1 text-muted">{{ $step->descricao }}</p>
                    @endif
                </li>
            @endforeach
        </ul>
    </div>
@endif
```

Acima desse bloco, o portal exibe **também** o campo HTML `service_letters.etapas`:

```blade
@if ($service->etapas)
    <div class="card mb-3">
        <div class="card-header "><i class="fas fa-tasks"></i> Etapas do Serviço</div>
        <div class="card-body">{!! $service->etapas !!}</div>
    </div>
@endif
```

> **Duplicidade verificada:** duas seções de etapas na mesma ficha. Se o operador preencher apenas o campo HTML, a seção estruturada fica oculta (count = 0) — mas o bloco HTML aparece. Se preencher apenas as etapas estruturadas, o bloco HTML não aparece. Se preencher **ambos**, o cidadão vê as etapas duas vezes, em formatos diferentes.

## 3. Canais — modelo de dados

| Campo | Origem | Uso no CMS | Uso no portal |
|---|---|---|---|
| `id` | serial | `data-id` | — |
| `service_letter_id` | FK | invisível | — |
| `tipo` | enum `online`/`telefone`/`presencial` (validado **só no create**) | badge colorido (azul/amarelo/verde) | `ucfirst($canal->tipo)` em negrito |
| `nome` | digitado | coluna | `"{tipo} - {nome}"` |
| `descricao` | digitado | **não exibida** | **não exibida** |
| `url` | digitado (visível só se `tipo=online`) | link na coluna "Contato" | link com ícone |
| `telefone` | digitado (visível só se `tipo=telefone`) | coluna "Contato" | linha com ícone de telefone |
| `endereco` | digitado (visível só se `tipo=presencial`) | truncado em 50 chars | **não exibido** (só o `nome`/tipo) |
| `horario_funcionamento` | digitado | coluna "Horário" | exibido em cinza |
| `ordem` | digitado | ordenação | — |
| `ativo` | checkbox | badge | **filtra** |

### 3.1 Contrato HTTP

**Criar** — `POST /cms/carta-de-servicos/canais`

```json
{
  "service_letter_id": 12,
  "tipo": "telefone",
  "nome": "Protocolo Geral",
  "descricao": "Atendimento de segunda a sexta",
  "url": null,
  "telefone": "(00) 3333-4444",
  "endereco": null,
  "horario_funcionamento": "Seg a Sex, 8h às 17h",
  "ordem": 0,
  "ativo": "true"
}
```

Validação: `service_letter_id` required|exists:`service_letters,id`; `tipo` required|in:`online,telefone,presencial`.

**Atualizar** — `PUT /cms/carta-de-servicos/canais/{id}` — **sem validação**. Persiste todos os campos de contato simultaneamente.

**Excluir** — `DELETE /cms/carta-de-servicos/canais/{id}`.

### 3.2 Comportamento por tipo

```js
$('#canal_tipo').change(function() {
    var tipo = $(this).val();
    $('#canal_url_group, #canal_telefone_group, #canal_endereco_group').hide();
    if (tipo === 'online') $('#canal_url_group').show();
    if (tipo === 'telefone') $('#canal_telefone_group').show();
    if (tipo === 'presencial') $('#canal_endereco_group').show();
});
```

> **Risco de dado verificado:** o `update` grava `url`, `telefone` e `endereco` **juntos**. Se o operador criar um canal `online` com URL, depois trocar o tipo para `telefone` e informar o telefone, o canal passa a ter **URL e telefone**. O portal mostra ambos (o `url` só é renderizado quando não é nulo, independentemente do `tipo`). A regra de "exibir apenas o contato do `tipo`" não é garantida pelo backend — apenas pela UI.

### 3.3 Renderização no portal

```blade
@if ($service->channels && $service->channels->where('ativo', true)->count() > 0)
    <div class="card mb-3">
        <div class="card-header "><i class="fas fa-headset"></i> Canais de Atendimento</div>
        <ul class="list-group list-group-flush">
            @foreach ($service->channels->where('ativo', true) as $canal)
                <li class="list-group-item">
                    <strong>{{ ucfirst($canal->tipo) }}</strong>
                    @if ($canal->nome) - {{ $canal->nome }} @endif
                    @if ($canal->telefone) <br><i class="fas fa-phone"></i> {{ $canal->telefone }} @endif
                    @if ($canal->url) <br><i class="fas fa-link"></i> <a href="{{ $canal->url }}" target="_blank">{{ $canal->url }}</a> @endif
                    @if ($canal->horario_funcionamento) <br><small class="text-muted"><i class="fas fa-clock"></i> {{ $canal->horario_funcionamento }}</small> @endif
                </li>
            @endforeach
        </ul>
    </div>
@endif
```

> O `endereco` do canal **presencial** não é exibido no portal — apenas o nome e o tipo. O endereço de atendimento do serviço (campo `service_letters.endereco_atendimento`) é que aparece, em outra seção.

## 4. Ciclo completo — do cadastro à publicação

| Etapa | Quem faz | Artefato | Efeito observável |
|---|---|---|---|
| 1 | Operador | `ServiceLetter::create()` | Serviço existe, `publicado=false` → **invisível** no portal |
| 2 | Operador | Etapas (nº, título, prazo) | Rascunho do fluxo interno |
| 3 | Operador | Canais (online/telefone/presencial) | Formas de contato |
| 4 | Operador | Documentos (nome, obrigatório) | Documentação exigida |
| 5 | Operador | Anexos (arquivo ≤ 10 MB) | Formulários e modelos para download — **não exibidos no portal** |
| 6 | Operador | Responsáveis (nome, cargo, e-mail/telefone) | Quem responde |
| 7 | Operador | Configuração (nome do módulo, textos do PDF, tipografia) | Identidade do documento |
| 8 | Operador | `publicado = true` | **O serviço passa a aparecer no portal** |
| 9 | Operador | "Gerar PDF" | PDF consolidado — **hoje falha** (classe ausente) |
| 10 | Cidadão | Visita `/carta-de-servicos/{assunto}/{servico}` | Ficha exibida; `visualizacoes` +1 |
| 11 | Cidadão | `/carta-de-servicos/pdf` | Download da carta completa (se houver PDF) |

### 4.1 Regra de ouro do fluxo

**Etapas e canais só são editáveis quando `tipo_exibicao = 'pagina'`.** O bloco de abas em `services/edit.blade.php` está inteiro dentro de:

```blade
@if ($serviceLetter->tipo_exibicao == 'pagina')
    <div class="nav-tabs-custom"> … 5 abas … </div>
@endif
```

Para serviços do tipo `link_externo`, **não há** como cadastrar etapas, canais, documentos, anexos ou responsáveis pela interface — o conteúdo é apenas um link.

## 5. Onde o workflow quebra

| Ponto | Problema | Evidência |
|---|---|---|
| Desativar uma etapa | Impossível pela UI (`has('ativo')` com string `"false"`) | `modulos.js` + `ServiceLetterStepController@store` |
| Ver o canal recém-criado | A linha não aparece (id `lista_canals` ≠ `lista_canais`) | `partials/channels.blade.php:20` vs `modulos.js` |
| Reordenar etapas | Rota existe, UI não | `etapas/order` sem caller |
| PDF consolidado | Classe inexistente | `ServiceLetterPdfController:14,62` |
| Anexos no portal | Nenhuma view os renderiza | busca por `attachments` nas views do portal: apenas o model |
| Descrição de canal/documento | Gravada, nunca exibida | — |
| Endereço do canal presencial | Gravado, nunca exibido no portal | — |
| `numero` × `ordem` | Duas ordens concorrentes | `sortBy('numero')` no portal vs `orderBy('ordem')` na relação |
