# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

A Laravel 13 + Livewire 3 system for running a Brazilian municipal city council's legislative sessions ("Câmara Municipal" — leggov = "legislativo governo", built for Arapiraca). It manages sessions (`Sessão`), agenda items/bills (`Matéria`), roll-call attendance (`Quórum`), voting (`Voto`), floor-speech requests (`Pedido de Fala`), committees (`Comissão`), document routing/protocols (`Protocolo`), meeting minutes (`Ata`), and a public results/live-broadcast screen.

The entire domain vocabulary — model names, DB columns, variable names, comments — is in **Portuguese**. Keep new code consistent with that (e.g. `$vereador`, `votacao_aberta`, `registrar()`), don't translate existing identifiers to English.

Many files carry Portuguese comments explaining *why* a decision was made, often contrasting with a "sistema legado" (legacy system) this app replaces. Read those comments before changing the surrounding logic — they encode institutional/regimental rules (parliamentary procedure) that aren't obvious from the code alone.

## Commands

This is a Windows dev environment; commands below assume PowerShell/Bash with PHP and Composer on PATH.

```bash
# Install
composer install
npm install

# Local dev (serves PHP, queue worker, log tail, and Vite together)
composer run dev

# Run the full test suite
composer test
# equivalent to: php artisan config:clear && php artisan test

# Run a single test file / filter
php artisan test tests/Feature/ExampleTest.php
php artisan test --filter=test_the_application_returns_a_successful_response

# Lint / format (Pint, PSR-12-based)
vendor/bin/pint
vendor/bin/pint --test   # check only, no changes

# Frontend build
npm run dev     # Vite dev server (Tailwind v4)
npm run build

# Migrations
php artisan migrate
php artisan migrate:fresh --seed
```

Tests run against an in-memory SQLite DB (`phpunit.xml`), regardless of the `.env` connection. Note `RefreshDatabase` is commented out in the example test — check whether a test needs it before assuming DB state resets between tests.

Production/local `.env` currently runs on `DB_CONNECTION=mysql`; `.env.example` defaults to `sqlite`.

## Architecture

### Layered structure
- **Controllers** (`app/Http/Controllers/{Admin,Vereador,Publico,Auth}`) — grouped by the three user-facing areas, not by resource. `Admin` = operator/staff back office, `Vereador` = councilmember-facing actions, `Publico` = unauthenticated public site.
- **Livewire components** (`app/Livewire/{Admin,Vereador,Presidente,Publico}`) — used for anything that needs to update live during a session (voting panels, the public results screen, the council-president's panel) or that has nontrivial multi-step form state (e.g. `MateriaForm`, `ProtocoloForm`, `AtaForm`).
- **Services** (`app/Services`) — hold logic that must be a single source of truth for sensitive/shared state, e.g. `VotoService::registrar()` is the *only* code path that writes a vote (used identically whether the councilmember votes themself or an operator votes on their behalf). `VotoBlocoService` and `AparteService` follow the same pattern for block voting and floor-speech/"aparte" timing.
- **Policies** (`app/Policies`) — authorization is centralized here (registered in `AuthServiceProvider`) specifically to avoid the legacy system's scattered `if ($_SESSION['tipo'] === 'operador')` checks. Extend a policy rather than adding ad hoc role checks in a controller.
- **Middleware** — `VerificarModulo` gates a whole route group behind `User::podeAcessarModulo($modulo)`, mirroring (and enforcing server-side) the same rule that hides menu items in the UI.

### Real-time updates: polling, not broadcasting
There's no websocket/broadcast layer wired up (`BROADCAST_CONNECTION=log`). Live screens (the public results panel, the councilmember voting panel, the operator panel) work via Livewire polling — e.g. `wire:poll.2s` in `resources/views/livewire/publico/resultado-ao-vivo.blade.php`. The `render()` method of `ResultadoAoVivo` (`app/Livewire/Publico/ResultadoAoVivo.php`) diffs current DB state against previously-seen IDs/timestamps on every poll to decide whether to `dispatch()` a one-shot browser event (sound cue, animation) — state that must survive across polls is kept in public Livewire properties, not local variables, and is reset whenever the tracked session changes (`assumirSessao()`).

The public "telão" (fixed lobby/TV screen) route (`/publico/painel`) takes no session ID — it self-detects whichever `Sessao` has `status = 'ativa'` and switches automatically when a new session starts, so it can be opened once on a TV and never touched again.

### Domain state machines
- `Sessao.status`: session lifecycle (e.g. `ativa` / `encerrada`); most actions are gated by this.
- `SessaoMateria` (pivot model between `Sessao` and `Materia`, accessed via `Sessao::materias()`/`sessaoMaterias()`) tracks each agenda item's per-session state: `fase` (`leitura_ata`, `expediente`, `ordem_do_dia`, `extra_pauta`), `status` (e.g. `votacao_aberta`), and vote tally columns (`votos_favor`, `votos_contra`, `votos_abstencao`, `resultado`). Voting is only allowed while `status === 'votacao_aberta'`, and only for councilmembers with a confirmed `Quorum` presence for that session.
- Every sensitive write (votes, session close, operator-on-behalf votes) goes through `AuditLog::registrar()` for a before/after trail — follow this pattern for any new sensitive mutation instead of writing to a log table ad hoc.
- `Voto` changes are mirrored into `VotoHistorico` on every save so vote-change history is auditable; whether a vote can be changed at all is a runtime setting (`Configuracao::atual()->permite_alterar_voto`).
- Secret ballots (`Materia.voto_secreto`) must never expose individual votes — check how `ResultadoAoVivo::votosPorVereador()` and `VotoService` handle this before touching vote-display code.

### Authorization model
`User.tipo_usuario` is the primary role (`operador`, `vereador`, plus `proponente` added later, and a boolean `eh_presidente` flag for the council president, checked directly rather than via policy — see the routes file comment for why). Operators have per-module permissions (`Permissao` model, `modulo` + `nivel` columns) unless `acesso_total` is set; use `User::podeAcessarModulo()` / `podeEditarModulo()` rather than re-deriving this.

### Routing conventions
`routes/web.php` has one explicit rule stated in its header comment: **every state-changing action is POST/PUT/DELETE, never GET** — this was a deliberate fix over the legacy system, so don't add GET routes that mutate data. Routes are grouped by area (`admin.*`, `vereador.*`, `presidente.*`, `publico.*`) matching the controller/Livewire split above.

### PDF & reports
`RelatorioController` (admin reports: attendance, votes-per-councilmember, productivity, protocols) renders each report as both an HTML view and a PDF via `barryvdh/laravel-dompdf` (`Pdf::loadView(...)->download(...)`), plus CSV export variants. `Ata` documents follow the same render-then-print approach (`atas/documento.blade.php`), with letterhead/logo/signature framing applied only at display/print time, not stored in the draft text — see `Sessao::gerarRascunhoAta()`, which assembles a full-prose (not bulleted) minutes draft from everything already recorded for the session.
