---
id: MOD-PUBLICACOES-TRABALHECONOSCO-ROTAS
tipo: modulo
onda: 4
status: concluido
unidades: [MOD-PUBLICACOES-TRABALHECONOSCO]
fontes:
  - V2/Modules/Publicacoes/TrabalheConosco/
depende_de: [CORE-BOOT, CORE-AUTH, CORE-CMS, CORE-RUNTIME]
consumidores: [UI-PORTAL, UI-CMS, SVC-SEO, SVC-MAIL]
criticidade: media
dono: Trabalhe
---

# Publicações/TrabalheConosco — rotas

> Anexo do `spec.md`. Treze endpoints (3 públicos, 10 no CMS) em dois arquivos de rota.
> O provider adiciona `web` + `module.active:TrabalheConosco` ao CMS e
> `web`/manutenção/caches ao portal; os arquivos adicionam `CmsAuthenticate` (CMS) e
> `ModuleAccessMiddleware` (portal). Não há API, throttle, cache de página efetivo nem
> FormRequest. Com a flag inativa, **todos** respondem 404.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Expor CRUD de vagas/candidaturas no CMS e leitura + candidatura no portal, com bloqueio integral por feature flag. |
| Entrada | `GET /trabalhe-conosco`, `GET /trabalhe-conosco/{slug}`, `POST /trabalhe-conosco/{slug}/apply`; `GET/POST/PUT/DELETE /cms/institucional/trabalhe-conosco/*`; query `search`, `status`, `job_opening_id`. |
| Saída | HTML (7 views), `302` com flash, `404` (vaga/flag), `500` (falhas não tratadas), JSON 404 em AJAX com flag inativa. |
| Efeitos colaterais | Escrita em duas tabelas + arquivo + e-mail (portal); auditoria e hooks de busca/feed (CMS). |
| Particularidade central | O grupo CMS não inclui `module.active` no arquivo: ele vem do provider (`V2ServiceProvider.php:303-307`), antes do `CmsAuthenticate`, então com a flag desligada a resposta é 404 mesmo sem login. |
| Como se integra | Nomes `v2.trabalhe-conosco.portal.*` e `v2.trabalhe-conosco.cms.*`; controllers resolvidos pelo namespace do provider; views `v2-trabalheconosco::*`. |
| Se removido, quebra | Links do portal/rodapé, botão do e-mail de notificação, `active_pattern`/`route` do `ModuleProvider`, permissão RBAC do módulo. |
| Superfície pública | As 13 rotas nomeadas; nenhuma rota de API. |

## 1. Efeito

### 1.1 O que o usuário consegue fazer

- **Visitante**: listar vagas abertas, abrir detalhe e candidatar-se (§5.1).
- **Operador CMS com permissão**: listar/criar/editar/arquivar vagas, listar candidaturas,
  alterar status/notas e remover candidatura (§5.2).
- **Qualquer visitante com a flag inativa**: recebe 404 em qualquer uma das 13 rotas.

### 1.2 O que muda no sistema

| Efeito | Rota | Fonte |
|---|---|---|
| Cria vaga + slug + auditoria + busca/feed | `POST /openings/novo` | `JobOpeningController.php:72-127` |
| Atualiza vaga + auditoria + busca/feed | `PUT/POST /openings/editar/{id}` | `JobOpeningController.php:149-200` |
| Soft delete + auditoria + remoção de busca/feed | `DELETE /openings/{id}` | `JobOpeningController.php:208-222` |
| Cria candidatura + arquivo + e-mail | `POST /trabalhe-conosco/{slug}/apply` | `TrabalheConoscoController.php:80-109` |
| Atualiza status/notas/revisor + auditoria | `PUT/POST /applications/{id}/status` | `JobApplicationController.php:74-104` |
| Apaga arquivo + soft delete + auditoria | `DELETE /applications/{id}` | `JobApplicationController.php:112-136` |

### 1.3 Contrato observável (entrada → saída)

