Fiscalapi
Validar XML de una factura: estructura y Anexo 20

Validar XML de una factura: estructura y Anexo 20

12 de septiembre de 2026

Validar XML de una factura recibida no es abrir el archivo y ver que trae datos. Es comprobar que el documento cumple el esquema que el SAT definió en el Anexo 20 para su versión, incluyendo los esquemas de cada complemento que el propio comprobante declara.

La diferencia importa porque un XML puede parsear sin errores en tu lenguaje favorito y seguir siendo inválido. xml.etree te dice si el documento está bien formado. No te dice nada sobre si Exportacion está presente, si ClaveProdServ existe en el catálogo o si los nodos van en el orden que exige el esquema.

Ese es el trabajo de la validación estructural, y es el primero de los controles que corres sobre una factura de proveedor. Primero, no el único.

Qué significa validar XML de una factura

Validar el XML de una factura es verificar que el documento cumple dos condiciones: que está bien formado según la especificación XML, y que es válido contra los XSD del Anexo 20 de su versión más los XSD de los complementos declarados en schemaLocation. La primera condición la revisa cualquier parser. La segunda requiere los esquemas del SAT.

Bien formado y válido son cosas distintas. Un XML bien formado tiene sus etiquetas cerradas, sus caracteres escapados y una sola raíz. Un XML válido, además, respeta un contrato: qué atributos son obligatorios, qué valores acepta cada uno, en qué orden aparecen los nodos hijos.

Los CFDI apócrifos que he visto casi siempre fallan aquí. No en el sello, que ni siquiera intentan falsificar bien, sino en detalles del esquema que alguien pasó por alto al copiar la estructura de un comprobante real.

La jerarquía que el esquema exige

El XSD del Anexo 20 define los hijos de cfdi:Comprobante como una secuencia. Secuencia significa orden fijo: si tu proveedor mandó la Addenda antes del Complemento, el documento es inválido aunque ambos nodos existan y tengan el contenido correcto.

    • cfdi:InformacionGlobal (opcional)
    • cfdi:CfdiRelacionados (opcional, repetible)
    • cfdi:Emisor (obligatorio)
    • cfdi:Receptor (obligatorio)
      • cfdi:Concepto (1 o mas)
    • cfdi:Impuestos (opcional)
      • tfd:TimbreFiscalDigital
    • cfdi:Addenda (opcional)

En un comprobante recibido, cfdi:Complemento nunca es realmente opcional: si no trae tfd:TimbreFiscalDigital, lo que tienes es un CFDI sin timbrar, o sea un borrador. Que llegue a cuentas por pagar más seguido de lo que parece.

Qué revisa la validación estructural

Qué se revisaEjemplo de falla
Atributos obligatorios presentesFalta Exportacion en un CFDI 4.0
Valores dentro de catálogoFormaPago con valor 99 cuando el catálogo no lo contempla para ese caso
Patrones y longitudesRfc de 11 caracteres, UUID fuera del formato 8-4-4-4-12
Tipos de dato y decimalesTotal con cuatro decimales en una moneda que admite dos
Orden de los nodos hijosAddenda colocada antes de Complemento
Namespaces declaradosPrefijo cfdi apuntando a cfd/3 en un comprobante marcado como 4.0
Esquemas de complementosSe declara el namespace de Carta Porte sin incluir su XSD en schemaLocation

Esta tabla es la razón por la que la validación estructural vale la pena aunque tengas los otros controles. Atrapa el comprobante roto antes de que entre a tu ERP y genere un registro contable que después alguien tiene que reversar.

schemaLocation decide contra qué se valida el comprobante

El atributo xsi:schemaLocation contiene pares de valores separados por espacios: primero un namespace, luego la URL del XSD que lo define. Un CFDI 4.0 con complemento de pagos declara al menos dos pares.

Aquí está el detalle que a mucha gente se le escapa: si el emisor declara el namespace de un complemento pero no incluye su XSD en schemaLocation, ese complemento no se valida. El documento pasa la validación estructural con un complemento que nadie revisó. No es un hueco de seguridad, es cómo funciona la validación por esquema. Pero explica por qué un comprobante "válido" puede traer un complemento de pago con datos que no cumplen nada.

Los pares de schemaLocation se separan por espacios en blanco, y un salto de línea entre el namespace y su URL es igual de válido que un espacio. Si tu código parte el atributo con split(" ") a secas, un XML formateado con saltos de línea te va a dar pares corridos. Divide por cualquier secuencia de espacio en blanco.

Los errores que rompen el parseo

