{
  "openapi": "3.1.0",
  "info": {
    "title": "Molinet cal Flari — API pública de la tienda",
    "version": "2026-08-26",
    "summary": "Lectura del catálogo, búsqueda y carrito de la almazara Molinet cal Flari.",
    "description": "Superficie pública y sin autenticación de la tienda Shopify de Molinet cal Flari (aceite de oliva virgen extra arbequina D.O.P. Les Garrigues, Cervià de les Garrigues, Lleida).\n\nTodos los endpoints de lectura funcionan sin credenciales: un agente puede consultar catálogo, precios y disponibilidad sin registrarse.\n\nPara **comprar**, esta tienda implementa el Universal Commerce Protocol (UCP) sobre MCP. El descubrimiento está en `/.well-known/ucp` y el servidor MCP en `POST /api/ucp/mcp`. Ahí es donde se crean y pagan los pedidos; esta especificación no duplica esas operaciones porque son JSON-RPC, no REST.\n\nLa API la sirve la plataforma Shopify, no un backend propio.\n\n## Versionado y obsolescencia\n\nEste documento se versiona **por ruta**: la version estable vive en `https://molinetcalflari.com/openapi-v1.json`, y `/openapi.json` apunta siempre a la ultima. Un cambio incompatible se publica en `/openapi-v2.json` y la ruta anterior sigue sirviendose al menos 6 meses. (La forma `/v1/...` no es posible aqui: Shopify solo resuelve redirecciones de un unico tramo de ruta.) El campo `info.version` lleva la fecha ISO de la revision. Una operacion que vaya a retirarse se marca antes con `deprecated: true` en esta misma especificacion, que es el campo estandar de OpenAPI para eso, y se mantiene sirviendo durante el plazo indicado. Hoy ninguna lo esta.\n\nLa capa de comercio se versiona aparte, por el propio protocolo UCP: las versiones admitidas se enumeran en `GET /.well-known/ucp` bajo `supported_versions`, cada una con su endpoint. Un agente debe negociar la version ahi y no dar por hecha la mas reciente.\n\nLos endpoints de catalogo (`/products.json` y companeros) son superficie de plataforma de Shopify: su contrato lo fija Shopify, no esta tienda.",
    "contact": {
      "name": "Molinet cal Flari",
      "email": "historiaterror420@gmail.com",
      "url": "https://molinetcalflari.com/pages/contact"
    },
    "license": {
      "name": "Uso público de lectura",
      "identifier": "LicenseRef-public-read"
    }
  },
  "x-api-versioning": {
    "scheme": "date-versioned-documents",
    "current": "2026-08-26",
    "summary": "La especificacion se versiona por ruta; la capa de comercio (UCP) se versiona por su propio protocolo. Los endpoints de catalogo son superficie de plataforma de Shopify y no llevan version propia.",
    "documents": {
      "latest": "https://molinetcalflari.com/openapi.json",
      "pinned": [
        "https://molinetcalflari.com/openapi-v1.json"
      ],
      "next_breaking_change": "https://molinetcalflari.com/openapi-v2.json",
      "note": "Un cambio incompatible estrena documento (v2) y el anterior se sigue sirviendo en su URL."
    },
    "commerce_layer": {
      "scheme": "url-path",
      "discovery": "https://molinetcalflari.com/.well-known/ucp",
      "pinned_template": "https://molinetcalflari.com/.well-known/ucp/{version}",
      "supported": [
        "2026-08-25",
        "2026-04-08",
        "2026-01-23"
      ],
      "note": "Negociar la version leyendo `supported_versions` del perfil base. No dar por hecha la mas reciente."
    },
    "deprecation_policy": {
      "minimum_notice": "P6M",
      "announced_in": [
        "El campo `deprecated: true` de la propia operacion en esta especificacion (campo estandar de OpenAPI).",
        "https://molinetcalflari.com/agents.md, seccion de versionado."
      ],
      "response_headers": {
        "Deprecation": "RFC 9745. Se enviara si algun dia esta tienda sirve la API desde un proxio propio.",
        "Sunset": "RFC 8594. Igual.",
        "caveat": "AVISO: hoy la respuesta la emite Shopify y esta tienda NO puede anadir cabeceras. El aviso de retirada llega por los dos canales de arriba, no por cabecera. Se documenta asi para no prometer una cabecera que no se va a ver."
      },
      "currently_deprecated": []
    },
    "rate_limits": {
      "documented": true,
      "headers_present": false,
      "caveat": "Shopify limita por IP y responde 429, pero NO emite cabeceras RateLimit/Retry-After en la superficie publica de la tienda, y esta tienda no puede anadirlas. Un agente debe hacer retroceso exponencial al ver un 429 en vez de leer un contador."
    }
  },
  "x-error-handling": {
    "envelope": {
      "schema": "#/components/schemas/Error",
      "example": {
        "status": 404,
        "message": "Product not found",
        "description": "No product with id 1 is published in the online store"
      },
      "note": "El codigo HTTP se repite dentro del cuerpo, en `status`."
    },
    "endpoints_with_envelope": [
      "GET /recommendations/products.json",
      "GET /search/suggest.json",
      "POST /cart/add.js",
      "POST /api/ucp/mcp (error JSON-RPC 2.0, formato distinto: `error.code` / `error.message`)"
    ],
    "endpoints_without_envelope": [
      {
        "endpoint": "GET /products/{handle}.json",
        "status": 404,
        "body": "vacio"
      },
      {
        "endpoint": "GET /collections/{handle}.json",
        "status": 404,
        "body": "vacio"
      }
    ],
    "caveat": "Los cuerpos vacios son comportamiento de la plataforma Shopify, no de esta tienda, y no se pueden cambiar desde el tema. Un cliente robusto se guia por el codigo de estado y solo parsea el cuerpo si trae bytes.",
    "advice": "Para resolver un producto por nombre usa `GET /search/suggest.json?q=...`: devuelve 422 con sobre completo si la consulta es invalida, y una lista vacia si no hay coincidencias. Nunca devuelve un cuerpo vacio."
  },
  "externalDocs": {
    "description": "Instrucciones para agentes (llms.txt) y perfil UCP",
    "url": "https://molinetcalflari.com/llms.txt"
  },
  "servers": [
    {
      "url": "https://molinetcalflari.com",
      "description": "Mercado España (es, ca)"
    },
    {
      "url": "https://molinetcalflari.com/de-de",
      "description": "Mercado Alemania (de)"
    },
    {
      "url": "https://molinetcalflari.com/fr-fr",
      "description": "Mercado Francia (fr)"
    },
    {
      "url": "https://molinetcalflari.com/fr-be",
      "description": "Mercado Bélgica (fr)"
    },
    {
      "url": "https://molinetcalflari.com/en-uk",
      "description": "Mercado Reino Unido (en)"
    },
    {
      "url": "https://molinetcalflari.com/en-pb",
      "description": "Mercado Países Bajos (en)"
    },
    {
      "url": "https://molinetcalflari.com/de-su",
      "description": "Mercado Suiza (de)"
    }
  ],
  "security": [
    {}
  ],
  "tags": [
    {
      "name": "catalogo",
      "description": "Productos y colecciones. Sin autenticación."
    },
    {
      "name": "busqueda",
      "description": "Sugerencias de búsqueda. Sin autenticación."
    },
    {
      "name": "carrito",
      "description": "Carrito de la sesión. Sin autenticación; usa cookie de sesión."
    },
    {
      "name": "comercio-agentico",
      "description": "Compra vía UCP sobre MCP."
    }
  ],
  "paths": {
    "/products.json": {
      "get": {
        "operationId": "listarProductos",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "catalogo"
        ],
        "summary": "Lista los productos publicados",
        "description": "Devuelve los productos del escaparate con sus variantes, precios e imágenes. El precio va como cadena decimal en euros, por ejemplo \"59.90\". Paginado por `page` y `limit`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Productos por página (1-250, por defecto 30).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 30
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Página, empezando en 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de productos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Producto"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/collections.json": {
      "get": {
        "operationId": "listarColecciones",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "catalogo"
        ],
        "summary": "Lista las colecciones publicadas",
        "description": "Devuelve las colecciones del escaparate. La colección principal del catálogo es `catalogo`; también existen `aceites-arbequina`, `vinagres-y-miel` y `camisetas-pod`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Colecciones por página (1-250).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 30
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Página, empezando en 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de colecciones.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "collections"
                  ],
                  "properties": {
                    "collections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Coleccion"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/collections/{handle}/products.json": {
      "get": {
        "operationId": "listarProductosDeColeccion",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "catalogo"
        ],
        "summary": "Lista los productos de una colección",
        "description": "Devuelve los productos de la colección indicada. Atención: si el handle no existe, Shopify responde 200 con una lista vacía, no 404.",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "description": "Identificador de la colección, por ejemplo `catalogo`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Productos por página (1-250).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250,
              "default": 30
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Página, empezando en 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de productos de la colección; vacía si el handle no existe.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Producto"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/search/suggest.json": {
      "get": {
        "operationId": "sugerirBusqueda",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "busqueda"
        ],
        "summary": "Sugerencias de búsqueda",
        "description": "Busca productos, colecciones, páginas y artículos por texto. Útil para resolver el nombre de un producto antes de añadirlo al carrito.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Texto a buscar, por ejemplo `aceite arbequina`.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "resources[type]",
            "in": "query",
            "required": false,
            "description": "Tipos separados por coma: product, collection, page, article.",
            "schema": {
              "type": "string",
              "default": "product"
            }
          },
          {
            "name": "resources[limit]",
            "in": "query",
            "required": false,
            "description": "Resultados por tipo (1-10).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultados agrupados por tipo de recurso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "resources"
                  ],
                  "properties": {
                    "resources": {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Peticion no procesable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/cart.js": {
      "get": {
        "operationId": "obtenerCarrito",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "carrito"
        ],
        "summary": "Devuelve el carrito de la sesión",
        "description": "Devuelve el carrito asociado a la cookie de sesión. Los importes van en céntimos como enteros: 5990 son 59,90 €. Responde con JSON pero con Content-Type `text/javascript`, que es como lo sirve Shopify.",
        "responses": {
          "200": {
            "description": "Carrito actual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Carrito"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/recommendations/products.json": {
      "get": {
        "operationId": "recomendarProductos",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "catalogo"
        ],
        "summary": "Productos recomendados a partir de uno dado",
        "description": "Devuelve productos relacionados con el indicado. Si el producto no está publicado, responde 404 con el sobre de error estándar.",
        "parameters": [
          {
            "name": "product_id",
            "in": "query",
            "required": true,
            "description": "Identificador numérico del producto.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Número de recomendaciones (1-10).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 4
            }
          },
          {
            "name": "intent",
            "in": "query",
            "required": false,
            "description": "Criterio de recomendación.",
            "schema": {
              "type": "string",
              "enum": [
                "related",
                "complementary"
              ],
              "default": "related"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Productos recomendados.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Producto"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/ucp/mcp": {
      "post": {
        "operationId": "llamarMcpUcp",
        "deprecated": false,
        "security": [
          {},
          {
            "cuentaDeCliente": [
              "openid",
              "email",
              "customer-account-api:full",
              "customer-account-mcp-api:full"
            ]
          }
        ],
        "tags": [
          "comercio-agentico"
        ],
        "summary": "Servidor MCP de comercio (Universal Commerce Protocol)",
        "description": "Endpoint JSON-RPC 2.0 que expone las herramientas de compra: `tools/list` las enumera y devuelve el esquema de cada una. Aquí se crean, actualizan y pagan los carritos. El perfil de descubrimiento está en `/.well-known/ucp`. Los importes van en unidades menores de la moneda: {\"amount\": 5990, \"currency\": \"EUR\"} son 59,90 €.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PeticionJsonRpc"
              },
              "examples": {
                "listarHerramientas": {
                  "summary": "Enumerar herramientas",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Respuesta JSON-RPC 2.0.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaJsonRpc"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ucp": {
      "get": {
        "operationId": "descubrirUcp",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "comercio-agentico"
        ],
        "summary": "Perfil UCP de la tienda y versiones admitidas",
        "description": "Punto de entrada del Universal Commerce Protocol. Devuelve la version en uso, el mapa `supported_versions` con la URL de cada version admitida, los servicios y las capacidades. Un agente debe leer esto ANTES de llamar al MCP, para negociar version en vez de dar por hecha la mas reciente.",
        "responses": {
          "200": {
            "description": "Perfil UCP.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ucp/{version}": {
      "get": {
        "operationId": "descubrirUcpVersionado",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "comercio-agentico"
        ],
        "summary": "Perfil UCP de una version concreta",
        "description": "Version fijada del perfil UCP. Es el versionado por ruta de la capa de comercio: una version publicada sigue sirviendose en su propia URL aunque salga una nueva, de modo que un agente puede anclarse a un contrato estable.",
        "parameters": [
          {
            "name": "version",
            "in": "path",
            "required": true,
            "description": "Fecha ISO de la version, tal como aparece en `supported_versions` del perfil base.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
              "examples": [
                "2026-08-25",
                "2026-04-08",
                "2026-01-23"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Perfil UCP de esa version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "No encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error. El cuerpo lleva el sobre estandar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{handle}.json": {
      "get": {
        "operationId": "obtenerProducto",
        "deprecated": false,
        "security": [
          {}
        ],
        "tags": [
          "catalogo"
        ],
        "summary": "Ficha de un producto por handle",
        "description": "Devuelve un unico producto con sus variantes y precios. Sin autenticacion.\n\n**Cuidado con el 404:** si el handle no existe, Shopify responde `404` con `Content-Type: application/json` pero **el cuerpo viene vacio**, no con el sobre de error. Comprueba el codigo de estado antes de parsear. Si necesitas un error con cuerpo legible, resuelve el nombre antes con `GET /search/suggest.json?q=...`, que si devuelve el sobre completo.",
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "description": "Identificador del producto en la URL, tal como aparece en `/products/{handle}`.",
            "schema": {
              "type": "string",
              "examples": [
                "garrafa-5-litros-aove-arbequina-premium"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "El producto.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Producto"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No existe ese handle. **Cuerpo vacio**, sin sobre de error: mira el status, no el cuerpo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas peticiones: reintentar tras esperar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cuentaDeCliente": {
        "type": "oauth2",
        "description": "Solo para operaciones sobre la cuenta de un cliente. Los metadatos legibles por máquina están en https://molinetcalflari.com/.well-known/oauth-authorization-server (RFC 8414) y el recurso protegido en /.well-known/oauth-protected-resource (RFC 9728). Requiere PKCE con S256.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://account.molinetcalflari.com/authentication/oauth/authorize",
            "tokenUrl": "https://account.molinetcalflari.com/authentication/oauth/token",
            "refreshUrl": "https://account.molinetcalflari.com/authentication/oauth/token",
            "scopes": {
              "openid": "Identificador del cliente autenticado.",
              "email": "Correo electrónico del cliente.",
              "customer-account-api:full": "Acceso completo a la API de cuenta de cliente: pedidos y datos propios.",
              "customer-account-mcp-api:full": "Acceso completo a la API MCP de cuenta de cliente."
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error estructurado en JSON.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "productoNoEncontrado": {
                "summary": "Producto no publicado",
                "value": {
                  "status": 404,
                  "message": "Product not found",
                  "description": "No product with id 1 is published in the online store"
                }
              },
              "carritoInvalido": {
                "summary": "Variante inexistente",
                "value": {
                  "status": 422,
                  "message": "Cart Error",
                  "description": "Cannot find variant"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Sobre de error de la tienda. El código HTTP y el campo status coinciden.",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "integer",
            "description": "Código HTTP repetido en el cuerpo.",
            "examples": [
              404,
              422
            ]
          },
          "message": {
            "type": "string",
            "description": "Resumen corto y estable del error.",
            "examples": [
              "Product not found",
              "Cart Error"
            ]
          },
          "description": {
            "type": "string",
            "description": "Explicación con el detalle concreto: qué hacer o qué faltaba.",
            "examples": [
              "Cannot find variant"
            ]
          }
        }
      },
      "Producto": {
        "type": "object",
        "required": [
          "id",
          "title",
          "handle",
          "variants"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string",
            "examples": [
              "Garrafa 5 Litros - AOVE Arbequina Premium"
            ]
          },
          "handle": {
            "type": "string",
            "description": "Identificador en la URL.",
            "examples": [
              "aceite-arbequina-5l"
            ]
          },
          "body_html": {
            "type": "string",
            "description": "Descripción en HTML."
          },
          "vendor": {
            "type": "string",
            "examples": [
              "El Molinet de cal Flari"
            ]
          },
          "product_type": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "published_at": {
            "type": "string",
            "format": "date-time"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Variante"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "src": {
                  "type": "string",
                  "format": "uri"
                },
                "width": {
                  "type": "integer"
                },
                "height": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Variante": {
        "type": "object",
        "required": [
          "id",
          "price",
          "available"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador que se usa para añadir al carrito."
          },
          "title": {
            "type": "string",
            "examples": [
              "Default Title",
              "M"
            ]
          },
          "price": {
            "type": "string",
            "description": "Precio decimal en euros, como cadena.",
            "examples": [
              "59.90"
            ]
          },
          "sku": {
            "type": [
              "string",
              "null"
            ]
          },
          "available": {
            "type": "boolean"
          },
          "grams": {
            "type": "integer",
            "description": "Peso en gramos."
          }
        }
      },
      "Coleccion": {
        "type": "object",
        "required": [
          "id",
          "handle",
          "title"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "handle": {
            "type": "string",
            "examples": [
              "catalogo",
              "aceites-arbequina"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "products_count": {
            "type": "integer"
          }
        }
      },
      "Carrito": {
        "type": "object",
        "required": [
          "token",
          "item_count",
          "total_price",
          "currency"
        ],
        "properties": {
          "token": {
            "type": "string"
          },
          "item_count": {
            "type": "integer"
          },
          "total_price": {
            "type": "integer",
            "description": "Total en céntimos: 5990 son 59,90 €."
          },
          "currency": {
            "type": "string",
            "examples": [
              "EUR"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "PeticionJsonRpc": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string"
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "RespuestaJsonRpc": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      }
    }
  }
}
