Dale a tu agente de IA la capacidad de hacer el súper en Jüsto (supermercado online de México).
Una herramienta de lĂnea de comandos, pensada para que un asistente (WhatsApp, Telegram, etc.)
busque productos, arme el carrito y genere una orden real — todo conversacional, todo por API.
Warning
Proyecto no oficial. No está afiliado ni respaldado por Jüsto. Usalo con tu propia cuenta, bajo tu responsabilidad, y respetando los Términos y Condiciones de Jüsto. Existe para automatización personal y como documentación de cómo el sitio se comunica con su backend.
Imaginá decirle a tu asistente por WhatsApp "che, pedà plátano, leche y 3 cocas a Jüsto" y que lo haga solo: busca los productos, arma el carrito, elige el horario, te muestra el total y —cuando le das el OK— genera la orden real (contra entrega).
Esta herramienta es ese "brazo": una CLI simple con salida JSON y comandos de una sola lĂnea, diseñada para que un agente de IA (OpenClaw, un bot propio, lo que sea) la ejecute por vos. Resuelve la parte difĂcil — la API de checkout de JĂĽsto no es trivial (ver 🔍 La API por dentro).
- 🧾 Salida JSON en todos los comandos → fácil de parsear por un LLM.
- 📏 Comandos de una lĂnea (Ătems separados por coma) → no se rompen en runtimes que rechazan multi-lĂnea.
- ⏳ Modo background (
order-bg/order-wait) → esquiva los timeouts cortos de RPC al ejecutar en un nodo remoto. - 🔒 Separa armar de comprar:
orderdeja el carritoSTAGED(no gasta);confirmrecién genera la orden → el agente puede pedir confirmación humana antes de pagar. - 🌎 Alias AR→MX (
palta→aguacate,choclo→elote…) → listas en cualquier dialecto.
👉 Ejemplo de skill listo para usar: examples/openclaw-skill.md
(un SKILL.md de OpenClaw con el candado de confirmaciĂłn incluido).
Python 3, solo librerĂa estándar — sin dependencias.
git clone https://github.com/zekear/justo-mx-api
cd justo-mx-api
chmod +x justo
ln -s "$PWD/justo" ~/.local/bin/justo # opcional: ponelo en el PATHImportant
Cloudflare bloquea IPs de datacenter. Jüsto está detrás de Cloudflare, que rechaza IPs de
servidores cloud (403 / challenge de JS). Corré esto desde una IP residencial (tu compu, un
server en casa, un nodo doméstico). Si tu agente vive en un VPS, ejecutá justo en un nodo
residencial vĂa exec remoto.
Credenciales por variables de entorno:
export JUSTO_EMAIL="vos@ejemplo.com"
export JUSTO_PASSWORD="tu-password"
export JUSTO_ZIP="11590" # tu código postal de entrega (default 11590)…o apuntá JUSTO_CREDENTIALS a un archivo estilo .env (ver .env.example).
Nunca subas tus credenciales reales al repo.
La dirección de entrega se toma de la dirección por defecto de tu cuenta (la gestionás en la app
de JĂĽsto). El pago es contra entrega (MANUAL): no se cobra ni se tokeniza ninguna tarjeta.
# buscar productos + precios
justo search "plátano"
# armar un carrito REAL (aparece en tu app de JĂĽsto), con slot + contra-entrega — NO compra todavĂa
justo order "Plátano x6, Manzana Roja x10, Leche 1L x4, Pan Bimbo x2, Aguacate Hass x4"order devuelve un resumen STAGED para revisar:
{
"status": "STAGED",
"checkout_token": "…",
"total": 710.86,
"currency": "MXN",
"address": "Casa",
"slot": "Recomendado — Mañana, 11:00AM - 01:00PM",
"slot_options": [
{"n": 1, "label": "Recomendado — Mañana, 11:00AM - 01:00PM"},
{"n": 2, "label": "Entrega gratis — Miércoles, 07:00AM - 09:00AM"}
],
"items": [ … ]
}# cambiar el horario de entrega (usá el n de slot_options)
justo set-slot <checkout_token> 2
# generar la orden real (contra entrega) — cancelable después desde la app de Jüsto
justo confirm <checkout_token>
# -> {"status":"ORDER_PLACED","order_number":"N0111…","total":710.86, …}- Cantidad como sufijo por Ătem:
x4,Ă—4o:4. Sin sufijo = 1. - Separá los Ătems por coma en un solo argumento.
- El match es difuso (palabra completa en el nombre; si no, el primer resultado con precio). Usá un tĂ©rmino especĂfico (marca/tamaño) cuando importe el producto exacto.
job=$(justo order-bg "…lista larga…" | jq -r .job)
justo order-wait "$job" # consultá hasta que esté el resumen STAGEDEl checkout de Jüsto no es un flujo Saleor común: maneja un cart-service propio y un
checkout-bff. El checkoutComplete público de Saleor está bloqueado para México
(Creation order for country MX not allowed), asĂ que un carrito armado directo contra Saleor nunca
se puede completar. El camino real se reverseĂł leyendo el bundle Next.js del sitio.
Dos hosts, ambos detrás de Cloudflare:
| Host | Rol |
|---|---|
api.justo.mx/graphql/ |
Saleor GraphQL — búsqueda de productos + login (tokenCreate) |
api.justo.cloud |
cart-service — crear carrito, agregar Ătems, consolidar |
client-api-gateway.justo.mx |
estado del checkout, horarios, pago, completar la orden |
Todas las llamadas autenticadas mandan: Authorization: JWT <token>, más
x-justo-country: MX, x-zip-code: <cp>, x-justo-platform: web-desktop, x-origin: cart_view.
El flujo completo:
- Login — Saleor
tokenCreate(email, password)→ JWT.me.id(base64User:<n>) →userIdnumérico. - Crear carrito —
POST api.justo.cloud/cart-service/v3/cartbody{userId, postalCode, items:[]}→{cartId}. - Agregar Ătems —
POST api.justo.cloud/cart-service/v3/cart/{cartId}/itemsbody{items:[{id:"<productId>", quantity}]}. ElproductIdes el id numérico del producto en Saleor (decodificá base64 eliddel nodo →Product:<n>). Elidva como string. - Consolidar —
POST api.justo.cloud/cart-service/v2/cart/{cartId}/consolidate?userId={userId}→{context:{checkoutId (base64), checkoutToken (uuid), postalCode}}. (es v2 y enapi.justo.cloud— v3 / el gateway dan 404/500.) - Estado del checkout —
POST client-api-gateway.justo.mx/v2/checkoutbody{checkoutId, zipCode}→ componentes de UI: dirección (default de la cuenta), horarios (slots-v2, cada uno concontext:{shippingMethodId (base64), deliveryDate}), métodos de pago, totales. - Setear horario —
POST client-api-gateway.justo.mx/v2/checkout/slotsbody{checkoutId, shippingMethodId, deliveryDate}. - Setear pago —
POST client-api-gateway.justo.mx/v2/checkout/paymentMethodsbody{checkoutId, gateway:"MANUAL"}(contra entrega). Obligatorio: sin método de pago, completar se rechaza. - Completar —
POST client-api-gateway.justo.mx/v1/checkout/completebody{cartId, checkoutId, zipcode, additionalData:{}}. La respuesta siempre muestra un modal de "completado" — el resultado real está entracking:orderCompleted(conorder_number,order_id) si salió, oorderRejected {reason}si falló (reason: 10= falta método de pago).
Note
Gotchas:
- Las Ăłrdenes creadas asĂ viven en el backend del cart/checkout (ids hex tipo
6a30d…), no enme.ordersde Saleor (solo histórico viejo). Se listan/cancelan en la app de Jüsto. checkoutCompletede Saleor está duramente bloqueado para MX; ignoralo.- Un carrito armado directo contra Saleor (salteando cart-service/consolidate) nunca lo
reconoce el Core → siempre
reason 10. El camino por cart-service es obligatorio.
Issues y PRs bienvenidos. Si la API de JĂĽsto cambia, los endpoints se reverifican leyendo los chunks
de https://justo.mx/_next/static/chunks/ (el _buildManifest.js mapea rutas → chunks).