# Newsletters — Resumo Executivo

> **Versão analisada:** `v6` (módulo idêntico em `v5`, sem alteração funcional)
> **Slug do módulo:** `newsletters` (tabela `modulos`, id 53) — o `menu_orders.slug` correspondente é `newsletter` (singular), `menu_orders.modulo_id = 53`
> **Natureza:** Ambos (CMS para gestão da lista + Portal público para a assinatura)
> **Estado:** OK v6 (copiado da v5 sem alterações; apenas a semente de configuração mudou)

---

## 1. Objetivo

O módulo implementa a lista deassinantes da newsletter institucional da prefeitura.
No **portal público**, o cidadão preenche um formulário de "Assine nossa newsletter"
(sidebar ou rodapé do layout ativo) e grava o próprio e-mail em `newsletters`, com o IP
de origem e a data/hora da assinatura. No **CMS**, o operador lista os inscritos em uma
DataTable, cadastra ou edita assinaturas manualmente, alterna a flag `active`
("Ativar E-mail"), exclui assinaturas (soft delete) e exporta toda a base em CSV.

O módulo **não envia e-mail**: existe a tabela, o formulário e a lista, mas nenhum
disparo de correspondência foi encontrado no repositório — o campo `active` e a coluna
`sent_at` são apenas marcadores de estado e de data de inscrição.

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Bloco "NEWSLETTER" no portal com campos Nome e E-mail. Após assinar, o navegador volta à página anterior e exibe o alerta verde "Inscrição realizada com sucesso!". E-mail duplicado é rejeitado com a mensagem "E-mail já cadastrado.". |
| Prefeitura (CMS) | Menu lateral "Newsletter" (`/cms/newsletter`, ícone `book`) com a lista "Lista de E-mails", botões "Cadastrar E-mail" e "Exportar E-mails", ações de editar e excluir por linha. |
| Transparência e legalidade | O CSV exportado expõe nome, e-mail, **endereço IP** e data/hora de cada assinante — dado pessoal trafegando em arquivo baixável pelo operador. |
| Operação técnica | Duas chaves independentes controlam a exibição no portal: `modulos.status` (slug `newsletters`) e `configurations.value` (chave `newsletters`). |

## 3. Comportamento

- **Portal:** o formulário só é renderizado se `isset($modulos['newsletters']) && $modulos['newsletters']`
  **e** `getStatusDisplayNewsletters()['displayNewsletters'] == 0`. Ambos precisam liberar.
- **Assinatura pelo portal (`POST /newsletter/store-portal`):** valida **somente** o e-mail
  (`required|email|unique:newsletters,email`). `name` e `active` são gravados sem validação.
- **Cadastro no CMS (`POST /cms/newsletter/store`):** valida `name` (`required|string|max:255`)
  e `email` (`required|email|unique:newsletters,email`).
- **`sent_at`** é preenchido pelo servidor com `Carbon::now()` na criação e **não** é
  alterável: o campo existe no formulário do CMS mas o `update()` ignora o valor enviado.
- **`ip_address`** é preenchido com `$request->ip()` (IP do cliente, respeitando proxies
  conforme a configuração do Laravel).
- **`active`** é booleano; no CMS vem de um par `hidden=0` + `checkbox=1`; no portal vem
  de um `hidden` com o valor `"true"`.
- **Exclusão:** sempre soft delete (`deleted_at`). A listagem do CMS lê a **view** `newsletter`,
  que já filtra `deleted_at IS NULL`; a exportação CSV **não** usa a view e portanto
  **inclui** as assinaturas excluídas.
- **E-mail único:** a restrição é do banco (`UNIQUE` em `newsletters.email`), portanto
  assinaturas excluídas continuam bloqueando o re-cadastro do mesmo e-mail.
- **Exportação:** CSV com BOM UTF-8, colunas `ID;Nome;E-mail;Endereço IP;Data de Inscrição;Ativo`,
  nome do arquivo `newsletters.csv`. Base vazia devolve redirect com a mensagem de erro
  "Nenhuma inscrição encontrada para exportar.".

## 4. Como o Usuário Interage

**Cidadão (portal)**

| Onde | O que vê |
|---|---|
| Layouts `modelo1`..`modelo4` | Bloco "NEWSLETTER" na **sidebar** (`portal.layouts.modelo{1..4}.layout.sidebar.newsletter`), com o texto "Assine nossa newsletter e acompanhe as ações da prefeitura." |
| Layout `modelo5` | Bloco no **rodapé** (`portal.layouts.modelo5.layout.footer.menu-footer`), com classe de layout recalculada conforme a newsletter esteja habilitada ou não. |
| Layout `modelo6` | Formulário compacto no **rodapé** (`portal.layouts.modelo6.layout.footer`), botão "Assine". |
| Layout `default` | **Sem** formulário de newsletter (nenhuma ocorrência de `newsletter` em `v6/resources/views/portal/layouts/default`). |

**Operador (CMS)**

| Tela | Rota | Ação |
|---|---|---|
| Lista | `/cms/newsletter` | DataTable (`tabela_newsletter`) com colunas ID, E-mail, Nome, IP, Data de inscrição e Ações; carregada por AJAX em `/cms/datatable/newsletter`. |
| Cadastro | `/cms/newsletter/create` | Formulário com Ativo, E-mail, Nome e Data de Cadastro (somente leitura). |
| Edição | `/cms/newsletter/edit/{id}` | Mesmo formulário, `PUT` em `/cms/newsletter/update/{id}`. |
| Exportação | `/cms/newsletter/export` | Botão "Exportar E-mails" — download imediato de `newsletters.csv`. |