| Entrada | Saída |
|---|---|
| `GET /trabalhe-conosco` | `200` + `v2-trabalheconosco::portal.index` com `$openings`. |
| `GET /trabalhe-conosco/{slug}` válido | `200` + `portal.show` com `$opening` e `$htmlDescription`. |
| `GET /trabalhe-conosco/{slug}` inválido | `404` (ModelNotFound → handler). |
| `POST .../apply` válido | `200` + `portal.success` (sem redirect). |
| `POST .../apply` inválido | `302` back + `$errors` (chaves `validation.*`). |
| `GET /cms/institucional/trabalhe-conosco/openings` | `200` + listagem paginada. |
| `POST /openings/novo` válido | `302` para `v2.trabalhe-conosco.cms.openings` + `created_item`. |
| `POST /openings/novo` inválido | `302` back com erros (não exibidos pelo layout). |
| `PUT /openings/editar/{id}` inexistente | `404` (`findOrFail`). |
| `DELETE /openings/{id}` | `302` + flash `Vaga "{título}" removida com sucesso!`. |
| `PUT /applications/{id}/status` válido | `302` back + flash `Status da candidatura atualizado com sucesso!`. |
| `PUT /applications/{id}/status` inválido | `302` back + erro `validation.in`. |
| `DELETE /applications/{id}` | `302` para a listagem + flash `Candidatura de "{nome}" removida com sucesso!`. |
| Qualquer rota, flag inativa | `404` com mensagem `Modulo desativado.`; AJAX `404 {"error":"Modulo desativado: TrabalheConosco","code":404}`. |
| CMS sem sessão (flag ativa) | `302` para `v2.cms.login`. |
| CMS sem papel | `302` login + `Acesso negado. Seu usuário não possui permissões administrativas.` |
| CMS sem permissão de módulo | `302` dashboard + `Acesso negado. Seu perfil não possui permissão para acessar o módulo: Trabalhe Conosco`. |

### 1.4 O que este anexo NÃO faz

- Não documenta as telas (ver `cms.md`/`portal.md`).
- Não há endpoints JSON, upload avulso, autocomplete ou exportação.
- Não há `OPTIONS`/CORS explícito; não há versionamento.

## 2. Abordagem

### 2.1 Estratégia em uma página

Duas pilhas de middleware declaradas em lugares diferentes. O provider envolve todo
`Routes/cms.php` com `web` + `module.active:<dir>` e todo `Routes/web.php` com
`web` + manutenção + caches. Os arquivos então declaram o que é específico: RBAC no CMS,
flag duplicada + cache-bypass no portal. A ordem efetiva é a concatenação dos grupos.

```mermaid
flowchart TD
  R1[GET/POST /trabalhe-conosco*] --> W1[web]
  W1 --> M1[MaintenanceMiddleware]
  M1 --> PC[page.cache<br/>exceto trabalhe-conosco*]
  PC --> PU[public.cache<br/>exceto /trabalhe-conosco]
  PU --> W2[web]
  W2 --> MA[module.active:TrabalheConosco]
  MA -->|inativa| N[404]
  MA -->|ativa| C1[TrabalheConoscoController]

  R2[cms/institucional/trabalhe-conosco/*] --> W3[web]
  W3 --> MA2[module.active:TrabalheConosco]
  MA2 -->|inativa| N
  MA2 --> CA[CmsAuthenticate]
  CA --> PM[V2PermissionMiddleware]
  PM --> C2[JobOpening/JobApplicationController]
```

### 2.2 Mecânica passo a passo

1. `V2ServiceProvider::mapModuleRoutes` registra `web.php` com
   `['web', MaintenanceMiddleware, 'page.cache', 'public.cache']` (`:297-301`) e `cms.php`
   com `['web', 'module.active:TrabalheConosco']` (`:303-307`), ambos com namespace dos
   controllers.
2. `Routes/web.php` abre grupo com `['web', ModuleAccessMiddleware.':TrabalheConosco']`
   e declara as três rotas públicas (`:3-21`).
3. `Routes/cms.php` abre grupo com `CmsAuthenticate` e prefixo
   `cms/institucional/trabalhe-conosco` (`:3-6`), declarando 10 endpoints.
4. `ModuleAccessMiddleware` resolve a flag por nome (`TrabalheConosco` → lowercase) e
   aborta 404 ou devolve JSON (`:40-63`).
5. `CmsAuthenticate` valida Sentinel, exige ≥1 papel e delega ao
   `V2PermissionMiddleware` (`:15-53`), que casa `active_pattern` e aplica
   `none/read/write` (`:31-110`).
6. O controller correspondente valida (`$this->validate`) e executa; erros de validação
   retornam `302` back com `$errors`.
7. Respostas: views, redirects com flash (`laracasts/flash`) e `created_item` na sessão.

### 2.3 Decisões de design e por quê

- **Proteção em dois níveis** `[C]`: flag (disponibilidade) e RBAC (autorização) são
  independentes; a flag é checada antes do login no CMS, o que evita revelar a existência
  do módulo desativado.
