{
  "openapi": "3.0.3",
  "info": {
    "title": "Catálogo de Productos · Wiracocha",
    "version": "1.3.0",
    "description": "Consulta SQL y respaldo externo con clave X-API-Key o sesión de administrador. La muestra pública de portada es fija, persistente y limitada a 12 productos sin GTIN ni metadatos de origen."
  },
  "paths": {
    "/api/v1/products/{barcode}": {
      "get": {
        "summary": "Obtener producto por EAN/UPC/GTIN",
        "parameters": [
          {
            "name": "barcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{8}$|^[0-9]{12,14}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Producto encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LookupResponse"
                }
              }
            }
          },
          "400": {
            "description": "Código inválido"
          },
          "404": {
            "description": "Producto ausente en todas las fuentes"
          },
          "429": {
            "description": "Límite de API alcanzado"
          },
          "503": {
            "description": "SQL o proveedor externo no disponible"
          },
          "401": {
            "description": "API_AUTH_REQUIRED: falta clave válida o sesión de administrador"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "AdminSession": []
          }
        ]
      }
    },
    "/api/v1/products/search": {
      "get": {
        "summary": "Buscar nombres del catálogo con comodines",
        "description": "Solo SQL. Al menos 3 letras o números, máximo 100 caracteres. Texto simple: contiene. Patrones explícitos: * y % representan varios caracteres; ? y _ uno. Ignora mayúsculas y acentos. Máximo 25 resultados. Si hay más, HTTP 422 sin productos.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "example": "*mr*musculo*500*"
          }
        ],
        "responses": {
          "200": {
            "description": "Hasta 25 coincidencias; lista vacía si no hay ninguna",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "query": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer",
                      "maximum": 25
                    },
                    "maxResults": {
                      "type": "integer",
                      "enum": [
                        25
                      ]
                    },
                    "products": {
                      "type": "array",
                      "maxItems": 25,
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "INVALID_SEARCH: criterio corto, solo comodines o más de 100 caracteres"
          },
          "422": {
            "description": "SEARCH_TOO_BROAD: más de 25 coincidencias; incluye maxResults=25 y matchesAtLeast=26, sin campo products"
          },
          "429": {
            "description": "Límite de API alcanzado"
          },
          "503": {
            "description": "SQL no disponible"
          },
          "401": {
            "description": "API_AUTH_REQUIRED: falta clave válida o sesión de administrador"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "AdminSession": []
          }
        ]
      }
    },
    "/api/v1/products/sample": {
      "get": {
        "summary": "Muestra pública fija de portada",
        "description": "La misma selección de hasta 12 productos para todas las visitas, persistida entre reinicios. No acepta filtros, paginación ni semillas; no devuelve códigos ni URLs de origen. Máximo 10 consultas/minuto/IP.",
        "responses": {
          "200": {
            "description": "Muestra aleatoria",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer",
                      "maximum": 12
                    },
                    "products": {
                      "type": "array",
                      "maxItems": 12,
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "brand": {
                            "type": "string"
                          },
                          "presentation": {
                            "type": "string"
                          },
                          "images": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uri"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Límite de API alcanzado"
          },
          "503": {
            "description": "SQL no disponible"
          }
        }
      }
    },
    "/api/v1/products/{barcode}/prices/history": {
      "get": {
        "summary": "Histórico de precios por producto y establecimiento",
        "description": "Solo catálogo SQL; 100 observaciones por página, fecha descendente. Variaciones respecto a la observación anterior por establecimiento y moneda, incluso fuera del rango filtrado. Porcentaje nulo para primera observación o precio anterior cero. Sin respaldo de precios externo.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "AdminSession": []
          }
        ],
        "parameters": [
          {
            "name": "barcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "establishmentCode",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "currency",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "PEN",
              "pattern": "^[A-Za-z]{3}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Histórico; lista vacía si no hay observaciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "barcode": {
                      "type": "string"
                    },
                    "gtin14": {
                      "type": "string"
                    },
                    "productName": {
                      "type": "string"
                    },
                    "establishmentCode": {
                      "type": "string"
                    },
                    "establishmentName": {
                      "type": "string"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer",
                      "enum": [
                        100
                      ]
                    },
                    "total": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "history": {
                      "type": "array",
                      "maxItems": 100,
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "initial",
                              "up",
                              "down",
                              "unchanged"
                            ]
                          },
                          "salePrice": {
                            "type": "number",
                            "nullable": false
                          },
                          "previousPrice": {
                            "type": "number",
                            "nullable": true
                          },
                          "changeAmount": {
                            "type": "number",
                            "nullable": true
                          },
                          "changePercent": {
                            "type": "number",
                            "nullable": true
                          },
                          "observedAtUtc": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "recordedAtUtc": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": false
                          },
                          "effectiveAtUtc": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": false
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Código o filtros inválidos"
          },
          "401": {
            "description": "Requiere X-API-Key o sesión de administrador"
          },
          "404": {
            "description": "Producto local o establecimiento inexistente"
          },
          "429": {
            "description": "Límite de consultas"
          },
          "503": {
            "description": "SQL no disponible"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Product": {
        "type": "object",
        "properties": {
          "barcode": {
            "type": "string"
          },
          "gtin14": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brand": {
            "type": "string"
          },
          "presentation": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "sourceUrl": {
            "type": "string"
          },
          "stores": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "presentationSource": {
            "type": "string"
          }
        }
      },
      "LookupResponse": {
        "type": "object",
        "properties": {
          "barcode": {
            "type": "string"
          },
          "gtin14": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "catalog",
              "openfoodfacts",
              "upcitemdb"
            ]
          },
          "product": {
            "$ref": "#/components/schemas/Product"
          },
          "attempts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "message": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "AdminSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "CatalogAdmin"
      }
    }
  }
}
