# Atas e Pontos de Frequência — Arquitetura

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

---

## 1. Visão geral

O módulo é implementado como uma **fatia do CRUD genérico de conteúdos** do
CMS, acrescida de um controller dedicado no portal público e de uma view SQL
para a listagem administrativa. Não há service, repository, Form Request
próprio nem model próprio: tudo reaproveita `contents`,
`category_contents` e `archives`.

```
                     ┌──────────────────────────── CMS (/cms) ────────────────────────────┐
Operador ──► /cms/conteudos/atas-e-pontos
                     │  routes/basepro.php:235  (route: listar-conteudo)
                     ▼
             Prefecture\ContentController@genericIndex
                     │  → returnIndex('Unidades de Atendimento', 'atas-e-pontos')   [BUG de título]
                     │  → menuClicks('atas-e-pontos')
                     ▼
             view prefecture.contents.atas-e-pontos.index
                     │  DataTable "tabela_atas_e_pontos"
                     ▼
             GET /cms/datatable/atas_e_pontos   (routes/basepro.php:400)
                     ▼
             Prefecture\DatatableController@get_atas_e_pontos
                     │  DB::table('atas_e_pontos')->get()   ← VIEW SQL
                     │  struct_views($atas, 'atas-e-pontos') → flags edit/delete/active/inactivate
                     ▼
             Yajra DataTables → JSON → v6/public/assets/js/custom/contents/atas-e-pontos/datatable.js
```

```
                     ┌──────────────────────────── Portal (público) ──────────────────────┐
Cidadão ──► GET /atas-e-pontos
                     │  routes/web.php:119  (atas-e-pontos.index)
                     ▼
             Portal\AtasEPontosController@getIndex
                     │  CategoryContent::whereHas('categoryContents', slug='atas-e-pontos')->orderBy('order')->first()
                     ▼  redirect → /atas-e-pontos/{slug}

Cidadão ──► GET /atas-e-pontos/{slug}
                     ▼
             Portal\AtasEPontosController@show
                     │  Content::where('publication_date','<=',now)->where('category_contents_id',…)
                     │  DB::select("… {$_GET['ano']} …")          [injeção SQL]
                     │  whereRaw("… ilike '%{$newTXT}%'")          [sem binding]
                     │  paginate(12)
                     ▼
             view portal.layouts.{modelo}.atas-e-pontos.show → Ajax → .list

Cidadão ──► GET /atas-e-pontos/{slug1}/{slug2}
                     ▼
             Portal\AtasEPontosController@view
                     │  Content::where('slug',$slug2)->first()  [sem firstOrFail]
                     │  Archive::where('contents_id',…)->where('highlight',null)
                     │  Archive::where('contents_id',…)->where('view_pdf',true)->first()
                     │  $content->update(['clicks' => $clicks + 1])
                     ▼
             view portal.layouts.{modelo}.atas-e-pontos.view
                     │  @include('portal.components.view_pdf.files-list')
                     ▼
             <iframe src="/{archive.path}"> + links de download url($archive->path)
```

## 2. Classes envolvidas

