{
  "openapi": "3.1.0",
  "info": {
    "title": "KalendarIA API",
    "version": "2026-10-21",
    "description": "API REST de KalendarIA: servicios, disponibilidad, retenciones, reservas, clientes y webhooks. Autenticación con clave del negocio: `Authorization: Bearer kal_live_…`. Fechas en ISO 8601; importes en unidades mínimas de la moneda (CLP sin decimales, EUR en céntimos). Errores en formato RFC 9457 (`application/problem+json`) con un `code` estable.",
    "contact": {
      "name": "KalendarIA",
      "email": "contacto@kisaso.eu"
    }
  },
  "servers": [
    {
      "url": "https://kalendaria.kisasotest.com/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Cuenta"
    },
    {
      "name": "Catálogo"
    },
    {
      "name": "Disponibilidad"
    },
    {
      "name": "Reservas"
    },
    {
      "name": "Clientes"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "tags": [
          "Cuenta"
        ],
        "summary": "Datos del negocio",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/services": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Listar servicios",
        "parameters": [
          {
            "name": "include_inactive",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Incluir ocultos"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Service"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Crear servicio",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/services/{id}": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Ver servicio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "svc_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Actualizar servicio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "svc_…"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Eliminar servicio (si tiene reservas se archiva)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "svc_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    },
                    "archived": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/staff": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Listar profesionales (con horario semanal)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Staff"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/resources": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Listar recursos (boxes, sillones, salas, equipos)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/availability": {
      "get": {
        "tags": [
          "Disponibilidad"
        ],
        "summary": "Horas libres de un servicio",
        "description": "Máximo 62 días por consulta. Fechas YYYY-MM-DD en la zona del negocio.",
        "parameters": [
          {
            "name": "service_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ID del servicio (obligatorio)"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Desde YYYY-MM-DD (por defecto hoy)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Hasta YYYY-MM-DD (por defecto +6 días)"
          },
          {
            "name": "staff_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Solo este profesional"
          },
          {
            "name": "location_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Solo esta sede"
          },
          {
            "name": "extra_ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Extras, separados por comas"
          },
          {
            "name": "also",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Visita: hasta 3 servicios más seguidos del primero, separados por comas. Cada hora trae `steps`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/holds": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Retener una hora (10 minutos)",
        "description": "Bloquea la hora mientras el cliente termina (checkout, WhatsApp…). Se confirma con POST /bookings { hold_id, customer }.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "service_id",
                  "start"
                ],
                "properties": {
                  "service_id": {
                    "type": "string"
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "staff_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings": {
      "get": {
        "tags": [
          "Reservas"
        ],
        "summary": "Listar reservas",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inicio desde (ISO 8601)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inicio hasta (ISO 8601)"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Estado"
          },
          {
            "name": "customer_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cliente"
          },
          {
            "name": "staff_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Profesional"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1–100"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor de la página siguiente"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Booking"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Crear reserva",
        "description": "Con `service_id` + `start` (de /availability) o con `hold_id`. Si el servicio tiene abono y el negocio cobra online, queda `pending_payment` y se devuelve `payment_url`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}": {
      "get": {
        "tags": [
          "Reservas"
        ],
        "summary": "Ver reserva",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}/cancel": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Cancelar",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "this",
                      "following"
                    ],
                    "description": "following = esta y las siguientes de su serie"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}/reschedule": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Cambiar hora",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "start"
                ],
                "properties": {
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "staff_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}/confirm": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Aprobar (pending_approval → confirmed)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}/complete": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Marcar como atendida",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings/{id}/no_show": {
      "post": {
        "tags": [
          "Reservas"
        ],
        "summary": "Marcar como no asistió",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "bkg_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/customers": {
      "get": {
        "tags": [
          "Clientes"
        ],
        "summary": "Listar o buscar clientes",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Texto"
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Email exacto"
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Teléfono E.164 exacto"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1–100"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Clientes"
        ],
        "summary": "Crear o actualizar cliente (por email/teléfono)",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/customers/import": {
      "post": {
        "tags": [
          "Clientes"
        ],
        "summary": "Alta masiva (hasta 1.000): deduplica por email/teléfono y solo completa datos vacíos de los que ya existen",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customers"
                ],
                "properties": {
                  "customers": {
                    "type": "array",
                    "maxItems": 1000,
                    "items": {
                      "type": "object",
                      "required": [
                        "name"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "phone": {
                          "type": "string"
                        },
                        "national_id": {
                          "type": "string"
                        },
                        "notes": {
                          "type": "string"
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "birthday": {
                          "type": "string",
                          "description": "MM-DD, DD/MM/AAAA o AAAA-MM-DD"
                        },
                        "marketing_consent": {
                          "type": "boolean"
                        }
                      }
                    }
                  },
                  "tag": {
                    "type": "string",
                    "description": "Etiqueta para todos"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created": {
                      "type": "integer"
                    },
                    "updated": {
                      "type": "integer"
                    },
                    "merged": {
                      "type": "integer"
                    },
                    "rejected": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "reason": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/customers/{id}": {
      "get": {
        "tags": [
          "Clientes"
        ],
        "summary": "Ver cliente",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "cus_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Clientes"
        ],
        "summary": "Actualizar cliente",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "cus_…"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "trusted": {
                    "type": "boolean"
                  },
                  "blocked": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Clientes"
        ],
        "summary": "Suprimir datos personales (RGPD / Ley 21.719)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "cus_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/discount_codes": {
      "get": {
        "tags": [
          "Descuentos"
        ],
        "summary": "Listar códigos de descuento (con usos)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Descuentos"
        ],
        "summary": "Crear código: kind percent (1–100) o amount (unidades mínimas), service_ids, valid_from/until, max_uses, max_per_customer, first_visit_only",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "kind",
                  "value"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "percent",
                      "amount"
                    ]
                  },
                  "value": {
                    "type": "integer"
                  },
                  "service_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "valid_from": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "valid_until": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "max_uses": {
                    "type": "integer"
                  },
                  "max_per_customer": {
                    "type": "integer"
                  },
                  "first_visit_only": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/reviews": {
      "get": {
        "tags": [
          "Reseñas"
        ],
        "summary": "Reseñas de clientes (status: published, pending, hidden)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filtro por estado"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/gift_cards": {
      "get": {
        "tags": [
          "Tarjetas regalo"
        ],
        "summary": "Listar tarjetas regalo",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Tarjetas regalo"
        ],
        "summary": "Crear (vender) una tarjeta regalo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "integer",
                    "description": "Unidades mínimas (CLP sin decimales, EUR en céntimos)"
                  },
                  "validity_days": {
                    "type": "integer"
                  },
                  "buyer_name": {
                    "type": "string"
                  },
                  "recipient_name": {
                    "type": "string"
                  },
                  "recipient_email": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/gift_cards/{code}": {
      "get": {
        "tags": [
          "Tarjetas regalo"
        ],
        "summary": "Consultar saldo por código (ABCD-EFGH) o id",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ABCD-EFGH"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/gift_cards/{code}/redeem": {
      "post": {
        "tags": [
          "Tarjetas regalo"
        ],
        "summary": "Canjear saldo",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ABCD-EFGH"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repite la petición sin duplicar (24 h)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "booking_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/webhook_endpoints": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar webhooks",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Suscribir un webhook (REST hook)",
        "description": "La respuesta incluye `secret` una sola vez.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "*",
                        "booking.held",
                        "booking.created",
                        "booking.confirmed",
                        "booking.pending_approval",
                        "booking.pending_payment",
                        "booking.rescheduled",
                        "booking.canceled",
                        "booking.completed",
                        "booking.no_show",
                        "booking.checked_in",
                        "booking.expired",
                        "payment.succeeded",
                        "payment.failed",
                        "payment.refunded",
                        "gift_card.purchased",
                        "review.created",
                        "customer.created",
                        "customer.updated",
                        "waitlist.joined",
                        "waitlist.notified"
                      ]
                    }
                  },
                  "description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/webhook_endpoints/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Eliminar webhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "whe_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Últimas entregas de un webhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "whe_…"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1–100"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Registro de eventos (reconciliación)",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Tipo de evento"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1–100"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/events/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Ver evento",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "evt_…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "booking.held": {
      "post": {
        "summary": "booking.held",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.created": {
      "post": {
        "summary": "booking.created",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.confirmed": {
      "post": {
        "summary": "booking.confirmed",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.pending_approval": {
      "post": {
        "summary": "booking.pending_approval",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.pending_payment": {
      "post": {
        "summary": "booking.pending_payment",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.rescheduled": {
      "post": {
        "summary": "booking.rescheduled",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.canceled": {
      "post": {
        "summary": "booking.canceled",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.completed": {
      "post": {
        "summary": "booking.completed",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.no_show": {
      "post": {
        "summary": "booking.no_show",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.checked_in": {
      "post": {
        "summary": "booking.checked_in",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "booking.expired": {
      "post": {
        "summary": "booking.expired",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "payment.succeeded": {
      "post": {
        "summary": "payment.succeeded",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "payment.failed": {
      "post": {
        "summary": "payment.failed",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "payment.refunded": {
      "post": {
        "summary": "payment.refunded",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "gift_card.purchased": {
      "post": {
        "summary": "gift_card.purchased",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "review.created": {
      "post": {
        "summary": "review.created",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "customer.created": {
      "post": {
        "summary": "customer.created",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "customer.updated": {
      "post": {
        "summary": "customer.updated",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "waitlist.joined": {
      "post": {
        "summary": "waitlist.joined",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    },
    "waitlist.notified": {
      "post": {
        "summary": "waitlist.notified",
        "description": "Cabeceras Standard Webhooks: webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256 de id.timestamp.cuerpo>).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recibido"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "kal_live_…"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Clave ausente o no válida",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "No existe",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflicto (p. ej. slot_unavailable)",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Datos no válidos",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Límite de peticiones (600/min lectura, 120/min escritura)",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "Money": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP",
              "EUR"
            ]
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "enum": [
              "CL",
              "ES"
            ]
          },
          "currency": {
            "type": "string"
          },
          "locale": {
            "type": "string"
          },
          "time_zone": {
            "type": "string"
          },
          "booking_page_url": {
            "type": "string"
          }
        }
      },
      "Service": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_min": {
            "type": "integer"
          },
          "buffer_before_min": {
            "type": "integer"
          },
          "buffer_after_min": {
            "type": "integer"
          },
          "slot_interval_min": {
            "type": "integer"
          },
          "min_notice_min": {
            "type": "integer"
          },
          "max_advance_days": {
            "type": "integer"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "deposit": {
            "$ref": "#/components/schemas/Money"
          },
          "requires_approval": {
            "type": "boolean"
          },
          "cancel_window_min": {
            "type": "integer"
          },
          "requires_resource": {
            "type": "boolean"
          },
          "staff_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "resource_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "ServiceInput": {
        "type": "object",
        "required": [
          "name",
          "duration_min"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "duration_min": {
            "type": "integer"
          },
          "buffer_before_min": {
            "type": "integer"
          },
          "buffer_after_min": {
            "type": "integer"
          },
          "slot_interval_min": {
            "type": "integer"
          },
          "min_notice_min": {
            "type": "integer"
          },
          "max_advance_days": {
            "type": "integer"
          },
          "price_amount": {
            "type": "integer"
          },
          "deposit_amount": {
            "type": "integer"
          },
          "requires_approval": {
            "type": "boolean"
          },
          "cancel_window_min": {
            "type": "integer"
          },
          "requires_resource": {
            "type": "boolean"
          },
          "staff_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "resource_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "Staff": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "weekly_hours": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "weekday": {
                  "type": "integer",
                  "description": "1 = lunes … 7 = domingo"
                },
                "start": {
                  "type": "string"
                },
                "end": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Resource": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "Availability": {
        "type": "object",
        "properties": {
          "service_id": {
            "type": "string"
          },
          "time_zone": {
            "type": "string"
          },
          "duration_min": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "slots": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "start": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "end": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "local_time": {
                        "type": "string"
                      },
                      "staff_ids": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "CustomerInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "Se normaliza a E.164 con el prefijo del país del negocio"
          },
          "national_id": {
            "type": "string",
            "description": "RUT (CL) o NIF/NIE (ES); se valida"
          },
          "whatsapp_consent": {
            "type": "boolean"
          },
          "marketing_consent": {
            "type": "boolean"
          }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "consents": {
            "type": "object"
          },
          "trusted": {
            "type": "boolean"
          },
          "blocked": {
            "type": "boolean"
          }
        }
      },
      "BookingInput": {
        "type": "object",
        "properties": {
          "service_id": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "staff_id": {
            "type": "string"
          },
          "hold_id": {
            "type": "string"
          },
          "customer_id": {
            "type": "string"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerInput"
          },
          "notes": {
            "type": "string"
          },
          "answers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "attribution": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "utm_*, fbclid, gclid…"
          },
          "collect_payment": {
            "type": "boolean",
            "description": "Cobrar el abono online si el negocio tiene pagos (por defecto true)"
          },
          "skip_notice": {
            "type": "boolean",
            "description": "Ignorar la antelación mínima"
          },
          "recurrence": {
            "type": "object",
            "description": "Crea una serie: misma hora local cada N semanas. Responde booking_series con las creadas y las omitidas por estar ocupadas.",
            "properties": {
              "every_weeks": {
                "type": "integer",
                "minimum": 1,
                "maximum": 8
              },
              "count": {
                "type": "integer",
                "minimum": 2,
                "maximum": 52
              }
            },
            "required": [
              "every_weeks",
              "count"
            ]
          },
          "extra_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "service_address": {
            "type": "object",
            "properties": {
              "address": {
                "type": "string"
              },
              "zone": {
                "type": "string"
              }
            },
            "required": [
              "address"
            ],
            "description": "Servicios a domicilio"
          },
          "also": {
            "type": "array",
            "maxItems": 3,
            "description": "Visita: más servicios seguidos del primero, un solo cobro. La respuesta trae `visit` con todas las reservas.",
            "items": {
              "type": "object",
              "required": [
                "service_id"
              ],
              "properties": {
                "service_id": {
                  "type": "string"
                },
                "extra_ids": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "discount_code": {
            "type": "string",
            "description": "Código de descuento del negocio"
          }
        }
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "held",
              "pending_payment",
              "pending_approval",
              "confirmed",
              "completed",
              "no_show",
              "canceled",
              "expired"
            ]
          },
          "service": {
            "type": "object"
          },
          "staff": {
            "type": [
              "object",
              "null"
            ]
          },
          "resource": {
            "type": [
              "object",
              "null"
            ]
          },
          "customer": {
            "type": [
              "object",
              "null"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          },
          "time_zone": {
            "type": "string"
          },
          "local_start": {
            "type": "string"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "deposit": {
            "$ref": "#/components/schemas/Money"
          },
          "paid": {
            "$ref": "#/components/schemas/Money"
          },
          "source": {
            "type": "string"
          },
          "attribution": {
            "type": "object"
          },
          "hold_expires_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "integer"
          },
          "manage_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "secret": {
            "type": "string",
            "description": "Solo al crear"
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "const": "event"
          },
          "type": {
            "type": "string",
            "enum": [
              "booking.held",
              "booking.created",
              "booking.confirmed",
              "booking.pending_approval",
              "booking.pending_payment",
              "booking.rescheduled",
              "booking.canceled",
              "booking.completed",
              "booking.no_show",
              "booking.checked_in",
              "booking.expired",
              "payment.succeeded",
              "payment.failed",
              "payment.refunded",
              "gift_card.purchased",
              "review.created",
              "customer.created",
              "customer.updated",
              "waitlist.joined",
              "waitlist.notified"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object"
          }
        }
      }
    }
  }
}