B Life · Scrapper Analytics API

API REST que automatiza la auditoría de cobros de B Life sobre el portal de Zumalabs: lista las órdenes y valida que cada una esté correctamente cobrada.

¿Qué hace?

Expone como servicio dos capacidades sobre tu cuenta de Zumalabs:

¿Por qué funciona así?

El portal de Zumalabs no ofrece una API pública, por lo que el servicio inicia sesión y obtiene los datos de forma confiable: prefiere las APIs internas del portal (p. ej. get_product_costs para los tabuladores) y, cuando no existen, cae a un scraping calibrado del DOM con Playwright. La lógica de negocio clave —agrupar variantes de un mismo producto en una familia, sumar su volumen y exigir el mejor descuento del escalón alcanzado— se aplica sobre esos datos para reproducir exactamente la política comercial.

¿Qué logramos?

Base URL

https://scrapper.blifecompany.mx

Todas las respuestas son application/json (excepto esta página y el PDF). Las peticiones se procesan de forma secuencial: el servicio comparte una única sesión de navegador.

▶ Usar aquí

¿No quieres escribir código? Abre la consola web: pega tu API key (se pide como contraseña y se queda solo en tu navegador), lista órdenes con filtros y valida cualquier orden viendo el desglose por producto con la diferencia cobrada — todo desde el navegador.

Abrir la consola → https://scrapper.blifecompany.mx/app

La consola usa tus mismos endpoints (/orders, /validate) con la API key que escribes; nada se guarda en el servidor. Ideal para el equipo de operaciones/finanzas.

Autenticación

Los endpoints de datos (GET /orders, POST /validate) y el GET /health están protegidos con una API key. Envíala en cada petición mediante uno de estos headers:

x-api-key: TU_API_KEY
# — o —
Authorization: Bearer TU_API_KEY

Si falta o es inválida, la respuesta es 401 Unauthorized. Solo la documentación (/api, /app) y /openapi.json son públicos.

La key se configura en el servidor con la variable de entorno API_KEY. Trátala como un secreto y envíala siempre sobre HTTPS. El servicio, a su vez, se autentica contra Zumalabs con ZUMA_EMAIL / ZUMA_PASSWORD (nunca expuestas al cliente).

Convenciones

Health

GET/health

Verifica que el servicio está arriba y que tu API key es válida (no abre el navegador ni scrapea). Requiere el header x-api-key; si falta o es inválida responde 401. La consola lo usa en su botón "Probar conexión".

Respuesta 200
{
  "status": "ok"
}

Listar órdenes

GET/orders

Devuelve las órdenes del portal (con paginación interna ya resuelta) y permite filtrar, ordenar y paginar la respuesta. Todos los parámetros son opcionales y combinables.

ParámetroTipoDescripción
statusstringStatus derivado. Lista con coma: Pendiente,En proceso
shippingStatusstringSubstring del estado de envío original
from / todateRango de fecha (ISO, incluyente)
minTotal / maxTotalnumberRango de total
qstringBúsqueda por número de orden
sortenumdate | total | orderNumber (default date)
orderenumasc | desc (default desc)
limit / offsetintegerPaginación de la respuesta
Ejemplo · cURL
curl "https://scrapper.blifecompany.mx/orders?status=En%20proceso&minTotal=5000000&sort=total&order=desc&limit=10" \
  -H "x-api-key: TU_API_KEY"
Respuesta 200
{
  "total": 80,
  "matched": 25,
  "count": 10,
  "filters": { "status": ["En proceso"] },
  "orders": [
    {
      "orderNumber": "S10996",
      "date": "2026-06-12",
      "placedAt": "12/06/2026 16:08:16",
      "shippingStatus": "Parcialmente entregado",
      "status": "En proceso",
      "total": 7921674.8
    }
  ]
}

Validar orden

POST/validate

Inicia sesión, resuelve la orden por su número, la scrapea y valida: (1) consistencia aritmética y (2) descuento por volumen de familia contra los tabuladores de /clientes. Devuelve el reporte completo y, opcionalmente, genera un PDF con la marca B Life.

Campo (body JSON)TipoDescripción
orderNumber requeridostringNúmero (S10995) o id numérico (7192)
dryRun opcionalbooleanSi true, no persiste el reporte JSON ni envía alertas
pdf opcionalbooleanSi true, genera el PDF en reports/
Ejemplo · cURL
curl -X POST https://scrapper.blifecompany.mx/validate \
  -H "content-type: application/json" \
  -H "x-api-key: TU_API_KEY" \
  -d '{ "orderNumber": "S10995", "pdf": true }'