| Classe / arquivo | Namespace | Papel no módulo |
|---|---|---|
| `v6/app/Http/Controllers/Portal/AtasEPontosController.php` | `App\Http\Controllers\Portal` | Controller público: `getIndex()`, `show()`, `view()`. Único controller dedicado do módulo. |
| `v6/app/Http/Controllers/Prefecture/ContentController.php` | `App\Http\Controllers\Prefecture` | CRUD genérico do CMS. `genericIndex()` (linha 116), `genericGenerate()` (linhas 241-247), `returnIndex()`, `returnData()`, `store`/`update`/`setStatus`. |
| `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `App\Http\Controllers\Prefecture` | `get_atas_e_pontos()` (linhas 34-38) e `struct_views()` (linha 504). |
| `v6/app/Http/Controllers/Admin/Controller.php` | `App\Http\Controllers\Admin` | Base dos controllers do portal; `getTemplateActived()` (linhas 39-49) decide o tema. |
| `v6/app/Http/Model/Prefecture/Content.php` | `App\Prefecture` | Model da tabela `contents`; relações `archives()`, `categoryContents()`. |
| `v6/app/Http/Model/Prefecture/CategoryContent.php` | `App\Prefecture` | Model da tabela `category_contents` (árvore por `category_contents_id`). |
| `v6/app/Http/Model/Prefecture/Archive.php` | `App\Prefecture` | Model da tabela `archives` (anexos). |
| `v6/app/Http/Requests/ContentRequest.php` | `App\Http\Requests` | Validação do formulário de conteúdo; para `atas-e-pontos` cai no `default` do `switch` (linhas 314-320). |
| `v6/app/Http/Middleware/CheckRoutePermission.php` | `App\Http\Middleware` | Autorização por rota no CMS (`check.route.permission`). |
| `v6/app/Library/Helper/appHelpers.php` | global | `has_access()` (linha 22) — permissões por slug de ação. |
| `v6/app/Helpers/functions.php` | global | `menuClicks($slug)` (linha 865), `clean_slug()` (linha 823), `embedVideo()` (linha 607). |
| `v6/app/Http/Controllers/Prefecture/CategoryContentController.php` | `App\Http\Controllers\Prefecture` | CRUD genérico das categorias filhas (`cadastrar-categorias`, `editar-categorias`). |

## 3. Fluxo detalhado por tela

### 3.1 Portal — listagem (`show`)

1. `categories` = filhas de `atas-e-pontos`, ordenadas por `order`.
2. `$category` = a categoria cujo `slug` casa com o parâmetro; se não existir,
   `redirect('/atas-e-pontos')`.
3. Conteúdos: `Content::where('publication_date','<=',$current_date)`
   filtrado por `category_contents_id`, `paginate(12)`.
4. `$anos`: `DB::select` cru com `extract(year from publication_date)`.
5. Se houver parâmetros, refaz a query com `when()` para ano/mês/texto.
6. Se `request()->ajax()`, devolve `Response::json(view('portal.layouts.{template}.atas-e-pontos.list')->render())`;
   caso contrário, renderiza `portal.layouts.{template}.atas-e-pontos.show`.

### 3.2 Portal — documento (`view`)

1. Busca o conteúdo pelo `slug` global (sem categoria).
2. Se `publication_date` for futura, redireciona para `/atas-e-pontos`.
3. Carrega `archives` do conteúdo com `highlight = null` e o `view_pdf`.
4. Incrementa `clicks` e salva.
5. Renderiza a view com o bloco `portal.components.view_pdf.files-list`.

### 3.3 CMS — listagem

1. `ContentController@genericIndex` → `returnIndex($title, $slug)`; gera log de
   auditoria (`generateLog($title)`) e incrementa `menu_orders.clicks`.
2. A view do CMS carrega `datatable.js`, que faz requisição Ajax ao endpoint
   `datatable.atas-e-pontos.index`.
3. `DatatableController@get_atas_e_pontos()` lê a **view SQL** `atas_e_pontos`
   (não a tabela `contents`), o que faz a listagem incluir a raiz e as filhas
   da categoria, mas **sem** o filtro `deleted_at` correto (ver 05).
4. `struct_views()` anexa as flags `edit`, `delete`, `active`, `inactivate`
   conforme `has_access()`.

## 4. Diagrama de dependências

```
             [Portal\AtasEPontosController]
                        │
        ┌───────────────┼────────────────┐
        ▼               ▼                ▼
 [CategoryContent]  [Content]       [Archive]
        │               │                │
        ▼               ▼                ▼
 category_contents  contents        archives
                        ▲                │
                        └──── FK ────────┘
                              │
                    [atas_e_pontos]  ← VIEW SQL (contents ⋈ category_contents)

      [ContentController (CMS)] ──► [ContentRequest] ──► [Content]
      [DatatableController (CMS)] ─► DB::table('atas_e_pontos')
```

## 5. Dependências externas

| Pacote / recurso | Uso no módulo |
|---|---|
| `laravel/framework` 5.5 | Roteamento, Eloquent, validação, views Blade. |
| `yajra/laravel-datatables-oracle` | DataTables server-side no CMS. |
| `cartalyst/sentinel` | Sessão e permissões (`has_access`, `Sentinel::inRole`). |
| PostgreSQL 10 | `contents`, `category_contents`, `archives`, view `atas_e_pontos`; funções `extract`, `translate`, `to_char`, `row_number() over`. |
| Bootstrap 4 + jQuery | Portal (temas `modelo1`..`modelo6`). |
| AdminLTE 1.22 | CMS (`prefecture.contents.*`). |
| Summernote | Editor de texto do documento no CMS. |

## 6. Onde o tema do portal é decidido

`getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39-49`)
lê a chave `layout` da tabela `configuration`. O controller do módulo monta as
views como:

```php
view('portal.layouts.' . $this->getTemplateActived() . '.atas-e-pontos.show', ...)
```

Consequência: existem **6 cópias** do conjunto `show/list/pub-nav/view` em
`v6/resources/views/portal/layouts/modelo1/` até `modelo6/`, todas com o mesmo
bloco de filtros e o mesmo JavaScript de Ajax — duplicação pura que precisa ser
eliminada na v7 (ver `05-REGRAS-E-PORTABILIDADE.md`).

## 7. Particularidades de arquitetura relevantes

- **Sem camada de serviço**: o CMS grava em `contents` diretamente pelo
  controller genérico; não há hook específico do módulo.
- **Acoplamento à categoria**: o módulo é identificado pelo slug
  `atas-e-pontos` da **categoria raiz**, enquanto o registro em `modulos` usa
  o slug `atas-e-pontos-de-frequencia`. Os dois não são iguais e a
  correspondência é feita por convenção, não por FK.
- **Módulo desligado por padrão**: `modulos.status = 0` no seed e no banco,
  ou seja, o operador precisa habilitar o módulo em `/cms/modulos`.
- **Métricas de menu**: `menu_orders` possui a linha `slug = 'atas-e-pontos'`,
  `modulo_id = 46`; `menuClicks()` incrementa `clicks` a cada abertura da
  listagem no CMS.
