SAFTCheck
Blog

4 min de leitura

Como corrigir o erro de codificação UTF-8 do SAF-T em Portugal

Porque a AT rejeita silenciosamente ficheiros SAF-T XML declarados como UTF-8 e a correção de uma linha que faz com que passem.

O erro que não consegue ver

Submete o seu SAF-T XML mensal à AT, o portal mostra "ficheiro inválido" e nada mais. Sem número de linha. Sem razão. O conselho habitual é voltar a exportar do ERP e submeter outra vez. Faz isso. Continua a falhar.

Se o seu ERP for Xero, QuickBooks, Datev ou uma exportação SAP genérica, é quase de certeza o mesmo bug: o seu ficheiro está codificado em UTF-8, mas a AT só aceita Windows-1252.

Porque é que a AT rejeita UTF-8 em silêncio

A especificação SAF-T (PT) da AT, esquema atual 1.04_01, exige Windows-1252 (CP-1252) como codificação do ficheiro. O validador da AT não devolve um erro útil quando a codificação está errada — o ficheiro é simplesmente rejeitado à entrada. O XML está bem formado; o esquema passa nas verificações. Mas a codificação ao nível dos bytes dos caracteres acentuados (ç, ã, é, ó) não corresponde ao que as ferramentas a jusante da AT esperam.

Os ERPs estrangeiros usam UTF-8 por defeito porque UTF-8 é o padrão moderno em todo o lado, exceto neste pipeline fiscal português específico. Eles escrevem o prólogo XML assim:

<?xml version="1.0" encoding="UTF-8"?>

A AT espera:

<?xml version="1.0" encoding="Windows-1252"?>

Porque é que mexer só na declaração não resolve

O primeiro instinto é abrir o ficheiro, substituir UTF-8 por Windows-1252 no prólogo, gravar e voltar a submeter. Isso piora as coisas. A declaração agora diz Windows-1252 mas os bytes continuam a ser UTF-8. Qualquer caractere fora de ASCII fica em garbage quando a AT ou qualquer outra pessoa lê o ficheiro com a codificação declarada.

A correção que funciona mesmo

Precisa de fazer duas coisas, pela ordem certa:

  1. Transcodifique os bytes do corpo de UTF-8 para Windows-1252. Cada Á (0xC3 0x81 em UTF-8) passa a um único byte 0xC1. Cada ç (0xC3 0xA7) passa a 0xE7. E por aí adiante.
  2. Reescreva a declaração de codificação para corresponder: encoding="Windows-1252".

Se saltar o passo 1, fica com garbage. Se saltar o passo 2, a AT continua a rejeitar.

Fazê-lo em PHP

$xml = file_get_contents('saft.xml');
$converted = mb_convert_encoding($xml, 'Windows-1252', 'UTF-8');
$converted = preg_replace(
    '/(<\?xml[^>]*encoding=)["\'][^"\']+["\']/i',
    '$1"Windows-1252"',
    $converted,
    1,
);
file_put_contents('saft.fixed.xml', $converted);

Fazê-lo num clique

O SAFTCheck faz exatamente esta transformação, mais retira quaisquer bytes BOM UTF-8 (outra causa de rejeição silenciosa), valida o ficheiro corrigido contra o esquema AT 1.04_01 e devolve um XML limpo para download. Grátis para uma correção; €7 para a correção paga com relatório completo e PDF.

Como confirmar que a correção pegou

Depois da conversão, corra file saft.fixed.xml num terminal Unix — deve devolver "Non-ISO extended-ASCII text" ou "ISO-8859 text", e não "UTF-8 Unicode". Abra o ficheiro num editor hexadecimal: qualquer caractere português acentuado deve ser um único byte na gama 0xA0–0xFF, e não uma sequência de dois bytes com prefixo 0xC2/0xC3.

Submeta o ficheiro à AT outra vez. O "ficheiro inválido" deve desaparecer. Se não desaparecer, a codificação não era o único problema — mas geralmente é.

Regras relacionadas que o SAFTCheck apanha ao mesmo tempo

  • BOM no início do ficheiro. A AT rejeita BOM UTF-8 (0xEF 0xBB 0xBF) e BOMs UTF-16.
  • Dígito de controlo do NIF. Algoritmo Mod-11; um dígito errado e o ficheiro é inválido.
  • Formato ATCUD. Obrigatório desde 2023, tem de corresponder a ^[A-Z0-9]{8,}-(?!0+$)\d+$.
  • Datas do cabeçalho. StartDate antes de EndDate, FiscalYear corresponde ao ano de StartDate, EndDate não está no futuro.

Outros artigos