Estos son los que se repiten. Ninguno es exótico, y todos han llegado a producción en algún momento.

SíntomaCausa raízQué hacer
El parser falla en el carácter 1BOM UTF-8 al inicio del archivoQuitar el BOM antes de parsear, o abrir con utf-8-sig
Acentos convertidos en símbolos rarosEl archivo viene en ISO-8859-1 pero la declaración dice UTF-8Respetar la declaración del XML, no la del sistema de archivos
Error de entidad no definidaUn & sin escapar en un nombre comercialEl emisor debe escaparlo como &, no se corrige del lado receptor
Válido contra el XSD pero el sello no verificaEspacios dobles o al inicio de un atributoVer la nota de abajo
Complemento ignoradoNamespace declarado sin XSD en schemaLocationValidar el complemento por separado contra su esquema
Rechazo por versiónVersion="4.0" con el namespace de cfd/3El namespace manda, no el atributo

El cuarto caso merece explicación, porque es el más desconcertante cuando lo ves por primera vez. Los tipos de dato del Anexo 20 usan whiteSpace="collapse", lo que significa que el procesador XML colapsa los espacios consecutivos y recorta los de los extremos antes de comparar. La cadena original a partir de la cual se calcula el sello se construye con los valores ya colapsados. Entonces si el XML trae RAMIREZ LOPEZ con dos espacios, el esquema lo acepta, pero el sello tiene que haberse calculado sobre RAMIREZ LOPEZ con uno. Si el sistema emisor selló el string crudo, el comprobante es estructuralmente válido y criptográficamente inválido al mismo tiempo.

No es un caso teórico. Es lo que pasa cuando alguien construye la cadena original concatenando strings en lugar de aplicar la transformación XSLT que publica el SAT.

Estructura válida no significa factura con efectos fiscales

Este es el punto donde la mayoría de las guías se detiene, y donde empieza el riesgo real.

Un XML puede cumplir el Anexo 20 al cien por ciento y aun así no servirte para deducir. Cumplir el esquema dice que el documento está bien armado. No dice quién lo firmó, si esa firma verifica, si el certificado estaba vigente el día de la emisión, si el comprobante sigue vigente en el SAT hoy, ni si el emisor está publicado como EFOS.

El orden del diagrama no es decorativo. La validación estructural va primero porque es la más barata y porque si falla, las demás no tienen sentido: no puedes verificar el sello de un documento cuya cadena original no se puede construir. Los listados van al final porque no dependen del XML, solo del RFC del emisor.

El criterio que más gente omite es el estatus en el SAT. Un CFDI cancelado conserva su estructura intacta y su sello sigue verificando: la cancelación no toca el XML. La única forma de enterarte es consultando el servicio del SAT, y hay que consultarlo otra vez antes del cierre, no solo al recibir.

Sobre los listados del artículo 69-B, el orden tiene una consecuencia práctica: un comprobante que pasa los cinco controles técnicos puede seguir sin producir efectos fiscales si el emisor está publicado como definitivo. Eso lo cubro a detalle en la guía sobre la lista 69-B y cómo verificar proveedores.

Cómo automatizar la validación del XML

Mantener los XSD del Anexo 20 y de cada complemento actualizados es trabajo recurrente que no le agrega nada a tu producto. Los esquemas cambian, los complementos se versionan, y el día que el SAT publica una revisión tu validador empieza a rechazar comprobantes legítimos. Siempre en cierre de mes.

La API de validación de CFDI de Fiscalapi corre la verificación estructural contra el servicio oficial del SAT, así que el esquema contra el que se valida es siempre el vigente. Mandas el XML en base64, marcas los criterios que necesitas y recibes un veredicto por criterio con la descripción del estado que lo sustenta, lista para archivarse junto al comprobante.

Valida tu primer XML en sandbox

Estructura del Anexo 20, sello del emisor, vigencia del certificado, sello del SAT, estatus del comprobante y listados 69-B en una sola llamada. Marcas los criterios que necesitas y pagas solo por esos.

Ver la API de validación

El contrato de respuesta y los SDK oficiales para .NET, TypeScript, Python, PHP y Java están en la documentación. Si quieres mandar un XML real antes de escribir una línea de integración, la cuenta de sandbox no tiene costo.

Preguntas frecuentes

Si vas a construir tu propio validador, empieza por la transformación XSLT de la cadena original que publica el SAT en lugar de concatenar atributos a mano. Es la decisión que separa un validador que funciona de uno que funciona hasta que llega un nombre con dos espacios.