---
id: casos/scd-type-2
area: Dados
uso: comum
esforco: médio
grao: Entidade x versão (_available_from, _available_until) ou entidade x dia da foto
---

# Histórico com SCD Type 2

Como cada cliente, contrato ou negócio estava em qualquer data passada, com o dono, o plano e o status da época, sem o estado de hoje reescrever o passado.

## Resumo executivo

**Para quem:** Empresas que precisam responder como algo estava numa data passada, com fontes que só mostram o estado de hoje.

**O que resolve**

- O número de um mês passado não muda quando o cadastro muda hoje.
- Cada fato é cruzado com o dono, o plano e o status vigentes na data dele.
- O dado que a fonte apaga depois de alguns dias fica guardado.

**Por que importa:** Billing, CRM e banco do produto guardam só o estado atual: o plano antigo desaparece, o dono do negócio é o de hoje e o status de ontem é sobrescrito. Sem histórico não existe MRR retroativo, comissão pelo dono do mês, carteira em data passada nem tendência para health score. E o histórico só existe a partir do dia em que é ligado, então cada dia sem ele é um dia perdido.

**Perguntas de negócio que responde**

- Qual era o plano e o status deste cliente no fim de março?
- Quem era o responsável por cada negócio quando ele foi ganho?
- Quantos contratos ou imóveis estavam ativos no último dia de cada mês?
- Qual era o segmento da empresa quando ela comprou?
- O que mudou neste cadastro na última semana, e quando?

**O que você precisa**

- As fontes conectadas na Nekt, com o histórico (SCD Type 2) ligado nas tabelas de estado
- A chave de negócio estável de cada entidade
- A lista de campos cuja mudança importa para o negócio

**O que você recebe**

- Uma tabela de versões por entidade, com início e fim de vigência
- Uma visão só com a versão vigente para o dia a dia
- Fotos de fim de dia ou de fim de mês para as métricas
- Um agente de IA que responde como algo estava em qualquer data

## Detalhado

### O problema

A maior parte das fontes devolve só a foto de agora. Quando o cliente muda de plano, de dono ou de status, o valor antigo é sobrescrito e todo relatório do passado passa a usar o valor novo. Numa base real, mudar o segmento, o executivo ou o valor mensal de uma empresa reescrevia todas as vendas antigas dela. Outras fontes só guardam 30 ou 45 dias, e a cada sincronização completa o passado encolhe.

### Regras de negócio

