---
id: saas/historico-de-estado
industria: SaaS e tecnologia
uso: comum
esforco: médio
grao: Entidade x versão (_available_from, _available_until) e entidade x dia
---

# Histórico de estado

Como cada assinatura, conta, dono e indicador de uso estava em qualquer dia, guardado antes que billing, CRM e produto sobrescrevam ou apaguem o passado.

## Resumo executivo

**Para quem:** Empresas de SaaS que precisam responder como estava a base num dia passado: financeiro, RevOps e CS.

**O que resolve**

- MRR, carteira e status de meses passados deixam de mudar com o estado de hoje.
- Uso e eventos que a fonte apaga ficam guardados.
- Uma sincronização que falha não leva o histórico junto.

**Por que importa:** Quase todo número de SaaS que importa é comparação com o passado: NRR, churn, comissão pelo dono do mês, tendência de uso. Se a fonte só guarda o agora, cada um desses números é reconstruído à mão, e sai diferente a cada vez. O passado que não foi gravado não volta.

**Perguntas de negócio que responde**

- Qual era o status da assinatura do cliente X em 30 de junho?
- Quem era o dono desta conta em março?
- Quais clientes pediram cancelamento no fim do ciclo e depois desistiram?
- Como evoluiu o uso desta conta nas últimas 12 semanas?
- Desde quando temos histórico de cada tabela?

**O que você precisa**

- Billing, CRM e produto conectados na Nekt
- SCD Type 2 ligado nas tabelas que mudam no lugar
- A lista dos campos de negócio que importam acompanhar

**O que você recebe**

- Tabelas versionadas com início e fim de vigência
- Fotos diárias de uso e de métricas calculadas
- Um mapa de cobertura com a data de início de cada histórico
- Um agente de IA que responde como estava em uma data

## Detalhado

### O problema

Billing, CRM e produto guardam só o estado de agora. A assinatura que estava past_due ontem hoje aparece active, o dono do deal que vendeu já é outro, o uso de 30 dias do analytics é sobrescrito a cada sincronização, e eventos do Stripe e coleções com TTL somem depois de algumas semanas. Sem histórico gravado não há MRR de meses passados, comissão pelo dono do mês nem tendência de uso. E uma sincronização completa que falha no meio pode apagar meses.

### Regras de negócio

