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:
- Listar órdenes con su número, fecha, status, estado de envío y total (con filtros).
- Validar una orden: comprueba su consistencia aritmética (líneas, subtotal,
IVA, total) y, sobre todo, el descuento por volumen de familia contra los tabuladores de
precios de
/clientes, detectando si te cobraron de más.
¿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?
- Detectar sobrecobros automáticamente: el sistema ya identificó líneas cobradas con menos descuento del que correspondía por volumen.
- Auditoría reproducible: cada validación deja un reporte JSON y, opcionalmente, un PDF con marca.
- Integración con otros sistemas: al ser una API REST + OpenAPI, se conecta a ERPs, dashboards, alertas (Slack/email) o flujos con AI sin tocar el portal manualmente.
Base URL
https://scrapper.blifecompany.mx
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
/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_KEYSi falta o es inválida, la respuesta es 401 Unauthorized. Solo la documentación
(/api, /app) y /openapi.json son públicos.
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
- Fechas en formato ISO
YYYY-MM-DD; los montos son numéricos (MXN, sin formato). - El
statusse deriva del estado de envío:Pendiente,En proceso,Entregado. - Los errores devuelven
{ "error": "...", "message": "..." }con el código HTTP correspondiente.
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".
{
"status": "ok"
}Listar órdenes
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ámetro | Tipo | Descripción |
|---|---|---|
status | string | Status derivado. Lista con coma: Pendiente,En proceso |
shippingStatus | string | Substring del estado de envío original |
from / to | date | Rango de fecha (ISO, incluyente) |
minTotal / maxTotal | number | Rango de total |
q | string | Búsqueda por número de orden |
sort | enum | date | total | orderNumber (default date) |
order | enum | asc | desc (default desc) |
limit / offset | integer | Paginación de la respuesta |
curl "https://scrapper.blifecompany.mx/orders?status=En%20proceso&minTotal=5000000&sort=total&order=desc&limit=10" \
-H "x-api-key: TU_API_KEY"{
"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
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) | Tipo | Descripción |
|---|---|---|
orderNumber requerido | string | Número (S10995) o id numérico (7192) |
dryRun opcional | boolean | Si true, no persiste el reporte JSON ni envía alertas |
pdf opcional | boolean | Si true, genera el PDF en reports/ |
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 }'{
"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 } ] }
]
}
}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
| Campo | Tipo | Ejemplo |
|---|---|---|
orderNumber | string | S10994 |
date | date | 2026-06-12 |
placedAt | string | 12/06/2026 16:09:05 |
shippingStatus | string | Sin entregar |
status | string | Pendiente |
total | number | 5507088.4 |
Códigos de estado
| 200 | OK (listado, o validación correcta — devuelve el reporte) |
| 422 | Validación con discrepancias (devuelve el reporte) |
| 400 | Petición inválida (falta orderNumber) |
| 401 | API key faltante o inválida |
| 404 | Orden no encontrada o sesión inválida |
| 502 | Fallo de scraping no recuperable |
{
"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.jsonImpó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 <...>.
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.
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).
https://scrapper.blifecompany.mx/openapi.json como contrato.