Sincronização Completa vs Incremental
Última atualização: 15 de julho de 2026
Visão Geral
Toda sincronização decide, antes de mais nada, se vai trazer cada entidade inteira de novo (full) ou só o que mudou desde a última vez (recent). Essa decisão não é por entidade, é uma única decisão tomada no início, baseada em quem é o usuário e em qual organização ele está agora, comparado com quem estava sincronizado antes.
Arquivos-chave
| Arquivo | Responsabilidade |
|---|---|
services/sync/sync.services.ts | prepareFullSyncMode, buildEntitySyncOptions, isSameSessionIdentity |
storage/sync/sync.storage.ts | getLastUpdatedAt/setLastUpdatedAt por entidade |
A identidade que decide entre full e recent
A identidade de uma sessão é só duas coisas: o id do usuário e o id da organização. Antes de sincronizar, o app compara a identidade guardada da última sincronização com a identidade atual:
// sync.services.ts
private buildCurrentSessionIdentity(authContexts: AuthContextsToken): SyncSessionIdentity {
return {
userId: authContexts.userId?.trim() || undefined,
organizationId: authContexts.organizationId?.trim() || undefined,
};
}
private isSameSessionIdentity(previousIdentity, currentIdentity): boolean {
if (!previousIdentity.userId || !previousIdentity.organizationId ||
!currentIdentity.userId || !currentIdentity.organizationId) {
return false;
}
return previousIdentity.userId === currentIdentity.userId &&
previousIdentity.organizationId === currentIdentity.organizationId;
}
Se qualquer um dos quatro valores estiver ausente, ou se usuário ou organização mudaram, a identidade é considerada diferente. Isso cobre tanto o primeiro login no aparelho (não existe identidade anterior) quanto uma troca de organização do mesmo usuário (o id de usuário é igual, mas o de organização não).
prepareFullSyncMode(previousIdentity, authContexts): "full" | "recent" {
const currentIdentity = this.buildCurrentSessionIdentity(authContexts);
const shouldReuseLocalData = this.isSameSessionIdentity(previousIdentity, currentIdentity);
if (!shouldReuseLocalData) {
this.clearAllLastSyncedAt();
this.setLastUpdatedAt(SYNC_ENTITY.ORGANIZATION);
return "full";
}
return "recent";
}
Quando a identidade muda, o app não só decide por full, ele também zera todos os lastUpdatedAt guardados (clearAllLastSyncedAt), para garantir que nenhuma entidade tente incrementalmente a partir de uma data que pertencia à identidade anterior.
Como cada entidade usa essa decisão
buildEntitySyncOptions traduz o modo full/recent em opções concretas para cada chamada de sincronização de entidade:
private buildEntitySyncOptions(entity: SyncEntity, recentOnly?: boolean) {
if (!recentOnly) {
return { clearLocal: true };
}
const dataHora = this.getLastSyncedAt(entity);
if (!dataHora) {
return { clearLocal: true };
}
return { clearLocal: false, dataHora };
}
Vale notar o segundo caso de fallback: mesmo em modo recent, se uma entidade específica nunca tiver um lastUpdatedAt próprio guardado, ela ainda cai para clearLocal: true. Isso pode acontecer se uma entidade nova for adicionada à sincronização depois que usuários já tinham dados antigos no aparelho.
Nem toda entidade participa da sincronização incremental
Olhando a sequência real de startSyncAll, várias chamadas não recebem { recentOnly } de forma alguma, o que significa que elas são sempre sincronizadas por completo, em toda sincronização, independente da identidade:
await this.syncCarriers(pageSize);
await this.syncCities(pageSize);
await this.syncProductCategoriesBatch(pageSize);
await this.syncProductFilters();
await this.syncFormattedReports(pageSize);
await this.syncPromotions(pageSize);
await this.syncOperations(pageSize);
await this.syncOperationsGroups(pageSize);
await this.syncPaymentMethods(pageSize);
await this.syncCompanies(pageSize);
await this.syncSaleStatuses(pageSize);
Essas são, em geral, entidades de referência relativamente pequenas e que mudam pouco (transportadoras, cidades, formas de pagamento, status de venda), então o custo de sempre buscar tudo de novo é aceitável. Já clients, aliquots, productAliquots, products, alternativeUnits, similarProductsDetailed, suggestedProductsDetailed, stocks, prices e saleDrafts recebem { recentOnly } explicitamente, e de fato participam da sincronização incremental quando a identidade permite.
Armadilhas conhecidas
Antes de assumir que uma entidade está sincronizando incrementalmente, confirme se a chamada correspondente em startSyncAll de fato recebe { recentOnly }. Adicionar uma nova entidade à sincronização sem decidir explicitamente se ela participa do modo incremental é fácil de esquecer, e o padrão da skill do projeto é claro sobre isso: toda decisão de sync deve ser explícita, nunca escondida atrás de um valor padrão implícito.