Files
balam/bind-api-sandbox/README.md
T
JohannVelazquez 633d05e330 Reorganiza repo como fuente de la verdad + propuesta v1.0 con emisión de facturas
- Estructura nueva: README maestro, bitacora/ (REGISTRO, PENDIENTES, plantillas),
  propuesta/, fuentes/; material superado a _archivado/
- Propuesta v1.0: MVP BIND-first con emisión asistida MXN/USD (dry-run +
  confirmación, timbra PAC de BIND), 112-136 h / $67,200-$81,600 + IVA,
  stack .NET 10 + EF Core + Angular 21 + PostgreSQL 17 sobre Azure
- Bitácora: historial de correos + 2 llamadas (incl. revisión 4-jun) y pendientes
- Prototipo y diagrama actualizados a v1.0; precios de Azure verificados
- Archivo ajeno (proyecto EOS) retirado del repo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:57:27 -06:00

8.4 KiB
Raw Blame History

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.

TL;DR del API de BIND (lo que descubrí del discovery)

Tema Hallazgo
Base URL https://api.bind.com.mx
Estilo REST con sintaxis OData v3 (filtros $filter, $top, $skip, $orderby, $count, IDs como guid'...')
Auth Dos headers: Authorization: Bearer <API_KEY> + Ocp-Apim-Subscription-Key: <SUBSCRIPTION_KEY> (este último cuando aplica)
Origen del API key Cuenta BIND → Perfil de usuario → pestaña Integraciones
Rate limit 20,000 peticiones / día (confirmado por Noe, 25-may-2026)
Sandbox oficial No existe. BIND recomienda Postman contra producción → razón #1 de este sandbox
Portal dev developers.bind.com.mx (login requerido para ver schemas detallados)
PAC para CFDI Integrado dentro del propio BIND — la plataforma de Balam no toca el SAT, solo orquesta
Recursos confirmados Activities, Customers, Products (y según el discovery doc: Invoices, Payments, Quotes muy probables)

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+.

# 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 (cuando llegue el API key)

Solo cambiar variables de entorno — el código no se modifica:

$env:BIND_BASE_URL = "https://api.bind.com.mx"
$env:BIND_API_KEY = "<key del perfil de usuario de Balam>"
$env:BIND_SUBSCRIPTION_KEY = "<si aplica>"
$env:BIND_MODE = "read-only"   # mantenlo así hasta tener autorización para escribir
npm run demo

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 de Invoice, Customer, etc. es una aproximación — está modelado a partir de la doc pública y del flujo que necesita Balam, no del SDK oficial. Cuando salga el primer GET real contra producción, hay que reconciliar nombres de campos (especialmente capitalización y campos opcionales).
  • El mock acepta cualquier Bearer, solo valida que exista. No es un servidor de auth real, es un placeholder.
  • El parser de OData del mock solo cubre lo que el cliente genera (eq, ne, gt, lt, ge, le, and, or, paréntesis). No soporta contains, startswith, funciones, lambdas. Suficiente para el MVP.
  • No reproduce la lógica de $expand — si BIND lo soporta para traer Lines o Customer embebidos, hay que extender.

Cuando alguno de estos límites se vuelva una piedra en el zapato, se extiende. Hoy es deliberadamente mínimo.