{
  "openapi": "3.1.0",
  "info": {
    "title": "Turnova — API externa",
    "version": "1",
    "description": "Para que un negocio saque turnos desde su propio sitio. El negocio sale de la credencial: no hay slug en la URL. Toda respuesta es un ResultadoDominio versión 1. Clave pública (`tnv_pk_…`) desde el navegador, solo en los orígenes permitidos; clave secreta (`tnv_sk_…`) solo desde un servidor. Incluida en el plan Enterprise."
  },
  "servers": [
    {
      "url": "https://turnovapp.com"
    }
  ],
  "security": [
    {
      "claveApi": []
    }
  ],
  "paths": {
    "/api/v1/catalogo": {
      "get": {
        "operationId": "leerCatalogo",
        "summary": "Servicios visibles, sedes y profesionales visibles del negocio",
        "description": "Scope `catalogo:leer`. Mismo catálogo que la página pública de reservas.",
        "parameters": [
          {
            "name": "sucursalId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Acota los profesionales a esa sede (si el negocio tiene más de una)."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Consultado"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "datos": {
                          "$ref": "#/components/schemas/Catalogo"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/LimiteUso"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Catálogo"
        ]
      },
      "options": {
        "operationId": "preflightCatalogo",
        "security": [],
        "summary": "Preflight CORS",
        "responses": {
          "204": {
            "description": "Origen cargado en alguna credencial pública activa."
          },
          "403": {
            "description": "Origen no permitido (sin cabeceras CORS)."
          }
        },
        "tags": [
          "Catálogo"
        ]
      }
    },
    "/api/v1/disponibilidad": {
      "get": {
        "operationId": "consultarDisponibilidad",
        "summary": "Horarios libres de un servicio",
        "description": "Scope `disponibilidad:leer`. Horas en la zona del negocio. Parámetros fuera de la lista dan 400. La respuesta puede tener hasta 45 segundos de antigüedad (caché por negocio y parámetros); la reserva vuelve a validar el horario.",
        "parameters": [
          {
            "name": "servicioId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "varianteId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opción del servicio (obligatoria si el servicio tiene opciones)."
          },
          {
            "name": "profesionalId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sucursalId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "desde",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "AAAA-MM-DD; por defecto, hoy."
          },
          {
            "name": "dias",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 14
            }
          },
          {
            "name": "addOnIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Ids de extras separados por coma (estiran la duración)."
          }
        ],
        "responses": {
          "200": {
            "description": "Horarios",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Consultado"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "datos": {
                          "$ref": "#/components/schemas/Disponibilidad"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/LimiteUso"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Disponibilidad"
        ]
      },
      "options": {
        "operationId": "preflightDisponibilidad",
        "security": [],
        "summary": "Preflight CORS",
        "responses": {
          "204": {
            "description": "Origen permitido."
          },
          "403": {
            "description": "Origen no permitido."
          }
        },
        "tags": [
          "Disponibilidad"
        ]
      }
    },
    "/api/v1/reservas": {
      "post": {
        "operationId": "crearReserva",
        "summary": "Crear una reserva de uno a ocho turnos",
        "description": "Scope `reservas:crear`. Exige `Idempotency-Key`: misma clave y mismo cuerpo devuelven la misma\nreserva (200, `operacion.repetida: true`); otro cuerpo con la misma clave da 409\n`CONFLICTO_INTENCION`. Con seña, la reserva queda GUARDADO con `pago: PENDIENTE` y\n`datos.sena.checkoutUrl` (Mercado Pago); se confirma solo por el webhook de MP.\n",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{8,100}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NuevaReserva"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reintento de una reserva ya hecha con la misma clave",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservaResultado"
                }
              }
            }
          },
          "201": {
            "description": "Reserva nueva",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservaResultado"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/LimiteUso"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Reservas"
        ]
      },
      "options": {
        "operationId": "preflightReservas",
        "security": [],
        "summary": "Preflight CORS",
        "responses": {
          "204": {
            "description": "Origen permitido."
          },
          "403": {
            "description": "Origen no permitido."
          }
        },
        "tags": [
          "Reservas"
        ]
      }
    },
    "/api/v1/reservas/{compraId}": {
      "get": {
        "operationId": "leerReserva",
        "summary": "Estado de una reserva hecha por la API",
        "description": "Scope `reservas:leer`, solo clave secreta. Solo reservas creadas por la API v1 de este negocio.",
        "parameters": [
          {
            "name": "compraId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estado",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Consultado"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "datos": {
                          "$ref": "#/components/schemas/EstadoReserva"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/LimiteUso"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Reservas"
        ]
      }
    },
    "/api/configuracion/api-externa/credenciales": {
      "get": {
        "operationId": "listarCredenciales",
        "summary": "Credenciales del negocio (panel, con sesión)",
        "description": "Dueña o administradora (la Super Recepción no pasa). Nunca devuelve la clave ni su hash.",
        "security": [
          {
            "sesionPanel": []
          }
        ],
        "responses": {
          "200": {
            "description": "Lista",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "datos": {
                      "type": "object",
                      "properties": {
                        "plan": {
                          "type": "object",
                          "properties": {
                            "incluyeApi": {
                              "type": "boolean",
                              "description": "false: el plan no incluye la API; las claves existentes no funcionan."
                            }
                          }
                        },
                        "credenciales": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Credencial"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Panel"
        ]
      },
      "post": {
        "operationId": "crearCredencial",
        "summary": "Crear una credencial; la clave se muestra una sola vez",
        "security": [
          {
            "sesionPanel": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nombre",
                  "tipo",
                  "scopes"
                ],
                "additionalProperties": false,
                "properties": {
                  "nombre": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "tipo": {
                    "enum": [
                      "PUBLICA",
                      "SECRETA"
                    ]
                  },
                  "scopes": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "enum": [
                        "catalogo:leer",
                        "disponibilidad:leer",
                        "reservas:crear",
                        "reservas:leer"
                      ]
                    },
                    "description": "`reservas:leer` solo para SECRETA."
                  },
                  "origenes": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "example": "https://mi-spa.com"
                    },
                    "description": "Obligatorio (1 a 10) para PUBLICA; prohibido para SECRETA. https, o http://localhost / 127.0.0.1."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Creada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "datos": {
                      "type": "object",
                      "properties": {
                        "credencial": {
                          "$ref": "#/components/schemas/Credencial"
                        },
                        "clave": {
                          "type": "string",
                          "description": "Única vez que se devuelve."
                        },
                        "aviso": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Panel"
        ],
        "description": "Exige el plan Enterprise: sin él, 403 `PLAN_SIN_API` con `upgrade_url`."
      }
    },
    "/api/configuracion/api-externa/credenciales/{id}/revocar": {
      "post": {
        "operationId": "revocarCredencial",
        "summary": "Revocar (repetible)",
        "security": [
          {
            "sesionPanel": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revocada (o ya lo estaba, `operacion.repetida`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "operacion": {
                      "type": "object",
                      "properties": {
                        "referencia": {
                          "type": "string"
                        },
                        "repetida": {
                          "type": "boolean"
                        }
                      }
                    },
                    "datos": {
                      "type": "object",
                      "properties": {
                        "credencial": {
                          "$ref": "#/components/schemas/Credencial"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Panel"
        ]
      }
    },
    "/api/configuracion/api-externa/uso": {
      "get": {
        "operationId": "leerUso",
        "tags": [
          "Panel"
        ],
        "summary": "Uso diario por credencial",
        "description": "Pedidos por día (hora de Argentina) y credencial: lecturas, escrituras, reservas creadas y rechazos por límite. Por defecto, los últimos 30 días; hasta 92.",
        "security": [
          {
            "sesionPanel": []
          }
        ],
        "parameters": [
          {
            "name": "desde",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "hasta",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Por defecto, hoy."
          }
        ],
        "responses": {
          "200": {
            "description": "Uso",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Consultado"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "datos": {
                          "$ref": "#/components/schemas/Uso"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "claveApi": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer tnv_pk_… (navegador) o tnv_sk_… (servidor)."
      },
      "sesionPanel": {
        "type": "apiKey",
        "in": "cookie",
        "name": "next-auth.session-token"
      }
    },
    "responses": {
      "Error": {
        "description": "Error C.1",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fallo"
            }
          }
        }
      },
      "LimiteUso": {
        "description": "Demasiados pedidos (LIMITE_USO). Reintentar con la misma clave de idempotencia.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Segundos."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Fallo"
            }
          }
        }
      }
    },
    "schemas": {
      "Progreso": {
        "type": "object",
        "required": [
          "principal"
        ],
        "properties": {
          "principal": {
            "enum": [
              "CONSULTADO",
              "NO_GUARDADO",
              "GUARDADO",
              "CONFIRMADO",
              "DESCONOCIDO"
            ]
          },
          "pago": {
            "enum": [
              "PENDIENTE",
              "CONFIRMADO",
              "EN_REVISION",
              "INCIERTO"
            ]
          },
          "avisos": {
            "enum": [
              "PENDIENTES",
              "RESUELTOS",
              "EN_REVISION"
            ]
          }
        }
      },
      "Consultado": {
        "type": "object",
        "required": [
          "version",
          "ok",
          "progreso",
          "requiereRevision",
          "datos"
        ],
        "properties": {
          "version": {
            "const": 1
          },
          "ok": {
            "const": true
          },
          "progreso": {
            "$ref": "#/components/schemas/Progreso"
          },
          "requiereRevision": {
            "type": "boolean"
          }
        }
      },
      "Fallo": {
        "type": "object",
        "required": [
          "version",
          "ok",
          "progreso",
          "requiereRevision",
          "error"
        ],
        "properties": {
          "version": {
            "const": 1
          },
          "ok": {
            "const": false
          },
          "progreso": {
            "$ref": "#/components/schemas/Progreso"
          },
          "requiereRevision": {
            "type": "boolean"
          },
          "error": {
            "type": "object",
            "required": [
              "dominio",
              "codigo",
              "mensaje",
              "categoria",
              "reintentable",
              "recuperacion"
            ],
            "properties": {
              "dominio": {
                "enum": [
                  "api",
                  "compras"
                ]
              },
              "codigo": {
                "type": "string",
                "description": "dominio api: API_NO_HABILITADA, PLAN_SIN_API, CREDENCIAL_FALTANTE, CREDENCIAL_INVALIDA, CREDENCIAL_REVOCADA,\nCLAVE_SECRETA_EN_NAVEGADOR, ORIGEN_NO_PERMITIDO, SCOPE_FALTANTE, LIMITE_USO,\nIDEMPOTENCY_KEY_FALTANTE, IDEMPOTENCY_KEY_INVALIDA, ENTRADA_INVALIDA, NO_ENCONTRADO,\nSERVICIO_DERIVADO_WHATSAPP, CONSULTA_RECHAZADA, ERROR_TECNICO.\ndominio compras: los de la tabla «Compras/reservas: código → política» de C.1\n(docs/copiloto/contratos/resultados-errores-comunes.md).\n"
              },
              "mensaje": {
                "type": "string"
              },
              "categoria": {
                "enum": [
                  "ENTRADA",
                  "ACCESO",
                  "ESTADO",
                  "IDENTIDAD",
                  "PROVEEDOR",
                  "TECNICO"
                ]
              },
              "reintentable": {
                "type": "boolean"
              },
              "recuperacion": {
                "enum": [
                  "CORREGIR_ENTRADA",
                  "RELEER_Y_CONFIRMAR",
                  "REINTENTAR_MISMA_INTENCION",
                  "CONSULTAR_ESTADO",
                  "REVISION_HUMANA",
                  "NO_REINTENTAR"
                ]
              },
              "referenciaDiagnostico": {
                "type": "string"
              },
              "campos": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "campo": {
                      "type": "string"
                    },
                    "codigo": {
                      "type": "string"
                    },
                    "mensaje": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "upgrade_url": {
            "type": "string",
            "description": "Solo con PLAN_SIN_API: pantalla de planes del panel del negocio."
          }
        }
      },
      "Catalogo": {
        "type": "object",
        "properties": {
          "negocio": {
            "type": "object",
            "properties": {
              "nombre": {
                "type": "string"
              },
              "zonaHoraria": {
                "type": "string",
                "example": "America/Argentina/Buenos_Aires"
              },
              "moneda": {
                "const": "ARS"
              },
              "cobraSena": {
                "type": "boolean"
              }
            }
          },
          "sedes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "nombre": {
                  "type": "string"
                },
                "direccion": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "principal": {
                  "type": "boolean"
                }
              }
            }
          },
          "profesionales": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "nombre": {
                  "type": "string"
                },
                "especialidad": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "servicios": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "nombre": {
                  "type": "string"
                },
                "descripcion": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "duracionMin": {
                  "type": "integer"
                },
                "precio": {
                  "type": "number",
                  "description": "Precio de lista. El importe final lo calcula el servidor al reservar."
                },
                "descuentoPct": {
                  "type": "number"
                },
                "sesiones": {
                  "type": "integer",
                  "description": "Mayor a 1: paquete de sesiones."
                },
                "cantidadProfesionales": {
                  "type": "integer"
                },
                "comboPorEtapas": {
                  "type": "boolean"
                },
                "seReservaPorWhatsapp": {
                  "type": "boolean",
                  "description": "true: la reserva por la API responde 409 SERVICIO_DERIVADO_WHATSAPP."
                },
                "categorias": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "nombre": {
                        "type": "string"
                      }
                    }
                  }
                },
                "opciones": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "nombre": {
                        "type": "string"
                      },
                      "precio": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "duracionMin": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "capacidadPersonas": {
                        "type": "integer"
                      },
                      "cantidadProfesionales": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "comboPorEtapas": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "extras": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "nombre": {
                        "type": "string"
                      },
                      "precio": {
                        "type": "number"
                      },
                      "duracionMin": {
                        "type": "integer"
                      },
                      "cantidadMax": {
                        "type": "integer",
                        "description": "Para pedir N unidades, repetir el id N veces en addOnIds."
                      }
                    }
                  }
                },
                "profesionales": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "nombre": {
                        "type": "string"
                      },
                      "especialidad": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Disponibilidad": {
        "type": "object",
        "properties": {
          "horarios": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string",
                "example": "10:00"
              }
            },
            "description": "Por fecha AAAA-MM-DD."
          },
          "horariosPorProfesional": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "diasSinLugar": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "profesionales": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "nombre": {
                  "type": "string"
                }
              }
            }
          },
          "finPorHorario": {
            "type": "object",
            "description": "Solo combos por etapas: fin real por fecha y hora de inicio."
          }
        }
      },
      "NuevaReserva": {
        "type": "object",
        "required": [
          "turnos",
          "cliente"
        ],
        "additionalProperties": false,
        "properties": {
          "turnos": {
            "type": "array",
            "minItems": 1,
            "maxItems": 8,
            "items": {
              "type": "object",
              "required": [
                "servicioId",
                "fecha",
                "hora"
              ],
              "additionalProperties": false,
              "properties": {
                "servicioId": {
                  "type": "string"
                },
                "varianteId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "profesionalId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "fecha": {
                  "type": "string",
                  "format": "date"
                },
                "hora": {
                  "type": "string",
                  "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
                  "description": "Hora del negocio."
                },
                "addOnIds": {
                  "type": "array",
                  "maxItems": 50,
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "sucursalId": {
            "type": [
              "string",
              "null"
            ]
          },
          "modoCadena": {
            "const": "uno_tras_otro"
          },
          "cliente": {
            "type": "object",
            "required": [
              "nombre",
              "telefono"
            ],
            "additionalProperties": false,
            "properties": {
              "nombre": {
                "type": "string",
                "maxLength": 120
              },
              "telefono": {
                "type": "string",
                "maxLength": 32
              },
              "codigoPais": {
                "type": "string",
                "maxLength": 6,
                "default": "+54"
              },
              "email": {
                "type": "string",
                "maxLength": 254
              },
              "consintioContacto": {
                "type": "boolean"
              }
            }
          },
          "notas": {
            "type": "string",
            "maxLength": 4000
          },
          "cuponCodigo": {
            "type": "string",
            "maxLength": 64
          }
        }
      },
      "ReservaResultado": {
        "type": "object",
        "required": [
          "version",
          "ok",
          "progreso",
          "operacion",
          "requiereRevision",
          "datos"
        ],
        "properties": {
          "version": {
            "const": 1
          },
          "ok": {
            "const": true
          },
          "progreso": {
            "$ref": "#/components/schemas/Progreso"
          },
          "operacion": {
            "type": "object",
            "properties": {
              "referencia": {
                "type": "string",
                "description": "compraId"
              },
              "repetida": {
                "type": "boolean"
              }
            }
          },
          "requiereRevision": {
            "type": "boolean"
          },
          "datos": {
            "type": "object",
            "properties": {
              "compraId": {
                "type": "string"
              },
              "total": {
                "type": "number"
              },
              "sena": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "monto": {
                    "type": "number"
                  },
                  "checkoutUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Link de Mercado Pago. MP vuelve a la página de resultado de Turnova."
                  }
                }
              },
              "turnos": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "turnoId": {
                      "type": "string"
                    },
                    "servicio": {
                      "type": "string"
                    },
                    "profesional": {
                      "type": "string"
                    },
                    "fecha": {
                      "type": "string",
                      "format": "date"
                    },
                    "hora": {
                      "type": "string"
                    },
                    "duracionMin": {
                      "type": "integer"
                    },
                    "extras": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "sesionesTotal": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "EstadoReserva": {
        "type": "object",
        "properties": {
          "compraId": {
            "type": "string"
          },
          "total": {
            "type": "number"
          },
          "estadoPago": {
            "enum": [
              "SIN_SENA",
              "PAGADA",
              "PENDIENTE",
              "VENCIDA",
              "EN_EL_LOCAL",
              "EN_REVISION"
            ]
          },
          "sena": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "monto": {
                "type": "number"
              },
              "checkoutUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Solo mientras la seña está PENDIENTE."
              }
            }
          },
          "turnos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "turnoId": {
                  "type": "string"
                },
                "estado": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Credencial": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "nombre": {
            "type": "string"
          },
          "tipo": {
            "enum": [
              "PUBLICA",
              "SECRETA"
            ]
          },
          "prefijo": {
            "type": "string",
            "example": "tnv_pk_ab12cd34"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "origenes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "ultimoUsoEn": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revocadaEn": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Uso": {
        "type": "object",
        "properties": {
          "desde": {
            "type": "string",
            "format": "date"
          },
          "hasta": {
            "type": "string",
            "format": "date"
          },
          "total": {
            "type": "object",
            "properties": {
              "lecturas": {
                "type": "integer"
              },
              "escrituras": {
                "type": "integer"
              },
              "reservasCreadas": {
                "type": "integer"
              },
              "limitados": {
                "type": "integer",
                "description": "Pedidos rechazados con 429."
              }
            }
          },
          "porCredencial": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "type": "object",
                  "properties": {
                    "credencialId": {
                      "type": "string"
                    },
                    "nombre": {
                      "type": "string"
                    },
                    "prefijo": {
                      "type": "string"
                    },
                    "tipo": {
                      "enum": [
                        "PUBLICA",
                        "SECRETA"
                      ]
                    },
                    "revocadaEn": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                },
                {
                  "type": "object",
                  "properties": {
                    "lecturas": {
                      "type": "integer"
                    },
                    "escrituras": {
                      "type": "integer"
                    },
                    "reservasCreadas": {
                      "type": "integer"
                    },
                    "limitados": {
                      "type": "integer",
                      "description": "Pedidos rechazados con 429."
                    }
                  }
                }
              ]
            }
          },
          "dias": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "type": "object",
                  "properties": {
                    "dia": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                },
                {
                  "type": "object",
                  "properties": {
                    "lecturas": {
                      "type": "integer"
                    },
                    "escrituras": {
                      "type": "integer"
                    },
                    "reservasCreadas": {
                      "type": "integer"
                    },
                    "limitados": {
                      "type": "integer",
                      "description": "Pedidos rechazados con 429."
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Catálogo",
      "description": "Servicios, sedes y profesionales que el negocio muestra."
    },
    {
      "name": "Disponibilidad",
      "description": "Horarios libres para un servicio."
    },
    {
      "name": "Reservas",
      "description": "Crear una reserva y consultar su estado."
    },
    {
      "name": "Panel",
      "description": "Gestión de claves y uso desde el panel de Turnova (sesión de la dueña o una administradora)."
    }
  ]
}