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
| Arquivo | Responsabilidade |
|---|---|
services/sync-entity-monitor/sync-entity-monitor.service.ts | Mutex de sincronização + poller de entidades alteradas |
services/sync-queue/sync-queue.service.ts | Cliente 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.