Artículo técnico

Automatización de comprobaciones preflight de PDF en Delphi con HotPDF

La ejecución mensual de estados de cuenta se veía bien en todas las pantallas de los desarrolladores, así que se envió. La imprenta devolvió el lote completo dos días después: imágenes RGB en un trabajo CMYK y ninguna declaración /Trapped. El costo no fue la reimpresión; fueron dos días de un plazo regulatorio. "Preflight" es el término de preprensa para detectar exactamente esta clase de problema antes de que los archivos salgan del edificio, y la pregunta de ingeniería interesante para un equipo Delphi es dónde pertenece esa comprobación cuando los PDFs los produce su propio código con una biblioteca como HotPDF y no herramientas de escritorio de un diseñador.

La prevención supera a la inspección cuando ustedes controlan el generador

El preflight clásico asume un archivo externo de calidad desconocida y lo inspecciona después. Cuando su propia aplicación genera el documento, esa arquitectura está invertida: cada propiedad que el inspector revisaría, fuentes embebidas, uso de espacios de color, output intents, metadatos, fue decidida por su código milisegundos antes. La falla de preflight más barata es la que se vuelve imposible durante la generación.

Conviene ser precisos sobre lo que HotPDF ofrece y no ofrece aquí. El componente incluye una ventana de reporte preflight como parte de su aplicación demo con GUI, pero no hay una API programática de preflight que puedan llamar desde un servicio o un script de build. Eso es menos vacío de lo que parece al principio, porque para documentos generados el patrón robusto tiene de todos modos dos mitades independientes: restringir el generador para que no pueda emitir estructuras no conformes, y luego verificar la salida con un validador que ustedes no mantienen. Una biblioteca que valida su propia salida califica su propia tarea; una herramienta externa les da evidencia que un cliente o auditor aceptará.

El lado de generación: conviertan el cumplimiento en configuración, no en punto de revisión

Las propiedades de estándares de HotPDF son la capa de prevención. Cuando PDFACompliance o PDFXCompliance se configuran antes de BeginDoc, el componente aplica las reglas correspondientes durante la generación: embebe fuentes, rastrea el uso de DeviceRGB y DeviceCMYK contra el output intent declarado, y rechaza funciones que el perfil prohíbe. Después de guardar, esas mismas propiedades registran qué se aplicó, que es exactamente lo que necesita el log de su canalización:

// After EndDoc: record the enforced profiles with the run metadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Escriban esos flags, el hash de datos de entrada y la versión de HotPDF en la misma línea de log. Cuando un validador más tarde discrepe con el generador, esa línea les dice qué revisión de plantilla y qué versión de biblioteca produjeron el archivo discutido, y una disciplina de log de una línea reemplaza una tarde de conjeturas forenses. La configuración completa que respalda estos flags, output intents, perfiles ICC y etiquetado, se cubre en nuestra guía de salida PDF/A, PDF/X y PDF/UA con HotPDF.

Clasifiquen los archivos entrantes antes de llegar a comprobaciones costosas

Muchas canalizaciones no son puramente generativas: los clientes cargan PDFs, los escáneres los depositan, los socios los envían por correo. Ejecutar una validación estructural completa sobre cada archivo entrante desperdicia tiempo de cola en entradas que ni siquiera se pueden abrir. La Direct File API de HotPDF lee la estructura del archivo sin cargar el árbol completo de objetos, lo que la convierte en una primera puerta barata:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // structurally unreadable: quarantine, do not validate
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Dos comportamientos de esta API moldean la lógica que la rodea. DAOpenFileReadOnly se mantiene en memoria plana sólo para entradas sin cifrar; pasen una contraseña y cae internamente en un parseo completo, así que enruten los archivos que se sabe que están cifrados por DecryptFile para producir primero una copia de trabajo en claro. Y DAGetPageCount sólo es válido sobre un handle de una apertura exitosa, así que mantengan estricta la comprobación del handle en lugar de asumir un valor positivo. Hay más patrones para esta API en el artículo de Direct File API para flujos de trabajo con PDF grandes.

El lado de verificación: veraPDF como paso de build

Para afirmaciones PDF/A y PDF/UA, veraPDF es el validador que vale la pena conectar a la canalización: corre sin interfaz, procesa lotes, emite XML o JSON legible por máquina y reporta cada falla por número de cláusula ISO, de modo que un hallazgo como una falla de regla contra ISO 19005-1 cláusula 6.2.2 se mapea directamente a una configuración conocida del generador. Invocarlo desde Delphi es control de proceso ordinario:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // bound the wait per file
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