- **Prefixo “institucional”** `[C]`: a URI do CMS não corresponde a `/trabalhe-conosco`;
  é `cms/institucional/trabalhe-conosco`, e o `active_pattern` replica exatamente isso.
- **`PUT` e `POST` na mesma rota** `[C]`: `Route::match(['PUT','POST'])` aceita forms com
  `_method=PUT` e POST direto (`cms.php:29,50`).
- **Sem `Route::resource`** `[C]`: rotas manuais com nomes explícitos, o que permite
  `openings/novo` e `openings/editar/{id}` em português.
- **Portal sem throttle/limite** `[C]`: não há `throttle` em nenhuma rota; a única
  barreira ao POST anônimo é CSRF.
- **Cache desligado por exclusão** `[C]`: `page.cache` e `public.cache` excluem o
  prefixo, então a pilha de cache não tem efeito (`PageCacheMiddleware.php:12-19`;
  `PublicCacheMiddleware.php:27-32`).

### 2.4 Trechos de código pivô

```php
// V2/Core/Providers/V2ServiceProvider.php:297-307 — as duas pilhas, declaradas longe
// dos arquivos de rota.
if (File::exists($webRoutes)) {
    Route::middleware(['web', \V2\Core\Middleware\MaintenanceMiddleware::class, 'page.cache', 'public.cache'])
        ->namespace($controllerNamespace)
        ->group($webRoutes);
}

if (File::exists($cmsRoutes)) {
    Route::middleware(['web', 'module.active:' . $moduleName])
        ->namespace($controllerNamespace)
        ->group($cmsRoutes);
}
```

O `namespace` é `V2\Modules\Publicacoes\TrabalheConosco\Controllers`; por isso as rotas
usam `'TrabalheConoscoController@index'` sem FQCN. A flag recebe o **nome do diretório**
(`TrabalheConosco`), não o slug do módulo.

```php
// V2/Modules/Publicacoes/TrabalheConosco/Routes/cms.php:3-6 — o grupo do CMS.
Route::group([
    'middleware' => [\V2\Core\Middleware\CmsAuthenticate::class],
    'prefix'     => 'cms/institucional/trabalhe-conosco',
], function () {
```

O prefixo completo é o que aparece em `getPrefix()`; é a razão pela qual o ramo de
redirect do `ModuleAccessMiddleware` (`=== 'cms'`) não é acionado.

```php
// V2/Modules/Publicacoes/TrabalheConosco/Routes/web.php:3-20 — portal e flag.
Route::group([
    'middleware' => ['web', \V2\Core\Middleware\ModuleAccessMiddleware::class . ':TrabalheConosco'],
], function () {

    Route::get('/trabalhe-conosco', [
        'as'   => 'v2.trabalhe-conosco.portal.index',
        'uses' => 'TrabalheConoscoController@index',
    ]);

    Route::get('/trabalhe-conosco/{slug}', [
        'as'   => 'v2.trabalhe-conosco.portal.show',
        'uses' => 'TrabalheConoscoController@show',
    ]);

    Route::post('/trabalhe-conosco/{slug}/apply', [
        'as'   => 'v2.trabalhe-conosco.portal.apply',
        'uses' => 'TrabalheConoscoController@apply',
    ]);
});
```

`{slug}` não tem `where()`: aceita pontos e hífens; um slug com `/` não casa (segmento
único). A rota de detalhe é registrada antes do POST, mas métodos diferentes não colidem.

```php
// V2/Core/Middleware/V2PermissionMiddleware.php:40-43,74-77 — o casamento do módulo.
foreach ($moduleConfigs as $moduleKey => $config) {
    $pattern = $config['active_pattern'] ?? null;
    if ($pattern && $request->is($pattern)) {
        $currentModuleKey = $moduleKey;
        $currentModuleConfig = $config;
        $matched = true;
        break;
    }
}
// ...
if (!$matched) {
    return $next($request);
}
```

O `active_pattern` é **array** com um padrão (`ModuleProvider.php:16`) e `Request::is()`
aceita array (qualquer um casa). A URI testada é sem barra inicial, então
`cms/institucional/trabalhe-conosco*` cobre todos os 10 endpoints. Sem match, a rota passa
sem RBAC — o que não ocorre aqui.

```php
// V2/Core/Middleware/V2PermissionMiddleware.php:74-110 — semântica de read/write.
$userPermission = $permissions[$currentModuleKey] ?? 'none';

if ($userPermission === 'none') {
    // redirect ao dashboard com "Acesso negado. Seu perfil não possui permissão..."
}

if ($userPermission === 'read') {
    if ($request->isMethod('GET') || $request->isMethod('HEAD')) {
        return $next($request);
    }
    // 403 (AJAX) ou redirect back com "Operação negada..."
}
```

