# Guía de integración con la Estación Fiscal — interfaz v1

Este documento es el contrato estable para que un sistema administrativo o punto de venta
le entregue ventas a la Estación Fiscal. Es la "guía" a la que apunta la puerta
`/api/venta`. **La interfaz v1 está congelada**: lo que está aquí no cambia de forma
incompatible; lo nuevo se agrega sin romper lo existente, y un cambio incompatible
saldría como una versión nueva, con puerta propia, sin tumbar la v1.

## Qué es la Estación Fiscal

Un programa que se instala en un equipo del propio negocio. **Una instalación
corresponde a un solo contribuyente (un RIF)**. El sistema del negocio le entrega la
venta ya cerrada y la Estación hace todo lo fiscal: arma el documento con su desglose
de impuestos, lo asienta en un libro local inmutable con numeración consecutiva,
lo transmite a la imprenta digital configurada, recibe el número de control y lo
resguarda. El sistema que entrega la venta no calcula impuestos, no numera y no
habla con ninguna imprenta.

La imprenta con la que se emite **se configura dentro de la Estación** (con su
adaptador y sus credenciales, incluyendo una segunda imprenta de respaldo cuyo cambio
es siempre manual). Eso significa que el mismo instalador sirve con cualquier imprenta
digital autorizada que tenga adaptador: el integrador no necesita conocer nada de ella.

## Autenticación: la llave de máquina

Toda llamada lleva la llave de máquina de esa Estación:

```
Authorization: Bearer <llave de máquina>
```

La llave se genera desde la pantalla de la Estación (sección de operadores) y es de esa
instalación: no sirve en otra. Sin llave la puerta responde `401`. La puerta es local
(la Estación escucha en `127.0.0.1:8739`), pero la llave existe porque un programa
cualquiera de esa computadora no tiene por qué poder quemar numeración fiscal.

## El apretón de manos

```
GET /api/puente
```

Comprueba que la llave sirve y a qué negocio le está hablando, sin escribir nada.

Respuesta:

```json
{
  "ok": true,
  "negocio": "Arepa Power Restaurant",
  "rif": "J-12345678-9",
  "documentos": 132,
  "puede_sellar": true,
  "interfaz": 1
}
```

`interfaz` dice qué versión de este contrato habla la Estación instalada. Un integrador
prudente la comprueba una vez al conectar.

## Entregar una venta

```
POST /api/venta
Content-Type: application/json
```

Cuerpo (la venta ya cerrada y cobrada, con los impuestos por renglón resueltos por el
sistema que la entrega):

```json
{
  "origen_id": "V-2026-000123",
  "canal": "caja",
  "cliente": {
    "nombre": "María Rodríguez",
    "rif": "V-22606827-5",
    "cedula": null,
    "direccion": "Av. Principal, Araure"
  },
  "renglones": [
    { "nombre": "Arepa de Pernil", "cantidad": 2, "precio_usd": 6.0, "alicuota": 16 },
    { "nombre": "Refresco", "cantidad": 1, "precio_usd": 2.0, "alicuota": 16 }
  ],
  "otros_cargos": [
    { "nombre": "Delivery", "monto_usd": 2.0, "alicuota": 16 }
  ],
  "tasa_bs": 744.23,
  "pago": { "metodo": "pago_movil", "referencia": "123456" }
}
```

Campo por campo:

| Campo | Obligatorio | Qué es |
| --- | --- | --- |
| `origen_id` | sí | Identificador de la venta en el sistema de origen. Es la llave de idempotencia: la misma venta entregada dos veces devuelve el documento ya sellado, nunca crea uno nuevo. |
| `canal` | sí | `mesa`, `delivery`, `pickup` o `caja`. Informativo para el reporte, no cambia el cálculo. |
| `cliente.nombre` | sí | Nombre del cliente. Sin RIF ni cédula, el documento sale como consumidor final. |
| `cliente.rif` / `cliente.cedula` / `cliente.direccion` | no | Datos fiscales del comprador cuando pide factura con sus datos. |
| `renglones[]` | sí | Lo vendido. `precio_usd` es el precio unitario tal como se cobró; `alicuota` es la que le toca a ese renglón (16, 8 o 0). |
| `otros_cargos[]` | no | Cargos que no son productos (delivery, servicio). Van gravados igual, con su alícuota. |
| `tasa_bs` | sí (puede ser `null`) | Tasa del día para expresar el documento también en bolívares. |
| `pago` | no | Cómo pagó el cliente. Informativo para el documento. |
| `pago.en_divisas` | no | `true` si el pago fue en moneda extranjera (efectivo en dólares, por ejemplo). Solo tiene efecto si el emisor fue designado agente de percepción del IGTF; en ese caso el documento percibe el 3% aparte. Zelle se asume divisa aunque no se marque; el efectivo nunca se adivina. Agregado sin romper la v1: quien no lo mande, sigue igual. |

