SAFTCheck
← Blog
2026-05-02 · 4 min de lectura

Cómo corregir el error de codificación UTF-8 del SAF-T en Portugal

Por qué la AT rechaza silenciosamente los ficheros SAF-T XML declarados como UTF-8 y la corrección de una sola línea que hace que se acepten.

El error que no se ve

Subes tu SAF-T XML mensual a la AT, el portal te muestra "ficheiro inválido" y nada más. Sin número de línea. Sin motivo. El consejo habitual es volver a exportar desde el ERP y reenviar. Lo haces. Sigue fallando.

Si tu ERP es Xero, QuickBooks, Datev o una exportación genérica de SAP, casi seguro que es el mismo bug: tu fichero está codificado en UTF-8, pero la AT solo acepta Windows-1252.

Por qué la AT rechaza UTF-8 en silencio

La especificación SAF-T (PT) de la AT, esquema actual 1.04_01, exige Windows-1252 (CP-1252) como codificación del fichero. El validador de la AT no devuelve un error útil cuando la codificación es incorrecta — el fichero se rechaza sin más en la entrada. El XML está bien formado; el esquema valida. Pero la codificación a nivel de bytes de los caracteres acentuados (ç, ã, é, ó) no coincide con lo que esperan las herramientas internas de la AT.

Los ERPs extranjeros usan UTF-8 por defecto porque UTF-8 es el estándar moderno en todas partes excepto en este canal fiscal portugués concreto. Escriben el prólogo XML así:

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

La AT espera:

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

Por qué editar solo la declaración no sirve

El primer instinto es abrir el fichero y sustituir UTF-8 por Windows-1252 en el prólogo, guardar y reenviar. Eso empeora las cosas. La declaración ahora dice Windows-1252 pero los bytes siguen siendo UTF-8. Cualquier carácter fuera de ASCII se ve como basura cuando la AT (o cualquiera) lee el fichero con la codificación declarada.

La corrección que sí funciona

Hay que hacer dos cosas, en el orden correcto:

  1. Transcodifica los bytes del cuerpo de UTF-8 a Windows-1252. Cada Á (0xC3 0x81 en UTF-8) se convierte en un único byte 0xC1. Cada ç (0xC3 0xA7) en 0xE7. Y así sucesivamente.
  2. Reescribe la declaración de codificación para que coincida: encoding="Windows-1252".

Si te saltas el paso 1, obtienes basura. Si te saltas el paso 2, la AT sigue rechazando.

Hacerlo en 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);

Hacerlo en un clic

SAFTCheck hace exactamente esa transformación, además de eliminar cualquier byte BOM UTF-8 (otra causa de rechazo silencioso), valida el fichero corregido contra el esquema AT 1.04_01 y te entrega un XML limpio para descargar. Gratis para una corrección; 7 € por la corrección de pago con informe completo y PDF.

Cómo confirmar que la corrección funcionó

Tras la conversión, ejecuta file saft.fixed.xml en un terminal Unix — debería responder "Non-ISO extended-ASCII text" o "ISO-8859 text", no "UTF-8 Unicode". Abre el fichero en un editor hexadecimal: cualquier carácter portugués acentuado debe ser un solo byte en el rango 0xA0–0xFF, no una secuencia de dos bytes con prefijo 0xC2/0xC3.

Vuelve a enviar el fichero a la AT. El "ficheiro inválido" debería desaparecer. Si no desaparece, la codificación no era el único problema — pero suele serlo.

Reglas relacionadas que SAFTCheck detecta a la vez

  • BOM al inicio del fichero. La AT rechaza BOM UTF-8 (0xEF 0xBB 0xBF) y BOMs UTF-16.
  • Dígito de control del NIF. Algoritmo Mod-11; un dígito mal y el fichero no es válido.
  • Formato ATCUD. Obligatorio desde 2023, debe encajar con ^[A-Z0-9]{8}-[1-9]\d{0,9}$.
  • Fechas de la cabecera. StartDate antes de EndDate, FiscalYear coincide con el año de StartDate, EndDate no está en el futuro.

Otros artículos

Usamos una cookie de sesión para iniciar sesión, una cookie de idioma para la preferencia lingüística y Tawk.to para el widget de chat en vivo (que establece sus propias cookies al abrir el chat). Google Analytics (GA4) se carga solo después de que aceptes, con anonimización de IP; sin rastreadores publicitarios. Consulta nuestra Política de Privacidad.