A permissão é a chave `TrabalheConosco` do JSON `permissions_v2` do `V2Role`; não há
middleware `can:` nem slug por ação. `super-admin` e o e-mail de suporte global passam
antes (`:62-65`).

### 2.5 Algoritmos, consultas e custos

- `GET /openings`: 2 queries (count + página) + 20 queries de contagem por linha (view)
  + 3 por termo de busca em `ILIKE` (custo de full scan).
- `GET /applications`: 2 queries (página + eager load) + 1 `SELECT * FROM v2_job_openings`
  para o select de filtro (`applications/index.blade.php:8`).
- `POST /apply`: 1 `SELECT` da vaga + 1 escrita de arquivo + 1 `INSERT` + 1 render/envio
  de e-mail.
- CMS writes: 3–5 queries cada, sem transação explícita.
- Nenhuma rota tem cache HTTP efetivo; `public.cache` sai antes de definir cabeçalhos
  para o prefixo.

## 3. Particularidades

### 3.1 O que só existe aqui

- **Prefixo de CMS fora do padrão do portal**: `cms/institucional/trabalhe-conosco` em
  vez de `cms/publicacoes/...`, coerente com o menu “Institucional” do portal.
- **Flag duplicada em duas camadas** no portal (`module.active` no provider e no
  arquivo) e **ausente no arquivo do CMS** (vem só do provider).
- **`openings/novo` e `openings/editar/{id}`** em português, sem `Route::resource`.
- **Rota pública de escrita** (`POST .../apply`) sem throttle.

### 3.2 Invariantes e regras não óbvias

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| `RN-TRABALHECONOSCO-R01` | Flag inativa ⇒ 404 em qualquer rota, inclusive CMS. | Sem login redirect, sem mensagem de módulo desativado. | `ModuleAccessMiddleware.php:46-62` |
| `RN-TRABALHECONOSCO-R02` | Flag ativa + sem sessão ⇒ 302 login (CMS). | `CmsAuthenticate` roda depois da flag. | `V2ServiceProvider.php:303-307`; `CmsAuthenticate.php:21-28` |
| `RN-TRABALHECONOSCO-R03` | `read` bloqueia métodos não-GET/HEAD com redirect back. | Operador só-leitura não altera status nem arquiva. | `V2PermissionMiddleware.php:92-110` |
| `RN-TRABALHECONOSCO-R04` | Nenhuma rota valida UUID de `{id}`; `findOrFail` gera 404 genérico. | UUID malformado → 404 (PostgreSQL pode gerar erro 500 em `uuid`). | `JobOpeningController.php:152`; `JobApplicationController.php:62,77,115` |
| `RN-TRABALHECONOSCO-R05` | `POST .../apply` responde `200`, não `302`. | Refresh reenvia a candidatura. | `TrabalheConoscoController.php:109` |
| `RN-TRABALHECONOSCO-R06` | Rotas de atualização aceitam PUT e POST. | Forms usam `_method=PUT`; POST direto também funciona. | `Routes/cms.php:29,50` |
| `RN-TRABALHECONOSCO-R07` | Portal sem paginação; CMS com 20 fixo. | Lista pública cresce sem limite. | `TrabalheConoscoController.php:39`; `JobOpeningController.php:51` |
| `RN-TRABALHECONOSCO-R08` | `search` do CMS usa `ILIKE` (PostgreSQL). | Em MySQL o operador não existe → erro 500. | `JobOpeningController.php:43-45` |

### 3.3 Casos-limite e comportamentos surpreendentes

- **Rota pública e slug de página fixa**: `/trabalhe-conosco` é rota de módulo; o
  catch-all `/{slug}` só é registrado depois (`fallback.php`), então não há sombreamento
  (`BasicPage/Routes/web.php:17-21`).
- **Slug reservado**: criar vaga com slug `trabalhe-conosco` gera URI
  `/trabalhe-conosco/trabalhe-conosco`, que funciona; não conflita com o índice.
- **Vaga fechada**: `GET` e `POST` usam `open()`, então a rota responde 404 mesmo que o
  slug exista.
- **CSRF**: o POST do portal depende do middleware `web`; um cliente sem token recebe
  `419`. O formulário inclui `csrf_field()` (`portal/show.blade.php:89`).
- **Método errado**: `DELETE` via GET não existe; `POST /openings/editar/{id}` é aceito
  (match PUT/POST) e atualiza.