1. **Ligue o histórico na fonte, desde já.** Na Nekt, ative o SCD Type 2 na tabela de origem. Cada mudança vira uma linha nova com _available_from (início da vigência), _available_until (fim, nulo na versão vigente) e _deleted. O histórico vale a partir do dia em que foi ligado: a primeira versão de cada entidade é o estado naquele dia, não na criação. (Evite: Esperar alguém pedir o histórico para ligar.)
2. **A entidade é a chave de negócio, não a substituta.** Cada versão pode ganhar um código substituto novo. Numa dimensão versionada de corretora, os códigos mudavam a cada versão da mesma conta. Identifique a entidade pela chave de negócio estável (id da assinatura, código da conta, id do negócio) e use o código substituto só para ligar o fato à versão daquela data. (Evite: Contar contas pelo código substituto.)
3. **Pergunta no passado usa o intervalo.** O estado numa data é a versão em que _available_from é menor ou igual à data e _available_until é nulo ou maior que ela. Para cruzar um fato com uma dimensão (o assessor da captação, o dono do negócio ganho, o plano da fatura), use a data do fato, não a versão de hoje. (Evite: Cruzar a captação de março com o assessor atual do cliente.)
4. **Vigente é _available_until nulo.** Para o dia a dia, publique uma visão só com a versão vigente (_available_until nulo e _deleted falso) e mantenha a linha do tempo completa numa tabela à parte. Quem consulta a tabela de versões sem filtro conta cada entidade várias vezes. (Evite: Somar a tabela de versões como se fosse o cadastro.)
5. **Vigência em UTC, corte no fuso do negócio.** _available_from e _available_until são instantes em UTC. Converta para o fuso do negócio (America/Sao_Paulo) antes de cortar o dia ou o mês, senão mudanças feitas à noite caem no dia seguinte e a foto de fim de mês pega a versão errada. (Evite: Truncar o timestamp em UTC para data.)
6. **Várias versões no dia: vale a última.** O SCD grava uma versão por sincronização que encontrou mudança, então a mesma entidade pode ter várias versões no mesmo dia. Na foto diária, use a última versão de cada entidade em cada dia. Na foto de fim de mês, use a versão vigente no último instante do mês, com o mesmo corte para todas as entidades. (Evite: Usar a primeira versão do dia, ou a última linha que cada entidade teve no mês.)
7. **Versão nova não é evento de negócio.** Uma versão nova pode ter mudado só o período de cobrança, a última fatura ou outro campo técnico. Para contar mudanças de status, de dono ou de plano, compare o campo com o da versão anterior. Colunas de controle como _nekt_sync_at nunca entram na comparação, e _available_from diz quando a sincronização viu a mudança, não quando ela aconteceu na fonte. (Evite: Contar versões como mudanças de status.)
8. **Fonte com retenção curta pede um acumulador.** Algumas APIs guardam poucos dias: eventos do Stripe por cerca de 30, analytics de e-mail por cerca de 45, coleções com TTL no banco. A sincronização completa reescreve a tabela com a janela vigente e apaga o resto. Acumule numa tabela própria por merge, com chave e dia, que só cresce, e nunca troque esse merge por overwrite. (Evite: Tratar a tabela bruta como arquivo histórico.)
9. **Foto de tabela derivada pede notebook.** Uma transformação SQL reconstrói a tabela inteira a cada execução e não guarda passado. Para fotografar um número calculado (carteira, saldo, MRR do dia), use um notebook que lê a própria saída, mantém os dias anteriores e substitui só o dia atual. Assim, rodar duas vezes no mesmo dia não duplica a foto. (Evite: Gravar a foto do dia por append, sem chave.)
10. **Mês fechado não se recalcula.** O resultado de um mês fechado é lido da foto congelada, não recalculado com a regra ou o cadastro de hoje. Correções posteriores entram como ajuste no mês em que foram descobertas. Numa base real, uma qualificação recalculada a cada execução fez o número de um mês encolher depois de apresentado. (Evite: Recalcular todos os meses a cada execução.)

### Que tipo de tabela responde a pergunta

Classifique a fonte antes de responder qualquer coisa sobre o tempo.

- **Cadastro atual** → Só responde sobre hoje (`sobrescreve o passado`)
- **Versões (SCD Type 2)** → Estado em qualquer data (`_available_from <= data < _available_until`)
- **Foto diária** → Estado no fim de cada dia (`1 linha por entidade e dia`)
- **Foto de fim de mês** → Fechamento congelado (`último instante do mês, no fuso local`)
- **Histórico de eventos da fonte** → Quando cada mudança aconteceu (`propertieswithhistory (HubSpot), stage_history (Bitrix24)`)
- **Última observação conhecida** → Cada entidade na sua data mais recente (`informar a defasagem entre entidades`)

### Definições

| Métrica | Definição |
|---|---|
| Versão | linha da entidade válida de _available_from (inclusivo) até _available_until (exclusivo); a vigente tem _available_until nulo |
| Estado na data D | versão da entidade em que _available_from <= fim do dia D e (_available_until nulo ou maior), com D no fuso do negócio |
| Foto diária | última versão de cada entidade em cada dia, 1 linha por entidade e dia |
| Foto de fim de mês | estado de cada entidade no último instante do mês, no fuso do negócio, com o mesmo corte para todas |
| Início do histórico | menor _available_from da tabela; antes dele só há estado reconstruído por eventos, faturas ou histórico de etapa |
| Mudança de campo | versão em que o valor do campo difere do valor na versão anterior da mesma entidade |
| Vigência quebrada | entidade sem nenhuma versão vigente, ou com duas versões vigentes ao mesmo tempo |
| Data do negócio | data em que o fato aconteceu (criação, ganho, cancelamento); nunca _nekt_sync_at nem _available_from |

### Armadilhas