## 5. Detalhes e Particularidades Técnicas

- Camadas: rota → `Prefecture\NewsLetterController` → validação inline (`$request->validate`)
  → model Eloquent `App\Http\Model\Prefecture\Newsletters` → tabela `newsletters`.
  **Não há repository, Form Request nem API Resource** — o módulo é o mais simples do portal.
- A listagem do CMS **não** usa o model: `DatatableController@get_newsletter` consulta
  `DB::table('newsletter')` (a **view** SQL) e injeta `edit`/`delete`/`active`/`inactivate`
  via `struct_views()`.
- A view `newsletter` renomeia as colunas: `nome`, `ip`, `enviado`, `ativo`.
- O nome da classe do model é **plural** (`Newsletters`); **não existe** o arquivo
  `NewsLetter.php` (singular) em `v6/app/Http/Model/Prefecture/`. A tabela `newsletters` é
  obtida pelo pluralizador do Laravel a partir do nome da classe.
- O controller herda de `Prefecture\Controller`, cujo construtor aplica o middleware
  `check.route.permission` — o `NewsLetterController` não define construtor próprio.
- Dependências de terceiros: `nesbot/carbon` (`Carbon::now()`), `yajra/laravel-datatables-oracle`
  (DataTable), `laravelcollective/html` (`Form::`), `cartalyst/sentinel` (papéis/permissões).

## 6. Estrutura de Dados

| Objeto | Tipo | Observação |
|---|---|---|
| `newsletters` | tabela | id, name (null), email (unique, not null), ip_address, sent_at, active, timestamps, deleted_at |
| `newsletter` | view SQL | `row_number()` + `nome`, `email`, `ip`, `enviado`, `ativo`, `id`; `WHERE deleted_at IS NULL` |
| `menu_orders` | linha | `slug = 'newsletter'`, `modulo_id = 53` — é o que gera o item de menu "Newsletter" |
| `modulos` | linha | id 53, `slug = 'newsletters'`, `status` liga/desliga o bloco no portal |
| `configurations` | linha | `key = 'newsletters'`, tipo `checkbox`, `order = 27` — "Desabilitar Newsletter?" |

## 7. Como Testar

1. Portal: abrir a home com o layout ativo, assinar com um e-mail novo e conferir a linha
   criada em `newsletters` (`ip_address` e `sent_at` preenchidos).
2. Portal: repetir com o mesmo e-mail e conferir o erro "E-mail já cadastrado.".
3. CMS: `/cms/newsletter` — validar ordenação, edição, exclusão e o botão de exportação.
4. CMS: exportar e confirmar que o CSV **contém** uma assinatura previamente excluída
   (comportamento atual, não desejado — ver `08-GAPS-E-DIVIDAS.md`).
5. Permissões: usuário sem `newsletter.index` não deve ver o item de menu; usuário
   sem `newsletter.edit` não deve ver o botão de editar na DataTable.

## 8. Migração para Laravel Moderno

- Model `Newsletter` (singular) com `protected $table = 'newsletters'`, `$fillable`,
  `SoftDeletes` e casts `sent_at => datetime`, `active => boolean`.
- `StoreNewsletterRequest` (portal) e `StoreNewsletterAdminRequest` (CMS) separados, com
  `unique:newsletters,email` e `Rule::unique()->ignore($id)`.
- Policy `NewsletterPolicy` no lugar do `check.route.permission` difuso.
- Endpoint público `POST /newsletter` retornando JSON `{ok: true}` (o portal moderno
  consome por `fetch`/Alpine em vez de redirect com `session('success')`).
- CSV gerado por `League\Csv` ou `streamedResponse` com escape de fórmulas (prefixo `'`),
  sempre respeitando o escopo de soft delete.
- Chave de idempotência opcional (evitar duplo registro por duplo clique).

## 9. Resumo Geral

O módulo `newsletters` é a lista de assinantes da newsletter institucional. O cidadão
assina pelo portal (formulário no rodapé/sidebar do layout ativo) e o operador gerencia
a lista no CMS. Não existe envio de e-mail no código: o módulo só captura e administra
contatos. Os dados ficam na tabela `newsletters` e a listagem do CMS passa por uma view
SQL `newsletter` que já exclui os registros apagados. A exportação em CSV, porém, lê a
tabela crua e entrega também os registros apagados, além de expor nome, e-mail, IP e
data de assinatura em um arquivo sem qualquer proteção adicional. A porta pública
`storePortal` valida apenas o e-mail, o que permite gravar `active` nulo (coluna
`NOT NULL`) e derrubar a requisição com erro 500. O nome plural do model (`Newsletters`)
e a inexistência da classe `NewsLetter` (singular) são armadilhas para quem for
reconstruir o módulo.

---

## Índice do dossiê

| Arquivo | Conteúdo |
|---|---|
| `00-RESUMO.md` | Este documento |
| `01-ARQUITETURA.md` | Camadas, classes e o diagrama de fluxo |
| `02-MODELAGEM.md` | Estrutura de dados detalhada |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, validações e permissões |
| `04-VIEWS-E-UX.md` | Telas do CMS, blocos do portal, JS e exportação |
| `05-REGRAS-E-PORTABILIDADE.md` | Regras não óbvias, acoplamentos e armadilhas |
| `06-MIGRACAO-MODERNA.md` | Plano de reconstrução em Laravel moderno |
| `07-TESTES.md` | Estratégia e casos de teste |
| `08-GAPS-E-DIVIDAS.md` | Pendências, riscos e comparativo v5 vs v6 |
