257 lines
5.9 KiB
Markdown
257 lines
5.9 KiB
Markdown
|
|
# API Reference
|
||
|
|
|
||
|
|
## Endpoints
|
||
|
|
|
||
|
|
### Health
|
||
|
|
```
|
||
|
|
GET /api/v1/health
|
||
|
|
```
|
||
|
|
Verifica que la API está funcionando.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{"status": "ok"}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Obtener Clave Pública
|
||
|
|
```
|
||
|
|
GET /api/v1/auth/public-key
|
||
|
|
```
|
||
|
|
Obtiene la clave pública RSA para cifrar contraseñas.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{"public_key": "base64_encoded_key"}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Registrar Certificado
|
||
|
|
```
|
||
|
|
POST /api/v1/auth/register
|
||
|
|
```
|
||
|
|
Registra y valida un certificado digital. El certificado se envía como base64 en el body (no como ruta de fichero).
|
||
|
|
|
||
|
|
**Request (snake_case):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"cert_name": "mi-certificado",
|
||
|
|
"cert_file": "BASE64_CONTENT_OF_P12_FILE",
|
||
|
|
"password_encrypted": "base64_encoded_encrypted_password"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Request (PascalCase — compatibilidad con frontend):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"CertName": "mi-certificado",
|
||
|
|
"CertFile": "BASE64_CONTENT_OF_P12_FILE",
|
||
|
|
"PasswordEncrypted": "base64_encoded_encrypted_password"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
> **Nota:** `password_encrypted` debe ser la contraseña del certificado cifrada con la clave pública RSA obtenida en `/api/v1/auth/public-key`. No se envía en texto plano.
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response (éxito):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"cert": {
|
||
|
|
"subject": "...",
|
||
|
|
"issuer": "...",
|
||
|
|
"expired": false,
|
||
|
|
"expiring_soon": false,
|
||
|
|
"days_until_expiry": 365
|
||
|
|
},
|
||
|
|
"token": "A1B2C3D4..."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response (error):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"error": "certificate_expired",
|
||
|
|
"cert": {
|
||
|
|
"subject": "...",
|
||
|
|
"issuer": "...",
|
||
|
|
"expired": true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Formatos Disponibles
|
||
|
|
```
|
||
|
|
GET /api/v1/formats
|
||
|
|
```
|
||
|
|
Lista los formatos de entrada soportados. La API detecta automáticamente el formato del JSON recibido.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"formats": ["dolibarr", "native"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Alta de Factura
|
||
|
|
```
|
||
|
|
POST /api/v1/facturas
|
||
|
|
```
|
||
|
|
Registra una factura en VeriFactu. El formato de entrada se detecta automáticamente (no requiere parámetro).
|
||
|
|
|
||
|
|
**Formato nativo (por defecto):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"tipo": "alta",
|
||
|
|
"factura": {
|
||
|
|
"emisor_nif": "53950250R",
|
||
|
|
"emisor_nombre": "EMPRESA EJEMPLO SL",
|
||
|
|
"num_serie": "FV2026/001",
|
||
|
|
"fecha_expedicion": "17-04-2026",
|
||
|
|
"tipo_factura": "F1",
|
||
|
|
"descripcion": "Factura de prueba",
|
||
|
|
"destinatario": {
|
||
|
|
"nombre": "CLIENTE SL",
|
||
|
|
"nif": "B98765432"
|
||
|
|
},
|
||
|
|
"iva": [
|
||
|
|
{"base": 100.00, "cuota": 21.00, "tipo": 21.0}
|
||
|
|
],
|
||
|
|
"importe_total": 121.00
|
||
|
|
},
|
||
|
|
"sistema": {
|
||
|
|
"nombre": "Mi Sistema",
|
||
|
|
"nif_proveedor": "53950250R",
|
||
|
|
"version": "1.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Formato Dolibarr BFF:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"invoice": {
|
||
|
|
"number": "FA2024/001",
|
||
|
|
"date": "2024-09-13T00:00:00Z",
|
||
|
|
"notePublic": "Factura de prueba",
|
||
|
|
"lines": [
|
||
|
|
{"description": "Servicio", "quantity": 1, "unitPrice": 100, "taxRate": 21}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"client": {
|
||
|
|
"name": "CLIENTE SL",
|
||
|
|
"vatNumber": "B98765432"
|
||
|
|
},
|
||
|
|
"emisor": {
|
||
|
|
"nif": "53950250R",
|
||
|
|
"nombre": "EMPRESA EJEMPLO SL"
|
||
|
|
},
|
||
|
|
"sistema": {
|
||
|
|
"nombre": "Mi Sistema",
|
||
|
|
"nif_proveedor": "53950250R",
|
||
|
|
"version": "1.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Ver [formatos.md](formatos.md) para detalles de cada formato y cómo añadir nuevos.
|
||
|
|
|
||
|
|
**Response (AEAT disponible):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"csv": "A-FSZKDA8UG7WD9U",
|
||
|
|
"estado": "Correcto"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response (fallback local):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"csv": "0CE5F940CEA...",
|
||
|
|
"estado": "Correcto (local)"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
> **Nota:** El campo `sistema.nombre` debe coincidir con el nombre registrado en la AEAT para el NIF del emisor. Si no coincide, la AEAT rechazará la factura con error de censo (código 1110 o 1239).
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Anular Factura
|
||
|
|
```
|
||
|
|
POST /api/v1/facturas/anular
|
||
|
|
```
|
||
|
|
Anula una factura previamente registrada. Usa el mismo formato que el alta pero con `tipo: "anulacion"` y `tipo_factura: "R1"` (u otro tipo de rectificativa).
|
||
|
|
|
||
|
|
**Request:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"tipo": "anulacion",
|
||
|
|
"factura": {
|
||
|
|
"emisor_nif": "53950250R",
|
||
|
|
"emisor_nombre": "JOSEP VICENT MESTRE LLOBELL",
|
||
|
|
"num_serie": "FV2026/001",
|
||
|
|
"fecha_expedicion": "21-05-2026",
|
||
|
|
"tipo_factura": "R1",
|
||
|
|
"descripcion": "Anulacion de factura de prueba",
|
||
|
|
"iva": [
|
||
|
|
{"base": 1000.00, "cuota": 210.00, "tipo": 21.0}
|
||
|
|
],
|
||
|
|
"importe_total": 1210.00
|
||
|
|
},
|
||
|
|
"sistema": {
|
||
|
|
"nombre": "JOSEP VICENT MESTRE LLOBELL",
|
||
|
|
"nif_proveedor": "53950250R",
|
||
|
|
"version": "1.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response (éxito):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"estado": "Anulada"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
> **Nota:** El campo `sistema.nombre` debe coincidir con el nombre registrado en la AEAT para el NIF del emisor. Si no coincide, la AEAT rechazará la factura con error de censo.
|
||
|
|
```
|
||
|
|
|
||
|
|
## Códigos de Error
|
||
|
|
|
||
|
|
| Código | Descripción |
|
||
|
|
|--------|------------|
|
||
|
|
| `certificate_expired` | El certificado ha expirado |
|
||
|
|
| `certificate_not_yet_valid` | El certificado aún no es válido |
|
||
|
|
| `certificate_expiring_soon` | El certificado caduca en menos de 30 días |
|
||
|
|
| `invalid_password_or_format` | Contraseña incorrecta o formato inválido |
|
||
|
|
| `file_not_found` | El archivo de certificado no existe |
|
||
|
|
| `validation_failed` | Los datos de la factura no son válidos |
|
||
|
|
| `aeat_error` | Error comunicando con la AEAT |
|
||
|
|
| `aeat_fault` | Error SOAP de la AEAT |
|
||
|
|
| `hash_save_error` | Error guardando el hash local |
|
||
|
|
| `hash_storage_error` | Error leyendo el hash anterior |
|
||
|
|
| `missing_fields` | Faltan campos obligatorios en el registro |
|
||
|
|
| `invalid_json` | El JSON enviado no es válido |
|
||
|
|
| `decrypt_failed` | No se pudo descifrar la contraseña con la clave privada |
|
||
|
|
| `emisor_nif_required` | Falta el NIF del emisor en la anulación |
|
||
|
|
| `num_serie_required` | Falta el número de serie en la anulación |
|
||
|
|
| `fecha_expedicion_required` | Falta la fecha de expedición en la anulación |
|
||
|
|
|
||
|
|
## Errores de la AEAT más comunes
|
||
|
|
|
||
|
|
| Código | Descripción | Solución |
|
||
|
|
|--------|------------|----------|
|
||
|
|
| 1110 | NIF no identificado en el censo | Verificar que `sistema.nombre` coincide con el nombre del NIF |
|
||
|
|
| 1189 | Bloque Destinatarios obligatorio para tipo F1/F3/R1-R4 | Incluir `destinatario` con nombre y NIF |
|
||
|
|
| 1239 | Error en bloque Destinatario | Verificar formato del NIF del destinatario |
|