Pular para o conteúdo principal

Monitoramento Contínuo e Filas de Retry

Última atualização: 15 de julho de 2026

Visão Geral

Além da sincronização completa disparada manualmente ou no login, o SyncProvider mantém três serviços rodando em segundo plano enquanto o app está aberto: um monitor que verifica periodicamente se alguma entidade mudou no servidor, um serviço que acompanha pedidos aguardando integração com o ERP, e a fila de reenvio automático de vendas que falharam por conexão, já documentada em Salvar e Enviar. Os três são ligados juntos, no mesmo efeito do provider:

// sync.provider.tsx
useEffect(() => {
syncEntityMonitorRef.current.start();
awaitingIntegrationRefreshServiceInstance.start();
failedSaleDraftAutoSendServiceInstance.start({
onSuccess: (message) => toast.show(message, "success", 3500, "top"),
onError: (message) => toast.show(message, "error", 4500, "top"),
});

return () => {
syncEntityMonitorRef.current.stop();
awaitingIntegrationRefreshServiceInstance.stop();
failedSaleDraftAutoSendServiceInstance.stop();
};
}, []);

Arquivos-chave

ArquivoResponsabilidade
services/sync-entity-monitor/sync-entity-monitor.service.tsMutex de sincronização + poller de entidades alteradas
services/sync-queue/sync-queue.service.tsCliente HTTP do endpoint de entidades alteradas
services/awaiting-integration-refresh/*Acompanha pedidos aguardando integração com o ERP

Um mutex compartilhado entre partes bem diferentes do app

SyncEntityMonitorService.registerSyncActivity/isSyncInProgress não serve só para o próprio monitor. É o mecanismo que várias features usam para avisar "existe uma sincronização em andamento agora, não comece outra por cima":

registerSyncActivity(source: string): () => void {
const currentCount = this.activeSyncSources.get(source) ?? 0;
this.activeSyncSources.set(source, currentCount + 1);
return () => { /* decrementa e remove quando chegar a zero */ };
}

isSyncInProgress(): boolean {
return this.activeSyncSources.size > 0;
}

O próprio SyncProvider.startSyncAll se registra com a chave "sync-provider:sync-all" antes de rodar a sincronização completa. O reenvio automático de venda, documentado em Salvar e Enviar, consulta isSyncInProgress() antes de rodar seu próprio ciclo. E o refresh de rascunhos depois de um envio bem-sucedido se registra com "save-draft-refresh" antes de disparar um syncSaleDrafts pontual. Cada fonte é contada separadamente (por isso um contador, não um booleano), então duas fontes diferentes registrando atividade ao mesmo tempo não se cancelam uma à outra por engano.

O monitor de entidades alteradas é um poller com intervalo configurável

Como a fila de reenvio de vendas, esse serviço se reagenda sozinho, com o intervalo lido de uma setting da organização:

// sync-entity-monitor.service.ts
private scheduleNextRun(): void {
if (!this.isStarted) return;

const interval = Math.max(1000, settings.get("cfg-intervalo-sincronizacao-entidades"));

this.timer = setTimeout(() => {
void this.runScheduledCycle();
}, interval);
}

private async runScheduledCycle(): Promise<void> {
try {
await this.pollOnce();
} finally {
this.scheduleNextRun();
}
}

Cada ciclo pergunta ao servidor quais entidades mudaram, e só dispara sincronização incremental para as que de fato mudaram, em vez de rodar a sequência completa de sincronização a cada intervalo:

async pollOnce(): Promise<SyncEntity[]> {
if (this.isPolling || this.isSyncInProgress()) return [];

const changedEntities = await this.syncQueueService.fetchChangedEntities(
MOCKED_CHANGED_ENTITIES_DATAHORA,
);
const pendingSyncs = this.buildPendingSyncs(changedEntities);
if (pendingSyncs.length === 0) return [];

const releaseSyncActivity = this.registerSyncActivity("sync-entity-monitor:poll");
// ... sincroniza só as entidades pendentes, uma a uma
}

O poller consulta sempre a mesma data fixa, não a data real da última verificação

Esse é o ponto mais importante deste documento: o parâmetro que deveria levar a data da última verificação é ignorado. fetchChangedEntities recebe um argumento _dataHora (o underscore no nome já indica que ele não é usado) e, na prática, sempre consulta o servidor com uma constante fixa:

// sync-queue.service.ts
export const MOCKED_CHANGED_ENTITIES_DATAHORA = "2024-05-20T17:00:00Z";

async fetchChangedEntities(_dataHora: string): Promise<ChangedEntity[]> {
const queryParams = buildQueryParams({ dataHora: MOCKED_CHANGED_ENTITIES_DATAHORA });
const res = await this.apiWithoutAccessToken.get<ChangedEntity[]>(
"/historicoIntegracao/entidadesAlteradas",
{ params: queryParams },
);
// ...
}

Isso significa que, em produção, esse poller não está de fato verificando "o que mudou desde a última checagem". Ele consulta sempre o mesmo ponto fixo no tempo, bem no passado. Dependendo de como o servidor responde a essa consulta, isso pode variar entre "nunca encontra nada relevante" e "sempre encontra as mesmas entidades", mas em nenhum dos dois casos o comportamento é o de um monitoramento incremental real.

Não existe sincronização em segundo plano no sentido de tarefa do sistema operacional

Vale deixar claro: nenhum desses serviços usa expo-background-fetch, expo-task-manager, ou qualquer outro mecanismo de tarefa agendada pelo sistema operacional para rodar com o app fechado. O que existe são laços de setTimeout dentro do próprio JavaScript do app, iniciados pelo SyncProvider enquanto ele está montado. Fechar o app por completo interrompe esses laços, e eles só recomeçam quando o app é reaberto e o provider monta de novo. Um arquivo chamado services/background/background.service.ts existe e usa react-native-background-actions, mas ele mantém o app "vivo" em primeiro plano para uma tarefa específica, não é um agendador de sincronização periódica.

Armadilhas conhecidas

Não trate o monitor de entidades alteradas como uma fonte confiável de atualização quase em tempo real. Enquanto fetchChangedEntities ignorar a data recebida, a sincronização completa e incremental já documentada é o mecanismo real de manter os dados em dia, não esse poller. Se for corrigir isso, o ajuste é trocar MOCKED_CHANGED_ENTITIES_DATAHORA pelo _dataHora recebido, e então validar que o endpoint /historicoIntegracao/entidadesAlteradas responde de forma coerente a datas variáveis antes de confiar no resultado.