Respuesta · 200 correcto / 422 con discrepancias (extracto)
{
  "summary": {
    "billingCorrect": true,
    "familyPricingCorrect": false,
    "hasDifferences": true,
    "differenceCount": 25
  },
  "billing": { "orderName": "S10995", "correct": true, "checks": [ ... ] },
  "familyPricing": {
    "correct": false,
    "families": [
      { "family": "b life: pure berberine", "totalQuantity": 13230,
        "bracketMinQty": 10000, "expectedSavings": 0.36, "passed": false,
        "members": [ { "sku": "BLNBERBE180", "actualSavings": 0.22,
          "expectedUnitPrice": 160.64, "passed": false } ] }
    ]
  }
}
El código HTTP es 200 si todo está correcto y 422 si hay discrepancias (cobro incorrecto o diferencias contra la referencia). En ambos casos el cuerpo es el reporte.

Modelos

OrderSummary

CampoTipoEjemplo
orderNumberstringS10994
datedate2026-06-12
placedAtstring12/06/2026 16:09:05
shippingStatusstringSin entregar
statusstringPendiente
totalnumber5507088.4

Códigos de estado

200OK (listado, o validación correcta — devuelve el reporte)
422Validación con discrepancias (devuelve el reporte)
400Petición inválida (falta orderNumber)
401API key faltante o inválida
404Orden no encontrada o sesión inválida
502Fallo de scraping no recuperable
Formato de error
{
  "error": "ExtractionError",
  "message": "No se encontró la orden \"S99999\" en /my/orders",
  "orderNumber": "S99999"
}

OpenAPI

Especificación OpenAPI 3.0 disponible para generar clientes/SDKs e integrar en otros sistemas:

GET https://scrapper.blifecompany.mx/openapi.json

Impórtala en Postman, Insomnia, Swagger UI o un generador de SDK (openapi-generator).

✦ Implementa esta API con AI · Claude

¿Quieres integrar esta API en tu software sin leer toda la doc a mano? Copia el siguiente prompt y pégalo en Claude (o Claude Code). Incluye todo el contexto necesario para que genere el cliente/integración en el lenguaje que uses. Ajusta las dos líneas marcadas con <...>.

Prompt para Claude
Eres un ingeniero senior. Integra la API "B Life · Scrapper Analytics API" en MI proyecto y
adáptala a sus necesidades. Detecta primero el TIPO de integración y sigue la rama que aplique:
- FRONTEND (React/Vue/Next/Angular/Flutter/…): crea un apartado "Validación de cobros".
- BACKEND/API (Node/Express/Next API/FastAPI/.NET/…): expón un cliente + endpoints y entrega un README.
- AGENTE/AI: genera el cliente tipado y documenta cómo invocarlo.
Mi stack es: <DESCRIBE LENGUAJE/FRAMEWORK FRONTEND Y/O BACKEND, BASE DE DATOS, AUTH Y ROLES>.

PRIMERO: descarga y razona sobre el OpenAPI (fuente de verdad de /orders y /health):
https://scrapper.blifecompany.mx/openapi.json
OJO: el cuerpo del reporte de /validate NO está en el OpenAPI; su esquema real está más abajo.

═══════════════════════════════ CONTRATO DE LA API ═══════════════════════════════
- Base URL: https://scrapper.blifecompany.mx
- Auth: API key en header `x-api-key: <API_KEY>` (o `Authorization: Bearer <API_KEY>`) en
  /orders, /validate y /health. Si falta → 401. Solo la doc (/api, /app) y /openapi.json son públicos.
- Respuestas JSON. Errores: { "error": string, "message": string } con HTTP 400/401/404/422/502.
- REGLA CRÍTICA: en /validate, HTTP 200 = correcto y **HTTP 422 = "validado con discrepancias"**
  (NO es fallo; trae el MISMO reporte). Solo 400/401/404/502 y errores de red son fallos.

ENDPOINTS
1) GET /health → { "status": "ok" }   (requiere key; sirve para validar la clave sin scrapear)
2) GET /orders  (requiere key) → { total, matched, count, filters, orders[] }
   Query (todos opcionales y combinables): status (Pendiente|En proceso|Entregado, lista con
   coma), shippingStatus, from, to (YYYY-MM-DD), minTotal, maxTotal, q (busca por número),
   sort (date|total|orderNumber), order (asc|desc), limit, offset.
   orders[]: { orderNumber, date(YYYY-MM-DD), placedAt(texto), shippingStatus, status, total }
