# API v1 de CPS Cuentas de Cobro (para agentes)

> Base URL: `http://192.168.1.26:8080/api/v1` (ajusta el host si accedes desde otro dominio).

Esta API está pensada para que un agente de IA (Claude Code, Hermes, OpenCode, un asistente de navegador, etc.)
actuando en nombre de un contratista pueda crear contratos, cuentas de cobro y subir documentos sin pasar por
el navegador. Usa la misma lógica de negocio que la interfaz web — no es un sistema paralelo.

## Autenticación

1. Inicia sesión en la web y ve a **Perfil → Tokens de API** (`/perfil/tokens`).
2. Genera un token con un nombre descriptivo (ej. `claude-code-laptop`). El valor solo se muestra una vez.
3. Envíalo en cada request como header:

```
Authorization: Bearer <tu-token>
```

Un token solo puede ver/crear lo que su usuario dueño podría ver/crear en la web — la misma autorización de
siempre, sin reglas nuevas. Revoca un token en cualquier momento desde la misma página.

Ejemplo completo de request:
```bash
curl -X POST https://<host>/api/v1/contratos \
  -H "Authorization: Bearer <tu-token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"numero_contrato": "3618-2024", ...}'
```
El servidor ya fuerza JSON en todas las respuestas de `/api/v1/*` independientemente de lo que envíes en
`Accept`, pero mándalo de todas formas: es lo que espera cualquier cliente HTTP estándar y evita sorpresas si
alguna vez pruebas la API desde una herramienta que no respeta ese forzado.

## Límites

- `POST /cuentas/{id}/documentos` está limitado a **5 solicitudes por minuto** por usuario (mismo límite que la
  web, porque cada llamada dispara una extracción real vía LLM). Si vas a subir varios documentos, espera
  ~15 segundos entre cada uno o agrúpalos en lotes de 5 con una espera de 60s entre lotes.
- Todas las respuestas son JSON. Errores de validación devuelven 422 con `{"error": "..."}` o el shape estándar
  de Laravel (`{"message": "...", "errors": {...}}`) para 422 de validación de formulario.

## Endpoints

### `GET /api/v1/contratos`
Lista los contratos del usuario dueño del token.

### `POST /api/v1/contratos`
Crea un contrato nuevo. Body:
```json
{
  "numero_contrato": "3618-2024",
  "fecha_contrato": "2024-09-20",
  "objeto": "Prestar servicios de apoyo a la gestión...",
  "entidad_id": 5,
  "valor_total": 8100000,
  "num_cuentas": 3,
  "fecha_inicio": "2024-09-23",
  "fecha_fin": "2024-12-22",
  "supervisor_nombre": "Reynaldo Prieto",
  "supervisor_email": "supervisor@entidad.gov.co",
  "obligaciones": [
    {"numero": 1, "texto": "Apoyar la gestión documental..."},
    {"numero": 2, "texto": "Apoyar la atención al ciudadano..."}
  ]
}
```
Devuelve `201` con el contrato creado (incluye `obligaciones`, cada una con su `id` — lo necesitas para el
siguiente paso).

`obligaciones` es **obligatorio** aquí (mínimo 1) — a diferencia del wizard web, que permite guardar un
contrato en borrador sin obligaciones y completarlas después. La API no tiene ese concepto de borrador
intermedio, así que un agente debe conocer al menos una obligación antes de crear el contrato (usa
`POST /contratos/documentos` primero si no las tienes).

### `POST /api/v1/contratos/documentos` — cuando el contrato todavía no existe
Si vas a registrar un contrato por primera vez y no conoces todos sus datos de memoria, sube uno o más de sus
documentos de formación para extraerlos en vez de adivinarlos. No persiste nada — solo devuelve los campos
extraídos para que arme tu propio `POST /contratos` (si subes varios documentos y hay datos distintos, decide
tú cuál prevalece, igual que hace un humano en el wizard web). Body:
```json
{
  "tipo_documento": "contrato",
  "archivo_base64": "<...>",
  "nombre_archivo": "contrato_3618.pdf"
}
```
`tipo_documento` acepta: `contrato`, `minuta`, `estudios_previos`, `acta_inicio`, `informe`,
`designacion_supervisor`, `cdp`, `rp`, `poliza`. Respuesta: `{"data": {"exito": true, "datos": {...}, "confianza": 90}}`.
Mismo límite de 5/min que `/cuentas/{id}/documentos`.

### `GET /api/v1/contratos/{id}`
Detalle del contrato con `entidad`, `secretaria`, `obligaciones`, `cuentasCobro`.

### `GET /api/v1/cuentas`
Lista las cuentas de cobro del usuario, con estado y estado de revisión.

### `POST /api/v1/cuentas`
Crea una cuenta en estado `borrador`. Body:
```json
{
  "contrato_id": 23,
  "numero_cuenta": 1,
  "periodo_inicio": "2024-10-01",
  "periodo_fin": "2024-10-30",
  "valor_cobrar": 2695270,
  "observaciones": "opcional"
}
```