Respuesta cuando quedó sellada (`200`):

```json
{
  "sellada": true,
  "serie": "A",
  "numero": 133,
  "numeroControl": "00-00000133",
  "sello": "https://sello.example/v/abc...",
  "transmision": "enviado"
}
```

| Campo | Qué es |
| --- | --- |
| `serie`, `numero` | Numeración interna de la Estación, consecutiva y sin saltos. |
| `numeroControl` | El que asigna la imprenta digital. Si la transmisión quedó pendiente, viene vacío y se completa al transmitir. |
| `sello` | Dirección de validación para el QR de la factura, si la imprenta la devolvió. |
| `transmision` | `enviado`, `pendiente` (sin internet: se reintenta sola, en orden, sin duplicar) o `rechazado`. |

Respuestas de error:

| Código | Cuerpo | Cuándo |
| --- | --- | --- |
| `401` | `{ "sellada": false, "problema": "Falta la llave de la estacion" }` | Sin llave o llave ajena. |
| `503` | `{ "sellada": false, "problema": "La estacion fiscal no esta configurada" }` | La Estación aún no tiene emisor configurado. No insistir en bucle: avisar al operador. |
| `400` | `{ "sellada": false, "problema": "..." }` | La venta no se pudo sellar (por ejemplo, el reloj del equipo está corrido). La venta NO se pierde: queda en el buzón y se sella al resolverse la causa. |

## Reglas del contrato (las que protegen al integrador)

1. **Idempotencia por `origen_id`**: reintentar una entrega jamás duplica documentos.
   Ante cualquier duda de red (timeout, respuesta perdida), reintentar es seguro.
2. **El cobro nunca espera**: la Estación sella local y transmite a la imprenta por su
   cuenta. La respuesta no depende del internet del local.
3. **Las correcciones van por nota de crédito**, nunca editando ni borrando: se hacen
   desde la pantalla de la Estación. Esta puerta no anula documentos en v1.
4. **Montos en USD** con `tasa_bs` para el contravalor: el documento fiscal sale en
   bolívares a esa tasa.
5. La forma "cruda" que también acepta la puerta (`{tipo, origen_id, venta: {...}}`) es
   **interna de Gustito y el Cerebrito** y no forma parte del contrato estable: un
   integrador de terceros usa exactamente lo descrito en esta guía.

## La vía de carpeta (para sistemas sin API)

Si el sistema administrativo no puede hacer un POST pero sí escribir archivos, la
Estación ofrece el mismo contrato por carpeta. Junto al programa vive la carpeta
`intercambio/` con cuatro subcarpetas:

| Carpeta | Quién escribe | Qué contiene |
| --- | --- | --- |
| `entrada/` | el sistema del negocio | Un archivo `.json` por venta, con exactamente el mismo cuerpo de `POST /api/venta`. |
| `respuestas/` | la Estación | Por cada archivo, `<nombre>.respuesta.json` con la misma respuesta de la API (`sellada`, `serie`, `numero`, `numeroControl`...). |
| `procesadas/` | la Estación | Los archivos que quedaron sellados, movidos tal cual. Nada se borra. |
| `rechazadas/` | la Estación | Los archivos que no se pudieron aceptar (JSON dañado, sin `origen_id`, sin renglones), cada uno con su porqué en `respuestas/`. |

Reglas de la vía de carpeta:

1. **Escribir completo y de último renombrar a `.json`** es lo más seguro. De todos
   modos la Estación le da un respiro a todo archivo recién tocado antes de leerlo,
   para no agarrar mitades.
2. La carpeta se revisa **cada pocos segundos**; los archivos se procesan en orden de
   nombre.
3. **Duplicar es seguro**: el mismo `origen_id` en dos archivos devuelve el documento
   ya sellado, no crea otro.
4. Si la Estación no puede sellar en ese momento (sin configurar, reloj del equipo
   corrido), los archivos **esperan en `entrada/`** y se procesan solos al resolverse:
   ninguna venta se pierde.
5. `entrada/` vacía = todo al día.

## Ejemplo mínimo (curl)

```bash
curl -s -X POST http://127.0.0.1:8739/api/venta \
  -H "Authorization: Bearer LA_LLAVE" \
  -H "Content-Type: application/json" \
  -d '{"origen_id":"PRUEBA-1","canal":"caja","cliente":{"nombre":"Consumidor Final"},"renglones":[{"nombre":"Cafe","cantidad":1,"precio_usd":1.5,"alicuota":16}],"tasa_bs":null}'
```

Con la Estación en modo práctica (imprenta de práctica), este circuito completo se puede
probar sin contrato con ninguna imprenta y sin gastar documentos reales.
