# TV Câmara — Gaps, Bugs e Dúvidas

> **Escopo:** módulo `tv-camara` na `v6`. Pendências técnicas e de negócio
> levantadas na leitura do código, do schema e do comportamento observável.

---

## 1. Bugs confirmados no código

| # | Severidade | Bug | Evidência | Correção sugerida |
|---|---|---|---|---|
| B1 | **Alta** | `down()` da migration derruba a tabela errada: `Schema::dropIfExists('tv_camara')` (o nome real é `tv_camaras`). O `rollback` não remove a tabela e deixa o schema divergente do esperado. | `v6/database/migrations/2024_11_11_164932_create_tv_camara_table.php:36` | Corrigir para `Schema::dropIfExists('tv_camaras');` (ou `tv_camara` apenas se a view também for derrubada, e nesta ordem). |
| B2 | **Alta** | `publication_date` tem `default(date('Y/m/d H:i'))`, avaliado no momento da **migration**: o literal `'2026-10-21 08:52:00'` fica gravado como default no banco. Qualquer registro criado sem informar a data recebe a data da instalação. | `v6/database/migrations/2024_11_11_164932_create_tv_camara_table.php:23` + `\d tv_camaras` | `default(now())` ou, melhor, remover o default e exigir a data no controller (já é `required`). |
| B3 | **Alta** | O botão de exclusão em lote verifica `has_access(['tv-camaras.delete'])` — slug inexistente (o correto é `tv-camara.delete`). O botão nunca aparece para perfis não `super-admin`. | `v6/resources/views/prefecture/contents/tv-camara/index.blade.php:16` | Trocar por `has_access(['tv-camara.delete'])`. |
| B4 | **Alta** | `struct_views($data, 'tv_camara')` gera as flags com prefixo `tv_camara` (underscore), diferente do slug do resource `tv-camara`. Resultado: botões de editar/excluir/ativar ocultos para qualquer perfil que não seja `super-admin`. | `v6/app/Http/Controllers/Prefecture/DatatableController.php:504-520` | Passar `'tv-camara'` em vez de `'tv_camara'`. |
| B5 | **Alta** | As rotas `tv-camara.destroy`, `tv-camara.setStatus` e `ajax.tv-camara.deleteByLote` geram permissões derivadas (`tv-camara.destroy`, `tv-camara.setStatus`, `ajax.tv-camara.deleteByLote`) que não existem na matriz `recurso.ação`; só `super-admin` as executa. | `v6/app/Http/Middleware/CheckRoutePermission.php:41-100` | Incluir `destroy`, `set-status` e `delete-por-lote` na lista de rotas abertas do middleware ou mapear explicitamente para `tv-camara.delete` / `tv-camara.inactivate`. |
| B6 | **Alta** | O breadcrumb da página de detalhes usa o **título** no lugar do `slug`: gera a URL `/tv-camara/{título}`, que não resolve no `firstOrFail()`. | `v6/routes/breadcrumbs.php:434` | `route('portal.tv-camara.show', [$content->slug])` — exige passar o slug ao breadcrumb. |
| B7 | **Média** | A regra dos "3 ativos" só desativa o terceiro mais antigo quando o **novo** registro já nasce desativado. Cadastrar um 4º vídeo ativo deixa 4 ativos. | `v6/app/Http/Controllers/Prefecture/TvCamaraController.php:36-40` e `:114-118` | Mover a desativação para um evento de modelo/serviço, aplicando-a sempre que o total de ativos exceder 3. |
| B8 | **Média** | Colisão silenciosa de `slug`: `str_slug($request->title)` sem verificação de unicidade e sem índice único. Dois vídeos com o mesmo título compartilham a URL; o portal exibe o primeiro encontrado (sem ordenação). | `v6/app/Http/Controllers/Prefecture/TvCamaraController.php:47` e `:126` | Índice unique + sufixo automático, ou slug composto por id. |
| B9 | **Média** | O ID cru do YouTube **não** é convertido pelo `embedVideo()`, embora o placeholder do formulário sugira esse formato. O vídeo aparece com miniatura na home e player em branco na página de detalhes. | `v6/app/Helpers/functions.php:607-665` e `v6/resources/views/prefecture/contents/tv-camara/form.blade.php:65` | Tratar o padrão `^[A-Za-z0-9_-]{11}$` no helper (o presenter já faz isso em `getYoutubeThumbnail`). |
| B10 | **Média** | `show()` do portal não filtra `status` nem `publication_date`: vídeo desativado ou com data futura continua acessível por URL direta. | `v6/app/Http/Controllers/Portal/TvCamaraController.php:33-40` | Reaplicar o mesmo escopo do `index()`. |
| B11 | **Média** | `{!! $content->text !!}` renderiza HTML do Summernote sem sanitização — risco de XSS armazenado. | `v6/resources/views/portal/layouts/modelo1/tv-camara/show.blade.php:29` | Sanitizar no servidor (HTML Purifier) ou gravar apenas texto puro. |
| B12 | **Média** | N+1 na listagem pública: `$content->archives[0]` sem `with('archives')` — uma query por item (até 12 por página). | `v6/app/Http/Controllers/Portal/TvCamaraController.php:23-30` | `TvCamara::with('archives')->...`. |
| B13 | **Baixa** | O `link` aceita no máximo 200 caracteres (Form Request), mas o campo do formulário declara `maxlen = 2999`. | `v6/app/Http/Requests/TvCamaraFormRequest.php:28` vs `v6/resources/views/prefecture/contents/tv-camara/form.blade.php:68` | Alinhar os dois limites (recomendado: 500). |
| B14 | **Baixa** | O `text` é marcado como obrigatório no formulário, mas não é validado pelo Form Request nem é `NOT NULL` no banco (`text` é nullable). | `v6/resources/views/prefecture/contents/tv-camara/form.blade.php:52-57` | Requerer `text` no request ou retirar a marcação de obrigatório. |
| B15 | **Baixa** | O link "Voltar" da tela de edição aponta para `route('listar-conteudo', $content->slug)`, rota do módulo de conteúdos, não da TV Câmara. | `v6/resources/views/prefecture/contents/tv-camara/edit.blade.php:26` | Apontar para `route('tv-camara.index')`. |
| B16 | **Baixa** | Código morto na tela de edição: widget `jquery.uploadfile` ligado a `#multipleupload` (elemento inexistente) e `deleteHighlightArchive` apontando para `ajax.delete.files` (rota do módulo de conteúdos). | `v6/resources/views/prefecture/contents/tv-camara/edit.blade.php` | Remover o widget ou implementar o equivalente para TV Câmara. |
| B17 | **Baixa** | A listagem do CMS ordena por `created_at` (view SQL) e o portal por `publication_date`: as ordens podem divergir. | migration da view vs `v6/app/Http/Controllers/Portal/TvCamaraController.php:28` | Padronizar o critério de ordenação ou documentar a diferença. |
| B18 | **Baixa** | `to_char(publication_date::timestamp with time zone, ...)` sobre coluna `without time zone`: com o banco em UTC, a data exibida no CMS pode estar deslocada em relação ao horário do PHP. | `v6/database/migrations/2024_11_11_190611_create_tv_camara_view.php` | Usar `publication_date` diretamente no `to_char` ou configurar o fuso do banco. |

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| Regra dos 3 ativos | Implementada de forma incompleta (ver B7) e sem lastro na UI: o rótulo do checkbox fala em "página inicial", mas nada permite escolher quais vídeos aparecem na home. |
| "Exibir na página inicial" | Não tem efeito próprio: o `status` controla a listagem; a home sempre mostra os 3 mais recentes ativos. |
| Exclusão em lote | Existe no backend, mas o botão que a dispara é inalcançável para perfis não `super-admin` (B3). |
| Restauração de excluídos | Não há rota nem tela, apesar de a permissão `tv-camara.restore` existir na matriz. |
| Galeria de arquivos | O `edit.blade.php` herda um widget de galeria que não se aplica ao módulo (B16). |
| Autor/fonte | As views testam `$content->source` e `$content->author`, colunas inexistentes: o bloco de crédito nunca aparece. |
| Métricas | Não há contagem de acessos, nem integração com `menu_orders.clicks` para os vídeos. |
| Fila / cache | Inexistentes: o `embedVideo()` roda a cada requisição, sem cache de resolução de URL. |
| API | Não há endpoint JSON do módulo em `v6/routes/api.php`. |
| Testes | Nenhuma suíte cobre o módulo. |
| Importação em lote | Não há importação de vídeos a partir de planilha ou do YouTube. |

