Usted ha construido una factura Factur-X y pasa todas las comprobaciones del contenedor. El catálogo lleva un arreglo /AF, el árbol de nombres EmbeddedFiles resuelve a la especificación de archivo correcta, el archivo factur-x.xml incrustado tiene el /AFRelationship correcto de Alternative, y el validador integrado ValidateFacturXInvoice devuelve 1. Luego, usted pasa el mismo archivo por veraPDF, el verificador de referencia que usan los portales de impuestos, y determina que todo el documento no es un PDF/A-3 válido. La estructura es correcta. Los metadatos son el problema, y el fallo es uno de los más fáciles de pasar por alto en todo el flujo de trabajo de facturación electrónica
Vale la pena entender la razón en su totalidad, porque explica una clase de defecto de PDF/A que no tiene nada que ver con la página visible o el archivo adjunto, y todo que ver con cómo XMP se describe a sí mismo. Esta es la trampa que se oculta detrás de una comprobación de contenedor en verde
Las cuatro propiedades que hacen que el archivo falle
Una factura Factur-X escribe cuatro propiedades personalizadas en su paquete XMP para que el software posterior pueda leer el perfil de la factura sin analizar el XML incrustado. Viven en el espacio de nombres de Factur-X bajo el prefijo fx: fx:DocumentFileName, fx:DocumentType, fx:Version y fx:ConformanceLevel. Son exactamente los metadatos que necesita un lector para saber que este PDF lleva una factura EN 16931 llamada factur-x.xml en la versión 1.0
Ninguna de esas cuatro propiedades forma parte de ningún esquema XMP que PDF/A predefina. Los esquemas Dublin Core, XMP Basic, PDF y los de identificación PDF/A son conocidos por un lector conforme, pero fx: no lo es. Cuando veraPDF recorre el XMP y llega a una propiedad cuyo espacio de nombres no reconoce, busca una declaración que le indique qué significa la propiedad. Si esa declaración está ausente, reporta un fallo frente a la cláusula 6.6.2.3.1 de la norma ISO 19005-3, la cual requiere que toda propiedad que no provenga de un esquema predefinido se describa en un esquema de extensión PDF/A. Cuatro propiedades sin declarar, cuatro formas de que el archivo sea rechazado, y ninguna de ellas es visible para una comprobación de contenedor
Por qué PDF/A rechaza una propiedad personalizada simple
La regla parece pedante hasta que se recuerda para qué sirve PDF/A. El formato existe para que un archivo se pueda abrir y entender décadas en el futuro, mediante un software que nunca fue informado sobre las convenciones de 2026. Se espera que un lector conforme entienda el documento a partir del documento en sí, sin necesidad de consultar ningún registro externo
Los metadatos personalizados rompen esa promesa a menos que el archivo lleve su propia descripción. Dada una propiedad fx:ConformanceLevel simple, un lector del futuro no puede saber el URI del espacio de nombres al que se vincula el prefijo fx, si el valor es texto, una fecha o un número entero, o si la propiedad describe al documento en sí mismo o a algún recurso externo. El mecanismo del esquema de extensión PDF/A cierra esa brecha. Permite que el archivo declare, en una estructura XMP fija, el espacio de nombres, el prefijo y para cada propiedad un tipo de valor y una categoría internal o external. Una vez que esa declaración está presente, la propiedad se describe a sí misma y se satisface la cláusula 6.6.2.3.1. Sin ella, el validador no tiene otra opción que tratar la propiedad como ininteligible y rechazar el archivo. La distinción de categoría es importante aquí: las propiedades de factura como estas describen datos que provienen de fuera del procesador PDF, por lo que se declaran como external en lugar de internal
Qué contiene la declaración del esquema de extensión
La declaración es un rdf:Description en el paquete XMP que usa los tres espacios de nombres definidos por AIIM: pdfaExtension, pdfaSchema y pdfaProperty. Dentro de un grupo pdfaExtension:schemas se encuentra una entrada de esquema que nombra al esquema Factur-X, proporciona su pdfaSchema:namespaceURI y pdfaSchema:prefix, y luego enumera las cuatro propiedades en una secuencia pdfaSchema:property. Cada propiedad lleva un nombre, un pdfaProperty:valueType de tipo Text y una pdfaProperty:category de tipo external. El marcado ilustrativo a continuación muestra la forma de ese bloque
<rdf:Description rdf:about=""
xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
<pdfaExtension:schemas>
<rdf:Bag>
<rdf:li rdf:parseType="Resource">
<pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
<pdfaSchema:namespaceURI>urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#</pdfaSchema:namespaceURI>
<pdfaSchema:prefix>fx</pdfaSchema:prefix>
<pdfaSchema:property>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<pdfaProperty:name>DocumentFileName</pdfaProperty:name>
<pdfaProperty:valueType>Text</pdfaProperty:valueType>
<pdfaProperty:category>external</pdfaProperty:category>
<pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
</rdf:li>
<!-- DocumentType, Version, ConformanceLevel se declaran de la misma manera -->
</rdf:Seq>
</pdfaSchema:property>
</rdf:li>
</rdf:Bag>
</pdfaExtension:schemas>
</rdf:Description>
El URI del espacio de nombres y el prefijo no son cadenas fijas. Siguen el perfil. Un documento Factur-X usa urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# con el prefijo fx, mientras que un archivo ZUGFeRD 2.0 seleccionado a través de zugferd-invoice.xml resuelve a un URI diferente bajo su propio nombre de esquema. El esquema de extensión tiene que declarar el mismo URI de espacio de nombres que usa realmente el bloque de propiedades, o de lo contrario el validador seguirá sin poder conectar los dos. PDFlibPas deriva ambos valores a partir del nombre de archivo y la versión que usted pasa, por lo que la declaración y el bloque de propiedades siempre concuerdan
Cómo el asistente escribe ambas mitades juntas
En PDFlibPas, usted no ensambla ese XML a mano. Simplemente pone el documento en un modo PDF/A-3 y llama a un método. Lo primero que hay que resolver es la bandera de conformidad, porque Factur-X requiere PDF/A-3. Llamar a SetPDFAMode(7) selecciona el nivel PDF/A-3u, lo que establece pdfaid:part en 3 y pdfaid:conformance en U en el esquema de identificación. El paquete XMP ahora lleva la parte y la conformidad correctas antes de que se agregue cualquier metadato de factura
var
FileID: Integer;
begin
PDF.SetPDFAMode(7); // PDF/A-3u: pdfaid:part=3, conformidad=U
PDF.NewDocument;
// dibuje aquí la página de la factura legible por humanos
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML, // bytes XML UTF-8 sin procesar
'EN16931', // ConformanceLevel
'factur-x.xml', // nombre de archivo incrustado
'Factur-X invoice XML', // texto para /Desc
'Alternative', // /AFRelationship
'1.0', // versión del perfil
''); // código de país opcional
if FileID = 0 then
Exit; // no es PDF/A-3, o falta de coincidencia entre XML/perfil
PDF.SaveToFile('factur-x.pdf');
end;
Una sola llamada a AddFacturXAssociatedFileFromString hace el trabajo que le faltaba al archivo rechazado. Incrusta el XML como un archivo asociado PDF/A-3 con la relación que usted nombró, y registra las cuatro propiedades fx junto con el nombre del esquema, el URI del espacio de nombres y el prefijo para el perfil elegido. Cuando el documento se guarda, un paso interno llamado ApplyFacturXMetadata inyecta tanto el bloque de propiedades como la declaración pdfaExtension:schemas correspondiente en el paquete XMP, de manera que las propiedades personalizadas lleguen ya descritas. El método devuelve 0 si el documento no está en un modo PDF/A-3 o si el XML no coincide con el perfil declarado, lo que es la misma barrera que impide que una factura malformada llegue al archivo en primer lugar
El punto ciego que la comprobación del contenedor no puede ver
Esta es la parte que hay que nombrar con claridad, porque es la razón por la que el error se oculta. ValidateFacturXInvoice verifica el contenedor. Confirma que el catálogo tiene una entrada /AF, que el árbol de nombres EmbeddedFiles está presente, que el XML de la factura existe, que el nombre del archivo incrustado coincide con el perfil, que el ID de la directriz en el XML concuerda con el nivel de conformidad y que el /AFRelationship es uno que PDF/A-3 permite. Esas son comprobaciones reales y detectan defectos reales. GetFacturXValidationIssues los reporta por nombre, con identificadores como MissingCatalogAF, NotPDFA3, ConformanceGuidelineMismatch, InvalidAFRelationship y InvalidFileNameProfile
Lo que no verifica es si el esquema de extensión XMP está presente y correcto. Un archivo cuyo contenedor es impecable pero cuyas propiedades fx no están declaradas pasa todas las comprobaciones de problemas y devuelve 1, porque nada en esa lista inspecciona el bloque pdfaExtension:schemas. Esa es precisamente la razón por la que una factura construida a mano, o una producida por una canalización que escribió el bloque de propiedades sin la declaración, puede pasar sin problemas por el validador integrado y aun así fallar en veraPDF por la cláusula 6.6.2.3.1. El validador del contenedor y el validador de metadatos PDF/A responden a preguntas diferentes, y solo el comprobador PDF/A completo responde a la segunda
Leer los problemas para saber qué capa falló
Dado que las dos capas fallan de forma independiente, el hábito de diagnóstico correcto es leer primero los problemas del contenedor y tratar un resultado limpio como una declaración sobre el contenedor únicamente, nunca sobre los metadatos PDF/A. Ejecute la validación integrada, recopile la lista de problemas y actúe en consecuencia antes de recurrir a una herramienta externa
var
Issues: WideString;
begin
if PDF.ValidateFacturXInvoice = 0 then
begin
Issues := PDF.GetFacturXValidationIssues('|');
// identificadores a nivel de contenedor, por ejemplo:
// MissingCatalogAF, NotPDFA3, MissingEmbeddedFilesNameTree,
// ConformanceGuidelineMismatch, InvalidAFRelationship
WriteLn('Problemas del contenedor: ', Issues);
end
else
WriteLn('Contenedor OK; verifique el esquema de extensión XMP con un validador PDF/A.');
end;
Cuando esa llamada devuelve un nombre de problema, el fallo está en el contenedor y el mensaje le dice en qué parte. Cuando devuelve un resultado limpio y veraPDF todavía rechaza el archivo, el fallo está casi siempre en el esquema de extensión XMP, y la solución es dejar que AddFacturXAssociatedFileFromString escriba los metadatos en lugar de construir el bloque de propiedades por su cuenta. Mantener las dos preguntas separadas en su propia mente es lo que convierte un rechazo desconcertante en un diagnóstico de una sola línea: los problemas del contenedor afloran a través de la lista de problemas, los problemas de declaración de esquemas afloran solo a través de un validador PDF/A, y confundir los dos es lo que permite que el error se esconda
El panorama más amplio de conformidad con PDF/A y PDF/UA, incluyendo cómo ejecutar una pasada de comprobación previa antes de que un archivo salga de su compilación, se cubre en la guía de comprobación previa de PDF/A y PDF/UA. Si su factura también tiene que ser accesible, el árbol de estructura del que dependen PDF/A-3a y el PDF etiquetado es el tema del artículo sobre accesibilidad en PDF etiquetado. El manejo del esquema de extensión descrito aquí se incluye como parte de la PDFlibPas Delphi PDF Library junto con el soporte para los perfiles Factur-X, ZUGFeRD y XRechnung documentado a lo largo de este blog