# Atas e Pontos de Frequência — Rotas e Controllers

> Versão analisada: `v6`. Módulo **novo na v6**. Slug `modulos`:
> `atas-e-pontos-de-frequencia` (id 46).

---

## 1. Rotas do portal (público)

Arquivo: `v6/routes/web.php`. Todas as rotas estão dentro do grupo
`Route::middleware(['check.site.manutention'])` aberto na linha 8.

| Método | URI | Nome | Controller | Linha |
|---|---|---|---|---|
| GET | `/atas-e-pontos` | `atas-e-pontos.index` | `Portal\AtasEPontosController@getIndex` | `v6/routes/web.php:119` |
| GET | `/atas-e-pontos/{slug}` | `atas-e-pontos.show` | `Portal\AtasEPontosController@show` | `v6/routes/web.php:120` |
| GET | `/atas-e-pontos/{slug1}/{slug2}` | `atas-e-pontos.view` | `Portal\AtasEPontosController@view` | `v6/routes/web.php:121` |

Características:

- O parâmetro `{slug}` de `show` é o **slug da categoria filha** (`atas`,
  `pontos-de-frequencia` ou outra criada pelo operador), não o slug do módulo.
- `{slug1}` de `view` é a categoria e `{slug2}` é o slug do conteúdo.
- `show` responde também a requisições Ajax (mesma rota, differentiated por
  `request()->ajax()`), o que **não** é feito com uma rota separada.
- Não há rota de download de arquivo: o download é o link direto para
  `url($archive->path)` (ver `04-VIEWS-E-UX.md`).
- Não há paginação por rota: a paginação é por query string (`?page=`).

### Breadcrumbs

Arquivo `v6/routes/breadcrumbs.php`:

| Nome registrado | Hierarquia | Linha |
|---|---|---|
| `atas-e-pontos` | Home › Atas e Pontos | `v6/routes/breadcrumbs.php:363-366` |
| `category-atas-e-pontos` | Home › Atas e Pontos › Categoria | `v6/routes/breadcrumbs.php:369-372` |
| `view-atas-e-pontos` | Home › Atas e Pontos › Categoria › Documento | `v6/routes/breadcrumbs.php:375-378` |

## 2. Rotas do CMS (`/cms`)

Arquivo `v6/routes/basepro.php`, grupo `Route::group(['prefix' => 'cms',
'before' => 'csrf'], ...)` (linha 33).

