# 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 |