HotPDF genera, detecta y valida facturas electrónicas ZUGFeRD y Factur-X desde Delphi y C++Builder. AddFacturXAssociatedFile incrusta el XML de factura EN 16931 en un contenedor PDF/A-3, DetectFacturXInvoice reconoce una factura híbrida entrante y HPDFValidateEInvoice ejecuta las reglas de negocio Schematron oficiales de EN 16931 contra el XML incrustado. Este artículo recorre los tres pasos y es sincero sobre dónde se detiene el motor de validación
La presión detrás de este conjunto de funciones es normativa, no técnica. Alemania y Francia están implantando por fases la facturación electrónica estructurada obligatoria para las transacciones B2B nacionales, y ambos mandatos convergen en el mismo modelo híbrido: la factura PDF que su ERP ya produce debe llevar dentro un gemelo XML legible por máquina. Una aplicación Delphi de contabilidad o ERP que hoy emite facturas PDF simples tiene una fecha límite firme para empezar a emitir Factur-X o ZUGFeRD, y la diferencia entre los dos nombres es menor de lo que sugiere el marketing: Factur-X y ZUGFeRD son la misma norma franco-alemana publicada bajo dos etiquetas
¿Qué convierte un PDF en una factura Factur-X válida?
Una factura Factur-X válida es un fichero PDF/A-3 (ISO 19005-3) que incrusta exactamente un XML de factura estructurado como fichero asociado, lo enlaza desde el catálogo del documento y lo declara en los metadatos de extensión XMP. Tres capas tienen que concordar. Primero, el contenedor debe ajustarse a PDF/A-3, el perfil de archivo que, a diferencia de PDF/A-1 y PDF/A-2, permite ficheros incrustados de cualquier tipo. Segundo, el XML de la factura, llamado factur-x.xml en los perfiles estándar o xrechnung.xml en la variante alemana XRechnung, debe registrarse en el array /AF del catálogo y en el árbol /Names /EmbeddedFiles, con una entrada AFRelationship que indique qué es el adjunto: Alternative significa que el XML es una representación equivalente de la factura visible, que es la semántica que exigen la mayoría de perfiles. Tercero, el esquema de extensión XMP de Factur-X debe nombrar el fichero incrustado, su nivel de conformidad y el tipo de documento, para que un sistema receptor pueda clasificar la factura sin analizar el XML en absoluto
La carga XML en sí sigue el modelo semántico EN 16931, la norma europea que define la factura básica: comprador, vendedor, líneas, desglose de impuestos, importe a pagar, cada uno con un número de término de negocio (BT-1 para el número de factura, BT-5 para la moneda, etc.). Por tanto, la validación EN 16931 se produce en dos capas distintas. La validación de esquema XML comprueba que el documento es CII bien formado con los nombres de elemento correctos. Las reglas de negocio Schematron comprueban que el contenido es coherente: que se cumple BR-CO-15 y el total de la factura equivale realmente a la suma de los importes netos de línea más el IVA, que los términos obligatorios están presentes para el perfil declarado. Un fichero puede superar la validación de esquema y seguir siendo una factura inválida, y por eso existe la capa de reglas de negocio
Por qué ZUGFeRD 2.5 no necesita un contenedor nuevo
ZUGFeRD 2.5 no cambia nada en el nivel del contenedor PDF: las URN de directriz, el esquema XMP y el token de versión son idénticos a los de ZUGFeRD 2.3 / Factur-X 1.0. Esto sorprende a la mayoría de implementadores, así que merece la pena citar la fuente. La especificación Factur-X 1.09 establece que el número de versión de la URI del esquema de extensión PDF/A no está relacionado con el número de versión de la especificación de datos XML, y que el versionado de las instancias de factura Factur-X se mantiene en 1.0, tanto en el fx:Version del XMP como en el BT-24 de factur-x.xml. El corpus oficial de muestras de ZUGFeRD 2.5 lo confirma: cada muestra incrusta factur-x.xml bajo una URN de directriz urn:factur-x.eu:1p0 con un fx:Version de 1.0. Lo que 2.5 añade realmente vive dentro del modelo de datos XML, con elementos nuevos como las sublíneas de factura y los identificadores de lote, que es la capa de negocio que genera su aplicación, no la capa de contenedor que gestiona la librería PDF
HotPDF codifica esta conclusión directamente. AddFacturXAssociatedFile acepta un argumento Version y lo normaliza mediante HPDFFacturXNormalizeVersion antes de escribir el XMP, de modo que un llamador que pase el alias 2p5 sigue emitiendo el token conforme a la especificación en lugar de inventar una versión XMP 2p5 que ningún validador aceptaría. Los auxiliares de detección tratan 2p5 como equivalente al leer, así que los viajes de ida y vuelta se mantienen simétricos. Si una librería o herramienta le dice que escribe un contenedor especial de ZUGFeRD 2.5, la propia especificación dice que tal cosa no existe
Incrustar el XML de la factura desde Delphi
THotPDF.AddFacturXAssociatedFile convierte una generación PDF/A-3 normal en una factura Factur-X con una sola llamada: incrusta los bytes del XML como fichero asociado, registra factur-x.xml en el array /AF del catálogo y en el árbol /Names /EmbeddedFiles, y emite los metadatos de extensión XMP de Factur-X correspondientes. Usted dibuja la página de factura legible por personas con la API de lienzo habitual de HotPDF; el trabajo del contenedor es una única línea entre BeginDoc y EndDoc
var
Pdf: THotPDF;
InvoiceXML, UBLXML: TBytes;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-2026-0042.pdf';
Pdf.PDFACompliance := '3B'; // contenedor PDF/A-3b
Pdf.BeginDoc;
// incrustar el XML de factura CII EN 16931 como factur-x.xml
InvoiceXML := BuildInvoiceXML; // producido por su capa de negocio
Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
// opcional: adjuntar una vista UBL como representación suplementaria
UBLXML := BuildUBLXML;
Pdf.AddUBLSupplementaryFile(UBLXML); // factur-xubl.xml, Alternative
// ... dibujar aquí la página visible de la factura ...
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Los valores predeterminados siguen la especificación: nombre de fichero factur-x.xml, relación Alternative, versión 1.0. AddUBLSupplementaryFile implementa la sección 6.4 de la especificación Factur-X 1.09, que permite que una representación UBL de la misma factura viaje como segundo adjunto llamado factur-xubl.xml con el tipo MIME application/xml y la relación Alternative. Aquí el orden importa, y HotPDF hace cumplir la restricción que la especificación implica: el fichero UBL es una vista suplementaria, nunca la factura principal, así que debe añadirse después de que AddFacturXAssociatedFile haya registrado el maestro CII. Tenga en cuenta que la página dibujada y el XML incrustado deben declarar la misma factura: un PDF que muestra un total y codifica otro es exactamente el modo de fallo que la facturación híbrida está diseñada para evitar. La parte PDF/A-3 de este flujo de trabajo, OutputIntent e incrustación de fuentes incluidos, se trata con más profundidad en el artículo complementario sobre validación PDF/A, PDF/X y PDF/UA en Delphi
¿Cómo se detecta una factura Factur-X en un PDF entrante?
THotPDF.DetectFacturXInvoice responde al lado receptor del mandato: cargue cualquier PDF y averigüe en una sola llamada si es una factura híbrida, qué perfil declara y qué adjuntos lleva. La función devuelve un registro THPDFFacturXInvoiceInfo con el nombre del fichero XML incrustado, el nombre y el tipo de fichero de documento declarados en el XMP, el token de versión, el nivel de conformidad, la URN de directriz y el valor de AFRelationship. Dos campos más informan de las vistas suplementarias: UBLFileName se rellena cuando hay un adjunto factur-xubl.xml, y EDIFACTFileName cuando viaja una vista EDIFACT; ninguno afecta al veredicto de detección, porque una representación suplementaria no puede existir sin la factura CII principal
var
Reader: THotPDF;
Info: THPDFFacturXInvoiceInfo;
begin
Reader := THotPDF.Create(nil);
try
if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
raise Exception.Create('No pages loaded.');
if Reader.DetectFacturXInvoice(Info) then
begin
Writeln('Invoice XML: ', string(Info.FileName));
Writeln('Conformance: ', string(Info.ConformanceLevel));
Writeln('Guideline URN: ', string(Info.GuidelineID));
Writeln('Relationship: ', string(Info.AFRelationship));
if Info.UBLFileName <> '' then
Writeln('UBL view: ', string(Info.UBLFileName));
end
else
Writeln('Not a Factur-X / ZUGFeRD hybrid invoice.');
finally
Reader.Free;
end;
end;
La detección lee las mismas señales que lee un validador conforme, el esquema de extensión XMP y el registro de ficheros asociados, en lugar de adivinar solo a partir de los nombres de fichero. El mismo objeto de documento cargado expone también los metadatos XMP y del diccionario Info para su inspección o corrección, un flujo de trabajo descrito en el artículo sobre editar metadatos en documentos PDF cargados
¿Reglas de negocio o esquema? Qué significa la validación EN 16931
HPDFValidateEInvoice, una función independiente de la unidad HPDFEInvoiceValidator, ejecuta ambas capas de validación en secuencia: primero la comprobación de la estructura del contenedor mediante ValidateFacturXInvoice, y después las reglas de negocio Schematron de EN 16931 contra el XML de factura extraído. HotPDF incluye los cinco ficheros de reglas oficiales de Factur-X 1.09, MINIMUM, BASIC WL, BASIC, EN 16931 y EXTENDED, bajo Lib/resources/Schematron/, y HPDFSchematronFileForProfile elige el fichero que corresponde al nivel de conformidad detectado, de modo que una factura EN 16931 se comprueba automáticamente contra Factur-X_1.09_EN16931.sch. La función devuelve True solo cuando el contenedor es válido y no se ha disparado ninguna regla de negocio
uses HPDFEInvoiceValidator;
var
Reader: THotPDF;
Report: THPDFEInvoiceValidationReport;
I: Integer;
begin
Reader := THotPDF.Create(nil);
try
Reader.LoadFromFile('incoming-invoice.pdf');
if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
Writeln('Container and business rules: OK')
else
Writeln('Validation found issues.');
if Report.BusinessRulesEvaluated then
begin
Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
for I := 0 to High(Report.BusinessRules) do
if (not Report.BusinessRules[I].Skipped) and
(Report.BusinessRules[I].Severity = stsError) then
Writeln(string(Report.BusinessRules[I].RuleID), ': ',
string(Report.BusinessRules[I].Message));
end;
finally
Reader.Free;
end;
end;
Cada entrada de BusinessRules lleva el identificador de regla extraído del texto de la aserción, BR-45, BR-CO-17 y el resto del esquema de nombres de EN 16931, más el XPath de contexto de la regla, la expresión de prueba y una severidad que refleja el atributo flag de Schematron. Eso hace que el informe sea accionable: en lugar de un simple aprobado o suspenso obtiene el término de negocio exacto que es incoherente, que es lo que su equipo de soporte necesita cuando un cliente pregunta por qué se ha rechazado una factura. Los equipos que ya generan informes de conformidad estructurados para PDF de archivo pueden integrar estos resultados en la misma canalización descrita en el artículo sobre automatizar informes de preflight PDF
Dónde se detiene el validador, con sinceridad
El motor Schematron se apoya en MSXML, que evalúa XPath 1.0. El fichero de reglas EN 16931 declara queryBinding="xslt2" y contiene 424 aserciones, la mayoría de las cuales usan solo funciones de XPath 1.0 como string-length y substring-after; esas se ejecutan de forma nativa. Una minoría se apoya en construcciones de XPath 2.0 como las conversiones xs:decimal o exists(); HotPDF preexplora cada expresión de prueba en busca de tokens conocidos de XPath 2.0 y marca esas reglas como Skipped con severidad info en lugar de intentar una evaluación que fallaría en silencio. El registro de resumen informa por separado de los recuentos de reglas evaluadas y omitidas, así que su registro siempre indica exactamente qué parte del conjunto de reglas se ha ejercitado. En Delphi 7, donde no hay enlaces MSXML disponibles, la unidad compila un stub que informa de que el motor no está disponible sin alterar el veredicto del contenedor; el motor completo requiere Delphi XE2 o posterior
El segundo límite importa aún más. Un resultado en verde de HPDFValidateEInvoice significa que la estructura del contenedor es conforme y que ninguna regla de negocio evaluada se ha disparado; no es un aval de cumplimiento fiscal. Las reglas de extensión nacionales, los requisitos específicos de cada receptor y las obligaciones legales sobre el contenido de la factura están por encima del núcleo de EN 16931 y fuera de cualquier librería. Trate el validador como la puerta que mantiene las facturas estructuralmente rotas fuera de su bandeja de salida y señala las incoherentes en su bandeja de entrada, ejecute un validador XPath 2.0 completo en su canalización de publicación cuando la certificación lo exija, y deje que sus asesores fiscales se ocupen de la cuestión del cumplimiento. Las API de incrustación, detección y validación mostradas aquí forman parte del componente HotPDF para Delphi estándar para Delphi y C++Builder, con un ejemplo completo de factura electrónica que cubre los siete perfiles de ZUGFeRD y Factur-X