Middleware que sincroniza datos contables de 2 sistemas operacionales costarricenses hacia QuickBooks Online. Stateless, multi-organizacion, multi-moneda (CRC/USD).

Arquitectura
APIs externas
api.admindual.com
api.flexmarketingcr.com
Middleware
Routes + Controllers
OrganizationQBService
Security + Rate Limit
QuickBooks Online
Customers
Invoices / Bills
CreditMemos / Payments
API REST - 22 endpoints

Interactivo en /api-docs — Seleccionar org con ?org=DUAL o header X-Organization

TagEndpoints
Sistema GET /health-check, /status/:org, /sync-logs
Organizaciones GET /organizations, /connect/:org, /callback/:org, /test-connection/:org, /test-all-connections
Clientes GETPOST /customers   GETDEL /customers/:id
Facturas GETPOST /invoices   GETPUT /invoices/:id   POST /invoices/simulate, /invoices/create-and-update   GET /customers/:id/invoices
Notas de Credito GETPOST /creditmemos   POST /payments
Impuestos GET /taxrates, /taxcodes
Reportes GET /reports/billing
APIs Externas

Autenticacion via header apiKey con la clave de cada organizacion.

Dual Servicios — api.admindual.com

MetodoEndpointDescripcion
GET/api/healthHealth check de la API
GET/api/facturas/reportefacturado?desde=&hasta=Facturas de venta por rango de fechas

DUAL es solo lectura — no soporta marcar facturas como sincronizadas.

Flex Marketing — api.flexmarketingcr.com

MetodoEndpointDescripcion
GET/api/healthHealth check de la API
GET/facturasFacturas de venta pendientes
PATCH/facturas/:idMarcar sincronizada (en_qb: 1)
GET/factura-comprasFacturas de compra pendientes
PATCH/factura-compras/:idMarcar sincronizada
GET/masters-ncsNotas de credito pendientes
PATCH/masters-ncs/:idMarcar sincronizada

Payload de actualizacion (PATCH)

{ "en_qb": 1, "quickbooks_id": "123" }
Flujo de sincronizacion
1. Consultar API externa  →  obtener facturas donde en_qb = 0
2. Por cada factura:
   a. Determinar moneda (CRC / USD)
   b. Buscar o crear cliente en QB (DisplayName + sufijo moneda, max 41 chars)
   c. Mapear: consecutivo → DocNumber (max 21 chars), montos, impuestos
   d. Crear Invoice / Bill / CreditMemo en QuickBooks
3. Actualizar factura en sistema externo  →  en_qb = 1
4. Generar resumen JSON en logs/{org}/sync_summary_*.json
5. Si hay errores  →  enviar notificacion email via SendGrid
6. Dashboard muestra ultimos sync logs via /sync-logs

Scripts de sincronizacion

# DUAL - Sync completo dia por dia (JSON + email)
./scripts/dual-servicios/sincronizar-por-dias-auto.sh
./scripts/dual-servicios/sincronizar-por-dias-auto.sh 2026-01-01 2026-01-31

# FLEX - Sync completo 3 pasos (JSON + email)
./scripts/flex-marketing/sincronizar-todo.sh

# Scripts individuales
node scripts/dual-servicios/facturas-organizacion.js sincronizar
node scripts/flex-marketing/facturas-organizacion.js ventas
node scripts/flex-marketing/factura-compras-organizacion.js sincronizar
node scripts/flex-marketing/credit-notes-organizacion.js procesar

Clases base (scripts/core/)

ClaseProposito
BaseApiServiceHTTP client con retry exponencial (429, timeouts)
BaseInvoiceSyncSync facturas: normalizar cliente, buscar/crear en QB, construir lineas
BaseBillSyncSync compras: asignacion IA de cuentas contables, gestion proveedores
BaseCreditNoteSyncSync NC: crear CreditMemo, buscar factura, aplicar Payment
Impuestos Costa Rica
TasaTipoTaxCode QB
13%IVA General18
13%IVA Servicios17
4%IVA Reducida-
2%IVA Minima-
1%IVA Especial-
0%Exento / Exportacion12
Seguridad
ComponenteDetalle
TokensAES-256-GCM en .organization-secrets (permisos 0o600)
Access TokenExpira 1 hora, auto-refresh en 401 (1 retry)
Refresh Token100 dias, re-auth manual si expira
API KeysValidadas via middleware validateApiKey
Rate LimitingBackoff exponencial: 1s, 2s, 4s, 8s en 429
HeadersHelmet + CORS configurado
Autenticacion

El middleware usa OAuth 2.0 con QuickBooks Online via el SDK oficial de Intuit.

SDK y portal de desarrollo

RecursoDetalle
SDKintuit-oauth v4 (Node.js)
Portal de desarrollodeveloper.intuit.com
Administrar appsDeveloper Dashboard
API ExplorerAPI Reference
Scopes requeridoscom.intuit.quickbooks.accounting

Tokens

AspectoDetalle
ProtocoloOAuth 2.0 (Authorization Code Flow)
Access TokenExpira cada hora, se renueva automaticamente
Refresh TokenValido por 100 dias, requiere re-auth manual si expira
AlmacenamientoTokens encriptados con AES-256-GCM

Flujo de renovacion

1. Usuario inicia conexion desde el dashboard
2. Redirige a QuickBooks para autorizar la app
3. QuickBooks devuelve un authorization code via callback
4. El middleware intercambia el code por access + refresh token
5. Tokens se almacenan encriptados localmente

Cuando renovar manualmente

La renovacion es automatica en la mayoria de los casos. Solo se requiere intervencion manual cuando:

EscenarioAccion
Refresh token expirado (100+ dias inactivo)Re-autorizar desde el dashboard
Credenciales corruptas o eliminadasRe-autorizar desde el dashboard
Cambio de permisos en la app QBRe-autorizar para aceptar nuevos scopes
Errores comunes
ErrorCausaSolucion
401Token expiradoAuto-refresh. Si falla → /connect/:org
403 (3100)Sin permisosSe simula respuesta exitosa
429Rate limitBackoff exponencial automatico
DocNumber dupConsecutivo existeSe registra error y continua
CurrencyRefMoneda inmutableCrear nuevo cliente con sufijo correcto
Guias detalladas