Files
JohannVelazquez 9ccfbe262e Validación técnica de la API de BIND contra producción (GET-only)
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.
2026-07-06 17:17:27 -06:00

183 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.