| Método | URI | Nome | Controller | Linha |
|---|---|---|---|---|
| GET | `/cms/conteudos/{slug}` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` | `v6/routes/basepro.php:235` |
| GET | `/cms/conteudos/cadastrar/{slug}` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` | `v6/routes/basepro.php:236` |
| GET | `/cms/conteudos/editar/{slug}/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` | `v6/routes/basepro.php:237` |
| GET | `/cms/categorias/{slug}` | `listar-categorias` | `Prefecture\CategoryContentController@genericIndex` | `v6/routes/basepro.php:238` |
| GET | `/cms/categorias/cadastrar/{slug}` | `cadastrar-categorias` | `Prefecture\CategoryContentController@genericCreate` | `v6/routes/basepro.php:239` |
| GET | `/cms/categorias/editar/{slug}/{id}` | `editar-categorias` | `Prefecture\CategoryContentController@genericEdit` | `v6/routes/basepro.php:240` |
| POST | `/cms/conteudo` | `conteudo.store` | `Prefecture\ContentController@store` | `v6/routes/basepro.php:174` (resource) |
| PUT/PATCH | `/cms/conteudo/{id}` | `conteudo.update` | `Prefecture\ContentController@update` | `v6/routes/basepro.php:174` (resource) |
| DELETE | `/cms/conteudo/{id}` | `conteudo.destroy` | `Prefecture\ContentController@destroy` | `v6/routes/basepro.php:174` (resource) |
| DELETE | `/cms/conteudo/delete/{id}` | `generic.conteudo.delete` | `Prefecture\ContentController@genericDestroy` | `v6/routes/basepro.php:223` |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` | `v6/routes/basepro.php:141` |
| GET | `/cms/datatable/atas_e_pontos` | `datatable.atas-e-pontos.index` | `Prefecture\DatatableController@get_atas_e_pontos` | `v6/routes/basepro.php:400` |

A URL efetiva da listagem é `/cms/conteudos/atas-e-pontos` (o `{slug}` é o
slug da **categoria raiz**, `atas-e-pontos`).

## 3. Controllers

### 3.1 `Portal\AtasEPontosController`

Arquivo: `v6/app/Http/Controllers/Portal/AtasEPontosController.php`.
Herda de `App\Http\Controllers\Admin\Controller` (herda
`getTemplateActived()`).

| Método | Assinatura | Comportamento |
|---|---|---|
| `getIndex` | `getIndex(): RedirectResponse` | Busca a primeira categoria filha da raiz `atas-e-pontos` (`whereHas('categoryContents', …)->orderBy('order')->first()`) e redireciona para `atas-e-pontos.show`. **Sem guarda**: se não houver categoria filha, `$categoryFirst` é `null` e `$categoryFirst->slug` gera erro 500. |
| `show` | `show($slug, Request $request)` | Carrega as categorias filhas; se o slug não existir, `redirect('/atas-e-pontos')`; monta `$anos` e `$meses` com SQL cru interpolado; aplica filtros de ano/mês/texto; `paginate(12)`; devolve JSON renderizado quando Ajax. |
| `view` | `view($slug1, $slug2)` | Busca o conteúdo pelo slug **sem** `firstOrFail` e **sem** validar a categoria; compara `publication_date` com a data atual; carrega `archives` (`highlight = null`) e o `view_pdf`; incrementa `clicks`. |

Trecho real do filtro por texto (fonte de risco, ver 05):

```php
->when($request->texto, function ($query, $reqTexto) {
    $query->where(function ($query) use ($reqTexto) {
        $newTXT = clean_slug($reqTexto);
        $query->whereRaw("translate(lower(title), ' ''àáâãäéèëêíìïîóòõöôúùüûçÇ', '--aaaaaeeeeiiiiooooouuuucc') ilike '%" . $newTXT . "%'")
            ->orWhereRaw("translate(lower(text), ' ''àáâãäéèëêíìïîóòõöôúùüûçÇ', '--aaaaaeeeeiiiiooooouuuucc') ilike '%" . $newTXT . "%'");
    });
})
```

### 3.2 `Prefecture\ContentController` (genérico)

Arquivo: `v6/app/Http/Controllers/Prefecture/ContentController.php`.

| Trecho | Linha | Observação |
|---|---|---|
| `if ($slug === 'atas-e-pontos') return $this->returnIndex('Unidades de Atendimento', $slug);` | 116 | **Bug**: título copiado do módulo de unidades de atendimento. Afeta o título da tela e o log de auditoria. |
| `if ($slug === 'atas-e-pontos') { return $this->returnData('Atas e Pontos de Frequencia', $slug, null, [...]); }` | 241-247 | Título correto aqui; fornece `categoryContent` (raiz) e `categoryContentSelect` (filhas). |
| `returnIndex($title, $slug)` | 121-125 | Chama `generateLog($title)` e `menuClicks($slug)`. |
| `returnData(...)` | 275 | Monta os dados das views de create/edit. |

`store`/`update` reaproveitam as regras do `ContentRequest`; para
`atas-e-pontos` não há `case` próprio no `switch`, caindo no `default`
(`v6/app/Http/Requests/ContentRequest.php:308-314`):

```php
default:
    $rules = [
        "title" => "required|max:120",
        "category_contents_id" => "required|numeric",
        "archive.*.0" => "mimes:jpg,jpeg,png,bmp,gif,rar,zip,doc,docx,pdf,odt,xls,xlsx,xlsb,xslm,ppt,pptx,ods,wav,mp3,wma,mp4,mpg4,mkv,flv",
        "archive_highlight" => "max:20000|mimes:jpg,jpeg,png,gif,webp",
    ];