## 3. Riscos de migração de schema

| Risco | Descrição | Mitigação |
|---|---|---|
| Slugs duplicados | Ao criar o índice unique, linhas repetidas quebram a migration. | Detectar duplicatas, gerar sufixos e mapear redirects das URLs antigas. |
| `status = null` | Coluna nullable com três interpretações (porta, view SQL, `setStatus`). | Migrar para `boolean NOT NULL DEFAULT false`. |
| Default literal de `publication_date` | O default fixo sobrevive à migração se não for tratado explicitamente. | Migrar os valores reais linha a linha e remover o default. |
| `archives` compartilhada | A coluna `tv_camara_id` convive com `contents_id`, `static_pages_id`, `calendar_events_id`, `requerimento_id` etc. | Preservar a coluna e a FK `ON DELETE CASCADE`; não recriar a galeria. |
| Soft delete vs cascade | A FK `ON DELETE CASCADE` só age em delete físico; o soft delete deixa os arquivos órfãos no storage. | Job de limpeza periódica para arquivos sem registro. |
| Fuso horário | `timestamp without time zone` + `::timestamp with time zone` na view | Migrar para `timestamp with time zone` e fixar o fuso da aplicação. |
| Permissões | A matriz `resources`/`permissions` precisa ser recriada no novo modelo de Gates, sob pena de perder o histórico de perfis. | Migrar `resources.slug = 'tv-camara'` para as policies. |
| Volume do storage | Os arquivos estão em `storage/tv-camara/{id}/destaque/` com `path` incluindo o prefixo `storage/`. | Copiar o diretório e ajustar o `path` para o novo layout. |