1. **Ligue o histórico nas tabelas de estado, desde já.** Ative o SCD Type 2 da Nekt nas tabelas que mudam no lugar: assinaturas e itens do billing, clientes, deals e empresas do CRM, contas e planos do banco do produto. Cada mudança vira uma versão com _available_from, _available_until e _deleted. O que veio antes não volta: para MRR, reconstrua pelas faturas; para dono e etapa, pelo histórico de propriedades do CRM. (Evite: Calcular o passado com a tabela de hoje.)
2. **Fonte que se apaga pede acumulador.** Eventos do Stripe ficam disponíveis por cerca de 30 a 40 dias, coleções do MongoDB com TTL apagam documentos (cerca de 45 dias num caso real) e gráficos do Amplitude e planilhas de métricas devolvem só uma janela de 30 dias sobrescrita. Acumule numa tabela própria por dia e id, com merge, reprocessando os últimos dias a cada execução para que uma falha se recupere sozinha. (Evite: Confiar que a fonte guarda o histórico de eventos e de uso.)
3. **Foto de número calculado é notebook, não SQL.** Transformação SQL na Nekt sobrescreve a tabela inteira a cada execução. Para fotografar MRR do dia, carteira ou uso agregado, use notebook com mode merge, ou uma query que lê a própria saída e mantém todos os dias anteriores, substituindo só o dia corrente. (Evite: Uma query SQL que recalcula a série inteira a partir do estado de hoje.)
4. **Nunca sobrescreva com vazio.** Antes de gravar, a rotina recusa resultado vazio ou muito menor que o anterior, salvo autorização explícita. Antes de uma sincronização completa de uma fonte grande, tire uma cópia congelada: um full sync que falhou no meio já apagou quase cinco meses de eventos de uma tabela. (Evite: Gravar o que vier, mesmo que venha vazio.)
5. **Versão nova não é evento de negócio.** No billing, a maior parte das versões novas só avança o ciclo de cobrança ou troca a última fatura. Para contar mudança de status, plano, quantidade, preço ou aviso de cancelamento no fim do ciclo (cancel_at_period_end), compare o campo de negócio com a versão anterior e conte só quando ele mudou. (Evite: Contar cada versão nova como uma mudança da assinatura.)
6. **Dia de corte no fuso do negócio.** O estado do dia D é a versão vigente em D+1 00:00 de Brasília, ou seja, D+1 03:00 UTC. Com várias versões no mesmo dia, vale a última. Rode a sessão em UTC e converta só no corte, uma vez. (Evite: Cortar o dia em UTC, ou converter o fuso duas vezes.)
7. **Dono do mês tem dia de corte.** Para carteira, NDR e comissão, o dono de um mês passado é o dono vigente num dia combinado (por exemplo, dia 5, dando ao time do dia 1 ao 4 para transferir contas). Trocas depois do corte ficam marcadas como troca no meio do mês, sem mudar o dono de base. O mês corrente usa o dono mais recente. (Evite: Atribuir todo o histórico ao dono de hoje.)
8. **O histórico do CRM vem das propriedades.** Quando o SCD não foi ligado a tempo, recupere etapa, valor e dono pelo histórico de propriedades do HubSpot (propertiesWithHistory, uma entrada por mudança com valor, data e origem). Se o conector não traz a propriedade de que você precisa, um script temporário busca na API, marcado como temporário e com data de remoção. (Evite: Ler só o valor atual da propriedade.)
9. **Reconstruído não é registrado.** Passado reconstruído por faturas, logs ou backfill fica marcado com a origem. Modelos e indicadores usam só o que estava disponível no fim do período anterior (point-in-time). Num caso real, atributos de contrato reconstruídos por backfill contaminaram um modelo de churn e tiveram de ser retirados. (Evite: Misturar histórico gravado e reconstruído sem marca.)
10. **Correção é ajuste, nunca reescrita.** Mês fechado é lido da foto congelada. Uma reclassificação histórica é feita uma vez, documentada com data e motivo, e não se repete a cada execução. Correção posterior entra como ajuste no mês em que foi descoberta. (Evite: Recalcular meses fechados a cada execução.)

### O que guardar e como

Cada tipo de dado de SaaS tem um jeito de não perder o passado.

- **Assinaturas e itens do billing** → SCD Type 2 na origem (`_available_from / _available_until`)
- **Eventos de mudança do Stripe (30 a 40 dias)** → Acumulador por id do evento
- **Deals, dono e etapa do CRM** → SCD ou histórico de propriedades (`propertiesWithHistory`)
- **Uso de 30 dias do analytics ou planilha** → Foto diária por conta
- **Coleção do produto com TTL** → Merge diário por dia e id
- **MRR do dia, carteira, score** → Notebook com merge ou query auto-referente
- **Tabela grande antes de um full sync** → Cópia congelada manual

### Definições

| Métrica | Definição |
|---|---|
| Versão vigente | linha com _available_until nulo e _deleted falso |
| Estado na data D | versão com _available_from até D+1 03:00 UTC (meia-noite de Brasília) e _available_until nulo ou posterior a esse instante |
| Foto diária | uma linha por entidade e dia com o estado de fim de dia, regravável só dentro da janela de reprocessamento declarada |
| Mudança de negócio | versão em que um campo declarado (status, plano, quantidade, preço, cancel_at_period_end, dono, etapa) difere da versão anterior |
| Dono do mês | dono vigente no dia de corte do mês para meses fechados; o mais recente no mês corrente |
| Cobertura do histórico | data da primeira versão ou foto gravada de cada tabela; antes dela, só reconstrução marcada |
| Lacuna | dia sem foto gravada numa fonte acumulada, listado e nunca preenchido com zero |

