mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
docs(C211,S27,C212,S28,C213): planos + designs hi-fi — Central de Conteúdos (Peça voto pra Solla 1313)
This commit is contained in:
parent
4123cd4b78
commit
0f1e95e8f1
9 changed files with 2261 additions and 0 deletions
374
docs/plans/central-conteudos-analytics-ui-design.html
Normal file
374
docs/plans/central-conteudos-analytics-ui-design.html
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C213 · Analytics da Central</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-sidebar: var(--sidebar);
|
||||
}
|
||||
[data-theme='campaign'] {
|
||||
--foreground: #1c1917;
|
||||
--primary: #c51414;
|
||||
--muted: #f4f4f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--border: rgb(28 25 23/12%);
|
||||
--sidebar: #fafaf9;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.board {
|
||||
background: #efeeec;
|
||||
background-image: radial-gradient(rgb(28 25 23/8%) 0.7px, transparent 0.7px);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 18px 52px rgb(28 25 23/10%);
|
||||
}
|
||||
.desktop {
|
||||
width: 1280px;
|
||||
}
|
||||
.phone {
|
||||
width: 390px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.primary {
|
||||
background: #c51414;
|
||||
color: #fff;
|
||||
}
|
||||
.outline {
|
||||
border: 1px solid #d6d3d1;
|
||||
background: #fff;
|
||||
}
|
||||
.field {
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
border: 1px solid #d6d3d1;
|
||||
border-radius: 6px;
|
||||
padding: 9px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.live {
|
||||
background: #fff1f2;
|
||||
color: #9f1239;
|
||||
}
|
||||
.draft {
|
||||
background: #f4f4f5;
|
||||
color: #57534e;
|
||||
}
|
||||
.asset {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #e7e5e4, #f5f5f4);
|
||||
color: #78716c;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.scene:after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: #d6d3d1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="board min-h-screen text-foreground antialiased">
|
||||
<main class="mx-auto max-w-[1480px] space-y-16 px-4 py-12 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<span class="badge bg-primary text-white">C213 · revisão do gate</span>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">Quatro sinais. Nenhum dashboard.</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Os contadores vivem junto da peça e respondem só ao que a assessoria precisa decidir.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 01</span>
|
||||
<h2>Desktop 1280 · uma coluna “Circulação” na lista</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl border bg-white">
|
||||
<div class="flex min-h-[680px]">
|
||||
<aside class="w-56 shrink-0 border-r bg-sidebar p-3">
|
||||
<div class="flex h-12 items-center gap-2 px-2">
|
||||
<div
|
||||
class="grid size-8 place-items-center rounded-lg bg-primary font-bold text-white"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
<div>
|
||||
<b class="text-sm">Solla</b>
|
||||
<p class="text-xs text-muted-foreground">Campanha</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-md bg-muted px-3 py-2 text-sm font-semibold">
|
||||
Comunicação
|
||||
</div>
|
||||
<div class="ml-4 border-l pl-3">
|
||||
<div
|
||||
class="mt-1 rounded-md bg-white px-3 py-2 text-sm font-semibold text-primary ring-1 ring-border"
|
||||
>
|
||||
Conteúdos
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="min-w-0 flex-1 p-8">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold">Conteúdos</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Publicação e circulação no mesmo lugar.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn primary">Enviar peças</button>
|
||||
</div>
|
||||
<div class="mt-7 flex gap-2">
|
||||
<input class="field" placeholder="Buscar conteúdos…" /><button class="btn outline">
|
||||
Filtros
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-5 overflow-hidden rounded-xl border">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="bg-stone-50 text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th class="p-4">Peça</th>
|
||||
<th>Publicação</th>
|
||||
<th>Circulação</th>
|
||||
<th class="pr-4 text-right">Ação</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="asset h-14 w-20 rounded-md">NEEDS ASSET</div>
|
||||
<div>
|
||||
<b>Fim da escala 6×1 é saúde</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Vídeo</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge live">Publicado</span></td>
|
||||
<td>
|
||||
<div class="grid grid-cols-4 gap-5">
|
||||
<div>
|
||||
<span class="block text-[10px] text-muted-foreground">Aberturas</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[10px] text-muted-foreground">Downloads</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[10px] text-muted-foreground">WhatsApp</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-[10px] text-muted-foreground">Link</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="pr-4 text-right"><button class="btn outline">Abrir</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<b>Solla em defesa do SUS</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Foto</p>
|
||||
</td>
|
||||
<td><span class="badge live">Publicado</span></td>
|
||||
<td class="font-semibold tabular-nums">
|
||||
0 abertura · 0 download · 0 WhatsApp · 0 link
|
||||
</td>
|
||||
<td class="pr-4 text-right"><button class="btn outline">Abrir</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<b>Por que defender o SUS</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Texto</p>
|
||||
</td>
|
||||
<td><span class="badge draft">Rascunho</span></td>
|
||||
<td class="text-xs text-muted-foreground">Sem dados — ainda não publicada</td>
|
||||
<td class="pr-4 text-right"><button class="btn outline">Abrir</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">
|
||||
Barras cinza = valores reais omitidos no gate · 0 = nenhum evento · — = contagem
|
||||
indisponível.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 02</span>
|
||||
<h2>Desktop 1280 · detalhe por canal, sem gráfico</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl border bg-white">
|
||||
<div class="border-b px-8 py-5">
|
||||
<a class="text-xs font-semibold text-primary">← Conteúdos</a>
|
||||
<h3 class="mt-2 text-xl font-semibold">Fim da escala 6×1 é saúde</h3>
|
||||
</div>
|
||||
<div class="grid grid-cols-[300px_1fr] gap-8 p-8">
|
||||
<div>
|
||||
<div class="asset aspect-video rounded-xl">NEEDS ASSET · PREVIEW REAL</div>
|
||||
<button class="btn outline mt-4 w-full">Editar peça</button>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-base font-semibold">Circulação</h4>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Contagem anônima e acumulada desde a publicação.
|
||||
</p>
|
||||
<div class="mt-5 grid grid-cols-4 overflow-hidden rounded-xl border">
|
||||
<div class="p-4">
|
||||
<span class="text-xs text-muted-foreground">Aberturas</span
|
||||
><span class="mt-3 block h-5 w-14 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="border-l p-4">
|
||||
<span class="text-xs text-muted-foreground">Downloads</span
|
||||
><span class="mt-3 block h-5 w-14 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="border-l p-4">
|
||||
<span class="text-xs text-muted-foreground">WhatsApp</span
|
||||
><span class="mt-3 block h-5 w-14 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="border-l p-4">
|
||||
<span class="text-xs text-muted-foreground">Link copiado</span
|
||||
><span class="mt-3 block h-5 w-14 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-muted-foreground">
|
||||
Valores reais omitidos neste gate; o produto mostra contagens absolutas.
|
||||
</p>
|
||||
<div class="mt-5 rounded-lg bg-muted p-4 text-sm leading-6 text-muted-foreground">
|
||||
<b class="text-foreground">Leitura:</b> compare os sinais sem somá-los. WhatsApp e
|
||||
link ficam separados porque mostram escolhas diferentes.
|
||||
</div>
|
||||
<p class="mt-4 text-xs text-muted-foreground">
|
||||
Sem IP persistido, cookie de identidade ou rastreio entre peças.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENAS 03–04</span>
|
||||
<h2>Mobile 390 · cards e falha da contagem</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-8">
|
||||
<div class="phone frame min-h-[760px] overflow-hidden border bg-white">
|
||||
<div class="flex h-14 items-center justify-between bg-primary px-4 text-white">
|
||||
<b>Conteúdos</b
|
||||
><button class="rounded-md border border-white/40 px-3 py-2 text-xs font-semibold">
|
||||
Enviar
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<input class="field" placeholder="Buscar conteúdos…" />
|
||||
<article class="mt-4 rounded-xl border p-4">
|
||||
<div class="flex justify-between gap-3">
|
||||
<b class="text-sm">Fim da escala 6×1 é saúde</b
|
||||
><span class="badge live h-fit">Publicado</span>
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-2 gap-px overflow-hidden rounded-lg bg-border">
|
||||
<div class="bg-white p-3">
|
||||
<span class="text-[10px] text-muted-foreground">Aberturas</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="bg-white p-3">
|
||||
<span class="text-[10px] text-muted-foreground">Downloads</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="bg-white p-3">
|
||||
<span class="text-[10px] text-muted-foreground">WhatsApp</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
<div class="bg-white p-3">
|
||||
<span class="text-[10px] text-muted-foreground">Link</span
|
||||
><span class="mt-1 block h-3 w-8 rounded bg-stone-300"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn outline mt-3 w-full">Abrir</button>
|
||||
</article>
|
||||
<article class="mt-3 rounded-xl border p-4">
|
||||
<div class="flex justify-between">
|
||||
<b class="text-sm">Solla em defesa do SUS</b
|
||||
><span class="badge live">Publicado</span>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">
|
||||
0 abertura · 0 download · 0 WhatsApp · 0 link
|
||||
</p>
|
||||
</article>
|
||||
<article class="mt-3 rounded-xl border p-4">
|
||||
<div class="flex justify-between">
|
||||
<b class="text-sm">Por que defender o SUS</b
|
||||
><span class="badge draft">Rascunho</span>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">Sem dados — ainda não publicada.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div class="phone frame min-h-[760px] overflow-hidden border bg-white">
|
||||
<div class="flex h-14 items-center gap-3 border-b px-4">
|
||||
<span>←</span><b>Detalhe da peça</b>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="asset aspect-video rounded-xl">NEEDS ASSET · PREVIEW REAL</div>
|
||||
<h3 class="mt-4 text-base font-semibold">Fim da escala 6×1 é saúde</h3>
|
||||
<h4 class="mt-6 text-sm font-semibold">Circulação</h4>
|
||||
<div class="mt-3 divide-y overflow-hidden rounded-xl border">
|
||||
<div class="flex justify-between p-4 text-sm"><span>Aberturas</span><b>—</b></div>
|
||||
<div class="flex justify-between p-4 text-sm"><span>Downloads</span><b>—</b></div>
|
||||
<div class="flex justify-between p-4 text-sm"><span>WhatsApp</span><b>—</b></div>
|
||||
<div class="flex justify-between p-4 text-sm">
|
||||
<span>Link copiado</span><b>—</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-lg bg-muted p-3 text-xs leading-5 text-muted-foreground">
|
||||
Contagem indisponível agora. A página pública, o download e o compartilhamento
|
||||
continuam funcionando.
|
||||
</div>
|
||||
<button class="btn outline mt-5 w-full">Editar peça</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
112
docs/plans/central-conteudos-analytics.md
Normal file
112
docs/plans/central-conteudos-analytics.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# C213 — Analytics da Central de Conteúdos — uso, downloads e compartilhamentos
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-22
|
||||
Issue: #1258
|
||||
Priority: P2
|
||||
Impeccable: B — encaixe na lista de peças do C211 (contadores) sem tela nova
|
||||
Design UI: docs/plans/central-conteudos-analytics-ui-design.html
|
||||
Appetite: ~1–1,5 dia eng; um outcome verificável — contadores por peça na lista do C211, sem serviço novo de analytics
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A Central de Conteúdos (C211 + S27) existe para a assessoria publicar peças e o público abrir, baixar e compartilhar. Mas hoje ninguém sabe se isso acontece: o self-hosted não tem analytics ativo (`@vercel/analytics` só rende com `VERCEL === '1'`; o Meta Pixel é mídia paga, não serve) e não há coleção de eventos. O dono pediu "acompanhar a utilização dos usuários e quanto compartilhamentos e downloads estão sendo feitos" — e a pergunta embutida é se vale criar algo próprio ou integrar algo existente.
|
||||
|
||||
Esta fatia responde de forma mínima e anônima: cada peça ganha contadores na lista interna que já existe, sem dashboard novo e sem serviço para operar. A assessoria precisa saber quais peças são abertas, baixadas e compartilhadas; a coordenação precisa saber se o esforço de manter a Central está valendo. Nada além disso.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria e coordenação de comunicação (role `communicator`; `coordinator`/`candidate` veem o mesmo), na mesa, decidindo o que reforçar e o que produzir. Quem gera os eventos é o visitante anônimo da página pública — que nunca é identificado.
|
||||
- **Job principal:** ler, por peça, o que foi aberto, baixado e compartilhado, e decidir o próximo passo de produção sem abrir outra ferramenta.
|
||||
- **Fluxo desejado:** entra na lista de peças → lê os contadores de cada linha → "muito aberta e pouco compartilhada" pede novo gancho/capa; "muito compartilhada" pede continuação → decide o que reforçar/reeditar e o que produzir em seguida.
|
||||
- **Anti-goals de produto:** não é analytics de site (sem pageview, funil, origem, heatmap); não é perfil de visitante (sem cookie de identidade, sem IP persistido, sem rastreio entre páginas ou entre peças); não é dashboard novo nem gráfico de vaidade; não rastreia o que acontece depois do clique — fora do site, nada.
|
||||
|
||||
### Esboço de fluxo (B)
|
||||
|
||||
```text
|
||||
[visita anônima abre a peça] → abertura · download · compartilhar (WhatsApp | link) contam na peça
|
||||
[assessoria] lista interna de peças (C211) → contadores por linha → "abriu muito, compartilhou pouco"
|
||||
→ reforça/reedita gancho, capa ou chamada → decide a próxima peça
|
||||
[coordenação] lê as peças mais servidas → decide se a Central vale o esforço de catalogar/publicar
|
||||
[falha na contagem] a página pública segue idêntica — download e compartilhar nunca dependem do contador
|
||||
```
|
||||
|
||||
### Design UI (B)
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-analytics-ui-design.html` — cenas: lista de peças do C211 com a coluna de contadores (com e sem dados) e o recorte por canal no detalhe da peça. Encaixe na lista existente; sem tela nova.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- A lista interna de peças do C211 mostra, por peça, contadores absolutos de aberturas, downloads e compartilhamentos (WhatsApp e link), anônimos e agregados.
|
||||
- A assessoria responde "qual peça reforçar/reeditar e o que produzir em seguida" e a coordenação responde "a Central está valendo o esforço" lendo a própria lista, sem ferramenta nova.
|
||||
- Fail-soft: se a contagem falhar, a página pública não quebra nem atrasa; nada bloqueia abertura, download ou compartilhamento — contador a menos é aceitável, dado de visitante não.
|
||||
- Privacidade: sem PII (sem IP persistido, sem cookie de identidade, sem user-agent completo), sem rastreio entre páginas/peças e sem qualquer dado de eleitor; contagem estritamente anônima.
|
||||
- Nenhum serviço novo no homeserver para operar/backup; nada de dashboard, gráfico ou rota de analytics.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Sim, superfície neste item — contadores anônimos por peça (abertura · download · compartilhamento) na lista interna de peças do C211.
|
||||
- **Decisões desbloqueadas:**
|
||||
- Assessoria de comunicação: qual peça reforçar/reeditar e o que produzir em seguida (aberta e pouco compartilhada = rever gancho/capa; muito compartilhada = desdobrar).
|
||||
- Coordenação de comunicação: se a Central está valendo o esforço de catalogar/publicar — as peças estão sendo servidas e circulando?
|
||||
- Assessoria: qual canal entrega circulação (WhatsApp × link copiado) para concentrar o CTA onde a rede responde.
|
||||
- **Forma:** _adiada ao plano de implementação_ — restrições: contagens absolutas por peça (sem %; a base é anônima e desconhecida), nunca somadas num índice único de "uso", sem série/tendência no v1, sem KPI de vaidade (tempo, scroll, ranking).
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Eventos v1 (rótulos): "Abertura da peça" · "Download" · "Clique em compartilhar (WhatsApp)" · "Clique em compartilhar (link)" — WhatsApp e link contam **separados** (o canal é a pergunta de produto; somar esconde onde a circulação para).
|
||||
- "Uso" não vira um número só: os contadores aparecem lado a lado, por peça, sem total "usos" e sem média.
|
||||
- Nada de PII: sem IP persistido, sem cookie de identidade, sem user-agent completo; contagem anônima e agregada, nunca associável a pessoa; sem rastreio entre páginas ou entre peças.
|
||||
- Sem `Consent` novo — não há dado pessoal a consentir; qualquer identificação de visitante está fora de escopo (fail-closed de privacidade).
|
||||
- Superfície: contadores na lista interna de peças (C211) — sem dashboard novo, sem gráfico, sem rota de analytics.
|
||||
- Decisão própria vs existente: **A** — coleção mínima de eventos anônimos no próprio Payload, escrita por endpoint público fail-soft e agregada na lista do C211; sem serviço novo, sem cookie, atribuição por peça garantida. Alternativas avaliadas: **B** `@10x-media/analytics` (`0.1.0-beta.0`, adapter `native()` self-hosted, rollups no próprio Postgres, superfície no `/admin`) e **C** serviço externo self-hosted (Umami/Plausible — mais um serviço para operar e ainda exigiria eventos customizados). Reavaliar B/C só se a pergunta virar funil/pageview agregado.
|
||||
- Fail-soft: se a contagem falhar, a página pública não quebra nem atrasa; nada de bloquear abertura, download ou compartilhamento por telemetria.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/collections/` (eventos anônimos por peça), `src/utilities/` (agregação para os contadores), `src/components/campaign/shared/` (coluna/célula na `CampaignTable`) e uma rota pública de escrita em `src/app/(frontend)/api/` chamada pela página do S27.
|
||||
- **Precedente a olhar:** `src/app/(frontend)/api/revalidate/route.ts` e `src/app/(frontend)/api/social-feed/sync/route.ts` (rota JSON pública, allowlist, fail-soft), `src/components/campaign/shared/CampaignTable.tsx` + `CampaignListOmnibox` + `src/utilities/campaignListUrl.ts` (a superfície), `src/collections/CampaignVoteSummarySnapshot.ts` (contagem/rollup, não dashboard), `src/lib/campaignPageChrome.ts` + `src/components/campaign/shell/nav.ts` (chrome). O rate limit existente (`src/utilities/ai/rateLimit.ts`) é por `userId` e não serve para o público.
|
||||
- **Risco de acoplamento:** a contagem é acessória — o download/compartilhar e a página do S27 não podem depender dela; a peça é a do C211 (nada de segundo modelo de peça); nenhum identificador de visitante pode nascer daqui.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **S27** (dura) — sem a página pública não há evento a contar.
|
||||
- **C211** (dura) — a peça catalogada e a lista interna são a superfície dos contadores.
|
||||
- Soft: **S28** (busca por tema) é ortogonal; os contadores entram como colunas, como as demais.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Funil/pageview agregado do site (visitantes, origem, rejeição) — se pedido, reavaliar plugin (B) ou serviço self-hosted (C) em item próprio.
|
||||
- Heatmap, scroll depth, tempo na página e A/B de título/capa/CTA.
|
||||
- Rastrear visitante entre páginas ou entre peças (sessão, device, identidade anônima) — o modelo é evento solto.
|
||||
- Dashboard, rota de analytics, gráfico de série, exportação e alertas.
|
||||
- Meta Pixel (mídia paga na home/abaixo-assinado), analytics de terceiros no self-hosted e plataformas externas (Instagram/YouTube).
|
||||
- UTM/encurtador e medição fora do site — o que acontece depois do `wa.me` ou do link copiado não é medível aqui (já adiado em S4/C166).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Virar analytics de site.** Se alguém "só completar": pageview, origem, cookie, banner de consent. **Corte neste item:** só eventos de peça, anônimos e sem identidade.
|
||||
- **Medir a circulação depois do clique.** Se alguém "só completar": encurtador, redirect com token, "quantas pessoas receberam no WhatsApp". **Corte neste item:** o clique é o teto do que o site sabe; pós-clique é fora.
|
||||
- **Placar único de "uso" / dashboard de vaidade.** Se alguém "só completar": somar tudo num número e desenhar tendência. **Corte neste item:** contadores separados, lado a lado, sem índice composto.
|
||||
- **Adotar o plugin só pelo widget.** Se alguém "só completar": superfície no `/admin` (errada para a comunicação) e migrations de pacote beta. **Corte neste item:** A; reavaliação B/C só para funil/pageview.
|
||||
- **Guardar tudo para sempre.** Se alguém "só completar": eventos crus com IP/user-agent e retenção infinita — vira perfil de visitante. **Corte neste item:** mínimo anônimo, sem identificador persistido.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Própria, plugin existente ou serviço self-hosted?** **Opções:** A) coleção mínima própria | B) `@10x-media/analytics` (beta) | C) Umami/Plausible self-hosted. **Recomendação:** A — atribuição por peça garantida e superfície onde a assessoria já está, sem serviço novo para operar/backup; B está em beta e vive no `/admin`; C somaria um serviço e ainda exigiria eventos customizados. Reavaliar se a pergunta virar funil/pageview agregado. _(assumido — validar com produto)_
|
||||
- **O que conta como "uso"?** **Opções:** A) só download + compartilhar | B) os quatro contadores lado a lado | C) um número "usos" por peça. **Recomendação:** B — a pergunta é "esta peça circula?" e cada contador mostra onde ela para; C esconde a decisão; A descarta o sinal de que a peça foi encontrada. _(assumido — validar com produto)_
|
||||
- **Granularidade temporal?** **Opções:** A) acumulado por peça desde a publicação | B) janela de 7 dias | C) série diária. **Recomendação:** A no v1 — responde "reforçar/reeditar" e "vale a pena" sem virar dashboard; janela de 7 dias é o próximo passo registrado se a pergunta virar "o que está circulando agora". _(assumido — validar com produto)_
|
||||
- **Onde a assessoria vê?** **Opções:** A) coluna na lista de peças do C211, com recorte por canal no detalhe | B) bloco no topo da Central | C) rota de analytics. **Recomendação:** A — a lista já é a mesa de trabalho; B/C criam superfície nova e puxam para dashboard. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-analytics-ui-design.html` (+ assets em `central-conteudos-analytics-ui-design-assets/`)
|
||||
- Planos irmãos: `docs/plans/central-conteudos-publica.md` (S27 — a página que gera os eventos), `docs/plans/central-conteudos-ingestao.md` (C211 — a lista/superfície), `docs/plans/compartilhar-conteudos-home-whatsapp.md` (S4 — "nada é coletado neste item"), `docs/plans/c166-compartilhar-trecho-link.md` (C166 — "sem telemetria de compartilhamento")
|
||||
- Plugins avaliados (não adotados): `@10x-media/analytics` (10x-media/payload-plugins, `0.1.0-beta.0`, adapter `native()` self-hosted, superfície no `/admin`) e os serviços Umami/Plausible
|
||||
- Arquivos como pista, não contrato: `src/app/(frontend)/layout.tsx` (analytics Vercel inerte), `src/components/MetaPixel.tsx`, `src/app/(frontend)/api/revalidate/route.ts`, `src/app/(frontend)/api/social-feed/sync/route.ts`, `src/components/campaign/shared/CampaignTable.tsx`, `src/utilities/campaignListUrl.ts`, `src/collections/CampaignVoteSummarySnapshot.ts`, `src/utilities/documents.ts`
|
||||
- `AGENTS-campaign.md` / `AGENTS.md` — vertical Comunicação, LGPD fail-closed, naming e `overrideAccess: false`
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
5/5 — (1) fatia = um outcome verificável (contadores anônimos por peça na lista interna, sem serviço novo); (2) appetite ~1–1,5 dia cabe num encaixe de coluna; (3) persona, job e aceite legíveis sem stack; (4) direção no codebase é hipótese com precedentes abertos; (5) zero decisão dura de engenharia — schema, rota e agregação ficam para a implementação.
|
||||
283
docs/plans/central-conteudos-busca-semantica-ui-design.html
Normal file
283
docs/plans/central-conteudos-busca-semantica-ui-design.html
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign-site">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — S28 · Busca por tema</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme {
|
||||
--font-sans: Arimo, Arial, sans-serif;
|
||||
--color-kit-red: #e4102f;
|
||||
--color-kit-blue: #184e92;
|
||||
--color-kit-yellow: #ffeb00;
|
||||
}
|
||||
:root {
|
||||
--muted: #6c615e;
|
||||
--line: rgb(0 0 0/12%);
|
||||
--display: 'Exo 2', 'Arial Black', sans-serif;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Arimo, Arial, sans-serif;
|
||||
}
|
||||
.display {
|
||||
font-family: var(--display);
|
||||
}
|
||||
.board {
|
||||
background: #e7e3e0;
|
||||
background-image: radial-gradient(rgb(24 78 146/9%) 0.7px, transparent 0.7px);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 22px 58px rgb(39 25 22/12%);
|
||||
}
|
||||
.desktop {
|
||||
width: 1280px;
|
||||
}
|
||||
.phone {
|
||||
width: 390px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
.field {
|
||||
min-height: 46px;
|
||||
width: 100%;
|
||||
border: 1px solid rgb(0 0 0/18%);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
padding: 10px 13px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.seg {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
border-radius: 10px;
|
||||
background: #ebe9e9;
|
||||
padding: 4px;
|
||||
}
|
||||
.seg span {
|
||||
display: flex;
|
||||
min-height: 42px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.seg .active {
|
||||
background: white;
|
||||
color: #184e92;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0/12%);
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
min-height: 38px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: white;
|
||||
padding: 7px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.card {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: white;
|
||||
}
|
||||
.asset {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #e5e2df, #f7f5f3);
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
padding: 9px 13px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.primary {
|
||||
background: #ffe607;
|
||||
box-shadow: 0 4px 0 #cfb900;
|
||||
}
|
||||
.outline {
|
||||
border: 1.5px solid rgb(162 28 28/28%);
|
||||
background: #fff;
|
||||
color: #a21c1c;
|
||||
}
|
||||
.scene:after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: #cfc9c5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="board min-h-screen text-black antialiased">
|
||||
<main class="mx-auto max-w-[1480px] space-y-16 px-4 py-12 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<span class="rounded-full bg-kit-red px-3 py-1 text-xs font-bold text-white"
|
||||
>S28 · revisão do gate</span
|
||||
>
|
||||
<h1 class="display mt-4 text-4xl font-black">
|
||||
Procure pelo assunto. Entenda cada resultado.
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-[#6c615e]">
|
||||
Uma escolha clara de modo e uma explicação curta por peça. Nada de score, ruído ou
|
||||
resultado forçado.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENA 01</span>
|
||||
<h2>Desktop 1280 · modo e proveniência, sem chrome extra</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl bg-white">
|
||||
<header class="flex h-16 items-center justify-between bg-[#ae1603] px-10">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
class="h-9"
|
||||
alt="Jorge Solla"
|
||||
/><b class="text-sm text-white">Central de Conteúdos</b>
|
||||
</header>
|
||||
<div class="mx-auto max-w-5xl px-10 py-12">
|
||||
<h3 class="display text-3xl font-black">Encontre uma peça para pedir voto</h3>
|
||||
<div class="mt-6 grid grid-cols-[1fr_300px] gap-3 rounded-xl bg-[#fff8f2] p-4">
|
||||
<input class="field" value="fim da escala 6x1" />
|
||||
<div>
|
||||
<span class="mb-1 block text-xs font-bold text-[#6c615e]">Buscar por:</span>
|
||||
<div class="seg">
|
||||
<span>Termo exato</span><span class="active">✦ Por tema</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 flex gap-2">
|
||||
<span class="chip">Tipo⌄</span><span class="chip">Cidade⌄</span
|
||||
><span class="chip">Tema⌄</span><span class="chip">Mais filtros⌄</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="display mt-9 text-2xl font-black">Resultado por tema</h3>
|
||||
<article class="card mt-4 grid grid-cols-[300px_1fr]">
|
||||
<div class="asset">NEEDS ASSET · POSTER REAL</div>
|
||||
<div class="p-6">
|
||||
<span class="rounded-full bg-[#eef4fb] px-2 py-1 text-xs font-bold text-[#184e92]"
|
||||
>✦ Tema</span
|
||||
>
|
||||
<h4 class="mt-3 text-xl font-extrabold">Fim da escala 6×1 é saúde</h4>
|
||||
<p class="mt-1 text-sm text-[#6c615e]">Vídeo · Trabalho · Salvador</p>
|
||||
<div class="mt-5 rounded-lg bg-[#f5f5f4] p-4">
|
||||
<b class="text-xs">Por que apareceu</b>
|
||||
<p class="mt-1 text-sm leading-6">
|
||||
“…reduzir a jornada sem reduzir o salário protege a saúde de quem trabalha…”
|
||||
</p>
|
||||
</div>
|
||||
<a class="mt-5 inline-block text-sm font-extrabold text-[#a21c1c]">Abrir peça →</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENA 02</span>
|
||||
<h2>Mobile 390 · uma decisão por vez</h2>
|
||||
</div>
|
||||
<div class="phone frame mx-auto min-h-[780px] overflow-hidden bg-white">
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
class="h-7"
|
||||
alt="Jorge Solla"
|
||||
/><span class="text-white">☰</span>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<h3 class="display text-2xl font-black">Encontre uma peça para pedir voto</h3>
|
||||
<div class="mt-4 rounded-xl bg-[#fff8f2] p-3">
|
||||
<input class="field" value="fim da escala 6x1" /><span
|
||||
class="mt-3 block text-xs font-bold text-[#6c615e]"
|
||||
>Buscar por:</span
|
||||
>
|
||||
<div class="seg mt-1">
|
||||
<span>Termo exato</span><span class="active">✦ Por tema</span>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2 overflow-hidden">
|
||||
<span class="chip shrink-0">Tipo⌄</span><span class="chip shrink-0">Cidade⌄</span
|
||||
><span class="chip shrink-0">Tema⌄</span>
|
||||
</div>
|
||||
</div>
|
||||
<article class="card mt-5">
|
||||
<div class="asset aspect-video">NEEDS ASSET · POSTER REAL</div>
|
||||
<div class="p-4">
|
||||
<span class="rounded-full bg-[#eef4fb] px-2 py-1 text-xs font-bold text-[#184e92]"
|
||||
>✦ Tema</span
|
||||
>
|
||||
<h4 class="mt-3 text-lg font-extrabold">Fim da escala 6×1 é saúde</h4>
|
||||
<div class="mt-3 rounded-lg bg-[#f5f5f4] p-3">
|
||||
<b class="text-xs">Por que apareceu</b>
|
||||
<p class="mt-1 text-sm leading-5">
|
||||
“…reduzir a jornada sem reduzir o salário protege a saúde de quem trabalha…”
|
||||
</p>
|
||||
</div>
|
||||
<a class="mt-4 inline-block text-sm font-extrabold text-[#a21c1c]">Abrir peça →</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENAS 03–04</span>
|
||||
<h2>Estados críticos · vazio honesto e degradação</h2>
|
||||
</div>
|
||||
<div class="grid gap-8 lg:grid-cols-[1fr_390px]">
|
||||
<article class="frame rounded-xl bg-white p-10 text-center">
|
||||
<div class="mx-auto max-w-lg">
|
||||
<h3 class="display text-2xl font-black">Nenhuma peça combina bem com esse tema</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-[#6c615e]">
|
||||
Não mostramos um resultado fraco só para preencher a tela.
|
||||
</p>
|
||||
<div class="mt-6 flex justify-center gap-2">
|
||||
<button class="btn primary">Reformular busca</button
|
||||
><button class="btn outline">Usar termo exato</button
|
||||
><button class="btn outline">Limpar filtros</button>
|
||||
</div>
|
||||
<p class="mt-6 text-xs text-[#6c615e]">Vazio · desktop 1280</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="phone frame overflow-hidden bg-white p-4">
|
||||
<div
|
||||
class="rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm leading-5 text-amber-900"
|
||||
>
|
||||
<b>Busca por tema indisponível agora.</b><br />A busca exata continua funcionando.
|
||||
</div>
|
||||
<input class="field mt-4" value="fim da escala 6x1" /><span
|
||||
class="mt-3 block text-xs font-bold text-[#6c615e]"
|
||||
>Buscar por:</span
|
||||
>
|
||||
<div class="seg mt-1">
|
||||
<span class="active !text-black">Termo exato</span
|
||||
><span class="text-[#8b6c1a]">⚠ Por tema</span>
|
||||
</div>
|
||||
<button class="btn primary mt-6 w-full">Ver resultados exatos</button>
|
||||
<p class="mt-5 text-center text-xs text-[#6c615e]">
|
||||
Degradação · mobile 390 · nenhum resultado fantasma.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
116
docs/plans/central-conteudos-busca-semantica.md
Normal file
116
docs/plans/central-conteudos-busca-semantica.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# S28 — Busca por tema na Central de Conteúdos pública
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-22
|
||||
Issue: #1257
|
||||
Priority: P2
|
||||
Impeccable: B — encaixe no campo de busca e na lista da Central (S27)
|
||||
Design UI: docs/plans/central-conteudos-busca-semantica-ui-design.html
|
||||
Appetite: ~1–2 dias eng; um outcome verificável — buscar por sentido na Central pública devolve peças publicadas com proveniência e degradação honesta
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A Central de Conteúdos pública (S27) deixa o eleitor achar as peças por termo exato — mas o eleitor pensa por assunto, não por palavra: "fim da escala 6x1" e "saúde no subúrbio" não aparecem literalmente em toda peça que trata disso. Sem o modo por tema, a Central só confirma o que o eleitor já sabia procurar; não o ajuda a descobrir. Este item é a fatia de busca-por-tema da Central: a peça certa aparece pelo sentido, com honestidade sobre por que apareceu.
|
||||
|
||||
O precedente é interno e já está em produção: C192 (acervo de falas) provou o mecanismo — expansão do tema por LLM + busca lexical, modo visível, selo por resultado, vazio honesto, degradação com aviso. A diferença crítica aqui é a rota ser PÚBLICA e sem login: não existe limitador anônimo hoje e a expansão não é cacheada — custo aberto é o risco novo, não o mecanismo. P2: a Central já funciona por termo exato; o modo por tema cumpre a intenção do dono, mas não bloqueia o go-live.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** eleitor sem login, majoritariamente no celular, chegando de link de WhatsApp/Instagram ou de busca, querendo ver o que o Solla fez/falou sobre um assunto. Secundária: assessoria que procura peça para reenviar.
|
||||
- **Job principal:** achar material sobre um tema pelo sentido, mesmo quando a peça não usa as palavras da busca.
|
||||
- **Fluxo desejado:**
|
||||
1. Digita o tema no campo de busca da Central.
|
||||
2. Escolhe como buscar: termo exato (como hoje) ou por tema — a escolha é visível; "Termo exato" é o padrão.
|
||||
3. Recebe as peças; cada resultado por tema vem marcado ("Tema") com um indício curto e real de por que apareceu (trecho da descrição/transcrição).
|
||||
4. Abre a peça (fluxo público atual da Central) e lê/compartilha.
|
||||
5. Se nada servir, vazio honesto com saída (reformular, usar termo exato, limpar filtros) — nunca resultado "parecido" só para preencher a tela.
|
||||
- **Anti-goals de produto:** não vira gerador gratuito de texto nem buscador geral do site; não expõe o acervo interno de falas, PII ou fala de terceiro fora de peça publicada; não troca a busca exata; não inventa resultado nem mostra score numérico.
|
||||
|
||||
### Esboço de fluxo (B)
|
||||
|
||||
```text
|
||||
[Central pública] → digita tema → escolhe "Por tema" (modo visível; "Termo exato" é o padrão)
|
||||
→ lista de peças publicadas; card por tema: selo "Tema" + "por que apareceu" (trecho real)
|
||||
→ abre a peça → lê/compartilha
|
||||
→ vazio honesto: reformular | usar termo exato | limpar filtros
|
||||
→ tema indisponível/limite atingido: busca exata continua + aviso discreto
|
||||
```
|
||||
|
||||
### Design UI (B)
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-busca-semantica-ui-design.html`
|
||||
- Por que B e não A: exige affordance nova (escolher o modo) e estados novos (selo/proveniência, vazio honesto, aviso de degradação) dentro do campo de busca e da lista que o S27 já entrega — sem rota nova. O HTML hi-fi é fonte de verdade do port.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Buscar um tema sem as palavras exatas (ex.: "fim da escala 6x1") devolve peças publicadas que tratam do tema, sobre o corpus da Central.
|
||||
- Todo resultado deixa claro por que apareceu: "Tema" vs "Termo exato", com indício curto e real (trecho da descrição/transcrição) — sem score numérico nem "parecido" enganoso.
|
||||
- "Termo exato" continua sendo o padrão e o comportamento de hoje não regride.
|
||||
- Sem resultado quando nada serve: vazio honesto com saída (reformular/trocar modo/limpar filtros); nunca resultado inventado.
|
||||
- Se o mecanismo por tema falhar/estiver indisponível, degrada para a busca exata com aviso discreto — nunca erro na cara nem resultado fantasma.
|
||||
- Filtros da Central (cidade, região, categoria, instituição, etc.) seguem valendo junto com a busca, nos dois modos.
|
||||
- **Guardrails:** a busca cobre SÓ peças publicadas da Central; nada do acervo interno, nada de PII, nada de fala de terceiro fora de peça publicada. A rota é pública: a busca por tema não pode virar custo aberto — precisa de limite de uso (mecanismo a critério da implementação) e as chamadas não podem ser disparadas por bot/crawler; sob abuso, o modo degrada e ninguém usa a Central como gerador gratuito de texto.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** N/A — resultado de busca não é métrica nem agregado; nenhuma decisão numérica é desbloqueada.
|
||||
- **Decisões desbloqueadas:** N/A — a escolha do eleitor ("qual peça abrir/compartilhar") é qualitativa.
|
||||
- **Forma:** N/A — sem superfície de dados; restrição de produto: o sinal do resultado é explicação qualitativa (trecho real), nunca score de confiança.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Modo visível no campo de busca: "Buscar por: Termo exato | Por tema"; "Termo exato" é o padrão.
|
||||
- Resultado por tema marcado ("Tema") com indício curto e real de por que apareceu (trecho da descrição/transcrição); nunca score numérico nem "parecido" enganoso.
|
||||
- Vazio honesto com saída: reformular · usar termo exato · limpar filtros.
|
||||
- Degradação: sem chave, timeout ou limite de uso, a busca exata continua e um aviso discreto explica — nunca erro na cara nem resultado fantasma.
|
||||
- Guardrail de custo/abuso: limite de uso obrigatório (mecanismo a decidir na implementação); chamada de tema não pode ser disparada por bot/crawler; sob abuso, o modo degrada.
|
||||
- Corpus: só peças publicadas da Central; nada do acervo interno, nada de PII.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota pública da Central em `src/app/(frontend)/` (S27); componentes da Central (campo de busca/lista/card); `src/utilities/ai/` (expansão + limitador anônimo novo); `src/lib/` (normalização/sanitização); loader da coleção de peças do C211.
|
||||
- **Reuso do C192 (pista, não contrato):** `src/utilities/ai/expandSpeechSearchTheme.ts` (contrato `null` em falha, timeout), `src/lib/speechThemeTerms.ts` (sanitização dos termos do LLM), `src/utilities/speech/speechListFilters.ts` (OR textual por termo), `src/lib/speechSearch.ts` (`normalizeForSearch` + espelho), `src/utilities/speech/speechListUrl.ts` (`mode=tema` só com `q`); UI para inspirar: `SpeechAcervoFilters.tsx`, `SpeechResultCard.tsx` (selo "Tema" + "Por que apareceu"), `SpeechThemeFallbackNotice.tsx`.
|
||||
- **A diferença da rota pública (o que o executor não pode copiar):** o rate limit existente (`src/utilities/ai/rateLimit.ts`) é chaveado por `userId` e só roda atrás de auth — não serve aqui; a expansão não é cacheada; `DEEPSEEK_API_KEY` é global. Corpus provavelmente um `searchText` normalizado por peça (padrão `Speech.searchText`/`Recording.searchText`), com índice trigram como precedente (`src/migrations/20260913_001200_add_speech_segment_trgm_index.ts`).
|
||||
- **Risco de acoplamento:** não expor acervo interno nem PII por descuido de `depth`/relação; não quebrar a busca exata nem o contrato de URL/filtros do S27; rota nova em `(frontend)` acorda os specs públicos no manifesto e2e (`scripts/lib/e2e-affected-manifest.mjs`).
|
||||
|
||||
## Dependências
|
||||
|
||||
- S27 (dura) — a página pública da Central e seu campo de busca/lista; sem ela não há onde encaixar o modo.
|
||||
- C211 (dura) — as peças catalogadas (título, descrição, temas, transcrição); sem descrição/transcrição o "por que apareceu" não tem trecho real.
|
||||
- C192 (soft) — mecanismo entregue e em produção; pista de reuso, não pré-requisito.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Busca por tema no acervo interno de falas — já é C192.
|
||||
- Embeddings/pgvector e infra vetorial.
|
||||
- Curadoria manual de sinônimos/ontologia.
|
||||
- Busca por voz.
|
||||
- Analytics de busca (termos que falham, cliques) — outro item se houver demanda.
|
||||
- Busca global no site inteiro ou em outros corpora (posts, notícias).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Índice vetorial/infra nova.** Se alguém "só completar": pipeline de embedding, backfill, storage vetorial, sync. **Corte neste item:** reusar o caminho lexical do C192; cobertura/mecanismo são decisão da implementação.
|
||||
- **"Sempre achar algo".** Se alguém "só completar": similaridade fraca enchendo a lista. **Corte:** vazio honesto; nunca "parecido" enganoso.
|
||||
- **Curador de sinônimos/ontologia.** **Corte:** fora; vazio honesto e reformulação cobrem.
|
||||
- **Acervo interno como corpus "porque é mais rico".** **Corte:** só peças publicadas da Central — anti-goal do C192 e regra de PII.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Modo explícito ou automático?** **Opções:** A) automático no mesmo campo | B) modo visível ("Termo exato | Por tema"), exato como padrão | C) ação "buscar por tema" só quando pedida. **Recomendação:** B — o eleitor precisa saber o que pediu e por que o resultado apareceu; o padrão exato preserva o que já funciona. _(assumido — validar com produto)_
|
||||
- **Como limitar custo/abuso numa página sem login?** **Opções:** A) limitador anônimo (IP/sessão) + degradação sob abuso | B) cachear a expansão e limitar por sessão | C) desafio de interação humana antes do modo. **Recomendação:** A + cache da expansão quando barato — degradar em vez de errar; o mecanismo exato fica na implementação. _(assumido — validar com produto)_
|
||||
- **Como o resultado sinaliza "por tema"?** **Opções:** A) selo "Tema" por resultado + trecho real | B) aviso único no topo | C) só o modo ativo no campo. **Recomendação:** A — proveniência por peça sustenta a confiança; o modo no campo complementa. _(assumido — validar com produto)_
|
||||
- **E se o mecanismo falhar/limite estourar?** **Opções:** A) degrada para termo exato com aviso discreto | B) erro pedindo nova tentativa | C) esconde o modo. **Recomendação:** A — nunca erro na cara nem resultado fantasma; o aviso mantém a honestidade. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-busca-semantica-ui-design.html` (+ assets em `central-conteudos-busca-semantica-ui-design-assets/`)
|
||||
- Precedente C192: `docs/plans/acervo-busca-semantica.md` e `docs/plans/acervo-busca-semantica-impl.md`
|
||||
- Planos irmãos: `docs/plans/central-conteudos-publica.md` (S27) · `docs/plans/central-conteudos-ingestao.md` (C211)
|
||||
- Arquivos-pista: `src/utilities/ai/expandSpeechSearchTheme.ts`, `src/lib/speechThemeTerms.ts`, `src/utilities/ai/rateLimit.ts`, `src/utilities/speech/speechListFilters.ts`, `src/utilities/speech/speechListUrl.ts`, `src/lib/speechSearch.ts`, `src/components/campaign/speech/SpeechResultCard.tsx`, `src/components/campaign/speech/SpeechThemeFallbackNotice.tsx`, `scripts/lib/e2e-affected-manifest.mjs`
|
||||
- `AGENTS-public.md` — convenções da rota pública (visibilidade fail-closed, cache/revalidação)
|
||||
|
||||
## Self-score
|
||||
|
||||
5/5 — um outcome verificável (achar por sentido com proveniência e degradação honesta) dentro do appetite; persona/job/aceite sem jargão; direção no codebase é hipótese com o reuso do C192 e a diferença da rota pública explícita; nenhuma decisão dura de engenharia (limite de uso e forma do corpus ficam na implementação).
|
||||
536
docs/plans/central-conteudos-ingestao-ui-design.html
Normal file
536
docs/plans/central-conteudos-ingestao-ui-design.html
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C211 · Central interna</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-sidebar: var(--sidebar);
|
||||
}
|
||||
[data-theme='campaign'] {
|
||||
--background: #fff;
|
||||
--foreground: #1c1917;
|
||||
--primary: #c51414;
|
||||
--muted: #f4f4f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--border: rgb(28 25 23/12%);
|
||||
--sidebar: #fafaf9;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.board {
|
||||
background: #efeeec;
|
||||
background-image: radial-gradient(rgb(28 25 23/8%) 0.7px, transparent 0.7px);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 18px 52px rgb(28 25 23/10%);
|
||||
}
|
||||
.desktop {
|
||||
width: 1280px;
|
||||
}
|
||||
.phone {
|
||||
width: 390px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.primary {
|
||||
background: #c51414;
|
||||
color: #fff;
|
||||
}
|
||||
.outline {
|
||||
border: 1px solid #d6d3d1;
|
||||
background: #fff;
|
||||
}
|
||||
.field {
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
border: 1px solid #d6d3d1;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
padding: 9px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.ready {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
.work {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
.fail {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
.draft {
|
||||
background: #f4f4f5;
|
||||
color: #57534e;
|
||||
}
|
||||
.live {
|
||||
background: #fff1f2;
|
||||
color: #9f1239;
|
||||
}
|
||||
.asset {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #e7e5e4, #f5f5f4);
|
||||
color: #78716c;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.scene:after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: #d6d3d1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="board min-h-screen text-foreground antialiased">
|
||||
<main class="mx-auto max-w-[1480px] space-y-16 px-4 py-12 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<span class="badge bg-primary text-white">C211 · revisão do gate</span>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">Receber, revisar, publicar</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
A mesa foi reduzida ao essencial: estado da peça, estado editorial e a próxima ação.
|
||||
Arquivo e link entram no mesmo fluxo de catalogação.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 01</span>
|
||||
<h2>Desktop 1280 · lista compacta e respirada</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl border bg-white">
|
||||
<div class="flex min-h-[720px]">
|
||||
<aside class="w-56 shrink-0 border-r bg-sidebar p-3">
|
||||
<div class="flex h-12 items-center gap-2 px-2">
|
||||
<div
|
||||
class="grid size-8 place-items-center rounded-lg bg-primary font-bold text-white"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
<div>
|
||||
<b class="text-sm">Solla</b>
|
||||
<p class="text-xs text-muted-foreground">Campanha</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-md bg-muted px-3 py-2 text-sm font-semibold">
|
||||
Comunicação
|
||||
</div>
|
||||
<div class="ml-4 border-l pl-3">
|
||||
<div
|
||||
class="mt-1 rounded-md bg-white px-3 py-2 text-sm font-semibold text-primary ring-1 ring-border"
|
||||
>
|
||||
Conteúdos
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="min-w-0 flex-1 p-8">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold">Conteúdos</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Peças que alimentam a Central pública.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn outline">Adicionar por link</button
|
||||
><button class="btn primary">Enviar peças</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-7 flex gap-2">
|
||||
<input class="field" placeholder="Buscar por título, tema ou cidade…" /><button
|
||||
class="btn outline"
|
||||
>
|
||||
Filtros
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-5 overflow-hidden rounded-xl border">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="bg-stone-50 text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th class="p-4">Peça</th>
|
||||
<th>Processamento</th>
|
||||
<th>Publicação</th>
|
||||
<th class="pr-4 text-right">Próxima ação</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<div class="flex gap-3">
|
||||
<div class="asset h-14 w-20 rounded-md">NEEDS ASSET</div>
|
||||
<div>
|
||||
<b>Fim da escala 6×1 é saúde</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Vídeo · Salvador · 02:14
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge ready">Pronto</span></td>
|
||||
<td><span class="badge live">Publicado</span></td>
|
||||
<td class="pr-4 text-right"><button class="btn outline">Abrir</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<b>Visita ao posto de saúde</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Vídeo enviado</p>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge work">Processando</span>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Transcrevendo…</p>
|
||||
</td>
|
||||
<td><span class="badge draft">Rascunho</span></td>
|
||||
<td class="pr-4 text-right"><button class="btn outline">Abrir</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4">
|
||||
<b>Mensagem para lideranças</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Áudio preservado</p>
|
||||
</td>
|
||||
<td><span class="badge fail">Falhou</span></td>
|
||||
<td><span class="badge draft">Rascunho</span></td>
|
||||
<td class="pr-4 text-right">
|
||||
<button class="btn outline">Reprocessar</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">
|
||||
Rascunhos não aparecem no site. Uma falha não interrompe as outras peças.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 02</span>
|
||||
<h2>Desktop 1280 · lote com progresso por arquivo</h2>
|
||||
</div>
|
||||
<div
|
||||
class="desktop frame relative mx-auto min-h-[680px] overflow-hidden rounded-xl border bg-stone-200"
|
||||
>
|
||||
<div class="absolute inset-0 bg-black/30"></div>
|
||||
<div class="relative mx-auto mt-14 w-[590px] rounded-xl bg-white p-6 shadow-2xl">
|
||||
<h3 class="text-lg font-semibold">Enviar peças</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Cada arquivo vira um rascunho independente.
|
||||
</p>
|
||||
<div
|
||||
class="mt-5 grid min-h-24 place-items-center rounded-lg border border-dashed bg-stone-50 text-center"
|
||||
>
|
||||
<div>
|
||||
<b class="text-sm">Selecionar arquivos</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Vídeo, foto, áudio, texto ou card</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 space-y-3">
|
||||
<div class="rounded-lg border p-3">
|
||||
<div class="flex justify-between text-sm"><b>fala-sus.mp4</b><b>68%</b></div>
|
||||
<div class="mt-2 h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div class="h-full w-[68%] bg-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg border p-3">
|
||||
<b class="text-sm">card-feira.png</b><span class="badge ready">Enviado</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 p-3"
|
||||
>
|
||||
<div>
|
||||
<b class="text-sm">audio-grupo.wav</b>
|
||||
<p class="mt-1 text-xs text-red-800">Falhou; os outros continuam.</p>
|
||||
</div>
|
||||
<button class="btn outline">Tentar de novo</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<button class="btn primary">Concluir lote</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 03</span>
|
||||
<h2>Desktop 1280 · link com mídia extraída pelo caminho oficial</h2>
|
||||
</div>
|
||||
<div
|
||||
class="desktop frame mx-auto grid min-h-[650px] grid-cols-[410px_1fr] overflow-hidden rounded-xl border bg-white"
|
||||
>
|
||||
<div class="border-r bg-stone-50 p-8">
|
||||
<h3 class="text-xl font-semibold">Adicionar por link</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Instagram ou YouTube. Conteúdo próprio é extraído somente quando a plataforma oferece
|
||||
caminho oficial.
|
||||
</p>
|
||||
<label class="label mt-6">Link da publicação</label
|
||||
><input class="field" value="https://www.instagram.com/p/…" /><button
|
||||
class="btn primary mt-4 w-full"
|
||||
>
|
||||
Adicionar e catalogar
|
||||
</button>
|
||||
<div
|
||||
class="mt-5 rounded-lg border bg-white p-4 text-xs leading-5 text-muted-foreground"
|
||||
>
|
||||
<b class="text-foreground">Sem scraping.</b><br />Se não houver acesso oficial à
|
||||
mídia, a peça continua pelo link e o arquivo original pode ser anexado.
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<h3 class="text-lg font-semibold">O que acontece depois</h3>
|
||||
<div class="mt-6 space-y-4">
|
||||
<div class="flex gap-4 rounded-xl border p-4">
|
||||
<span
|
||||
class="grid size-9 shrink-0 place-items-center rounded-full bg-primary text-sm font-bold text-white"
|
||||
>1</span
|
||||
>
|
||||
<div>
|
||||
<b class="text-sm">Mídia encontrada na conta oficial</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
O arquivo entra na peça. Origem e link permanecem visíveis.
|
||||
</p>
|
||||
</div>
|
||||
<span class="badge ready ml-auto h-fit">Extraída</span>
|
||||
</div>
|
||||
<div class="flex gap-4 rounded-xl border p-4">
|
||||
<span
|
||||
class="grid size-9 shrink-0 place-items-center rounded-full bg-primary text-sm font-bold text-white"
|
||||
>2</span
|
||||
>
|
||||
<div>
|
||||
<b class="text-sm">Mesmo processamento do upload</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Transcrição, título, descrição, temas e territórios são preparados para revisão.
|
||||
</p>
|
||||
</div>
|
||||
<span class="badge work ml-auto h-fit">Processando</span>
|
||||
</div>
|
||||
<div class="flex gap-4 rounded-xl border p-4">
|
||||
<span
|
||||
class="grid size-9 shrink-0 place-items-center rounded-full bg-muted text-sm font-bold"
|
||||
>↳</span
|
||||
>
|
||||
<div>
|
||||
<b class="text-sm">Quando não há caminho oficial</b>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Peça-link criada. A assessoria pode anexar o arquivo original; mídia de terceiro
|
||||
não é baixada.
|
||||
</p>
|
||||
<button class="btn outline mt-3">Anexar arquivo original</button>
|
||||
</div>
|
||||
<span class="badge draft ml-auto h-fit">Peça-link</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENA 04</span>
|
||||
<h2>Desktop 1280 · ficha editável e um próximo passo</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl border bg-white">
|
||||
<div class="flex items-center justify-between border-b px-8 py-5">
|
||||
<div>
|
||||
<a class="text-xs font-semibold text-primary">← Conteúdos</a>
|
||||
<h3 class="mt-2 text-xl font-semibold">Fim da escala 6×1 é saúde</h3>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<span class="badge ready">Pronto</span><span class="badge draft">Rascunho</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn primary">Publicar</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-[300px_1fr] gap-8 p-8">
|
||||
<div>
|
||||
<div class="asset aspect-video rounded-xl">NEEDS ASSET · PREVIEW REAL</div>
|
||||
<dl class="mt-4 space-y-2 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-muted-foreground">Origem</dt>
|
||||
<dd>Instagram · mídia extraída</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-muted-foreground">Duração</dt>
|
||||
<dd>02:14</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-5 rounded-lg bg-muted p-3">
|
||||
<p class="text-[11px] font-semibold text-muted-foreground">QUANDO PUBLICADO</p>
|
||||
<button class="btn outline mt-2 w-full">Despublicar</button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<label class="label">Título</label
|
||||
><input class="field" value="Fim da escala 6×1 é saúde" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="label">Descrição</label
|
||||
><textarea class="field min-h-20">
|
||||
Jorge Solla explica por que reduzir a jornada protege a saúde de quem trabalha.</textarea
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Tipo</label
|
||||
><select class="field">
|
||||
<option>Vídeo</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Data</label><input class="field" placeholder="Data da peça" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Temas</label><input class="field" value="Trabalho; Saúde" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">Cidade · região</label
|
||||
><input class="field" value="Salvador · Metropolitana" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="label">Instituição</label
|
||||
><input class="field" value="Câmara dos Deputados" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="label">Transcrição</label
|
||||
><textarea class="field min-h-24">
|
||||
A redução da jornada sem redução de salário é também uma pauta de saúde pública…</textarea
|
||||
>
|
||||
</div>
|
||||
<div class="col-span-2 flex justify-end">
|
||||
<button class="btn outline">Salvar alterações</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-stone-600">
|
||||
<span>CENAS 05–06</span>
|
||||
<h2>Mobile 390 · lista e link em processamento</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-8">
|
||||
<div class="phone frame min-h-[800px] overflow-hidden border bg-white">
|
||||
<div class="flex h-14 items-center justify-between bg-primary px-4 text-white">
|
||||
<b>Conteúdos</b
|
||||
><button class="rounded-md border border-white/40 px-3 py-2 text-xs font-semibold">
|
||||
+ Link
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<button class="btn primary w-full">Enviar peças</button
|
||||
><input class="field mt-3" placeholder="Buscar conteúdos…" />
|
||||
<div class="mt-4 space-y-3">
|
||||
<article class="rounded-xl border p-4">
|
||||
<div class="flex justify-between gap-3">
|
||||
<b class="text-sm">Fim da escala 6×1 é saúde</b
|
||||
><span class="badge ready h-fit">Pronto</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Vídeo · Publicado</p>
|
||||
<button class="btn outline mt-3 w-full">Abrir</button>
|
||||
</article>
|
||||
<article class="rounded-xl border p-4">
|
||||
<div class="flex justify-between gap-3">
|
||||
<b class="text-sm">Visita ao posto</b
|
||||
><span class="badge work h-fit">Processando</span>
|
||||
</div>
|
||||
<div class="mt-3 h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div class="h-full w-1/2 bg-primary"></div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-muted-foreground">Transcrevendo e catalogando…</p>
|
||||
</article>
|
||||
<article class="rounded-xl border p-4">
|
||||
<div class="flex justify-between gap-3">
|
||||
<b class="text-sm">Mensagem para lideranças</b
|
||||
><span class="badge fail h-fit">Falhou</span>
|
||||
</div>
|
||||
<button class="btn outline mt-3 w-full">Reprocessar</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="phone frame min-h-[800px] overflow-hidden border bg-white">
|
||||
<div class="flex h-14 items-center gap-3 border-b px-4">
|
||||
<span>←</span><b>Peça adicionada por link</b>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="asset aspect-video rounded-xl">NEEDS ASSET · POSTER EXTRAÍDO</div>
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
<span class="badge work">Processando</span
|
||||
><span class="text-xs text-muted-foreground">Instagram · conta oficial</span>
|
||||
</div>
|
||||
<h3 class="mt-3 text-lg font-semibold">Preparando a peça</h3>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span>Arquivo extraído</span><b>Concluído</b>
|
||||
</div>
|
||||
<div class="mt-2 h-2 rounded-full bg-green-600"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span>Transcrição e catalogação</span><b>Em andamento</b>
|
||||
</div>
|
||||
<div class="mt-2 h-2 overflow-hidden rounded-full bg-muted">
|
||||
<div class="h-full w-1/2 bg-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 rounded-lg bg-muted p-3 text-xs leading-5 text-muted-foreground">
|
||||
A peça nasce como rascunho. Revise título, descrição e transcrição antes de
|
||||
publicar.
|
||||
</div>
|
||||
<button class="btn outline mt-5 w-full">Voltar para a lista</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
123
docs/plans/central-conteudos-ingestao.md
Normal file
123
docs/plans/central-conteudos-ingestao.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# C211 — Central de Conteúdos — enviar, transcrever e catalogar as peças de campanha (interno)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-22
|
||||
Issue: #1254
|
||||
Priority: P1
|
||||
Impeccable: C — fluxo novo na vertical `/campanha/comunicacao`
|
||||
Design UI: docs/plans/central-conteudos-ingestao-ui-design.html
|
||||
Appetite: ~2–3 dias eng; um outcome verificável — a assessoria sobe um lote de peças (ou cola um link) e cada peça sai catalogada, transcriada e com estado de publicação
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A campanha "Peça voto pra Solla 1313" vai ter uma página pública com todo o material — Reels, textos, fotos, jingles, cards. Mas o material de campanha vive hoje em pastas de celular, grupos e drives: ninguém sabe o que já existe, nada é encontrável por assunto e a peça vira garimpo manual na hora de publicar.
|
||||
|
||||
Este item é o lado interno: a assessoria envia peças em lote (ou cola um link do Instagram/YouTube), e cada peça é transcrita, catalogada com o máximo de detalhes e depois publicada/despublicada com um gesto. Peça que entra por link tem a mídia extraída e catalogada quando a plataforma oferecer caminho oficial; onde não houver, ela circula pelo link e a assessoria pode anexar o arquivo original. A página pública é o S27 e a busca semântica pública é o S28 — este item entrega o material catalogado que os dois vão consumir.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`, "Assessor de Comunicação"), na mesa, recebendo muitas peças por dia de gente diferente; `coordinator`/`candidate` também usam. `advisor`/`leader` não veem nada (fail-closed).
|
||||
- **Job principal:** subir o material da campanha de uma vez e deixá-lo catalogado o bastante para a peça certa ser encontrada e publicada.
|
||||
- **Fluxo desejado:** abre `/campanha/comunicacao/conteudos` → "Enviar peças" (vários arquivos de uma vez) ou "Adicionar por link" (Instagram/YouTube; a mídia é extraída quando houver caminho oficial) → cada peça tem estado visível → ao ficar "Pronto", a peça já vem com título, descrição, tipo, temas, cidade, região, instituição, data, duração e transcrição/texto → a assessoria revisa e edita o que quiser → "Publicar" quando for a hora; "Despublicar" tira da Central na hora, sem apagar o arquivo → se "Falhou", "Reprocessar".
|
||||
- **Anti-goals de produto:** não é editor de vídeo nem estúdio de card; não duplica peça que já tem dono no site (jingle/artigo); não espelha o Instagram sozinho; não é dashboard de vaidade; não apaga arquivo em silêncio ao despublicar.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[/campanha/comunicacao/conteudos]
|
||||
→ "Enviar peças": N arquivos de uma vez (ou "Adicionar por link" Instagram/YouTube
|
||||
— a mídia é extraída e catalogada quando houver caminho oficial; senão, peça-link)
|
||||
→ uma peça por arquivo; estados por peça (Processando → Pronto | Falhou)
|
||||
→ Pronto: catalogação automática preenche os campos; assessoria revisa e edita tudo
|
||||
→ "Publicar" (entra na Central pública) | "Rascunho" (fora)
|
||||
→ "Despublicar" tira da Central na hora e preserva o arquivo
|
||||
→ Falhou: "Reprocessar" (uma peça não derruba as outras)
|
||||
[outcome: acervo de peças de campanha catalogado e publicável, sem garimpo]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-ingestao-ui-design.html` — lista com estados, envio em lote, adicionar por link, ficha da peça (catalogação editável) e publicação, na linguagem visual do `/campanha`.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- `communicator` — e `coordinator`/`candidate` — opera a Central; `advisor`/`leader` negados (fail-closed).
|
||||
- Envio em lote: vários arquivos numa ação → uma peça por arquivo; falha de um não derruba os outros e cada peça tem estado próprio ("Processando" | "Pronto" | "Falhou").
|
||||
- Vídeo e Áudio saem com transcrição; Texto sai com o texto extraído; Foto e Card não exigem transcrição.
|
||||
- Catalogação automática é ponto de partida: título, descrição, tipo, temas, cidade, região, instituição, data, duração e transcrição/texto sempre editáveis pela assessoria.
|
||||
- Adicionar por link (Instagram/YouTube) cria a peça e, quando a plataforma oferecer caminho oficial de acesso à mídia (ex.: API do Instagram para a própria conta), o arquivo é extraído e passa pelo mesmo pipeline de transcrição/catalogação; sem caminho oficial, a peça circula pelo link e a assessoria pode anexar o arquivo original.
|
||||
- "Rascunho" fica fora da Central pública; "Publicado" aparece; despublicar tira na hora e preserva o arquivo; "Falhou" permite "Reprocessar".
|
||||
- **Guardrails:** extração só por caminhos oficiais, nunca scraping/contorno (a varredura automática é investigada no C212); sem PII de eleitor; sem disparo em massa; acervo de falas e cortes só entram se a assessoria adicionar manualmente como peça; sem segundo cadastro de pessoa.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Sim, só derivado — a catalogação (metadados + transcrição/texto) é dado estruturado consumido por S27/S28 e, depois, pelo C213; este item não apresenta agregado nem KPI.
|
||||
- **Decisões desbloqueadas:** N/A — a escolha da assessoria é editorial ("qual peça publicar/despublicar"), não numérica.
|
||||
- **Forma:** N/A — restrição de produto: sem contadores de vaidade (views/curtidas) e sem score de busca exposto na tela.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Rota interna:** `/campanha/comunicacao/conteudos`; sub-item "Conteúdos" na nav de Comunicação.
|
||||
- **Papel:** `communicator` = "Assessor de Comunicação"; `coordinator`/`candidate` também; `advisor`/`leader` negados (gate de comunicação, fail-closed).
|
||||
- **Tipos de peça:** "Vídeo" | "Foto" | "Texto" | "Áudio" | "Card".
|
||||
- **Publicação:** "Rascunho" | "Publicado" — despublicar tira da Central na hora, arquivo preservado.
|
||||
- **Processamento:** "Processando" | "Pronto" | "Falhou" (com "Reprocessar").
|
||||
- **Envio em lote:** vários arquivos numa vez → uma peça por arquivo; falha de um não derruba os outros.
|
||||
- **Catalogação automática (revisável):** título, descrição, tipo, temas, cidade, região, instituição, data, duração, transcrição/texto.
|
||||
- **Adicionar por link:** Instagram/YouTube (colar link) — a peça entra com a mídia extraída e catalogada quando houver caminho oficial; sem caminho oficial, circula pelo link e o arquivo pode ser anexado pela assessoria. Mídia de terceiro nunca é baixada.
|
||||
- **Varredura automática do Instagram:** investigação aprovada no gate → **C212** (viabilidade com API oficial × scraping; o C211 aplica o mecanismo que ela recomendar). Scraping não é mecanismo — nem no C211 nem no C212.
|
||||
- **Acervo de falas e cortes:** fora por produto; entram só se a assessoria adicionar manualmente como peça.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/app/(campaign)/campanha/(app)/comunicacao/conteudos/`, `src/components/campaign/<dominio>/`, `src/utilities/<dominio>/`, `src/collections/`, `src/lib/campaignPaths.ts` + `src/components/campaign/shell/nav.ts`.
|
||||
- **Precedente a olhar:** C199 (`src/collections/Recording*.ts`, `src/utilities/recordings/recordingUpload.ts|recordingJob.ts|recordingScheduler.ts`, rota `.../acervo/gravacoes/enviar/route.ts`, estados em `src/lib/recording.ts`, retry em `src/app/(campaign)/campanha/actions/recording.ts`); catalogação (`speechClassifier.ts`, `speechCutMetadata.ts`, taxonomias em `src/lib/speechFacets.ts|municipalityCatalog.ts|bahiaTerritories.ts|institutionCatalog.ts`); kill switch + revalidação (`Jingle.ts`, `src/utilities/documents.ts`, `jingleReads.ts`); listas (`CampaignTable.tsx`, `CampaignListOmnibox.tsx`, `campaignListUrl.ts`); feeds oficiais (`SocialFeedSettings`, `instagramFeed.ts`, `youtubeFeed.ts`) — hoje não há fetch por link (oEmbed), só listagem do perfil.
|
||||
- **Risco de acoplamento:** (1) não existe upload em lote no app — o job roda via `after()` no mesmo processo, então N arquivos = N jobs concorrentes (o lote precisa de política própria; decisão do plano de implementação); (2) mídia da peça publicada vs mídia privada do C199/C193 — respeitar o dono de cada mecanismo, sem segunda cópia de arquivo que já tem dono; coleção de upload nova precisa entrar na lista do plugin S3; (3) sub-item novo na nav exige serializar com itens em voo; (4) gate de comunicação já existe — não criar gate gêmeo.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura aberta — reusa C199 (upload + transcrição), C193 (mídia privada) e C194 (biblioteca interna, kill switch/revalidação) entregues.
|
||||
- Soft: **C212** (investigação da varredura/extração) refina o caminho de link; S27 (página pública) e S28 (busca semântica) consomem este item; C213 (analytics) vem depois. Não invadir esses escopos.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Página pública da Central (S27) e busca semântica pública (S28).
|
||||
- Analytics de consumo (C213).
|
||||
- Scraping/varredura do perfil do Instagram — a viabilidade é o C212; a implementação, se aprovada, é item próprio.
|
||||
- Download/espelho de mídia de terceiro (só a própria conta, por caminho oficial).
|
||||
- Editor de vídeo, gerador de card personalizável e edição de mídia.
|
||||
- Ingestão automática do acervo de falas/cortes.
|
||||
- Aprovação editorial multi-etapas e fluxo de revisão com mais de um aprovador.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Scraping do Instagram.** Se alguém "só completar": crawler, login em conta, bloqueio e risco jurídico. **Corte neste item:** caminhos oficiais e link; a varredura é investigada no C212 e nunca por scraping.
|
||||
- **Segundo CMS/acervo paralelo.** Se alguém "só completar": reupload de jingle/artigo que já têm dono, com duas fontes de verdade. **Corte neste item:** peça convive com o dono existente; sem cópia de arquivo que já tem dono.
|
||||
- **Estúdio de criação.** Se alguém "só completar": templates, corte, legenda queimada, editor embutido. **Corte neste item:** catalogar, transcrever e publicar — criar peça é de outra ferramenta.
|
||||
- **Publicação automática sem curadoria.** Se alguém "só completar": tudo que sobe já aparece no público. **Corte neste item:** "Rascunho" é o padrão e publicar é gesto explícito.
|
||||
- **Infra de fila/upload genérico.** Se alguém "só completar": fila, retomada, chunking, retry global, tempo real. **Corte neste item:** lote com estado por peça e "Reprocessar".
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Varredura automática do Instagram?** **Decidido no gate:** investigar agora, via **C212** (viabilidade com a API oficial; scraping fora). O C211 mantém o link manual como caminho e aplica o mecanismo que o C212 recomendar. _(decidido)_
|
||||
- **Peça nasce "Rascunho" ou "Publicado"?** **Opções:** A) "Rascunho" por padrão, publicar é gesto explícito | B) publica no envio | C) escolher no envio. **Recomendação:** A — evita material sem revisão no ar e deixa o kill switch com lugar claro. _(assumido — validar com produto)_
|
||||
- **Peça por link: circula ou é baixada?** **Decidido no gate:** extrair e catalogar quando houver caminho oficial (conteúdo próprio); sem caminho oficial, circular pelo link. **Corte que permanece:** mídia de terceiro nunca é baixada. _(decidido — refinar no C212)_
|
||||
- **Limite do envio em lote?** **Opções:** A) limite generoso com aviso claro acima dele | B) sem limite | C) limite baixo (≈10). **Recomendação:** A — o caso real é despejo de material de um evento; o valor é decisão da implementação. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-ingestao-ui-design.html` (+ assets em `central-conteudos-ingestao-ui-design-assets/`)
|
||||
- Planos irmãos: `docs/plans/acervo-gravacoes-enviadas.md` (C199) · `docs/plans/reels-reel-privado.md` (C193) · `docs/plans/reels-biblioteca.md` (C194) · `docs/plans/central-conteudos-publica.md` (S27) · `docs/plans/central-conteudos-busca-semantica.md` (S28) · `docs/plans/central-conteudos-varredura-instagram.md` (C212)
|
||||
- Arquivos-pista: `src/collections/Recording.ts` · `src/collections/RecordingMedia.ts` · `src/utilities/recordings/recordingUpload.ts` · `src/utilities/recordings/recordingJob.ts` · `src/utilities/recordings/recordingScheduler.ts` · `src/lib/recording.ts` · `src/utilities/speech/speechClassifier.ts` · `src/utilities/speech/speechCutMetadata.ts` · `src/collections/Jingle.ts` · `src/globals/SocialFeedSettings.ts` · `src/utilities/socialFeed/instagramFeed.ts` · `src/utilities/socialFeed/youtubeFeed.ts` · `src/lib/campaignPaths.ts` · `src/components/campaign/shell/nav.ts` · `src/lib/campaignRoles.ts` · `src/utilities/campaignPageActor.ts`
|
||||
- `AGENTS-campaign.md` — RBAC da vertical `/campanha`, comunicação interna e leader lockdown
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
1. Fatia = um outcome verificável? **Sim** — lote/link entra e a peça sai catalogada e publicável.
|
||||
2. Appetite declarado e a intenção cabe? **Sim** (~2–3 dias; scraping e editor cortados para fora).
|
||||
3. Persona + job + aceite claros sem jargão de stack? **Sim**.
|
||||
4. Direção no codebase é hipótese? **Sim** — precedentes e riscos, sem contrato técnico.
|
||||
5. Zero decisões duras de engenharia no plano? **Sim** — schema, fila, limites e coleção ficam para o plano de implementação.
|
||||
|
||||
**Score: 5/5.**
|
||||
499
docs/plans/central-conteudos-publica-ui-design.html
Normal file
499
docs/plans/central-conteudos-publica-ui-design.html
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign-site">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — S27 · Peça voto pra Solla 1313</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme {
|
||||
--font-sans: Arimo, Arial, sans-serif;
|
||||
--color-kit-red: #e4102f;
|
||||
--color-kit-blue: #184e92;
|
||||
--color-kit-yellow: #ffeb00;
|
||||
}
|
||||
:root {
|
||||
--red: #a21c1c;
|
||||
--dark: #47130e;
|
||||
--yellow: #ffe607;
|
||||
--cream: #fff8f2;
|
||||
--muted: #6c615e;
|
||||
--line: rgb(0 0 0/12%);
|
||||
--display: 'Exo 2', 'Arial Black', sans-serif;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Arimo, Arial, sans-serif;
|
||||
}
|
||||
.display {
|
||||
font-family: var(--display);
|
||||
}
|
||||
.board {
|
||||
background: #e7e3e0;
|
||||
background-image: radial-gradient(rgb(24 78 146/9%) 0.7px, transparent 0.7px);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 22px 60px rgb(39 25 22/13%);
|
||||
}
|
||||
.desktop {
|
||||
width: 1280px;
|
||||
}
|
||||
.phone {
|
||||
width: 390px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
border-radius: 10px;
|
||||
padding: 9px 13px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.primary {
|
||||
background: var(--yellow);
|
||||
color: #171000;
|
||||
box-shadow: 0 4px 0 #cfb900;
|
||||
}
|
||||
.outline {
|
||||
border: 1.5px solid rgb(162 28 28/28%);
|
||||
background: white;
|
||||
color: var(--red);
|
||||
}
|
||||
.field {
|
||||
min-height: 46px;
|
||||
width: 100%;
|
||||
border: 1px solid rgb(0 0 0/18%);
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
padding: 10px 13px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
min-height: 38px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: white;
|
||||
padding: 7px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.card {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: white;
|
||||
box-shadow: 0 8px 24px rgb(71 19 14/7%);
|
||||
}
|
||||
.asset {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #e5e2df, #f7f5f3);
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
border-radius: 999px;
|
||||
background: #eef4fb;
|
||||
padding: 4px 8px;
|
||||
color: #184e92;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.scene:after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: #cfc9c5;
|
||||
}
|
||||
.skeleton {
|
||||
background: linear-gradient(100deg, #eee 20%, #ddd 38%, #eee 56%);
|
||||
background-size: 220% 100%;
|
||||
animation: shine 1.6s infinite;
|
||||
}
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position: -220% 0;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="board min-h-screen text-black antialiased">
|
||||
<main class="mx-auto max-w-[1480px] space-y-16 px-4 py-12 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<span class="rounded-full bg-kit-red px-3 py-1 text-xs font-bold text-white"
|
||||
>S27 · revisão do gate</span
|
||||
>
|
||||
<h1 class="display mt-4 text-4xl font-black">Peça voto. Faça o 1313 chegar mais longe.</h1>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-[#6c615e]">
|
||||
Mobile-first, direto no catálogo: ver, ouvir, baixar e compartilhar uma peça oficial sem
|
||||
abrir outra página.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENA 01</span>
|
||||
<h2>Desktop 1280 · catálogo mais limpo, com ação no card</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl bg-white">
|
||||
<header class="flex h-16 items-center justify-between bg-[#ae1603] px-10">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
class="h-9"
|
||||
alt="Jorge Solla"
|
||||
/><span class="text-sm font-bold text-white">Central de Conteúdos</span>
|
||||
</header>
|
||||
<div class="bg-[linear-gradient(120deg,#e4102f,#b60825_60%,#184e92)] text-white">
|
||||
<div class="mx-auto flex max-w-6xl items-center justify-between px-10 py-12">
|
||||
<div>
|
||||
<p class="text-sm font-bold text-[#ffeb00]">PEÇA VOTO PRA SOLLA 1313</p>
|
||||
<h3 class="display mt-2 max-w-2xl text-4xl font-black leading-tight">
|
||||
Uma mensagem sua pode conquistar mais um voto.
|
||||
</h3>
|
||||
<p class="mt-3 max-w-xl text-base text-white/85">
|
||||
Escolha uma peça, mande para alguém que você conhece e peça o voto no 1313.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
src="../../public/campaign-kit/numero-negativo.png"
|
||||
class="h-24"
|
||||
alt="1313 Deputado Federal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-6xl px-10 py-10">
|
||||
<div class="grid grid-cols-[1fr_auto] gap-3">
|
||||
<input
|
||||
class="field"
|
||||
placeholder="Encontre uma peça por assunto, cidade ou tema…"
|
||||
/><button class="btn primary px-6">Escolher uma peça e pedir voto</button>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<span class="chip">Tipo⌄</span><span class="chip">Cidade⌄</span
|
||||
><span class="chip">Região⌄</span><span class="chip">Tema⌄</span
|
||||
><span class="chip">Instituição⌄</span>
|
||||
</div>
|
||||
<div class="mt-9 flex items-end justify-between">
|
||||
<div>
|
||||
<h3 class="display text-2xl font-black">Escolha, compartilhe e peça voto</h3>
|
||||
<p class="mt-1 text-sm text-[#6c615e]">
|
||||
O play só carrega a mídia quando você pedir.
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-sm text-[#6c615e]">Abra o título para ver detalhes</span>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-3 gap-6">
|
||||
<article class="card">
|
||||
<div class="asset relative aspect-video">
|
||||
<span>NEEDS ASSET · POSTER REAL</span
|
||||
><button
|
||||
class="absolute grid size-14 place-items-center rounded-full bg-[#ffe607] text-xl shadow-lg"
|
||||
aria-label="Reproduzir vídeo"
|
||||
>
|
||||
▶</button
|
||||
><span
|
||||
class="absolute right-3 bottom-3 rounded bg-black/70 px-2 py-1 text-xs text-white"
|
||||
>02:14</span
|
||||
>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<span class="tag">Vídeo</span>
|
||||
<h4 class="mt-2 text-base font-extrabold">Fim da escala 6×1 é saúde</h4>
|
||||
<p class="mt-1 text-xs text-[#6c615e]">Trabalho · Salvador</p>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">Baixar</button
|
||||
><button class="btn outline">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="asset relative aspect-video">
|
||||
<span>NEEDS ASSET · CAPA REAL DO ÁUDIO</span
|
||||
><button
|
||||
class="absolute grid size-14 place-items-center rounded-full bg-[#ffe607] text-xl shadow-lg"
|
||||
aria-label="Reproduzir áudio"
|
||||
>
|
||||
▶</button
|
||||
><span
|
||||
class="absolute right-3 bottom-3 rounded bg-black/70 px-2 py-1 text-xs text-white"
|
||||
>00:46</span
|
||||
>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<span class="tag">Áudio</span>
|
||||
<h4 class="mt-2 text-base font-extrabold">Mensagem para mandar no grupo</h4>
|
||||
<p class="mt-1 text-xs text-[#6c615e]">Campanha</p>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">Baixar</button
|
||||
><button class="btn outline">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card border-[#184e92]/25 bg-[#fff8f2]">
|
||||
<div
|
||||
class="asset aspect-video bg-[linear-gradient(135deg,#e4102f,#184e92)] text-white"
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
src="../../public/campaign-kit/numero-negativo.png"
|
||||
class="mx-auto h-16"
|
||||
alt="1313 Deputado Federal"
|
||||
/>
|
||||
<p class="mt-2">SEU NOME + SUA FOTO</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<span class="tag">Card personalizado</span>
|
||||
<h4 class="mt-2 text-base font-extrabold">Faça seu card com Solla 1313</h4>
|
||||
<p class="mt-1 text-xs text-[#6c615e]">Criado no seu aparelho.</p>
|
||||
<a class="btn outline mt-4 w-full" href="#">Fazer meu card</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-2 gap-6">
|
||||
<article class="card flex items-center gap-4 p-3">
|
||||
<div class="asset h-24 w-32 shrink-0 rounded-lg">NEEDS ASSET · FOTO REAL</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<span class="tag">Foto</span>
|
||||
<h4 class="mt-2 text-sm font-extrabold">Solla em defesa do SUS</h4>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<button class="btn outline min-h-9">Baixar</button
|
||||
><button class="btn outline min-h-9">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card flex items-center gap-4 p-3">
|
||||
<div class="asset h-24 w-32 shrink-0 rounded-lg bg-[#fff8f2] px-3">
|
||||
“Saúde é direito.”
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<span class="tag">Texto</span>
|
||||
<h4 class="mt-2 text-sm font-extrabold">Por que defender o SUS</h4>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<button class="btn outline min-h-9">Baixar</button
|
||||
><button class="btn outline min-h-9">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="bg-[#180a09] px-10 py-5 text-xs leading-5 text-white/65">
|
||||
Jorge José Santos Pereira Solla · Candidato a Deputado Federal · Nº 1313 · Federação
|
||||
Brasil da Esperança (PT/PCdoB/PV) · CNPJ: 68.430.467/0001-05 · Propaganda eleitoral
|
||||
gratuita · Eleições 2026 · 1º turno: 04/10/2026.
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENAS 02–03</span>
|
||||
<h2>Mobile 390 · antes e depois do play sob demanda</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-8">
|
||||
<div class="phone frame min-h-[860px] overflow-hidden bg-white">
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
class="h-7"
|
||||
alt="Jorge Solla"
|
||||
/><span class="text-white">☰</span>
|
||||
</header>
|
||||
<div class="bg-[linear-gradient(145deg,#e4102f,#184e92)] px-5 py-7 text-white">
|
||||
<p class="text-xs font-bold text-[#ffeb00]">PEÇA VOTO PRA SOLLA 1313</p>
|
||||
<h3 class="display mt-1 text-3xl font-black leading-tight">
|
||||
Mande uma peça.<br />Peça mais um voto.
|
||||
</h3>
|
||||
<p class="mt-3 text-sm leading-5 text-white/85">
|
||||
Escolha alguém, compartilhe e peça o voto no 1313.
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<input class="field" placeholder="Busque um assunto…" />
|
||||
<div class="mt-3 flex gap-2 overflow-hidden">
|
||||
<span class="chip shrink-0">Tipo⌄</span><span class="chip shrink-0">Cidade⌄</span
|
||||
><span class="chip shrink-0">Tema⌄</span>
|
||||
</div>
|
||||
<article class="card mt-5">
|
||||
<div class="asset relative aspect-video">
|
||||
<span>NEEDS ASSET · POSTER REAL</span
|
||||
><button
|
||||
class="absolute grid size-16 place-items-center rounded-full bg-[#ffe607] text-2xl shadow-lg"
|
||||
>
|
||||
▶</button
|
||||
><span
|
||||
class="absolute right-3 bottom-3 rounded bg-black/70 px-2 py-1 text-xs text-white"
|
||||
>02:14</span
|
||||
>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<span class="tag">Vídeo · ainda não carregado</span>
|
||||
<h4 class="mt-2 text-lg font-extrabold">Fim da escala 6×1 é saúde</h4>
|
||||
<p class="mt-1 text-xs text-[#6c615e]">
|
||||
Toque no play para carregar só este vídeo.
|
||||
</p>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">Baixar</button
|
||||
><button class="btn primary">Compartilhar</button>
|
||||
</div>
|
||||
<a class="mt-3 block text-center text-xs font-bold text-[#a21c1c]"
|
||||
>Ver detalhes da peça →</a
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div class="phone frame min-h-[860px] overflow-hidden bg-white">
|
||||
<header class="flex h-14 items-center gap-3 bg-[#ae1603] px-4 text-white">
|
||||
<span>←</span><b>Central de Conteúdos</b>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<p class="text-xs font-bold text-[#6c615e]">DEPOIS DO PLAY</p>
|
||||
<article class="card mt-3">
|
||||
<div class="relative aspect-video bg-black">
|
||||
<div class="asset absolute inset-0 bg-stone-800 text-white">
|
||||
NEEDS ASSET · VÍDEO EM REPRODUÇÃO
|
||||
</div>
|
||||
<button
|
||||
class="absolute inset-0 m-auto grid size-14 place-items-center rounded-full bg-white/90 text-xl"
|
||||
>
|
||||
Ⅱ
|
||||
</button>
|
||||
<div class="absolute inset-x-3 bottom-3">
|
||||
<div class="h-1.5 overflow-hidden rounded-full bg-white/35">
|
||||
<div class="h-full w-[38%] bg-[#ffeb00]"></div>
|
||||
</div>
|
||||
<div class="mt-1 flex justify-between text-[10px] text-white">
|
||||
<span>00:49</span><span>02:14</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h4 class="text-lg font-extrabold">Fim da escala 6×1 é saúde</h4>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">Baixar</button
|
||||
><button class="btn outline">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="mt-5 rounded-t-2xl border p-4 shadow-[0_-12px_35px_rgb(0_0_0/12%)]">
|
||||
<div class="mx-auto h-1 w-10 rounded-full bg-stone-300"></div>
|
||||
<h4 class="display mt-4 text-lg font-black">Peça esse voto no WhatsApp</h4>
|
||||
<p class="mt-1 text-xs text-[#6c615e]">A mensagem é sua: revise antes de abrir.</p>
|
||||
<textarea class="field mt-3 min-h-32 leading-5">
|
||||
Estamos na reta final e o Jorge Solla 1313 precisa do seu voto. Olha esse vídeo: Fim da escala 6×1 é saúde — <link>. Peça voto pra Solla 1313 pra quem você conhece.</textarea
|
||||
><button class="btn primary mt-3 w-full">Abrir no WhatsApp</button>
|
||||
<div class="mt-3 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">A mídia</button
|
||||
><button class="btn outline">Copiar link</button>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-[11px] leading-4 text-[#6c615e]">
|
||||
Sem destinatário e sem envio automático. Se o aparelho não compartilhar o arquivo,
|
||||
ele será baixado.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENA 04</span>
|
||||
<h2>Página individual · detalhes e link bonito, sem repetir o catálogo inteiro</h2>
|
||||
</div>
|
||||
<div class="desktop frame mx-auto overflow-hidden rounded-xl bg-white">
|
||||
<header class="flex h-16 items-center justify-between bg-[#ae1603] px-10">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
class="h-9"
|
||||
alt="Jorge Solla"
|
||||
/><a class="text-sm font-bold text-white">Voltar à Central</a>
|
||||
</header>
|
||||
<div class="mx-auto grid max-w-5xl grid-cols-[1.2fr_.8fr] gap-10 px-10 py-12">
|
||||
<div class="asset aspect-video rounded-2xl">NEEDS ASSET · PREVIEW REAL DA PEÇA</div>
|
||||
<div>
|
||||
<span class="tag">Vídeo</span>
|
||||
<h3 class="display mt-3 text-3xl font-black">Fim da escala 6×1 é saúde</h3>
|
||||
<p class="mt-3 text-sm leading-6 text-[#6c615e]">
|
||||
Jorge Solla explica por que reduzir a jornada protege a saúde de quem trabalha.
|
||||
</p>
|
||||
<dl class="mt-5 space-y-2 border-y py-4 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-[#6c615e]">Tema</dt>
|
||||
<dd>Trabalho e saúde</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-[#6c615e]">Local</dt>
|
||||
<dd>Salvador</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-[#6c615e]">Duração</dt>
|
||||
<dd>02:14</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-5 grid grid-cols-2 gap-2">
|
||||
<button class="btn outline">Baixar</button
|
||||
><button class="btn primary">Compartilhar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="scene mb-3 flex items-center gap-3 text-xs font-bold text-[#6c615e]">
|
||||
<span>CENA 05</span>
|
||||
<h2>Estados críticos preservados · vazio, indisponível e carregando</h2>
|
||||
</div>
|
||||
<div class="grid gap-6 lg:grid-cols-3">
|
||||
<article class="frame rounded-xl bg-white p-8 text-center">
|
||||
<h3 class="display text-xl font-black">As primeiras peças estão a caminho</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-[#6c615e]">
|
||||
Enquanto isso, faça seu card e peça voto pra Solla 1313.
|
||||
</p>
|
||||
<a class="btn primary mt-5" href="#">Fazer meu card</a>
|
||||
<p class="mt-5 text-xs text-[#6c615e]">Vazio · 1280/390</p>
|
||||
</article>
|
||||
<article class="frame rounded-xl bg-white p-8 text-center">
|
||||
<h3 class="display text-xl font-black">Esta peça não está disponível</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-[#6c615e]">
|
||||
Ela pode ter sido retirada. Encontre outro material oficial e continue pedindo voto.
|
||||
</p>
|
||||
<a class="btn primary mt-5" href="#">Ver outras peças</a>
|
||||
<p class="mt-5 text-xs text-[#6c615e]">Link despublicado · 1280/390</p>
|
||||
</article>
|
||||
<article class="frame rounded-xl bg-white p-5">
|
||||
<div class="skeleton h-44 rounded-xl"></div>
|
||||
<div class="skeleton mt-4 h-5 w-3/4 rounded"></div>
|
||||
<div class="skeleton mt-3 h-4 w-full rounded"></div>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<div class="skeleton h-11 rounded"></div>
|
||||
<div class="skeleton h-11 rounded"></div>
|
||||
</div>
|
||||
<p class="mt-4 text-center text-xs text-[#6c615e]">Carregando · 1280/390</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
121
docs/plans/central-conteudos-publica.md
Normal file
121
docs/plans/central-conteudos-publica.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# S27 — Central de Conteúdos pública — Peça voto pra Solla 1313
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-22
|
||||
Issue: #1255
|
||||
Priority: P1
|
||||
Impeccable: C — fluxo público novo (`/conteudos` + página da peça)
|
||||
Design UI: docs/plans/central-conteudos-publica-ui-design.html
|
||||
Appetite: ~2–3 dias eng; a Central no ar com as peças de C211 filtráveis, cada peça com página própria e compartilhamento contextual de voto
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
Estamos na reta final — 1º turno em 4 de outubro — e o maior cabo eleitoral de Solla é o próprio eleitor: quem já vota precisa pedir voto. Hoje o material (Reels, textos, fotos, jingles, cards) está espalhado entre grupo de WhatsApp, drive e redes, e quem quer ajudar não sabe onde pegar a peça certa nem o que escrever junto. Esta fatia cria a campanha "Peça voto pra Solla 1313" com uma casa para o material: a Central de Conteúdos. O eleitor chega, filtra pelo que faz sentido para a sua cidade/região/tema, vê e ouve a peça direto no catálogo (play sob demanda) e baixa ou compartilha dali mesmo no WhatsApp, com uma mensagem que já pede voto — contextualizada pela peça. Entre as peças, a criação do card personalizado aparece como uma peça do catálogo, que leva ao estúdio. A chamada e o CTA da tela têm um objetivo único: fazer o visitante pedir voto pro Solla 1313 — apelativos e verificáveis. E a Central não dispara nada: ela empodera o eleitor a fazer, no aparelho dele, o que a campanha não pode fazer em massa.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** eleitor/simpatizante/liderança no celular, com pressa, no meio da rotina; quer repassar material de Solla e não sabe o que escrever nem onde achar a peça certa.
|
||||
- **Job principal:** pegar uma peça oficial de Solla e pedir voto pra ele no WhatsApp, sem trabalho e sem medo de errar.
|
||||
- **Fluxo desejado:** abre `/conteudos` → vê "Peça voto pra Solla 1313 · Central de Conteúdos" → filtra por Tipo/Cidade/Região/Tema/Instituição ou busca por termo → vê a peça no próprio catálogo (vídeo/áudio só carrega ao tocar em play; foto com preview leve) → baixa ou compartilha dali mesmo (mensagem de voto sugerida, editável) → repassa; se quiser o card dele, toca na peça de card e cai no `/cards`.
|
||||
- **Anti-goals de produto:** não vira rede social (sem comentários/likes/perfil); não vira editor de mídia nem segundo catálogo (quem cataloga é o interno, C211); não captura dado nem pede login; não dispara em massa (nada de WhatsApp Business API); não re-hospeda o que já tem dono (jingle/artigo/card).
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[eleitor no celular] → /conteudos ("Peça voto pra Solla 1313 · Central de Conteúdos")
|
||||
→ filtra (Tipo · Cidade · Região · Tema · Instituição) e/ou busca por termo
|
||||
→ vê/ouve a peça no catálogo (play sob demanda; foto com preview leve)
|
||||
→ Baixar · Compartilhar (mensagem de voto no WhatsApp · a mídia · copiar link) dali mesmo
|
||||
· peça de Card → /cards (fazer o meu)
|
||||
→ [eleitor edita e envia no próprio WhatsApp] → a peça circula
|
||||
→ /conteudos/<slug> continua existindo para detalhes e para o link abrir bonito
|
||||
[peça despublicada] → some da lista; link direto mostra estado honesto de indisponível
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-publica-ui-design.html` — classe C porque é fluxo público novo (catálogo filtrável com preview/ações + página da peça); o design hi-fi é obrigatório no gate. Revisão pedida no gate: layout mais limpo e copy focada em pedir voto.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- O visitante abre `/conteudos` e vê as peças publicadas (as de C211), filtráveis por Tipo · Cidade · Região · Tema · Instituição e por termo de busca; os filtros combinam entre si.
|
||||
- Cada peça tem página própria `/conteudos/<slug>` com preview (título + imagem) para o link abrir bem no WhatsApp.
|
||||
- Preview no próprio catálogo: Vídeo e Áudio só carregam quando o eleitor toca em play (nada de baixar tudo de uma vez); Foto aparece com preview leve; as ações `Baixar` e `Compartilhar` (Mensagem no WhatsApp · A mídia · Copiar link) funcionam direto do card, sem precisar abrir a peça.
|
||||
- A criação do card personalizado aparece no catálogo como uma peça (mesmo grid), que leva ao estúdio `/cards` para o eleitor fazer o dele.
|
||||
- A chamada e o CTA da página pedem voto (apelativos, sem promessa irrealista) — o objetivo único da tela é o eleitor pedir voto pro Solla 1313.
|
||||
- Ações por peça: `Baixar` · `Compartilhar` (opções: "Mensagem no WhatsApp" com a mensagem de voto sugerida e editável · "A mídia" · "Copiar link") · `Fazer meu card` (peça de Card → `/cards`).
|
||||
- "A mídia": onde o aparelho permitir, abre a folha de compartilhamento nativa com o arquivo (o eleitor escolhe o WhatsApp); onde não houver, baixa — nunca fingir que anexou. `wa.me` não anexa arquivo; o link é sempre a alternativa que funciona em qualquer aparelho.
|
||||
- Peça-link (Instagram/YouTube) compartilha pelo link da plataforma; peça com arquivo permite baixar e compartilhar pelo link da peça — sem segunda cópia.
|
||||
- O compartilhamento é o eleitor no próprio aparelho: `wa.me/?text=`, sem destinatário e sem envio pelo sistema; a mensagem nunca sai sozinha.
|
||||
- Mobile-first e leve: usável em conexão ruim, sem login e sem cadastro.
|
||||
- **Kill switch:** despublicar uma peça a tira da Central e da própria página na hora; sem peça publicada, a Central mostra estado honesto e o link de descoberta some.
|
||||
- **Guardrails:** LGPD — sem login, sem captura de dado, sem PII e sem rastreio neste item (analytics = C213); TSE — nenhuma promessa irrealista, identidade eleitoral visível (bloco obrigatório do rodapé preservado), nada de disparo em massa; cards seguem 100% no aparelho (a Central só convida para `/cards`); nenhuma mídia de terceiro baixada; o link do Instagram/YouTube da peça nunca deixa a Central parecer fonte oficial da plataforma.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Dados: N/A** — a Central não apresenta número, contador nem ranking; é superfície de peças. Quem mede circulação é C213 (fora de escopo).
|
||||
- **Decisões desbloqueadas:** o eleitor decide o que baixar/compartilhar; a comunicação decide o que publicar/despublicar. Nenhum dado do visitante é coletado.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Nome na tela: `Central de Conteúdos`; chamada principal: `Peça voto pra Solla 1313` — chamada e CTA de pedido de voto, apelativos e verificáveis.
|
||||
- URL (decidida no gate): lista `/conteudos`; peça `/conteudos/<slug>`; `conteudos` entra nos slugs reservados do site.
|
||||
- Filtros (rótulos): `Tipo` · `Cidade` · `Região` · `Tema` · `Instituição`; mais campo de busca por termo (busca semântica é S28).
|
||||
- Ações por peça (no card do catálogo e na página): `Baixar` · `Compartilhar` com as opções `Mensagem no WhatsApp` · `A mídia` · `Copiar link`; a peça de Card leva a `Fazer meu card` → `/cards`.
|
||||
- Preview: Vídeo e Áudio com play sob demanda (a mídia só é buscada no toque, sem baixar tudo); Foto com preview leve; a página da peça continua existindo para detalhes e para o link abrir bonito.
|
||||
- Mensagens de voto (assumidas — validar com a comunicação), sempre editáveis, com o sistema nunca enviando:
|
||||
- Vídeo → `Estamos na reta final e o Jorge Solla 1313 precisa do seu voto. Olha esse vídeo: <título> — <link>. Peça voto pra Solla 1313 pra quem você conhece.`
|
||||
- Áudio → `Ouça e mande pro grupo: <título> — <link>. Peça voto pra Solla 1313.`
|
||||
- Foto/Texto/Card → `Fiz/achei esse material do Solla 1313: <título> — <link>. Peça voto pra Solla 1313 também.`
|
||||
- O sistema nunca envia: abre o WhatsApp com o texto pronto e o eleitor edita antes de mandar (`wa.me/?text=`, sem destinatário).
|
||||
- Peça-link (Instagram/YouTube) compartilha pelo link; peça-arquivo permite `Baixar`, `A mídia` (folha nativa quando houver, senão download) e compartilhar pelo link da peça — sem re-hospedar cópia.
|
||||
- Toda peça publicada tem página própria com preview `título + imagem` (OG) para o link abrir bem no WhatsApp.
|
||||
- Kill switch por peça (publicado/despublicado) — despublicar tira na hora, sem apagar arquivo.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota nova `src/app/(frontend)/conteudos/` (lista + `[slug]`); filtros/busca e card em `src/components/`; share reusando `src/lib/contentShare.ts`, `src/lib/phone.ts` (`buildWhatsAppTextShareUrl`), `ContentShareButton`/`CopyLinkButton` e o padrão de "copiar com feedback"; cache e revalidação no padrão de `src/utilities/documents.ts` + allowlist em `src/app/(frontend)/api/revalidate/route.ts`; descoberta em `src/components/CampaignFooter.tsx`/`SiteHeader.tsx`.
|
||||
- **Precedente a olhar:** `/jingles` (página pública com kill switch + `<a download>`), `/corte/[id]` (página pública de peça + share kit), `ShareLink` (OG + `published` como kill switch), `CardsStudio` (`/cards` com deep-link `?model=`), S4 (`docs/plans/compartilhar-conteudos-home-whatsapp.md`).
|
||||
- **Risco de acoplamento:** rota estática nova precisa entrar em `SHARE_LINK_RESERVED_SLUGS` (`src/lib/shareLink.ts`) senão o drift test falha; mídia pública baixa por atributo `download` (proxy `/api/media/file/...` sem `Content-Disposition`) — sem zip/lote; cards personalizáveis são canvas client-side (blob sem URL pública) → só convite para `/cards`; não existe precedente de filtro/busca pública (o maquinário de URL state é interno) — seguir o padrão do site, sem importar o interno; a folha nativa de compartilhamento com arquivo (`navigator.share`) é suportada só em parte dos aparelhos — sempre com fallback de download; o preview de vídeo/áudio no catálogo exige carregamento sob demanda (poster + play) para a página não pesar.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C211** (peças catalogadas e publicadas pelo interno) — dura: sem catálogo publicado não há Central.
|
||||
- Design hi-fi aprovado no gate.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Busca semântica (S28) — aqui é busca por termo.
|
||||
- Analytics, contadores, UTM e qualquer rastreio do visitante (C213).
|
||||
- Editar/subir mídia pela Central (é C211); comentários/likes/perfil; app de disparo ou integração com WhatsApp Business API.
|
||||
- Segunda cópia de arquivo (re-hospedar jingle/artigo/card); download em zip/lote.
|
||||
- Página de bandeira/SEO §7.2 (`docs/campanha/plano-site-campanha-2026.md` §7.2) — peça não vira landing de bandeira; redesign do shell/nav/home.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Uma tela por tipo de mídia.** Se alguém "só completar": player de áudio, lightbox de foto, embed de IG, cada tipo com layout próprio. **Corte neste item:** um board único com card por peça; tipo é dado do filtro, não tela.
|
||||
- **Catálogo virar cabine de mídia.** Se alguém "só completar": carregar todos os vídeos/áudios na abertura, autoplay, player próprio. **Corte neste item:** play sob demanda (a mídia só carrega no toque) e nenhum recurso além de dar play/ouvir.
|
||||
- **A Central virar rede social.** Se alguém "só completar": curtir, comentar, salvar, perfil, feed. **Corte:** nada de UGC/engajamento — só encontrar, baixar e repassar.
|
||||
- **Re-hospedar o acervo inteiro.** Se alguém "só completar": subir jingle/artigo/card em cópia própria na Central. **Corte:** a Central referencia o dono; só a peça que nasce nela ganha arquivo.
|
||||
- **Disparo em massa "para ajudar".** Se alguém "só completar": lista, agendamento, API Business. **Corte:** `wa.me` do eleitor, sem destinatário — vedação TSE.
|
||||
- **Personalizador embutido na Central.** Se alguém "só completar": renderizar o card no servidor para "baixar pronto". **Corte:** a peça de card leva a `/cards`; o card do eleitor segue 100% no aparelho.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **URL `/conteudos` + `/conteudos/<slug>`?** **Decidido no gate:** A — lista e peça no mesmo namespace; entra em `SHARE_LINK_RESERVED_SLUGS`. _(decidido)_
|
||||
- **Chamada/CTA da página?** **Decidido no gate:** foco em pedir voto ("Peça voto pra Solla 1313"), apelativo e verificável; a copy final passa pela comunicação. _(decidido)_
|
||||
- **Mensagem sugerida por tipo?** **Opções:** A) os 3 templates (Vídeo/Áudio/Foto-Texto-Card) dos literais | B) um template único | C) só o link, sem sugestão. **Recomendação:** A — é o pedido do dono; sempre editável antes de enviar. _(assumido — validar com a comunicação)_
|
||||
- **Como a mídia circula?** **Opções:** A) peça-link compartilha pelo link da plataforma; peça-arquivo oferece "A mídia" (folha nativa com o arquivo, quando suportada) e `Baixar` como fallback | B) tudo pelo link da peça | C) só baixar e o eleitor anexa. **Recomendação:** A — é o pedido do dono (compartilhar a mídia diretamente), com fallback honesto onde o aparelho não suportar; `wa.me` não anexa arquivo. _(assumido)_
|
||||
- **Filtros v1?** **Opções:** A) os 5 rótulos + busca por termo | B) só `Tipo` + `Cidade` + busca | C) só busca. **Recomendação:** A, exibindo apenas filtro que tenha valor entre as peças publicadas. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Design UI (gate): `docs/plans/central-conteudos-publica-ui-design.html` (+ assets em `central-conteudos-publica-ui-design-assets/`)
|
||||
- Planos irmãos: `docs/plans/central-conteudos-ingestao.md` (C211 — o catálogo que alimenta a página), `docs/plans/compartilhar-conteudos-home-whatsapp.md` (S4 — wa.me do remetente, sem telemetria), `docs/plans/links-compartilhamento-miniatura-og.md` (S19), `docs/plans/cards-personalizados-campanha.md` (S13), `docs/plans/corte-pagina-publica-design.md` (C167).
|
||||
- Arquivos-pista: `src/lib/contentShare.ts`, `src/components/ContentShareButton.tsx`, `src/lib/phone.ts`, `src/lib/shareLink.ts`, `src/components/jingles/JingleCards.tsx`, `src/components/cards/CardsStudio.tsx`, `src/components/CampaignFooter.tsx`, `src/utilities/documents.ts`, `src/app/(frontend)/api/revalidate/route.ts`.
|
||||
- `AGENTS-public.md` — convenções do site público; `docs/campanha/plano-site-campanha-2026.md` §7.1 (compartilhamento por seção — ideia do cliente).
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
5/5 — (1) fatia = um outcome verificável (Central no ar com peças filtráveis e compartilhamento de voto); (2) appetite ~2–3 dias cabe com design hi-fi + port; (3) persona/job/aceite em linguagem de produto; (4) direção no codebase é hipótese; (5) zero decisão dura de engenharia.
|
||||
97
docs/plans/central-conteudos-varredura-instagram.md
Normal file
97
docs/plans/central-conteudos-varredura-instagram.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# C212 — Central de Conteúdos — viabilidade da varredura automática do Instagram (e da extração de mídia por link)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-22
|
||||
Issue: #1256
|
||||
Priority: P2
|
||||
Impeccable: A — N/A sem UI
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~0,5–1 dia eng; um outcome verificável — as quatro perguntas respondidas com evidência (repo + docs oficiais) e uma recomendação de mecanismo, limites e cortes, sem implementar
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
No gate do C211 o dono decidiu duas coisas: peça adicionada por link entra com a mídia extraída e catalogada, não só com o link; e, sobre a ingestão do Instagram, "investigar varredura automática agora". A eleição é 04/10 e a Central (C211/S27) ainda vai nascer — se a varredura for viável, ela muda o desenho da ingestão antes de existir; se não for, o C211 precisa saber com evidência, não por precaução genérica.
|
||||
|
||||
O Teqo já tem integração oficial com a Graph API do Instagram (feed da home, S11) e com a YouTube Data API v3 (S2). O que falta saber é se esses mesmos caminhos oficiais bastam para a Central catalogar/transcrever mídia, o que exatamente eles não entregam e a que custo (limites, token, curadoria). Este item entrega a investigação com evidência e recomendação; a implementação (se aprovada) vira item próprio.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`) que vai operar a Central; e quem decide produto/engenharia (`coordinator`/`candidate` e o dono no gate).
|
||||
- **Job principal:** saber, com evidência, se a varredura automática do perfil e a extração de mídia por link são viáveis e sob quais limites — para decidir o que implementar no C211.
|
||||
- **Fluxo desejado:** pergunta → evidência (o que o repo já faz + docs oficiais datadas) → recomendação (mecanismo, limites, o que fica de fora, próximo item) → decisão no gate.
|
||||
- **Anti-goals de produto:** não é a implementação da varredura nem da extração; não vira spike de scraping; não redesenha o feed da home; não cria tela; não promete o que não é verificável.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- **Q1 — a Graph API oficial já integrada entrega as mídias recentes do perfil do Solla com metadados e `media_url` suficientes para catalogar/transcrever?** Resposta com campos, paginação, rate limit, validade/refresh do token e periodicidade segura, citando o que a integração atual pede e o que descarta (`src/utilities/socialFeed/instagramFeed.ts`) + docs oficiais. Limite/expiração que não se confirmar na doc: declarar **"a confirmar na implementação"** — nunca estimar.
|
||||
- **Q2 — peça por link (IG/YT):** qual o caminho oficial/aceitável de extração em cada plataforma; o que NÃO é aceitável (scraping, contorno, download de mídia de terceiro); e o que fazer quando não há caminho oficial — a peça circula pelo link e a assessoria pode anexar o arquivo (refina a regra do C211).
|
||||
- **Q3 — varredura automática do perfil: vale a pena?** Comparar explicitamente (A) API oficial agendada/recebida; (B) scraping; (C) sem varredura (link manual do C211) — com curadoria proposta (o que entra, rascunho, filtro por tipo/período), riscos (ToS da Meta, bloqueio de conta/IP, jurídico/reputacional, fragilidade) e custo operacional.
|
||||
- **Q4 — recomendação final:** mecanismo, limites, o que fica de fora e o item de implementação que nasceria daí (descrito, não criado).
|
||||
- **Registro:** os achados ficam anexados a este plano (seção de achados, com links oficiais datados) — o entregável é decisão documentada, não código.
|
||||
- **Guardrails:** NUNCA scraping/contorno como mecanismo recomendado; se a conclusão for que só o scraping alcança algo desejado, a recomendação é NÃO fazer e dizer o que se perde. Nada entra na Central pública sem curadoria (rascunho → publicado). Sem PII; sem credencial em log. A investigação não altera comportamento de produção (feed da home intocado); evidência = repo + docs oficiais, com probe na API real só leitura, se necessário.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — não há superfície de dados: o resultado é decisão documentada (evidência citada + recomendação), não métrica ou agregado para usuário.
|
||||
- **Decisões desbloqueadas:** N/A — a decisão é do gate (dono), a partir da recomendação.
|
||||
- **Forma:** N/A.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Decisão do dono no gate (verbatim):** "Quando o assessor adiciona uma peça por link do Instagram ou Youtube, a mídia deve ser automaticamente extraída e catalogada."; e "Investigar varredura automática agora".
|
||||
- Peça por link entra com a mídia extraída e catalogada **quando houver caminho oficial**; sem caminho oficial, a peça circula pelo link e a assessoria pode anexar o arquivo (isto é o C211; aqui só se refina).
|
||||
- **NUNCA scraping/contorno de plataforma como mecanismo recomendado**; se a investigação concluir que scraping é a única via para algo desejado, a recomendação é NÃO fazer e dizer o que se perde.
|
||||
- **Curadoria:** nada entra na Central pública sem passar pela assessoria ("Rascunho" → "Publicado" — regra do C211).
|
||||
- **Nada de PII; nada de credencial em log.**
|
||||
- **Escopo de plataformas:** Instagram (`@depjorgesolla`, conta Business/Creator) e YouTube (canal oficial `@JorgeSollaDep`) — demais redes fora.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/utilities/socialFeed/` (`instagramFeed.ts`, `instagramFeedView.ts`, `instagramSync.ts`, `youtubeFeed.ts`), `src/globals/SocialFeedSettings.ts`, `src/lib/speechVod.ts` (parse de link de YouTube já existente) e o runbook `docs/ops/instagram-feed-token-runbook.md`; a Central é o C211.
|
||||
- **Fatos já verificados no repo (ponto de partida, a revalidar):** a integração pede `id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,children{...}` em `GET /{userId}/media` (`instagramFeed.ts:94`) — o `media_url` chega, mas hoje é descartado no parse (vira thumbnail, `instagramFeed.ts:111`); `limit` é capado em 50 (`instagramFeed.ts:223`) e o sync busca `maxItems + 10` (`instagramSync.ts:103`); não há paginação (`paging.cursors.after`) nem fetch por link (grep por oEmbed = 0); o refresh é `grant_type=ig_refresh_token` (`instagramFeed.ts:237`); a YouTube Data API (`search.list`/`videos.list`) não fornece arquivo de vídeo; o feed é atualizado por render com cache de 5 min (`instagramFeedView.ts:96`) — cadência de board, não de acervo.
|
||||
- **Precedente a olhar:** S3 (`docs/plans/secao-conteudos-home-instagram.md`) e o runbook do token; C199 (`src/utilities/recordings/recordingScheduler.ts`) se a varredura agendada for aprovada; C211 para o encaixe da peça-link.
|
||||
- **Risco de acoplamento:** o feed da home tem contrato próprio (fail-closed, snapshot, kill switch) — a investigação não o redesenha; se a varredura usar o mesmo token/global, a proposta deve explicitar como não ampliar o escopo do board nem criar segundo estado de sync.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Soft: C211** — este item informa a implementação dele (ingestão por link e eventual varredura); o C211 começa sem este (link manual é o fallback). **S27 não depende.**
|
||||
- Credenciais Meta/Google já existentes (token/ID IG e API key YT no `SocialFeedSettings`); a validade real do token é parte da investigação (painel de status + runbook).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Implementar a varredura (se aprovada, vira item próprio) e implementar a extração de mídia por link.
|
||||
- Qualquer scraping/contorno, download de mídia de terceiro ou automação de conta.
|
||||
- Mudar a integração do feed da home (S3) ou o board público.
|
||||
- Criar UI nova (o resultado é decisão documentada; a UI da Central é do C211).
|
||||
- Transcrição/embeddings (C211/S28) e analytics (C213).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Investigar virou implementar".** Se alguém "só completar": cria coleção, agendador, rota e migration dentro da investigação. **Corte neste item:** o entregável é evidência + recomendação; implementação é item próprio.
|
||||
- **"Scraping porque a API não dá conta".** Se alguém "só completar": crawler, login em conta, contorno para pegar mídia de terceiro ou perfil fora do caminho oficial. **Corte:** nunca como recomendação; a investigação diz o que se perde e para.
|
||||
- **"Cobrir todas as redes".** Se alguém "só completar": TikTok, Facebook, X, Kwai. **Corte:** escopo é Instagram + YouTube, as duas com oficialidade já decidida.
|
||||
- **"Credencial/estado no lugar errado".** Se alguém "só completar": token em arquivo/log, cursor de varredura em memória, mídia em pasta sem dono. **Corte:** credencial só no `SocialFeedSettings` (admin-only), nunca em log; estado novo tem dono definido no item de implementação.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Se a varredura for aprovada, o que ela traz?** **Opções:** A) só as mídias recentes do perfil oficial, tudo como rascunho para a assessoria triar | B) varredura com filtro (ex.: só Reels/vídeos, janela de período) que já descarta o que não interessa | C) tudo entra direto na Central. **Recomendação:** A — rascunho é a regra do C211 e a triagem é editorial; filtro fino (tipo/período) é decisão de implementação com a assessoria. _(assumido — validar com produto)_
|
||||
- **Peça por link de perfil de terceiro (ex.: post que menciona o Solla):** **Opções:** A) entra só pelo link, sem extrair/baixar mídia | B) entra com thumbnail/embed quando o caminho oficial (oEmbed) permitir | C) não aceitar link de terceiro. **Recomendação:** B, com o arquivo nunca baixado — o caminho oficial de terceiro dá embed/thumbnail, não o arquivo; extração de mídia só para conteúdo próprio (conta do Solla via Graph API). _(assumido — validar com produto)_
|
||||
- **Periodicidade, se aprovada:** **Opções:** A) varredura diária agendada + "Sincronizar agora" | B) semanal | C) só sob demanda. **Recomendação:** A — a Central é acervo, não tempo real; a cadência de board (5 min) não serve, e o custo em chamadas da API é assunto da implementação. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- Planos irmãos: `docs/plans/central-conteudos-ingestao.md` (C211) · `docs/plans/central-conteudos-publica.md` (S27) · `docs/plans/secao-conteudos-home-instagram.md` (S3) · `docs/plans/secao-conteudos-home-youtube.md` (S2)
|
||||
- Runbook: `docs/ops/instagram-feed-token-runbook.md` · decisões do board: `docs/campanha/plano-site-campanha-2026.md` §4.2
|
||||
- Arquivos-pista: `src/globals/SocialFeedSettings.ts` · `src/utilities/socialFeed/instagramFeed.ts` · `src/utilities/socialFeed/instagramFeedView.ts` · `src/utilities/socialFeed/instagramSync.ts` · `src/utilities/socialFeed/youtubeFeed.ts` · `src/lib/speechVod.ts`
|
||||
- Docs oficiais a abrir e datar na investigação: Instagram Platform / Graph API (`developers.facebook.com/docs/instagram-platform` — media, paginação, refresh de token) · Instagram oEmbed (`developers.facebook.com/docs/instagram/oembed`) · Termos da Plataforma Meta (`developers.facebook.com/terms`) · YouTube Data API v3 (`developers.google.com/youtube/v3/docs/videos` e `/search`) · YouTube oEmbed (`www.youtube.com/oembed`) · Termos/Developer Policies do YouTube (`developers.google.com/youtube/terms`)
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
1. Fatia = um outcome verificável? **Sim** — as quatro perguntas respondidas com evidência + recomendação.
|
||||
2. Appetite declarado e a intenção cabe? **Sim** (~0,5–1 dia; implementação, scraping e UI fora).
|
||||
3. Persona + job + aceite claros sem jargão de stack? **Sim**.
|
||||
4. Direção no codebase é hipótese? **Sim** — fatos verificados + áreas prováveis, sem contrato técnico.
|
||||
5. Zero decisões duras de engenharia no plano? **Sim** — schema, agendador e limites ficam no item de implementação, se aprovado.
|
||||
|
||||
**Score: 5/5.**
|
||||
Loading…
Reference in a new issue