- **CMS com flag inativa e AJAX**: o `module.active` responde JSON 404, mas as telas são
  server-side; o caso AJAX só apareceria em chamadas manuais.
- **Rota `applications/{id}` vs `applications`**: `/applications/` (barra final) tende a
  redirecionar/normalizar; `/applications?status=x` é a listagem filtrada.

### 3.4 Armadilhas de reimplementação

- **Mover `module.active` para o arquivo do CMS** muda a ordem relativa ao
  `CmsAuthenticate`; mantenha a flag antes do login para preservar o 404 sem sessão.
- **Adicionar `{id}` com `whereUuid`** mudaria o contrato: hoje um id inválido pode
  produzir 500 do PostgreSQL em vez de 404.
- **Trocar `open()` por `findOrFail($slug)` no POST** permitiria candidatura a vaga
  fechada — o módulo não faz isso.
- **Usar `Route::resource`** renomearia as rotas e quebraria `ModuleProvider::route`,
  views e o e-mail de notificação (`route('...applications.show')`).
- **Contar com redirect após candidatura**: o sucesso é uma view `200`; redirecionar
  muda o comportamento de refresh e os testes.

### 3.5 Divergências documentação × código e dívidas

- Comentário da migration de flag afirma que as rotas CMS são bloqueadas “pelo mesmo
  middleware” e que o redirect ao dashboard ocorre; o código só redireciona se
  `getPrefix() === 'cms'` (`2026_09_03_000001...:13-15` vs `ModuleAccessMiddleware.php:56-58`).
- `Routes/cms.php` não declara `module.active`; a proteção existe apenas por causa do
  provider — um `require` manual do arquivo em teste ficaria sem flag.
- Nenhuma rota tem `throttle`, embora o POST público grave arquivo e envie e-mail.
- Rotas de listagem não declaram `where` para query strings; `status` inválido é
  silenciosamente ignorado (`JobApplicationController.php:45-47`).

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `v2.trabalhe-conosco.portal.index` | rota nomeada | `GET /trabalhe-conosco` | flag ativa | views/links do portal |
| `v2.trabalhe-conosco.portal.show` | rota nomeada | `GET /trabalhe-conosco/{slug}` | vaga aberta | listagem, feed, busca, editor rico |
| `v2.trabalhe-conosco.portal.apply` | rota nomeada | `POST /trabalhe-conosco/{slug}/apply` | CSRF + vaga aberta | formulário do detalhe |
| `v2.trabalhe-conosco.cms.openings` | rota nomeada | `GET /cms/institucional/trabalhe-conosco/openings` | login + permissão | sidebar, `ModuleProvider::route` |
| `v2.trabalhe-conosco.cms.applications` | rota nomeada | `GET .../applications` | login + permissão | sidebar, listagem de vagas |
| `v2.trabalhe-conosco.cms.applications.show` | rota nomeada | `GET .../applications/{id}` | login + permissão | e-mail de notificação, listagem |

### 4.2 Contrato de UI/JS (quando aplicável)

- Nenhum endpoint AJAX. Os formulários CMS usam POST clássico; o upload de mídia é feito
  pelos componentes de Galeria (fora deste módulo).
- `openings/editar/{id}` é usado por dois links na listagem e pelo formulário
  (`openings/index.blade.php:57,85`).
- A listagem de candidaturas envia `status` e `job_opening_id` por GET no
  `filter-card` (`applications/index.blade.php:31-57`).

### 4.3 Ordem e ciclo de vida

1. Provider registra views/migrations/rotas no boot; `v2.modules.configs` é montado
   antes do registro de rotas (`V2ServiceProvider.php:100-127`).
2. Fallback `/{slug}` é registrado depois de todos os módulos (`:137`), garantindo
   precedência de `/trabalhe-conosco`.
3. `refreshNameLookups()`/`refreshActionLookups()` ao final do boot tornam os nomes
   disponíveis para `route()` dentro de hooks (`:141-142`).
4. Flag é lida em runtime a cada request (cache estático por request).

### 4.4 Receitas de uso

**1. Gerar URL pública de uma vaga:**

```php
$url = route('v2.trabalhe-conosco.portal.show', ['slug' => $opening->slug]);
// relativo: route(..., [], false) → /trabalhe-conosco/{slug}
```

**2. Enviar candidatura por HTTP (com CSRF de sessão):**

