Firmar PDF con certificado
Incorpora el código AL de firma a tu extensión existente de Business Central. El procedimiento público FirmarPdf, del codeunit BC PDF Signing, recibe el PDF original y un alias de certificado; devuelve el PDF firmado en un Temp Blob. La clave privada permanece en el almacén estándar de BC.
La API resuelve los emisores automáticamente: el código AL existente puede seguir enviando solo el certificado público del firmante. UANATACA CA1 2021 y ROOT 2016 vienen incluidos en la imagen API desde 0.2.14; para esa CA basta con actualizar y redesplegar la API, sin subir emisores ni cambiar AL o variables de entorno. La resolución también admite otras entidades mediante AIA y catálogo público.
1. Preparar lo necesario
- Una API accesible por HTTPS desde BC cloud y su bearer de integración. Si ya existe, actualiza la API.
- Tu proyecto AL existente, con acceso a los símbolos del tenant y un rango de IDs disponible.
- Un certificado RSA de al menos 2048 bits, vigente y con clave privada, que importarás en BC.
Este recorrido añade las fuentes a tu propia extensión. Los archivos que tienes que pegar están completos en las dos páginas siguientes.
2. Copiar los objetos AL en tu proyecto
Crea una carpeta, por ejemplo src/PdfSigning/, dentro de tu proyecto AL. Crea estos archivos y pega en cada uno el bloque completo correspondiente:
| Archivo | Qué contiene | Código para copiar |
|---|---|---|
PdfSigning.Codeunit.al |
Codeunit 50100 BC PDF Signing: función pública, firma, HTTP y descarga | Codeunit completo |
SigningSecrets.Codeunit.al |
Codeunit 50101 BC PDF Signing Secrets: bearer cifrado | Secretos |
SigningSetup.Table.al |
Table 50100 BC PDF Signing Setup: URL y timeout | Tabla de configuración |
CertificateAlias.Table.al |
Table 50101 BC PDF Certificate Alias: autorización por usuario/empresa | Tabla de alias |
SigningSetup.Page.al |
Page 50100 BC PDF Signing Setup: configurar conexión y token | Página de configuración |
CertificateAliases.Page.al |
Page 50101 BC PDF Certificate Aliases: administrar alias | Página de alias |
SigningUser.PermissionSet.al |
Permission set 50101 BC PDF SIGN USER: permisos de firma | Permisos de usuario |
SigningAdmin.PermissionSet.al |
Permission set 50100 BC PDF SIGN ADMIN: permisos de configuración | Permisos de administrador |
SignPdf.Page.al |
Page 50102 BC PDF Sign Document: vista previa, posición de firma, firma y descarga | Página de prueba |
Copia los diez objetos para disponer del flujo completo, incluido PdfPreview.ControlAddIn.al, disponible en el objeto del visor. Incluye la carpeta business-central/controladdin/ y business-central/viewer/startup.js en las rutas relativas declaradas de tu extensión. Reconstruye el visor local con npm ci --prefix tablet y node business-central/viewer/build.mjs desde la raíz del repositorio; consulta las instrucciones de compilación y validación. El conjunto de permisos de usuario referencia la página de prueba, por lo que esa página también debe existir para compilar las fuentes tal como se muestran. Después puedes llamar desde tu propia página o proceso.
Adaptar y compilar tu extensión
- Conserva el contenido completo de cada objeto, incluidos
namespace,using, procedimientos locales, variables y atributos. Todos los objetos de esta integración deben vivir en tu misma extensión: el helper de secretos tieneAccess = Internal. - Ajusta los IDs 50100–50102 a tu rango si hay colisiones. Si cambias nombres o namespace, actualiza también las referencias entre objetos, páginas, permission sets y código llamador.
- Mantén la configuración de tu proyecto y descarga los símbolos del tenant objetivo. Estas fuentes parten de BC 24/runtime AL 13; comprueba su compatibilidad al compilar tu extensión.
- Compila y publica tu extensión en el sandbox siguiendo tu proceso habitual de desarrollo.
- En Extension Management → Configure → Extension Settings, activa Allow HttpClient Requests para tu extensión, que realiza las llamadas HTTP. Documentación de Microsoft.
Si necesitas llevar la lógica a un codeunit que ya tienes, copia los tres overloads de FirmarPdf, todos sus procedimientos locales, las variables globales y labels que haya, y las importaciones using necesarias. Conserva los objetos auxiliares y adapta las referencias al nuevo nombre; una firma de procedimiento o el wrapper del paso 6 por sí solos no implementan la firma.
3. Asignar permisos
Asigna BC PDF SIGN ADMIN a quien configura URL, token y alias. Asigna BC PDF SIGN USER a quien ejecuta la firma. El administrador que importa certificados necesita además los permisos estándar BC correspondientes.
Los permisos y el alias deben pertenecer al usuario que realmente ejecuta el código, incluida una Job Queue.
4. Configurar conexión, certificado y alias
Sigue Configurar certificados y alias. Al terminar debes tener:
- PDF Signing Setup con el origen HTTPS de la API, timeout y token guardado mediante Save Token.
- Un certificado en Isolated Certificate con clave privada y vigencia correcta.
- Un alias habilitado que relacione empresa y usuario con el Certificate Code interno; por ejemplo,
HOTEL-MADRID.
El bearer debe coincidir con BC_BEARER_TOKEN, sin el prefijo Bearer. Se guarda cifrado en Isolated Storage, con ámbito de empresa y dentro de tu extensión; configúralo en cada empresa que vaya a firmar. PFX/P12, contraseña y clave privada permanecen en BC.
5. Vista previa, selección del área de firma y descarga
- Abre Sign PDF con la empresa y usuario autorizados y elige Certificate Alias.
- Pulsa Upload PDF y selecciona un PDF original sin cifrar, de hasta 5 MB y sin firmas previas.
- Ve a una página y dibuja el rectángulo de firma. Muévelo arrastrándolo, cambia su tamaño con el control de la esquina inferior derecha o usa Place signature y los controles de teclado que muestra el visor. El zoom no cambia las coordenadas del PDF.
- Pulsa Sign and Download. BC confirma el área seleccionada, fija el PDF original, certificado, posición y
OperationId, y descargasigned.pdf. El recuadro visible muestra el titular real del certificado, la hora de preparación en UTC y el emisor. - Tras un fallo de conexión o servicio, vuelve a pulsar Sign and Download mientras la página permanezca abierta. New Operation permite cambiar la selección, el documento o el certificado. Al cerrar la página se pierde el documento y la posición que conserva en memoria; la recuperación duradera debe implementarse en el registro de negocio.
La extensión completa se ha compilado con Microsoft AL Compiler 17.0.34.45391 y símbolos BC 24.5/runtime 13. El comportamiento real del tenant y del iframe, así como la firma con el certificado de BC y la descarga, todavía requieren validación en sandbox. Las llamadas directas existentes a las sobrecargas originales de FirmarPdf siguen siendo compatibles y generan firmas invisibles si no se proporciona una posición.
6. Llamar desde tu proceso o página de negocio
Cuando ya tengas un PDF generado por un informe o guardado como adjunto, crea su InStream y llama a FirmarPdf. La función rellena SignedPdf o lanza un error; no devuelve un valor con exit.
| Parámetro | Qué pasar |
|---|---|
Pdf: InStream |
Stream binario del PDF original |
CertificateAlias: Code[100] |
Alias habilitado para el usuario y empresa actuales |
var SignedPdf: Codeunit "Temp Blob" |
Variable donde se escribe el PDF firmado |
Este codeunit de negocio completo convierte un Temp Blob de entrada a InStream. Puedes crear src/MiFirmaDocumentos.Codeunit.al o incorporar su procedimiento a tu codeunit existente. Ajusta el ID 70000 al rango de tu extensión y el using si renombraste el namespace de firma:
namespace MiEmpresa.Documentos;
using Baltoya.PdfSigning;
using System.Utilities;
codeunit 70000 "Mi Firma de Documentos"
{
procedure FirmarDocumento(PdfOriginal: Codeunit "Temp Blob"; Alias: Code[100]; var PdfFirmado: Codeunit "Temp Blob")
var
Signing: Codeunit "BC PDF Signing";
PdfIn: InStream;
begin
PdfOriginal.CreateInStream(PdfIn);
Signing.FirmarPdf(PdfIn, Alias, PdfFirmado);
end;
}
Concede ejecución al wrapper o integra el procedimiento en un codeunit al que el usuario ya tenga acceso, además de BC PDF SIGN USER. Después de la llamada, crea el InStream de PdfFirmado para archivarlo o descargarlo.
Para una acción de página completa utiliza SignPdf.Page.al. La página separa la subida y vista previa de la firma, y confirma la posición actual mediante el controladdin antes de invocar la sobrecarga de firma visible. En tu página de negocio sustituye la subida por la obtención del PDF generado; mantén la llamada en esa acción o en un codeunit de aplicación. Evita iniciar HTTP y firmas desde triggers de tabla que puedan ejecutarse durante modificaciones automáticas o transacciones ajenas al proceso de firma.
Recuperar una operación interrumpida
La llamada sencilla crea un GUID nuevo. Para recuperar después de un timeout o una respuesta perdida, persiste un GUID en tu registro de negocio antes de llamar y usa el overload real de cuatro parámetros:
procedure FirmarPdf(Pdf: InStream; CertificateAlias: Code[100]; OperationId: Guid; var SignedPdf: Codeunit "Temp Blob")
Ejemplo de llamada, dentro de un procedimiento que ya tiene estas variables declaradas:
PdfOriginal.CreateInStream(PdfIn);
Signing.FirmarPdf(PdfIn, Alias, OperationIdGuardado, PdfFirmado);
Para firmas visibles usa la sobrecarga de seis parámetros, con VisibleSignature: JsonObject y ExpectedThumbprint: Text antes de var SignedPdf. Guarda ambos valores junto con el PDF original, el alias y OperationId antes de realizar la primera petición. Consulta el contrato de coordenadas y reintentos.
En cada reintento crea de nuevo el InStream y reutiliza el mismo GUID, PDF, alias, empresa, usuario y certificado. Un GUID nuevo crea otra solicitud; cambiar documento o certificado con el mismo GUID produce conflicto. Mantén la asignación del certificado durante la recuperación.
Qué hace el codeunit
- Lee setup y resuelve el alias habilitado para usuario/empresa; comprueba certificado, clave privada y caducidad.
- Envía el PDF y el certificado público a
prepare, con una clave de idempotencia derivada de empresa, usuario yOperationId. - Comprueba algoritmo y huella, y decodifica
toBeSignedBase64. - Obtiene la clave mediante Certificate Management y firma los bytes DER exactos con Cryptography Management.SignData, SHA-256/RSA PKCS#1 v1.5.
- Envía la firma a
complete, consulta si sigue en curso y descarga el PDF alTemp Blob.
El codeunit contiene los reintentos temporales de HTTP; la recuperación del proceso de negocio requiere conservar OperationId. Después de actualizar la API, crea una operación nueva con el PDF original para emitir un resultado con los intermedios; los PDFs ya firmados no se reescriben. El perfil es PAdES B-B, sin TSA ni evidencia OCSP/CRL o B-LT. Una cadena completa no garantiza la confianza del lector. Consulta Protocolo PAdES y Errores y recuperación.