9ccfbe262e
Plan A del saldo CONFIRMADO (Total − Payments − CreditNotes, misma fila); flujo MVP sostenible 100% en lectura; PDF del CFDI por API. Hallazgo duro: sin recurso de pagos individuales ni REP (escalar a Pedro). Cliente extendido (Quotes, Currencies, Locations, pdf/xml, GET por ID estilo REST) y tipos reales en types.real.ts. Detalle en bind-api-sandbox/VALIDACION-API.md; reporte crudo y token gitignoreados.
183 lines
9.9 KiB
Markdown
183 lines
9.9 KiB
Markdown
# BIND ERP API · sandbox local
|
||
|
||
Sandbox para validar la integración del MVP **BIND-first** de Balam **sin tocar producción**.
|
||
Está pensado para que tú (Johann), Pedro (Balam) o un futuro dev puedan:
|
||
|
||
1. Entender la forma real del API de BIND ERP antes de tener el API key.
|
||
2. Probar el cliente tipado contra un mock que replica los headers, el formato OData y el rate-limit observable de BIND.
|
||
3. Cuando Pedro entregue las credenciales reales, **cambiar dos variables de entorno** y apuntar el mismo código a `https://api.bind.com.mx` sin reescribir nada.
|
||
|
||
> ✅ **ACTUALIZACIÓN 6-jul-2026 — validación contra el API real EJECUTADA.**
|
||
> Con el token entregado por Pedro (usuario de Arturo Rosas) se corrió la validación
|
||
> técnica de solo lectura (117 peticiones GET). Resultados completos, tabla de
|
||
> cobertura de endpoints, inventario de campos y veredicto del saldo en
|
||
> **[VALIDACION-API.md](VALIDACION-API.md)**. Los hallazgos clave ya están
|
||
> reconciliados en el cliente (`types.real.ts`, `BindClient` con `idStyle`).
|
||
> El mock conserva el schema aproximado previo — sigue siendo útil para CI/demo,
|
||
> pero el contrato real es el de `types.real.ts`.
|
||
|
||
---
|
||
|
||
## TL;DR del API de BIND (reconciliado con la validación del 6-jul)
|
||
|
||
| Tema | Hallazgo |
|
||
|---|---|
|
||
| **Base URL** | `https://api.bind.com.mx` ✅ confirmado |
|
||
| **Estilo** | REST con filtros OData v3 (`$filter`, `$top`≤100, `$skip`, `$orderby`). ⚠️ `$select` NO funciona (500); conteo total inaccesible; **GET por ID es REST `/{id}`, no `(guid'...')`** |
|
||
| **Auth** | ✅ **Solo** `Authorization: Bearer <token>` — la `Ocp-Apim-Subscription-Key` no se requiere. ⚠️ Token inválido → **500** (no 401) |
|
||
| **Origen del API key** | Cuenta BIND → Perfil de usuario → pestaña *Integraciones* (el de Balam salió del usuario de **Arturo Rosas**) |
|
||
| **Rate limit** | **20,000 peticiones / día** — no observable en headers; llevar contador local |
|
||
| **Sandbox oficial** | **No existe.** Este mock local sigue siendo la única red de pruebas sin efecto fiscal |
|
||
| **PAC para CFDI** | Integrado en BIND; el PDF del CFDI se descarga vía `GET /api/Invoices/{id}/pdf` ✅ |
|
||
| **Recursos confirmados (200)** | `Invoices`, **`Clients`** (no Customers), **`Quotes`**, `Products`, `Currencies`, `Warehouses`, `Locations`, `Activities`, `PriceLists`, `Orders`, `Providers`, `Banks`, `BankAccounts`, `Users` |
|
||
| **Recursos que NO existen** | **`Payments`** (≈20 nombres probados → 404 — el acumulado pagado viene DENTRO de cada factura), `Customers`, `Series`, `CreditNotes`, `Companies`… |
|
||
| **Saldo por factura** | ✅ **Plan A operativo:** `Total − Payments − CreditNotes` en la misma fila de `/api/Invoices` (verificado aritméticamente) |
|
||
|
||
### Por qué un sandbox propio y no Postman
|
||
|
||
- BIND solo tiene producción → cualquier `POST` real toca facturas reales con consecuencias fiscales.
|
||
- El contrato exacto está detrás de login → necesitamos un lugar donde ir **acumulando lo que aprendemos** del API real conforme Pedro nos dé acceso.
|
||
- Tener el contrato en código (TypeScript + tipos) hace que **el motor de cobranza del MVP se pueda probar con CI** sin depender de la red.
|
||
- Cuando llegue Belvo / BUK en Fase 2 esta misma estructura sirve como plantilla.
|
||
|
||
---
|
||
|
||
## Cómo correrlo
|
||
|
||
Requiere Node 20+.
|
||
|
||
```powershell
|
||
# 1) Instalar deps
|
||
cd bind-api-sandbox
|
||
npm install
|
||
|
||
# 2) Levantar el mock en una terminal
|
||
npm run mock
|
||
# -> [bind-mock] escuchando en http://localhost:4010
|
||
|
||
# 3) Correr la demo end-to-end en otra terminal
|
||
npm run demo
|
||
```
|
||
|
||
La demo ejecuta 5 escenarios alineados al MVP:
|
||
|
||
| # | Escenario | Qué demuestra |
|
||
|---|---|---|
|
||
| 1 | Listar clientes activos | Cómo se construye la query OData base del dashboard |
|
||
| 2 | Facturas vencidas (`Status eq 'overdue' and DueDate lt ...`) | El motor de cobranza |
|
||
| 3 | CxC agregada por cliente (MXN) | KPI directivo del dashboard |
|
||
| 4 | Factura USD a cliente extranjero | Regla sin-IVA + TC fijado al emitir |
|
||
| 5 | Intento de `POST /Activities` en modo read-only | El guardrail que evita escribir a BIND prod por accidente |
|
||
|
||
Output esperado (verificado):
|
||
|
||
```
|
||
── 2. Facturas vencidas — motor de cobranza
|
||
┌─────────┬──────────┬────────────┬──────────────┬──────────┬─────────┐
|
||
│ Folio │ Cliente │ DueDate │ Currency │ Balance │
|
||
│ 'A-0003' │ '22222222' │ '2026-04-01' │ 'MXN' │ 20880 │
|
||
│ 'A-0005' │ '11111111' │ '2026-05-20' │ 'MXN' │ 62640 │
|
||
|
||
── 5. Intento de escritura en read-only (debe BLOQUEARSE)
|
||
✅ Guardrail OK: Read-only mode bloqueó POST /api/Activities. Cambia BIND_MODE=write...
|
||
|
||
Stats del cliente
|
||
{ requestsToday: 6, quota: 20000, remaining: 19994, mode: 'read-only' }
|
||
```
|
||
|
||
### Apuntar a producción (token real)
|
||
|
||
Solo cambiar variables de entorno — el código no se modifica (el cliente detecta
|
||
el estilo de ID; contra el API real usa `/{id}`):
|
||
|
||
```powershell
|
||
$env:BIND_BASE_URL = "https://api.bind.com.mx"
|
||
$env:BIND_API_TOKEN = "<token del perfil de Arturo — NUNCA versionarlo>"
|
||
$env:BIND_MODE = "read-only" # mantenlo así hasta tener autorización para escribir
|
||
npm run demo
|
||
```
|
||
|
||
> ⚠️ La demo fue escrita contra el schema del mock (`types.ts` aproximados);
|
||
> contra producción algunos escenarios no aplican (p. ej. `Customers` → 404 real).
|
||
> Para explorar el API real usa el script de validación:
|
||
>
|
||
> ```powershell
|
||
> npm run validate:real # solo GET, presupuesto de peticiones, reporte sanitizado
|
||
> ```
|
||
|
||
---
|
||
|
||
## Mapeo a la arquitectura de Balam
|
||
|
||
Este sandbox es el prototipo de lo que en el repo principal vivirá en `packages/integrations/bind/`:
|
||
|
||
```
|
||
balam/
|
||
└── packages/
|
||
└── integrations/
|
||
└── bind/
|
||
├── BindClient.ts ← este sandbox lo prototipa
|
||
├── types.ts ← este sandbox lo prototipa
|
||
├── odata.ts ← este sandbox lo prototipa
|
||
└── README.md
|
||
apps/
|
||
└── worker/
|
||
└── src/
|
||
└── modules/
|
||
└── bind-sync/ ← consume BindClient, escribe a Postgres,
|
||
respeta tenant_id + outbox pattern
|
||
```
|
||
|
||
El cliente está pensado para encajar con los principios del proyecto (ver `01 - ARQUITECTURA-TECNICA.md`):
|
||
|
||
- **Modo seguro por default** (`read-only`): bloquea `POST/PUT/PATCH/DELETE` en código. Cumple §1 "La plataforma no escribe a BIND en MVP".
|
||
- **Dry-run** opcional: imprime el request sin enviarlo (cumple §1 punto 2 "Modo dry-run disponible en cualquier acción con efecto externo").
|
||
- **Idempotencia preparada**: el método `addActivity` está aislado para que cuando se autorice escritura, sea fácil envolverlo con `idempotency_key`.
|
||
- **Quota awareness**: el cliente lleva un contador local de requests del día y avisa cuando se acerca al límite de 20K.
|
||
- **Retries con backoff** en 429 y 5xx, respetando `Retry-After`.
|
||
|
||
---
|
||
|
||
## Decisiones de discovery que este sandbox **acelera**
|
||
|
||
Estos son los puntos del `03_Anexo_Tecnico_Integraciones_Discovery_Balam.docx` y del `04_Checklist_Accesos_Datos_Dependencias_Balam.docx` que dejan de estar "pendientes de validar" en cuanto se ejecuta esta prueba con un API key real:
|
||
|
||
| Item del discovery | Cómo lo cierra este sandbox |
|
||
|---|---|
|
||
| Tipo de autenticación, headers requeridos | Ya implementado en `BindClient`: dos headers, listos para producción |
|
||
| Estructura de URLs por recurso | Confirmada (`/api/{Recurso}` + OData) y probada en mock |
|
||
| Operaciones de lectura: clientes, facturas, pagos | Demo las ejerce todas. Una vez con API key, basta correr `BIND_BASE_URL=https://api.bind.com.mx npm run demo` |
|
||
| Filtros y paginación (`$filter`, `$top`, `$skip`) | Validados contra mock con la misma sintaxis que documenta BIND |
|
||
| Estrategia de pruebas sin sandbox | **Esta es la respuesta**: mock local + cliente tipado + modos read-only / dry-run / write |
|
||
| Rate limits | El cliente cuenta requests; el mock simula `?simulate=throttle` para probar el backoff |
|
||
|
||
---
|
||
|
||
## Pendientes para cerrar con Pedro (sugerencia de mail)
|
||
|
||
> Pedro, para destrabar la integración con BIND necesitamos:
|
||
>
|
||
> 1. **API key** generado desde *Perfil → Integraciones* en la cuenta de Balam, idealmente con permisos **solo lectura** primero.
|
||
> 2. **Subscription Key** si el plan de Balam la requiere (algunos planes en Azure API Management la piden además del Bearer).
|
||
> 3. Confirmación del **plan contratado** — para saber si 20K req/día aplica o si está reducido.
|
||
> 4. **Schema exacto** del recurso `Invoices` (especialmente nombres de campos `UUID`, `Folio`, `Status`, `Balance`). Una llamada de ejemplo con una factura real anonimizada serviría: `GET /api/Invoices?$top=1`.
|
||
> 5. ¿Existe endpoint para descargar **XML/PDF del CFDI**? Por la doc parece que sí, pero falta confirmar la ruta exacta.
|
||
>
|
||
> Con (1)–(2) podemos correr el sandbox apuntando a `https://api.bind.com.mx` y validar lo demás de un jalón sin necesidad de otra llamada.
|
||
|
||
---
|
||
|
||
## Limitaciones honestas de este sandbox
|
||
|
||
- **El schema del MOCK (`types.ts`) sigue siendo la aproximación previa** — el contrato
|
||
confirmado contra producción vive en **`src/client/types.real.ts`** y en
|
||
[VALIDACION-API.md](VALIDACION-API.md). Pendiente (opcional): regenerar el seed del
|
||
mock con los shapes reales para que la demo ejercite el contrato confirmado.
|
||
- **El mock acepta cualquier Bearer**, solo valida que exista. No es un servidor de auth real.
|
||
- **El mock implementa el GET por ID estilo OData `(guid'...')`** — el API real usa `/{id}`;
|
||
el cliente lo resuelve con `idStyle`, el mock quedó intacto.
|
||
- **El parser de OData del mock solo cubre lo que el cliente genera** (`eq, ne, gt, lt, ge, le, and, or`, paréntesis).
|
||
- **No reproduce la lógica de `$expand`** — y el API real ni siquiera soporta `$select`, así que el payload completo es la norma.
|
||
|
||
Cuando alguno de estos límites se vuelva una piedra en el zapato, se extiende. Hoy es deliberadamente mínimo.
|