Esta página foi escrita para valer sozinha. As duas primeiras partes mostram o estado da entrega — ao vivo, sem depender de perguntar a ninguém. A terceira é a especificação: cada rota, a requisição exata que o aplicativo manda, a resposta que ele espera e o porquê de cada regra — o suficiente para a equipe de backend implementar sem a nossa presença. O botão no topo gera o PDF; cada seção tem um endereço fixo (o # ao lado do título) para citar em e-mail ou tarefa.
/mobile/* — a API inteira, nenhuma escondidaO aplicativo não ficou esperando a API nova: ele está pronto e operando hoje. Três coisas já estão entregues — e a quarta, o contrato desta página, é acompanhada item a item logo abaixo.
Esta lista não é um retrato: é o mesmo checklist que a equipe marca no painel interno — uma tela praticamente igual a esta, com senha, onde os itens são marcáveis e as manutenções são acompanhadas. O que for concluído lá aparece aqui na próxima abertura da página, e o PDF que você baixar sai com o andamento do momento em que baixou.
Nada acima é promessa: dá para instalar e percorrer o fluxo inteiro agora, sem servidor, sem cadastro e sem VPN — o acesso de demonstração roda com dados locais no próprio aparelho.
Ao digitar 0000 no primeiro
campo, o aplicativo preenche o resto sozinho e abre um romaneio completo de exemplo —
entregas, coletas, todos os tipos de documento, baixa com foto e assinatura, modo avião.
Por que é seguro: esse CPF é inválido de propósito — não passa no dígito verificador, então nenhum motorista real cai nesse acesso por engano, e nesse modo o aplicativo nunca envia nada para servidor nenhum.
Toda vez que sobe uma versão do aplicativo, a anotação entra aqui — da mais nova para a mais antiga. Serve para responder, sem perguntar a ninguém, “o que mudou desde a última vez que eu olhei?”.
carregando…
A pedido da equipe do TMS: a comparação entre a API de hoje (o Swagger do
ApiMobile) e o contrato novo, listando apenas o que não existe hoje —
campo por campo, com o bloco onde entra e a finalidade de cada um. O que a API já manda
não aparece aqui e continua valendo igual. Cada linha aponta para a seção
desta página que traz o contrato completo daquele item. E, como a decisão entre
adequar a API atual ou subir as rotas novas é da equipe,
a comparação entre os dois caminhos está logo abaixo — os campos são os mesmos nos dois,
muda o endereço onde entram e o risco de cada um.
GetDocumentos, AtualizaDocumento,
CadastraLocalizacaoMotorista). O limite a vigiar: esses endpoints são os
mesmos que o aplicativo da Play consome hoje — tudo precisa ser adição
compatível, e dois itens da lista mexem em comportamento que ele pressupõe.
/mobile/* desta página sobem ao lado das
atuais: o Swagger de hoje fica limpo, sem remendo, e o aplicativo da Play continua
funcionando sem tomar risco nenhum. As regras novas (idempotência, lote, anexo separado,
erro em 4xx) nascem dentro das rotas novas, em vez de
serem encaixadas em endpoints que já têm outro contrato.
| O item | No caminho da adequação (Swagger de hoje) | No mundo ideal (rotas /mobile/*) |
|---|---|---|
| latitude / longitude | preencher os dois campos que já existem vazios no
GetDocumentos |
já nascem preenchidos em cada serviço da sessão |
regras da baixa e do rastreio (parametros,
rastrear) |
campos a mais na resposta do GetDocumentos — adição compatível, o
aplicativo da Play ignora campo que não conhece |
bloco parametros da resposta da sessão |
| romaneio rebuscável + estado do serviço | mudar o “entrega uma vez só” do GetDocumentos — o item
delicado: o aplicativo da Play pressupõe o comportamento de hoje, então a
mudança precisa ser combinada com cuidado dobrado |
a regra nasce dentro do POST /mobile/sessao, sem tocar no endpoint
que o aplicativo da Play usa |
| baixa duplicada recusada | conferir “este documento já está baixado” no AtualizaDocumento —
dá para fazer já, e protege inclusive o aplicativo da Play |
chave idempotencia que toda baixa manda |
| posições em lote | o CadastraLocalizacaoMotorista passar a aceitar uma lista |
POST /mobile/posicoes já é lote por definição |
| fotos separadas da baixa | endpoint novo de qualquer jeito — não existe onde pendurar isso no Swagger atual | POST /mobile/anexos — o mesmo endpoint, nos dois caminhos |
| coleta | campo tipo (mais volumes e peso previstos) a mais no
GetDocumentos |
já definida no formato do serviço (tipo: "coleta") |
erro de negócio em 4xx |
não dá sem quebrar o aplicativo da Play — ficaria como está
(200 com o motivo no corpo) |
nasce certo nas rotas novas |
Na adequação, quase tudo é adição compatível e barata — preencher campo, aceitar campo a mais — mas os dois itens que mais importam (o romaneio rebuscável e o erro de verdade) mexem em comportamento que o aplicativo da Play pressupõe, e é aí que o caminho fica caro. No mundo ideal, o Swagger atual fica intocado e cada regra nova nasce no lugar certo. Os dois caminhos terminam no mesmo aplicativo: ele já fala os dois servidores hoje, e a troca é um seletor — nada precisa ser reinstalado.
Não é campo novo nem renomeado: o GetDocumentos de hoje já devolve
latitude e longitude em cada documento — mas vazias em 100% dos
casos (medido em 10/08/2026: 0 de 427 serviços com coordenada). A adequação
é preenchê-las com a coordenada do destino. No contrato novo elas continuam
com os mesmos nomes, dentro de cada serviço do romaneio (sessão).
Enquanto vêm vazias, o aplicativo descobre a coordenada pelo endereço, no próprio aparelho —
paliativo que erra número e complemento em endereço brasileiro e depende de rede.
Hoje o CadastraLocalizacaoMotorista aceita uma posição por chamada.
O contrato novo (POST /mobile/posicoes) manda um lote:
{ placa, romaneio, posicoes[…] }, cada ponto com lat, lon,
precisaoM e em (o horário em que o GPS mediu — o
dataLocalizacao de hoje). O motivo é o modo de operar sem rede: as posições se
acumulam no aparelho e sobem juntas quando o sinal volta. O único campo realmente novo por
ponto é precisaoM — a precisão do GPS em metros, para a central saber o quanto
confiar em cada ponto.
Hoje o retorno é o do GetDocumentos; no contrato novo é a resposta da
sessão. A coluna “onde entra” usa os nomes do contrato novo.
| Campo | Onde entra | Hoje | Para quê |
|---|---|---|---|
latitude, longitude |
em cada serviço do romaneio | os campos existem e vêm vazios (0 de 427) | distância real no cartão (“a 3,2 km”), ordenação por proximidade — sem adivinhar pelo endereço |
parametros (bloco novo): fotosObrigatorias,
exigeNome, exigeDocumento, exigeAssinatura,
intervaloPosicaoSeg, intervaloEnvioSeg |
na resposta do login, uma vez por romaneio | não existe — o aplicativo repete as regras fixas do 5.x | o TMS passa a mandar as regras da baixa e o ritmo do rastreio; mudar uma regra deixa de exigir versão nova do aplicativo |
parametros.rastrear |
dentro do bloco parametros |
o parâmetro existe no TMS e não chega ao aplicativo | com false o GPS nem liga e a permissão “Sempre” nem é pedida — hoje
ela é pedida a todo motorista, inclusive de carga não rastreada |
romaneio.numero estável |
no envelope do romaneio | o número muda a cada lote do mesmo dia (734/26 → 735/26 →
736/26, em 10/08/2026) |
o aplicativo precisa reconhecer que é o mesmo romaneio — o número instável já causou perda de serviços na tela |
| estado do serviço (concluído / cancelado) | em cada serviço | serviço entregue ou cancelado apenas some da resposta — os dois casos são indistinguíveis | é a única forma de o aplicativo saber que a central cancelou um serviço; hoje o cancelado fica na tela até alguém baixar |
tipo do serviço (entrega | coleta)
+ volumesPrevistos, pesoPrevisto |
em cada serviço | não existe — tudo é entrega | habilita a coleta com conferência de volumes, que o aplicativo já tem pronta |
motorista.nome |
no bloco motorista |
não vem em campo nenhum — o aplicativo mostra a placa no lugar | o nome do motorista no perfil e no cabeçalho; quando vier, aparece sozinho |
janelaTexto (opcional) |
em cada serviço | não vem nos campos | janela de horário no cartão da entrega; sem o campo, o bloco simplesmente não aparece |
| O que muda | Hoje | Para quê |
|---|---|---|
| Buscar o romaneio de novo devolve o mesmo romaneio — a mudança mais importante da lista (as três garantias) | cada serviço é entregue uma vez só; da segunda busca em diante, erro pelo resto do dia | sair e entrar do aplicativo, reiniciar ou trocar de aparelho sem perder o dia — hoje o romaneio não volta nem pelo suporte |
Motorista sem romaneio recebe 200 com lista vazia |
erro 400 com mensagem |
“sem trabalho no momento” é situação normal do dia, não falha |
| Segunda baixa do mesmo serviço é reconhecida como repetição | o AtualizaDocumento aceita e gera movimento novo — aconteceu em
produção em 10/08/2026, duas baixas do mesmo CT-e aceitas |
a fila offline reenvia por projeto; a chave
idempotencia que o aplicativo manda em toda baixa é o que permite ao
servidor responder sucesso sem duplicar |
Recusa de negócio responde 4xx com mensagem |
o servidor responde 200 mesmo recusando (o motivo vem em
logDaRequisicao) |
o aplicativo distingue recusa (mostra o motivo ao motorista) de falha de rede (tenta de novo sozinho) |
O sentido inverso: o que o servidor precisa passar a aceitar (contrato completo da baixa).
| Campo | Hoje | Para quê |
|---|---|---|
idempotencia (UUID gerado no aparelho) |
não existe | a mesma baixa reenviada pela fila é reconhecida e não duplica |
fotos [{seq, anexoId}] e
assinatura {anexoId} no lugar das fotos
dentro da baixa |
foto e assinatura vão em base64 dentro do
AtualizaDocumento — corpo de 2 a 3 MB, e uma foto pesada derruba a baixa
inteira |
a baixa vira um JSON pequeno que só cita as provas; cada foto sobe separada no endpoint de anexos e uma foto que falha nunca trava a baixa |
posicao.precisaoM |
latitudeEntrega/longitudeEntrega já existem; a precisão
não |
a central sabe o quanto confiar na coordenada da baixa |
coleta (volumes e peso conferidos) |
não existe — coleta não existe hoje | o resultado da conferência da coleta, junto da baixa |
Todo o resto desta lista é campo ou regra em cima do que já existe. O endpoint de
anexos é a exceção: recebe uma foto (ou a assinatura) por chamada,
em multipart/form-data — o arquivo como parte binária e os metadados
(anexoId, servicoId, baixaIdempotencia,
tipo, seq, formato, capturadoEm) como
campos de texto. O anexoId nasce no aparelho — o servidor não
o devolve, só o aceita; reenvio do mesmo anexoId é substituição, nunca
duplicata. O anexo pode chegar antes ou depois da baixa; a amarração é
pela baixaIdempotencia. As regras de dimensionamento (e o alerta sobre o campo
seq, que é ordem, não contagem) estão na seção do endpoint. Esse formato é o
que o documento do cliente de 12/08 definiu.
É esta a lista inteira — o suficiente para abrir a tarefa de adequação. O contrato completo de cada item está nas seções seguintes, o que ainda depende de decisão da equipe está em as onze perguntas — e há um recurso à parte, o ditado por voz, que não entra nesta tarefa: o aplicativo já o faz desde a 6.0.10 sem exigir nada do servidor de hoje, e o lado do TMS, quando for feito, reaproveita o endpoint de anexos em vez de criar rota.
Quase toda regra deste documento é consequência de um destes três fatos. Quando uma exigência parecer arbitrária, o motivo dela está aqui.
Não é um modo separado que se liga. A baixa gravada no aparelho já está concluída para o motorista; enviar é problema do aplicativo. Fechar o aplicativo no meio de uma baixa e reabrir não perde nada. Para o servidor, isso quer dizer: o que chega pode ter sido feito há minutos ou há dias — e as duas coisas estão certas.
Seis rotas — e não há nenhuma escondida. As duas últimas são evoluções: exigem uma versão nova do aplicativo e nada depende delas; a API funciona completa sem as duas.
POST /mobile/sessao abre o dia; devolve o romaneio inteiro GET /mobile/romaneio/{numero}/alteracoes o que mudou desde a última consulta POST /mobile/servicos/{servicoId}/baixa o resultado do serviço POST /mobile/anexos fotos e assinatura, uma a uma POST /mobile/posicoes rastreamento, em lote evoluções — exigem versão nova do aplicativo; nada depende delas: POST /mobile/dispositivos registra o aparelho para push → push (FCM), do TMS para o aparelho avisa que o romaneio mudou → anexos com tipo "audio" + origem na baixa ditado por voz — já no app 6.0.10, falta o TMS
Ir direto a um endpoint: sessão · alterações · baixa · anexos · posições · dispositivo e push · ditado por voz (já no app 6.0.10).
O motorista faz o login e POST /mobile/sessao devolve o dia inteiro numa
resposta só. A partir daqui o aparelho consegue operar até a noite sem rede nenhuma.
O aplicativo consulta as alterações do romaneio a cada minuto e ao concluir cada serviço; cada conclusão vira uma baixa, com as fotos e a assinatura subindo como anexos, uma a uma; e as posições do GPS sobem em lotes, no ritmo que a própria sessão configurou.
O que a rede engoliu volta a ser enviado — com a mesma idempotência, até receber sucesso. É por isso que a repetição não é um caso raro a tolerar: é o comportamento normal do sistema, e o contrato inteiro foi desenhado em volta dela.
Fechar isto antes evita a situação clássica de cada endpoint inventar o próprio formato. Cada regra vem com o motivo — e, quando existe, com o caso real que a justificou.
// sucesso 200 / 201 { "sucesso": true, …campos do endpoint } // recusa — com CÓDIGO estável, para o app decidir sem ler a frase 4xx { "sucesso": false, "codigo": "RECEBEDOR_OBRIGATORIO", // ESTÁVEL e versionado "mensagem": "Informe quem recebeu", // em português: vai para a tela do motorista "campo": "recebedor.nome" // opcional: o app foca o campo }
Recusar com 200 e "sucesso": false é o pior dos
mundos — o aplicativo tem de olhar o corpo para descobrir que falhou, e é assim
que baixa some sem ninguém ver. O contrário também: responder erro carregando uma
resposta boa no corpo obriga o cliente a adivinhar. Código 4xx para pedido
que não serve, 5xx para falha do servidor, 2xx para o resto.
Um item recusado permanece na fila do aparelho, com o motivo visível para o motorista, e volta a ser tentado em espera crescente: 30 s, 1 min, 2 min… até o teto de 15 minutos. Mas 15 minutos é o teto, não o ritmo — a volta do sinal e o botão tentar agora zeram a espera, então o mesmo pacote pode reaparecer em segundos.
Daí a regra que não se negocia: reenvio de algo que já foi gravado responde
2xx, jamais 409 de duplicado — o 409
prenderia para sempre, na fila do motorista, uma baixa que já está no TMS.
Toda gravação carrega um identificador de idempotência gerado no aparelho — o mesmo em
todas as tentativas. Quando ele repetir, o servidor devolve a mesma resposta da
primeira vez (o mesmo baixaId), não um “já existe” — e nos
anexos, cujo anexoId também nasce no aparelho, reenvio do mesmo
anexoId é substituição, nunca duplicata. E o identificador precisa de
índice único no banco: a duplicidade acontece em corrida, não em
sequência.
Uma baixa feita offline há dias sobe com o horário original de execução e precisa ser aceita — inclusive se o romaneio já fechou. Recusar por antiguidade apaga trabalho que foi feito de verdade e que o motorista não tem como refazer. O horário que vale é o do momento da entrega, não o da chegada do pacote.
2026-08-10T14:32:10-03:00 — em todo campo de data, nos dois sentidos. E
são sempre duas datas que não se misturam: a do aparelho (quando o
motorista fez) e a do servidor (quando o TMS gravou). Cada uma tem seu campo.
Em 09/08/2026, o servidor atual recusou 82 das 86 chamadas do dia por não aceitar exatamente o formato de data que a documentação define — só passava em UTC. Nenhuma baixa se perdeu (a fila segurou tudo), mas o rastreamento ficou mudo o dia inteiro. A escolha do formato precisa estar escrita no contrato — é a pergunta 4.
Seguindo a convenção atual, não há cabeçalho de autorização: a identidade (transportadora, CPF, placa) viaja no corpo. Se a equipe preferir adotar token, a sessão passa a devolvê-lo e o aplicativo o envia em toda chamada — com uma exigência: validade que cubra o turno inteiro com folga. Token que vence às 14h joga o motorista para a tela de login no meio da rua, com baixas ainda no aparelho. É a pergunta 3.
Compressão (gzip) nas respostas é configuração de servidor e derruba um
romaneio grande de ~900 KB para ~90 KB — e o aplicativo espera no máximo
45 segundos por resposta, então um romaneio de 400 serviços
precisa caber nesse tempo. Duas lições de campo que valem para todo endpoint:
no contrato novo o arquivo viaja em multipart, fora do JSON (onde
ainda houver base64 — como no Swagger atual —, vai sem o prefixo
data: e sem quebras de linha), e código de ocorrência é
numérico — as duas coisas já derrubaram baixas em produção com um
erro genérico que não dizia qual era o problema.
É o único ponto em que este documento fala de dentro do TMS, e vem antes dos endpoints porque as chaves daqui reaparecem em todas as requisições: motorista, dispositivo, romaneio, serviço, documento, baixa, anexo, posição e o catálogo de ocorrências.
servico.id único e estávelO login do motorista devolve, numa resposta só, tudo o que o aparelho precisa para trabalhar o dia inteiro sem rede: quem ele é, as regras da baixa, os motivos de ocorrência e o romaneio completo.
{
"transportadora": "0001", // 3 ou 4 dígitos
"cpf": "12345678901", // 11 dígitos, sem máscara
"placa": "ABC1D23", // maiúscula, sem hífen — placa antiga e Mercosul
"dispositivo": {
"so": "android", // "android" | "ios"
"versaoApp": "6.0.9", // a versão instalada — o app manda a dele
"id": "…" // identificador estável do aparelho
}
}
O bloco dispositivo não participa da autenticação: ele existe para o suporte
saber, olhando o registro do servidor, qual versão do aplicativo e qual aparelho
estavam por trás de cada chamada — sem depender de perguntar ao motorista.
→ 200 { "sucesso": true, "motorista": { "nome": "…", "cpf": "…", "transportadora": "…", // nome de exibição, não o código "telefoneCentral": "…" // opcional: vira o botão "ligar para a central" }, "parametros": { "fotosObrigatorias": 2, // 0 a 10 — monta os passos da baixa "exigeNome": true, "exigeDocumento": false, "exigeAssinatura": true, "intervaloPosicaoSeg": 5, // ritmo da medição de GPS "intervaloEnvioSeg": 120, // ritmo da subida dos lotes (120–300) "rastrear": true // false = o GPS nem liga e a permissão nem é pedida }, "ocorrencias": [ // o catálogo INTEIRO — sem rede não dá para buscar depois { "codigo": 4, // numérico e estável "texto": "Destinatário ausente", "tipo": "entrega", // "entrega" | "coleta" | "ambos" "exigeFoto": true } ], "romaneio": { "numero": "4471", "servicos": [ … ] // todos — o formato está logo abaixo } }
// cada item de "servicos": { "id": "S-88213", // ESTÁVEL entre sessões — vai na URL da baixa "tipo": "entrega", // "entrega" | "coleta" "ordem": 3, // sugestão de sequência — o app não trava por ela "cliente": "…", "remetente": "…", "logradouro": "…", "bairro": "…", "cidade": "…", "uf": "SP", "cep": "…", "latitude": -23.55, "longitude": -46.63, // mandem sempre — sem elas o app adivinha a distância pelo endereço "janelaTexto": "08:00–12:00", // texto livre, mostrado como veio "documentos": [ { "tipo": "NFE", // NFE | CTE | CTP | MDE | COLETA "numero": "…", "chave": "4425…", // 44 dígitos — é o que a bipagem lê "parceiro": "…" } ], "volumesPrevistos": 12, "pesoPrevisto": 340.5 // coleta: base da conferência }
1 — Abrir a sessão de novo devolve o mesmo romaneio. A segunda, a quinta e a vigésima abertura do dia devolvem o romaneio inteiro, quantas vezes for preciso. Motorista sai e entra no aplicativo o tempo todo: bateria acabou, aparelho reiniciou, trocou de veículo.
2 — Serviço já baixado continua vindo na lista, com o estado marcado (concluído), em vez de sumir. Sumir da lista é indistinguível de “a central removeu este documento” — e o aplicativo agiria de acordo.
3 — Motorista sem romaneio recebe lista vazia com 200,
não erro. “Não ter trabalho no momento” é uma situação normal do dia, não uma falha.
Por que virou a parte mais importante: o servidor atual trata a busca do
romaneio como uma retirada — cada serviço é entregue uma vez só,
e buscar de novo responde erro. Os registros de 10/08/2026 mostram os dois estragos. O
primeiro: uma placa recebeu 427 serviços às 14:17 e ouviu a mesma recusa
onze vezes seguidas às 14:19 — motorista que saiu e entrou do aplicativo
ficou com a tela vazia, sem ter dado nenhuma baixa; e o aplicativo que está na loja mostra
o mesmo padrão em 170 placas (ele só nunca reclamou porque nunca busca a
lista duas vezes). O segundo, pior: no mesmo dia, o mesmo romaneio saiu
picado em pedaços com números diferentes — 427 serviços como
734/26, depois 7 como 735/26, depois 123, 12 e 285 como
736/26 (7 + 123 + 12 + 285 = 427) —
e, como sumir da lista significa “removido pela central”,
135 serviços desapareceram da tela do motorista com o dia em andamento.
O aplicativo ganhou remendos locais (guarda o romaneio no aparelho e trata a lista do servidor atual como aditiva), mas remendo tem custo: o aplicativo fica cego para cancelamento da central, e troca ou perda do aparelho não tem salvação — essa informação só existe do lado do servidor. No contrato novo, as três garantias acima resolvem as duas coisas de uma vez, e a regra nasce dentro do endpoint.
| Regra | Motivo |
|---|---|
| O romaneio vem inteiro, sem paginação | O aplicativo é offline por projeto. Paginar aqui equivale a entregar meio dia de trabalho — o motorista não tem como pedir a página seguinte no meio da estrada. Com compressão, até um romaneio de 400 serviços chega em segundos. |
| Sessão nova não invalida a fila | Baixas feitas antes do relogin continuam válidas quando subirem. Amarrar a gravação à “sessão aberta agora” transforma cada relogin em perda de trabalho. |
| Recusa de negócio explica o motivo | CPF não cadastrado, placa de outra transportadora, romaneio de outro motorista:
4xx com a mensagem em português. Ela vai direto para a tela do login. |
De minuto em minuto o aplicativo pergunta “mudou alguma coisa?” — e a resposta quase sempre é “não”. Este endpoint devolve só a diferença, para que a pergunta mais frequente da API seja também a mais barata.
Nada no corpo. O número do romaneio vai na URL, e desde é o
sincronizadoEm que veio na resposta anterior — devolvido exatamente como
chegou. Na primeira consulta do dia, desde é o momento da sessão.
→ 200 { "sucesso": true, "incluidos": [ … ], // serviços NOVOS — objeto completo, formato da sessão "alterados": [ … ], // objeto INTEIRO, mesmo id — nunca parcial "removidos": [ "S-88213" ], // só os ids "sincronizadoEm": "2026-08-10T14:40:11-03:00" // relógio DO SERVIDOR } // nada mudou (o caso comum): 200 com as três listas vazias
| Regra | Motivo |
|---|---|
sincronizadoEm vem do relógio do servidor |
É a âncora da conversa: o aplicativo guarda o valor e o devolve na consulta seguinte. O relógio do aparelho não participa — ele pode estar errado, e aí a conversa perderia ou repetiria mudanças para sempre. |
alterados traz o serviço inteiro |
O aplicativo substitui o que tem pelo que recebe. Mandar só os campos que mudaram criaria um estado híbrido — metade de uma versão, metade de outra. |
removidos importa tanto quanto incluidos |
Serviço cancelado que continua na tela é motorista dirigindo até um endereço que não existe mais. |
| Baixa de serviço removido ainda chega — e deve ser aceita | Uma baixa que já estava na fila não é apagada quando o serviço some da lista. O que o TMS faz com ela é decisão de negócio — é a pergunta 7. |
Serviço entregue não entra em removidos |
Conclusão não é remoção. Foi um dos defeitos do servidor atual: o documento sumia da lista assim que era entregue, e o aplicativo entendia que a central o havia cancelado. |
Com ETag na resposta e If-None-Match na consulta, o caso comum vira
304 sem corpo — e sem consulta ao banco. O marcador é a versão do
romaneio, não da resposta: se mudar a cada requisição, o 304 nunca
acontece. A economia não é de banda, é de banco — multiplicada pelo número de motoristas em
rua vezes uma consulta por minuto. O aplicativo em campo hoje manda só o desde;
implementar o ETag desde já não atrapalha em nada.
Cada serviço concluído vira um pacote pequeno com quem recebeu, quando, onde e as provas — e reenviar o mesmo pacote nunca pode duplicar o registro nem travar a fila.
{
"idempotencia": "9f1c2b7e-…", // gerado no aparelho; o MESMO em toda tentativa
"servicoId": "S-88213", // repete o da URL — se divergirem, recuse
"resultado": "executado", // "executado" | "ocorrencia"
"executadoEm": "2026-08-10T14:32:10-03:00", // quando o MOTORISTA confirmou
"posicao": { "lat": -23.55, "lon": -46.63, "precisaoM": 8 }, // pode faltar
"recebedor": { "nome": "…", "documento": "…", "setor": "…" }, // conforme os parâmetros
"fotos": [ { "seq": 1, "anexoId": "a7f3c9…" } ], // pode vir vazio
"assinatura": { "anexoId": "…" }, // quando exigida
// só quando resultado = "ocorrencia":
"ocorrencia": { "codigo": 4, "observacao": "…", "retornaHoje": true },
// só quando o serviço é coleta:
"coleta": {
"volumesConferidos": 11,
"motivoDivergencia": "…", // obrigatório quando divergiu do previsto
"documentos": [ { "tipo": "NFE", "numero": "…", "chave": "…" } ]
}
}
→ 200 { "sucesso": true, "baixaId": "B-88213", "registradoEm": "2026-08-10T14:35:02-03:00", "situacao": "registrada" } // "registrada" | "duplicada" | "ignorada"
situacao conta o que aconteceu: registrada — baixa nova;
duplicada — a mesma idempotência voltou, o servidor devolve a resposta
guardada e não gera movimento novo; ignorada — a central já tinha baixado o
documento por dentro do TMS, e a versão do motorista fica só como histórico. Nos três casos
o HTTP é 200: responder 409 ou 422 faria a fila do aparelho tratar como
falha e reenviar a mesma baixa para sempre.
| Regra | Motivo |
|---|---|
| Idempotência repetida devolve a resposta guardada | O caso real: a gravação funciona, a confirmação se perde na volta, o aplicativo
reenvia dias depois. Se o reenvio devolver só “já existe”, o aparelho fica sem o
baixaId e sem saber se o que valeu foi o dele. |
| Baixa antiga não tem prazo | Recusar por antiguidade — ou por “romaneio já fechado” — apaga trabalho feito de
verdade, que o motorista não tem como refazer. executadoEm é o relógio do
motorista; registradoEm, o do servidor. As duas datas convivem. |
fotos pode vir vazia — e citar arquivo que ainda não subiu |
O anexoId nasce no aparelho (ver anexos), então a
baixa já sai citando cada foto — mesmo as que ainda estão na fila para subir. Prender a
entrega por causa de um arquivo que já está garantido no aparelho inverte a prioridade —
a baixa nunca espera anexo. |
posicao pode nem vir |
O carimbo é tentado no “Cheguei”, com oito segundos de paciência. GPS desligado, subsolo ou permissão negada deixam a baixa sair sem coordenada, de propósito — exigir o campo prende para sempre a entrega feita num galpão sem sinal. |
| Ocorrência não é fracasso — é resultado | “Destinatário ausente” fecha o serviço do mesmo jeito que “executado”. O código é numérico e vem do catálogo que a própria sessão entregou. |
| Coleta valida a conferência, não a foto | volumesConferidos pode divergir do previsto — aí o
motivoDivergencia vem preenchido, e é obrigatório. Os documentos vinculados
na coleta chegam na própria baixa. |
Cada foto e a assinatura sobem sozinhas, uma por chamada, antes ou depois da baixa — e uma foto que falha nunca segura a entrega.
A chamada é multipart/form-data — o arquivo vai como parte binária, sem base64, e os metadados vão como campos de texto (formato definido pelo documento do cliente de 12/08):
anexoId "a7f3c9…" // GERADO NO APARELHO — é o mesmo que a baixa cita servicoId "S-88213" baixaIdempotencia "9f1c2b7e-…" // amarra à baixa — que pode AINDA NÃO ter chegado tipo "foto" // "foto" | "assinatura" seq 1 // ordem da foto; assinatura = 0 formato "jpeg" // foto = jpeg · assinatura = png capturadoEm "2026-08-10T14:31:58-03:00" // relógio do aparelho, no clique arquivo (binário) // a parte de arquivo do multipart
→ 200 { "sucesso": true }
O anexoId não vem na resposta — ele nasce no celular e chega
no próprio envio. É isso que permite à baixa citar a foto antes de o arquivo terminar de
subir. Receber de novo o mesmo anexoId nunca duplica: o TMS
trata como substituição (ou confirmação) do mesmo anexo.
| Regra | Motivo |
|---|---|
| O anexo pode chegar antes da baixa | A rede decide a ordem. Se a baixa daquela idempotência já existe, amarre o anexo a ela; se ainda não existe, guarde e amarre quando chegar. Anexo órfão é estado normal do sistema, não inconsistência. |
| Foto que falha nunca trava a baixa | É a razão de o anexo ser um endpoint separado. A baixa é o que a operação espera; a foto é prova complementar e sobe no seu próprio ritmo. |
seq é ordem, não contagem |
O número da próxima foto é o último + 1, e apagar uma foto não devolve o número:
uma baixa com 4 fotos pode chegar com seq 1, 2, 5 e 9 — e o
seq pode passar de 10. A identidade do anexo é o
anexoId; o seq só diz a posição na baixa. Não validem
faixa nem exijam sequência sem buracos — isso recusaria baixa boa. |
| O tamanho é conhecido — dimensionem o limite por ele | A foto sai do aparelho comprimida, entre ~360 e ~525 KB — e em multipart o corpo da chamada fica nesse tamanho mesmo, sem o inchaço de ~33% que o base64 teria. Cinco fotos são cinco chamadas (2–3 MB no total) — nunca uma requisição só. O limite de corpo aceito precisa comportar isso com folga (pergunta 5). |
1. POST /mobile/anexos/autorizacao // só metadados + sha256 (com o anexoId do aparelho) → { "url": "https://storage…", "expiraEm": "…" } 2. PUT https://storage… // os bytes — SEM passar pelo TMS 3. POST /mobile/anexos/a7f3c9…/confirmacao → { "sucesso": true } // o TMS confere tamanho e hash
Tira o arquivo de dentro do corpo da chamada: o TMS deixa de ser o caminho dos bytes e passa
a ser só quem autoriza e registra. Três decisões fazem o desenho funcionar: o
anexoId continua nascendo no celular — o passo 1 apenas o
registra —, então a baixa segue podendo citá-lo antes de os bytes subirem; a
URL assinada vale bastante tempo (sugestão: 30 minutos —
o motorista pode entrar numa sombra de cobertura logo depois da foto); e
o passo 3 pode não chegar — o aparelho pode descarregar no meio — então o
melhor é o TMS ouvir o evento do próprio armazenamento. Do lado do aplicativo isso é um
método só: custa uma versão nova, não uma reescrita.
O recurso vem do documento do cliente de 12/08 (Sitra Mobile 1C — Demandas Consolidadas) e foi entregue no aplicativo na versão 6.0.10 (13/08): o motorista já dita nome, documento e observação, e o reconhecimento acontece dentro do próprio aparelho. Nada disso muda o servidor de hoje — o que chega ao TMS continua sendo o texto, no campo normal da baixa; o áudio fica guardado no aparelho.
O que está descrito abaixo é o que falta do lado de vocês para o áudio também viajar: não exige rota nova, mas cria um tipo novo de anexo e seis campos novos na baixa — mais o player, sem o qual o recurso não se paga (última linha da tabela de regras).
O motorista fala em vez de digitar — nome do recebedor, documento e observação da ocorrência —, o texto reconhecido entra no campo (e pode ser corrigido), e o áudio original sobe como anexo: a prova de voz que vale numa contestação.
anexoId "b41d07…" // gerado no aparelho, como qualquer anexo servicoId "S-88213" baixaIdempotencia "9f1c2b7e-…" tipo "audio" // o tipo novo campo "nome" // "nome" | "documento" | "observacao" transcricao "João da Silva" // texto reconhecido ANTES da correção — PODE VIR VAZIA duracaoSeg 4 // teto: 20 s (nome/documento) · 60 s (observação) formato "m4a" // AAC mono 16 kHz — ~25 a 180 KB, menor que uma foto capturadoEm "2026-08-10T14:31:40-03:00" arquivo (binário)
seq não se aplica ao áudio: por campo existe no máximo um
áudio, e regravar substitui o anterior — a baixa cita sempre o último
anexoId.
"recebedor": { "nome": "João da Silva", // o texto FINAL, já corrigido pelo motorista "nomeOrigem": "voz_corrigida", // "teclado" | "voz" | "voz_corrigida" "nomeAudioAnexoId": "b41d07…", // presente quando a origem foi voz "documento": "12345678-9", "documentoOrigem": "voz", "documentoAudioAnexoId": "c02e91…" }, "ocorrencia": { "codigo": 4, "observacao": "…", "observacaoOrigem": "teclado" // sem áudio quando veio do teclado }
| Regra | Motivo |
|---|---|
transcricao pode divergir do texto final — nunca validar
igualdade |
A transcrição é o que o reconhecimento entendeu antes da correção; o campo
da baixa é o que o motorista confirmou. Divergirem é o caminho feliz — é exatamente o
caso voz_corrigida. Recusar por diferença mataria o recurso. |
| Áudio sem transcrição é aceito | Em Android anterior ao 13, ou sem o pacote de português baixado, o aparelho grava
mas não reconhece — a transcricao chega vazia e o áudio continua valendo
como prova. O motorista digita o texto nesses casos. Medido em campo na 6.0.10. |
| Os campos de origem são opcionais | Baixa sem nomeOrigem/documentoOrigem/observacaoOrigem
(aplicativo atual, ou motorista que digitou tudo) continua válida. Origem ausente
significa teclado. |
| Sem player no TMS, o recurso não se paga | O valor do ditado não é digitar menos — é a prova em contestação. Isso só existe se a tela do TMS tocar o áudio ao lado da baixa. Guardar arquivo que ninguém consegue ouvir é custo sem retorno. |
1. Confirmam que anexo de áudio sem transcrição é aceito (Android antigo)? 2. A retenção do áudio segue a regra das fotos ou tem prazo próprio — voz é dado pessoal (LGPD)? 3. O formato AAC/M4A serve para o player de vocês, ou só WAV?
O caminhão aparece no mapa porque o aparelho junta as posições e as sobe em lotes — inclusive as das horas em que ele estava sem sinal.
{
"placa": "ABC1D23",
"romaneio": "4471",
"posicoes": [
{ "lat": -23.55, "lon": -46.63,
"precisaoM": 8, // raio de erro do GPS, em metros
"em": "2026-08-10T14:31:55-03:00" }, // quando o GPS MEDIU
… // já houve lote real com 316 posições
]
}
→ 200 { "sucesso": true }
O ritmo vem dos parâmetros da sessão: mede a cada intervaloPosicaoSeg, sobe a
cada intervaloEnvioSeg. O rastreamento roda apenas enquanto houver
serviço pendente — romaneio concluído, o GPS desliga e a notificação some.
Ligar e desligar é pelo parâmetro rastrear da sessão, e ele é
por romaneio, não por documento: é um caminhão, um GPS, uma trilha — de
cinco cargas no mesmo romaneio, não há como seguir três e não seguir duas. O ganho de mandar
esse campo é concreto: com rastrear: false o aplicativo não liga o GPS e
não pede a permissão de localização “Sempre” — que no Android 11 em diante
é uma ida manual aos ajustes, o passo onde mais se perde motorista na instalação. Hoje o
TMS tem esse parâmetro e ele não chega ao aplicativo
— carga marcada como “não rastrear” está sendo rastreada assim mesmo.
| Regra | Motivo |
|---|---|
| Guardem as duas datas | Quando o GPS mediu (vem no pacote) e quando o servidor gravou. A diferença entre elas é a métrica mais barata de saúde de rede em campo — e é a base da seção de observabilidade. |
| Posição com data antiga é comportamento correto | Um lote que chega às 17h com posições das 14h significa três horas sem sinal, não atraso de fila. Descartar por antiguidade apaga justamente o trecho de estrada que ninguém viu. |
| Lote parcialmente inválido: aceitem o que der | Recusar o lote inteiro por uma coordenada ruim faz o aplicativo reenviar o mesmo lote para sempre. |
| A recusa aqui é silenciosa — desduplicação é de vocês | É o único recurso em que o erro não aparece para o motorista nem entra na espera
crescente: o lote fica pendente e sobe de novo no próximo ciclo. Se vocês gravaram e
ainda assim responderam erro, as mesmas coordenadas chegam repetidas — e o pacote não
tem identificador próprio. A chave prática é placa + romaneio +
horário da medição. |
| Caminhão parado não manda posição — e está certo | O envio é por movimento, não por relógio: é o que faz a bateria durar o turno. Parado no almoço ou numa descarga de uma hora, o GPS dorme e volta quando o veículo se mexe. Alarme de “sem posição há X minutos”, sozinho, dispara todo dia à toa. |
Em vez de o aplicativo perguntar a cada minuto se o romaneio mudou, o TMS avisa o aparelho na hora — e a consulta periódica continua existindo como rede de segurança.
// na abertura da sessão, o aplicativo registra o aparelho: POST /mobile/dispositivos { "so": "android", "versaoApp": "6.1.0", "id": "…", "tokenPush": "…" } → 200 { "sucesso": true } // quando o romaneio muda, o TMS dispara pelo FCM (Firebase Cloud Messaging): { "tipo": "romaneio-alterado", "romaneio": "4471" } // o aplicativo acorda e consulta /alteracoes — o mesmo endpoint de sempre
Mensagem de dados, não de notificação. Com notificação, o sistema mostra um balão a cada mudança de romaneio; em uma semana o motorista desliga as notificações do aplicativo inteiro — e junto vai o aviso que importa. Mensagem de dados não aparece na tela: ela acorda o aplicativo.
O push é otimização, nunca garantia. O aparelho pode estar sem rede ou com economia de bateria agressiva. A consulta periódica continua existindo, e o pior caso é exatamente o comportamento sem push — por isso este é o último endpoint da lista: com as alterações baratas, perguntar já custa quase nada.
O aplicativo entrega a posição; quem sabe se o rastreamento está saudável é o TMS. São cinco informações de tela — e quatro delas saem de dados que já chegam nos endpoints acima, sem campo novo no aplicativo.
“Não chega posição há X minutos”, sozinho, é um alarme quebrado — dispara todo almoço e toda descarga, e em duas semanas ninguém mais olha. O alarme confiável cruza as três primeiras informações desta seção: deveria estar rastreando? · há quanto tempo não vem posição? · há quanto tempo o aparelho não fala? Em degraus: ⚪ tudo certo · 🟡 sem posição há 30 minutos devendo rastrear · 🔴 sem posição há 60 minutos, ou aparelho mudo.
Tudo o que este documento assume e precisa de confirmação — ou que só a equipe do TMS pode decidir. Cada pergunta vem com o porquê, e a linha em branco é para anotar a resposta na própria reunião, no papel ou por cima do PDF. Respondidas as onze, não sobra decisão em aberto.
/mobile/* vão responder?
A documentação define os caminhos, não a base. O aplicativo assumiu a convenção atual
— produção em ws.aleff.com.br, homologação em
wshomdev.grupoaleff.com.br (por exemplo:
…/ApiMobileNew/mobile/sessao). No aplicativo é um único ponto de
configuração; sem essa resposta, nada é testável.
Hoje a transportadora 999 cai no servidor de homologação — essa regra
continua? E quais credenciais de teste (transportadora, CPF, placa, romaneio de
exemplo) a equipe do aplicativo pode usar para validar os endpoints novos de ponta a
ponta?
Hoje a identidade viaja no corpo, sem token — o aplicativo seguiu isso. Se a equipe preferir token: em qual cabeçalho, e com qual validade? A exigência inegociável é que a validade cubra o turno inteiro com folga — token que vence no meio do dia joga o motorista para a tela de login no meio da rua.
A documentação define ISO 8601 com o fuso escrito
(…T14:32:10-03:00); o servidor atual só aceita UTC
(…T17:32:10Z) — e essa diferença já recusou 82 das 86 chamadas de um dia.
As duas opções servem ao aplicativo; o que não serve é ficar sem resposta
por escrito.
O documento de 12/08 já definiu o formato: multipart/form-data, com o
anexoId gerado no aparelho, como
especificado acima. Restam duas pontas: qual o limite de corpo
aceito por requisição (o aplicativo manda até ~525 KB por foto)? E se o desenho em
três passos interessar — qual armazenamento (S3, Azure Blob, outro)?
Reabrir devolve o mesmo romaneio; serviço baixado continua na lista com o estado marcado; sem romaneio é lista vazia, não erro (a forma de entrega do romaneio). É o único item desta lista que já custou o dia de um motorista em campo — precisa de um “sim” explícito.
Um serviço removido pela central pode ter uma baixa já feita offline, que chega minutos depois da remoção. O contrato manda o TMS aceitar e registrar essa baixa — mas o que ela vale no processo (reativa o serviço? vira pendência para a central resolver?) é decisão de negócio de vocês.
fotosObrigatorias (0 a 10), exigeNome,
exigeDocumento, exigeAssinatura e os dois ritmos do
rastreamento montam os passos da baixa sem nova versão do aplicativo.
Onde isso vai ser cadastrado no TMS — por romaneio, por transportadora, por cliente?
E quem mantém?
O TMS já tem o parâmetro que diz se a carga é rastreada — e hoje ele não chega ao aplicativo, que rastreia todo mundo. A proposta é mandá-lo na resposta da sessão, junto dos outros parâmetros (o porquê de ser por romaneio). Se preferirem mandar por documento, funciona — mas fica combinado que um documento marcado liga o rastreamento do romaneio inteiro.
A sessão entrega o catálogo inteiro: código numérico e estável, texto, tipo (entrega, coleta ou ambos) e se o motivo exige foto. Esse cadastro já existe no TMS? Quem o mantém — e o código de cada motivo está garantido como imutável?
Só interessa se a evolução do push entrar no plano: o disparo é feito pelo FCM (Firebase Cloud Messaging), do lado do TMS. Existe conta ou projeto Firebase da empresa? Quem dispararia o aviso quando o romaneio muda?