El timeout no es decoración. Un archivo malformado puede llevar cualquier parser a territorio patológico, y una espera sin límite dentro de un worker de cola derriba toda la cola. Limiten la espera, traten un timeout como una falla con su propio código y pongan la entrada en cuarentena para inspección humana. Analicen el XML por identificadores de regla en lugar de raspar mensajes legibles por humanos; los IDs de regla son estables entre releases del validador, el texto de los mensajes no, y los códigos estables son lo que el personal de soporte puede buscar en tickets pasados.

El comportamiento de lote merece el mismo cuidado que la corrección de un solo archivo. Ejecuten el validador como un proceso por archivo en lugar de un proceso por lote, para que una entrada patológica cueste el timeout de ese archivo y no el lote; limiten los procesos de validador concurrentes al conteo de núcleos, ya que la generación de reportes XML consume CPU; e impongan un techo de tamaño de archivo en la entrada, porque un monstruo escaneado de 2 GB dominará la cola por bien portado que sea el parser. Nada de esto es lógica de preflight, pero es la diferencia entre una puerta que sobrevive al volumen de fin de mes y una que se deshabilita la primera vez que bloquea la canalización a las 2 a.m.

PDF/X es el hueco en esta historia: veraPDF no lo cubre, y la comprobación práctica sigue siendo Preflight de Acrobat con el perfil ISO 15930 correspondiente. Acrobat es interactivo, así que úsenlo para muestreo, primer artículo de una plantilla nueva, más una pequeña muestra aleatoria por lote, mientras la puerta automatizada cubre lo que sí puede automatizarse. Una comprobación manual por muestra que realmente ocurre supera a una automatización completa teórica que nadie terminó de construir.

Qué debe contener el reporte para que valga la pena conservarlo

Una puerta de preflight produce valor dos veces: una cuando bloquea un archivo malo, y otra meses después cuando alguien pregunta por qué se aceptó un archivo. El segundo uso es el que dicta el formato del reporte. Conserven, por cada archivo comprobado: el hash de entrada, los flags de cumplimiento y versión del generador desde la línea de log anterior, el nombre y versión del validador, el perfil contra el que se comprobó, el resultado aprobado o fallido, y la lista de IDs de reglas fallidas con números de página donde el validador los proporcione. Guarden el reporte junto al artefacto que describe, no en un sistema separado que se retirará antes que el archivo.

Las desviaciones aceptadas también necesitan documentación. Cuando un cliente insiste en enviar un archivo que la puerta rechaza, registren quién lo aprobó, por qué y hasta cuándo, y adjunten ese registro de excepción al reporte en lugar de debilitar la regla globalmente. Una excepción con responsable y fecha de vencimiento es una excepción gestionada; una comprobación comentada es un incidente futuro.

Las fallas merecen un paso más: copien el archivo fallido en una carpeta de regresión con nombre. Cada incidente de preflight que ayudamos a depurar terminó dependiendo de una entrada reproducible, y los equipos que conservaron esas entradas corrigieron regresiones en horas en lugar de redescubrirlas en producción.

FAQ

¿HotPDF puede validar programáticamente cualquier PDF de terceros?

No. El reporte preflight del producto es una función demo con GUI, no una API invocable. El patrón de automatización soportado es aplicación del lado de generación mediante las propiedades de cumplimiento, más un validador externo como veraPDF para el veredicto formal.

¿veraPDF alcanza para trabajos de impresión?

Cubre PDF/A y PDF/UA. Para originales de impresión PDF/X, ejecuten Acrobat Preflight con el perfil que especifique su impresor, y confirmen que el output intent coincida con la caracterización de prensa que espera.

¿Qué debe fallar el build: sólo errores o también advertencias?

Pongan la puerta sobre fallas de regla para el perfil cuyo cumplimiento declaran, y registren advertencias con monitoreo de tendencia. Promover cada advertencia a bloqueo entrena a la gente para saltarse la puerta, lo que es peor que no tener ninguna.

Referencia de producto

Las propiedades de cumplimiento y la Direct File API usadas en esta canalización pertenecen a HotPDF Component para Delphi y C++Builder; su documentación describe por completo cada llamada mostrada aquí.