No terreno, nem sempre há rede

Caves, salas técnicas, coberturas: um técnico tem de conseguir registar horas, tirar notas e fotografias sem ligação. A app móvel, em Flutter, escreve por isso cada ação numa base de dados local, numa fila de envio (outbox), e sincroniza-a por lotes assim que a rede volta. A base local serve para trabalhar offline; o servidor continua a ser a fonte de verdade.

Os eventos sincronizados cobrem toda a intervenção:

  • chegada, início, pausa, retoma e fim do trabalho;
  • notas e medições estruturadas;
  • peças utilizadas ou indisponíveis;
  • fotografias e assinatura do cliente;
  • cliente ausente, acesso bloqueado, perigo, nova visita necessária, colocação em segurança.

Um identificador por evento

Cada evento recebe no telemóvel um clientEventId (UUID v4), que nunca é gerado de novo numa nova tentativa. Repetir exatamente o mesmo evento não tem qualquer efeito, mesmo depois do encerramento da intervenção: a resposta limita-se a assinalá-lo como duplicado. Reutilizar o mesmo identificador com outro conteúdo é recusado (409).

A app pode assim reenviar um lote inteiro depois de perder uma resposta de rede, sem risco de duplicados. Um lote tem no máximo 100 eventos e é transacional: se um deles for inválido, nenhum é criado. O servidor controla também a sequência dos registos de horas; uma pausa sem início ou dois inícios seguidos são rejeitados. Uma peça declarada só desconta o stock da viatura uma vez, mesmo que o envio seja repetido.

Ficheiros verificados antes de serem aceites

Antes de enviar uma fotografia ou uma assinatura, a app declara uma intenção de envio: tipo MIME, tamanho e impressão digital SHA-256. O servidor responde com um URL que inclui um token aleatório, válido durante quinze minutos e de utilização única.

Na receção, verifica o token, o tamanho, a impressão digital e a assinatura binária real do ficheiro. Só um ficheiro validado, que pertença à mesma organização e à mesma intervenção, pode depois ser referido por um evento no terreno. Os ficheiros vão para um armazenamento de objetos (S3 ou compatível, como o MinIO), e não para o PostgreSQL.

Um fluxo em tempo real que retoma onde parou

Os ecrãs de planeamento e a app recebem as alterações através de um fluxo SSE autenticado e isolado por organização. Após uma falha, o cliente retoma a partir do último evento recebido graças ao cabeçalho Last-Event-ID; os eventos são conservados sete dias por predefinição. Um técnico só recebe os das intervenções que lhe estão atribuídas.

As notificações push (Firebase Cloud Messaging) são propositadamente genéricas: só transportam identificadores, e a app volta sempre a carregar os dados de negócio através da API autenticada.

O que a idempotência muda

Coloca a complexidade no sítio certo. A app não precisa de saber se um envio anterior chegou: na dúvida, reenvia. O servidor garante que o mesmo facto só é registado uma vez.