3) Modo offline e sincronização

Regras completas para funcionamento offline-first, fila local, autenticação, envio de eventos e atualização dos dados da UTG recebidos do ERP.

Offline-first Limite offline: 4 dias SQLite + fila local Idempotência por client_event_id X-APP-KEY + X-DEVICE-TOKEN

Resumo

Depois que o aparelho estiver pareado, aprovado e possuir um device_token, a UTG poderá utilizar o aplicativo sem internet por até 4 dias, contados a partir da última validação online bem-sucedida.

Toda ação operacional deve ser salva primeiro no SQLite. Havendo internet, o aplicativo tenta sincronizar. Se o envio falhar, o registro permanece na fila local para nova tentativa.

Importante: primeiro acesso

O endpoint f=sincronizacao_offline somente pode ser chamado depois do pareamento, pois exige X-DEVICE-TOKEN.

Antes do pareamento, utilizar exclusivamente os endpoints parear_device_utg e valida_device_token, conforme a documentação de primeiro acesso e login automático.

Regras obrigatórias

  • Salvar toda ação primeiro no SQLite.
  • Gerar um client_event_id UUID único para cada evento.
  • Manter o mesmo client_event_id em todas as tentativas de reenvio.
  • Não apagar o registro local antes da confirmação do ERP.
  • Salvar fotos e arquivos no FileSystem até o envio ser confirmado.
  • Salvar o device_token somente no SecureStore.
  • Não confiar em id_utg enviado pelo aplicativo; a API deve identificar a UTG pelo token.
  • O ERP é a fonte oficial dos dados.

Limite de uso offline: 4 dias

  • Após validação online bem-sucedida, salvar ultima_validacao_em e offline_ate.
  • Sem internet, permitir o uso por até 4 dias desde a última validação.
  • Durante esse período, as ações continuam sendo registradas normalmente no aparelho.
  • Após 4 dias sem validação online, bloquear novas ações que dependam de autorização.
  • Exibir mensagem solicitando conexão com a internet.
  • Não apagar eventos pendentes quando o prazo offline terminar.
SE agora <= offline_ate
→ permitir uso offline

SE agora > offline_ate
→ bloquear novas ações
→ manter eventos já salvos
→ solicitar conexão com a internet

Fila de sincronização

  1. Criar o evento no SQLite.
  2. Gerar um client_event_id.
  3. Salvar o tipo, endpoint, payload e arquivos relacionados.
  4. Definir status_sync = pendente.
  5. Se houver internet, tentar enviar.
  6. Se falhar, manter o evento na fila.
  7. Ao receber confirmação do ERP, marcar como enviado.
  8. Remover da fila somente após confirmação explícita.

Idempotência

Todo endpoint que grava uma ação deve receber um client_event_id. Esse identificador impede duplicidade quando o aplicativo reenviar o mesmo evento após timeout, perda de conexão ou resposta não recebida.

Resposta para evento já registrado