### Armadilhas

- **Meses apagados por um full sync.** Uma sincronização completa de eventos de produto falhou no meio e apagou quase cinco meses da tabela. Como evitar: Cópia congelada antes de qualquer full sync de fonte grande.
- **Foto acumulada gravada por cima.** Uma execução sobrescreveu a tabela de fotos diárias e três semanas tiveram de ser reconstruídas à mão. Como evitar: Query auto-referente que só troca o dia corrente, e trava contra resultado vazio.
- **Sem tendência no health score.** A coleção de conversas do produto apagava documentos com mais de 45 dias, e o componente de tendência nunca pôde ser calculado. Como evitar: Acumulador diário por cliente desde o primeiro dia.
- **Uso que some no dia seguinte.** A planilha de métricas trazia só os últimos 30 dias, sobrescritos todo dia; não havia como saber se 45% de adesão vinha subindo de 20% ou caindo de 80%. Como evitar: Foto semanal ou diária acumulada por conta.
- **Histórico de dono que não chegou.** O conector não trazia o histórico do dono dos deals, e a carteira por mês ficava com o dono de hoje. Como evitar: Script temporário pela API, marcado para remoção quando o conector trouxer o campo.
- **Modelo treinado com o futuro.** Atributos de contrato preenchidos por backfill vazaram informação posterior para o treino de um modelo de churn. Como evitar: Features só com o que existia no fim do período anterior.

Validado em dados reais: No histórico das assinaturas do Stripe da própria Nekt, cerca de 7 em cada 10 versões novas não mudaram status, itens nem aviso de cancelamento: quase todas só avançaram o ciclo de cobrança. Só cerca de 2 em cada 10 mudaram o status. Contar versões como mudanças inflaria os eventos de assinatura mais de 3 vezes.

### Teste de aceitação

- [ ] Para 20 assinaturas, o status na data D lido do histórico bate com a fatura ou com o relatório do billing daquele dia.
- [ ] Cada entidade tem exatamente uma versão vigente em qualquer instante, sem sobreposição de vigências.
- [ ] A foto diária tem uma linha por entidade e dia desde a primeira coleta, e as lacunas aparecem listadas.
- [ ] Rodar a rotina duas vezes no mesmo dia não muda as fotos dos dias anteriores.
- [ ] Uma execução com resultado vazio falha sem gravar.

### Context document

```text
Histórico de estado
- Para qualquer pergunta sobre o passado, use a tabela versionada ou a foto diária, nunca a tabela de estado atual.
- Estado na data D = versão com _available_from até D+1 03:00 UTC e _available_until nulo ou posterior. Vigente hoje = _available_until nulo e _deleted falso.
- Versão nova não é mudança de negócio. Mudança = status, plano, quantidade, preço, cancel_at_period_end, dono ou etapa diferente da versão anterior.
- Dono do mês = dono vigente no dia <dia de corte> do mês; no mês corrente, o atual.
- Cobertura: <tabela> tem histórico desde <data>. Antes disso, o dado é reconstruído e está marcado; diga isso na resposta.
- Dia sem foto é lacuna, não zero.
- Mês fechado vem da foto congelada; não recalcule.
Exceções por cliente:
- <cliente>: o histórico de dono dos deals vem de um script temporário até <data>.
- <cliente>: reclassificação única feita em <data> pelo motivo <motivo>; não reaplicar.
```

### Variações

- **Períodos de congelamento:** Congelamento e desativação de clientes reconstruídos em períodos (início e fim) a partir de eventos de automação, sem sobreposição.
- **Score versionado:** Lead score e health score gravados por dia, para medir se a régua acertou sem reescrever o que o time viu.
- **Base para previsão:** Atributos por conta e mês calculados no fim do mês anterior, para prever churn e NRR sem vazar o futuro.
