Artículo técnico

Esquemas de extensión PDF/A-3 para XMP de Factur-X en Delphi

Usted ha construido una factura Factur-X y todas las comprobaciones del contenedor pasan. El catálogo lleva un arreglo /AF, el árbol de nombres EmbeddedFiles resuelve a la especificación de archivo correcta, el factur-x.xml incrustado tiene el /AFRelationship correcto de Alternative, y el ValidateFacturXInvoice integrado devuelve 1. Luego pasa el mismo archivo por veraPDF, el verificador de referencia que usan los portales tributarios, y este dictamina que todo el documento no es un PDF/A-3 válido. La estructura está bien. El problema son los metadatos, y la falla es una de las más fáciles de pasar por alto en todo el flujo de facturación electrónica

Vale la pena entender la razón por completo, porque explica una clase de defecto PDF/A que no tiene nada que ver con la página visible ni con el adjunto, y todo que ver con cómo el XMP se describe a sí mismo. Esta es la trampa que se esconde detrás de una comprobación de contenedor en verde

Las cuatro propiedades que hacen fallar el archivo

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 un lector necesita 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 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 diga qué significa la propiedad. Si esa declaración está ausente, reporta una falla contra la cláusula 6.6.2.3.1 de ISO 19005-3, que exige que toda propiedad no tomada 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

PDF Library for Delphi: un validador busca en los cuatro esquemas XMP predefinidos y no encuentra ningún esquema de extensión PDF/A para las propiedades fx, así que la factura Factur-X falla
veraPDF busca una declaración de esquema que el archivo nunca escribió — cuatro propiedades fx, cuatro oportunidades de fallar la cláusula 6.6.2.3.1

Por qué PDF/A rechaza una propiedad personalizada sin declarar

La regla parece pedante hasta que uno recuerda para qué existe PDF/A. El formato existe para que un archivo pueda abrirse y entenderse dentro de décadas, con software al que nunca se le contaron las convenciones de 2026. Se espera que un lector conforme le dé sentido al documento a partir del documento solo, sin ningún registro externo que consultar

Los metadatos personalizados rompen esa promesa a menos que el archivo lleve su propia descripción. Ante una propiedad fx:ConformanceLevel sin declarar, un lector futuro no puede saber a qué URI de espacio de nombres se vincula el prefijo fx, si el valor es texto, una fecha o un entero, ni si la propiedad describe el documento mismo o algún recurso externo. El mecanismo de 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 presente esa declaración, la propiedad se describe a sí misma y la cláusula 6.6.2.3.1 queda satisfecha. Sin ella, el validador no tiene más opción que tratar la propiedad como ininteligible y hacer fallar el archivo. La distinción de categoría importa aquí: las propiedades de factura como estas describen datos que provienen de fuera del procesador de PDF, así que se declaran 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 una bolsa pdfaExtension:schemas se ubica una entrada de esquema que nombra el esquema Factur-X, da 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 Text y un pdfaProperty:category de external. El marcado ilustrativo de abajo muestra la forma de ese bloque

Anatomía de PDF Library for Delphi del esquema de extensión PDF/A-3 que declara el esquema Factur-X con su URI de espacio de nombres, el prefijo fx y cuatro propiedades Text externas
Cada afirmación que hace la declaración debe coincidir con el bloque fx que la factura realmente escribe, o el validador sigue rechazando el archivo
<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 y ConformanceLevel se declaran de la misma forma -->
          </rdf:Seq>
        </pdfaSchema:property>
      </rdf:li>
    </rdf:Bag>
  </pdfaExtension:schemas>
</rdf:Description>

El URI de espacio de nombres y el prefijo no son cadenas fijas. Siguen al 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 mediante 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 el bloque de propiedades realmente usa, o el validador sigue sin poder conectar ambos. PDF Library for Delphi deriva ambos valores del nombre de archivo y la versión que usted pasa, de modo que la declaración y el bloque de propiedades siempre concuerdan

Cómo el asistente escribe ambas mitades juntas

En PDF Library for Delphi usted no arma ese XML a mano. Pone el documento en un modo PDF/A-3 y llama a un método. Lo primero que hay que resolver es el indicador de conformidad, porque Factur-X requiere PDF/A-3. Llamar a SetPDFAMode(7) selecciona el nivel PDF/A-3u, 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 agregar cualquier metadato de factura

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(7);            // PDF/A-3u: pdfaid:part=3, conformance=U
  PDF.NewDocument;
  // dibuje aquí la página de factura legible por humanos

  FileID := PDF.AddFacturXAssociatedFileFromString(
    InvoiceXML,                  // bytes XML UTF-8 sin procesar
    'EN16931',                   // ConformanceLevel
    'factur-x.xml',              // nombre del archivo incrustado
    'Factur-X invoice XML',      // texto de /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 el XML no coincide con el perfil

  PDF.SaveToFile('factur-x.pdf');
end;

Una sola llamada a AddFacturXAssociatedFileFromString hace el trabajo que le faltaba al archivo que fallaba. 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 de espacio de nombres y el prefijo del 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 modo que las propiedades personalizadas llegan 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, que es la misma protección que impide que una factura malformada llegue al archivo en primer lugar

El punto ciego que la comprobación de 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 esconde. ValidateFacturXInvoice comprueba 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 directriz del XML concuerda con el nivel de conformidad y que el /AFRelationship es uno de los 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 e InvalidFileNameProfile

Lo que no comprueba es si el esquema de extensión XMP está presente y es 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. Precisamente por eso una factura armada a mano, o una producida por un pipeline que escribió el bloque de propiedades sin la declaración, puede pasar sin problemas el validador integrado y aun así fallar en veraPDF por la cláusula 6.6.2.3.1. El validador de contenedor y el validador de metadatos PDF/A responden preguntas distintas, y solo el verificador PDF/A completo responde la segunda

PDF Library for Delphi: la misma factura Factur-X pasa todas las comprobaciones de contenedor de ValidateFacturXInvoice mientras veraPDF la rechaza porque ninguna capa lee el bloque de esquemas pdfaExtension
El validador de contenedor y el validador PDF/A responden preguntas distintas sobre el mismo archivo

Leer los problemas para saber qué capa se rompió

Como 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 afirmación solo sobre el contenedor, nunca sobre los metadatos PDF/A. Ejecute la validación integrada, recopile la lista de problemas y actúe sobre ella 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('Container issues: ', Issues);
  end
  else
    WriteLn('Container OK; verify XMP extension schema with a PDF/A checker.');
end;

Cuando esa llamada devuelve un nombre de problema, la falla está en el contenedor y el mensaje le dice qué parte. Cuando devuelve limpio y veraPDF sigue rechazando el archivo, la falla es casi siempre 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 usted mismo. Mantener las dos preguntas separadas en su propia mente es lo que convierte un rechazo desconcertante en un diagnóstico de una línea: los problemas de contenedor afloran a través de la lista de problemas, los problemas de declaración de esquema afloran solo a través de un validador PDF/A, y confundir ambos es lo que permite que el error se esconda

El panorama más amplio de conformidad PDF/A y PDF/UA, incluido cómo ejecutar una pasada de preflight antes de que un archivo salga de su compilación, se cubre en el recorrido de preflight 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 de el artículo de accesibilidad de PDF etiquetado. El manejo de esquemas de extensión descrito aquí se incluye como parte de PDF Library for Delphi, la biblioteca PDF para Delphi, junto con el soporte de perfiles Factur-X, ZUGFeRD y XRechnung documentado en todo este blog