# API DNI / RUC — guía para LLM / agentes

## Propósito
Dos consultas distintas sobre documentos peruanos:

- **DNI** (8 dígitos) → titular: nombres, apellido paterno y materno. Dos
  versiones según el shape que necesites (`v1` con `cliente`, `v2` con
  `nombre_completo` + `codigo_verificacion`).
- **RUC** (11 dígitos) → razón social, estado, condición de domicilio y
  domicilio fiscal, desde el Padrón Reducido oficial de SUNAT.

## Base URL
`https://api-dni-ruc.gd.pe`

## Autenticación (los tres endpoints)
- Header: `Authorization: Bearer <api-key>`.
- Rate limit por API key: por minuto + cuota diaria + cuota mensual. La
  respuesta incluye headers `X-RateLimit-Limit-Minute`,
  `X-RateLimit-Remaining-Minute`, `X-RateLimit-Limit-Day`,
  `X-RateLimit-Remaining-Day`, `X-RateLimit-Limit-Month`,
  `X-RateLimit-Remaining-Month`. Al superar el límite por minuto se agrega
  `Retry-After` (segundos).
- Cache-Control: `public, max-age=300` en respuestas 200.
- `source` y header `X-Source`: en DNI, `"cache"` si la respuesta vino de la
  BD local (sin consumir cupo) o `"provider"` si se llamó al proveedor externo
  (peruapi.com o api.perudevs.com — el balanceo es transparente para el
  cliente). En RUC es siempre `"padron"`.

## Endpoint v1 — shape histórico

```
GET /api/dni/{dni}
```

- `dni` (path, requerido): exactamente 8 dígitos numéricos.

### Respuesta 200 OK (v1)

```json
{
  "success": true,
  "source": "cache",
  "data": {
    "dni": "60012345",
    "cliente": "QUISPE PEREZ JULIO FERNANDO",
    "nombres": "JULIO FERNANDO",
    "apellido_paterno": "QUISPE",
    "apellido_materno": "PEREZ",
    "mensaje": "OK",
    "code": "200"
  }
}
```

## Endpoint v2 — con nombre_completo y código de verificación

```
GET /api/v2/dni/{dni}
```

- Mismos parámetros, mismas reglas de auth/rate limit/cuotas que v1.
- `cliente` se reemplaza por `nombre_completo`.
- Se agrega `codigo_verificacion`: dígito verificador del DNI provisto por
  `api.perudevs.com`. Puede ser `null` si la respuesta vino cacheada de un
  proveedor que no lo expone (en ese caso, una nueva consulta servida por
  perudevs lo poblará en el cache).

### Respuesta 200 OK (v2)

```json
{
  "success": true,
  "source": "provider",
  "data": {
    "dni": "60012345",
    "nombres": "JULIO FERNANDO",
    "apellido_paterno": "QUISPE",
    "apellido_materno": "PEREZ",
    "nombre_completo": "JULIO FERNANDO QUISPE PEREZ",
    "codigo_verificacion": "8",
    "mensaje": "OK",
    "code": "200"
  }
}
```

## Endpoint RUC — razón social desde SUNAT

```
GET /api/ruc/{ruc}
```

- `ruc` (path, requerido): 11 dígitos, empezando en 10, 15, 16, 17 o 20.
- Se resuelve contra el Padrón Reducido del RUC cargado en la BD local, que se
  refresca a diario desde el archivo oficial de SUNAT. **No** hay proveedor
  externo: la consulta no consume cupo de `provider_tokens`, pero sí gasta la
  cuota de tu API key.
- `Cache-Control: public, max-age=3600` (el padrón cambia una vez al día).
- `padron_actualizado_al`: fecha del archivo de SUNAT con el que se pobló la
  tabla. Implica que un RUC dado de alta hoy puede no aparecer hasta mañana, y
  que un cambio de estado tarda lo mismo en reflejarse. Puede ser `null` si el
  import se corrió desde un archivo local, sin fecha de origen.
- `source` es siempre `"padron"` (header `X-Source: padron`). Este endpoint
  **nunca** devuelve `502 provider_error` ni `503 no_tokens_available`.

### Respuesta 200 OK

