El descubrimiento llegó seis años después del despliegue: treinta y ocho mil facturas archivadas declaraban PDF/A-1b en sus metadatos XMP, y una muestra corrida por veraPDF falló casi por completo — RGB dependiente del dispositivo sin OutputIntent, dos fuentes nunca incrustadas, banderas de anotación que ISO 19005-1 prohíbe. La aplicación generadora había escrito el identificador de conformidad sin validar nunca contra el estándar, y todos los sistemas posteriores confiaron en la etiqueta. La conformidad autodeclarada vale exactamente lo que cuesta escribirla. La alternativa duradera es preflight en el momento en que un archivo entra al archivo histórico, y losLab PDF Library (PDFlibPas) lo vuelve práctico para sistemas Delphi y C++Builder al integrar validadores PDF/A y PDF/UA en la propia biblioteca, sin un servicio externo en el circuito.
Dos estándares que hacen fallar archivos por razones opuestas
ISO 19005 (PDF/A) es un contrato de reproducción: un archivo conforme debe renderizarse de forma idéntica dentro de décadas, en software que nunca vio el sistema de origen. Sus reglas atacan por lo tanto dependencias externas — toda fuente incrustada, color anclado a un OutputIntent ICC incrustado o expresado en espacios independientes del dispositivo, sin cifrado en PDF/A-1, sin JavaScript, metadatos XMP consistentes con el diccionario de información del documento.
ISO 14289 (PDF/UA) es un contrato de semántica: la tecnología de asistencia debe poder recorrer el documento con sentido. Sus reglas viven en una capa completamente distinta — un árbol de estructura completo, texto alternativo en figuras, un título de documento configurado para visualización, niveles de encabezado que nunca saltan, relaciones de encabezados de tabla que se sostienen fuera de pantalla.
Ambos son ortogonales, y los archivos peligrosos están en medio. Un documento perfecto para archivo puede ser ilegible para un lector de pantalla; un documento perfectamente etiquetado puede referenciar una fuente de escritorio que no existirá en diez años. Las canalizaciones que necesitan ambos — la publicación del sector público es el caso canónico — deben ejecutar ambas comprobaciones y rutear los hallazgos a distintos responsables, porque las fuentes no incrustadas son un defecto del código generador, mientras que el texto alternativo faltante pertenece a quien mantiene las plantillas.
Elegir la parte importa tanto como el veredicto. PDF/A-1, congelado sobre PDF 1.4, rechaza transparencia y JPEG2000 — características que la salida moderna de reportes usa libremente. PDF/A-2 (ISO 19005-2, construido sobre ISO 32000-1) acepta ambas y es el valor predeterminado sensato para archivos nuevos. PDF/A-3 además permite archivos incrustados de cualquier tipo, algo de lo que dependen formatos regulados de factura electrónica. Un equipo que todavía estandariza en PDF/A-1b en 2026 suele estar heredando un requisito escrito hace quince años, y la corrección más barata a menudo es renegociar la parte objetivo en lugar de arrancar la transparencia de cada gráfico.
Hallazgos estructurados en tiempo de recepción
El punto de entrada de la API plana es CheckFileCompliance, con el selector de prueba 1 para PDF/A y 2 para PDF/UA. Devuelve un handle de lista de cadenas cuyos elementos son hallazgos individuales, lo que le da la forma adecuada para una puerta automatizada:
function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0); // 1 = PDF/A
if ListId = 0 then
begin
// 0 means "no findings" OR "file unreadable" -- disambiguate before passing
Result := Pdf.LastErrorCode = 0;
Exit;
end;
for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
Pdf.ReleaseStringList(ListId);
Result := False;
end;
Dos detalles de implementación deciden si esto funciona sin atención. Primero, CheckFileCompliance devuelve 0 tanto cuando el archivo cumple por completo como cuando el archivo no pudo abrirse en absoluto — internamente, una lista de resultados vacía produce 0 en ambos casos — así que una puerta que lea 0 como aprobación dejará entrar cargas corruptas directamente al archivo histórico. Desambigüen con LastErrorCode, como arriba. Segundo, el verificador corre sobre el lector de streaming de la biblioteca en lugar del modelo de documento completo: abre el archivo directamente con lectura compartida y nunca necesita LoadFromFile, por eso maneja archivos de varios gigabytes sin construir árboles de objetos — pero fallará mientras otro proceso aún tenga el archivo abierto para escritura, precisamente el estado de una carga en progreso. Apliquen la puerta después de que termine la transferencia, no durante.
El rendimiento se desprende del mismo diseño. Como cada comprobación abre su entrada en solo lectura y comparte el archivo para lectura, una auditoría de corpus se paraleliza naturalmente entre hilos o procesos worker, con una instancia TPDFlib por worker. Los handles de lista de cadenas son el recurso con el que hay que ser disciplinado: cada handle no cero de CheckFileCompliance permanece asignado hasta ReleaseStringList, y una puerta de larga duración que los fuga se degrada lentamente en lugar de fallar fuerte.
Informes para humanos, diffs para puertas de build
Las listas de hallazgos tienen la forma correcta para una puerta y la incorrecta para un correo al equipo de plantillas. CreatePreflightReport representa el mismo análisis como un informe legible, CreatePreflightReportEx agrega un selector de formato de informe, y SavePreflightReport escribe el informe en disco para enviarlo dentro del paquete documental — un requisito contractual en muchos acuerdos de entrega archivística.
El miembro subestimado de la familia es ComparePreflightReports. Los resultados de cumplimiento son una superficie de regresión como cualquier otra: un ajuste de plantilla, una nueva fuente corporativa o una actualización de biblioteca pueden introducir hallazgos ausentes en el release anterior. Mantengan informes dorados de un corpus de documentos representativos bajo control de versiones, regenérenlos después de cada cambio y dejen que ComparePreflightReports calcule el delta. Un diff vacío se vuelve artefacto de release; un hallazgo sorpresa falla el build en lugar de fallar la auditoría.
Generar salida que pase en la primera ejecución
Preflight gana su lugar en archivos entrantes; para documentos que produce su propio código, reparar hallazgos después de generar va al revés. PDFlibPas incluye un modo de generación para cada estándar, y ambos se combinan:
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: required for PDF/UA
// ... draw tagged content here ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// the preflight that counts runs on the saved file:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;
La trampa se esconde en el guardado. Varias reparaciones de conformidad — forzar la bandera de impresión en anotaciones, escribir el AFRelationship predeterminado para archivos incrustados PDF/A-3, normalizar el orden de tabulación y las descripciones de campos de formulario para PDF/UA — se aplican mientras el documento se serializa, no cuando se habilita el modo. El documento en memoria no es byte a byte idéntico a lo que llega a disco, así que el único veredicto preflight que cuenta es el calculado desde el archivo guardado. Validen statement.pdf; nunca infieran cumplimiento desde el objeto de documento que aún está en memoria.
Los escenarios de facturación que incrustan XML legible por máquina junto al documento visual — el patrón ZUGFeRD y Factur-X, construido sobre PDF/A-3 — deberían establecer explícitamente la relación del adjunto mediante SetPDFA3DefaultAFRelationship, porque ISO 19005-3 exige que todo archivo incrustado declare su rol relativo al documento.
Árbitros independientes: veraPDF y Acrobat
Nunca dejen que un productor califique su propia tarea. Los verificadores de PDFlibPas entregan veredictos rápidos, estructurados y dentro del proceso; la puerta de release para lotes archivísticos aun así debería incluir un validador independiente. veraPDF es la implementación de referencia mantenida por la comunidad para validación PDF/A y la herramienta que la mayoría de archivos históricos nombra en sus criterios de aceptación; los perfiles preflight de Acrobat aportan una tercera opinión útil cuando dos herramientas no coinciden. Registren el nombre y la versión del validador junto a cada informe almacenado — afirmar que pasó veraPDF dice poco sin saber cuál veraPDF.
Cuando los validadores discrepan, y en los bordes de los estándares ocasionalmente lo harán, reduzcan el archivo a una muestra mínima y decidan contra el texto del estándar, no contra una herramienta u otra. El ejercicio toma una hora y suele revelar un bug de herramienta que vale reportar o una cláusula mal leída que conviene documentar en las notas de cumplimiento del equipo.
Las entradas cifradas merecen una regla especial. Ambos verificadores aceptan un argumento de contraseña, pero para PDF/A-1 el diccionario de cifrado en sí ya es una violación de conformidad — ISO 19005-1 lo prohíbe directamente — así que una presentación cifrada puede rechazarse antes de cualquier análisis más profundo. Auditar lo que realmente permite un diccionario de cifrado es un ejercicio aparte, cubierto en auditoría de cifrado y permisos PDF.
Los hallazgos PDF/UA casi siempre se remontan a cómo se creó el árbol de estructura; las técnicas de etiquetado se cubren en creación de árboles de estructura Tagged PDF en Delphi. Los archivos que además requieren firmas digitales deberían combinar esta puerta con el flujo de firma y validación PAdES. La referencia completa de la API preflight está en la página del producto losLab PDF Library for Delphi.