# Firma con posiciones personalizadas (CUSTOM) Fuente: /document-management/custom-signature-position/ Además de las posiciones automáticas (`UPPER_RIGHT`, `UPPER_LEFT`, `LOWER_RIGHT`, `LOWER_LEFT`), Verificamex permite que **cada firmante coloque su firma en coordenadas exactas** del PDF mediante el valor `CUSTOM` de `sign_position`. Esto es lo que usa nuestra propia app web en su editor visual de firma ("Firma Beta"), donde el usuario arrastra el recuadro de firma sobre el documento. > **Requisito previo** > > El posicionamiento `CUSTOM` solo tiene efecto cuando `sign_mode` es `PDF_SIGNATURE`. Con `CLASSIC` o `CLASSIC_BATCH` la firma se agrega en una hoja de firmas independiente al final del documento, y el campo `positions` es ignorado. *** ## Cómo activarlo 1. Al [crear el documento](/document-management/create-document), define `sign_position: "CUSTOM"` a nivel del documento. 2. Por cada firmante en el arreglo `signatures`, agrega el campo `positions` con la(s) coordenada(s) donde deseas que aparezca su firma. 3. Sube el PDF normalmente en el paso de [subir documento](/document-management/upload-document). *** ## El campo `positions` `positions` es un arreglo de objetos, uno por cada lugar del documento donde ese firmante estampará su firma. | Campo | Tipo | Requerido | Default | Descripción | | :- | :- | :- | :- | :- | | `pos_x` | `float` | No | `100` | Distancia desde el borde **izquierdo** de la página, en puntos PDF. | | `pos_y` | `float` | No | `100` | Distancia desde el borde **superior** de la página, en puntos PDF. | | `width` | `float` | No | `140` | Ancho del recuadro de firma, en puntos PDF. | | `height` | `float` | No | `80` | Alto del recuadro de firma, en puntos PDF. | | `page` | `integer` | No | `1` | Página donde se coloca la firma. **Base 1** (la primera página es `1`, no `0`). | | `rotation` | `integer` | No | `0` | Rotación del recuadro de firma en grados. Valores soportados: `0`, `90`, `180`, `270`. | > **\`positions\` viaja como texto, no como arreglo** > > En el endpoint de creación (`POST /v1/documents`, cuerpo JSON), `positions` debe enviarse como un **string con el JSON ya serializado**, no como un arreglo nativo dentro del JSON. Es el error de integración más común en este flujo. > > **❌ Incorrecto** > > ```json > { > "name": "Juan Perez", > "email": "juan@correo.com", > "positions": [ > { "pos_x": 120, "pos_y": 650, "width": 180, "height": 70, "page": 1, "rotation": 0 } > ] > } > ``` > > **✅ Correcto** > > ```json > { > "name": "Juan Perez", > "email": "juan@correo.com", > "positions": "[{\"pos_x\":120,\"pos_y\":650,\"width\":180,\"height\":70,\"page\":1,\"rotation\":0}]" > } > ``` *** ## Sistema de coordenadas - **Origen:** esquina **superior izquierda** de la página (igual que un canvas o visor tipo PDF.js), no la esquina inferior izquierda que usa el estándar PDF internamente. La conversión la hace el backend por ti. - **Unidades:** puntos PDF (1 punto = 1/72 pulgada) — el mismo tamaño real de la página, **no píxeles de pantalla ni porcentajes**. Por ejemplo, una página carta mide aproximadamente `612 x 792` puntos. - El recuadro de firma se dibuja desde `(pos_x, pos_y)` extendiéndose `width` puntos a la derecha y `height` puntos hacia abajo. > **Cómo obtener el tamaño real de la página** > > La petición de creación del documento (paso 2) se envía **antes** de subir el PDF (paso 3), por lo que el backend todavía no conoce el tamaño de tus páginas al momento de calcular `positions`. Antes de armar el payload, inspecciona tu propio PDF del lado del cliente (con cualquier librería de manejo de PDF: `pdf-lib`, `PyPDF2`/`pypdf`, `PDFBox`, `iText`, etc.) para obtener: > > - El **tamaño de página en puntos** de cada página (puede variar si el PDF mezcla tamaños). > - El **número total de páginas**, para validar que el `page` que envías existe. *** ## Firmas en múltiples posiciones Un mismo firmante puede estampar su firma en más de un lugar del documento (por ejemplo, en varias páginas de un contrato) enviando más de un objeto en `positions`: ```json "positions": "[{\"pos_x\":100,\"pos_y\":700,\"width\":140,\"height\":80,\"page\":1,\"rotation\":0},{\"pos_x\":100,\"pos_y\":700,\"width\":140,\"height\":80,\"page\":3,\"rotation\":0}]" ``` > **Nota** > > La **primera posición del arreglo** es la que lleva el sello de tiempo (TSA) cuando el documento tiene más de un firmante — es la firma criptográfica principal. Las posiciones adicionales son estampas incrementales sobre el mismo PDF ya firmado. Para efectos prácticos de validez legal, todas las posiciones pertenecen a la misma firma del mismo firmante; la distinción solo importa si vas a inspeccionar el detalle criptográfico del PDF resultante. *** ## Rotación | Valor | Descripción | | :- | :- | | `0` | Sin rotación (default). | | `90` | Rotación 90° en sentido horario. | | `180` | Firma invertida 180°. | | `270` | Rotación 90° en sentido antihorario. | *** ## Comportamiento si falta información `CUSTOM` no valida ni rechaza la petición si algo falta: si un firmante no incluye `positions`, o el JSON enviado no es válido, el sistema **no genera un error** — simplemente coloca esa firma con los valores default (`pos_x=100, pos_y=100, width=140, height=80`) en la **última página** del documento. Del mismo modo, no se valida que las coordenadas quepan dentro de la página. **Recomendación:** valida en tu propia integración, antes de enviar la petición, que: - `pos_x + width` y `pos_y + height` no excedan el tamaño real de esa página. - `page` sea un número entre `1` y el total de páginas del PDF. Esto evita firmas que terminan fuera del área visible o en una página inesperada. *** ## Ejemplo completo de integración **JSON de Petición** ```json { "company_id": "ac110005-9ae6-1318-819a-fc564ae00125", "tries": 3, "remember_every": 3, "remember_at": "16:54:00", "sign_mode": "PDF_SIGNATURE", "sign_position": "CUSTOM", "mode_logo": "FULL_BRANDING", "notification_mode": "ENABLED", "sign_ordered": "INACTIVE", "config": { "identity_providers": ["LOCAL", "VERIFICAMEX"], "allow_type_signatures": ["VERIFICAMEX", "EFIRMA"] }, "signatures": [ { "name": "Juan Perez", "email": "juanperez@gmail.com", "positions": "[{\"pos_x\":120,\"pos_y\":650,\"width\":180,\"height\":70,\"page\":1,\"rotation\":0}]" } ], "spectators": [], "webhook": "https://webhook.site/my-webhook" } ``` **cURL** ```bash curl --location 'https://api.digitafirma.com/v1/documents' \ --header 'X-Api-Key: {X-API-KEY}' \ --header 'Content-Type: application/json' \ --data '{ "company_id": "ac110005-9ae6-1318-819a-fc564ae00125", "tries": 3, "remember_every": 3, "remember_at": "16:54:00", "sign_mode": "PDF_SIGNATURE", "sign_position": "CUSTOM", "mode_logo": "FULL_BRANDING", "notification_mode": "ENABLED", "sign_ordered": "INACTIVE", "signatures": [ { "name": "Juan Perez", "email": "juanperez@gmail.com", "positions": "[{\"pos_x\":120,\"pos_y\":650,\"width\":180,\"height\":70,\"page\":1,\"rotation\":0}]" } ], "spectators": [], "webhook": "https://webhook.site/my-webhook" }' ``` Con el `uuid` de la respuesta, continúa con [Subir un documento](/document-management/upload-document) para cargar el PDF y activar el proceso de firma. *** ## Actualizar posiciones de firmantes existentes El campo `positions` también está disponible al [actualizar firmantes](/document-management/upload-signatures) de un documento ya creado (`PATCH /v1/documents/{uuid}`), con el mismo formato de string JSON descrito arriba. > **Próximos pasos** > > Antes de pasar a producción, descarga el PDF firmado ([Descargar documento firmado](/document-management/download-signed-pdf)) y verifica visualmente que las firmas caigan donde esperas, especialmente si tus documentos tienen páginas de distinto tamaño.