3) POST /validate  (requiere key)  body: { orderNumber: string, dryRun?: bool, pdf?: bool }
   → "Report" (200 correcto / 422 con discrepancias). ESQUEMA REAL del Report:
   {
     generatedAt: string(ISO), dryRun: bool,
     order: { id, name, subtotal, tax, taxRate(0..1), discount, total, date,
              lines: [ { sku, description, quantity, unitPrice, taxRate, subtotal } ] },
     billing: { orderId, orderName, correct: bool,
                checks: [ { name, expected, actual, delta, passed, message } ],
                explanation: string[] },
     familyPricing: {
        correct: bool, available: bool,
        families: [ { family, totalQuantity, bracketMinQty, expectedSavings(0..1), passed,
                      overcharge,                  // cobro de más total de la familia (MXN)
                      members: [ { sku, name, quantity, basePrice, actualUnitPrice,
                                   actualSavings(0..1), expectedUnitPrice,
                                   difference,      // (cobrado−esperado)×cantidad; + = cobro de más
                                   passed, note } ] } ],
        unmatched: [ { sku, name, reason } ],     // líneas sin tabulador de volumen
        explanation: string[] },
     comparison: { totalScraped, totalReference, matched,
                   differences: [ { type, id, message, reference?, scraped? } ] },
                   // type ∈ { MISSING_IN_SCRAPED, MISSING_IN_REFERENCE, AMOUNT_MISMATCH, … }
     summary: { hasDifferences: bool, billingCorrect: bool, familyPricingCorrect: bool,
                differenceCount: number },
     pricing: [ { id, name, family, tiers: [ { minQty, price, savings(0..1) } ] } ]
                   // catálogo de precios de REFERENCIA (no es por orden; suele ser grande)
   }
   Notas: los porcentajes (expectedSavings, actualSavings, savings) son fracciones 0..1 → x100 para %.
   Importes en MXN. `pricing` puede pesar >150 KB: NO lo muestres crudo en UI.

═══════════════════ ENTREGABLE COMÚN A TODAS LAS INTEGRACIONES ═══════════════════
Cliente tipado en <LENGUAJE>:
- health(), listOrders(filtros), validateOrder(orderNumber, { dryRun?, pdf? }).
- Tipos para Order, OrdersResponse y el Report completo (todos los bloques opcionales y con
  índice permisivo, porque pueden variar por orden).
- Reintentos con backoff exponencial + jitter SOLO en transitorios (red y 429/500/502/503/504),
  respetando Retry-After; NUNCA reintentar 400/401/404/422. Timeout por intento (~30s).
- Error tipado (status, code=campo "error", body). validateOrder devuelve
  { httpStatus, hasDiscrepancies, report } y trata 422 como éxito-con-discrepancias.
- Config por entorno: SCRAPPER_API_KEY (req.), SCRAPPER_BASE_URL (default la de prod),
  esquema de auth x-api-key|bearer.
SEGURIDAD (obligatoria): la API key vive SOLO en el servidor. El navegador/cliente NUNCA la ve;
siempre pasa por un proxy/endpoint propio. Restringe ese proxy a los roles que correspondan: <ROLES>.

═══════════════════ RAMA A — FRONTEND: apartado "Validación de cobros" ═══════════════════
1) Endpoints proxy en mi backend (con la auth de mi app): GET <api>/orders y POST <api>/validate.
2) Pantalla/sección nueva "Validación de cobros" (ruta sugerida /cobros) que:
   - Filtra por estado (Todos/Pendiente/En proceso/Entregado), número de orden, rango de fechas.
   - Lista las órdenes en tabla: Orden, Fecha, Estado (badge), Envío, Total (MXN), acción "Validar".
   - Al validar, muestra el resultado (correcto / con discrepancias) y, expandible:
       • Resumen de la orden (subtotal, IVA, total, nº de líneas).
       • Estado de Aritmética / Descuento por familia / Comparación.
       • Descuento por familia: tabla de familias y, para las que fallan, el desglose por SKU.
       • Comparación: conteos + lista de diferencias.
     (Para el desglose COMPLETO tramo-a-tramo/SKU, usa el PDF.)
   - Botón "Descargar PDF" que abre el endpoint de PDF (ver más abajo) autenticado por sesión.
   - Maneja carga/errores por orden (un fallo no rompe la tabla). NO muestres `pricing` crudo.
3) Genera el PDF en el backend (ver "ESPECIFICACIÓN DEL PDF").

═══════════════════ RAMA B — BACKEND/API: cliente + README ═══════════════════
1) Implementa el cliente tipado y, si aplica, endpoints que reexpongan listOrders/validateOrder
   con la auth de mi API.
