Una factura Factur-X o ZUGFeRD consta de dos documentos con un solo nombre de archivo. El documento externo es un contenedor PDF/A-3 que un lector de archivo tiene que aceptar durante los próximos diez años. El documento interno es una factura XML que el sistema contable de un comprador debe analizar frente a EN 16931. El error que envía facturas rotas a producción es creer que hacer bien lo primero otorga lo segundo de forma gratuita. No es así. Un archivo puede ser un PDF/A-3 impecable y aún así llevar un XML que ninguna autoridad fiscal aceptará, y puede llevar un XML EN 16931 de libro de texto dentro de un contenedor que no pasa la validación de archivo. Las dos capas son validadas por dos herramientas diferentes que no saben nada la una de la otra, y un flujo de trabajo real tiene que satisfacer a ambas
Dos validadores, dos preguntas diferentes
veraPDF es la implementación de referencia para PDF/A. Apúntelo a una factura y responderá a una pregunta: ¿es este un archivo PDF/A-3 conforme? Comprueba las cosas que le importan a la ISO 19005-3. ¿Está incrustada cada fuente? ¿Hay un OutputIntent? ¿Los metadatos XMP declaran la parte y el nivel de conformidad correctos? Para una factura electrónica, también comprueba la fontanería del archivo asociado que requiere PDF/A-3, porque el XML viaja como un archivo incrustado con un /AFRelationship y una entrada en el arreglo /AF del catálogo del documento. veraPDF no dice nada acerca de si el total de la factura cuadra, porque eso no está dentro de sus competencias
Mustang es el validador de código abierto del Mustangproject. Hace la pregunta ortogonal: ¿es el XML incrustado una factura válida? Ejecuta el XML contra el esquema para el perfil declarado y luego aplica las reglas comerciales de EN 16931 y los conjuntos de reglas específicos del país en capas sobre él, entre ellos el CIUS de XRechnung. Comprueba que un identificador de IVA del vendedor esté presente cuando los totales exigen uno, que los montos de asignación y cargo se concilien con el total del documento, que el URN del perfil en el XML coincida con lo que el archivo dice ser. A Mustang no le importa si el PDF circundante incrusta sus fuentes, porque ese es el trabajo de veraPDF
Ninguna herramienta es un superconjunto de la otra. veraPDF pasa un contenedor estructuralmente perfecto alrededor de un XML sin sentido. Mustang pasa un XML perfecto envuelto en un contenedor al que le falta un OutputIntent. Cada una detecta exactamente la clase de defecto a la que la otra es ciega, lo cual es la razón por la que un entorno de validación serio ejecuta ambas y trata un archivo como apto para su envío solo cuando ambas están de acuerdo
La matriz de validación
Para probar que la biblioteca produce archivos que sobreviven a ambas puertas, el entorno de pruebas construye una matriz. Seis perfiles de factura cubren el rango que un flujo de trabajo europeo encuentra en la práctica: Factur-X EN 16931, Factur-X BASIC, la variante Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT y ZUGFeRD 2.0 BASIC. Cada perfil se genera frente a dos niveles de subconformidad PDF/A, 3b y 3u, porque los requisitos del nivel B y el nivel U difieren en el mapeo Unicode y un archivo que pasa uno puede reprobar el otro. Seis perfiles por dos niveles son doce archivos, todos ellos construidos sin interfaz gráfica (headless) por la misma ruta de código que se incluye en el ejemplo GUI, por lo que los artefactos bajo prueba no están ajustados a mano para la prueba
El generador escribe los doce y un script envía cada uno a ambos validadores. En la primera ejecución completa, veraPDF aprobó los doce. La fontanería del contenedor era correcta en todos los ámbitos: archivos asociados registrados, conformidad XMP declarada, output intents en su lugar. Mustang aprobó ocho. Cuatro facturas eran archivos PDF/A-3 estructuralmente válidos que llevaban XML que el validador de reglas comerciales rechazó, que es precisamente la división que el enfoque de dos herramientas existe para hacer aflorar. Si el entorno de pruebas hubiera confiado solo en veraPDF, esos cuatro habrían parecido terminados
Las dos correcciones que cerraron la brecha
Las cuatro fallas de Mustang provinieron de dos causas distintas, y la corrección para cada una es un detalle que vale la pena conocer antes de que usted mismo genere estos perfiles
El primero fue el perfil Factur-X EXTENDED France B2B. El generador original pasó una etiqueta interna como nivel de conformidad y un URN interno como directriz, y Mustang rechazó el archivo con un error de valor de conformidad no válido seguido de un error de tipo de perfil no compatible. La razón es que el campo XMP fx:ConformanceLevel no es un espacio de texto libre para la nomenclatura de su propio perfil. Factur-X define exactamente cinco valores estándar para él: MINIMUM, BASIC WL, BASIC, EN 16931 y EXTENDED. Una factura B2B específica de Francia sigue siendo un documento de perfil EXTENDED en lo que respecta a los metadatos XMP. El carácter francés de la factura no se expresa inventando un sexto valor de conformidad. Se expresa mediante el código de país, FR, y por el identificador de la directriz dentro del XML, que tiene que llevar el prefijo urn:cen.eu:en16931:2017#conformant# que marca un CIUS conforme a EN 16931. Pasar el valor EXTENDED estándar con FR como código de país y el URN de directriz correcto hizo que el archivo fuera conforme
En la API de la biblioteca, esa es una llamada a AddFacturXAssociatedFileFromString con la conformidad, el país y la directriz alineados. El argumento del nivel de conformidad lleva el token estándar, el argumento del código de país lleva FR, y el URN de la directriz se encuentra en los bytes XML que usted le pase
var
FileID: Integer;
begin
PDF.SetPDFAMode(5); // PDF/A-3b
PDF.NewDocument;
// ... draw the human-readable invoice page ...
// ExtendedXML carries an EN 16931 guideline URN of the form
// urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
FileID := PDF.AddFacturXAssociatedFileFromString(
ExtendedXML,
'EXTENDED', // standard fx:ConformanceLevel, not an internal label
'factur-x.xml',
'Factur-X EXTENDED invoice',
'Alternative', // /AFRelationship
'1.0',
'FR'); // France B2B marked by country code, not by conformance
if FileID = 0 then
raise Exception.Create('Factur-X attachment rejected');
PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;
La segunda causa fue el perfil ZUGFeRD 1.0 COMFORT, y no tenía nada que ver con los metadatos. ZUGFeRD 1.0 se valida frente al XSD :1p0, que es más estricto con la cardinalidad de lo que sugieren los resúmenes en prosa. El XSD requiere que la suma de liquidación del encabezado, ram:SpecifiedTradeSettlementMonetarySummation, contenga ram:ChargeTotalAmount y ram:AllowanceTotalAmount exactamente una vez cada uno. El XML generado omitía ambos, por lo que Mustang reportó que los elementos deben aparecer exactamente una vez. Estos no son opcionales cuando el esquema dice que minOccurs es uno. La emisión de ambos en orden de secuencia XSD, inmediatamente después de ram:LineTotalAmount, con un valor de 0.00 cuando no hay cargos ni asignaciones, satisfizo el esquema. Un cero es un elemento presente; un elemento ausente es una violación del esquema. Con esas dos correcciones implementadas, la matriz pasó a doce de doce en Mustang, mientras se mantenía doce de doce en veraPDF
Los campos de XRechnung que cambian de no válido a válido
XRechnung merece su propia nota porque su CIUS alemán agrega reglas comerciales que están ausentes del conjunto básico EN 16931, y fallan en formas en que parece que no hay nada malo con el documento a simple vista. Dos de ellas se refieren a las direcciones electrónicas. BT-34 es la dirección electrónica del vendedor y BT-49 es la dirección electrónica del comprador, los puntos finales de enrutamiento que utiliza un portal del sector público alemán para entregar y acusar recibo de la factura. El modelo base EN 16931 los trata como opcionales. XRechnung no lo hace. Omita cualquiera de los dos y la factura estará bien formada, será válida para el esquema y será rechazada
La tercera es la regla BR-DE-6, que requiere que el número de teléfono de contacto del vendedor esté presente. Es el tipo de campo que un desarrollador elimina porque se siente como una presentación en lugar de datos, y su ausencia produce una falla de validación que apunta al grupo de contacto del vendedor en lugar de a cualquier cosa obviamente faltante. Proporcionar el BT-34, el BT-49 y el número de teléfono del vendedor es lo que hace que un archivo XRechnung pase de ser no válido a válido bajo Mustang, y nada de eso cambia lo que veraPDF ve, porque los tres viven en el XML
Conectar la salida de la biblioteca a un validador
El punto arquitectónico detrás del entorno de pruebas se generaliza a cualquier sistema comercial. La biblioteca PDF escribe un contenedor conforme e incrusta el XML. No intenta, ni debería intentar, ser la autoridad de reglas comerciales EN 16931. ValidateFacturXInvoice en la biblioteca verifica la consistencia del contenedor, que el arreglo /AF del catálogo, el árbol de nombres de archivos incrustados, el XMP DocumentFileName, el perfil, la directriz y el /AFRelationship concuerden, pero no valida códigos de impuestos ni concilia montos. La división correcta del trabajo es que el sistema comercial extraiga el XML y se lo entregue a un validador de facturas dedicado, exactamente de la forma en que el entorno de pruebas se lo entrega a Mustang
Volver a leer el archivo le indica qué fue lo que se escribió en realidad. DetectFacturXInvoice reporta si se reconoció una factura, y GetFacturXInvoiceInfo lee los campos de metadatos por etiqueta: la etiqueta 1 es el nombre del archivo incrustado, la etiqueta 2 el XMP DocumentFileName, la etiqueta 5 el nivel de conformidad, la etiqueta 6 el identificador de la directriz y la etiqueta 7 el /AFRelationship. Confirmar que el nivel de conformidad que usted vuelve a leer es el token estándar y no una etiqueta interna es la forma más barata de detectar el error de EXTENDED antes de que un archivo salga de su compilación
function ExtractAndInspect(const PdfPath: string): AnsiString;
var
Profile, Guideline: WideString;
begin
Result := '';
PDF.LoadFromFile(PdfPath);
if PDF.DetectFacturXInvoice = 1 then
begin
Profile := PDF.GetFacturXInvoiceInfo(5); // fx:ConformanceLevel
Guideline := PDF.GetFacturXInvoiceInfo(6); // XML guideline ID
Writeln('Profile: ', Profile);
Writeln('Guideline: ', Guideline);
// Hand the raw XML to a dedicated EN 16931 / Mustang validator.
Result := PDF.ExtractFacturXXMLToString;
end;
end;
ExtractFacturXXMLToString retorna los bytes XML en bruto como un AnsiString, listos para escribir en un archivo o transmitir a un proceso validador. En el entorno de pruebas ese objetivo es Mustang, invocado a través de su jar de línea de comandos, con veraPDF ejecutándose en la misma pasada sobre el mismo archivo. El cableado es pequeño: un generador de consola, EInvoiceValidation.dpr, escribe los doce archivos utilizando el modelo de factura compartido del ejemplo, y un script, run-validation.ps1, maneja ambos validadores sobre el directorio de salida e imprime una tabla de aprobados y reprobados. La misma forma de dos pasos, generar con la biblioteca y verificar con validadores externos, es lo que un trabajo de integración continua debería ejecutar en cada cambio en la generación de facturas, porque la única forma de saber que un archivo satisface ambas capas es preguntarles a ambas herramientas
Si su flujo de trabajo también tiene que certificar el contenedor antes de firmar, el lado del preflight de este trabajo está cubierto en nuestro recorrido por el preflight PDF/A y PDF/UA en Delphi, y el flujo más amplio de certificar y luego firmar se describe en el entorno de trabajo de cumplimiento y firma. Ambos se basan en la misma ruta de generación que se envía como parte de la Biblioteca PDF de Delphi para Delphi y C++Builder, junto con las API de PDF/A, archivos asociados y metadatos que se utilizan aquí