## 4. Mapa v5 → v6

### 4.1 O módulo não existe na v5

Verificação executada sobre a árvore da `v5`:

```bash
grep -ril "tv-camara\|tv_camara\|TV Câmara" v5/ --include="*.php" --include="*.blade.php" --include="*.js"
```

**Resultado: nenhuma ocorrência.**

Não existe na `v5`:

| Artefato | Existe na v5? |
|---|---|
| Rota de portal (`/tv-camara`) | Não |
| Rota de CMS (`/cms/tv-camara`) | Não |
| Migration da tabela `tv_camaras` | Não |
| Migration da view `tv_camara` | Não |
| Migration da coluna `archives.tv_camara_id` | Não |
| Model `TvCamara` | Não |
| Controller `Portal\TvCamaraController` | Não |
| Controller `Prefecture\TvCamaraController` | Não |
| Form Request `TvCamaraFormRequest` | Não |
| Views `portal/**/tv-camara/*` | Não |
| Views `prefecture/contents/tv-camara/*` | Não |
| JS `custom/contents/tv-camara/datatable.js` | Não |
| Linhas em `modulos` / `resources` / `menu_orders` | Não |

### 4.2 Lacuna funcional representada

Na `v5`, o portal municipal **não oferecia nenhum canal de vídeo das sessões da
Câmara**. Concretamente, o município da `v5` não tinha:

1. **Página pública de vídeos** (`/tv-camara`) com listagem paginada.
2. **Player institucional** para as sessões plenárias e extraordinárias.
3. **Destaque de vídeos na home** do portal, ao lado de notícias e avisos.
4. **Administração de conteúdo audiovisual** por parte da Câmara: cadastro de
   título, descrição, data e imagem de destaque.
5. **Publicação programada**: não havia como agendar a exibição de um vídeo
   para uma data futura.

O impacto é de transparência e de acesso: o cidadão que não assistia à
sessão presencial não tinha nenhum canal oficial no portal para ver o
conteúdo. O módulo `tv-camara` nasce na v6 (novembro de 2024) justamente
para fechar essa lacuna, ao lado dos módulos `sessoes`, `pauta-do-dia`,
`vereadores`, `transmissoes` e `atas-e-pontos-de-frequencia` — todos também
novos na v6 e desligados por padrão no seed.

### 4.3 O que precisa ser decidido / replicado na v7

| Item | Decisão necessária |
|---|---|
| Publicação da TV Câmara | Reproduzir o módulo inteiro (recomendado) ou vinculá-lo ao módulo `sessoes` (cada sessão gera um vídeo)? A v6 mantém as tabelas desacopladas, sem FK. |
| Formato do link | Padronizar `video_url` + `provider` + `video_id` + `embed_url` e passar a aceitar o ID cru do YouTube. |
| Regra dos 3 | Manter o limite de 3 na home ou torná-lo configurável (`Configuration`)? A v7 deveria permitir a marcação manual de "destaque na home". |
| Ativação do módulo | Preservar o conceito de `modulos.status` (módulo desligado por padrão) ou tornar o módulo sempre ativo? |
| Galeria | Manter a coluna `archives.tv_camara_id` ou migrar para `highlight_archive_id` dedicado. |
| Permissões | Migrar para Policies/Gates com uma permission por ação, eliminando os slugs divergentes (`tv-camaras.delete`, `tv_camara.edit`). |
| Vídeo próprio | Avaliar upload de arquivo de vídeo (MP4) no storage, o que hoje não existe e cairia no fallback do `embedVideo()`. |
| Legibilidade | Incluir legendas, transcrição e descrição acessível, ausentes na v6. |

## 5. Perguntas em aberto para o negócio

1. O limite de 3 vídeos na home é regra da Câmara ou do sistema? Deve ser
   configurável por município?
2. "Exibir na página inicial" deve virar "Publicado no portal"? A vídeo
   desativado deve sumir da listagem `/tv-camara` ou apenas da home?
3. Vídeos agendados para data futura devem ficar invisíveis até a data? (Hoje
   ficam invisíveis na listagem, mas aparecem na home.)
4. É aceitável que o thumbnail seja baixado do YouTube em tempo de renderização
   (dependência externa e de internet do usuário)?
5. Deve haver limite de tamanho/duração do vídeo e retenção de arquivos no
   servidor?
6. Quem pode desativar/excluir um vídeo já publicado — a Comunicação, a Controladoria ou a própria Câmara?
7. É preciso manter o histórico de versões do vídeo (troca de link, troca de
   imagem) para auditoria?
8. Em telas de celular, o player deve iniciar com autoplay e mudo por padrão?

## 6. Checklist de pendências para a v7

- [ ] Corrigir B1 a B6 (bugs de alta severidade) antes de qualquer migração.
- [ ] Criar suíte de testes do módulo (`v6/tests/Feature/TvCamara*Test.php`).
- [ ] Definir o novo schema (`provider`, `video_id`, `embed_url`, `is_active`,
      `published_at`).
- [ ] Migrar os dados existentes, tratando slugs duplicados.
- [ ] Migrar as permissões para Policies/Gates.
- [ ] Copiar e validar os arquivos do storage.
- [ ] Recriar a view SQL ou abandoná-la em favor de uma query Eloquent com
      `casts`.
- [ ] Decidir sobre vídeo próprio, legendas e acessibilidade.