```bash
curl -X POST "$BASE/trabalhe-conosco/analista-suporte/apply" \
  -b cookies.txt -F "_token=$TOKEN" -F "name=Fulano" -F "email=fulano@example.com" \
  -F "resume=@curriculo.pdf;type=application/pdf"
# 200 com HTML de sucesso; 302 back se validação falhar; 404 se vaga fechada
```

**3. Redirecionar para a listagem de candidaturas de uma vaga:**

```php
return redirect()->route('v2.trabalhe-conosco.cms.applications', [
    'job_opening_id' => $opening->id,
]);
```

**4. Verificar disponibilidade antes de linkar o módulo:**

```php
if (app(\V2\Core\Services\ModuleFlagService::class)->isActive('TrabalheConosco')) {
    // seguro linkar /trabalhe-conosco
}
```

### 4.5 Anti-uso

- **Linkar `/trabalhe-conosco` sem checar a flag**: hoje o link do rodapé existe e leva a
  404 (a flag está desligada). O `SucesuHomepageSeeder` cria o link
  (`SucesuHomepageSeeder.php:115`) sem condicionar à flag.
- **Proteger o CMS só com `CmsAuthenticate`**: sem `module.active` (que vem do provider),
  um módulo desativado continuaria acessível. Não remova a dependência do provider.
- **Usar `POST` para atualizar sem `_method`**: funciona (match PUT/POST), mas quebra
  convenções de cache/auditoria de proxies; prefira `_method=PUT` como as views fazem.

### 4.6 Custos e limites da integração

- Sem throttle; um script autenticado pode criar vagas/candidaturas sem limite.
- POST público limita o corpo por `post_max_size` do PHP antes da validação de 5 MB.
- Sem cache de página: cada visita executa queries e o `trackAccess` do SEO.
- 20 itens por página em ambas as listagens; sem `per_page`.

## 5. Rotas e contratos HTTP

### 5.1 Portal (público)

| Método | URI | Nome | Middleware efetivo | Controller@ação |
|---|---|---|---|---|
| GET | `/trabalhe-conosco` | `v2.trabalhe-conosco.portal.index` | `web`, `MaintenanceMiddleware`, `page.cache`, `public.cache`, `web`, `module.active:TrabalheConosco` | `TrabalheConoscoController@index` |
| GET | `/trabalhe-conosco/{slug}` | `v2.trabalhe-conosco.portal.show` | idem | `TrabalheConoscoController@show` |
| POST | `/trabalhe-conosco/{slug}/apply` | `v2.trabalhe-conosco.portal.apply` | idem + CSRF (`web`) | `TrabalheConoscoController@apply` |

Parâmetros:

| Rota | Parâmetro | Tipo | Obrigatório | Validação/limite |
|---|---|---|---|---|
| todas | `slug` | path string | sim | sem `where`; precisa casar com `v2_job_openings.slug` |
| apply | `name` | body string | sim | `required|string|max:255` |
| apply | `email` | body string | sim | `required|email|max:255` |
| apply | `phone` | body string | não | `nullable|string|max:50` |
| apply | `linkedin_url` | body string | não | `nullable|url|max:255` |
| apply | `cover_letter` | body string | não | `nullable|string` (sem limite explícito) |
| apply | `resume` | file | sim | `required|file|mimes:pdf,doc,docx|max:5120` |

Respostas do portal:

- `200` com `portal.index`/`portal.show`/`portal.success`.
- `302` back com `$errors` (chaves `validation.*`) em falha de validação.
- `404` para vaga inexistente/fechada e para flag inativa.
- `419` para CSRF inválido (middleware `web`).
- `500` se `store()` falhar (coluna `resume_path` NOT NULL) ou se o banco falhar.

### 5.2 CMS (autenticado)

Prefixo comum: `/cms/institucional/trabalhe-conosco`, middleware efetivo
`web`, `module.active:TrabalheConosco`, `CmsAuthenticate` (+RBAC).

| Método | URI | Nome | Ação | Permissão |
|---|---|---|---|---|
| GET | `/openings` | `v2.trabalhe-conosco.cms.openings` | `JobOpeningController@index` | read |
| GET | `/openings/novo` | `...cms.openings.create` | `create` | read |
| POST | `/openings/novo` | `...cms.openings.store` | `store` | write |
| GET | `/openings/editar/{id}` | `...cms.openings.edit` | `edit` | read |
| PUT/POST | `/openings/editar/{id}` | `...cms.openings.update` | `update` | write |
| DELETE | `/openings/{id}` | `...cms.openings.destroy` | `destroy` | write |
| GET | `/applications` | `...cms.applications` | `JobApplicationController@index` | read |
| GET | `/applications/{id}` | `...cms.applications.show` | `show` | read |
| PUT/POST | `/applications/{id}/status` | `...cms.applications.status` | `updateStatus` | write |
| DELETE | `/applications/{id}` | `...cms.applications.destroy` | `destroy` | write |