{
  "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 significa que o evento pode ser retirado da fila de envio.

Tabelas locais

Dados do aparelho

CREATE TABLE app_dispositivo (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  device_id INTEGER,
  device_name TEXT NOT NULL,
  account_type TEXT NOT NULL DEFAULT 'utg',
  id_utg INTEGER,
  nome_utg TEXT,
  id_programa INTEGER,
  nome_programa TEXT,
  ultima_validacao_em TEXT,
  ultimo_login_online_em TEXT,
  offline_ate TEXT,
  criado_em TEXT NOT NULL,
  atualizado_em TEXT
);

Fila de eventos

CREATE TABLE app_eventos_sync (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  client_event_id TEXT NOT NULL UNIQUE,
  tipo_evento TEXT NOT NULL,
  endpoint TEXT NOT NULL,
  payload_json TEXT NOT NULL,
  arquivo_local TEXT NULL,
  status_sync TEXT NOT NULL DEFAULT 'pendente',
  tentativas_envio INTEGER NOT NULL DEFAULT 0,
  ultimo_erro TEXT NULL,
  criado_em TEXT NOT NULL,
  ultima_tentativa_em TEXT NULL,
  enviado_em TEXT NULL
);

Geração do client_event_id

Usar uma biblioteca UUID compatível com React Native. Não usar Math.random() para eventos definitivos.

import * as Crypto from 'expo-crypto';

const clientEventId = Crypto.randomUUID();

Regra: o identificador é criado uma única vez, salvo no SQLite e reutilizado em todas as tentativas.

Comportamento da sincronização

1. Verificar se existe conexão
2. Ler o DEVICE_TOKEN do SecureStore
3. Enviar X-APP-KEY e X-DEVICE-TOKEN
4. Buscar eventos pendentes ou com erro no SQLite
5. Enviar os eventos mantendo o mesmo client_event_id
6. success=true → marcar como enviado
7. already_registered=true → tratar como enviado
8. Falha de rede → manter na fila
9. Erro de validação → guardar a mensagem
10. Atualizar ultima_validacao_em quando houver resposta autenticada válida
11. Atualizar last_sync_at usando server_time

Armazenamento local

Cache local da UTG

O aplicativo pode armazenar os dados necessários aos módulos já sincronizados:

  • Produtores pertencentes à própria UTG.
  • Programações e atividades autorizadas.
  • Produtos, estoque e movimentações liberadas para o aparelho.
  • Configurações do programa e da UTG.

Dados recebidos do ERP substituem o cache local. Registros removidos, bloqueados ou inativados devem ser desativados localmente na próxima sincronização.

Endpoint de sincronização

POST https://institutog10bio.com.br/router/action.php

Headers obrigatórios

Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-APP-KEY: <APP_KEY_FIXA_DO_APP_UTG>
X-DEVICE-TOKEN: <DEVICE_TOKEN_DO_APARELHO>

Body

action=apiG10bio_utg
f=sincronizacao_offline
app_version=1.0.0
last_sync_at=YYYY-MM-DD HH:MM:SS
eventos=<JSON>

Campos da requisição

CampoObrigatórioDescrição
app_versionSimVersão instalada no aparelho.
last_sync_atNãoData e hora da última sincronização concluída.
eventosNãoLista JSON de eventos pendentes gerados pela UTG.

Formato genérico dos eventos

[
  {
    "client_event_id": "550e8400-e29b-41d4-a716-446655440000",
    "tipo_evento": "TIPO_DO_EVENTO",
    "endpoint": "funcao_que_recebe_o_evento",
    "criado_em": "2026-07-29 15:30:00",
    "payload": {
      "campo_1": "valor",
      "campo_2": 123
    }
  }
]

Cada módulo deverá documentar o conteúdo de payload. O envelope de sincronização permanece o mesmo.

Resposta esperada — HTTP 200

{
  "success": true,
  "msg": "Sincronização realizada com sucesso.",
  "data": {
    "server_time": "2026-07-29 15:35:00",
    "device_id": 435,
    "device_name": "Tablet UTG 01",
    "account_type": "utg",
    "id_utg": 1,
    "nome_utg": "UTG Porto Ferreira",
    "id_programa": 1,
    "nome_programa": "Instituto G10 Bio",
    "offline_limit_days": 4,
    "offline_valid_until": "2026-08-02 15:35:00",
    "versao_app": {
      "versao_instalada": "1.0.0",
      "versao_atual": "1.0.0",
      "atualizado": 1,
      "atualizacao_obrigatoria": 0,
      "bloqueado_por_versao": 0,
      "observacao": null
    },
    "eventos_confirmados": [
      "550e8400-e29b-41d4-a716-446655440000"
    ],
    "dados_atualizados": {
      "produtores": [],
      "programacoes": [],
      "estoque": [],
      "configuracoes": []
    }
  }
}

Como tratar a resposta

Controle de versão

  • O app deve enviar app_version em toda sincronização.
  • Se atualizacao_obrigatoria=0, apenas exibir aviso.
  • Se atualizacao_obrigatoria=1 e atualizado=0, bloquear novas operações.
  • Eventos já salvos no SQLite não devem ser apagados.

Erros e comportamento

  • 401 missing_device_token: voltar ao primeiro acesso.
  • 401 device_token_not_found: remover a sessão local.
  • 401 device_token_inactive: informar que o aparelho foi revogado.
  • 401 device_token_expired: solicitar novo primeiro acesso.
  • 403 invalid_api_key: informar necessidade de atualização ou suporte.
  • 409 utg_inactive: bloquear novas operações e informar que a UTG está inativa.
  • 400 invalid_app_version: corrigir a versão enviada.
  • 500: manter a fila e tentar novamente, respeitando o prazo offline.

NÃO IMPLEMENTAR nesta etapa

  • Login com usuário e senha.
  • Seleção manual de outra UTG.
  • Envio de dados pertencentes a outra UTG.
  • Criação de device_token pelo endpoint de sincronização.
  • Remoção da fila antes da confirmação do ERP.
  • Uso de controladores de outros aplicativos.
  • Liberação offline após o prazo de 4 dias.

Exemplo de chamada cURL

curl -X POST "https://institutog10bio.com.br/router/action.php" \
  -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
  -H "X-APP-KEY: SUA_APP_KEY" \
  -H "X-DEVICE-TOKEN: SEU_DEVICE_TOKEN" \
  --data-urlencode "action=apiG10bio_utg" \
  --data-urlencode "f=sincronizacao_offline" \
  --data-urlencode "app_version=1.0.0" \
  --data-urlencode "last_sync_at=2026-07-29 15:30:00" \
  --data-urlencode 'eventos=[]'

Regra final

Registrar localmente
→ tentar sincronizar
→ manter na fila se falhar
→ reenviar com o mesmo client_event_id
→ confirmar no ERP
→ retirar da fila somente após confirmação