---
id: agencias/portal-do-cliente
industria: Agências de marketing
uso: específico
esforco: médio
grao: Um contrato por tela (ex.: dia x cliente x plataforma x conjunto x anúncio)
---

# Portal do cliente

O painel próprio da agência recebe da Nekt uma tabela pronta por tela, isolada por cliente e sem dado pessoal cru, e não quebra quando alguém mexe numa coluna.

## Resumo executivo

**Para quem:** Agências que entregam ao cliente um portal ou app próprio em vez de Looker, planilha ou ferramenta de relatório.

**O que resolve**

- Cada tela do portal lê uma tabela pronta, com nome, colunas e chave combinados.
- Nenhum cliente vê dado de outro, nem quando a fonte é compartilhada.
- Telefone, e-mail e documento chegam ao app pseudonimizados, nunca crus.

**Por que importa:** O portal vira o produto que o cliente abre toda semana. Se uma coluna muda, a tela fica em branco sem erro nenhum. Se um filtro some, o cliente vê avaliação ou lead de outro negócio, e isso é vazamento de dado, não bug visual.

**Perguntas de negócio que responde**

- O portal do cliente X está atualizado até ontem?
- Qual tabela alimenta a tela de anúncios do portal?
- Algum cliente está recebendo dado de outra conta?
- Quais telas do portal ainda dependem de carga manual?
- O número do portal bate com o relatório que enviamos?

**O que você precisa**

- As tabelas finais de mídia e funil já modeladas na Nekt
- Um destino configurado (Postgres do app, Supabase ou endpoint HTTP) com schema de entrada próprio
- O registro que liga cada cliente às suas contas e à sua camada

**O que você recebe**

- Uma tabela de entrega por tela, com grão e chave de upsert declarados
- Carga disparada pelo fim da fonte, com data da última atualização em cada linha
- Isolamento por cliente e pseudonimização feitos antes de o dado sair da Nekt
- Uma tabela por cliente quando cada cliente tem o próprio agente de IA

## Detalhado

### O problema

O app do cliente não consulta a Nekt em tempo real: a Nekt empurra o dado pronto para o banco do app. Sem um contrato por tela, qualquer ajuste de coluna deixa a tela vazia, o upsert com chave incompleta sobrescreve linhas e um filtro esquecido numa fonte compartilhada entrega ao cliente dado de outro negócio. Nada disso gera erro: o destino continua rodando verde.

### Regras de negócio

