Arquitectura y flujo¶
Los dos componentes¶
El sistema completo de firma digital tiene dos partes que corren en el mismo servidor Windows, pero son proyectos y procesos independientes:
| Componente | Qué es | Rol |
|---|---|---|
Este servicio (file-service) | API REST, Spring Boot | Recibe documentos, autentica, expone consulta/descarga, administra clientes/usuarios/procesos |
ssigner-server | App de escritorio, JavaFX | Administra certificados y la configuración detallada de cada proceso de firma; vigila carpetas; invoca a la librería que realmente firma los PDF |
Hay un tercer componente, una librería de firma digital (PAdES), pero este servicio nunca la toca directamente — solo ssigner-server la invoca. Para efectos de esta API, es una caja negra: le llega un PDF a una carpeta, y en algún momento sale firmado en otra.
El estilo de integración: sin llamadas directas¶
La decisión de diseño central de todo el sistema es esta: file-service y ssigner-server nunca se llaman entre sí — ninguno expone un endpoint HTTP que el otro consuma, y ninguno conoce la dirección IP o el proceso del otro. En vez de eso, se coordinan por dos medios compartidos:
- Una base de datos SQLite compartida (
ssigner.db, conjournal_mode=WALpara que ambos procesos puedan escribir sin corromper datos). Cada tabla tiene un dueño de escritura — ver Modelo de datos. - Carpetas de archivos compartidas — cada proceso de firma configurado tiene su propia carpeta de entrada (
ruta_entrada) y de salida (ruta_salida).
¿Por qué importa esto para vos, que estás integrando contra la API? Porque explica por qué la firma de un documento no es instantánea dentro de la misma llamada HTTP — POST /api/v1/documentos responde apenas el archivo queda guardado, no cuando termina de firmarse. El estado real se consulta después, por separado.
Cómo se mueve un documento, paso a paso¶
sequenceDiagram
participant C as Cliente (sistema externo o front)
participant FS as file-service (este servicio)
participant DB as ssigner.db (SQLite compartida)
participant SS as ssigner-server
participant M as Librería de firma
C->>FS: POST /api/v1/documentos (multipart: pdf, idProcesoFirma) + Bearer
FS->>DB: valida el proceso, verifica permisos si es FIRMANTE
FS->>FS: escribe el PDF en la carpeta de entrada del proceso
FS->>DB: INSERT documentos (estado=EN_PROCESO)
FS-->>C: 201 { trackingId, estado: EN_PROCESO }
Note over SS: en otro proceso, sin llamada directa
SS->>SS: detecta el archivo nuevo en la carpeta de entrada
SS->>DB: lee la configuración del proceso (certificado, apariencia)
SS->>M: invoca la firma
M-->>SS: PDF firmado
SS->>SS: escribe el resultado en la carpeta de salida
loop este servicio revisa periódicamente
FS->>FS: escanea la carpeta de salida de procesos EN_PROCESO
end
FS->>DB: UPDATE documentos SET estado=FIRMADO
C->>FS: GET /api/v1/documentos/{trackingId}
FS-->>C: 200 { estado: FIRMADO }
C->>FS: GET /api/v1/documentos/{trackingId}/archivo
FS->>DB: UPDATE documentos SET estado=ENTREGADO (solo la primera vez)
FS-->>C: 200 application/pdf
Los estados posibles de un documento, en orden: RECIBIDO → EN_PROCESO → FIRMADO → ENTREGADO, o ERROR si algo falla en el camino (ver más abajo).
Cuánto se demora, y qué pasa si no aparece firmado¶
No hay un tiempo fijo garantizado — depende de que ssigner-server esté corriendo y detecte el archivo. En la práctica, para un servidor sano, son segundos. Pero como no hay comunicación directa entre los dos componentes, este servicio necesita una forma de detectar si algo se rompió del otro lado (por ejemplo, si ssigner-server se cayó).
El mecanismo es un timeout: si un documento lleva más de 15 minutos (configurable) sin aparecer firmado en la carpeta de salida, este servicio lo marca como ERROR automáticamente. Si consultás el estado de un documento y ves ERROR, es la señal de que algo falló del lado de la firma — el mensaje de error (mensajeError en la respuesta) suele ser genérico ("no se firmó a tiempo"), porque este servicio no tiene visibilidad de la causa real (esa vive en los logs de ssigner-server).
Qué determina el idProcesoFirma¶
Cada "proceso de firma" es una configuración completa: qué certificado usar, cómo se ve la firma en el PDF (texto, imagen, posición), en qué página va, si lleva sello de tiempo, etc. Todo eso se configura una sola vez desde la app de escritorio, por soporte — no por la API. El idProcesoFirma que enviás en cada documento es solo una referencia a esa configuración ya existente; no vas a poder cambiar cómo se ve la firma desde esta API, solo elegir a cuál de los procesos ya configurados mandarle el documento.
Para saber qué procesos existen y cuáles podés usar, ver GET /api/v1/procesos (como ADMIN) o GET /api/v1/mis-procesos (como FIRMANTE, solo lo que te asignaron).
Multi-tenant: varios clientes en una sola base¶
Este servicio administra más de una organización ("cliente") sobre la misma base de datos — por ejemplo, distintas entidades que usan el mismo servidor para firmar sus documentos, cada una con sus propios firmantes y procesos.
El patrón usado es el más simple posible: una tabla clientes, y una columna cliente_id en las tablas que necesitan saber a quién pertenecen (usuarios, procesos). No hay una base de datos ni un esquema separado por cliente — es deliberado, es el trade-off correcto para la escala actual (decenas de clientes, no miles): mantener una sola base para respaldar y migrar es más simple operativamente que aislar cada cliente en su propia base.
El aislamiento entre clientes no lo impone la base de datos — SQLite no tiene seguridad a nivel de fila. Lo impone la aplicación: en cada request, si quien llama es un FIRMANTE, se verifica que el proceso pedido esté entre los que el ADMIN le asignó explícitamente. Si no, 403 PROCESO_NO_AUTORIZADO. Ver el detalle completo en Autenticación y roles.