{
  "openapi": "3.1.0",
  "info": {
    "title": "ERP Ferreterías — API pública",
    "version": "2.0.0",
    "summary": "Endpoints públicos (sin sesión de usuario) de ERP Ferreterías.",
    "description": "Solo se documentan acá los endpoints que no requieren la sesión del panel: estado del servicio, identidad pública del comercio, inicio de sesión, y las dos integraciones externas (asistente y servidor MCP), que exigen una clave propia por comercio. El resto de la API (artículos, ventas, stock, clientes, facturación, etc.) requiere sesión de usuario y no se publica acá porque expone datos de negocio de cada comercio — ver /docs y /developers para el contexto completo. Versionado: por header \"API-Version\" (no por URL); las respuestas 200 de estos endpoints lo incluyen. El cliente puede además enviarlo en el request (parámetro ApiVersionHeader, ver components/parameters): opcional, y si se manda un valor que esta instancia no sirve se responde 400 (DATOS_INVALIDOS) en vez de ignorarlo. Un cambio incompatible sumaría una versión nueva y anunciaría la anterior como deprecada con los headers \"Deprecation\" y \"Sunset\" (RFC 8594), con al menos 90 días de aviso. Errores: todo 4xx/5xx de estos endpoints devuelve el schema Error (campo \"codigo\" estable + \"error\" legible).",
    "contact": {
      "name": "ERP Ferreterías",
      "url": "https://demo.ferreteriaclarita.com.ar/developers"
    }
  },
  "servers": [
    {
      "url": "/",
      "description": "Mismo origen que sirve este documento (multi-tenant: cada dominio es un comercio distinto)."
    }
  ],
  "tags": [
    {
      "name": "sistema",
      "description": "Estado del servicio."
    },
    {
      "name": "auth",
      "description": "Autenticación e identidad pública del comercio."
    },
    {
      "name": "asistente",
      "description": "Integraciones externas del asistente con IA (requieren clave por comercio)."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "obtenerEstadoServicio",
        "tags": [
          "sistema"
        ],
        "summary": "Estado del servicio",
        "description": "Chequeo de salud del backend. Sin autenticación.",
        "responses": {
          "200": {
            "description": "El servicio está operativo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "version"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "true si el servicio responde con normalidad."
                    },
                    "version": {
                      "type": "string",
                      "description": "Versión desplegada de la API."
                    },
                    "fiscalProvider": {
                      "type": "string",
                      "description": "Proveedor de facturación electrónica activo (ej. \"mock\" o \"arca\")."
                    },
                    "meliProvider": {
                      "type": "string",
                      "description": "Proveedor de integración con Mercado Libre activo."
                    },
                    "baseDeDatos": {
                      "type": "string",
                      "description": "Motor de base de datos en uso."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "version": "2.0.0",
                  "fiscalProvider": "mock",
                  "meliProvider": "auto",
                  "baseDeDatos": "PostgreSQL"
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/auth/identidad": {
      "get": {
        "operationId": "obtenerIdentidadPublica",
        "tags": [
          "auth"
        ],
        "summary": "Identidad pública del comercio",
        "description": "Devuelve el nombre, rubro, colores de marca, sucursales y datos de contacto que el propio comercio ya tiene cargados y públicos en su cartelería. Nunca devuelve datos sensibles (usuarios, cotizaciones, parámetros internos). Sin autenticación: la usa la pantalla de ingreso para pintar la marca antes de loguearse.",
        "responses": {
          "200": {
            "description": "Identidad pública del comercio configurado en este dominio.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nombre": {
                      "type": "string"
                    },
                    "razonSocial": {
                      "type": "string"
                    },
                    "rubro": {
                      "type": "string",
                      "enum": [
                        "FERRETERIA",
                        "FARMACIA"
                      ]
                    },
                    "slogan": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "logoUrl": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "colorMarca": {
                      "type": "string"
                    },
                    "colorMarcaSecundario": {
                      "type": "string"
                    },
                    "domicilio": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "localidad": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "provincia": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "sucursales": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "nombre": {
                            "type": "string"
                          },
                          "localidad": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "direccion": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "telefono": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "horario": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/auth/login": {
      "post": {
        "operationId": "iniciarSesion",
        "tags": [
          "auth"
        ],
        "summary": "Inicio de sesión",
        "description": "Autentica un usuario del panel y devuelve un token de sesión (JWT) para usar en el resto de la API. Limitado por un límite de frecuencia por IP (ver headers RateLimit-* en la respuesta).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "usuario",
                  "clave"
                ],
                "properties": {
                  "usuario": {
                    "type": "string",
                    "minLength": 1
                  },
                  "clave": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sesión iniciada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "JWT a usar como Authorization: Bearer <token>."
                    },
                    "usuario": {
                      "type": "object"
                    },
                    "empresa": {
                      "type": "object"
                    },
                    "plan": {
                      "type": "object"
                    }
                  }
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "401": {
            "description": "Usuario o contraseña incorrectos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Demasiados intentos: ver el header Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "IETF draft-7."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/asistente/externo": {
      "post": {
        "operationId": "consultarAsistenteExterno",
        "tags": [
          "asistente"
        ],
        "summary": "Consultar el asistente con IA desde afuera del panel",
        "description": "Pensado para integraciones tipo WhatsApp/n8n/Make: recibe una pregunta en lenguaje natural y devuelve la respuesta del asistente sobre los datos del comercio dueño de la clave.",
        "security": [
          {
            "ClaveDeComercio": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pregunta"
                ],
                "properties": {
                  "pregunta": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "telefono": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Si el comercio configuró teléfonos autorizados, solo responde a esos."
                  },
                  "historial": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "rol": {
                          "type": "string",
                          "enum": [
                            "usuario",
                            "asistente"
                          ]
                        },
                        "texto": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Respuesta del asistente.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "respuesta": {
                      "type": "string"
                    },
                    "pasos": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "ms": {
                      "type": "number"
                    }
                  }
                }
              }
            },
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            }
          },
          "401": {
            "description": "Falta la clave o es inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "El plan del comercio no incluye el asistente, o el teléfono no está autorizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "invocarServidorMcp",
        "tags": [
          "asistente"
        ],
        "summary": "Servidor MCP (Model Context Protocol)",
        "description": "Endpoint JSON-RPC 2.0 (transporte \"streamable HTTP\" en modo simple, sin stream SSE) para conectar un cliente MCP directamente a los datos del comercio dueño de la clave. Acepta un único mensaje JSON-RPC o un array de mensajes (batch). Las notificaciones (sin \"id\") responden 202 sin cuerpo.",
        "security": [
          {
            "ClaveDeComercio": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "description": "Mensaje JSON-RPC 2.0 (o array de mensajes para un batch) según Model Context Protocol.",
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/McpMensaje"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/McpMensaje"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Respuesta JSON-RPC (o array de respuestas si el pedido fue un batch).",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/JsonRpcResponse"
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/JsonRpcResponse"
                      }
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Notificación recibida (sin id): sin cuerpo de respuesta."
          },
          "401": {
            "description": "Falta la clave o es inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      },
      "get": {
        "operationId": "mcpMetodoNoPermitido",
        "tags": [
          "asistente"
        ],
        "summary": "No soportado (este servidor MCP no ofrece stream SSE)",
        "security": [
          {
            "ClaveDeComercio": []
          }
        ],
        "responses": {
          "405": {
            "description": "Este servidor MCP solo acepta POST (sin stream SSE).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ClaveDeComercio": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Clave propia de cada comercio, generada desde su panel en Configuración → Asistente → Generar clave. También se acepta como \"Authorization: Bearer <clave>\". No hay una clave global: cada comercio tiene la suya sobre sus propios datos."
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "Versión de la API pública (versionado por header, no por URL). Ver la política de deprecación en info.description.",
        "schema": {
          "type": "string",
          "example": "2.0"
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "codigo"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Mensaje de error legible (en castellano)."
          },
          "codigo": {
            "type": "string",
            "description": "Código estable en mayúsculas para manejar el error por código sin parsear el mensaje. No cambia aunque se retoque el texto de \"error\".",
            "enum": [
              "DATOS_INVALIDOS",
              "NO_AUTENTICADO",
              "SIN_PERMISO",
              "NO_ENCONTRADO",
              "CONFLICTO",
              "DEMASIADAS_SOLICITUDES",
              "METODO_NO_PERMITIDO",
              "REGISTRO_DUPLICADO",
              "REGISTRO_EN_USO",
              "ERROR_INTERNO"
            ]
          },
          "detalle": {
            "description": "Detalle adicional opcional (por ejemplo, errores de validación por campo). Forma variable según el error."
          }
        },
        "example": {
          "error": "Usuario o contraseña incorrectos",
          "codigo": "NO_AUTENTICADO"
        }
      },
      "JsonRpcError": {
        "type": "object",
        "required": [
          "jsonrpc",
          "error"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {},
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "Código de error JSON-RPC 2.0 (por ejemplo -32600, -32601)."
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc",
          "id"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "description": "Mismo id que el mensaje de request."
          },
          "result": {
            "$ref": "#/components/schemas/McpResultadoUnion"
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        },
        "example": {
          "jsonrpc": "2.0",
          "id": 1,
          "result": {
            "protocolVersion": "2025-03-26"
          }
        }
      },
      "McpInitializeRequest": {
        "description": "Handshake MCP: primer mensaje de cualquier cliente. No requiere la clave del comercio (ver security de esta operación).",
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "description": "Identificador de correlación de este mensaje."
          },
          "method": {
            "const": "initialize"
          },
          "params": {
            "type": "object",
            "description": "Negociación de versión/capacidades del cliente MCP. El servidor la acepta pero no la valida ni la requiere."
          }
        }
      },
      "McpPingRequest": {
        "description": "Handshake MCP: verificación de vida del servidor. No requiere la clave del comercio.",
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "description": "Identificador de correlación de este mensaje."
          },
          "method": {
            "const": "ping"
          }
        }
      },
      "McpNotificationsInitializedRequest": {
        "description": "Handshake MCP: notificación (sin \"id\") de que el cliente terminó de inicializar. Responde 202 sin cuerpo. No requiere la clave del comercio.",
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "method": {
            "const": "notifications/initialized"
          }
        }
      },
      "McpToolsListRequest": {
        "description": "Lista las herramientas disponibles para la clave del comercio. Requiere la clave (X-Api-Key / Authorization Bearer).",
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "description": "Identificador de correlación de este mensaje."
          },
          "method": {
            "const": "tools/list"
          }
        }
      },
      "McpToolsCallRequest": {
        "description": "Ejecuta una herramienta de solo lectura sobre los datos del comercio. Requiere la clave.",
        "type": "object",
        "required": [
          "jsonrpc",
          "method",
          "params"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "description": "Identificador de correlación de este mensaje."
          },
          "method": {
            "const": "tools/call"
          },
          "params": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "Nombre de una herramienta devuelta por tools/list."
              },
              "arguments": {
                "type": "object",
                "description": "Argumentos de la herramienta, según su inputSchema."
              }
            }
          }
        }
      },
      "McpMensaje": {
        "description": "Un mensaje JSON-RPC 2.0 según el método (Model Context Protocol). Cualquier \"method\" fuera de esta lista responde el error JSON-RPC -32601 (\"Método no soportado\").",
        "oneOf": [
          {
            "$ref": "#/components/schemas/McpInitializeRequest"
          },
          {
            "$ref": "#/components/schemas/McpPingRequest"
          },
          {
            "$ref": "#/components/schemas/McpNotificationsInitializedRequest"
          },
          {
            "$ref": "#/components/schemas/McpToolsListRequest"
          },
          {
            "$ref": "#/components/schemas/McpToolsCallRequest"
          }
        ],
        "discriminator": {
          "propertyName": "method",
          "mapping": {
            "initialize": "#/components/schemas/McpInitializeRequest",
            "ping": "#/components/schemas/McpPingRequest",
            "notifications/initialized": "#/components/schemas/McpNotificationsInitializedRequest",
            "tools/list": "#/components/schemas/McpToolsListRequest",
            "tools/call": "#/components/schemas/McpToolsCallRequest"
          }
        }
      },
      "McpInitializeResult": {
        "type": "object",
        "required": [
          "protocolVersion",
          "capabilities",
          "serverInfo"
        ],
        "properties": {
          "protocolVersion": {
            "type": "string",
            "example": "2025-03-26"
          },
          "capabilities": {
            "type": "object",
            "properties": {
              "tools": {
                "type": "object",
                "properties": {
                  "listChanged": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "serverInfo": {
            "type": "object",
            "required": [
              "name",
              "version"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "instructions": {
            "type": "string"
          }
        }
      },
      "McpEmptyResult": {
        "description": "Resultado vacío (ping, notifications/initialized).",
        "type": "object",
        "properties": {}
      },
      "McpToolsListResult": {
        "type": "object",
        "required": [
          "tools"
        ],
        "properties": {
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "description",
                "inputSchema"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "inputSchema": {
                  "type": "object",
                  "description": "JSON Schema de los argumentos de la herramienta."
                }
              }
            }
          }
        }
      },
      "McpToolsCallResult": {
        "type": "object",
        "required": [
          "content",
          "isError"
        ],
        "properties": {
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "text"
              ],
              "properties": {
                "type": {
                  "const": "text"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "isError": {
            "type": "boolean"
          }
        }
      },
      "McpResultadoUnion": {
        "description": "Forma de \"result\" según el método invocado: McpInitializeResult (initialize), McpEmptyResult (ping / notifications/initialized), McpToolsListResult (tools/list) o McpToolsCallResult (tools/call).",
        "oneOf": [
          {
            "$ref": "#/components/schemas/McpInitializeResult"
          },
          {
            "$ref": "#/components/schemas/McpEmptyResult"
          },
          {
            "$ref": "#/components/schemas/McpToolsListResult"
          },
          {
            "$ref": "#/components/schemas/McpToolsCallResult"
          }
        ]
      }
    },
    "parameters": {
      "ApiVersionHeader": {
        "name": "API-Version",
        "in": "header",
        "required": false,
        "description": "Versión de la API pública que espera el cliente. Opcional: sin enviarlo, se usa la única versión vigente. Un valor no soportado devuelve 400 (DATOS_INVALIDOS) en vez de ignorarse. Ver la política de deprecación en info.description.",
        "schema": {
          "type": "string",
          "enum": [
            "2.0"
          ],
          "default": "2.0"
        }
      }
    }
  }
}