### `GET /api/v1/cuentas/{id}`
Detalle de la cuenta con `contrato`, `actividades.obligacion`, `seguridadSocial`, `estampillas`.

### `POST /api/v1/cuentas/{id}/documentos`
Sube un documento, lo extrae vía IA y **lo guarda automáticamente** en la cuenta (a diferencia del wizard
humano, no necesitas reconstruir ningún JSON interno). Body:
```json
{
  "tipo_documento": "acta_ejecucion",
  "archivo_base64": "<contenido del PDF en base64, sin el prefijo data:...>",
  "nombre_archivo": "acta_octubre.pdf"
}
```
`tipo_documento` acepta: `acta_ejecucion`, `seguridad_social`, `estampilla`, `informe_cumplimiento`,
`informe_supervisor`. Respuesta:
```json
{
  "data": {
    "extraido": { "exito": true, "datos": { "...": "..." }, "confianza": 100, "actividades": [ {"numero": 1, "texto": "..."} ] },
    "cuenta": { "...": "estado actualizado de la cuenta, incluye seguridadSocial/estampillas ya sincronizados" }
  }
}
```
Si el documento trae `actividades`/`obligaciones` (típico de `informe_cumplimiento`), vienen en
`extraido.datos.actividades` para que decidas cómo mapearlas a los `obligacion_id` reales del contrato antes
de llamar al siguiente endpoint — este endpoint no las guarda por sí solo.

### `POST /api/v1/cuentas/{id}/soportes` — clasificar evidencia (ZIP de soportes)
No hay manejo de ZIP en el servidor: si tienes acceso a shell (Claude Code, OpenCode), descomprime el ZIP tú
mismo y llama este endpoint una vez por archivo. Cada llamada clasifica el archivo y lo **acumula** (no
reemplaza) en el análisis de soportes de la cuenta. Body:
```json
{
  "archivo_base64": "<...>",
  "nombre_archivo": "reporte_siif_octubre.pdf",
  "obligacion_id": 90
}
```
`obligacion_id` es opcional — si no lo sabes, la IA sugiere a cuál obligación corresponde. Si lo mandas, debe
pertenecer al contrato de esta cuenta (una obligación de otro contrato es rechazada con 422). Respuesta:
```json
{ "data": { "archivo": {"nombre": "...", "tipo": "reporte", "obligacion_sugerida": "90", "metadata_ia": {"fecha_documento": "2024-10-15", "...": "..."}}, "total_archivos": 3 } }
```
Antes de pasar la cuenta a `completa`, el revisor (y `GET /cuentas/{id}` indirectamente, vía el panel de
revisión) verá si falta soporte para alguna obligación o si alguna fecha de soporte no corresponde al periodo
facturado — corrígelo antes de reenviar para no entrar en un ciclo de revisión-corrección. Nota: si una
obligación genuinamente no tuvo actividad en el periodo, no hace falta soporte — redacta la actividad
indicándolo explícitamente (ej. "No se presentaron requerimientos de este tipo durante el periodo") en vez de
dejarla sin evidencia.

### `PUT /api/v1/cuentas/{id}/actividades`
Reemplaza las actividades de la cuenta. Body:
```json
{
  "actividades": [
    {"obligacion_id": 90, "producto": "...", "actividad_realizada": "...", "cantidad": "1", "evidencia": "..."}
  ]
}
```

### `POST /api/v1/cuentas/{id}/estado`
Transiciona el estado de la cuenta (`borrador → completa → generada`, o de vuelta a `borrador`). Al pasar a
`completa` se dispara automáticamente la validación cruzada de documentos y su resultado viene en la respuesta:
```json
{
  "data": { "...": "cuenta actualizada" },
  "validacion_cruzada": { "validacion": { "confianza": 87, "discrepancias": [] } }
}
```
Revisa `discrepancias` antes de dar la cuenta por lista — si hay alguna crítica, corrige y vuelve a intentar.

## Flujo típico de un agente

1. Si el contrato no existe todavía: `POST /contratos/documentos` con el contrato/minuta/estudios previos, etc.
   para obtener los campos reales en vez de adivinarlos.
2. `POST /contratos` (una vez por contrato) → guarda `obligacion_id` de la respuesta.
3. `POST /cuentas` (una vez por periodo).
4. `POST /cuentas/{id}/documentos` por cada documento real (acta, seguridad social, estampilla, informes) —
   respeta el límite de 5/min.
5. `POST /cuentas/{id}/soportes` una vez por archivo de evidencia (si tienes el ZIP, descomprímelo primero).
6. `PUT /cuentas/{id}/actividades` con los `obligacion_id` correctos.
7. `POST /cuentas/{id}/estado` con `{"estado": "completa"}` — revisa `validacion_cruzada.discrepancias`.
8. La cuenta queda visible para el flujo humano de revisión (rol revisor) igual que si la hubiera creado un
   contratista desde el navegador — incluyendo avisos de obligaciones sin soporte o con fechas fuera de periodo.
