# firmin: firmar PDF desde la línea de comandos

Guía para agentes de IA y scripts. `firmin` firma PDF en el Mac con un
certificado del llavero de macOS (PAdES-B-B, SHA-256), con el mismo motor y la
misma firma visible que la app Firmín. Todo pasa en el Mac: el PDF no se sube
a ningún sitio y la firma no consulta ningún servidor.

`firmin` va dentro de la app. Al abrir Firmín queda enlazada en
`~/.local/bin/firmin`; si no está en el PATH, la ruta completa es
`"/Applications/Firmín.app/Contents/Helpers/firmin"`. Si Firmín no está
instalada, sigue antes https://firmin.gobierto.es/instalar.md.

```sh
firmin --version     # 0.3
firmin --help        # la ayuda corta, en la terminal
```

## Antes de firmar por una persona

- Firma solo los documentos que te ha pedido firmar, y con su certificado.
- Si hay varios certificados, pregúntale cuál. Uno puede ser suyo y otro de
  la empresa que representa. `firmin` no elige: con varios, `--cert` es
  obligatorio.
- Enséñale la vista previa (`firmin preview`) y espera a que la confirme.
- Avísale de que macOS le va a pedir permiso para usar la clave del
  certificado. `firmin` espera mientras tanto. Si lo deniega, `firmin`
  termina con el código 3: no lo reintentes sin preguntarle.
- El original no se toca. La copia firmada se guarda junto a él.

## El flujo

```sh
firmin certs --json                                       # 1. el certificado
firmin detect contrato.pdf --cert 1A2B3C --json           # 2. dónde irá la firma
firmin preview contrato.pdf --cert 1A2B3C -o /tmp/vista.png --json   # 3. la vista previa
firmin sign contrato.pdf --cert 1A2B3C --json             # 4. firmar
```

Lo firmado aparece también en la lista de últimos firmados de la app.

## Órdenes

Todas aceptan `--json`: la salida, y también los errores, va en JSON por la
salida estándar. Sin `--json`, el texto es para personas y puede cambiar.

### firmin certs

Los certificados de firma vigentes del llavero, con su clave privada.

```json
{
  "certificates": [
    {
      "authority": "AC FNMT Usuarios",
      "expires": "2028-03-14T10:22:05Z",
      "holder": "Ana García López",
      "name": "GARCÍA LÓPEZ ANA - 12345678Z",
      "sha1": "1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D"
    },
    {
      "authority": "AC Representación",
      "expires": "2027-11-02T09:15:40Z",
      "holder": "Ana García López",
      "name": "12345678Z ANA GARCÍA (R: B99999999)",
      "organization": "EMPRESA DE PRUEBAS SL",
      "organizationID": "B99999999",
      "sha1": "9F8E7D6C5B4A39281706F5E4D3C2B1A098765432"
    }
  ]
}
```

- `sha1` es la huella que se pasa a `--cert`.
- `organization` y `organizationID` solo salen en los certificados de
  representante de una empresa.
- Sin certificados, la lista va vacía. El DNI electrónico y las tarjetas
  criptográficas todavía no funcionan.

### firmin detect `<pdf>`

Dónde puede ir la firma visible, en orden. La que tiene `"default": true` es
la que se usa si no se dice otra. Con `--cert`, Firmín tiene en cuenta el
nombre del firmante para elegirla (busca su «Fdo.:», por ejemplo).

```json
{
  "certificate": "1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D",
  "file": "/Users/ana/Documents/solicitud.pdf",
  "pages": 1,
  "positions": [
    {
      "default": true,
      "detected": true,
      "field": "FirmaSolicitante",
      "index": 1,
      "label": "Campo de firma «FirmaSolicitante» · pág. 1",
      "page": 1,
      "rect": { "x": 72, "y": 220, "width": 228, "height": 70 }
    },
    {
      "default": false,
      "detected": true,
      "index": 2,
      "label": "Sobre «Fdo.: Ana García López» · pág. 1",
      "page": 1,
      "rect": { "x": 72, "y": 216, "width": 250, "height": 79 }
    },
    {
      "default": false,
      "detected": false,
      "index": 3,
      "label": "Al final del contenido · pág. 1",
      "page": 1,
      "rect": { "x": 72, "y": 95, "width": 250, "height": 81 }
    }
  ]
}
```

- `index` es lo que se pasa a `--position`.
- `field` sale cuando la posición es un campo de firma vacío del PDF; su
  nombre es lo que se pasa a `--field`.
- `detected: false` es la posición de reserva: debajo del último texto de la
  última página, cuando el documento no tiene hueco de firma.
