{
  "$comment": "Mantener sincronizado a mano con la otra copia de este spec (server/src/openapi.json y public/openapi.json) — son dos despliegues separados (gateway Fastify vs sitio estático nginx), sin build compartido que los enlace.",
  "openapi": "3.1.0",
  "info": {
    "title": "Ciberfobia Agent Gateway",
    "version": "1.0.0",
    "description": "API pública y sin autenticación de 4 tools de captación de leads de Ciberfobia, pensada para ser llamada por agentes de IA (function calling) y humanos vía REST. Cubre: consultar información de servicios/FAQ, consultar huecos libres del calendario real, reservar una cita real de 30 minutos y pedir un presupuesto a medida (crea un lead). Estos mismos endpoints también están expuestos vía MCP (streamable-http) en `/mcp`, fuera del alcance de este documento OpenAPI. Sin autenticación por diseño: la defensa contra abuso es rate limiting (30 req/min general, 10 req/min en `/api/reservar-cita`, `/api/presupuesto` y `/mcp`). No expone datos de otros contactos ni de la cartera de Ciberfobia — solo escribe leads nuevos o consulta contenido curado/disponibilidad de calendario.",
    "contact": {
      "name": "Ciberfobia",
      "url": "https://ciberfobia.com"
    }
  },
  "servers": [
    {
      "url": "https://agents.ciberfobia.com",
      "description": "Gateway de producción"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Estado del servicio",
        "description": "Comprobación de salud del gateway. No requiere parámetros ni autenticación.",
        "responses": {
          "200": {
            "description": "El servicio está operativo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "ok" }
                  },
                  "required": ["status"],
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    },
    "/api/buscar-info": {
      "get": {
        "operationId": "buscarInfo",
        "summary": "Buscar información de servicios y FAQ",
        "description": "Devuelve el catálogo de servicios y preguntas frecuentes de Ciberfobia, opcionalmente filtrado por tema. No consulta ningún sistema externo (contenido curado, sin escritura). Sin rate limit especial (usa el límite general de 30 req/min).",
        "parameters": [
          {
            "name": "tema",
            "in": "query",
            "required": false,
            "description": "Texto libre para filtrar servicios y FAQ cuyo nombre/descripción o pregunta/respuesta lo contengan (case-insensitive). Si se omite, devuelve el catálogo completo.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo de servicios y FAQ (completo o filtrado por `tema`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "servicios": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "nombre": { "type": "string" },
                          "descripcion": { "type": "string" },
                          "url": { "type": "string", "format": "uri" }
                        },
                        "required": ["nombre", "descripcion", "url"]
                      }
                    },
                    "faq": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "pregunta": { "type": "string" },
                          "respuesta": { "type": "string" }
                        },
                        "required": ["pregunta", "respuesta"]
                      }
                    }
                  },
                  "required": ["servicios", "faq"],
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    },
    "/api/disponibilidad": {
      "get": {
        "operationId": "consultarDisponibilidad",
        "summary": "Consultar huecos libres del calendario real",
        "description": "Consulta contra GoHighLevel los huecos libres del calendario real de Ciberfobia en el rango de fechas indicado. `fechaInicio` y `fechaFin` deben ser fechas parseables (recomendado ISO 8601); `fechaFin` debe ser posterior a `fechaInicio`. Sin rate limit especial (usa el límite general de 30 req/min).",
        "parameters": [
          {
            "name": "fechaInicio",
            "in": "query",
            "required": true,
            "description": "Inicio del rango a consultar, fecha parseable (ISO 8601 recomendado, p. ej. 2026-09-01T00:00:00Z).",
            "schema": { "type": "string", "minLength": 1 }
          },
          {
            "name": "fechaFin",
            "in": "query",
            "required": true,
            "description": "Fin del rango a consultar, fecha parseable (ISO 8601 recomendado). Debe ser posterior a fechaInicio.",
            "schema": { "type": "string", "minLength": 1 }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de huecos libres en el rango solicitado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "startTime": { "type": "string", "format": "date-time" }
                        },
                        "required": ["startTime"]
                      }
                    }
                  },
                  "required": ["slots"],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos (faltan/vacíos) o no se pudo consultar la disponibilidad contra GoHighLevel.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/reservar-cita": {
      "post": {
        "operationId": "reservarCita",
        "summary": "Reservar una cita real de 30 minutos",
        "description": "Crea o actualiza un contacto en el CRM (GoHighLevel) y reserva una cita real de 30 minutos en el calendario de Ciberfobia. `inicio` debe ser una fecha ISO válida y futura. Endpoint de escritura: rate limit de 10 req/min. Requiere que el `Origin` de la petición esté permitido (CORS) si se llama desde un navegador.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": { "type": "string", "minLength": 1, "description": "Nombre del contacto." },
                  "email": { "type": "string", "format": "email", "description": "Email del contacto." },
                  "telefono": { "type": "string", "description": "Teléfono del contacto (opcional)." },
                  "inicio": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Fecha/hora ISO de inicio de la cita. Debe ser futura. La duración es fija: 30 minutos."
                  },
                  "nota": { "type": "string", "description": "Nota opcional. No se persiste actualmente en GHL (solo viaja en la petición)." }
                },
                "required": ["nombre", "email", "inicio"],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contacto y cita creados/actualizados en GoHighLevel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contactId": { "type": "string" },
                    "appointment": {
                      "type": "object",
                      "description": "Respuesta cruda de la API de citas de GoHighLevel."
                    }
                  },
                  "required": ["contactId", "appointment"]
                }
              }
            }
          },
          "400": {
            "description": "Datos inválidos (validación de esquema) o no se pudo crear la reserva contra GoHighLevel.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/presupuesto": {
      "post": {
        "operationId": "pedirPresupuesto",
        "summary": "Pedir presupuesto a medida (crea un lead)",
        "description": "Crea o actualiza un contacto en el CRM (GoHighLevel) como lead interesado en un presupuesto a medida, sin agendar cita. Endpoint de escritura: rate limit de 10 req/min. Requiere que el `Origin` de la petición esté permitido (CORS) si se llama desde un navegador.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": { "type": "string", "minLength": 1, "description": "Nombre del contacto." },
                  "email": { "type": "string", "format": "email", "description": "Email del contacto." },
                  "telefono": { "type": "string", "description": "Teléfono del contacto (opcional)." },
                  "nota": { "type": "string", "description": "Nota opcional. No se persiste actualmente en GHL (solo viaja en la petición)." }
                },
                "required": ["nombre", "email"],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contacto creado/actualizado en GoHighLevel como lead.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contactId": { "type": "string" }
                  },
                  "required": ["contactId"],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Datos inválidos (validación de esquema) o no se pudo procesar la solicitud contra GoHighLevel.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Mensaje de error genérico, sin detalles internos ni datos de GoHighLevel." },
          "detalles": {
            "description": "Presente solo en errores de validación de esquema (zod `flatten()`): campos con formErrors/fieldErrors.",
            "type": "object"
          }
        },
        "required": ["error"]
      }
    }
  }
}