Payloads:

| Rota | Campos | Regras |
|---|---|---|
| store/update | `title` | `required|string|max:255` |
| store/update | `type` | `nullable|string|max:50` |
| store/update | `workload` | `nullable|string|max:100` |
| store/update | `location` | `nullable|string|max:255` |
| store/update | `salary_range` | `nullable|string|max:100` |
| store/update | `description_detailed` | `nullable|string` (AST JSON) |
| store/update | `cover_image_id` | `nullable|string|max:36` |
| store/update | `attachment_pdf_id` | `nullable|string|max:36` |
| store/update | `is_active` | `boolean` (ausente ⇒ `true` no store, `false` no update) |
| store/update | `published_at` | `nullable|date` (ausente ⇒ `now()` no store, `null` no update) |
| store/update | `closes_at` | `nullable|date|after_or_equal:published_at` |
| index | `search` (query) | livre; `ILIKE` em title/type/location |
| applications | `status` (query) | só aplica se estiver em `$validStatuses` |
| applications | `job_opening_id` (query) | igualdade simples, sem validação de UUID |
| status | `status` | `required|in:pending,reviewed,interviewed,accepted,rejected` |
| status | `notes` | `nullable|string|max:2000` |

Respostas do CMS: `200` (views), `302` (redirects e validação), `302` login/dashboard
(auth/RBAC), `404` (flag inativa ou `findOrFail`), `500` (falha de persistência em
`updateStatus`/`destroy`, que não têm `try/catch`).

Mensagens de validação: não há arquivos de tradução no repositório; o translator devolve
as chaves (`validation.required`, `validation.email`, `validation.url`,
`validation.mimes`, `validation.max.file`, `validation.max.string`, `validation.date`,
`validation.after_or_equal`, `validation.in`, `validation.boolean`) — `[C]` no mecanismo,
`[I]` no texto exibido ao usuário (ver `LG-TRABALHECONOSCO-01`).

## 6. Dados e modelo de domínio

Não se aplica a este anexo: ver `dados.md`. As rotas escrevem apenas
`v2_job_openings`, `v2_job_applications`, arquivo no disco `local`, `activity_log` e,
por hooks, `v2_public_search_index`/`v2_feed_rss_items`.

## 7. Fluxos e estados

```mermaid
sequenceDiagram
  participant U as Usuário
  participant MW as Middlewares
  participant C as Controller
  U->>MW: requisição
  MW->>MW: web → manutenção/cache → module.active
  alt flag inativa
    MW-->>U: 404 (HTML ou JSON)
  else flag ativa (CMS)
    MW->>MW: CmsAuthenticate → V2PermissionMiddleware
    alt sem permissão
      MW-->>U: 302 login/dashboard
    else autorizado
      MW->>C: ação
      C-->>U: 200/302
    end
  else flag ativa (portal)
    MW->>C: ação
    C-->>U: 200/302/404
  end
```

## 8. UI CMS

Não se aplica a este anexo (ver `cms.md`): as rotas apenas entregam as quatro telas.

## 9. UI Portal

Não se aplica a este anexo (ver `portal.md`): as rotas apenas entregam as três páginas.

## 10. Configuração, permissões e flags

- `ModuleProvider::getConfig()['active_pattern'] = ['cms/institucional/trabalhe-conosco*']`.
- `route = 'v2.trabalhe-conosco.cms.openings'` é o destino do item de menu.
- Permissão: chave `TrabalheConosco`, sem slug dedicado; `read`/`write`/`none`.
- Flag: `module_key='trabalheconosco'`, `active=false`; checada pelo middleware em todas
  as rotas; sem fallback de config.
- Não há `throttle`, `cache.headers` ou `signed` em nenhuma rota.

## 11. Observabilidade, erros e limites

- 404 de negócio (`firstOrFail`) não é logado; 404 de flag também não.
- `Log::error` nos `catch` de validação? Não: falhas de validação não geram log.
- `store/update` capturam exceção de `save()` e logam `[JobOpening] Erro ao criar/atualizar
  vaga: ...`; `updateStatus`/`destroy` não capturam.
- `ActivityLogger` grava todas as escritas CMS (não as do portal).
- Limites: 5 MB de upload; 20 itens por página; sem rate limit.

## 12. Seeds e dados iniciais

