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.
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:
- Entender la forma real del API de BIND ERP antes de tener el API key.
- Probar el cliente tipado contra un mock que replica los headers, el formato OData y el rate-limit observable de BIND.
- Cuando Pedro entregue las credenciales reales, cambiar dos variables de entorno y apuntar el mismo código a
https://api.bind.com.mxsin 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. Los hallazgos clave ya están reconciliados en el cliente (
types.real.ts,BindClientconidStyle). El mock conserva el schema aproximado previo — sigue siendo útil para CI/demo, pero el contrato real es el detypes.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
POSTreal 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+.
# 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}):
$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.tsaproximados); contra producción algunos escenarios no aplican (p. ej.Customers→ 404 real). Para explorar el API real usa el script de validación: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): bloqueaPOST/PUT/PATCH/DELETEen 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
addActivityestá aislado para que cuando se autorice escritura, sea fácil envolverlo conidempotency_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:
- API key generado desde Perfil → Integraciones en la cuenta de Balam, idealmente con permisos solo lectura primero.
- Subscription Key si el plan de Balam la requiere (algunos planes en Azure API Management la piden además del Bearer).
- Confirmación del plan contratado — para saber si 20K req/día aplica o si está reducido.
- Schema exacto del recurso
Invoices(especialmente nombres de camposUUID,Folio,Status,Balance). Una llamada de ejemplo con una factura real anonimizada serviría:GET /api/Invoices?$top=1.- ¿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.mxy 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 ensrc/client/types.real.tsy en 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 conidStyle, 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.