```json
{
  "success": true,
  "source": "padron",
  "padron_actualizado_al": "2026-09-02T07:38:29.000Z",
  "data": {
    "ruc": "20123456789",
    "razon_social": "EMPRESA DEMO SOCIEDAD ANONIMA CERRADA",
    "estado": "ACTIVO",
    "condicion": "HABIDO",
    "ubigeo": "150101",
    "direccion": "JR. EJEMPLO NRO. 521 A.H. TRAPECIO NORTE",
    "direccion_partes": {
      "tipo_via": "JR.",
      "nombre_via": "EJEMPLO",
      "codigo_zona": "A.H.",
      "tipo_zona": "TRAPECIO NORTE",
      "numero": "521",
      "interior": null,
      "lote": null,
      "departamento": null,
      "manzana": null,
      "kilometro": null
    }
  }
}
```

### Domicilio fiscal

`direccion` ya viene concatenado y listo para mostrar. `direccion_partes` trae
lo mismo sin unir, por si necesitás armarlo de otra forma.

| Campo | Contenido |
|-------|-----------|
| `tipo_via` | Abreviatura del tipo de vía: AV., JR., CAL., PSJ. |
| `nombre_via` | Nombre de la vía. |
| `codigo_zona` | **Pese al nombre, la ABREVIATURA del tipo de zona**: URB., A.H., P.J., C.P. |
| `tipo_zona` | **Pese al nombre, el NOMBRE propio de la zona.** |
| `numero` | Número de puerta. |
| `interior` | Interior. |
| `lote` | Lote. |
| `departamento` | Departamento (piso/unidad), **no** el departamento geográfico. |
| `manzana` | Manzana. |
| `kilometro` | Kilómetro, para direcciones sobre carretera. |

**Ojo con la zona**: SUNAT tiene invertidos los rótulos de esas dos columnas en su
archivo fuente, y conservamos sus nombres originales para que el dato sea
rastreable contra la fuente. Por eso al concatenar va `codigo_zona` primero y
`tipo_zona` después — `"A.H. TRAPECIO NORTE"`, no al revés.

**Todos los campos de `direccion_partes` pueden ser `null`**, igual que
`estado`, `condicion`, `ubigeo` y `direccion`. Los únicos garantizados en un
200 son `ruc` y `razon_social`.

### Qué NO trae
El padrón reducido no incluye fecha de inscripción, tipo de contribuyente,
actividad económica (CIIU), representantes legales ni establecimientos anexos.
Para eso hace falta la ficha RUC completa, que este endpoint no consulta.

Además, los RUC 10 (personas naturales con negocio) normalmente vienen sin
domicilio desglosado: `direccion` será `null` y sólo tendrás razón social,
estado y condición. Programá para ese caso.

## Códigos de error

| HTTP | error                  | Significado                                          |
|------|------------------------|------------------------------------------------------|
| 400  | invalid_dni            | El parámetro no tiene 8 dígitos.                    |
| 400  | invalid_ruc            | El RUC no tiene 11 dígitos o el prefijo no es válido.|
| 401  | unauthorized           | Falta la API key o es inválida/inactiva.            |
| 404  | not_found              | El DNI no existe, o el RUC no figura en el padrón.  |
| 429  | rate_limit_minute      | Se superó el límite de requests por minuto.         |
| 429  | quota_daily            | Se agotó la cuota diaria de la API key.             |
| 429  | quota_monthly          | Se agotó la cuota mensual de la API key.            |
| 502  | provider_error         | El proveedor externo falló tras reintentos (sólo DNI).|
| 503  | no_tokens_available    | Tokens del proveedor agotados hoy (sólo DNI).       |
| 503  | padron_unavailable     | El padrón RUC todavía no fue importado (sólo /api/ruc).|

## Ejemplo cURL

```bash
# v1 (cliente)
curl -H "Authorization: Bearer TU_API_KEY" https://api-dni-ruc.gd.pe/api/dni/60012345

# v2 (nombre_completo + codigo_verificacion)
curl -H "Authorization: Bearer TU_API_KEY" https://api-dni-ruc.gd.pe/api/v2/dni/60012345

# RUC (razón social)
curl -H "Authorization: Bearer TU_API_KEY" https://api-dni-ruc.gd.pe/api/ruc/20123456789
```

## Cómo elegir v1 vs v2
- Usá **v1** si ya tenés integraciones que leen `data.cliente` y no necesitás
  el dígito verificador.
- Usá **v2** si necesitás el código de verificación (`codigo_verificacion`)
  o preferís el nombre canónico `nombre_completo`.

## Panel administrativo

Bajo `/admin`, protegido por login admin. Permite gestionar los tokens de
los proveedores (peruapi.com y api.perudevs.com), ver métricas, auditar
consultas y limpiar el cache local. El padrón RUC no se administra desde el
panel: se refresca con `pnpm padron:import` (PM2 lo corre a diario).