1. **A tabela de entrega é um contrato.** Cada tela lê uma tabela de consumo com nome fixo (por exemplo, prefixo mart_) e colunas explícitas e tipadas. O nome e as colunas são o contrato com o app. Coluna nova se adiciona; coluna existente nunca se renomeia nem muda de tipo; mudança de grão vira tabela nova. Normalização de fonte fica em tabelas stg_, sem regra de negócio. (Evite: Renomear uma coluna da tabela que o app lê e descobrir pela reclamação do cliente.)
2. **A chave de upsert é o grão inteiro.** Declare a chave de upsert com todas as colunas do grão. Na tela de anúncios, a chave é data, cliente, plataforma, conjunto e anúncio: o conjunto entra porque no Google o mesmo anúncio pode estar em mais de um grupo. Rode a checagem de unicidade da chave antes de cada carga. (Evite: Upsert por data e anúncio, que sobrescreve o anúncio repetido em outro grupo.)
3. **Nunca escreva no schema público do app.** O destino escreve num schema de entrada (staging ou landing). Os triggers e as regras de acesso do app (RLS) fazem deduplicação, derivação e exposição a partir dali. Escrever direto no schema público atropela essa lógica e deixa dado cru visível para o front. (Evite: Apontar o destino para o schema public do banco do app.)
4. **Isolamento por cliente dentro da transformação.** Fonte compartilhada (conta do Google Meu Negócio com vários locais, Business Manager com várias contas) precisa de filtro pelo identificador oficial do cliente (location_id, account_id do registro) dentro da própria transformação de entrega. Um filtro esquecido não dá erro: entrega ao cliente os dados de dezenas de negócios. Quando cada cliente tem o próprio agente de IA, grave uma tabela por cliente na pasta dele, para que o agente só enxergue o seu dataset. (Evite: Confiar que o app filtra por cliente depois de receber a tabela da conta inteira.)
5. **Dado pessoal sai pseudonimizado.** A pseudonimização acontece na Nekt, não no app. Telefone vira hash do número normalizado (só dígitos) mais os últimos 4 dígitos; e-mail e documento viram hash; endereço e campos que o painel não usa não saem. Se uma tela precisa do dado cru, a discussão é sobre permissão no app, não sobre a consulta. (Evite: Mandar o telefone completo e esconder na tela.)
6. **O cliente vem do registro, não do nome da camada.** Ligue cliente e camada por uma coluna de identidade no cadastro (por exemplo, client_slug ou a camada da Nekt registrada no portal), com no máximo um cliente por camada. Nomes de camada não podem ser renomeados e divergem do nome comercial do cliente. (Evite: Casar o cliente do portal com a camada da Nekt pelo nome.)
7. **Carga por evento, não por relógio.** A transformação de entrega dispara no fim da execução da fonte que ela lê, para nunca rodar antes de a extração terminar. Horário fixo só onde o destino tem janela própria. Gatilho manual não é pipeline vivo e precisa estar marcado como tal. (Evite: Agendar a carga às 7h e torcer para a fonte ter terminado.)
8. **Frescor dentro da tabela.** Toda tabela de entrega carrega a data e hora da carga e a data máxima do dado. A cópia do cadastro do portal na Nekt pode ter até 24 horas de atraso: antes de acusar divergência entre portal e Nekt, confira a última execução da fonte. (Evite: Mostrar o número sem dizer até quando ele vai.)
9. **Mesma métrica, mesmo nome em todas as plataformas.** As colunas de métrica têm os mesmos nomes da tabela de mídia da carteira (spend, clicks, leads, conversions), para o app reaproveitar o mapeamento. Cada plataforma preenche as suas colunas e deixa as da outra nulas, nunca zero. Valores já em reais, convertidos antes da entrega. (Evite: Uma coluna por plataforma com nomes diferentes, ou zero onde a métrica não existe.)
10. **Miniatura com link que não expira.** A imagem do anúncio vem de adimages.permalink_url no Meta, descrito como link permanente. Os demais links de miniatura expiram. Marque na linha se a miniatura é permanente, para o app saber quando buscar de novo. (Evite: Gravar a thumbnail temporária e ver a imagem quebrar em poucos dias.)

### Do warehouse à tela

Camadas que separam a regra de negócio do que o app lê.

- **stg_<fonte>** → Normalização de uma fonte, um grão por linha, sem regra de negócio
- **mart_<tela>** → Formato exato da tela: o contrato com o app
- **filtro e pseudonimização** → Só o cliente da tela, sem dado pessoal cru
- **destino em staging ou landing** → Upsert pela chave do grão (`date, client, platform, adset_id, ad_id`)
- **triggers e RLS do app** → O app deduplica e expõe
- **schema public** → Nunca escrever direto aqui

### Definições

| Métrica | Definição |
|---|---|
| Contrato de carga | nome da tabela de entrega, lista de colunas com tipo, grão e chave de upsert de uma tela, combinados com quem constrói o app |
| Chave de upsert | conjunto de colunas que identifica uma linha do grão; nenhuma combinação pode se repetir na tabela de entrega |
| Cliente do portal | identificador do cliente no cadastro do portal, ligado a uma única camada da Nekt |
| Linha segura para o cliente | linha que só contém dados do cliente da tela e nenhum dado pessoal cru |
| Telefone pseudonimizado | hash do número só com dígitos, mais os últimos 4 dígitos |
| Frescor | data e hora da última carga e data máxima do dado, por tabela de entrega |
| Atraso aceitável | intervalo entre execuções da fonte mais uma margem combinada (ex.: 24 horas para fonte diária) |

