HotPDF genera, detecta y valida facturas electrónicas ZUGFeRD y Factur-X desde Delphi y C++Builder. AddFacturXAssociatedFile incrusta el XML de la factura EN 16931 en un contenedor PDF/A-3, DetectFacturXInvoice reconoce una factura híbrida entrante y HPDFValidateEInvoice ejecuta las reglas de negocio oficiales Schematron de EN 16931 contra el XML incrustado. Este artículo detalla estos tres pasos y analiza con honestidad los límites del motor de validación
La necesidad detrás de este conjunto de características es regulatoria, no técnica. Alemania y Francia están introduciendo progresivamente la facturación electrónica estructurada obligatoria para transacciones domésticas B2B, y ambos mandatos convergen en el mismo modelo híbrido: la factura en PDF que ya produce su ERP debe contener un XML gemelo legible por máquina. Una aplicación de contabilidad o ERP en Delphi que emita facturas en PDF plano hoy en día se enfrenta a un plazo estricto para comenzar a emitir Factur-X o ZUGFeRD, y la diferencia entre ambos nombres es menor de lo que el marketing sugiere: Factur-X y ZUGFeRD son el mismo estándar franco-alemán publicado bajo dos denominaciones diferentes
¿Qué hace que un PDF sea una factura Factur-X válida?
Una factura Factur-X válida es un archivo PDF/A-3 (ISO 19005-3) que incrusta exactamente una factura estructurada en XML como archivo asociado, la vincula desde el catálogo del documento y la declara en los metadatos de extensión XMP. Tres capas deben coincidir. En primer lugar, el contenedor debe cumplir con PDF/A-3, el perfil de archivo que (a diferencia de PDF/A-1 y PDF/A-2) permite archivos incrustados de cualquier tipo. En segundo lugar, el XML de la factura, denominado factur-x.xml para perfiles estándar o xrechnung.xml para la variante alemana XRechnung, debe registrarse en la matriz /AF del catálogo y en el árbol /Names /EmbeddedFiles, con una entrada AFRelationship que indique qué es el archivo adjunto: Alternative significa que el XML es una representación equivalente de la factura visible, que es la semántica requerida por la mayoría de los perfiles. En tercer lugar, el esquema de extensión XMP de Factur-X debe indicar el nombre del archivo incrustado, su nivel de conformidad y el tipo de documento, para que un sistema receptor pueda clasificar la factura sin necesidad de analizar el XML en absoluto
La carga útil de XML sigue el modelo semántico EN 16931, la norma europea que define la factura básica: comprador, vendedor, artículos de línea, desglose de impuestos, importe pagadero, cada uno con un número de término de negocio (BT-1 para el número de factura, BT-5 para la divisa, etc.). Por lo tanto, la validación de EN 16931 se realiza en dos capas distintas. La validación del esquema XML comprueba que el documento sea un CII bien formado con los nombres de elemento correctos. Las reglas de negocio de Schematron comprueban que el contenido sea coherente: que se cumpla BR-CO-15 y que el total de la factura sea realmente igual a la suma de los importes netos de las líneas más el IVA, y que los términos obligatorios estén presentes para el perfil declarado. Un archivo puede superar la validación del esquema y seguir siendo una factura no válida, razón por la cual existe la capa de reglas de negocio
Por qué ZUGFeRD 2.5 no necesita un nuevo contenedor
ZUGFeRD 2.5 no cambia nada a nivel de contenedor PDF: los URN de directrices, 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 los desarrolladores, por lo que vale la pena citar la fuente. La especificación Factur-X 1.09 establece que el número de versión en el 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 la versión de las instancias de factura de Factur-X se mantiene en 1.0, tanto en el parámetro XMP fx:Version como en el BT-24 de factur-x.xml. El corpus de muestras oficial de ZUGFeRD 2.5 lo confirma: cada muestra incrusta factur-x.xml bajo un URN de directriz urn:factur-x.eu:1p0 con un valor de fx:Version de 1.0. Lo que realmente añade la versión 2.5 reside dentro del modelo de datos XML (nuevos elementos como líneas de subfactura e identificadores de lote), que es la capa de negocio que genera su aplicación, no la capa de contenedor que gestiona la biblioteca PDF
HotPDF codifica este hallazgo directamente. AddFacturXAssociatedFile acepta un argumento Version y lo normaliza a través de HPDFFacturXNormalizeVersion antes de escribir el XMP, por lo que un llamador que pase el alias 2p5 seguirá emitiendo el token compatible con la especificación en lugar de inventar una versión XMP 2p5 que ningún validador aceptaría. Las funciones auxiliares de detección tratan 2p5 como equivalente al leer, por lo que los ciclos de ida y vuelta se mantienen simétricos. Si una biblioteca o herramienta le indica que escribe un contenedor ZUGFeRD 2.5 especial, la propia especificación dice que no existe tal cosa
HotPDF codifica este hallazgo directamente. AddFacturXAssociatedFile acepta un argumento Version y lo normaliza a través de HPDFFacturXNormalizeVersion antes de escribir el XMP, por lo que un llamador que pase el alias 2p5 seguirá emitiendo el token compatible con la especificación en lugar de inventar una versión XMP 2p5 que ningún validador aceptaría. Las funciones auxiliares de detección tratan 2p5 como equivalente al leer, por lo que los ciclos de ida y vuelta se mantienen simétricos. Si una biblioteca o herramienta le indica que escribe un contenedor ZUGFeRD 2.5 especial, la propia especificación dice que no existe tal cosa
Incrustación del XML de la factura desde Delphi
THotPDF.AddFacturXAssociatedFile convierte una ejecución de generación de PDF/A-3 normal en una factura Factur-X con una sola llamada: incrusta los bytes XML como archivo asociado, registra factur-x.xml en la matriz /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 la factura legible por humanos con la API de lienzo normal de HotPDF; el trabajo del contenedor consiste en una sola línea entre BeginDoc and EndDoc
var
Pdf: THotPDF;
InvoiceXML, UBLXML: TBytes;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-2026-0042.pdf';
Pdf.PDFACompliance := '3B'; // PDF/A-3b container
Pdf.BeginDoc;
// incrustar el XML de la factura EN 16931 CII 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 complementaria
UBLXML := BuildUBLXML;
Pdf.AddUBLSupplementaryFile(UBLXML); // factur-xubl.xml, Alternative
// ... dibujar la página de la factura visible aquí ...
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Los valores predeterminados siguen la especificación: nombre de archivo 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 se incorpore como un segundo archivo adjunto llamado factur-xubl.xml con el tipo MIME application/xml y la relación Alternative. El orden importa en este caso, y HotPDF aplica la restricción implícita en la especificación: el archivo UBL es una vista complementaria, nunca la factura principal, por lo que debe añadirse después de que AddFacturXAssociatedFile haya registrado el CII principal. Tenga en cuenta que la página dibujada y el XML incrustado deben reflejar 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 de PDF/A-3 de este flujo de trabajo, incluyendo OutputIntent y la incrustación de fuentes, se cubre con más detalle en el artículo complementario sobre validación de PDF/A, PDF/X y PDF/UA en Delphi
¿Cómo detectar 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é archivos adjuntos contiene. La función devuelve un registro THPDFFacturXInvoiceInfo con el nombre de archivo XML incrustado, el nombre de archivo de documento y tipo declarados en XMP, el token de versión, el nivel de conformidad, el URN de la directriz y el valor de AFRelationship. Otros dos campos informan sobre vistas complementarias: UBLFileName se rellena cuando hay un archivo adjunto factur-xubl.xml presente, y EDIFACTFileName cuando se incluye una vista EDIFACT; ninguno de los dos afecta al veredicto de detección, porque una representación complementaria 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('vista UBL: ', string(Info.UBLFileName));
end
else
Writeln('No es una factura híbrida Factur-X / ZUGFeRD.');
finally
Reader.Free;
end;
end;
La detección lee las mismas señales que lee un validador compatible (el esquema de extensión XMP y el registro de archivos asociados) en lugar de deducirlo únicamente a partir de los nombres de los archivos. El mismo objeto de documento cargado también expone los metadatos de XMP y del diccionario de información para su inspección o corrección, un flujo de trabajo descrito en el artículo sobre la edición de 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 a través de ValidateFacturXInvoice, y luego las reglas de negocio Schematron de EN 16931 contra el XML de la factura extraído. HotPDF distribuye los cinco archivos de reglas oficiales de Factur-X 1.09 (MINIMUM, BASIC WL, BASIC, EN 16931 y EXTENDED) bajo la ruta Lib/resources/Schematron/, y HPDFSchematronFileForProfile selecciona el archivo correspondiente al nivel de conformidad detectado, por lo 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 activado 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('Contenedor y reglas de negocio: OK')
else
Writeln('La validación detectó problemas.');
if Report.BusinessRulesEvaluated then
begin
Writeln(Report.BusinessSummary.Evaluated, ' reglas evaluadas, ',
Report.BusinessSummary.Skipped, ' omitidas (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 en BusinessRules contiene el identificador de regla analizado a partir del texto de la aserción (BR-45, BR-CO-17 y el resto del esquema de nomenclatura de EN 16931), además del XPath de contexto de la regla, la expresión de prueba y una gravedad que refleja el atributo flag de Schematron. Esto hace que el informe sea útil para tomar medidas: en lugar de un simple resultado de aprobado/suspenso, obtiene el término de negocio exacto que es incoherente, lo cual es lo que necesita su equipo de soporte cuando un cliente pregunta por qué se rechazó una factura. Los equipos que ya generan informes de conformidad estructurados para archivos PDF pueden integrar estos resultados en la misma canalización descrita en el artículo sobre la automatización de informes preflight de PDF
Límites honestos del validador
El motor Schematron está basado en MSXML, que evalúa XPath 1.0. El archivo de reglas de EN 16931 declara queryBinding="xslt2" y contiene 424 aserciones, la mayoría de las cuales utilizan únicamente funciones de XPath 1.0 como string-length y substring-after, las cuales se ejecutan de forma nativa. Una minoría depende de construcciones de XPath 2.0 como conversiones de tipo xs:decimal o exists(); HotPDF analiza previamente cada expresión de prueba en busca de tokens conocidos de XPath 2.0 y marca esas reglas como Skipped (omitidas) con una gravedad informativa, en lugar de intentar una evaluación que fallaría de forma silenciosa. El registro de resumen informa del número de reglas evaluadas y omitidas por separado, de modo que su registro siempre indica exactamente qué parte del conjunto de reglas se ejecutó. En Delphi 7, donde las vinculaciones de MSXML no están disponibles, la unidad compila un código de prueba (stub) que informa del motor como no disponible sin alterar el veredicto del contenedor; el motor completo requiere Delphi XE2 o posterior
El segundo límite es aún más importante. Un resultado verde de HPDFValidateEInvoice significa que la estructura del contenedor cumple con la norma y que no se ha activado ninguna regla de negocio evaluada; no se trata de una certificación de cumplimiento fiscal. Las reglas de extensión nacional, los requisitos específicos del receptor y las obligaciones legales en torno al contenido de las facturas se sitúan por encima del núcleo de EN 16931 y fuera del alcance de cualquier biblioteca. Utilice el validador como el filtro que evita que salgan facturas rotas estructuralmente de su bandeja de salida y que marca las incoherentes en su bandeja de entrada, ejecute un validador XPath 2.0 completo en su línea de compilación cuando la certificación lo exija, y deje que sus asesores fiscales resuelvan la cuestión del cumplimiento normativo. Las API de incrustación, detección y validación que se muestran aquí forman parte de HotPDF Component estándar para Delphi y C++Builder, que incluye un ejemplo completo de facturación electrónica que cubre los sete perfiles de ZUGFeRD y Factur-X