XML de compras públicas rejeitado: a versão do leiaute é parte do arquivo
Guarde o XSD de cada versão do leiaute, escolha a versão pela data de vigência, valide antes de enviar e registre versão, hash e protocolo de cada envio.
O gerador de XML funcionou por dois anos. Numa segunda-feira, todos os envios de aviso de licitação voltaram com erro de esquema, e o sistema mostrou só "falha no envio". O arquivo continuava idêntico ao que passava na semana anterior. O que mudou foi o leiaute: o órgão publicou uma versão nova do manual, com um elemento que deixou de ser opcional. Integração com sistema de compras públicas não é "um XML". É um XML de uma versão específica, válido a partir de uma data.
A resposta curta: guarde no repositório o XSD de cada versão do leiaute, faça o código escolher a versão pela data de vigência, valide o arquivo contra o esquema antes de enviar e registre em cada envio a versão usada, o hash do arquivo, o protocolo e a mensagem de retorno. Quando o órgão rejeitar, mostre o motivo, não "falhou".
Por que o arquivo que passava agora é rejeitado
O leiaute de um sistema de governo é uma especificação com data. Quando muda, o manual novo pode tornar obrigatório um campo antes opcional, mudar o tamanho de outro, trocar um código de tabela ou alterar o namespace. O portal passa a validar contra o esquema novo e o seu gerador, que implementou o esquema antigo de memória, continua produzindo o formato anterior.
Há um agravante: quem testa localmente costuma testar contra o arquivo de exemplo que veio com o manual antigo. O teste passa, porque compara o gerador com ele mesmo. Sem o XSD oficial da versão vigente no pipeline, ninguém descobre a mudança antes do órgão.
Este guia trata do lado técnico: guardar, escolher e validar a versão. Para o hábito de registrar a data do manual que você implementou, veja manual de integração do governo tem data.
Como organizar as versões do leiaute no projeto
- Baixe os esquemas oficiais de cada versão. Pegue os arquivos XSD que acompanham o manual, do site oficial, e guarde em pastas por versão, por exemplo
schemas/v2.3/eschemas/v3.0/. Anote a URL e a data do download num arquivo de texto ao lado. - Registre a data de vigência de cada versão. O manual ou o comunicado do órgão diz a partir de quando a versão vale. Essa data vai para o código, não para a memória da equipe.
- Escolha a versão pela data do envio. Na transição, as duas versões podem coexistir por um período, e o gerador tem de saber qual usar em cada dia.
- Valide antes de enviar. Todo arquivo passa pelo XSD da versão escolhida. Se não passa, o envio nem sai.
- Teste no ambiente de testes do órgão, com dado fictício. Se o manual oferece homologação, use. Nunca descubra campo obrigatório enviando uma compra real.
- Registre cada envio. Versão, hash, horário, protocolo e resposta completa do órgão.
Os números de versão e as datas do exemplo abaixo são fictícios. Os seus vêm do manual oficial vigente.
Como validar o XML contra o XSD da versão certa
Em PHP, a extensão DOM valida contra XSD com schemaValidate, e a libxml devolve a lista de erros com linha e mensagem. É exatamente o que você quer mostrar a quem vai corrigir:
<?php
declare(strict_types=1);
const LEIAUTES = [
'2.3' => ['xsd' => __DIR__ . '/schemas/v2.3/aviso.xsd', 'desde' => '2025-01-01'],
'3.0' => ['xsd' => __DIR__ . '/schemas/v3.0/aviso.xsd', 'desde' => '2026-07-01'],
];
function leiauteVigente(DateTimeImmutable $data): string
{
$escolhida = null;
foreach (LEIAUTES as $versao => $info) {
if ($data >= new DateTimeImmutable($info['desde'])) {
$escolhida = (string) $versao;
}
}
if ($escolhida === null) {
throw new RuntimeException('Nenhum leiaute vigente em ' . $data->format('Y-m-d'));
}
return $escolhida;
}
function errosDeEsquema(string $xml, string $versao): array
{
libxml_use_internal_errors(true);
libxml_clear_errors();
$doc = new DOMDocument();
$valido = $doc->loadXML($xml) && $doc->schemaValidate(LEIAUTES[$versao]['xsd']);
$erros = array_map(
fn(LibXMLError $e): string => 'linha ' . $e->line . ': ' . trim($e->message),
libxml_get_errors()
);
libxml_clear_errors();
return $valido ? [] : $erros;
}
O array LEIAUTES está em ordem crescente de data, então a última versão cuja data já chegou é a vigente. A conversão (string) tem um motivo: o PHP guarda chaves como '2.3' como texto, mas converte chaves que são inteiros, como '4', para número. Sem a conversão, uma futura versão '4' faria a função devolver int e, com strict_types, quebrar no tipo de retorno.
Para conferir um arquivo na mão, sem rodar o sistema, o xmllint faz o mesmo papel:
xmllint --noout --schema schemas/v3.0/aviso.xsd envio-2026-10-12.xml
Se o esquema inclui outros arquivos por xs:include ou xs:import, mantenha a estrutura de pastas igual à do pacote oficial, porque os caminhos são relativos. E gere o XML com uma biblioteca, não concatenando texto; o guia de gerar XML em PHP sem concatenar mostra como.
O registro de cada envio
Quando o órgão reclamar de um arquivo de três meses atrás, você precisa saber exatamente o que saiu. Uma tabela simples resolve:
CREATE TABLE envios_compras (
id bigint PRIMARY KEY,
processo varchar(40) NOT NULL,
leiaute_versao varchar(10) NOT NULL,
arquivo_sha256 char(64) NOT NULL,
enviado_em timestamp NOT NULL,
ambiente varchar(12) NOT NULL,
protocolo varchar(60),
resposta_status varchar(20) NOT NULL,
resposta_texto text
);
O hash, calculado com hash('sha256', $xml), prova qual conteúdo saiu, e o arquivo em si fica guardado num armazenamento com retenção definida. A coluna ambiente evita a confusão clássica entre o envio de teste e o real. A resposta_texto guarda a mensagem do órgão inteira, para a tela mostrar "elemento X obrigatório na linha 42" em vez de "falhou".
Por fim, lembre que o sistema não decide sozinho. O envio é feito em nome do órgão e há um responsável por ele. A tela deve deixar claro quem enviou, quando e com qual resultado, para essa pessoa poder agir.
Erros comuns
- Implementar o leiaute de memória ou de um exemplo antigo. O teste compara o gerador com ele mesmo e passa até o dia em que o órgão rejeita tudo.
- Trocar a versão no código na data da mudança, à mão. Se alguém esquecer ou adiantar, os envios do período saem no formato errado.
- Apagar o XSD antigo ao adotar o novo. Reenvios e retificações de processos antigos podem exigir a versão anterior.
- Mostrar "falha no envio" sem o motivo. Quem pode corrigir o dado não sabe o que corrigir e abre chamado para a TI.
- Testar com dado real em produção. Um envio de teste vira publicação oficial e precisa de retificação formal.
- Não registrar o hash do arquivo. Numa contestação, você não consegue provar o que enviou.
Checklist
- Os XSDs oficiais de cada versão estão no repositório, com URL e data do download anotadas.
- A data de vigência de cada versão está no código.
- O gerador escolhe a versão pela data do envio.
- Todo arquivo é validado contra o XSD antes de sair.
- Cada envio registra versão, hash, ambiente, protocolo e resposta completa.
- A tela de envio mostra a mensagem de rejeição do órgão.
- Mudanças de leiaute são testadas no ambiente de testes com dado fictício.
Abra o código do seu gerador agora e responda sem consultar ninguém: qual versão do manual ele implementa, e em que data essa versão deixa de valer?
Referência: https://www.gov.br/compras/pt-br/acesso-a-informacao/manuais/manual-siasg/manual-siasg-sidec/manual_sidec