# Istmo Commerce — documentación completa Fuente: https://admin.istmotech.com/docs/index.md # Construye con Istmo POS Documentación pública para conectar tu tienda con Istmo Commerce. ## Una API para tu tienda Conecta un sitio Vue, React o cualquier backend con el catálogo y las solicitudes de pedido de Istmo POS. Usa el contrato OpenAPI para generar clientes o comparte la versión Markdown con tu agente de IA. - [Comenzar la integración](https://admin.istmotech.com/docs/inicio/) - [Explorar la API](https://admin.istmotech.com/docs/api/) ## Estado del contrato · v1.5.0 /orders recibe solicitudes pendientes. checkout-commands permite cotizar/reservar y solicitar ACH habilitado por la empresa. Sólo el administrador concilia el pago y emite la factura. checkoutEnabled=false: no hay cobro automático. - Disponible en código: credenciales por tienda, catálogo autorizado, solicitudes pendientes, consulta y rechazo administrativo. - Requiere actualizar el servidor operativo y aplicar la migración de Commerce antes de usar sus endpoints. Publicar estas páginas no activa la API. - Gateway y puente saliente implementados en código; requieren despliegue y configuración. Disponibles: cotización y reservas temporales por checkout-commands. Disponible por API administrativa: ACH/transferencia con conciliación y emisión idempotente. Disponible: preparación y retiro auditados. Pendientes: tarjeta/Yappy web y webhooks. ## Recursos para personas e IA Las páginas contienen HTML estático y versiones Markdown. Se pueden leer sin iniciar sesión ni ejecutar JavaScript. La API operativa siempre requiere autenticación. - [OpenAPI JSON](https://admin.istmotech.com/docs/openapi.json) - [Índice para agentes · llms.txt](https://admin.istmotech.com/llms.txt) - [Documentación completa en texto](https://admin.istmotech.com/llms-full.txt) - [Guía para agentes](https://admin.istmotech.com/docs/ia/) - [Cliente Node sin dependencias](https://admin.istmotech.com/docs/istmo-commerce.mjs) --- Fuente: https://admin.istmotech.com/docs/inicio/index.md # Primeros pasos Del aprovisionamiento de la tienda a la primera solicitud. ## 1. Identifica el servidor operativo La base de la API es https://TU-SERVIDOR-POS/api/v1/commerce. El dominio de esta documentación no es la URL de la API. cloud-api administra licencias; Commerce se ejecuta en pos-server, tanto local como cloud-pos. Para instalaciones locales, localhost e IP privadas no son accesibles desde Vercel. Consulta la guía local/cloud antes de configurar el backend. - [Instalaciones locales y cloud](https://admin.istmotech.com/docs/local-cloud/) ## 2. Crea una integración Un administrador del POS crea la integración con su JWT vigente. El servidor comprueba el permiso actual admin.configuracion o un rol superadmin activo, pertenencia a la sucursal y licencia POS/facturación. Empresa y sucursal se derivan de la identidad autenticada. ```json { "nombre": "Mi tienda", "sitioUrl": "https://tienda.example.com", "almacenId": "11111111-1111-4111-8111-111111111111", "numeroLista": 1, "scopes": [ "catalog:read", "orders:write", "orders:read" ] } ``` Enviar a POST /admin/integrations. El UUID es ilustrativo: usar un almacén real de la sucursal. Guardar data.id y data.credential; la credencial se devuelve una sola vez. Si se pierde la respuesta, listar antes de repetir y rotar si se perdió la clave. ## 3. Publica una selección PUT /admin/integrations/{id}/products reemplaza toda la selección mediante {"productoIds":["UUID-REAL"]}. Una lista vacía despublica todo. Se admiten hasta 200 productos simples activos con precio en USD en la lista seleccionada. No se publican automáticamente todos los productos. Esta versión excluye servicios, variantes, lotes, seriales, compuestos y precio libre. No incluye fotos. ## 4. Consulta desde el backend Guardar la credencial sólo en el servidor de la tienda. Nunca en variables VITE_, código Vue, localStorage o parámetros de URL. El secreto identifica una integración, no autentica a cada comprador. ```bash curl "https://TU-SERVIDOR-POS/api/v1/commerce/catalog?limit=50" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` - [Implementar con Vue y Vercel](https://admin.istmotech.com/docs/vue-vercel/) ## Precios y disponibilidad - unitPriceReference es texto decimal con cuatro posiciones: precio de lista referencial, no cotización fiscal ni total a cobrar. - availableReference es max(0, existencia − reservada) del almacén autorizado. No garantiza disponibilidad ni reserva. - No calcular impuestos, envío o cobros a partir del precio de referencia. Para cotizar y reservar usar checkout-commands. - Paginación: usar limit (1–100) y after=nextCursor. El cursor ordena UUID, no es una fecha ni un mecanismo de sincronización incremental. --- Fuente: https://admin.istmotech.com/docs/api/index.md # Referencia API Métodos, credenciales y esquemas generados desde OpenAPI 1.6.0 ## Autenticación y API keys Cada tienda usa una API key independiente en la cabecera x-istmo-commerce-key. En el POS se generan 32 bytes aleatorios y sólo se guarda el hash SHA-256; la clave se entrega una vez al crear o rotar. En gateway, el operador aprovisiona una clave de sitio independiente en el gestor de secretos del despliegue. La clave POS fija empresa, sucursal y almacén y revalida contexto y licencia. La clave gateway sólo accede a su tienda y scopes; el conector revalida el POS al sincronizar. Durante desconexión el gateway puede recibir solicitudes con el último catálogo. Las operaciones administrativas requieren JWT de usuario y permisos vigentes; una API key de tienda no los sustituye. Guarda los secretos sólo en el backend, nunca en Vue o variables VITE_. - catalog:read: consultar únicamente el catálogo publicado. - orders:write: enviar solicitudes de pedido; no autoriza cobros ni facturas. - orders:read: consultar solicitudes de esa integración; el backend también debe autorizar al comprador. - Crear: POST /admin/integrations. Guardar data.credential de inmediato; el listado no vuelve a mostrarla. - Rotar: POST /admin/integrations/{id}/rotate. Invalida la clave anterior; si pierdes la respuesta, vuelve a rotar. - Revocar: DELETE /admin/integrations/{id}. Preserva pedidos e historial; una conexión revocada no se reactiva. - Se auditan creación, rotación y revocación. Ante una filtración, revoca o rota y actualiza el secreto del backend. - Las operaciones /admin anteriores se ejecutan en POS. Para gateway, el operador rota siteKey en su configuración; enabled=false y reinicio de todas las réplicas corta recepción inmediatamente. Cambiar sólo la clave POS no garantiza revocación cloud mientras está desconectado. ## GET /catalog Consultar catálogo autorizado Catálogo publicado de referencia. Cotizar y reservar mediante checkout-commands; disponibilidad y precios deben confirmarse en POS. Autenticación: x-istmo-commerce-key · scope catalog:read. ```json { "parameters": [ { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "nextCursor previo. Orden UUID ascendente; no es un cursor de sincronización incremental." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "sku": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "unitPriceReference": { "type": "string", "pattern": "^\\d+\\.\\d{4}$", "description": "Precio de la lista; referencia, no cotización fiscal ni total a cobrar. No calcular impuestos o confirmar cobros a partir de este campo." }, "currency": { "type": "string", "enum": [ "USD" ] }, "availableReference": { "type": "string", "pattern": "^\\d+\\.\\d{4}$", "description": "max(0, existencia-reservada) del almacén autorizado. No garantiza disponibilidad ni reserva." }, "stockUpdatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "id", "sku", "name", "description", "unitPriceReference", "currency", "availableReference", "stockUpdatedAt" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true }, "integrationId": { "type": "string", "format": "uuid" }, "observedAt": { "type": "string", "format": "date-time" }, "availabilityGuaranteed": { "type": "boolean", "enum": [ false ] }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] }, "mode": { "type": "string", "enum": [ "request_only" ] }, "lastSyncAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Sólo gateway: última publicación recibida. El catálogo puede estar desactualizado durante desconexión." } }, "required": [ "items", "nextCursor", "integrationId", "observedAt", "availabilityGuaranteed", "checkoutEnabled", "mode" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/catalog" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/catalog', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "sku": "ARROZ", "name": "Arroz nacional 1 kg", "description": "Arroz nacional 1 kg", "unitPriceReference": "1.2500", "currency": "USD", "availableReference": "8.2500", "stockUpdatedAt": null } ], "nextCursor": null, "integrationId": "11111111-1111-4111-8111-111111111111", "observedAt": "2026-09-17T15:00:00.000Z", "availabilityGuaranteed": false, "checkoutEnabled": false, "mode": "request_only", "lastSyncAt": null } } ``` ## POST /orders Registrar solicitud de pedido externalId es la clave idempotente por integración y se conserva mientras exista el pedido. Mismo contenido devuelve el mismo ID; contenido diferente devuelve 409. Cantidades decimales equivalentes y orden de líneas no cambian la identidad. No acepta precios, totales ni afirmaciones de pago. No reserva inventario, cobra o factura. Los reintentos consultan el estado actual, incluso si fue rechazado. Autenticación: x-istmo-commerce-key · scope orders:write. ```json { "requestBody": { "type": "object", "additionalProperties": false, "properties": { "externalId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$", "description": "Referencia estable única por integración. Conservar exactamente en reintentos, incluso tras un timeout." }, "customer": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 150 }, "email": { "type": "string", "format": "email", "maxLength": 150 }, "phone": { "type": "string", "minLength": 5, "maxLength": 40 } }, "required": [ "name", "email", "phone" ] }, "fulfillment": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "pickup" ] } }, "required": [ "method" ] }, { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "delivery" ] }, "address": { "type": "string", "minLength": 5, "maxLength": 500 } }, "required": [ "method", "address" ] } ], "discriminator": { "propertyName": "method" } }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "quantity": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,5})(?:\\.\\d{1,4})?$", "description": "Decimal como texto > 0, hasta 999999.9999. No repetir productId." } }, "required": [ "productId", "quantity" ] }, "minItems": 1, "maxItems": 100 }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "externalId", "customer", "fulfillment", "lines" ] }, "responseStatus": 202, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "recibido_gateway", "pendiente_confirmacion", "rechazado" ] }, "reason": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "paymentStatus": { "type": "string", "enum": [ "not_requested" ] }, "stockReserved": { "type": "boolean", "enum": [ false ] }, "invoiced": { "type": "boolean", "enum": [ false ] }, "posOrderId": { "type": "string", "format": "uuid", "nullable": true, "description": "Sólo gateway. Recibo local, o null mientras no existe. El id principal del gateway permanece estable." } }, "required": [ "id", "externalId", "status", "reason", "createdAt", "updatedAt", "paymentStatus", "stockReserved", "invoiced" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/orders" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" \ -H "Content-Type: application/json" \ --data '{ "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/orders', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "externalId": "web-7d2f4c9a", "status": "recibido_gateway", "reason": null, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z", "paymentStatus": "not_requested", "stockReserved": false, "invoiced": false, "posOrderId": null } } ``` ## GET /orders/{externalId} Consultar estado de una solicitud Sólo solicitudes de la integración autenticada. No exponer esta consulta públicamente sin comprobar propiedad del comprador en el backend de la tienda. Autenticación: x-istmo-commerce-key · scope orders:read. ```json { "parameters": [ { "name": "externalId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$", "description": "Referencia estable única por integración. Conservar exactamente en reintentos, incluso tras un timeout." } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "recibido_gateway", "pendiente_confirmacion", "rechazado" ] }, "reason": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "paymentStatus": { "type": "string", "enum": [ "not_requested" ] }, "stockReserved": { "type": "boolean", "enum": [ false ] }, "invoiced": { "type": "boolean", "enum": [ false ] }, "posOrderId": { "type": "string", "format": "uuid", "nullable": true, "description": "Sólo gateway. Recibo local, o null mientras no existe. El id principal del gateway permanece estable." } }, "required": [ "id", "externalId", "status", "reason", "createdAt", "updatedAt", "paymentStatus", "stockReserved", "invoiced" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/orders/web-7d2f4c9a" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/orders/web-7d2f4c9a', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "externalId": "web-7d2f4c9a", "status": "recibido_gateway", "reason": null, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z", "paymentStatus": "not_requested", "stockReserved": false, "invoiced": false, "posOrderId": null } } ``` ## POST /admin/integrations Crear conexión de tienda Empresa y sucursal se derivan del JWT y permisos vigentes de BD. No se aceptan desde el body. Requiere admin.configuracion o rol superadmin vigente, licencia POS/facturación y almacén autorizado. La creación no es idempotente: ante respuesta perdida listar antes de repetir; una clave perdida se rota. Autenticación: JWT administrativo con permisos vigentes. ```json { "requestBody": { "type": "object", "additionalProperties": false, "properties": { "nombre": { "type": "string", "minLength": 1, "maxLength": 100 }, "sitioUrl": { "type": "string", "format": "uri", "maxLength": 500, "pattern": "^https://", "description": "URL HTTPS sin credenciales, query ni fragmento. Informativa; no se usa para autenticación ni llamadas salientes." }, "almacenId": { "type": "string", "format": "uuid" }, "numeroLista": { "type": "integer", "minimum": 1, "maximum": 3, "default": 1 }, "scopes": { "type": "array", "items": { "type": "string", "enum": [ "catalog:read", "orders:write", "orders:read" ] }, "minItems": 1, "maxItems": 3, "uniqueItems": true } }, "required": [ "nombre", "sitioUrl", "almacenId", "scopes" ] }, "responseStatus": 201, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "empresaId": { "type": "string", "format": "uuid" }, "sucursalId": { "type": "string", "format": "uuid" }, "almacenId": { "type": "string", "format": "uuid" }, "nombre": { "type": "string" }, "sitioUrl": { "type": "string", "format": "uri" }, "numeroLista": { "type": "integer" }, "scopes": { "type": "array", "items": { "type": "string", "enum": [ "catalog:read", "orders:write", "orders:read" ] }, "minItems": 1, "maxItems": 3, "uniqueItems": true }, "activo": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "credential": { "type": "string", "pattern": "^istmo_com_[a-f0-9]{64}$", "description": "Se entrega una sola vez. Guardar exclusivamente como secreto del backend." } }, "required": [ "id", "empresaId", "sucursalId", "almacenId", "nombre", "sitioUrl", "numeroLista", "scopes", "activo", "createdAt", "updatedAt", "credential" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "nombre": "Mi tienda", "sitioUrl": "https://tienda.example.com", "almacenId": "11111111-1111-4111-8111-111111111111", "numeroLista": 1, "scopes": [ "catalog:read" ] }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "nombre": "Mi tienda", "sitioUrl": "https://tienda.example.com", "almacenId": "11111111-1111-4111-8111-111111111111", "numeroLista": 1, "scopes": [ "catalog:read" ] }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "empresaId": "11111111-1111-4111-8111-111111111111", "sucursalId": "11111111-1111-4111-8111-111111111111", "almacenId": "11111111-1111-4111-8111-111111111111", "nombre": "Mi tienda", "sitioUrl": "https://tienda.example.com", "numeroLista": 1, "scopes": [ "catalog:read" ], "activo": false, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z", "credential": "CREDENCIAL-SOLO-EN-EL-BACKEND" } } ``` ## GET /admin/integrations Listar conexiones de la sucursal Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "nextCursor previo. Orden UUID ascendente; no es un cursor de sincronización incremental." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "empresaId": { "type": "string", "format": "uuid" }, "sucursalId": { "type": "string", "format": "uuid" }, "almacenId": { "type": "string", "format": "uuid" }, "nombre": { "type": "string" }, "sitioUrl": { "type": "string", "format": "uri" }, "numeroLista": { "type": "integer" }, "scopes": { "type": "array", "items": { "type": "string", "enum": [ "catalog:read", "orders:write", "orders:read" ] }, "minItems": 1, "maxItems": 3, "uniqueItems": true }, "activo": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } }, "required": [ "id", "empresaId", "sucursalId", "almacenId", "nombre", "sitioUrl", "numeroLista", "scopes", "activo", "createdAt", "updatedAt" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "empresaId": "11111111-1111-4111-8111-111111111111", "sucursalId": "11111111-1111-4111-8111-111111111111", "almacenId": "11111111-1111-4111-8111-111111111111", "nombre": "Mi tienda", "sitioUrl": "https://tienda.example.com", "numeroLista": 1, "scopes": [ "catalog:read" ], "activo": false, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z" } ], "nextCursor": null } } ``` ## POST /admin/integrations/{id}/rotate Rotar credencial Invalida la anterior. Devuelve el secreto una sola vez; si se pierde la respuesta volver a rotar. No reactiva integraciones revocadas. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "credential": { "type": "string", "pattern": "^istmo_com_[a-f0-9]{64}$", "description": "Se entrega una sola vez. Guardar exclusivamente como secreto del backend." } }, "required": [ "id", "credential" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/rotate" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/rotate', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "credential": "CREDENCIAL-SOLO-EN-EL-BACKEND" } } ``` ## DELETE /admin/integrations/{id} Revocar conexión Revocación lógica irreversible, idempotente. Preserva pedidos e historial. Operaciones que ya adquirieron el bloqueo pueden terminar antes de la revocación. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "activo": { "type": "boolean", "enum": [ false ] } }, "required": [ "id", "activo" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X DELETE "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111', { method: 'DELETE', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "activo": false } } ``` ## PUT /admin/integrations/{id}/products Reemplazar productos publicados Selección atómica por integración, máximo 200 productos simples activos. No cambia productos ni precios del POS. No publica automáticamente todo el inventario. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "productoIds": { "type": "array", "items": { "type": "string", "format": "uuid" }, "maxItems": 200, "uniqueItems": true, "description": "Reemplaza toda la selección. [] despublica todo. Sólo productos simples activos con precio USD." } }, "required": [ "productoIds" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "productoIds": { "type": "array", "items": { "type": "string", "format": "uuid" } } }, "required": [ "id", "productoIds" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X PUT "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/products" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "productoIds": [ "11111111-1111-4111-8111-111111111111" ] }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/products', { method: 'PUT', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "productoIds": [ "11111111-1111-4111-8111-111111111111" ] }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "productoIds": [ "11111111-1111-4111-8111-111111111111" ] } } ``` ## GET /admin/integrations/{id}/products Buscar productos elegibles para publicar Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "search", "in": "query", "schema": { "type": "string", "maxLength": 100, "default": "" } }, { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "sku": { "type": "string" }, "name": { "type": "string" } }, "required": [ "id", "sku", "name" ] } }, "nextCursor": { "type": "string", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/products" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/products', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "sku": "ARROZ", "name": "Arroz nacional 1 kg" } ], "nextCursor": null } } ``` ## GET /admin/integrations/{id}/orders Consultar bandeja de solicitudes Incluye datos personales y snapshot; acceso administrativo, no usar desde Vue público. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "nextCursor previo. Orden UUID ascendente; no es un cursor de sincronización incremental." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "pendiente_confirmacion", "rechazado" ] }, "reason": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "paymentStatus": { "type": "string", "enum": [ "not_requested" ] }, "stockReserved": { "type": "boolean", "enum": [ false ] }, "invoiced": { "type": "boolean", "enum": [ false ] }, "request": { "type": "object", "additionalProperties": false, "properties": { "externalId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$", "description": "Referencia estable única por integración. Conservar exactamente en reintentos, incluso tras un timeout." }, "customer": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 150 }, "email": { "type": "string", "format": "email", "maxLength": 150 }, "phone": { "type": "string", "minLength": 5, "maxLength": 40 } }, "required": [ "name", "email", "phone" ] }, "fulfillment": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "pickup" ] } }, "required": [ "method" ] }, { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "delivery" ] }, "address": { "type": "string", "minLength": 5, "maxLength": 500 } }, "required": [ "method", "address" ] } ], "discriminator": { "propertyName": "method" } }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "quantity": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,5})(?:\\.\\d{1,4})?$", "description": "Decimal como texto > 0, hasta 999999.9999. No repetir productId." } }, "required": [ "productId", "quantity" ] }, "minItems": 1, "maxItems": 100 }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "externalId", "customer", "fulfillment", "lines" ] }, "snapshot": { "type": "object", "additionalProperties": false, "properties": { "currency": { "type": "string", "enum": [ "USD" ] }, "products": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "sku": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "unitPriceReference": { "type": "string", "pattern": "^\\d+\\.\\d{4}$", "description": "Precio de la lista; referencia, no cotización fiscal ni total a cobrar. No calcular impuestos o confirmar cobros a partir de este campo." }, "currency": { "type": "string", "enum": [ "USD" ] }, "availableReference": { "type": "string", "pattern": "^\\d+\\.\\d{4}$", "description": "max(0, existencia-reservada) del almacén autorizado. No garantiza disponibilidad ni reserva." }, "stockUpdatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "id", "sku", "name", "description", "unitPriceReference", "currency", "availableReference", "stockUpdatedAt" ] } }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] } }, "required": [ "currency", "products", "checkoutEnabled" ] } }, "required": [ "id", "externalId", "status", "reason", "createdAt", "updatedAt", "paymentStatus", "stockReserved", "invoiced", "request", "snapshot" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/orders" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/orders', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "externalId": "web-7d2f4c9a", "status": "pendiente_confirmacion", "reason": null, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z", "paymentStatus": "not_requested", "stockReserved": false, "invoiced": false, "request": { "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" }, "snapshot": { "currency": "USD", "products": [ { "id": "11111111-1111-4111-8111-111111111111", "sku": "ARROZ", "name": "Arroz nacional 1 kg", "description": "Arroz nacional 1 kg", "unitPriceReference": "1.2500", "currency": "USD", "availableReference": "8.2500", "stockUpdatedAt": null } ], "checkoutEnabled": false } } ], "nextCursor": null } } ``` ## POST /admin/integrations/{id}/orders/{orderId}/reject Rechazar solicitud con motivo Mismo motivo es idempotente; otro motivo después del rechazo devuelve 409. No anula documentos fiscales ni devuelve dinero. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "orderId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "motivo": { "type": "string", "minLength": 5, "maxLength": 500 } }, "required": [ "motivo" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "recibido_gateway", "pendiente_confirmacion", "rechazado" ] }, "reason": { "type": "string", "nullable": true }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "paymentStatus": { "type": "string", "enum": [ "not_requested" ] }, "stockReserved": { "type": "boolean", "enum": [ false ] }, "invoiced": { "type": "boolean", "enum": [ false ] }, "posOrderId": { "type": "string", "format": "uuid", "nullable": true, "description": "Sólo gateway. Recibo local, o null mientras no existe. El id principal del gateway permanece estable." } }, "required": [ "id", "externalId", "status", "reason", "createdAt", "updatedAt", "paymentStatus", "stockReserved", "invoiced" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/orders/11111111-1111-4111-8111-111111111111/reject" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "motivo": "Producto sin disponibilidad" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/orders/11111111-1111-4111-8111-111111111111/reject', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "motivo": "Producto sin disponibilidad" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "externalId": "web-7d2f4c9a", "status": "recibido_gateway", "reason": null, "createdAt": "2026-09-17T15:00:00.000Z", "updatedAt": "2026-09-17T15:00:00.000Z", "paymentStatus": "not_requested", "stockReserved": false, "invoiced": false, "posOrderId": null } } ``` ## GET /admin/options Consultar almacenes y requisitos del puente Autenticación: JWT administrativo con permisos vigentes. ```json { "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "warehouses": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "nombre": { "type": "string" } }, "required": [ "id", "nombre" ] } }, "allowedGateways": { "type": "array", "items": { "type": "string" } }, "encryptionReady": { "type": "boolean" }, "paymentInstruments": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "nombre": { "type": "string" } }, "required": [ "id", "nombre" ] } }, "permissions": { "type": "object", "additionalProperties": false, "properties": { "reconcile": { "type": "boolean" }, "invoice": { "type": "boolean" }, "fulfill": { "type": "boolean" } }, "required": [ "reconcile", "invoice", "fulfill" ] } }, "required": [ "warehouses", "allowedGateways", "encryptionReady", "paymentInstruments", "permissions" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/options" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/options', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "warehouses": [ { "id": "11111111-1111-4111-8111-111111111111", "nombre": "Mi tienda" } ], "allowedGateways": [ "Ejemplo" ], "encryptionReady": false, "paymentInstruments": [ { "id": "11111111-1111-4111-8111-111111111111", "nombre": "Mi tienda" } ], "permissions": { "reconcile": false, "invoice": false, "fulfill": false } } } ``` ## GET /admin/integrations/{id}/settings Consultar publicación y diagnóstico sin secretos Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "selected": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "sku": { "type": "string" }, "name": { "type": "string" } }, "required": [ "id", "sku", "name" ] } }, "bridge": { "type": "object", "additionalProperties": false, "properties": { "source": { "type": "string", "enum": [ "environment", "database" ] }, "gatewayUrl": { "type": "string" }, "binding": { "type": "object", "additionalProperties": false, "properties": { "installationId": { "type": "string", "format": "uuid" }, "integrationId": { "type": "string", "format": "uuid" } }, "required": [ "installationId", "integrationId" ] }, "enabled": { "type": "boolean" }, "status": { "type": "object", "additionalProperties": false, "properties": { "attemptAt": { "type": "string", "nullable": true }, "successAt": { "type": "string", "nullable": true }, "errorCode": { "type": "string", "nullable": true }, "paused": { "type": "boolean" } }, "required": [ "attemptAt", "successAt", "errorCode", "paused" ] } }, "required": [ "source", "gatewayUrl", "binding", "enabled", "status" ], "nullable": true }, "paymentSettings": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "instrumentoId": { "type": "string", "format": "uuid" }, "instructions": { "type": "string", "minLength": 10, "maxLength": 1000 } }, "required": [ "enabled", "instrumentoId", "instructions" ], "nullable": true } }, "required": [ "selected", "bridge", "paymentSettings" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/settings" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/settings', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "selected": [ { "id": "11111111-1111-4111-8111-111111111111", "sku": "ARROZ", "name": "Arroz nacional 1 kg" } ], "bridge": null, "paymentSettings": null } } ``` ## PUT /admin/integrations/{id}/bridge Guardar y activar vínculo cifrado Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "gatewayUrl": { "type": "string", "format": "uri", "description": "Origen HTTPS incluido en la lista del operador. Inmutable después de guardar." }, "installationId": { "type": "string", "format": "uuid" }, "connectorKey": { "type": "string", "pattern": "^istmo_bridge_[a-f0-9]{64}$", "writeOnly": true }, "localKey": { "type": "string", "pattern": "^istmo_com_[a-f0-9]{64}$", "writeOnly": true } }, "required": [ "gatewayUrl", "installationId", "connectorKey", "localKey" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "enabled": { "type": "boolean" } }, "required": [ "id", "enabled" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X PUT "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/bridge" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "gatewayUrl": "https://tienda.example.com", "installationId": "11111111-1111-4111-8111-111111111111", "connectorKey": "Ejemplo", "localKey": "Ejemplo" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/bridge', { method: 'PUT', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "gatewayUrl": "https://tienda.example.com", "installationId": "11111111-1111-4111-8111-111111111111", "connectorKey": "Ejemplo", "localKey": "Ejemplo" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "enabled": false } } ``` ## DELETE /admin/integrations/{id}/bridge Solicitar pausa del puente Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "enabled": { "type": "boolean" } }, "required": [ "id", "enabled" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X DELETE "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/bridge" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/bridge', { method: 'DELETE', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "enabled": false } } ``` ## POST /checkout-commands Cotizar, reservar, solicitar transferencia, consultar o cancelar quote/payment/cancel requieren orders:write; status requiere orders:read. quote/payment nuevos requieren gateway sincronizado en últimos 45s y llegada al POS antes de 60s. payment requiere configuración ACH y reserva vigente; reintentos recuperan la intención original. Las API keys de tienda no confirman pagos ni facturas. cancel rechaza pagos conciliados: resolución administrativa. Reserva de 5 minutos. Autenticación: x-istmo-commerce-key · scope undefined. ```json { "requestBody": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "checkoutId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$" }, "action": { "type": "string", "enum": [ "quote" ] }, "order": { "type": "object", "additionalProperties": false, "properties": { "externalId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$", "description": "Referencia estable única por integración. Conservar exactamente en reintentos, incluso tras un timeout." }, "customer": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 150 }, "email": { "type": "string", "format": "email", "maxLength": 150 }, "phone": { "type": "string", "minLength": 5, "maxLength": 40 } }, "required": [ "name", "email", "phone" ] }, "fulfillment": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "pickup" ] } }, "required": [ "method" ] }, { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "delivery" ] }, "address": { "type": "string", "minLength": 5, "maxLength": 500 } }, "required": [ "method", "address" ] } ], "discriminator": { "propertyName": "method" } }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "quantity": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,5})(?:\\.\\d{1,4})?$", "description": "Decimal como texto > 0, hasta 999999.9999. No repetir productId." } }, "required": [ "productId", "quantity" ] }, "minItems": 1, "maxItems": 100 }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "externalId", "customer", "fulfillment", "lines" ] } }, "required": [ "commandId", "checkoutId", "action", "order" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "checkoutId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$" }, "action": { "type": "string", "enum": [ "status" ] } }, "required": [ "commandId", "checkoutId", "action" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "checkoutId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$" }, "action": { "type": "string", "enum": [ "cancel" ] } }, "required": [ "commandId", "checkoutId", "action" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "checkoutId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$" }, "action": { "type": "string", "enum": [ "payment" ] } }, "required": [ "commandId", "checkoutId", "action" ] } ], "description": "commandId estable por operación. quote requiere order y checkoutId=externalId. payment solicita instrucciones ACH en una reserva vigente, sin confirmar cobro. status con UUID nuevo consulta estado actual; GET devuelve recibo histórico." }, "responseStatus": 202, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "pending" ] }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] } }, "required": [ "commandId", "state", "checkoutEnabled" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "completed" ] }, "result": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "reservado", "expirado", "cancelado", "vendido" ] }, "expiresAt": { "type": "string", "format": "date-time" }, "observedAt": { "type": "string", "format": "date-time" }, "stockReserved": { "type": "boolean" }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] }, "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "quote": { "type": "object", "additionalProperties": false, "properties": { "currency": { "type": "string", "enum": [ "USD" ] }, "fulfillment": { "type": "string", "enum": [ "pickup" ] }, "pricingPolicy": { "type": "string", "enum": [ "published_list_no_discounts" ] }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "quantity": { "type": "string" }, "unitPrice": { "type": "string" }, "taxRate": { "type": "string" }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" } }, "required": [ "productId", "name", "quantity", "unitPrice", "taxRate", "subtotal", "tax", "total" ] } } }, "required": [ "currency", "fulfillment", "pricingPolicy", "subtotal", "tax", "total", "lines" ] }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "id", "externalId", "status", "expiresAt", "observedAt", "stockReserved", "checkoutEnabled", "paymentStatus", "invoiced", "quote" ] } }, "required": [ "commandId", "state", "result" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "failed" ] }, "error": { "type": "object", "additionalProperties": false, "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": [ "code", "message" ] } }, "required": [ "commandId", "state", "error" ] } ], "description": "pending en gateway sólo confirma persistencia. completed/failed son recibos históricos: para consultar pago/factura actuales enviar status con nuevo commandId. checkoutEnabled=false significa que no hay cobro automático. ACH requiere confirmación administrativa." } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/checkout-commands" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" \ -H "Content-Type: application/json" \ --data '{ "commandId": "11111111-1111-4111-8111-111111111111", "checkoutId": "web-7d2f4c9a", "action": "quote", "order": { "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" } }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/checkout-commands', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ "commandId": "11111111-1111-4111-8111-111111111111", "checkoutId": "web-7d2f4c9a", "action": "quote", "order": { "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" } }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "commandId": "11111111-1111-4111-8111-111111111111", "state": "pending", "checkoutEnabled": false } } ``` ## GET /checkout-commands/{commandId} Recuperar recibo histórico de operación Autenticación: x-istmo-commerce-key · scope undefined. ```json { "parameters": [ { "name": "commandId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "pending" ] }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] } }, "required": [ "commandId", "state", "checkoutEnabled" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "completed" ] }, "result": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string", "enum": [ "reservado", "expirado", "cancelado", "vendido" ] }, "expiresAt": { "type": "string", "format": "date-time" }, "observedAt": { "type": "string", "format": "date-time" }, "stockReserved": { "type": "boolean" }, "checkoutEnabled": { "type": "boolean", "enum": [ false ] }, "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "quote": { "type": "object", "additionalProperties": false, "properties": { "currency": { "type": "string", "enum": [ "USD" ] }, "fulfillment": { "type": "string", "enum": [ "pickup" ] }, "pricingPolicy": { "type": "string", "enum": [ "published_list_no_discounts" ] }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "quantity": { "type": "string" }, "unitPrice": { "type": "string" }, "taxRate": { "type": "string" }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" } }, "required": [ "productId", "name", "quantity", "unitPrice", "taxRate", "subtotal", "tax", "total" ] } } }, "required": [ "currency", "fulfillment", "pricingPolicy", "subtotal", "tax", "total", "lines" ] }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "id", "externalId", "status", "expiresAt", "observedAt", "stockReserved", "checkoutEnabled", "paymentStatus", "invoiced", "quote" ] } }, "required": [ "commandId", "state", "result" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "state": { "type": "string", "enum": [ "failed" ] }, "error": { "type": "object", "additionalProperties": false, "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": [ "code", "message" ] } }, "required": [ "commandId", "state", "error" ] } ], "description": "pending en gateway sólo confirma persistencia. completed/failed son recibos históricos: para consultar pago/factura actuales enviar status con nuevo commandId. checkoutEnabled=false significa que no hay cobro automático. ACH requiere confirmación administrativa." } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/checkout-commands/11111111-1111-4111-8111-111111111111" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/checkout-commands/11111111-1111-4111-8111-111111111111', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "commandId": "11111111-1111-4111-8111-111111111111", "state": "pending", "checkoutEnabled": false } } ``` ## PUT /admin/integrations/{id}/payment-settings Configurar ACH/transferencia Requiere admin.configuracion vigente. Opt-in por integración. Instrumento ACH/TRANSFERENCIA activo USD. instructions se publica al solicitar payment; nunca incluir credenciales bancarias. Consultar configuración en GET settings. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "instrumentoId": { "type": "string", "format": "uuid" }, "instructions": { "type": "string", "minLength": 10, "maxLength": 1000 } }, "required": [ "enabled", "instrumentoId", "instructions" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "instrumentoId": { "type": "string", "format": "uuid" }, "instructions": { "type": "string", "minLength": 10, "maxLength": 1000 } }, "required": [ "enabled", "instrumentoId", "instructions" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X PUT "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/payment-settings" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "enabled": false, "instrumentoId": "11111111-1111-4111-8111-111111111111", "instructions": "Ejemplo" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/payment-settings', { method: 'PUT', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "enabled": false, "instrumentoId": "11111111-1111-4111-8111-111111111111", "instructions": "Ejemplo" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "enabled": false, "instrumentoId": "11111111-1111-4111-8111-111111111111", "instructions": "Ejemplo" } } ``` ## GET /admin/integrations/{id}/checkouts Listar cotizaciones y pagos Requiere admin.configuracion. Paginación limit 1–100 y after UUID. Sólo esta empresa/sucursal. confirmed sin invoice significa pago recibido pendiente de resolver, nunca pedir otro cobro. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" } }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "externalId": { "type": "string" }, "status": { "type": "string" }, "expiresAt": { "type": "string", "format": "date-time" }, "customer": { "type": "object", "additionalProperties": false, "properties": { "externalId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$", "description": "Referencia estable única por integración. Conservar exactamente en reintentos, incluso tras un timeout." }, "customer": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 150 }, "email": { "type": "string", "format": "email", "maxLength": 150 }, "phone": { "type": "string", "minLength": 5, "maxLength": 40 } }, "required": [ "name", "email", "phone" ] }, "fulfillment": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "pickup" ] } }, "required": [ "method" ] }, { "type": "object", "additionalProperties": false, "properties": { "method": { "type": "string", "enum": [ "delivery" ] }, "address": { "type": "string", "minLength": 5, "maxLength": 500 } }, "required": [ "method", "address" ] } ], "discriminator": { "propertyName": "method" } }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "quantity": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,5})(?:\\.\\d{1,4})?$", "description": "Decimal como texto > 0, hasta 999999.9999. No repetir productId." } }, "required": [ "productId", "quantity" ] }, "minItems": 1, "maxItems": 100 }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "externalId", "customer", "fulfillment", "lines" ] }, "quote": { "type": "object", "additionalProperties": false, "properties": { "currency": { "type": "string", "enum": [ "USD" ] }, "fulfillment": { "type": "string", "enum": [ "pickup" ] }, "pricingPolicy": { "type": "string", "enum": [ "published_list_no_discounts" ] }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "lines": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "productId": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "quantity": { "type": "string" }, "unitPrice": { "type": "string" }, "taxRate": { "type": "string" }, "subtotal": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "tax": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" }, "total": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" } }, "required": [ "productId", "name", "quantity", "unitPrice", "taxRate", "subtotal", "tax", "total" ] } } }, "required": [ "currency", "fulfillment", "pricingPolicy", "subtotal", "tax", "total", "lines" ] }, "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "id", "externalId", "status", "expiresAt", "customer", "quote", "paymentStatus", "invoiced", "payment", "invoice", "fulfillment" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "externalId": "web-7d2f4c9a", "status": "Ejemplo", "expiresAt": "2026-09-17T15:00:00.000Z", "customer": { "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "quantity": "2.0000" } ], "note": "" }, "quote": { "currency": "USD", "fulfillment": "pickup", "pricingPolicy": "published_list_no_discounts", "subtotal": "Ejemplo", "tax": "Ejemplo", "total": "Ejemplo", "lines": [ { "productId": "11111111-1111-4111-8111-111111111111", "name": "María González", "quantity": "2.0000", "unitPrice": "Ejemplo", "taxRate": "Ejemplo", "subtotal": "Ejemplo", "tax": "Ejemplo", "total": "Ejemplo" } ] }, "paymentStatus": "not_requested", "invoiced": false, "payment": null, "invoice": null, "fulfillment": { "status": "pendiente", "updatedAt": null } } ], "nextCursor": null } } ``` ## POST /admin/integrations/{id}/checkouts/{checkoutId}/confirm-transfer Conciliar transferencia recibida Requiere admin.configuracion + admin.bancos vigentes. El administrador verifica el banco antes de enviar. Monto exacto, referencia normalizada a mayúsculas única por empresa dentro de Commerce y motivo auditado. Idempotente con mismo cuerpo; no registra abono bancario contable ni emite factura. No aceptar esta declaración del comprador. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "checkoutId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "UUID POS del checkout, distinto de la referencia externa usada en los comandos." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "reference": { "type": "string", "minLength": 3, "maxLength": 120 }, "reason": { "type": "string", "minLength": 10, "maxLength": 500 } }, "required": [ "amount", "reference", "reason" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "paymentStatus", "invoiced", "payment", "invoice", "fulfillment" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/confirm-transfer" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "amount": "Ejemplo", "reference": "Ejemplo", "reason": "Ejemplo" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/confirm-transfer', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "amount": "Ejemplo", "reference": "Ejemplo", "reason": "Ejemplo" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "paymentStatus": "not_requested", "invoiced": false, "payment": null, "invoice": null, "fulfillment": { "status": "pendiente", "updatedAt": null } } } ``` ## POST /admin/integrations/{id}/checkouts/{checkoutId}/invoice Emitir venta desde pago conciliado Requiere admin.configuracion + pos.documentos, licencia y capacidad de facturación. Consumidor final 02 existente. Reutiliza motor documental, precios/permisos y FE. Stock/reserva/factura/pago atómicos, sin estación/turno. Si reserva expiró, comprueba stock disponible; falta de stock o cambio de precio conserva pago confirmado sin factura. Repetir devuelve factura original; no vuelve a cobrar. No garantiza aceptación PAC: revisar fiscalStatus y flujo fiscal administrativo. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "checkoutId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "UUID POS del checkout, distinto de la referencia externa usada en los comandos." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "terceroId": { "type": "string", "format": "uuid" } }, "required": [ "terceroId" ] }, "responseStatus": 201, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "paymentStatus", "invoiced", "payment", "invoice", "fulfillment" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/invoice" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "terceroId": "11111111-1111-4111-8111-111111111111" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/invoice', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "terceroId": "11111111-1111-4111-8111-111111111111" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "paymentStatus": "not_requested", "invoiced": false, "payment": null, "invoice": null, "fulfillment": { "status": "pendiente", "updatedAt": null } } } ``` ## POST /admin/integrations/{id}/checkouts/{checkoutId}/record-refund Registrar devolución bancaria realizada Requiere admin.configuracion + admin.bancos. Sólo pago confirmado sin factura. Registra importe completo, referencia de devolución y motivo verificados; no mueve dinero ni emite nota de crédito. Repetir mismo cuerpo es idempotente; bloquea emisión posterior y libera reserva. Si existe factura usar flujo documental de anulación/nota de crédito. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "checkoutId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "UUID POS del checkout, distinto de la referencia externa usada en los comandos." } ], "requestBody": { "type": "object", "additionalProperties": false, "properties": { "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "reference": { "type": "string", "minLength": 3, "maxLength": 120 }, "reason": { "type": "string", "minLength": 10, "maxLength": 500 } }, "required": [ "amount", "reference", "reason" ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "paymentStatus": { "type": "string", "enum": [ "not_requested", "pending", "confirmed", "applied", "refunded" ] }, "invoiced": { "type": "boolean" }, "payment": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "method": { "type": "string", "enum": [ "bank_transfer" ] }, "status": { "type": "string", "enum": [ "pending", "confirmed", "applied", "refunded" ] }, "amount": { "type": "string", "pattern": "^(?:0|[1-9]\\d{0,11})\\.\\d{2}$" }, "currency": { "type": "string", "enum": [ "USD" ] }, "instructions": { "type": "string" }, "reference": { "type": "string", "nullable": true } }, "required": [ "id", "method", "status", "amount", "currency", "instructions", "reference" ], "nullable": true }, "invoice": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "number": { "type": "string" }, "status": { "type": "string", "nullable": true }, "fiscalStatus": { "type": "integer", "nullable": true } }, "required": [ "id", "number", "status", "fiscalStatus" ], "nullable": true }, "fulfillment": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "paymentStatus", "invoiced", "payment", "invoice", "fulfillment" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/record-refund" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "amount": "Ejemplo", "reference": "Ejemplo", "reason": "Ejemplo" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/record-refund', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "amount": "Ejemplo", "reference": "Ejemplo", "reason": "Ejemplo" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "paymentStatus": "not_requested", "invoiced": false, "payment": null, "invoice": null, "fulfillment": { "status": "pendiente", "updatedAt": null } } } ``` ## GET /admin/integrations/{id}/customers Buscar cliente para emitir factura Requiere admin.configuracion. Hasta 50 clientes activos consumidor final 02 de la empresa, por nombre/código/RUC. nextCursor permite continuar; no crea clientes ni elige automáticamente al comprador. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "search", "in": "query", "schema": { "type": "string", "maxLength": 100, "default": "" } }, { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "nombre": { "type": "string" }, "codigo": { "type": "string" }, "ruc": { "type": "string", "nullable": true } }, "required": [ "id", "nombre", "codigo", "ruc" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/customers" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/customers', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "id": "11111111-1111-4111-8111-111111111111", "nombre": "Mi tienda", "codigo": "Ejemplo", "ruc": null } ], "nextCursor": null } } ``` ## POST /admin/integrations/{id}/checkouts/{checkoutId}/fulfillment Avanzar preparación y retiro Requiere admin.configuracion y pos.documentos vigentes. Sólo pago aplicado y factura no anulada. Secuencia pendiente → preparando → listo → entregado, sin saltos ni retrocesos. commandId UUID estable por transición: mismo cuerpo devuelve recibo histórico y otro cuerpo genera 409. entregado requiere receivedBy y comprobante fiscal disponible si FE está activa (mismo criterio del recibo fiscal, incluida contingencia firmada). Actor/nota/receptor se auditan localmente; no se publican. Para estado actual, comando status nuevo vía POS o gateway. Autenticación: JWT administrativo con permisos vigentes. ```json { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "ID de integración, no ID de empresa." }, { "name": "checkoutId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "UUID POS del checkout, distinto de la referencia externa usada en los comandos." } ], "requestBody": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "preparando" ] }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "commandId", "status" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "listo" ] }, "note": { "type": "string", "maxLength": 500, "default": "" } }, "required": [ "commandId", "status" ] }, { "type": "object", "additionalProperties": false, "properties": { "commandId": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "entregado" ] }, "note": { "type": "string", "maxLength": 500, "default": "" }, "receivedBy": { "type": "string", "minLength": 3, "maxLength": 150 } }, "required": [ "commandId", "status", "receivedBy" ] } ] }, "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "status": { "type": "string", "enum": [ "pendiente", "preparando", "listo", "entregado" ] }, "updatedAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "status", "updatedAt" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/fulfillment" \ -H "Authorization: Bearer $ISTMO_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "commandId": "11111111-1111-4111-8111-111111111111", "status": "preparando", "note": "" }' ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/admin/integrations/11111111-1111-4111-8111-111111111111/checkouts/11111111-1111-4111-8111-111111111111/fulfillment', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { Authorization: 'Bearer ' + process.env.ISTMO_ADMIN_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ "commandId": "11111111-1111-4111-8111-111111111111", "status": "preparando", "note": "" }) } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "status": "pendiente", "updatedAt": null } } ``` ## GET /webhook-deliveries Consultar entregas webhook (gateway) Sólo gateway, no POS directo. Paginación UUID para inspección, no cursor incremental. Evento sin PII: consultar estado actual con un nuevo comando status. No devuelve destino ni secreto. Autenticación: x-istmo-commerce-key · scope orders:read. ```json { "parameters": [ { "name": "after", "in": "query", "schema": { "type": "string", "format": "uuid" }, "description": "nextCursor previo. Orden UUID ascendente; no es un cursor de sincronización incremental." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "items": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "event": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "version": { "type": "string", "enum": [ "1" ] }, "integrationId": { "type": "string", "format": "uuid" }, "type": { "type": "string", "enum": [ "checkout.reserved", "checkout.expirado", "checkout.cancelado", "checkout.vendido", "checkout.invoiced", "checkout.fulfillment", "transfer.requested", "transfer.confirmed", "transfer.refund_recorded", "invoice.updated" ] }, "occurredAt": { "type": "string", "format": "date-time" }, "data": { "type": "object", "additionalProperties": false, "properties": { "checkoutId": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$" } }, "required": [ "checkoutId" ] } }, "required": [ "id", "version", "integrationId", "type", "occurredAt", "data" ] }, "status": { "type": "string", "enum": [ "pending", "retry", "processing", "delivered", "failed", "disabled" ] }, "attempts": { "type": "integer", "minimum": 0 }, "httpStatus": { "type": "integer", "nullable": true }, "nextAttemptAt": { "type": "string", "format": "date-time" }, "deliveredAt": { "type": "string", "format": "date-time", "nullable": true } }, "required": [ "event", "status", "attempts", "httpStatus", "nextAttemptAt", "deliveredAt" ] } }, "nextCursor": { "type": "string", "format": "uuid", "nullable": true } }, "required": [ "items", "nextCursor" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X GET "$ISTMO_COMMERCE_BASE_URL/webhook-deliveries" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/webhook-deliveries', { method: 'GET', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "items": [ { "event": { "id": "11111111-1111-4111-8111-111111111111", "version": "1", "integrationId": "11111111-1111-4111-8111-111111111111", "type": "checkout.reserved", "occurredAt": "2026-09-17T15:00:00.000Z", "data": { "checkoutId": "web-7d2f4c9a" } }, "status": "pending", "attempts": 0, "httpStatus": null, "nextAttemptAt": "2026-09-17T15:00:00.000Z", "deliveredAt": null } ], "nextCursor": null } } ``` ## POST /webhook-deliveries/{eventId}/retry Reintentar entrega fallida (gateway) Sólo gateway. Reactiva failed/disabled con destino actualmente configurado y contador cero. Pending/retry/processing no se duplican; La respuesta conserva el estado actual de trabajos activos. Delivered devuelve 409. Requiere webhook habilitado. Sin body. Autenticación: x-istmo-commerce-key · scope orders:write. ```json { "parameters": [ { "name": "eventId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ], "responseStatus": 200, "responseBody": { "type": "object", "additionalProperties": false, "properties": { "success": { "type": "boolean", "enum": [ true ] }, "data": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "pending", "retry", "processing" ] } }, "required": [ "id", "status" ] } }, "required": [ "success", "data" ] } } ``` ### Ejemplo cURL ```bash curl -X POST "$ISTMO_COMMERCE_BASE_URL/webhook-deliveries/{eventId}/retry" \ -H "x-istmo-commerce-key: $ISTMO_COMMERCE_KEY" ``` ### Ejemplo Node.js ```js // Node 20+ · ejecutar sólo en el servidor const response = await fetch( process.env.ISTMO_COMMERCE_BASE_URL + '/webhook-deliveries/{eventId}/retry', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000), headers: { 'x-istmo-commerce-key': process.env.ISTMO_COMMERCE_KEY } } ); const result = await response.json(); if (!response.ok) throw new Error(result.error?.code); // En un timeout, conservar externalId y consultar su estado. ``` ### Respuesta ilustrativa ```json { "success": true, "data": { "id": "11111111-1111-4111-8111-111111111111", "status": "pending" } } ``` --- Fuente: https://admin.istmotech.com/docs/local-cloud/index.md # Instalaciones locales y cloud El mismo contrato operativo, con distintas necesidades de conectividad. ## Administración desde IstmoAdmin Configuración → Tienda online permite crear integraciones, rotar/revocar claves, buscar y publicar productos y consultar/rechazar solicitudes recibidas por el POS. La clave recién creada sólo se muestra una vez; no se guarda en el navegador. Para guardar un puente desde la pantalla, el operador configura COMMERCE_BRIDGE_ENCRYPTION_KEY (64 caracteres hexadecimales aleatorios) y COMMERCE_GATEWAY_ALLOWED_ORIGINS (orígenes HTTPS separados por coma) en el servidor. Las credenciales se cifran con AES-256-GCM y contexto de empresa/sucursal/integración. Respaldar la llave junto con la BD. Guardar no confirma conectividad. El diagnóstico indica último intento, último ciclo correcto y error. Una pausa sólo queda confirmada tras recibir respuesta del gateway. Los vínculos de COMMERCE_CONNECTORS tienen prioridad y se administran desde el entorno. ## Configuración y revocación del puente Un error de autorización local intenta retirar el catálogo del gateway. Sin conexión, esa actualización puede tardar: la revocación cloud debe hacerse también en el gateway. HTTP 503 indica catálogo aún no sincronizado o servicio pausado. Los rechazos por producto retirado antes de importar no generan pedido POS; posOrderId queda null. La cola conserva pedidos tras reiniciar. El gateway vuelve a ofrecer pendientes después de 30 segundos para recuperar confirmaciones perdidas y propagar rechazos. Mantener reloj sincronizado, respaldos, política de retención y límites en el proxy HTTPS. - El operador crea la integración local con los tres scopes y obtiene installationId de GET /api/v1/empresa/connection-identity con sesión autorizada. - Aprovisionar el servicio Commerce gateway por HTTPS y una base PostgreSQL exclusiva de comercio; no reutilizar la base de licencias. - COMMERCE_GATEWAY_STORES configura UUID de tienda, binding con installationId/integrationId, siteKey, connectorKey, scopes y enabled. Conservar el binding: no se puede reasignar una tienda existente. - COMMERCE_CONNECTORS en el entorno POS configura gatewayUrl, el mismo binding, connectorKey y localKey. El conector hace consultas salientes cada 15 segundos y recupera hasta 10 pedidos por ciclo. Requiere reiniciar el proceso. - Guardar siteKey sólo en el backend de tienda; localKey sólo en POS y connectorKey sólo en gateway/POS. No publicar estos secretos. - Para revocar inmediatamente, enabled=false en gateway y reiniciar todas sus réplicas. Rotar siteKey actualiza gateway/Vercel; rotar connectorKey actualiza gateway/POS. Configuración → Tienda online permite administrar la integración y guardar el vínculo cifrado; las claves del gateway siguen bajo control del operador. ## POS alojado en cloud La tienda llama por HTTPS al servidor operativo actualizado. La credencial queda restringida a empresa, sucursal, almacén y scopes. La BD de licencias permanece separada de los datos operativos. Se requiere desplegar pos-server con Commerce, aplicar su migración y aprovisionar la integración. Una página de documentación accesible no demuestra que esos pasos se hayan completado. ## POS instalado en el negocio El servidor local ofrece el mismo contrato dentro de su red después de actualizarlo. Para recibir solicitudes desde una web alojada en Vercel hace falta un gateway de comercio y un conector que establezca conexiones salientes desde el negocio. El puente está implementado y requiere aprovisionar gateway HTTPS, base dedicada y credenciales de conector. No abrir PostgreSQL, exponer directamente el puerto del POS ni configurar Vercel contra localhost o una IP privada como solución. ## Puente saliente: despliegue requerido El gateway ofrece las mismas cinco operaciones públicas de catálogo, pedidos y checkout-commands, con x-istmo-commerce-key; los endpoints /admin permanecen en el POS. Webhooks firmados opcionales: ver la guía Webhooks. El operador configura el gateway y el conector antes de conectar Vercel. - Vinculación autorizada entre tienda, empresa e instalación, con credenciales independientes. - Catálogo completo con bajas explícitas, observedAt y lastSyncAt; durante desconexión puede estar desactualizado. - Cola durable en gateway y descarga saliente con confirmación de recepción, reintentos e idempotencia. - Distinguir recibido_gateway de pendiente_confirmacion: sólo el segundo tiene un recibo POS. El id público permanece estable; posOrderId identifica el recibo local. - Durante desconexión: solicitudes pendientes sin cobro ni promesa de stock. Las ventas físicas siguen funcionando. --- Fuente: https://admin.istmotech.com/docs/vue-vercel/index.md # Vue y Vercel Conecta el backend de tu sitio sin enviar secretos al navegador. ## Separación de responsabilidades El navegador consume rutas del propio sitio. El backend valida las peticiones, aplica límites, conserva los intentos y usa la credencial dedicada. No asumir Next.js o Nuxt: adaptar los handlers al framework real del proyecto. ```text Vue → backend del propio sitio en Vercel → API Istmo Commerce por HTTPS ``` ## Configuración de servidor Estas variables no llevan prefijo VITE_. No registrar sus valores ni cabeceras de autenticación. El cliente Node descargable requiere Node 20+, usa timeout de 10 segundos y no sigue redirecciones ni reintenta escrituras automáticamente. ```text ISTMO_COMMERCE_BASE_URL=https://TU-SERVIDOR-POS/api/v1/commerce ISTMO_COMMERCE_KEY=SECRETO-ENTREGADO-AL-CREAR-LA-INTEGRACION ``` - [Descargar cliente Node](https://admin.istmotech.com/docs/istmo-commerce.mjs) ## Llamadas internas del backend savedAttempt representa un intento persistido previamente con externalId y cuerpo estables. El fragmento no es un handler público completo. Almacenar esa información de forma durable; no depender de la memoria de una función serverless. Comprobar la propiedad de cada pedido por sesión/comprador antes de consultar su estado. Nunca permitir que el navegador seleccione URL remota, credencial, empresa o un endpoint administrativo. ```js import { createCommerceClient } from './istmo-commerce.mjs'; const commerce = createCommerceClient({ baseUrl: process.env.ISTMO_COMMERCE_BASE_URL, key: process.env.ISTMO_COMMERCE_KEY, }); const catalog = await commerce.catalog({ limit: 50 }); const receipt = await commerce.createOrder(savedAttempt); // Tras una respuesta perdida, conservar savedAttempt: const recovered = await commerce.order(savedAttempt.externalId); ``` ## Interfaz del comprador - Mostrar “Solicitar pedido” y “Solicitud recibida, pendiente de confirmación”. - checkoutEnabled=false deshabilita cobros automáticos. ACH manual requiere quote vigente, action=payment e instrucciones habilitadas por administración. - Tras timeout, conservar el intento y mostrar que su estado está por comprobar. - No guardar datos personales innecesarios en el navegador. No simular conexión local o una compra pagada. --- Fuente: https://admin.istmotech.com/docs/pedidos/index.md # Pedidos y recuperación Referencias estables, estados explícitos y reintentos sin duplicados. ## Cotización y reserva temporal POST /checkout-commands acepta commandId UUID, checkoutId y action quote/payment/status/cancel. quote incluye order con el formato de /orders y externalId igual a checkoutId. El POS calcula precio de la lista publicada e ITBMS configurado con Decimal; sólo retiro pickup, productos simples y un impuesto explícito (incluido exento). No aplica promociones ni convenios. La reserva dura cinco minutos desde que la procesa el POS. Gateway devuelve pending hasta recibir el resultado. Si lleva más de 45 segundos sin catálogo del POS rechaza nuevas cotizaciones; si el POS recibe una solicitud nueva de más de 60 segundos, la rechaza sin reservar. GET /checkout-commands/{commandId} recupera el recibo histórico e inmutable. Para saber el estado actual enviar action=status con otro commandId. Un recibo antiguo con stockReserved=true no acredita una reserva actual: comprobar expiresAt y obtener un estado reciente. Conservar commandId y cuerpo ante timeout. La misma referencia no renueva una reserva vencida. action=cancel libera unidades; sin conexión queda pendiente hasta el POS. Las reservas vencidas se liberan por el proceso POS, incluso sin gateway. Las salidas físicas que invaden reservas activas reciben 409 COMMERCE_STOCK_RESERVED. La conversión a venta requiere transferencia conciliada y emisión administrativa; checkoutEnabled=false no habilita cobros automáticos. ## ACH / transferencia con conciliación El administrador habilita PUT /admin/integrations/{id}/payment-settings con instrumento ACH/TRANSFERENCIA USD activo e instrucciones para el comprador. Consultar en GET settings. Estas instrucciones son públicas para la tienda: no incluir contraseñas ni credenciales bancarias. Configuración → Tienda online → Administrar pagos y entregas permite guardar instrucciones, conciliar, emitir y registrar devoluciones verificadas. Tras cotizar, enviar action=payment con UUID nuevo para obtener instrucciones y amount calculado por POS. paymentStatus=pending NO acredita un pago. Ante timeout consultar el mismo commandId. Para el estado actual enviar action=status con otro UUID. Una captura o declaración del comprador nunca confirma el cobro. El administrador comprueba el banco y llama confirm-transfer con amount exacto, reference y reason. Requiere admin.configuracion y admin.bancos vigentes. La referencia se normaliza a mayúsculas y no se reutiliza entre checkouts Commerce de la empresa. Esto registra evidencia de conciliación; no importa ni concilia movimientos del módulo Bancos. Con pago confirmed, POST invoice con terceroId consumidor final 02 requiere admin.configuracion y pos.documentos. Reutiliza facturación y FE existentes, sin caja/turno. La reserva, salida de inventario y factura se confirman juntas. Reintentos devuelven la misma factura. applied significa vinculado a factura; fiscalStatus no implica aceptación automática del PAC. Si la reserva venció, se verifica stock disponible al emitir. Falta de stock, cambio de precio/impuesto o receptor incompatible dejan el pago confirmed sin factura: no pedir otro pago. Listar /admin/integrations/{id}/checkouts para resolver. Se puede reponer/corregir y repetir emisión, o devolver el dinero por el banco y registrar record-refund con evidencia. Ese endpoint sólo registra una devolución ya realizada, completa y sin factura; no envía fondos. Una venta emitida usa los flujos existentes de anulación/nota de crédito y devolución; no se borra ni se vuelve a facturar desde el checkout. Cancelar por API de tienda no está permitido después de conciliar. Hay preparación y retiro auditados; aún no hay envíos, notificaciones, tarjeta ni Yappy web. ## Preparación y retiro del pedido Después de emitir, POST /admin/integrations/{id}/checkouts/{checkoutId}/fulfillment avanza de pendiente a preparando, listo y entregado. Requiere admin.configuracion y pos.documentos; no acepta la clave pública de tienda. El mismo commandId/cuerpo recupera el resultado anterior sin duplicar eventos. Se rechazan saltos, retrocesos, factura anulada o pago sin aplicar. Entregado requiere el nombre de quien retira y un comprobante fiscal disponible cuando corresponde. La pantalla pide confirmación y registra al operador; no envía correos ni mensajes. Ante timeout bloquea cambios hasta actualizar el estado. La nota interna y el nombre de quien retira no se publican al sitio. El backend Vue obtiene fulfillment.status y updatedAt en el resultado de un comando status nuevo, a través del mismo gateway local. GET de un comando viejo conserva su estado histórico. No suponer entrega por factura emitida, reserva vencida o pago aplicado. ## Crear una solicitud POST /orders requiere orders:write. Generar externalId una sola vez antes del primer envío y conservarlo junto al contenido. El mismo ID es único por integración. ```json { "externalId": "web-7d2f4c9a", "customer": { "name": "María González", "email": "maria@example.com", "phone": "6000-1234" }, "fulfillment": { "method": "pickup" }, "lines": [ { "productId": "22222222-2222-4222-8222-222222222222", "quantity": "2.0000" } ], "note": "Retiro por la tarde" } ``` Sustituir el UUID por un producto publicado. Para entrega: fulfillment.method=delivery y address. No calcula ni acepta un costo de envío. No enviar precios, totales ni afirmaciones de pago. ## Interpretar HTTP 202 202 significa solicitud persistida pendiente de confirmación, no venta confirmada. La respuesta declara paymentStatus=not_requested, stockReserved=false e invoiced=false. Estados disponibles: recibido_gateway (sólo gateway), pendiente_confirmacion y rechazado. El administrador puede rechazar con motivo; repetir el mismo rechazo es idempotente. /orders no reserva. checkout-commands permite cotizar y reservar por separado; el flujo ACH y su emisión administrativa se realizan sobre checkouts. ## Recuperar una respuesta perdida - Consultar GET /orders/{externalId} con orders:read o reenviar el mismo cuerpo con el mismo externalId. - Una respuesta 404 significa que no hay solicitud visible con esa referencia en esa integración en ese momento. Mantener el mismo ID para el reintento. - Mismo contenido devuelve el mismo pedido y su estado actual. Otro contenido con la referencia utilizada devuelve 409. - Cantidades equivalentes (2 y 2.0000) y distinto orden de líneas son equivalentes; repetir un producto en varias líneas se rechaza. - La referencia se conserva mientras exista el pedido, incluso rechazado. No reciclarla para otra compra. ## Errores y límites Las respuestas usan {success:true,data} o {success:false,error:{code,message}}. Los pedidos y las respuestas administrativas no deben almacenarse en cachés públicas. - 400: revisar esquema, cantidades, productos publicados o parámetros. - 401: credencial ausente, inválida, rotada o revocada. - 403: scope, licencia, permisos o contexto de empresa/sucursal/almacén. - 409: conciliar referencia/contenido o estado; no crear otro ID automáticamente. - 429: esperar; limitador básico por IP y proceso: 240 solicitudes/minuto en POS, 600 en gateway. No es una cuota distribuida. - Red/5xx: resultado incierto; conservar referencia y comprobar antes de repetir. --- Fuente: https://admin.istmotech.com/docs/ia/index.md # Integrar con una IA Un punto de entrada breve y contratos que un agente puede leer directamente. ## Recursos legibles por agentes llms.txt es una convención propuesta para facilitar el descubrimiento de documentación por agentes. No garantiza indexación ni que todos los modelos la utilicen. Cada página enlaza su versión Markdown; el contrato sigue siendo la referencia de endpoints y esquemas. - [Índice llms.txt](https://admin.istmotech.com/llms.txt) - [Texto completo llms-full.txt](https://admin.istmotech.com/llms-full.txt) - [Contrato OpenAPI](https://admin.istmotech.com/docs/openapi.json) - [Cliente Node](https://admin.istmotech.com/docs/istmo-commerce.mjs) ## Prompt para tu agente ```text Lee https://admin.istmotech.com/llms.txt y después la guía de inicio, la guía Vue/Vercel y https://admin.istmotech.com/docs/openapi.json. Integra el sitio conservando su diseño y usando credenciales sólo en servidor. Inspecciona primero el framework real. Implementa catálogo y solicitudes pendientes, con externalId estable y recuperación de respuestas perdidas. No inventes endpoints ni cobros automáticos: checkoutEnabled=false. ACH manual requiere leer la guía de pedidos y conciliación. La instalación local requiere desplegar y configurar el puente saliente. No declares conectividad de producción sin comprobar servidor, migración, credenciales y transporte reales. Protege la consulta de pedidos por comprador. ``` ## Orden recomendado de lectura - Primeros pasos: alcance, credenciales y publicación. - Local/cloud: decidir transporte antes de configurar una URL. - OpenAPI: métodos, autenticación y esquemas exactos. - Pedidos: idempotencia, errores y recuperación. - Vue/Vercel: separación entre navegador y backend. - [Propuesta llms.txt](https://llmstxt.org/) --- Fuente: https://admin.istmotech.com/docs/webhooks/index.md # Webhooks Cambios de checkout desde instalaciones locales o cloud mediante el gateway. ## Entrega durable El POS conserva eventos junto a cada cambio de reserva, transferencia, factura y entrega. El conector saliente los lleva al gateway, que los reintenta por HTTPS. Una desconexión demora la notificación; no pierde el evento confirmado. Requiere la migración Commerce de webhooks, conector actualizado y gateway configurado. POS cloud también puede usar este puente; no hay envío directo desde POS. La entrega puede repetirse o llegar fuera de orden. El evento contiene id, version, integrationId, type, occurredAt y data.checkoutId (referencia externa). No contiene referencias bancarias, datos del comprador ni instrucciones privadas. Consulta un nuevo comando action=status para conocer el estado actual. ## Configurar el receptor El operador añade webhook:{url,secret,enabled:true} a la tienda en COMMERCE_GATEWAY_STORES. URL HTTPS con nombre DNS público IPv4, puerto 443, sin usuario, query ni fragmento. El transporte bloquea redes privadas y redirecciones. secret usa el formato istmo_whk_ seguido de 64 caracteres hexadecimales aleatorios; se guarda únicamente en los secretos de gateway y backend de la tienda. Guardar destino y secreto es configuración del operador, no un endpoint público. Reiniciar todas las réplicas tras cambios. Sin receptor, eventos quedan disabled y pueden activarse mediante reintento manual. Eventos anteriores a la migración no se generan retroactivamente. No habilitar envíos a un destino sin verificar su propietario. ## Verificar antes de procesar Firma HMAC-SHA256 de timestamp + punto + cuerpo exacto, cabecera x-istmo-signature con prefijo v1=. El cliente valida tolerancia de cinco minutos y comparación constante; mantener reloj sincronizado. No volver a serializar JSON antes de verificar. x-istmo-event-id es informativa: usar el id dentro del cuerpo firmado. Si falla firma responder 401; si no puede persistir responder 503. Un evento ya persistido puede responder 204. Si status está pendiente por desconexión, reintentar el trabajo interno, no borrar el evento. El receptor no confirma pagos ni emite facturas. ```js import { verifyCommerceWebhook } from './istmo-commerce.mjs'; const event = verifyCommerceWebhook({ rawBody, // Buffer de bytes originales, antes del parser JSON secret: process.env.ISTMO_WEBHOOK_SECRET, timestamp: req.headers['x-istmo-timestamp'], signature: req.headers['x-istmo-signature'], }); // Comprobar integrationId contra la tienda configurada. // Guardar event.id en una bandeja durable con índice UNIQUE. // Confirmar esa transacción antes de responder HTTP 204. // Un trabajador consulta status; eventos repetidos no repiten trabajo. ``` ## Diagnóstico y recuperación GET /webhook-deliveries muestra estado y contador con scope orders:read. POST /webhook-deliveries/{eventId}/retry requiere orders:write y reactiva failed/disabled. Ambas operaciones sólo existen en gateway. Las entregadas devuelven 409 al reintentar. La lista ordena UUID; recorrerla completa para inspección, no guardar el último UUID como cursor de nuevos eventos. Se intentan hasta ocho envíos por ciclo manual, con timeout de ocho segundos y espera exponencial de hasta una hora. Sólo HTTP 2xx confirma entrega. Un proceso interrumpido recupera trabajos tras la concesión de 60 segundos; puede duplicarse un envío cuya respuesta se perdió. Si cambia el destino, trabajos pendientes del destino anterior quedan failed antes de enviarse y necesitan reintento explícito. Pausar el puente o deshabilitar la tienda detiene nuevos envíos; una petición ya en curso puede terminar. Respaldar ambas bases. No borrar recibos ni eventos sin una política de retención coordinada con la deduplicación del receptor. No se envían correos ni SMS; estas notificaciones están dirigidas al backend del sitio.