Pular para o conteúdo principal

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

ArquivoResponsabilidade
services/sync/sync.services.tsprepareFullSyncMode, buildEntitySyncOptions, isSameSessionIdentity
storage/sync/sync.storage.tsgetLastUpdatedAt/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.