Não se aplica a rotas. A migration `2026_09_03_000001` altera o estado inicial para
“desativado”, o que muda a resposta de todas as rotas até que a flag seja ligada.

## 13. Lacunas

- `LG-TRABALHECONOSCO-R01` — Comportamento de `findOrFail` com id não-UUID em PostgreSQL:
  não foi possível executar; pode retornar 404 (conversão inválida) ou 500. Verificar no
  ambiente real.
- `LG-TRABALHECONOSCO-R02` — Não há testes de rota no repositório que fixem os nomes; a
  paridade depende do `ModuleProvider` e das views.
- `LG-TRABALHECONOSCO-R03` — Mensagens de validação visíveis dependem de arquivos de
  tradução ausentes (`LG-TRABALHECONOSCO-01`).

## 14. Checklist de paridade

- [ ] `PC-TRABALHECONOSCO-34` — Registrar exatamente 3 rotas públicas e 10 de CMS com os nomes da §5; `route()` resolve todos. Fonte: `Routes/web.php:7-20`; `Routes/cms.php:9-58`.
- [ ] `PC-TRABALHECONOSCO-35` — Com a flag ativa, `POST /openings/novo` cria e redireciona; `PUT /openings/editar/{id}` atualiza; `DELETE /openings/{id}` arquiva. Fonte: `Routes/cms.php:19-37`.
- [ ] `PC-TRABALHECONOSCO-36` — `PUT` e `POST` funcionam em `/openings/editar/{id}` e `/applications/{id}/status`. Fonte: `Routes/cms.php:29,50`.
- [ ] `PC-TRABALHECONOSCO-37` — Com a flag inativa, as 13 rotas retornam 404; AJAX retorna o JSON com `code:404`. Fonte: `ModuleAccessMiddleware.php:46-62`.
- [ ] `PC-TRABALHECONOSCO-38` — Sem sessão e flag ativa, rotas CMS redirecionam para `v2.cms.login`. Fonte: `CmsAuthenticate.php:21-28`.
- [ ] `PC-TRABALHECONOSCO-39` — Perfil `read` recebe 200 em GET e redirect em POST/PUT/DELETE; perfil `none` é enviado ao dashboard. Fonte: `V2PermissionMiddleware.php:74-110`.
- [ ] `PC-TRABALHECONOSCO-40` — `GET /trabalhe-conosco/{slug}` de vaga fechada ou inexistente retorna 404. Fonte: `TrabalheConoscoController.php:56`.
- [ ] `PC-TRABALHECONOSCO-41` — POST do portal sem `_token` retorna 419. Fonte: middleware `web`; `portal/show.blade.php:89`.
- [ ] `PC-TRABALHECONOSCO-42` — POST válido retorna 200 (não 302) e cria uma linha. Fonte: `TrabalheConoscoController.php:105-109`.
- [ ] `PC-TRABALHECONOSCO-43` — Listagem de vagas aceita `search` e pagina 20; candidaturas aceitam `status` e `job_opening_id`. Fonte: `JobOpeningController.php:40-51`; `JobApplicationController.php:41-49`.
- [ ] `PC-TRABALHECONOSCO-44` — Nenhuma rota responde com cache de página (`X-Cache-Status` ausente). Fonte: `PageCacheMiddleware.php:12-19`; `PublicCacheMiddleware.php:27-32`.

## 15. Evidências consultadas

- `V2/Modules/Publicacoes/TrabalheConosco/Routes/web.php:1-21`
- `V2/Modules/Publicacoes/TrabalheConosco/Routes/cms.php:1-59`
- `V2/Core/Providers/V2ServiceProvider.php:90-146,290-315`
- `V2/Core/Middleware/ModuleAccessMiddleware.php:20-66`
- `V2/Core/Middleware/CmsAuthenticate.php:15-46`
- `V2/Core/Middleware/V2PermissionMiddleware.php:22-130`
- `V2/Core/Middleware/PageCacheMiddleware.php:10-92`
- `V2/Core/Middleware/PublicCacheMiddleware.php:25-70`
- `V2/Core/Middleware/MaintenanceMiddleware.php:28-47` (pilha do provider)
- `V2/Modules/Publicacoes/TrabalheConosco/Controllers/*.php`
- `V2/Modules/Publicacoes/TrabalheConosco/ModuleProvider.php:9-34`
- `V2/Modules/Layouts/BasicPage/Routes/web.php:10-21`; `V2/Core/Routes/fallback.php:19-21`
- `app/Http/Kernel.php:65` (alias `module.active`)