- `rect` va en puntos PDF, desde la esquina inferior izquierda de la página.

### firmin preview `<pdf>`

Dibuja en un PNG la página con la firma en su sitio, como la enseña la app.
Acepta las mismas opciones de posición que `sign`. Sin `--output`, la imagen
va a una carpeta temporal.

```json
{
  "certificate": "1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D",
  "file": "/Users/ana/Documents/contrato.pdf",
  "image": "/tmp/vista.png",
  "label": "Sobre «Fdo.: Ana García López» · pág. 1",
  "page": 1,
  "rect": { "x": 72, "y": 216, "width": 250, "height": 79 }
}
```

Enseña `image` a la persona antes de firmar.

### firmin sign `<pdf>…`

Firma uno o varios PDF y guarda cada copia como `<nombre>-firmado.pdf` junto
al original. Si ya existe, usa `<nombre>-firmado-2.pdf`, `-3`… Con un solo
PDF, `--output` elige otra ruta; `--force` la sobrescribe si existe.

```json
{
  "certificate": {
    "authority": "AC FNMT Usuarios",
    "expires": "2028-03-14T10:22:05Z",
    "holder": "Ana García López",
    "name": "GARCÍA LÓPEZ ANA - 12345678Z",
    "sha1": "1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D"
  },
  "failed": [
    {
      "error": "El PDF está certificado y no admite más firmas.",
      "input": "/Users/ana/Documents/certificado.pdf"
    }
  ],
  "signed": [
    {
      "input": "/Users/ana/Documents/contrato.pdf",
      "milliseconds": 50,
      "output": "/Users/ana/Documents/contrato-firmado.pdf",
      "visible": {
        "label": "Sobre «Fdo.: Ana García López» · pág. 1",
        "page": 1,
        "rect": { "x": 72, "y": 216, "width": 250, "height": 79 }
      }
    }
  ]
}
```

- Con varios PDF, cada uno usa su posición por defecto. `--position` y
  `--page` van con un solo PDF.
- Un PDF que falla no para los demás: va a `failed` y el código de salida es
  1. Mira siempre `failed`.
- `visible` no sale con `--invisible`.
- Si la persona deniega el permiso de la clave, `firmin` para ahí: la
  respuesta lleva lo firmado hasta entonces, `"error"` y `"exitCode": 3`.

## Elegir el certificado

`--cert` acepta la huella SHA-1 entera, su principio (`--cert 1A2B3C`) o parte
del nombre, del titular o de la empresa, sin distinguir mayúsculas ni
acentos. Si coincide con más de uno, `firmin` termina con el código 2 y la
lista de los que coinciden: usa la huella.

## Elegir la posición

Por defecto, la que elegiría la app (`"default": true` en `detect`). Para otra:

| Opción | Dónde |
|---|---|
| `--position <n>` | La posición `n` de `firmin detect`. |
| `--field <nombre>` | Dentro de un campo de firma vacío del PDF. |
| `--page <n> --rect <x,y,ancho,alto>` | Un recuadro en la página `n` (desde 1), en puntos PDF desde la esquina inferior izquierda. |
| `--invisible` | Sin firma visible. |

## Lo que pone la firma visible

El nombre del titular, el DNI enmascarado (`***4567**`), quién emitió el
certificado, la fecha y la hora, y «Firmado con firmin.gobierto.es».

- `--full-id`: el DNI completo.
- `--no-branding`: sin «Firmado con firmin.gobierto.es».

## Errores y códigos de salida

| Código | Qué ha pasado |
|---|---|
| 0 | Todo bien. |
| 1 | Error: el PDF no existe o no se abre, no hay certificados, o algún PDF de `sign` ha fallado. |
| 2 | Uso incorrecto: falta `--cert` con varios certificados, una opción mal escrita… |
| 3 | La persona ha denegado el permiso para usar la clave. |

Con `--json`, los errores salen así:

```json
{
  "error": "Hay 2 certificados: elige uno con --cert <huella o nombre>.\n  1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D  GARCÍA LÓPEZ ANA - 12345678Z\n  9F8E7D6C5B4A39281706F5E4D3C2B1A098765432  12345678Z ANA GARCÍA (R: B99999999)",
  "exitCode": 2
}
```

## Lo que `firmin` no hace

- No rellena formularios: eso solo lo hace la app.
- No usa AutoFirma de respaldo, como la app: si el motor propio no puede
  firmar un PDF, falla.
- No firma PDF certificados, que no admiten más firmas, ni con el DNI
  electrónico o tarjetas.
- La firma no lleva sello de tiempo.

Más: la ayuda de Firmín, https://firmin.gobierto.es/ayuda/ · abre@gobierto.es
