{
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/external/ping": {
      "post": {
        "operationId": "External_ping",
        "x-papedir-scopes": [],
        "summary": "Verificar la llave",
        "description": "Confirma que la llave es válida y devuelve el negocio, sus scopes y el estado de la integración. Límite: 30 por minuto.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PingResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/external/stock": {
      "put": {
        "operationId": "External_applyStock",
        "x-papedir-scopes": [
          "stock:write"
        ],
        "summary": "Enviar existencias",
        "description": "Fija la existencia **absoluta** de hasta 500 productos en una transacción. Un ítem malo no tumba el lote: cada ítem trae su `status`. A lo que envías se le restan las ventas de papedir que tu sistema todavía no confirmó (`reserved`). Límite: 60 por minuto.\n\nScope requerido: `stock:write`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "required": true,
            "in": "header",
            "description": "Única por lote (8 a 100 caracteres: letras, números, `. _ : -`). Repítela en los reintentos del mismo lote.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,100}$",
              "example": "lote-2026-10-08T15-00-00Z"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalStockDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalStockResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED` (con `details`), `IDEMPOTENCY_KEY_REQUIRED` o `EXTERNAL_STOCK_TOO_MANY_ITEMS`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/external/catalog": {
      "get": {
        "operationId": "External_catalog",
        "x-papedir-scopes": [
          "catalog:read"
        ],
        "summary": "Leer el catálogo",
        "description": "Productos vivos del negocio en orden de `id`, por páginas. `tracked: false` = papedir no lleva inventario de ese producto. Límite: 60 por minuto.\n\nScope requerido: `catalog:read`.",
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "description": "`next_cursor` de la página anterior (0 o vacío = desde el principio).",
            "schema": {
              "minimum": 0,
              "maximum": 2147483647,
              "example": 0,
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Tamaño de página (100 por defecto).",
            "schema": {
              "minimum": 1,
              "maximum": 500,
              "default": 100,
              "example": 100,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalCatalogPageDto"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/external/catalog/mapping": {
      "patch": {
        "operationId": "External_map",
        "x-papedir-scopes": [
          "stock:write"
        ],
        "summary": "Cruzar productos por SKU",
        "description": "Asigna a cada producto de papedir (buscado por SKU) el id que tiene en tu sistema; `null` quita el cruce. Resultado por producto. Límite: 30 por minuto.\n\nScope requerido: `stock:write`.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalMappingDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalMappingResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "409": {
            "description": "`EXTERNAL_ID_TAKEN`: otro cruce simultáneo tomó el mismo id; reintenta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/external/orders": {
      "get": {
        "operationId": "External_orders",
        "x-papedir-scopes": [
          "orders:read"
        ],
        "summary": "Leer eventos de pedido",
        "description": "Feed de eventos de pedido en orden (respaldo del webhook, o vía principal si no hay webhook). Los ya confirmados siguen apareciendo: guarda tu cursor y deduplica por `id`. Límite: 60 por minuto.\n\nScope requerido: `orders:read`.",
        "parameters": [
          {
            "name": "after",
            "required": false,
            "in": "query",
            "description": "`next_cursor` de la página anterior (0 = desde el principio).",
            "schema": {
              "minimum": 0,
              "example": 0,
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Tamaño de página (100 por defecto).",
            "schema": {
              "minimum": 1,
              "maximum": 500,
              "default": 100,
              "example": 100,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalOrdersPageDto"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/external/orders/ack": {
      "post": {
        "operationId": "External_ackOrders",
        "x-papedir-scopes": [
          "orders:read"
        ],
        "summary": "Confirmar eventos",
        "description": "Marca como confirmados los eventos que procesaste por el feed: el webhook ya no los manda y dejan de reservar existencia. Límite: 60 por minuto.\n\nScope requerido: `orders:read`.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalOrdersAckDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExternalOrdersAckResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY` o `API_KEY_REVOKED`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "`MODULE_DISABLED`, `INSUFFICIENT_SCOPE` o `INTEGRATION_DISABLED` (escrituras).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`. Espera los segundos de `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Segundos que hay que esperar antes de reintentar.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`. Reintenta con espera; cita el `request_id` a soporte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseDto"
                }
              }
            }
          }
        },
        "tags": [
          "Inventario externo"
        ],
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    }
  },
  "info": {
    "title": "API pública de papedir",
    "description": "API para conectar sistemas de terceros con un negocio de papedir.\n\n- Autenticación: `Authorization: Bearer pdk_live_…` (una llave por sistema, con scopes).\n- Errores: siempre `{ \"error\": { \"code\", \"message\", \"details\"? }, \"request_id\" }` y cabecera `X-Request-Id`.\n- Versiones: `/api/v1` es estable; solo se agregan campos, rutas y eventos (ignora lo que no conozcas). Un cambio que rompe es `/api/v2`, con 6 meses de aviso (`Deprecation`/`Sunset`).\n\nGuías y ejemplos: https://docs.papedir.com",
    "version": "1.0.0",
    "contact": {
      "name": "papedir",
      "url": "https://docs.papedir.com",
      "email": "soporte@papedir.com"
    },
    "license": {
      "name": "Términos de uso de papedir",
      "url": "https://www.papedir.com/terminos"
    }
  },
  "tags": [
    {
      "name": "Inventario externo",
      "description": "Tu sistema es la fuente de verdad de la existencia."
    },
    {
      "name": "Webhooks",
      "description": "Eventos que papedir envía a la URL del negocio."
    }
  ],
  "servers": [
    {
      "url": "https://api.pedidos.transformit.com.co",
      "description": "Producción"
    },
    {
      "url": "http://127.0.0.1:3001",
      "description": "Local"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "scheme": "bearer",
        "bearerFormat": "pdk_live_<prefijo>_<secreto>",
        "type": "http",
        "description": "Llave de API del negocio. La crea el negocio en su panel y se muestra una sola vez."
      }
    },
    "schemas": {
      "PingIntegrationDto": {
        "type": "object",
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "bidirectional"
            ],
            "example": "inbound"
          },
          "enabled": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "direction",
          "enabled"
        ]
      },
      "PingResponseDto": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11",
            "description": "Negocio al que pertenece la llave."
          },
          "key_prefix": {
            "type": "string",
            "example": "pdk_live_ab12cd34"
          },
          "scopes": {
            "type": "array",
            "example": [
              "stock:write",
              "catalog:read"
            ],
            "items": {
              "type": "string",
              "enum": [
                "stock:write",
                "catalog:read",
                "orders:read"
              ]
            }
          },
          "integration": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PingIntegrationDto"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "ok",
          "tenant_id",
          "key_prefix",
          "scopes",
          "integration"
        ]
      },
      "ErrorDetailDto": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "example": "items.0.stock",
            "description": "Campo con el problema."
          },
          "issue": {
            "type": "string",
            "example": "must not be less than 0"
          }
        },
        "required": [
          "field",
          "issue"
        ]
      },
      "ErrorBodyDto": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "INVALID_API_KEY",
              "API_KEY_REVOKED",
              "MODULE_DISABLED",
              "INSUFFICIENT_SCOPE",
              "INTEGRATION_DISABLED",
              "IDEMPOTENCY_KEY_REQUIRED",
              "EXTERNAL_STOCK_TOO_MANY_ITEMS",
              "EXTERNAL_STOCK_ITEMS_REQUIRED",
              "STOCK_IDEMPOTENCY_KEY_REQUIRED",
              "EXTERNAL_ID_TAKEN",
              "VALIDATION_FAILED",
              "BAD_REQUEST",
              "UNAUTHORIZED",
              "FORBIDDEN",
              "NOT_FOUND",
              "CONFLICT",
              "RATE_LIMITED",
              "INTERNAL_ERROR"
            ],
            "example": "VALIDATION_FAILED"
          },
          "message": {
            "type": "string",
            "example": "La petición no cumple el formato."
          },
          "details": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorDetailDto"
            }
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "ErrorResponseDto": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBodyDto"
          },
          "request_id": {
            "type": "string",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11",
            "description": "Mismo valor que la cabecera `X-Request-Id`; sirve para pedir soporte."
          }
        },
        "required": [
          "error",
          "request_id"
        ]
      },
      "ExternalStockItemDto": {
        "type": "object",
        "properties": {
          "sku": {
            "type": "string",
            "description": "SKU del producto en papedir. Obligatorio si no viene `external_id`.",
            "minLength": 1,
            "maxLength": 50,
            "example": "PAN-02"
          },
          "external_id": {
            "type": "string",
            "description": "Id del producto en tu sistema (asignado con `PATCH /catalog/mapping`). Gana sobre `sku`.",
            "minLength": 1,
            "maxLength": 100,
            "example": "b3f1c2d4-0000-4000-8000-000000000001"
          },
          "stock": {
            "type": "number",
            "description": "Existencia absoluta (no un delta), hasta 3 decimales.",
            "minimum": 0,
            "maximum": 99999999999.999,
            "example": 12
          },
          "version": {
            "type": "integer",
            "description": "Entero que crece con cada cambio en tu sistema (contador o `updated_at` en ms). Lo menor o igual a lo último aplicado se descarta como `stale`.",
            "minimum": 0,
            "example": 1728399600000
          },
          "observed_at": {
            "type": "string",
            "description": "Cuándo se midió la existencia (ISO 8601). Se usa si no hay `version`.",
            "format": "date-time",
            "example": "2026-10-08T15:00:00Z"
          }
        },
        "required": [
          "stock"
        ]
      },
      "ExternalStockDto": {
        "type": "object",
        "properties": {
          "items": {
            "minItems": 1,
            "maxItems": 500,
            "description": "Un ítem por producto; el mismo producto dos veces en un lote queda `invalid`.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalStockItemDto"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "ExternalStockResultDto": {
        "type": "object",
        "properties": {
          "index": {
            "type": "number",
            "description": "Posición del ítem en el lote enviado (desde 0).",
            "example": 0
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "b3f1c2d4-0000-4000-8000-000000000001"
          },
          "sku": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "menu_item_id": {
            "type": [
              "number",
              "null"
            ],
            "example": 43
          },
          "status": {
            "type": "string",
            "enum": [
              "applied",
              "duplicate",
              "stale",
              "not_found",
              "untracked",
              "invalid"
            ],
            "example": "applied"
          },
          "stock_after": {
            "type": [
              "number",
              "null"
            ],
            "example": 12
          },
          "reserved": {
            "type": [
              "number",
              "null"
            ],
            "description": "Unidades vendidas en papedir que tu sistema todavía no confirmó y se restaron al absoluto.",
            "example": 0
          }
        },
        "required": [
          "index",
          "external_id",
          "sku",
          "menu_item_id",
          "status",
          "stock_after"
        ]
      },
      "ExternalStockSummaryDto": {
        "type": "object",
        "properties": {
          "applied": {
            "type": "number",
            "example": 2
          },
          "duplicate": {
            "type": "number",
            "example": 0
          },
          "stale": {
            "type": "number",
            "example": 0
          },
          "not_found": {
            "type": "number",
            "example": 0
          },
          "untracked": {
            "type": "number",
            "example": 0
          },
          "invalid": {
            "type": "number",
            "example": 0
          }
        },
        "required": [
          "applied",
          "duplicate",
          "stale",
          "not_found",
          "untracked",
          "invalid"
        ]
      },
      "ExternalStockResponseDto": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalStockResultDto"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/ExternalStockSummaryDto"
          }
        },
        "required": [
          "results",
          "summary"
        ]
      },
      "ExternalCatalogItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number",
            "example": 43
          },
          "sku": {
            "type": [
              "string",
              "null"
            ],
            "example": "PAN-01"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "name": {
            "type": "string",
            "example": "Pan de yuca"
          },
          "unit": {
            "type": "string",
            "example": "unidad"
          },
          "stock": {
            "type": [
              "number",
              "null"
            ],
            "example": 12
          },
          "tracked": {
            "type": "boolean",
            "description": "`false`: papedir no lleva inventario de este producto.",
            "example": true
          },
          "available": {
            "type": "boolean",
            "example": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          }
        },
        "required": [
          "id",
          "sku",
          "external_id",
          "name",
          "unit",
          "stock",
          "tracked",
          "available",
          "updated_at"
        ]
      },
      "ExternalCatalogPageDto": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalCatalogItemDto"
            }
          },
          "next_cursor": {
            "type": [
              "number",
              "null"
            ],
            "description": "Pásalo como `cursor` para la página siguiente; `null` = no hay más.",
            "example": 43
          }
        },
        "required": [
          "items",
          "next_cursor"
        ]
      },
      "ExternalMappingItemDto": {
        "type": "object",
        "properties": {
          "sku": {
            "type": "string",
            "description": "SKU del producto en papedir.",
            "minLength": 1,
            "maxLength": 50,
            "example": "PAN-01"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id del producto en tu sistema; `null` quita el cruce. Único por negocio.",
            "minLength": 1,
            "maxLength": 100,
            "example": "b3f1c2d4-0000-4000-8000-000000000001"
          }
        },
        "required": [
          "sku",
          "external_id"
        ]
      },
      "ExternalMappingDto": {
        "type": "object",
        "properties": {
          "items": {
            "minItems": 1,
            "maxItems": 500,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalMappingItemDto"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "ExternalMappingResultDto": {
        "type": "object",
        "properties": {
          "index": {
            "type": "number",
            "example": 0
          },
          "sku": {
            "type": "string",
            "example": "PAN-01"
          },
          "status": {
            "type": "string",
            "enum": [
              "mapped",
              "unmapped",
              "not_found",
              "conflict"
            ],
            "example": "mapped"
          },
          "menu_item_id": {
            "type": [
              "number",
              "null"
            ],
            "example": 43
          }
        },
        "required": [
          "index",
          "sku",
          "status",
          "menu_item_id"
        ]
      },
      "ExternalMappingResponseDto": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalMappingResultDto"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "OrderEventItemDto": {
        "type": "object",
        "properties": {
          "menu_item_id": {
            "type": "number",
            "example": 43
          },
          "sku": {
            "type": [
              "string",
              "null"
            ],
            "example": "PAN-01"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "b3f1c2d4-0000-4000-8000-000000000001"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Pan de yuca"
          },
          "quantity": {
            "type": "number",
            "example": 2
          },
          "unit_price": {
            "type": [
              "number",
              "null"
            ],
            "example": 2000
          },
          "line_total": {
            "type": [
              "number",
              "null"
            ],
            "example": 4000
          }
        },
        "required": [
          "menu_item_id",
          "sku",
          "external_id",
          "name",
          "quantity",
          "unit_price",
          "line_total"
        ]
      },
      "OrderEventDataDto": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string",
            "format": "uuid",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11"
          },
          "order_num": {
            "type": [
              "number",
              "null"
            ],
            "example": 128
          },
          "status": {
            "type": "string",
            "example": "pending"
          },
          "payment_status": {
            "type": [
              "string",
              "null"
            ],
            "example": "pending"
          },
          "payment_type": {
            "type": [
              "string",
              "null"
            ],
            "example": "cash"
          },
          "is_paid": {
            "type": [
              "boolean",
              "null"
            ],
            "example": false
          },
          "order_type": {
            "type": [
              "string",
              "null"
            ],
            "example": "takeout"
          },
          "total": {
            "type": [
              "number",
              "null"
            ],
            "example": 6500
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "example": "COP"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderEventItemDto"
            }
          }
        },
        "required": [
          "order_id",
          "order_num",
          "status",
          "payment_status",
          "payment_type",
          "is_paid",
          "order_type",
          "total",
          "currency",
          "created_at",
          "items"
        ]
      },
      "ExternalOrderEventDto": {
        "type": "object",
        "properties": {
          "cursor": {
            "type": "number",
            "description": "Posición en el feed: pásala como `after` para seguir.",
            "example": 1042
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11",
            "description": "Id del evento; deduplica por este valor."
          },
          "type": {
            "type": "string",
            "enum": [
              "order.created",
              "order.updated",
              "order.cancelled"
            ],
            "example": "order.created"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          },
          "data": {
            "$ref": "#/components/schemas/OrderEventDataDto"
          },
          "delivered": {
            "type": "boolean",
            "description": "Ya confirmado (webhook con 2xx o `POST /orders/ack`).",
            "example": false
          }
        },
        "required": [
          "cursor",
          "id",
          "type",
          "created_at",
          "data",
          "delivered"
        ]
      },
      "ExternalOrdersPageDto": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExternalOrderEventDto"
            }
          },
          "next_cursor": {
            "type": [
              "number",
              "null"
            ],
            "example": 1042
          }
        },
        "required": [
          "events",
          "next_cursor"
        ]
      },
      "ExternalOrdersAckDto": {
        "type": "object",
        "properties": {
          "event_ids": {
            "example": [
              "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11"
            ],
            "minItems": 1,
            "maxItems": 500,
            "description": "Ids de los eventos que ya procesaste.",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "event_ids"
        ]
      },
      "ExternalOrdersAckResponseDto": {
        "type": "object",
        "properties": {
          "acknowledged": {
            "type": "number",
            "example": 2,
            "description": "Eventos que pasaron a confirmados en esta llamada."
          }
        },
        "required": [
          "acknowledged"
        ]
      },
      "WebhookOrderEventDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11"
          },
          "type": {
            "type": "string",
            "enum": [
              "order.created",
              "order.updated",
              "order.cancelled"
            ],
            "example": "order.created"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          },
          "data": {
            "$ref": "#/components/schemas/OrderEventDataDto"
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "WebhookPingDataDto": {
        "type": "object",
        "properties": {
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          }
        },
        "required": [
          "sent_at"
        ]
      },
      "WebhookPingEventDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "5b1f0c1e-7c1a-4a8e-9d51-2d8d3f0a9c11"
          },
          "type": {
            "type": "string",
            "enum": [
              "integration.ping"
            ],
            "example": "integration.ping"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-08T15:00:00.000Z"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookPingDataDto"
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      }
    }
  },
  "webhooks": {
    "order.created": {
      "post": {
        "operationId": "webhook_order_created",
        "summary": "order.created",
        "description": "Se creó un pedido en el punto de venta o la tienda (papedir ya descontó su existencia). Registra la venta en tu sistema, responde 2xx y luego manda la existencia.",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-Papedir-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Tipo del evento (igual a `type`)."
          },
          {
            "name": "X-Papedir-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Id del evento; deduplica por este valor."
          },
          {
            "name": "X-Papedir-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Segundos Unix. Rechaza timestamps de más de 5 minutos."
          },
          {
            "name": "X-Papedir-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=<hex(HMAC-SHA256(secreto, \"<timestamp>.<cuerpo crudo>\"))>`. Verifícala sobre el cuerpo sin re-serializar."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookOrderEventDto"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Recibido. Responde en menos de 10 s; cualquier otro código, timeout o error de red se reintenta (30 s, 2 min, 10 min, 1 h y cada 6 h, hasta 8 intentos)."
          }
        }
      }
    },
    "order.updated": {
      "post": {
        "operationId": "webhook_order_updated",
        "summary": "order.updated",
        "description": "Cambió el estado o el estado de pago del pedido.",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-Papedir-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Tipo del evento (igual a `type`)."
          },
          {
            "name": "X-Papedir-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Id del evento; deduplica por este valor."
          },
          {
            "name": "X-Papedir-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Segundos Unix. Rechaza timestamps de más de 5 minutos."
          },
          {
            "name": "X-Papedir-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=<hex(HMAC-SHA256(secreto, \"<timestamp>.<cuerpo crudo>\"))>`. Verifícala sobre el cuerpo sin re-serializar."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookOrderEventDto"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Recibido. Responde en menos de 10 s; cualquier otro código, timeout o error de red se reintenta (30 s, 2 min, 10 min, 1 h y cada 6 h, hasta 8 intentos)."
          }
        }
      }
    },
    "order.cancelled": {
      "post": {
        "operationId": "webhook_order_cancelled",
        "summary": "order.cancelled",
        "description": "Se canceló el pedido (papedir devolvió su existencia). Revierte la venta y manda la existencia.",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-Papedir-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Tipo del evento (igual a `type`)."
          },
          {
            "name": "X-Papedir-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Id del evento; deduplica por este valor."
          },
          {
            "name": "X-Papedir-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Segundos Unix. Rechaza timestamps de más de 5 minutos."
          },
          {
            "name": "X-Papedir-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=<hex(HMAC-SHA256(secreto, \"<timestamp>.<cuerpo crudo>\"))>`. Verifícala sobre el cuerpo sin re-serializar."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookOrderEventDto"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Recibido. Responde en menos de 10 s; cualquier otro código, timeout o error de red se reintenta (30 s, 2 min, 10 min, 1 h y cada 6 h, hasta 8 intentos)."
          }
        }
      }
    },
    "integration.ping": {
      "post": {
        "operationId": "webhook_integration_ping",
        "summary": "integration.ping",
        "description": "Prueba del webhook pedida desde el panel del negocio.",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-Papedir-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Tipo del evento (igual a `type`)."
          },
          {
            "name": "X-Papedir-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Id del evento; deduplica por este valor."
          },
          {
            "name": "X-Papedir-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Segundos Unix. Rechaza timestamps de más de 5 minutos."
          },
          {
            "name": "X-Papedir-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=<hex(HMAC-SHA256(secreto, \"<timestamp>.<cuerpo crudo>\"))>`. Verifícala sobre el cuerpo sin re-serializar."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookPingEventDto"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Recibido. Responde en menos de 10 s; cualquier otro código, timeout o error de red se reintenta (30 s, 2 min, 10 min, 1 h y cada 6 h, hasta 8 intentos)."
          }
        }
      }
    }
  }
}
