Pular para o conteúdo principal

Estado, Modal e Recuperação de Falha

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

Visão Geral​

Uma sincronização pode ser interrompida a qualquer momento, o app pode ser fechado, o sistema pode matar o processo, a bateria pode acabar. O SyncProvider trata isso como um caso esperado, não como uma exceção: o progresso é persistido a cada mudança, e ao reabrir o app, uma sincronização que ficou presa em "em andamento" é reclassificada como erro recuperável, preservando o que já tinha sido concluído.

Arquivos-chave​

ArquivoResponsabilidade
providers/sync/sync.provider.tsxEstado, persistência, detecção de interrupção, retomada
storage/sync/sync.storage.tsgetSyncState/setSyncState, com pub-sub reativo
features/sync/sync.tsx + sync.modal.tsxModal de progresso, lista de erros, ação de minimizar
services/sync-progress-notification/sync-progress-notification.service.tsEspelha o progresso numa notificação Android

O estado é persistido a cada mudança​

Todo estado de sincronização, com a lista de passos e o status de cada um, é gravado em MMKV a cada atualização, exceto quando está idle ou success (nesses casos o estado guardado é simplesmente removido):

// sync.provider.tsx
useEffect(() => {
if (state.status === "idle" || state.status === "success") {
clearSyncState();
} else {
setSyncState(state as unknown as Record<string, unknown>);
}
}, [state]);

Detectando uma sincronização interrompida​

Ao montar o provider, restoreOrBuildInitialState lê o que sobrou de uma sessão anterior. Se o status salvo ainda for "running", é sinal de que o app foi fechado no meio da sincronização, sem chance de marcar nada como erro. O provider então reclassifica esse estado como erro, e qualquer passo que estivesse "active" no momento da interrupção recebe uma mensagem específica:

// sync.provider.tsx
if (status === "running") {
return {
status: "error",
startedAt: raw.startedAt,
finishedAt: nowIso(),
runOptions,
steps: (steps ?? buildInitialState().steps).map((s) => ({
...s,
status: s.status === "active" ? "error" : s.status,
errors: s.status === "active" ? ["Sincronização interrompida"] : s.errors,
})),
};
}

Como o modal de sincronização abre automaticamente sempre que o status é "running" ou "error", isso garante que o vendedor veja essa situação e possa decidir retomar assim que abrir o app de novo, em vez de continuar operando com dados possivelmente incompletos sem saber.

Retomando de onde parou​

Nem todo reinício depois de uma falha refaz tudo. Se pelo menos um passo já tinha terminado ("done") quando o erro aconteceu, o próximo startSyncAll entra em modo de retomada:

function shouldResumeSync(state: SyncState): boolean {
if (state.status !== "error") return false;
return state.steps.some((step) => step.status === "done");
}

Passos concluídos são preservados como estão (só reafirmando progresso 100% e limpando erros antigos), enquanto os demais voltam para "pending", prontos para rodar de novo:

function clearRetryableStepState(step: SyncStep): SyncStep {
if (step.status === "done") {
return { ...step, errors: [], progress: 100 };
}
return { ...step, status: "pending", errors: [], progress: undefined, startedAt: undefined, finishedAt: undefined };
}

Dentro de startSyncAll, cada passo já concluído é pulado de fato, não só visualmente: runStepIfNeeded verifica um conjunto de ids já concluídos antes de decidir se chama runStep de novo.

O modal de progresso​

features/sync/sync.tsx (o conteúdo) e sync.modal.tsx (o wrapper de Modal do React Native) mostram uma barra de progresso geral, um card por etapa com seu status e tempo decorrido, e uma lista de erros que pode ser expandida ou escondida:

<Text style={styles.secondaryActionText}>
{showErrors ? "Ocultar erros" : "Exibir erros"}
</Text>
<Text style={styles.retryActionText}>Tentar novamente</Text>

Minimizar a sincronização (fechar o modal sem cancelar o processo) pede confirmação enquanto ela ainda está rodando, avisando que os dados podem aparecer incompletos até o fim:

Alert.alert(
"Minimizar sincronização?",
"Os dados ainda não estão totalmente disponíveis e algumas informações do app podem aparecer incompletas até o fim da sincronização.",
[
{ text: "Continuar sincronizando", style: "cancel" },
{ text: "Minimizar", onPress: onClose },
],
);

O mesmo progresso também é espelhado numa notificação Android, através de syncProgressNotificationServiceInstance.syncState(state), chamado a cada mudança de estado, para que o vendedor acompanhe a sincronização mesmo com o app em segundo plano.

Armadilhas conhecidas​

O provider tem dois efeitos separados, ambos comentados como "Control modal visibility based on status", reagindo à mesma transição de state.status para "success". Um só chama setIsModalVisible(false) depois de um setTimeout, o outro chama closeModal() (que já faz o mesmo setIsModalVisible(false), além de limpar o estado persistido) depois de um setTimeout equivalente. Não é um bug que quebra o fluxo, os dois convergem para o mesmo resultado, mas é uma duplicação real. Ao tocar nessa lógica de fechamento automático do modal, edite os dois efeitos, ou melhor, aproveite para unificá-los num só.