diff --git a/docs/plans/municipio-v2-agora-impl.md b/docs/plans/municipio-v2-agora-impl.md
new file mode 100644
index 00000000..08989f9d
--- /dev/null
+++ b/docs/plans/municipio-v2-agora-impl.md
@@ -0,0 +1,67 @@
+# Impl: Município v2 — Agora (encaminhamento + sugestão + visita)
+
+Status: aprovado
+Atualizado em: 2026-08-03
+Issue: #333
+Intenção: docs/plans/municipio-v2-agora.md
+Appetite restante: herdado (~0,5–1 dia eng)
+
+## Leitura da intenção
+
+- **Outcome:** Bloco “Agora” na rota v2 com encaminhamento editável no lugar, até duas sugestões acionáveis (ou silêncio explícito) e visita condensada em uma linha.
+- **O que NÃO negociar:** um só motor E11; resolve/dispensa existente; `nextSteps` como fonte única; sem checklist E13 expandida; staff only (`noLeader`); silêncio explícito.
+- **O que reavaliar:** hipótese de componente monolítico — separar loader server, controle client de `nextSteps` e seção RSC; JSON route (precedente political-trend) vs form action (precedente AdvisorDebouncedTextCell).
+
+## Abordagem recomendada
+
+```mermaid
+flowchart TB
+ page[v2/page.tsx] --> loader[loadMunicipalityV2AgoraData]
+ loader --> VM[strategy.nextSteps + E11 slice 0-2 + E13 summary]
+ page --> section[MunicipalityV2AgoraSection RSC]
+ section --> next[MunicipalityV2NextStepsControl]
+ section --> cards[SuggestionCard × 0-2]
+ section --> visit[formatVisitEligibilitySummary + link giros]
+ next -->|POST /municipios/next-steps| action[updateMunicipalityStrategy partial]
+ cards -->|resolveSuggestionFormAction| E11[resolveSuggestion]
+```
+
+**Opções consideradas:** A) reutilizar `SuggestionsPanel` inteiro | B) compor loader + peças novas enxutas | C) novo motor de sugestão v2
+**Recomendação:** B — mesmos loaders/ações; UI condensada sem statute/card E13.
+**Rejeitadas:** A (torre de chrome do dashboard); C (twin motor).
+
+### Componentes / mudanças
+
+- **`formatVisitEligibilitySummary`** (`src/utilities/visit/visitEligibility.ts`): headline + frase curta; teste unitário.
+- **`loadMunicipalityV2AgoraData`** (`src/utilities/municipality/municipalityV2AgoraData.ts`): `Promise.all` detail VM + suggestions + visit.
+- **`MunicipalityV2NextStepsControl`** + **`/municipios/next-steps/route.ts`**: autosave debounced (precedente political-trend JSON).
+- **`MunicipalityV2AgoraSection`**: RSC que monta os três sub-blocos.
+- **`resolveSuggestion`**: `revalidateMunicipalityListPaths` inclui v2.
+- **Migration:** sem migration.
+
+### Dados → forma
+
+- Encaminhamento: textarea auto-save (edit where you see).
+- Sugestões: 0–2 `SuggestionCard` compactos; silêncio copiado do OverviewTab.
+- Visita: uma linha “Elegível|Não elegível · motivo” + link compositor.
+
+## Fases verificáveis
+
+1. **Schema/server** — summary helper, loader, next-steps route, revalidation fix.
+2. **UI** — section + control + wire v2 page.
+3. **Gates** — `pnpm gate:fast`; push via `pnpm push`.
+
+## Rabbit holes / Não escopo (engenharia)
+
+- FAB B151; Conta/Rede B148–B149; compositor completo.
+
+## Riscos e mitigação
+
+- Partial strategy update sobrescrever outros campos → action só envia `nextSteps`.
+- Sugestão stale na v2 → revalidate v2 no resolve.
+
+## Aceite de engenharia
+
+- [x] Aceite de produto da intenção ainda coberto
+- [x] Invariantes AGENTS/engineering-standards
+- [x] Testes de domínio previstos (unit summary visit)
diff --git a/src/app/(campaign)/campanha/(app)/municipio/[slug]/v2/page.tsx b/src/app/(campaign)/campanha/(app)/municipio/[slug]/v2/page.tsx
index 5a00d136..5e333848 100644
--- a/src/app/(campaign)/campanha/(app)/municipio/[slug]/v2/page.tsx
+++ b/src/app/(campaign)/campanha/(app)/municipio/[slug]/v2/page.tsx
@@ -2,13 +2,16 @@ import config from '@payload-config'
import { notFound } from 'next/navigation'
import { getPayload } from 'payload'
+import { resolveSuggestionFormAction } from '@/app/(campaign)/campanha/(app)/suggestionFormActions'
import { createMunicipalityV2SignalFormAction } from '@/app/(campaign)/campanha/(app)/municipio/[slug]/v2/formActions'
+import { MunicipalityV2AgoraSection } from '@/components/campaign/municipality/MunicipalityV2AgoraSection'
import { MunicipalityV2NetworkSection } from '@/components/campaign/municipality/MunicipalityV2NetworkSection'
import { MunicipalityV2StatusStrip } from '@/components/campaign/municipality/MunicipalityV2StatusStrip'
import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell'
import { campaignPageMetadata } from '@/lib/campaignPageChrome'
import { getMunicipalityCatalogEntry } from '@/lib/municipalityCatalog'
import { requireCampaignPageActor } from '@/utilities/campaignPageActor'
+import { loadMunicipalityV2AgoraData } from '@/utilities/municipality/municipalityV2AgoraData'
import { MunicipalityNotFoundError } from '@/utilities/municipality/municipalityPageData'
import { loadMunicipalityV2NetworkData } from '@/utilities/municipality/municipalityV2NetworkData'
import { loadMunicipalityV2StatusData } from '@/utilities/municipality/municipalityV2StatusData'
@@ -33,13 +36,16 @@ export default async function MunicipalityV2Page({ params }: MunicipalityV2PageP
let status
let network
+ let agora
try {
- const loaded = await Promise.all([
+ const [statusLoaded, networkLoaded, agoraLoaded] = await Promise.all([
loadMunicipalityV2StatusData(payload, user, slug),
loadMunicipalityV2NetworkData(payload, user, slug),
+ loadMunicipalityV2AgoraData(payload, user, slug),
])
- ;({ status } = loaded[0])
- network = loaded[1]
+ ;({ status } = statusLoaded)
+ network = networkLoaded
+ agora = agoraLoaded
} catch (error) {
if (error instanceof MunicipalityNotFoundError) notFound()
throw error
@@ -53,7 +59,7 @@ export default async function MunicipalityV2Page({ params }: MunicipalityV2PageP
signalFormAction={createMunicipalityV2SignalFormAction}
/>
- {/* Placeholders for B148–B150 — keep the vertical rhythm without shipping twin content. */}
+ {/* Placeholder for B148 — keep the vertical rhythm without shipping twin content. */}
Conta local
@@ -61,13 +67,7 @@ export default async function MunicipalityV2Page({ params }: MunicipalityV2PageP
Em breve nesta visão.
-
-
-
- Agora
-
-
Em breve nesta visão.
-
+
)
diff --git a/src/app/(campaign)/campanha/(app)/municipios/[slug]/MunicipalityDetailTabs.tsx b/src/app/(campaign)/campanha/(app)/municipios/[slug]/MunicipalityDetailTabs.tsx
index b9bd1690..456630a6 100644
--- a/src/app/(campaign)/campanha/(app)/municipios/[slug]/MunicipalityDetailTabs.tsx
+++ b/src/app/(campaign)/campanha/(app)/municipios/[slug]/MunicipalityDetailTabs.tsx
@@ -30,7 +30,7 @@ import {
import { loadMunicipalityElectoralBaseline } from '@/utilities/municipality/municipalityElectoralBaseline'
import { loadMunicipalityGoalAccount } from '@/utilities/municipality/municipalityGoalAccount'
import type { getMunicipalityDetailViewModel } from '@/utilities/municipality/municipalityPageData'
-import { formatSilenceAgeLabel } from '@/utilities/municipality/municipalitySignal'
+import { formatMunicipalitySuggestionEmptyMessage } from '@/utilities/municipality/municipalitySignal'
import { loadMunicipalityTicketPartners } from '@/utilities/municipality/municipalityTicketPartnerData'
import { loadMunicipalitySuggestions } from '@/utilities/municipality/municipalityTriggers'
import {
@@ -149,17 +149,11 @@ const SuggestionsCard = async ({
activeCount={bundle.suggestions.length}
resolveAction={resolveSuggestionFormAction}
emptyState={
- silence ? (
-
- Nenhum padrão do catálogo dispara aqui — e o município{' '}
- {formatSilenceAgeLabel(silence.lastSignalAgeDays)}. Silêncio é pergunta: vale checar a
- rede e o registro.
-