Complemento de APIs del Agente Vendedor Transaccional (que trae el código completo).
Este documento explica para qué sirve cada endpoint y cómo se usa: método, ruta, parámetros y forma de la respuesta. No repite el código, solo el comportamiento.
Última revisión contra el código: 2026-10-12 (rama
fixes). Si existe versión v2 de un
endpoint, la v1 ya no se usa; aquí sólo se documenta la v2 (ver
Endpoints obsoletos).
Todas las rutas cuelgan de /apiCrm/externalAccess (en esta guía se omite ese prefijo).
Autenticación (aplica a todos los endpoints)
Todas las rutas exigen un header:
api_access_token: <id del documento externalAccess en Mongo>
validateToken resuelve ese token a un crm (conexión Firebird/MSSQL del cliente) y a
externalAccess.alias (el usuario Terra con el que se opera). Errores posibles:
"El token proporcionado no es válido", "El token está deshabilitado",
"Se ha superado la vigencia del token" (vigente hasta el final del día de valid_until),
"La conexión a la base de datos no se pudo recuperar".
En los endpoints GET de imagen/galería, en vez del header se puede mandar ?token=... en
el query string (útil para usarlos directo como <img src="..."> en HTML).
Formato de errores
- Errores de validación y de negocio → HTTP 200 con:
{ "error": true, "message": "texto del error" } - Excepciones inesperadas (error de SQL, conexión, etc.) → también HTTP 200 con
{ "error": true, "message": "<stack completo del error>" }. - Excepción:
createDoctoresponde HTTP 500 cuando el ERP rechaza el documento o hay
timeout del envío (ver ese endpoint).
El bot siempre debe revisar error === true; no basta con el código HTTP.
Índice
- Carrito y pedido
- Catálogo
- Clientes y direcciones
- Consultas para el contacto (saldo, existencias, tracking)
- PDF de cotización
- Multimedia
- Utilidades / depuración
- Endpoints obsoletos (no usar)
- Puntos a cuidar
Carrito y pedido
El “carrito” es una cotización de Terra (DCDOCTOS_PDV, TIPO_DOCTO = 'C'). Su id es el
DOCTO_PV_ID. Sólo se puede modificar mientras su estatus sea N (Normal/pendiente).
Flujo típico:
getIdCart → (createCart o cart/:id/add|update|remove) → getCart → cart/:id/observations
→ createDocto (envía al ERP) → statusCart / getPDF.
Obtener carritos abiertos de un cliente
POST /accessToken/api/Daiko/v2/getIdCart/:id
A pesar del nombre, :id es el CLIENTE_ID, no un id de carrito. Busca ese cliente
(o prospecto si no existe como cliente) y devuelve sus datos junto con los carritos que
tenga abiertos (cotizaciones con ESTATUS = 'N').
- Path:
id— CLIENTE_ID (o PROSPECTO_ID). - Body: sin parámetros.
- Respuesta 200:
Si no tiene carritos abiertos:{ "NOMBRE_CLIENTE": "...", "MONEDA_ID": 1, "CARRITOS_ID": [316, 317], "CARRITOS": [{ "CARRITO_ID": 316, "FOLIO": "COT001234" }] }"CARRITOS_ID": nully"CARRITOS": []. - Errores:
"No existe cliente. Proporciona uno válido"si no se encuentra ni como
cliente ni como prospecto.
Crear carrito (nueva cotización)
POST /accessToken/api/Daiko/v2/createCart/:id
Crea una cotización nueva (tipo C, estatus N) para el cliente :id, con los productos
indicados.
- Path:
id— CLIENTE_ID (también funciona con PROSPECTO_ID). - Body:
Campo Tipo Requerido Notas almacen_idint Sí vendedor_idint Sí moneda_idint Sí productosarray Sí [{ articulo_id, unidades }].unidadesdefault 1 si se omite.dir_consig_idint No Dirección de envío (de getAddresses/createAddress). Si no se manda, se usa la dirección de facturación/principal del cliente. - Valida que todos los
articulo_idexistan antes de crear nada. - Lo que calcula el servidor (no lo manda Daiko):
- Precio de cada partida: lista de precios del cliente, precio del ERP (SAE/MyBusiness) y
políticas de precio. - Descuento de cada partida: si el cliente tiene descuento propio (
DESCUENTO_CLIENTE),
ese aplica a todo; si no, la política de descuento por artículo (GET_DSCTO_ART). Si
Daiko manda undescuento, se ignora. - El descuento del cliente también se guarda en el encabezado (en SAE se envía como
descuento total del pedido). - Impuestos según el artículo y la configuración del cliente.
- Folio con la serie del usuario del token; condición de pago predeterminada; uso de CFDI
del cliente; fecha de hoy.
- Precio de cada partida: lista de precios del cliente, precio del ERP (SAE/MyBusiness) y
- Respuesta 200:
{ "error": false, "created": true, "cliente_id": "316", "carrito_creado": 1026408, "folio": "COT001234", "createOrders": [{ "message": "Artículo 12846 agregado al carrito exitosamente." }] } - Errores:
"No se encontró el artículo X","El Folio: X ya existe.", errores de
configuración de folios del usuario, parámetros faltantes.
Obtener carrito
POST /accessToken/api/Daiko/v2/getCart/:id
- Path:
id— DOCTO_PV_ID (id del carrito/cotización). - Body: sin parámetros.
- Respuesta 200:
{ "importeCarrito": { "ID_CARRITO": 1026408, "ESTATUS": "N", "TIPO_DOCTO": "C", "IMPORTE_NETO": 1000, "TOTAL_IMPUESTOS": 160, "TOTAL_RETENCIONES": 0, "TOTAL_CARRITO": 1160, "NOMBRE_CLIENTE": "...", "FOLIO": "COT001234", "FECHA": "...", "VENDEDOR_ID": 12, "DSCTO_EXTRA": 0, "MONEDA_ID": 1, "CLIENTE_ID": 316, "OBSERVACIONES_ENC": "", "OBSERVACIONES": "" }, "Carrito": [ { "ARTICULO_ID": 12846, "NOMBRE": "...", "UNIDADES": 2, "PRECIO_UNITARIO": 500, "PCTJE_IMPUESTO": 16, "MONTO_IMPUESTO": 160, "COSTO_ENVIO": 0, "ESTATUS": "A", "DESCUENTO": 0, "POSICION": 1, "PARTIDA": "1", "PRECIO_UNITARIO_IMPTO": 580, "PRECIOTOTALARTICULOS": 1160 } ] }PCTJE_IMPUESTOya viene neto de retenciones (impuesto − retención).DESCUENTOes porcentaje;PRECIOTOTALARTICULOS= (precio × unidades − descuento) + impuesto.TOTAL_CARRITO=IMPORTE_NETO + TOTAL_IMPUESTOS − TOTAL_RETENCIONES.
- Errores:
"Carrito no existe."
Agregar / actualizar / eliminar artículo del carrito
POST /accessToken/api/Daiko/v2/cart/:id/:action
-
Path:
id— DOCTO_PV_ID del carrito.action— uno deadd,update,remove. -
Body (una partida):
Campo Requerido Notas articulo_idSí Numérico (no como texto, ver Puntos a cuidar). unidadesSí, salvo en removeNumérico. En updatedebe ser > 0.También acepta varias partidas a la vez:
{ "productos": [ {...}, {...} ] }.
La partida se localiza porarticulo_id; no hace falta mandarDOCTO_PV_DET_ID. -
Comportamiento por acción:
add: si el artículo ya está en el carrito, suma las unidades a las que ya tenía; si no
está, lo agrega al final.update: reemplaza las unidades de la partida. Error si el artículo no está en el carrito.remove: quita la partida y recorre las posiciones. Error si el artículo no está en el carrito.- Si tras la operación
unidades <= 0, la partida se quita. - En
add/updatese recalculan precio y descuento de las partidas tocadas, con las mismas
reglas quecreateCart; después se recalculan los totales del carrito.
-
Respuesta 200:
{ "error": false, "created": true, "message": "Carrito actualizado." } -
Errores:
"Parámetro 'x' no válido."(acción inválida),
"Artículo id X no existe en el carrito.","El parámetro unidades no es válido.",
"Carrito no existe.","La cotización se encuentra <estatus>."(cualquier estatus distinto deN).
Actualizar observaciones del carrito
POST /accessToken/api/Daiko/v2/cart/:id/observations
Guarda el texto en OBSERVACIONES_ENC (observaciones del encabezado). No valida el estatus del carrito.
- Path:
id— DOCTO_PV_ID. - Body:
{ "observaciones": "texto libre" }(requerido; puede ser cadena vacía para borrar). - Respuesta 200:
{ "success": true, "message": "Observaciones actualizadas correctamente" } - Errores:
"No existe el carrito indicado."
Estatus del carrito
POST /accessToken/api/Daiko/v2/statusCart/:id
Consulta de solo lectura.
- Path:
id— DOCTO_PV_ID. - Respuesta 200:
{ "status": "OK", "data": [{ "TIPO_DOCTO": "Cotizacion", "ESTATUS_ORDEN": "Pendiente", "CLIENTE_ID": 316, "DOCTO_PV_ID": 1026408, "ESTATUS_PAGO": "" }] }dataes un arreglo de un elemento.TIPO_DOCTOse traduce a texto
(Cotizacion/Pedido/Remision/Facturado);ESTATUS_ORDENusa el catálogo
Utils.statusQuotationsen forma femenina (ej. “Pendiente”, “Ganada”, “Cancelada”). - Errores:
"No existe Órden."
Cambiar estatus del carrito
POST /accessToken/api/Daiko/v2/setStatusCart/:id
- Path:
id— DOCTO_PV_ID. - Body:
{ "estatus": "N" }— debe ser un código válido deUtils.statusQuotations. - No valida el estatus actual (puede reabrir un carrito cancelado o ganado).
- Respuesta 200:
{ "status": "OK", "data": "1026408" } - Errores:
"No existe Órden.","El estatus no es válido."
Eliminar carrito
POST /accessToken/api/Daiko/v2/deleteCart/:id
En realidad cancela (no borra el registro): pone ESTATUS = 'C' y registra usuario/fecha
de cancelación.
- Path:
id— DOCTO_PV_ID. - Respuesta 200:
{ "status": "OK", "data": "1026408" } - Errores:
"Carrito no existe.","El Carrito se encuentra ganado."(ya se convirtió en
pedido),"El Carrito se encuentra cancelado."(ya estaba cancelado).
Generar pedido desde una cotización
POST /accessToken/api/Daiko/v2/createDocto/:id
El endpoint más pesado: toma la cotización :id y la envía al ERP. El documento destino
depende de MODO_USO: Microsip → Pedido (P), SAE → Pedido, ContPAQ → documento 2 (Pedido),
MyBusiness POS → Pedido (PE).
-
Path:
id— DOCTO_PV_ID de la cotización ya armada. -
Body:
Campo Requerido Notas cliente_idSí Con celular, valida que el contacto tenga el permisoGENERAR_PEDIDOS.celularSí Celular del contacto que pide el pedido ( CONTACTO_PERMISOS).confirm_pricesNo Mandar trueen el reintento si el usuario aceptó los cambios de precio.confirm_componentsNo Mandar trueen el reintento si el usuario aceptó componentes de kit con precio $0. -
Validaciones, en orden:
- Permiso
GENERAR_PEDIDOSdel contacto. - Que el cliente exista. Si es prospecto y el CRM es MyBusiness POS (
MODO_USO = 4) con el
parámetroCONVERTIR_PROSPECTO_PEDIDO = 'S', lo convierte a cliente automáticamente. - Cliente no suspendido, sin saldo vencido y dentro de su límite de crédito.
- SAE/ContPAQ: uso de CFDI y forma de pago SAT (de la cotización o, si faltan, del cliente).
- Artículos y componentes catalogados.
- Existencias, sólo si el usuario del token tiene la preferencia
SURTIR_SOLO_EXISTENCIAS. - Precios vigentes, sólo si tiene la preferencia
VALIDAR_PRECIOS. - Componentes de kits con precio 0.
- ContPAQ: características de los artículos.
- Permiso
-
Respuesta 200 (éxito): el resultado que regresa el ERP (incluye
FOLIO_DESTINO,
TIPO_DOCTO_DESTINO,COMPLETAR, entre otros). -
Respuestas de “necesita confirmación” (HTTP 200; no son error definitivo, hay que reintentar):
{ "error": true, "confirm_prices": "Los siguientes articulos cambiaron de precio:\n...\n¿Desea actualizar los precios y continuar con el envío?" }{ "error": true, "confirm_components": "Las siguientes partidas contienen componentes con precio 0:\n...\n¿Deseas continuar con el envío?" }
En ambos casos se reintenta el mismo POST agregando el flag correspondiente en
true. Si
se aceptaconfirm_prices, el pedido se envía con los precios actuales. -
Errores (HTTP 200,
{ error: true, message }): permiso no autorizado, cliente/prospecto no
encontrado, cliente suspendido/con saldo vencido/sin crédito, falta uso de CFDI o forma de pago
SAT, artículo no catalogado, sin existencias, timeout de preprocesamiento (60 s). -
Errores del ERP (HTTP 500): si el ERP rechaza el documento o no responde en 90 s, la
respuesta es HTTP 500 con el texto del error como cuerpo (no viene en{ error, message }). -
Si el bot corta la conexión por su propio timeout, el servidor deja de procesar; conviene
consultarstatusCartantes de reintentar para no duplicar el pedido.
Catálogo
Buscar/listar productos
POST /accessToken/api/Daiko/v1/getProducts
También sirven getProductSearch/:keyword, getProductByCategory/:category,
getProductByLabels/ y getProductByKey: es la misma función y cambia según lo que venga en
la ruta o el body. No hay versión v2.
- Body:
Campo Requerido Uso cliente_idSí Para calcular precio, descuento e impuestos del cliente. moneda_idSí Moneda en la que se calcula el precio. claveNo Busca por clave de artículo (contiene). Si viene, se ignoran los demás filtros. query(o path:keyword)No Búsqueda por nombre que empieza con el texto. Si no encuentra nada, reintenta con “contiene”. categoria(o path:category)No Filtra por línea de artículo (contiene). etiquetasNo Array de textos; busca en los valores de los clasificadores del artículo. Requiere que la base tenga clasificadores ( CAPTURA_CLASIFICADORES).filtrosNo { marca, tipo, medida, caracteristicas, compatibilidad }, cada uno array de textos. Se combinan con AND sobre el nombre. Si no hay resultados, se van quitando uno a uno.existenciaNo true→ sólo artículos con existencia > 0 enalmacen_id. Defaultfalse.almacen_idSí si existencia: trueAlmacén donde se revisa la existencia. per_pageNo Activa la paginación (tamaño de página). Va en la raíz del body. current_pageNo Página (default 1). Va en la raíz del body. - Con
queryy sincategoria/etiquetas, el mismo texto también se busca como línea de
artículo y como etiquetas (separado por espacios); los resultados se combinan con OR. - Sólo devuelve artículos activos (
ESTATUS = 'A'), ordenados por nombre. - Respuesta 200 (sin paginación):
{ "productos": [ { "ARTICULO_ID": 12846, "NOMBRE": "...", "PRECIO": 500, "PCTJE_IMPUESTO": 16, "PCTJE_RET": 0, "MONTO_IMPUESTO": 80, "IMPUESTO_ID": 3, "CLAVE": "ABC-1", "UNIDAD_VENTA": "PZA", "NOMBRE_PRECIO": "Lista 1", "ES_JUEGO": "N", "ES_ALMACENABLE": "S", "ROL": "N", "ES_CATALOGADO": "S" } ] } - Respuesta 200 (con
per_page):{ "meta": { "count", "count1", "current_page", "page_size" }, "data": [...] }
(count= total que cumple el filtro;count1= los que vienen en esta página). - Errores:
"La base de datos no permite búsqueda por etiquetas"(sólo etiquetas y la base
no tiene clasificadores),"El parámetro almacen_id no ha sido indicado.".
Obtener un producto por id
POST /accessToken/api/Daiko/v1/getProduct/:id
- Path:
id— ARTICULO_ID. - Body:
cliente_idymoneda_idrequeridos (mismo uso que arriba). - Respuesta 200: el artículo con
NOTAS_VENTASy precio/impuesto ya calculados para ese
cliente y moneda (no filtra por estatus). - Errores:
"No existe el producto. Proporciona uno válido".
Listado de categorías
POST /accessToken/api/Daiko/v1/getListCategory
- Body: opcional
per_page/current_pageen la raíz para paginar. - Respuesta 200:
{ "Categorias": [ { "LINEA_ARTICULO_ID", "NOMBRE" }, ... ] }. Con paginación,
Categoriastrae{ meta, data }.
Clientes y direcciones
Obtener clientes
POST /accessToken/api/Daiko/v1/getCustomers
- Body (ambos opcionales):
cliente_id— busca ese cliente exacto, y también un prospecto con ese mismo id.name— búsqueda por nombre (contiene, sin distinguir mayúsculas), en clientes y prospectos.- Si no se manda ninguno de los dos, regresa TODOS los clientes (sin prospectos). Usar
con cuidado en catálogos grandes.
- Respuesta 200: arreglo
[{ "CLIENTE_ID", "NOMBRE", "MONEDA_ID", "VENDEDOR_ID", "CLI_PRO": "C" }, ...]
(los prospectos vienen con"CLI_PRO": "P").
Estatus de un cliente
POST /accessToken/api/Daiko/v1/getstatusCustomers/:id
- Path:
id— CLIENTE_ID. - Respuesta 200:
{ "clienteDetalle": [{ "CLIENTE_ID", "NOMBRE", "MONEDA_ID", "COND_PAGO_ID", "TIPO_CLIENTE_ID", "VENDEDOR_ID", "NOTAS" }] } - Errores:
"Cliente no existe."(sólo busca clientes, no prospectos).
Crear prospecto
POST /accessToken/api/Daiko/v1/createProspecto
- Body:
Campo Requerido nombreSí nombre_contactoSí celularSí — queda como celular del contacto principal telefono_oficinaNo claveNo — clave de cliente, si se quiere fijar una en particular permisos_defaultNo — si es truey el CRM está enMODO_USO = 4, asigna al contacto todos los permisos (GENERAR_PEDIDOS,VER_EXISTENCIAS,VER_SALDO,ESTATUS_PEDIDO) - Usa los catálogos predeterminados del CRM (tipo de cliente, vendedor, moneda, zona, tipo de contacto).
- Respuesta 200:
{ "message": "Prospecto creado exitosamente.", "PROSPECTO_ID": ... }
Obtener direcciones del cliente
POST /accessToken/api/Daiko/v1/getAddresses
- Body:
cliente_idrequerido. - Respuesta 200:
{ "error": false, "data": [ { "DIR_CLI_ID", "NOMBRE", "ES_DEFAULT", "VIA_EMBARQUE", "VIA_EMBARQUE_ID" }, ... ] }(máximo 1000).
Crear dirección
POST /accessToken/api/Daiko/v1/createAddress
- Body:
cliente_idynombre_consigrequeridos; además cualquier campo de dirección
que useapiCrm/addresses(calle, num_ext, num_int, colonia,estado,ciudad,
poblacion,codigo_postal, etc.). Los nombres se pasan a mayúsculas y, si no existen, se
crean los catálogos de país (México), estado, ciudad, municipio y código postal. - Delega el guardado en
apiCrm/addresses/controller.js(save). - Respuesta 200:
{ "error": false, "data": { "DIR_CLI_ID": ... } }
Invitados (sin uso en el flujo actual)
POST /accessToken/api/Daiko/v1/getGuest— devuelve el cliente conNOMBRE = 'Invitado'
({ "CLIENTE_ID", "NOMBRE" }), o respuesta vacía si no existe.POST /accessToken/api/Daiko/v1/getIdGuest—
crea un cliente nuevo en cada llamada
(nombre fijo'Juan'). Es código de prueba; no usar en producción.
Consultas para el contacto
Los tres endpoints validan que el celular pertenezca a un contacto del cliente con el permiso
indicado (CONTACTO_PERMISOS). Si no lo tiene, responden un error de permiso.
Saldo vencido del cliente
POST /accessToken/api/Daiko/v1/getBalanceDue
- Body:
cliente_id,celularrequeridos. Permiso:VER_SALDO. - Respuesta 200: datos de saldo vencido (
Utils.getBalanceDue).
Existencia de un artículo
POST /accessToken/api/Daiko/v1/getStockArticle
- Body:
cliente_id,celular,articulo_idrequeridos. Permiso:VER_EXISTENCIAS. - Respuesta 200: existencia del artículo (usa el almacén del ERP si
crm.system >= 2).
Seguimiento/tracking de un pedido
POST /accessToken/api/Daiko/v1/getTrackingPedido
- Body:
cliente_id,celular,foliorequeridos. Permiso:ESTATUS_PEDIDO. folioes el folio de la cotización (DCDOCTOS_PDV.FOLIO).- Respuesta 200: cadena de documentos de la cotización (
Utils.getTracking). - Errores:
"El folio proporcionado no se encuentra registrado."
PDF de cotización
Obtener PDF
POST /accessToken/api/Daiko/v1/getPDF
Genera el PDF de una cotización con el formato que la cuenta marcó para Daiko
(FORMATOS_REPORTES.ES_DAIKO = 'S'), igual al PDF de crmzeus (imágenes, márgenes, condición de
pago, cantidad en letra). No hay versión v2.
- Body:
{ "carrito_id": 1026408 }(DOCTO_PV_ID) — requerido. - Respuesta 200:
{ "content": "<PDF en base64>", "name": "Cotizacion 1026408.pdf" }contentviene sin el prefijodata:application/pdf;base64,. - Sin formato marcado:
{ "error": true, "code": "NO_FORMATO_DAIKO", "message": "No hay un formato de cotización marcado para el Agente vendedor transaccional (Daiko)." }
En ese caso el bot debe generar su propio PDF (no hay formato de respaldo).
Multimedia
Imagen de un artículo
GET /accessToken/api/Daiko/v1/image/:articulo_id
- Path:
articulo_id. - Query:
?token=...como alternativa al headerapi_access_token(para usarlo directo en
un<img src>). - Respuesta 200: la imagen principal en binario (
Content-Type: image/jpeg, cache 24 h), o
un SVG “SIN IMAGEN” (cache 1 h) si el artículo no tiene imagen o pesa más de ~300 KB.
Galería HTML de productos
GET /accessToken/api/Daiko/v1/gallery
Para mandarle al usuario un link que abre una página con tarjetas de producto (imagen + nombre
- precio opcional), en vez de mandar imágenes sueltas por chat.
- Query:
Campo Requerido Notas idsSí Lista de ARTICULO_ID separados por coma. tokenSí (o header) Se reenvía a cada <img>de la galería.cliente_id+moneda_idNo Si se mandan ambos, cada tarjeta incluye el precio de lista del cliente (sin políticas ni descuentos). - Respuesta 200: HTML completo (
Content-Type: text/html), no JSON. - Errores:
"El parámetro ids es requerido.","No se proporcionaron IDs válidos."
Utilidades / depuración
Ejecutar SQL directo
POST /accessToken/api/Daiko/v1/SQL
Endpoint sensible. Ejecuta tal cual el SQL recibido contra la conexión del CRM, sin
filtro. Pensado para depuración; no debería estar disponible para el bot en producción.
- Body:
Campo Requerido Notas querySí SQL a ejecutar tal cual. dbNo ''(Firebird Terra, default),'2'(Firebird Microsip) o'3'(BD del ERP). - Respuesta 200:
{ "data": [...] }
Endpoints obsoletos (no usar)
Tienen versión v2 y la v1 ya no se usa. Siguen ruteados, pero trabajan sobre los documentos de
Microsip (DOCTOS_VE) y no sobre las cotizaciones de Terra.
| v1 (no usar) | Usar |
|---|---|
v1/getIdCart/:id |
v2/getIdCart/:id |
v1/createCart/:id |
v2/createCart/:id |
v1/getCart/:id |
v2/getCart/:id |
v1/cart/:id/add, v1/cart/:id/update, v1/cart/:id/remove |
v2/cart/:id/:action |
v1/statusCart/:id |
v2/statusCart/:id |
v1/setStatusCart/:id |
v2/setStatusCart/:id |
v1/deleteCart/:id |
v2/deleteCart/:id |
v1/createDocto/:id (pedía tipo_docto) |
v2/createDocto/:id |
Los demás endpoints v1 de esta guía (productos, clientes, direcciones, consultas, PDF,
imágenes) siguen vigentes porque no tienen v2.
Puntos a cuidar
Comportamientos del código actual que conviene conocer (no son cambios hechos en este documento):
articulo_idcomo número encart/:id/:action. La partida se busca con comparación
estricta; si llega"12846"(texto),update/removeresponden “no existe en el carrito” y
addlo agrega como partida nueva en vez de sumar.unidadescomo número enadd. Si el artículo ya está yunidadesllega como texto, la
suma se vuelve concatenación ("2" + 3 = "23").- Paginación en la raíz.
getProductsygetListCategoryleenper_page/current_pagedel
body raíz; un objetopagination: {...}se ignora. existencia: truefiltra después de paginar. Una página puede traer menos deper_page
ymeta.countincluye artículos sin existencia.- Token inválido en
createCart. La conexión a la base se abre antes de revisar el resultado
del token; con un token inválido la respuesta puede ser un error genérico en vez de
“El token proporcionado no es válido”. setStatusCartno restringe transiciones (puede reabrir cancelados o ganados), y
cart/:id/observationsno valida el estatus del carrito.- Errores con stack completo. Las excepciones devuelven el stack del servidor en
message;
el bot no debería mostrarlo tal cual al usuario.