{
  "openapi": "3.0.3",
  "info": {
    "title": "Bicheros API",
    "version": "1.0.0",
    "description": "API HTTP de Bicheros, la plataforma argentina de cuidado de mascotas. Expone la API interna de campañas de referidos (autenticación por sesión, consumida por el propio front-end de bicheros.com) y la API de integración de WhatsApp (autenticación por token Bearer, service-to-service). No hay endpoints públicos sin autenticación: para navegar el catálogo de cuidadores, servicios y mascotas perdidas, usá las páginas HTML server-rendered listadas en /sitemap.xml.",
    "contact": {
      "name": "Soporte Bicheros",
      "email": "soporte@bicheros.com",
      "url": "https://bicheros.com/contacto"
    },
    "termsOfService": "https://bicheros.com/privacidad"
  },
  "servers": [
    { "url": "https://bicheros.com", "description": "Producción" }
  ],
  "externalDocs": {
    "description": "Documentación para desarrolladores",
    "url": "https://bicheros.com/docs"
  },
  "security": [],
  "components": {
    "securitySchemes": {
      "sessionAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "_bicheros_session",
        "description": "Autenticación por sesión de Devise. Solo válida para requests originadas desde el propio navegador logueado en bicheros.com; no está pensada para consumo por agentes externos."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Token compartido service-to-service (WHATSAPP_BOT_INTERNAL_TOKEN). Uso interno: el único consumidor previsto es el workflow de orquestación de WhatsApp de Bicheros. No se emiten tokens a terceros."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Código de error estable, apto para lógica de programa (ej. \"unauthorized\", \"invalid_phone\")." },
          "message": { "type": "string", "description": "Descripción del error legible por humanos o agentes." },
          "hint": { "type": "string", "description": "Sugerencia de cómo resolver el error, cuando aplica." }
        },
        "required": ["error"]
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "active": { "type": "boolean" },
          "rewards": {
            "type": "object",
            "properties": {
              "share": { "type": "number" },
              "signup": { "type": "number" },
              "qualified": { "type": "number" }
            }
          }
        }
      },
      "CampaignReferral": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "description": "Código único de referido del usuario para esta campaña." },
          "url": { "type": "string", "format": "uri", "description": "URL corta compartible que aplica el código de referido." }
        }
      },
      "WhatsappConversationState": {
        "type": "object",
        "properties": {
          "flow_type": { "type": ["string", "null"], "description": "Tipo de flujo conversacional activo (ej. \"lost_dog_report\"), o null si no hay conversación en curso." },
          "state": { "type": ["string", "null"], "description": "Estado actual de la máquina de estados de la conversación." },
          "context": { "type": "object", "description": "Datos acumulados de la conversación en curso." }
        }
      }
    }
  },
  "paths": {
    "/api/campaigns/{id}": {
      "get": {
        "operationId": "getCampaign",
        "summary": "Obtener el detalle y configuración de recompensas de una campaña",
        "description": "Devuelve nombre, descripción, estado activo y recompensas configuradas para una campaña de referidos. Requiere sesión de usuario autenticado.",
        "tags": ["Campaigns"],
        "security": [{ "sessionAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "ID de la campaña." }
        ],
        "responses": {
          "200": {
            "description": "Detalle de la campaña.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Campaign" } } }
          },
          "401": {
            "description": "No autenticado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Campaña inexistente.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/campaigns/{id}/referral": {
      "get": {
        "operationId": "getOrCreateCampaignReferral",
        "summary": "Obtener (o crear) el código de referido del usuario para una campaña",
        "description": "Idempotente: si el usuario ya tiene un código de referido para esta campaña lo devuelve, si no lo crea. Requiere sesión de usuario autenticado.",
        "tags": ["Campaigns"],
        "security": [{ "sessionAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "ID de la campaña." }
        ],
        "responses": {
          "200": {
            "description": "Código y URL de referido del usuario.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CampaignReferral" } } }
          },
          "401": {
            "description": "No autenticado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/campaigns/{id}/share": {
      "post": {
        "operationId": "trackCampaignShare",
        "summary": "Registrar que el usuario compartió su link de referido",
        "description": "Crea (si hace falta) el referido del usuario para la campaña y registra un evento de share con la fuente indicada, a fines de tracking. Requiere sesión de usuario autenticado.",
        "tags": ["Campaigns"],
        "security": [{ "sessionAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "ID de la campaña." }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source": { "type": "string", "description": "Canal por el que se compartió (ej. \"whatsapp\", \"instagram\"). Por defecto \"other\"." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evento registrado.",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } }
          },
          "401": {
            "description": "No autenticado.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/v1/whatsapp/conversations/current": {
      "get": {
        "operationId": "getCurrentWhatsappConversation",
        "summary": "Consultar el estado de la conversación de WhatsApp en curso para un teléfono",
        "description": "Uso interno service-to-service (workflow de orquestación de WhatsApp). Devuelve el flujo y estado actual de la conversación para el teléfono dado, o valores null si no hay ninguna en curso (las conversaciones inactivas por más de 6 horas se consideran abandonadas).",
        "tags": ["WhatsApp"],
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "phone", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Teléfono normalizado (ej. 5491122334455)." }
        ],
        "responses": {
          "200": {
            "description": "Estado de la conversación (o vacío si no hay ninguna en curso).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WhatsappConversationState" } } }
          },
          "401": {
            "description": "Token Bearer inválido o ausente.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Límite de tasa excedido (120 req/min por IP). Ver el encabezado Retry-After.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/v1/whatsapp/conversations/events": {
      "post": {
        "operationId": "createWhatsappConversationEvent",
        "summary": "Aplicar un evento a la máquina de estados de una conversación de WhatsApp",
        "description": "Uso interno service-to-service (workflow de orquestación de WhatsApp). Busca o crea la conversación según flow_type, valida que el evento sea una transición legal desde el estado actual y la aplica.",
        "tags": ["WhatsApp"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["phone", "external_id", "event"],
                "properties": {
                  "phone": { "type": "string", "description": "Teléfono normalizado del contacto." },
                  "external_id": { "type": "string", "description": "ID externo del mensaje/evento de origen." },
                  "flow_type": { "type": "string", "description": "Tipo de flujo (obligatorio solo para iniciar una conversación nueva)." },
                  "event": { "type": "string", "description": "Nombre del evento/transición a aplicar (ej. \"confirmed\")." },
                  "payload": { "type": "object", "description": "Datos adicionales asociados al evento." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estado resultante de la conversación tras aplicar el evento.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WhatsappConversationState" } } }
          },
          "401": {
            "description": "Token Bearer inválido o ausente.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "Transición inválida, flow_type desconocido o faltante, payload/imagen inválidos, o falló la confirmación.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Límite de tasa excedido (120 req/min por IP, 20 req/min por teléfono). Ver el encabezado Retry-After.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  }
}
