Modo offline (sem internet) — padrão do app
No offline, o app não perde dados: ele registra os eventos no aparelho, mantém os itens em uma fila/outbox de sincronização e tenta enviar automaticamente quando houver internet. Mesmo com internet disponível, o app deve continuar seguindo esse padrão para evitar duplicidade, inconsistência e perda de dados em campo.
Regra principal (offline-first)
- Toda ação operacional (entrega_kit, coleta_analise, fotos, aplicação etc.) deve primeiro ser salva localmente no aparelho.
- No momento em que o evento local for criado, o app deve gerar um
client_event_idúnico (UUID) e associá-lo permanentemente àquele evento. - Se houver internet, o app pode tentar sincronizar imediatamente; se falhar, o item deve permanecer salvo localmente para reenvio posterior.
- O ERP continua sendo a fonte definitiva. O armazenamento no app é temporário (fila/cache), não é um “mini ERP”.
Validação offline (TTL 72h — sessão do app)
- Quando o app consegue validar online (pareamento,
valida_device_tokenou qualquer endpoint administrativo com HTTP 200), ele atualizaultima_validacao_em. - Se estiver sem internet, o app permite operar por até 72 horas desde a última validação online.
- Após 72 horas sem validar online, o app bloqueia novas ações e solicita conexão para validar novamente.
Fila de sincronização (outbox)
- Todo evento local deve entrar em uma fila/outbox com status de sincronização (pendente, erro, enviado, por exemplo).
- Se um envio falhar por timeout, ausência de internet ou erro transitório, o evento deve continuar salvo localmente e permanecer pendente para novo envio.
- Nos reenvios do mesmo evento, o app deve reutilizar exatamente o mesmo
client_event_id. - Ao receber HTTP 200 com
success=true, o app deve marcar o item como enviado e removê-lo da fila local. - Ao receber HTTP 200 com
success=trueealready_registered=true, o app também deve considerar o item como concluído, removendo-o da fila local. - Somente respostas com
success=falsedevem manter o item pendente para nova tentativa, conforme a política de retry do aplicativo.
Idempotência (evitar duplicar no servidor)
- Para todo evento enviado (entrega_kit, foto, coleta, aplicação etc.), o app deve gerar
e enviar um identificador único
client_event_id(UUID). - Obrigatório: sem
client_event_ido servidor não consegue deduplicar reenvios (offline/timeout/conexão instável). - O ERP controla a idempotência consultando e registrando esse
client_event_idpara garantir que o mesmo evento não seja gravado duas vezes. - O simples fato de o
client_event_idjá existir no ERP significa que aquele evento já foi processado com sucesso. - Se o app reenviar um evento já registrado, o servidor retorna HTTP 200
(
success=true) comalready_registered=truepara o app remover o item da fila.
client_event_id (UUID)
Exemplo de resposta idempotente
{
"success": true,
"msg": "Evento já registrado (idempotente).",
"data": {
"already_registered": true,
"client_event_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
success=true ou already_registered=true = remover da fila
local
Cache de QR Codes (pré-sincronização) — TTL 48h
Alguns fluxos precisam de metadados para orientar o usuário em campo (ex.: tipo/descrição da análise). Para operar offline, o app deve manter um cache temporário de QR Codes e metadados mínimos.
- Quando o app estiver online, ele deve sincronizar com o ERP os QR Codes e metadados necessários
para operação offline (ex.:
kit_entrega,coleta_analise). - O app salva localmente (cache) os QR Codes e os metadados mínimos necessários e define um TTL de 48 horas.
- No momento do scan sem internet, o app pode consultar esse cache local para orientar a operação, desde que o item ainda esteja dentro do TTL.
- Esse cache é apenas apoio operacional; a validação definitiva continua sendo feita pelo ERP no momento da sincronização.
- Se o QR não estiver no cache (ou estiver expirado), o app ainda pode registrar o evento na fila, mas deve informar que a confirmação e/ou instruções específicas dependem de sincronização com o servidor.
- O app não deve depender exclusivamente do cache para concluir a operação no ERP; o cache serve apenas para melhorar a experiência em campo durante a ausência de conexão.
Aplicação (GPS a cada 3 segundos) — recomendado
- Durante a aplicação, a internet pode oscilar. O app não deve enviar ponto a ponto em tempo real.
- O correto é salvar a trilha localmente e sincronizar depois (offline-first).
- Modelo recomendado: salvar uma sessão (início/fim) + os pontos GPS (lat/lon) a cada 3 segundos.
- Na sincronização, enviar em lotes (chunks) para evitar timeout e permitir retomar em caso de falha.
Armazenamento local (recomendação técnica)
- SQLite: recomendado para a fila (outbox), cache de QR e trilhas/pontos de GPS (volume alto e consulta por status).
- FileSystem: fotos/arquivos devem ser salvos localmente e enviados depois.
- SecureStore: credenciais sensíveis (ex.:
device_token). - AsyncStorage: apenas configurações/estado simples (ex.:
account_type,ultima_validacao_em).
Exemplo prático — tabela local de sincronização (SQLite)
Para facilitar a implementação do app, recomenda-se que cada evento operacional seja salvo primeiro
em uma
tabela local de sincronização (outbox). Essa tabela pode ter um id interno
autoincrement para uso
local do app e um client_event_id (UUID) para identificação global do evento durante a
sincronização com o ERP.
id: identificador local do SQLite, apenas para uso interno do app.client_event_id: UUID único do evento, reutilizado em todos os reenvios do mesmo item.tipo_evento: ex.:receber_kit_aplicacao,coleta_analise.payload_json: dados do evento a serem enviados ao ERP.status_sync: ex.:pendente,erro,enviado.tentativas_envio: contador de tentativas.criado_em: data/hora local de criação do evento.enviado_em: data/hora da confirmação de envio, quando houver.
CREATE TABLE app_eventos_sync (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_event_id TEXT NOT NULL UNIQUE,
tipo_evento TEXT NOT NULL,
payload_json TEXT NOT NULL,
status_sync TEXT NOT NULL DEFAULT 'pendente',
tentativas_envio INTEGER NOT NULL DEFAULT 0,
criado_em TEXT NOT NULL,
enviado_em TEXT NULL
);
id autoincrement local + client_event_id UUID para
sincronização
Exemplo prático — criação de um evento local
Quando o usuário realizar uma ação no app, o evento deve nascer primeiro no banco local. Nesse
momento, o app
gera um client_event_id único e salva o item na fila de sincronização.
// Exemplo conceitual
const novoEvento = {
client_event_id: '550e8400-e29b-41d4-a716-446655440000',
tipo_evento: 'coleta_analise',
payload_json: JSON.stringify({
qrcode: 'CA_ABC123',
data: '2026-04-14',
hora: '10:30:00',
latitude: -20.9487,
longitude: -48.4790
}),
status_sync: 'pendente',
tentativas_envio: 0,
criado_em: '2026-04-14 10:30:00'
};
- O evento é salvo localmente antes de qualquer tentativa de envio.
- Se o envio falhar, o mesmo
client_event_iddeve ser mantido no próximo reenvio. - Se a API responder
success=trueoualready_registered=true, o item deve ser marcado comoenviadoe removido da fila local.
Exemplo prático — geração do client_event_id
O client_event_id deve ser gerado no momento em que o evento local é criado. Ele não
depende do ERP
e não substitui o id autoincrement do banco local.
// Exemplo em JavaScript / React Native
function gerarClientEventId() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
const clientEventId = gerarClientEventId();
client_event_id; nunca reutilizar em eventos diferentes
Exemplo prático — sincronização da fila
// Fluxo conceitual
1. Buscar eventos com status_sync = 'pendente' ou 'erro'
2. Enviar o payload ao ERP com o mesmo client_event_id
3. Se a resposta for success=true:
- marcar status_sync = 'enviado'
- preencher enviado_em
- remover da fila local, se essa for a estratégia escolhida
4. Se a resposta for already_registered=true:
- tratar como sucesso
- marcar como enviado
- remover da fila local
5. Se houver erro:
- incrementar tentativas_envio
- manter como pendente ou erro para nova tentativa
O app pode continuar usando id autoincrement no SQLite para controle interno. O
client_event_id é um identificador adicional, do tipo UUID, usado para garantir
idempotência na
comunicação com o ERP. Assim, o mesmo evento pode ser reenviado várias vezes sem risco de
duplicidade no servidor.