- **Overwrite apagou a série.** Uma foto diária foi sobrescrita por engano e quase três semanas de histórico tiveram de ser reconstruídas. Como evitar: Ponha uma guarda antes de gravar: recuse saída vazia e aborte se algum dia já gravado desaparecer.
- **A sincronização completa comeu o arquivo.** A sincronização completa de uma fonte de analytics reescreveu a tabela com a janela vigente e apagou mais da metade das linhas de uma vez. Como evitar: Acumule numa tabela própria por merge e nunca use a tabela bruta como arquivo.
- **Vigência quebrada na fonte.** Uma dimensão versionada vinda de um parceiro tinha contas sem nenhuma versão marcada como vigente, uma delas com dezenas de versões. Como evitar: Teste uma versão vigente por entidade todos os dias e use o intervalo de datas, não a flag de vigente da fonte.
- **O passado com o dono de hoje.** A comissão cruzava negócios ganhos em meses passados com o responsável atual, e quem trocou de carteira levava vendas que não fez. Como evitar: Cruze pelo responsável vigente na data do ganho.
- **A primeira versão não é o começo.** A primeira versão de cada entidade era a do dia em que o histórico foi ligado. Lida como se valesse desde a criação, ela atribuía o segmento e o plano de hoje a vendas antigas. Como evitar: Registre o início do histórico e reconstrua o período anterior por eventos, faturas ou histórico de etapa.
- **Backfill com data de carga.** Um backfill gravou atributos de contrato com a data da carga e contaminou o cálculo de expansão dos meses anteriores. Como evitar: Marque as linhas de backfill e nunca use a data da carga como data do negócio.

Validado em dados reais: Na conta de Stripe da própria Nekt, cerca de 7 em cada 10 assinaturas já existiam antes do primeiro registro do histórico, então a primeira versão não é o começo da assinatura. Cada assinatura tem exatamente uma versão vigente e os intervalos são contínuos, sem buraco nem sobreposição. E só cerca de 1 em cada 5 versões novas mudou o status: a maioria mudou a última fatura ou o período de cobrança, por isso contar versões não é contar mudanças de status.

### Teste de aceitação

- [ ] Cada entidade não excluída tem exatamente uma versão com _available_until nulo.
- [ ] Os intervalos de cada entidade são contínuos: o fim de uma versão é o início da seguinte, sem buraco nem sobreposição.
- [ ] A visão vigente tem o mesmo número de entidades e os mesmos valores que o cadastro atual da fonte.
- [ ] Rodar o pipeline de novo não altera a foto de nenhum mês fechado.
- [ ] Rodar a foto diária duas vezes no mesmo dia não cria linha nova.

### Context document

```text
Histórico e datas
- Antes de responder sobre o tempo, classifique a tabela: cadastro atual, versões (SCD Type 2), foto diária, foto mensal, histórico de eventos ou última observação conhecida. Não chame cadastro atual de histórico.
- Tabelas com SCD Type 2 têm _available_from, _available_until e _deleted. Versão vigente: _available_until nulo e _deleted falso.
- Estado numa data: _available_from <= data e (_available_until nulo ou > data). Converta de UTC para America/Sao_Paulo antes de cortar dia ou mês.
- Cruze cada fato com a versão vigente na data do fato (dono, plano, status, segmento), nunca com a de hoje.
- A entidade é a chave de negócio. Códigos substitutos mudam a cada versão.
- Versão nova não é mudança de status: compare o campo com a versão anterior.
- _nekt_sync_at e _available_from não são data do negócio. Use a data do evento.
- O histórico começa no menor _available_from da tabela. Antes disso, diga que não há histórico ou use a fonte de eventos indicada.
- Meses fechados vêm da foto congelada. Não recalcule com o cadastro atual.
Exceções por cliente:
- <cliente>: histórico de <tabela> ligado em <data>; antes disso, usar <tabela de eventos>.
- <cliente>: foto de fim de mês com corte às <hora> do último dia útil.
```

### Variações

- **Histórico nativo da fonte:** Quando a fonte guarda histórico (propertieswithhistory do HubSpot, stage_history do Bitrix24, log de eventos), use-o para o período anterior ao SCD.
- **Fotos de métricas:** Para números calculados (saldo bancário, estoque, carteira), uma foto diária por merge, com retenção definida.
- **Webhook de mudança:** Para CRMs sem histórico de etapa, um webhook grava cada mudança como evento, com o valor de antes e o de depois.