2) Entrega un README.md que explique, para MI API:
   - Variables de entorno y cómo obtener/guardar la API key (solo servidor).
   - Cómo llamar a cada endpoint (ejemplos de request/response reales, incluido el caso 422).
   - El significado de los campos del Report (billing, familyPricing.families/members, comparison).
   - Manejo de errores y reintentos; semántica 200 vs 422.
   - Ejemplo de ejecución end-to-end (listar → validar → generar PDF).
3) Genera el PDF según "ESPECIFICACIÓN DEL PDF".

═══════════════════ ESPECIFICACIÓN DEL PDF (formato oficial) ═══════════════════
Genera el PDF EN EL SERVIDOR a partir del JSON del Report (no dependas del flag pdf:true de la
API). Endpoint sugerido: GET <api>/validate/pdf?orderNumber=...  → stream application/pdf
(`Content-Disposition: attachment`), mismo gating de rol. Layout (tamaño Carta), en ESTE orden:
  1. Encabezado: logo a la izquierda; a la derecha el título "VALIDACIÓN DE COBROS" y
     "Generado: <generatedAt> · DRY-RUN" (si dryRun).
  2. Banner oscuro con esquinas redondeadas: "Orden <order.name> id <order.id>" (número en color
     de marca) y, a la derecha, "FECHA DE LA ORDEN" + <order.date>.
  3. Chips de estado (verde=ok / rojo=revisar):
     - "Aritmética: Correcto/Revisar"  ← billing.correct
     - "Descuento familia: Correcto/Revisar"  ← familyPricing.correct
     - "Comparación: Correcto/Revisar"  ← comparison.differences.length === 0
  4. RESUMEN DE LA ORDEN: tarjetas SUBTOTAL, IVA (<taxRate*100>%), TOTAL, LÍNEAS(=order.lines.length).
  5. VALIDACIÓN ARITMÉTICA: si billing.correct → "La orden está bien cobrada a nivel documento.
     (<#passed>/<#checks> cálculos OK)"; si no, lista los checks con passed=false (name/expected/
     actual/delta/message).
  6. DESCUENTO POR VOLUMEN DE FAMILIA: mensaje según familyPricing.correct y una tabla:
     FAMILIA | CANT. TOTAL | ESCALÓN(bracketMinQty) | DESC. ESP.(expectedSavings%) |
     DIFERENCIA(overcharge; "—" si ≤0) | ESTADO.
     Las familias con passed=false se resaltan (fondo/barra roja, ESTADO "Cobro de más") y debajo
     muestran una SUBTABLA con TODOS sus members (no solo los que fallan), resaltando en rojo los
     de passed=false:
       SKU | PRODUCTO(name, nombre completo) | CANT.(quantity) | BASE(basePrice) |
       COBRADO(actualUnitPrice) | % COB.(actualSavings%) | % ESP.(expectedSavings% de la familia) |
       PRECIO ESP.(expectedUnitPrice) | DIFERENCIA(difference, con signo) | ESTADO.
     Al final: "Líneas sin tabulador (no validadas): <unmatched[].sku unidos por coma>".
  7. COMPARACIÓN CONTRA REFERENCIA: "Scrapeados: X · Referencia: Y · Coinciden: Z" y tabla
     TIPO | ID | DETALLE(message) con la columna DETALLE multilínea (alto variable).
  8. Pie en cada página: "B Life® · Validación automatizada de cobros" (izq.) y
     "Datos extraídos del dashboard de Zumalabs · Confidencial" (der.).
  Formato: importes en es-MX/MXN; porcentajes = fracción×100.
  CUIDADO CON LA FUENTE: si tu librería de PDF usa una fuente estándar con codificación WinAnsi
  (p. ej. pdfkit/Helvetica), caracteres como "Δ ✓ ✗ ✅ ❌ ≥ ▸" NO existen y salen corruptos.
  Solución: embebe una fuente Unicode (TTF) o sanitiza el texto de la API (los mensajes de
  comparación traen "Δ", p. ej. "(Δ=234060)") reemplazándolos por ASCII (Δ→"dif", ✓→"OK", etc.).
  Los acentos del español (á é í ó ú ñ) sí están en WinAnsi.