### Armadilhas

- **Tela em branco depois de um ajuste.** Uma coluna foi renomeada na tabela de entrega. O destino continuou entregando e o app passou a ler campo nulo, sem nenhum erro. Como evitar: Trate a tabela como contrato: adicione colunas, nunca renomeie.
- **Avaliações de outros negócios no painel.** A conta do Google Meu Negócio da agência cobria dezenas de locais. Sem o filtro por location_id, o painel de um cliente mostraria avaliações de todos. Como evitar: Filtre pelo id oficial do cliente dentro da transformação de entrega.
- **Anúncio sobrescrito no upsert.** No Google, o mesmo anúncio aparecia em dois grupos. Com a chave sem o grupo, a segunda linha apagava a primeira. Como evitar: Inclua o conjunto ou grupo na chave de upsert.
- **Portal e Nekt com carteiras diferentes.** Edições feitas no portal depois da extração diária só apareciam na Nekt no dia seguinte, e a contagem de clientes e o MRR divergiram por um dia. Como evitar: Confira a última execução da fonte antes de acusar divergência.
- **Painel parado por fonte de outra camada.** O painel de um cliente dependia de um conector registrado na camada da própria agência. Quando ele falhou, a investigação olhou só a camada do cliente. Como evitar: Documente a camada de origem de cada tabela de entrega.
- **Dois destinos para a mesma tela.** Dois destinos ativos entregavam o mesmo dado ao mesmo app, um mais novo que o outro. Mudar o errado não teve efeito. Como evitar: Um destino por tabela de entrega, com o antigo desligado.

### Teste de aceitação

- [ ] Para um cliente e um mês fechado, o investimento somado na tabela de entrega é igual ao da tabela de mídia da carteira (diferença zero).
- [ ] A chave de upsert não se repete em nenhuma linha da tabela de entrega.
- [ ] Cada destino recebe exatamente um cliente distinto, ou só os clientes autorizados para aquele app.
- [ ] Nenhum telefone entregue tem mais de 4 dígitos legíveis.
- [ ] A data da última carga de cada tabela está dentro do atraso aceitável da sua fonte.

### Context document

```text
Portal do cliente
- O portal não lê a Nekt em tempo real. A Nekt entrega tabelas de consumo (mart_<tela>) no schema de entrada do banco do app, por upsert.
- Cada tabela de entrega é um contrato: nome, colunas, grão e chave fixos. Não renomeie colunas; mudança de grão é tabela nova.
- Chave de upsert da tela de anúncios: date, client, platform, adset_id, ad_id.
- O destino nunca escreve no schema public do app.
- Toda tabela de entrega é filtrada pelo id oficial do cliente (account_id, location_id) dentro da transformação. Fonte compartilhada sem filtro é vazamento.
- Telefone sai como hash do número normalizado mais os últimos 4 dígitos. E-mail e documento saem em hash. Endereço não sai.
- O cliente é ligado à camada pela coluna de identidade do cadastro, nunca pelo nome.
- Para perguntar se o portal está atualizado, leia a data da última carga e a data máxima do dado da tabela de entrega.
- Métricas com nome único entre plataformas; nulo onde a plataforma não mede.
Exceções por cliente:
- <cliente>: destino <destino>, schema <schema>, telas <lista de tabelas mart_>.
- <cliente>: avaliações filtradas por location_id <id> a partir da conta compartilhada da agência.
```

### Variações

- **Endpoint HTTP:** App sem banco acessível: envie a tabela em lotes (ex.: 1.000 linhas por chamada), com o token guardado no cofre de segredos da Nekt, nunca no código.
- **Template da ferramenta antiga:** Para reaproveitar um painel pronto no Looker, entregue a tabela com os mesmos nomes de campo do conector que alimentava o template.
- **Dados do negócio do cliente:** O mesmo padrão serve para telas além do marketing, como avaliações, atendimento ou renovação, sempre com uma tabela por tela.