```

Observação: o `default` **não** exige `text` nem `publication_date`, embora o
formulário do CMS marque ambos como obrigatórios — a validação é apenas do
lado do cliente nesses campos.

### 3.3 `Prefecture\DatatableController`

Arquivo: `v6/app/Http/Controllers/Prefecture/DatatableController.php`.

```php
public function get_atas_e_pontos()
{
    $atas = DB::table('atas_e_pontos')->get();
    return DataTables::of($this->struct_views($atas, 'atas-e-pontos'))->make(true);
}
```

`struct_views($data, $permission)` (linha 504) anexa a cada linha as flags
`edit`, `delete`, `active`, `inactivate` a partir de `has_access()`.

## 4. Permissões

| Permissão | Onde é exigida | Efeito quando ausente |
|---|---|---|
| `atas-e-pontos.create` | `v6/resources/views/prefecture/contents/atas-e-pontos/index.blade.php` | Esconde o botão "Cadastrar Conteúdo" e o botão "Categorias de Atas e Pontos de Frequência". |
| `atas-e-pontos.delete` | mesma view | Esconde o checkbox mestre e o botão de exclusão em lote. |
| `atas-e-pontos.edit` | `struct_views()` | Esconde o botão de edição da linha no DataTable. |
| `atas-e-pontos.restore` / `atas-e-pontos.inactivate` | `struct_views()` | Flags de ativação/inativação. |

Helper: `has_access()` em `v6/app/Library/Helper/appHelpers.php:22`
(Sentinel `hasAccess`/`hasAnyAccess`; `super-admin` passa por tudo). O padrão
de slug é `<módulo>.<ação>`, e o "módulo" aqui é o **slug da categoria raiz**
`atas-e-pontos` — não o slug `atas-e-pontos-de-frequencia` da tabela
`modulos`.

Autorização de rota: middleware `check.route.permission`
(`v6/app/Http/Middleware/CheckRoutePermission.php`), que redireciona com
aviso "Você não tem autorização para acessar esta funcionalidade!" quando o
usuário não é `super-admin` e não tem acesso.

## 5. Auditoria

| Evento | Registro |
|---|---|
| Abertura da listagem no CMS | `generateLog($title)` em `returnIndex()`; o título gravado é **"Unidades de Atendimento"** por causa do bug da linha 116. |
| Cliques no menu do CMS | `menuClicks('atas-e-pontos')` → incrementa `menu_orders.clicks` (linha `menu_orders` id 40, `modulo_id` 46). |
| Abertura de documento no portal | `contents.clicks` incrementado em `Portal\AtasEPontosController@view`. |
| Gravação/alteração de conteúdo | Trilha genérica do CMS via `activity()` — `activity()->performedOn(...)->causedBy(Sentinel::getUser())->withProperty('Detalhe', ...)->log(...)`. |

## 6. Saídas de API / Resources

O módulo **não expõe API JSON** própria. A única saída JSON é a do
DataTables server-side (Yajra), com o formato padrão
`{draw, recordsTotal, recordsFiltered, data: [...]}`, em que cada item de
`data` traz `value`, `titulo`, `data_publicacao`, `categoria`,
`categoria_slug`, `slug`, `id`, `edit`, `delete`, `active`, `inactivate`.

## 7. Mapa de navegação

```
Portal
  /atas-e-pontos  ──(redirect)──►  /atas-e-pontos/atas
                                        │  (lista com filtros)
                                        ▼
                                  /atas-e-pontos/atas/{slug-do-conteudo}
                                        │  (documento + anexos)
                                        ▼
                                  download direto do arquivo (sem rota)

CMS
  /cms/conteudos/atas-e-pontos        (lista DataTable)
  /cms/conteudos/cadastrar/atas-e-pontos
  /cms/conteudos/editar/atas-e-pontos/{id}
  /cms/categorias/atas-e-pontos        (categorias filhas)
  /cms/categorias/cadastrar/atas-e-pontos
  /cms/modulos                         (habilitar o módulo, id 46)
```