═══════════════════ CONFIGURACIÓN Y ENTREGA ═══════════════════
- Variables: SCRAPPER_API_KEY (req.), SCRAPPER_BASE_URL (opcional), <+ auth/roles de mi proyecto>.
- Entrega: cliente tipado, proxy/endpoints, (frontend) la pantalla "Validación de cobros" con
  descarga de PDF / (backend) el README, y el generador de PDF; + instrucciones de configuración
  y un ejemplo de ejecución (listar órdenes "En proceso", validar S10995 y descargar su PDF).
- Asegúrate de que compile/lint sin errores y de no exponer la API key al cliente.

(Opcional) Automatización: un cron/job horario que liste "En proceso", valide cada una
(concurrencia acotada) y alerte (Slack/email) las órdenes con summary.familyPricingCorrect=false.

Prompt específico · Frontend "Validación de cobros"

Si ya integraste la API y solo quieres pintar (o actualizar) el desglose por producto con la diferencia entre lo cobrado y lo que debió cobrarse, usa este prompt. Lee directamente los campos del Report (familyPricing.families[].overcharge y …members[].difference), así no recalcula nada y siempre coincide con el PDF oficial.

Prompt para Claude · Frontend
Eres un ingeniero senior frontend. En MI proyecto, en la sección "Validación de cobros",
actualiza el desglose de "Descuento por volumen de familia" para que muestre lo mismo que el
PDF oficial, con los datos del scrapper actualizados (sin cachés viejos).

Mi stack frontend es: <FRAMEWORK + librería de UI/tablas>.
Mi proxy/backend expone GET <api>/orders y POST <api>/validate con la auth de mi app
(la API key vive SOLO en el servidor; el navegador NUNCA la ve).

REGLAS DE LA API:
- POST /validate devuelve un "Report" JSON. HTTP 200 = correcto; HTTP 422 = "validado con
  discrepancias" (NO es error: trae el MISMO reporte). 400/401/404/502/red = fallo.
- Pide SIEMPRE datos frescos (no-store / cache-busting); no reuses respuestas cacheadas.
- NO muestres report.pricing crudo (>150 KB).

EL REPORTE YA TRAE LA DIFERENCIA CALCULADA (no la recalcules; léela tal cual):
report.familyPricing.families[] = {
  family, totalQuantity, bracketMinQty,
  expectedSavings,      // fracción 0..1 → ×100 para %
  overcharge,           // cobro de más total de la familia, en MXN (>0 = revisar)
  passed,
  members: [ {
     sku, name,         // SKU y NOMBRE COMPLETO del producto
     quantity, basePrice, actualUnitPrice,
     actualSavings,     // fracción 0..1 → ×100 (descuento que LE ASIGNARON)
     expectedUnitPrice, // precio que debió cobrarse
     difference,        // (cobrado−esperado)×cantidad en MXN; + = cobro de más, − = a favor
     passed
  } ]
}
report.familyPricing.unmatched[] = { sku, name, reason }  // líneas sin tabulador

RENDERIZA (igual que el PDF):
1) Tabla por grupo (familias). Columnas:
   Familia | Cant. total | Escalón (bracketMinQty) | Desc. esperado (expectedSavings×100 %) |
   Diferencia (overcharge; "—" si ≤0, resáltala en rojo si >0) | Estado (✅ / ❌ cobro de más).
2) Desglose por producto (expandible, TODAS las variantes). Columnas EN ESTE ORDEN:
   SKU | Producto (name, celda nueva — NO quitar el SKU) | Cant. | Base | Cobrado |
   % cobrado (actualSavings×100) | % esperado (family.expectedSavings×100) | Precio esperado |
   Diferencia (difference, con signo: "+$…" rojo si >0, "−$…" si <0) | Estado (✅/❌).
   Resalta en rojo las filas con passed=false.
3) Si hay unmatched: "Líneas sin tabulador (no validadas): <skus unidos por coma>".

FORMATO:
- Importes con Intl.NumberFormat('es-MX', { style:'currency', currency:'MXN' }).
- Porcentajes = fracción × 100.
- Trata 200 y 422 como éxito (ambos pintan el desglose); errores por orden sin romper la tabla.
- Conserva el botón "Descargar PDF" que apunta al endpoint de PDF del backend.

ENTREGABLE: el componente/tabla tipado, que compile/lint sin errores, leyendo datos frescos.
Verifícalo con la orden S10995, familia "b life: pure berberine" (debe mostrar el cobro de más).
Integración nativa con Claude (tool use / MCP). El mismo prompt sirve para pedirle a Claude que envuelva estos endpoints como herramientas (tool use) o como un pequeño servidor MCP, de modo que un agente pueda listar y validar órdenes por sí mismo. Apunta a https://scrapper.blifecompany.mx/openapi.json como contrato.