Un equipo de reclamaciones publica un formulario de consentimiento generado en Delphi que se ve impecable en Acrobat: cada casilla se dibuja, cada leyenda queda alineada y el diseño coincide con el original en papel. Tres semanas después, el servicio de recepción empieza a rechazar un tercio de todos los envíos. El backend espera que la casilla de consentimiento publique el valor Y; el formulario exporta Yes, porque quien lo creó supuso que la leyenda y el valor exportado eran lo mismo. Nada en la página renderizada muestra la diferencia. Esa es la propiedad que define el desarrollo con AcroForm: el widget visible y el contrato de datos que hay debajo son estructuras separadas, y solo una de ellas se verifica cuando alguien abre el archivo y lo mira.
HotPDF es un componente VCL nativo que escribe diccionarios de campos AcroForm directamente desde código Delphi y C++Builder, de modo que ambas estructuras quedan bajo control explícito del programa, y ambas pueden fallar de manera independiente. Lo siguiente cubre la creación de campos, las acciones de botones y JavaScript a nivel de campo, con énfasis en los puntos donde la vista previa de la página y los datos del formulario se apartan sin hacer ruido.
Los nombres de campos son claves de enrutamiento, no leyendas
Cada campo AcroForm lleva un nombre totalmente calificado, e ISO 32000-1 §12.7.3 define ese nombre, nunca la leyenda visible, como la clave con la que el valor del campo viaja en envíos FDF, XFDF y formularios HTTP. De ahí se desprenden dos consecuencias que sorprenden con frecuencia a quienes vienen del diseño de formularios VCL, donde el nombre de un control es poco más que un identificador en el código.
Primero, dos campos con el mismo nombre totalmente calificado no son dos campos. El modelo PDF los trata como dos anotaciones widget de un solo campo, que comparten un único valor: escriban en uno y el otro se actualiza de inmediato. Repetir el nombre de un cliente en cada página de un contrato es un uso legítimo de este comportamiento. Obtenerlo por accidente, porque un ciclo de generación reutilizó 'Field1' en tres páginas, es un error que ninguna inspección visual detecta; cada página sigue mostrando su propio widget, y el reflejo solo aparece cuando un usuario empieza a escribir.
Segundo, los nombres con puntos como applicant.email construyen una jerarquía: el nodo padre applicant agrupa a sus hijos para operaciones de restablecimiento y envío que apuntan solo a una parte del formulario. Nombrar los campos jerárquicamente desde el inicio no cuesta nada y rinde la primera vez que el sistema receptor pide "solo el bloque del solicitante".
Los botones de opción agregan una tercera regla: los botones que deben alternarse como un solo grupo deben compartir un nombre de grupo. En HotPDF, las llamadas a AddRadioButton que usan el mismo nombre de grupo unen sus widgets a un campo padre, y el valor exportado de cada botón, 'basic' o 'full', identifica la opción seleccionada. Si dan a cada botón un nombre único, obtendrán interruptores independientes de activar/desactivar en lugar de un grupo mutuamente excluyente.
Crear el conjunto de campos página por página
HotPDF coloca los campos mediante métodos de THPDFPage, así que cada campo pertenece al objeto de página que lo creó. Aquí domina una trampa de secuenciación: AddPage reasigna de inmediato CurrentPage a la nueva página, por lo que cualquier llamada de campo emitida después cae en esa nueva página aunque lógicamente pertenezca a la anterior. Construyan cada página por completo, contenido dibujado y campos juntos, antes de llamar a AddPage.
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Page 1: applicant block
Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
Pdf.CurrentPage.AddComboBox('plan', 'Standard',
['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));
Pdf.AddPage; // CurrentPage now points at page 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Las coordenadas siguen la convención de PDF: el origen está en la esquina inferior izquierda de la página, la misma convención que usa TextOut para el texto dibujado. Por lo tanto, Rect(50, 100, 200, 120) queda cerca de la parte inferior de una página Letter, no de la superior. Los equipos que migran tablas de diseño desde VCL, donde Y crece hacia abajo desde la parte superior, producen de forma predecible un primer borrador con todos los campos reflejados verticalmente. Conviertan las coordenadas en un helper compartido, no en cada punto de llamada, para que la corrección llegue a todos lados a la vez.
Conectar botones con acciones URI, JavaScript y envío
Un botón pulsador no hace nada hasta que se le adjunta una acción. HotPDF expone los tipos de acción de ISO 32000-1 §12.6.4 mediante la enumeración THPDFButtonAction, baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed, además de dos métodos que crean el botón y enlazan su acción en un solo paso.
// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
Las banderas de envío merecen más atención de diseño de la que suelen recibir. AddPushButtonWithSubmitAction toma un conjunto THPDFSubmitFormFlags, y un conjunto vacío produce una publicación url-encoded simple, el formato que muchos endpoints de ejemplo aceptan y muchos endpoints de producción no. Agregar sffXFDF cambia la carga útil a XFDF; sffGetMethod cambia el verbo HTTP; sffIncludeNoValueFields hace que los campos vacíos aparezcan en la carga en lugar de descartarse en silencio, algo importante cuando el consumidor distingue entre "ausente" y "en blanco". El conjunto de banderas forma parte efectiva del contrato de interfaz con el endpoint receptor, así que elíjanlo junto con el equipo que analiza el envío, no después del primer lote rechazado.
JavaScript a nivel de campo: pulsación, formato y validación
Más allá de los clics en botones, HotPDF adjunta JavaScript a los eventos por campo que los visores con scripting disparan durante la captura de datos. Los tres disparadores corresponden a momentos distintos del ciclo de entrada: las acciones de pulsación se ejecutan cuando llegan caracteres, las acciones de formato reescriben el valor mostrado después de confirmar un cambio y las acciones de validación aceptan o rechazan el valor confirmado.
// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Establecer event.rc = false dentro de un script de pulsación o validación hace que el visor rechace la entrada. La limitación que conviene mantener a la vista: este código solo se ejecuta en visores que incluyen un motor JavaScript. Acrobat y algunos productos de escritorio lo ejecutan; la mayoría de lectores móviles, renderizadores embebidos en navegador y tuberías de impresión lo ignoran por completo. Los scripts de campo elevan la calidad de los datos para los usuarios que los reciben; no son un límite de seguridad, y cada valor enviado sigue necesitando validación en el servidor cuando llega.
Defectos que pasan la revisión visual
Algunos defectos de formularios son estructuralmente invisibles para una revisión de abrir y mirar. Estos cuatro explican la mayoría de las escalaciones de AcroForm que llegan a soporte, y cada uno puede detectarse mecánicamente antes de publicar:
- Deriva del valor exportado. Una casilla creada como
AddCheckBox('consent', 'Yes', ...)publicaYes; un consumidor que compara contraYrechaza todos los envíos aunque la página se vea perfecta. Exporten el formulario lleno como XFDF desde Acrobat y comparen los valores contra el esquema del consumidor. - Reflejo accidental de valores. Los nombres totalmente calificados duplicados fusionan campos en uno. El síntoma aparece durante la captura de datos, nunca durante la generación, así que prueben escribiendo en el formulario, no solo renderizándolo.
- Valores de combo fuera de la lista de opciones. Si el valor actual pasado a
AddComboBoxno está entre las opciones, los visores discrepan entre mostrarlo, dejarlo en blanco o marcarlo. Mantengan el valor predeterminado dentro de la lista. - Campos aún editables después de cerrar el flujo. HotPDF no tiene una llamada de aplanado de apariencias para campos AcroForm; la forma soportada de congelar un formulario completado es crear campos con la bandera
ffReadOnly, que mantiene el valor renderizado mediante el propio appearance stream del campo mientras bloquea las ediciones. Un campo de solo lectura sigue siendo un objeto de formulario activo, que las herramientas posteriores de ensamblado y firma manejan de forma predecible.
Un comportamiento del lado del visor merece una nota de regresión aunque ningún cambio de código lo corrija: las implementaciones empresariales de Acrobat pueden deshabilitar JavaScript o restringir objetivos de envío por política, así que una acción que funcionó durante todo el desarrollo puede quedar inerte en el escritorio de un cliente. Den a los envíos una alternativa visible, como mínimo una instrucción impresa, para el caso en que el botón no haga nada.
Cómo se conecta el trabajo de formularios con el resto del documento
Un campo de firma es en sí mismo un tipo de campo AcroForm, por lo que un formulario que luego será certificado o contrafirmado debe reservar ese campo durante la generación en lugar de parchearlo después; la mecánica a nivel de bytes se cubre en el artículo complementario sobre firmas digitales y firma PAdES con HotPDF. Si sus entradas llegan como paquetes XFA en lugar de AcroForm nativo, aplanar XFA en campos AcroForm es un flujo separado con su propio modelo de pérdida, ya que las dos tecnologías de formulario son mutuamente excluyentes dentro de un solo archivo.
Los métodos de campos, acciones y disparadores que se muestran aquí forman parte de la API estándar de HotPDF Component para Delphi y C++Builder; la página del producto enlaza la referencia completa, incluidas las sobrecargas de banderas de campo y la enumeración completa de banderas de envío.