{
  "openapi": "3.1.0",
  "info": {
    "title": "RADAR · Investigaciones y API pública",
    "version": "0.5.0",
    "description": "Página pública y motor beta por invitación. Piloto de investigación interna de casas completas. POST investigaciones admite un trabajo; GET privado con Bearer consulta su estado y resultado durante 24 horas. Máximo global cinco trabajos por día UTC y uno simultáneo. La ejecución puede terminar parcial si faltan fuentes o precios; no garantiza cotizaciones ni equivalencia. Las rutas encargos y analisis permanecen como opciones de integración con evidencia externa, no tareas del usuario. Referencias y archivos data son un flujo de catálogo separado. No cuentas, vigilancia OTA programada ni cambios automáticos de precios."
  },
  "servers": [
    {
      "url": "https://radaroperador.com"
    }
  ],
  "paths": {
    "/api/v1/investigaciones": {
      "post": {
        "operationId": "startInternalResearch",
        "summary": "Iniciar investigación interna de la propiedad y hasta 20 candidatos",
        "description": "Ruta principal para personas y agentes. JSON UTF-8 de hasta 32 KiB sin compresión; no parámetros de consulta. La admisión 202 no acredita precios encontrados. Capacidad global del piloto: cinco trabajos por día UTC y una investigación simultánea. Conservar la misma clave y entrada para reintentar sin duplicar. Los resultados son privados durante 24 horas desde la creación; el motor puede devolver cobertura parcial. No hay CORS abierto entre sitios. Requiere invitación privada; la aceptación integral del motor sigue pendiente. Las fechas de ejemplo son históricas y no deben ejecutarse como consulta actual.",
        "security": [
          {
            "BetaInvite": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "32 bytes aleatorios criptográficos en hexadecimal o base64url sin relleno. Conservar privada y reutilizar al reintentar la misma entrada; otra consulta requiere nueva clave.",
            "schema": {
              "type": "string",
              "pattern": "^(?:[a-fA-F0-9]{64}|[A-Za-z0-9_-]{43})$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResearchInput"
              },
              "example": {
                "property": {
                  "name": "Naya",
                  "publicUrls": [
                    "https://www.airbnb.mx/rooms/1638656317370598635"
                  ]
                },
                "market": "la-paz",
                "checkin": "2026-10-01",
                "checkout": "2026-10-04",
                "adults": 6,
                "type": "entire_home",
                "maxCompetitors": 20
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Admitido o reintento del mismo trabajo. Cache-Control: no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchHandle"
                }
              }
            }
          },
          "400": {
            "description": "Entrada, clave, JSON o parámetros de consulta inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Método incorrecto; Allow: POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key ya corresponde a otra entrada (idempotency_conflict).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Solicitud de esa clave ya caducó; usar nueva clave para una nueva investigación.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Cuerpo mayor de 32 KiB, con o sin Content-Length.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Se requiere application/json UTF-8 sin compresión.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Piloto ocupado, capacidad diaria agotada o límite de peticiones. Respetar Retry-After si existe; el cuerpo puede ser de Netlify.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos antes de reintentar, cuando lo proporciona el servicio.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Motor, almacenamiento o despacho no disponible. Un fallo de despacho puede incluir id/accessToken/statusUrl del trabajo fallido.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[a-f0-9]{32}$"
                    },
                    "accessToken": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_-]{43}$",
                      "description": "Capacidad privada de lectura. Enviar solo como Bearer; nunca añadirla a URLs ni reportes públicos."
                    },
                    "statusUrl": {
                      "type": "string",
                      "pattern": "^/api/v1/investigaciones/[a-f0-9]{32}$",
                      "description": "Ruta relativa del mismo origen, sin parámetros ni fragmentos."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Falta código privado de invitación o no es válido."
          },
          "403": {
            "description": "La solicitud se originó desde otro sitio."
          }
        }
      }
    },
    "/api/v1/investigaciones/{id}": {
      "get": {
        "operationId": "getInternalResearch",
        "summary": "Leer estado y resultado privados de una investigación",
        "description": "Usar Authorization: Bearer accessToken; no acepta parámetros de consulta. Sondear aproximadamente cada tres segundos, respetar Retry-After y detenerse en completed/partial/failed/expired. Caduca a las 24 horas desde la creación. Los trabajos existentes vencidos responden expired sin resultado; tras limpieza pueden responder 404. Token ausente o incorrecto también responde 404. No confundir completed con garantía de equivalencia, vigencia o disponibilidad de todas las ofertas.",
        "security": [
          {
            "ResearchCapability": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estado real del trabajo. Cache-Control: no-store; result solo cuando exista y siga vigente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchJob"
                }
              }
            }
          },
          "400": {
            "description": "Parámetros de consulta no admitidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Trabajo o acceso inexistente, incorrecto o eliminado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Método incorrecto; Allow: GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Límite de peticiones de Netlify; respetar Retry-After si existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "No fue posible consultar el almacenamiento.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/data/catalogo.json": {
      "get": {
        "operationId": "readPublicSnapshot",
        "summary": "Leer catálogo completo con captura y procedencia",
        "description": "Sin parámetros ni autenticación. Conservar generatedAt y las fechas originales de cada fuente. No refresca la captación. Los importes ya son MXN, no centavos.",
        "responses": {
          "200": {
            "description": "Captura del catálogo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "schemaVersion",
                    "generatedAt",
                    "properties"
                  ],
                  "properties": {
                    "schemaVersion": {
                      "const": "0.2"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "properties": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "name",
                          "source",
                          "collection",
                          "rights"
                        ],
                        "properties": {
                          "collection": {
                            "type": "object",
                            "properties": {
                              "status": {
                                "enum": [
                                  "ok",
                                  "stale",
                                  "unavailable",
                                  "not_connected"
                                ]
                              },
                              "checkedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "attemptedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "description": "Estado de la lectura pasada; ok no garantiza frescura. checkedAt ausente o mayor a 24h requiere marcar captura pendiente de actualización. Nunca rejuvenece sourceUpdatedAt."
                          },
                          "data": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "kind": {
                                "enum": [
                                  "published_base",
                                  "published_proposal",
                                  "dated_snapshot"
                                ]
                              },
                              "sourceUpdatedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "availability": {
                                "const": "unknown"
                              },
                              "currency": {
                                "const": "MXN"
                              },
                              "unit": {
                                "const": "MXN",
                                "description": "Importes monetarios en pesos, NO centavos. calendar contiene precio por noche y categoría; reference contiene bases y cargos separados."
                              },
                              "taxesIncluded": {
                                "type": [
                                  "boolean",
                                  "null"
                                ]
                              },
                              "breakfastIncluded": {
                                "type": [
                                  "boolean",
                                  "null"
                                ]
                              },
                              "includedGuests": {
                                "type": "integer",
                                "description": "Ocupación cubierta por la tarifa cuando la fuente la declara; ausencia no equivale a maxGuests."
                              },
                              "notice": {
                                "type": "string"
                              },
                              "categories": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "required": [
                                    "id",
                                    "name",
                                    "maxGuests"
                                  ],
                                  "properties": {
                                    "id": {
                                      "type": "string"
                                    },
                                    "name": {
                                      "type": "string"
                                    },
                                    "maxGuests": {
                                      "type": "integer",
                                      "description": "Capacidad física; no acredita ocupación tarifaria."
                                    }
                                  }
                                }
                              },
                              "calendar": {
                                "type": "object",
                                "description": "Fecha civil YYYY-MM-DD -> categoryId -> importe MXN por noche. Llegada incluida y salida excluida. Exigir todas las noches; no inferir datos ausentes. No inventario.",
                                "additionalProperties": {
                                  "type": "object",
                                  "additionalProperties": {
                                    "type": "number",
                                    "minimum": 0
                                  }
                                }
                              },
                              "reference": {
                                "type": "object",
                                "description": "Base de casa completa, no cotización por estancia; impuestos aparte. No calcular total definitivo solo con estas bases.",
                                "properties": {
                                  "weekday": {
                                    "type": "number"
                                  },
                                  "weekend": {
                                    "type": "number"
                                  },
                                  "cleaning": {
                                    "type": "number"
                                  },
                                  "includedGuests": {
                                    "type": "number"
                                  },
                                  "extraGuest": {
                                    "type": "number"
                                  },
                                  "minGuests": {
                                    "type": "number"
                                  },
                                  "maxGuests": {
                                    "type": "number"
                                  },
                                  "minNights": {
                                    "type": "number"
                                  }
                                }
                              },
                              "blocks": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "description": "Bloqueos públicos. No prueba de ocupación, demanda o inventario. No garantizan disponibilidad; última sincronización OTA no informada.",
                                "properties": {
                                  "blockedDates": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "format": "date"
                                    }
                                  },
                                  "checkedAt": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "sourceUpdatedAt": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                }
                              }
                            }
                          },
                          "id": {
                            "type": "string",
                            "description": "Identificador estable de alojamiento"
                          },
                          "source": {
                            "type": "string",
                            "format": "uri"
                          },
                          "rights": {
                            "type": "object",
                            "description": "Alcance de publicación y uso específico de la fuente"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/data/ejemplo-agente.json": {
      "get": {
        "operationId": "readAgentAcceptanceExample",
        "summary": "Leer entrada y salida esperada de una prueba reproducible",
        "responses": {
          "200": {
            "description": "Caso ligado al SHA256 de su captura de origen; no es una cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "catalogSha256",
                    "catalogGeneratedAt",
                    "input",
                    "expected"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/data/cobertura.json": {
      "get": {
        "operationId": "readCoverageIndex",
        "summary": "Descubrir alojamientos, categorías, fechas cubiertas y condiciones sin procesar todo el calendario",
        "responses": {
          "200": {
            "description": "Índice de la misma captura que catalogo.json. No precios en vivo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "schemaVersion",
                    "generatedAt",
                    "mode",
                    "properties"
                  ],
                  "properties": {
                    "schemaVersion": {
                      "const": "0.2"
                    },
                    "mode": {
                      "const": "published_snapshot"
                    },
                    "generatedAt": {
                      "type": "string"
                    },
                    "properties": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "coverage",
                          "conditions",
                          "categories",
                          "comparison"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/data/consultas/altamar-2026-11-05-6adultos.json": {
      "get": {
        "summary": "Lectura puntual de agentes en navegador; no nueva búsqueda ni cotización garantizada",
        "responses": {
          "200": {
            "description": "Observaciones con estancia, total, fuente, hora de lectura, cancelación y diferencias de producto. Esquema browser-observations-1 documentado en /agentes/observaciones.md."
          }
        }
      }
    },
    "/data/consultas/altamar-2026-11-05-producto.json": {
      "get": {
        "summary": "Observaciones originales de la selección evaluada; conserva hora de captura",
        "responses": {
          "200": {
            "description": "Captura publicada de solo lectura. No ejecuta agentes ni actualiza precios. Protocolo /agentes/evaluacion.md."
          }
        }
      }
    },
    "/data/evaluaciones/altamar-producto.json": {
      "get": {
        "summary": "Evaluación de producto, portadas atribuidas, siete ejes y acciones pendientes",
        "responses": {
          "200": {
            "description": "Captura publicada de solo lectura. No ejecuta agentes ni actualiza precios. Protocolo /agentes/evaluacion.md."
          }
        }
      }
    },
    "/data/consultas/altamar-2026-11-05-full.json": {
      "get": {
        "summary": "Estudio Full: 20 propiedades únicas con ofertas fechadas",
        "responses": {
          "200": {
            "description": "Captura puntual, no inventario vivo. No ejecuta una búsqueda. Selección personal y reglas documentadas en /agentes/evaluacion.md."
          }
        }
      }
    },
    "/data/evaluaciones/altamar-full.json": {
      "get": {
        "summary": "Full: evidencia de producto y fotos de los 20 candidatos",
        "responses": {
          "200": {
            "description": "Captura puntual, no inventario vivo. No ejecuta una búsqueda. Selección personal y reglas documentadas en /agentes/evaluacion.md."
          }
        }
      }
    },
    "/api/v1/encargos": {
      "post": {
        "operationId": "prepareExternalResearch",
        "summary": "Preparar un encargo para un agente externo; no lo ejecuta",
        "description": "Opción para integradores que aportan investigación propia; no es necesaria en el flujo del usuario. Máximo 512 KiB. Sin parámetros de consulta ni autenticación. Los campos extra se eliminan, también en objetos anidados. Usa clientes HTTP de servidor: no se habilita CORS entre sitios. No entrega un jobId ni una cola en marcha. Configuración de rateLimit: 30 solicitudes/60 segundos por IP+dominio para la función compartida; no es garantía de límite de gasto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado sin persistencia, Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Assignment"
                }
              }
            }
          },
          "400": {
            "description": "JSON o contrato inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Método no permitido; Allow: POST",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Cuerpo mayor de 512 KiB, con o sin Content-Length",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Solo application/json UTF-8 sin compresión",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Límite de Netlify alcanzado; no reintentar agresivamente. El cuerpo puede ser generado por la plataforma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/analisis": {
      "post": {
        "operationId": "analyseExternalEvidence",
        "summary": "Validar y evaluar evidencia aportada; no navega ni la guarda",
        "description": "Opción para integradores que aportan investigación propia; no es necesaria en el flujo del usuario. Máximo 512 KiB. Sin parámetros de consulta ni autenticación. Los campos extra se eliminan, también en objetos anidados. Usa clientes HTTP de servidor: no se habilita CORS entre sitios. No entrega un jobId ni una cola en marcha. Configuración de rateLimit: 30 solicitudes/60 segundos por IP+dominio para la función compartida; no es garantía de límite de gasto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalysisInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado sin persistencia, Cache-Control: no-store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Analysis"
                }
              }
            }
          },
          "400": {
            "description": "JSON o contrato inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Método no permitido; Allow: POST",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Cuerpo mayor de 512 KiB, con o sin Content-Length",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Solo application/json UTF-8 sin compresión",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Límite de Netlify alcanzado; no reintentar agresivamente. El cuerpo puede ser generado por la plataforma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/capacidades": {
      "get": {
        "operationId": "readOperationalCapabilities",
        "summary": "Consultar capacidades y límites explícitos de la versión pública",
        "responses": {
          "200": {
            "description": "Capacidades operativas, sin credenciales",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          },
          "400": {
            "description": "No acepta query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Solo GET",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Límite de Netlify",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/referencias": {
      "get": {
        "summary": "Consultar referencias directas almacenadas, sin ejecutar navegación",
        "parameters": [
          {
            "name": "alojamiento",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Known property id"
          },
          {
            "name": "llegada",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD; requires salida"
          },
          {
            "name": "salida",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD; stay 1 to 30 nights"
          },
          {
            "name": "adultos",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            },
            "description": "1 to 12, no children"
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo o referencias por estancia; schemaVersion 0.2 conserva compatibilidad. retrieval explica origen Blob/semilla, error de lectura y upstreamFetch:false.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schemaVersion": {
                      "const": "0.2"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
                    },
                    "properties": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "query": {
                      "type": "object"
                    },
                    "retrieval": {
                      "type": "object",
                      "properties": {
                        "mode": {
                          "const": "stored_snapshot"
                        },
                        "source": {
                          "enum": [
                            "blob",
                            "seed",
                            "memory"
                          ]
                        },
                        "readAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "upstreamFetch": {
                          "const": false
                        },
                        "cached": {
                          "type": "boolean"
                        }
                      },
                      "required": []
                    }
                  },
                  "required": [
                    "schemaVersion",
                    "generatedAt",
                    "properties"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters"
          },
          "404": {
            "description": "Unknown property"
          },
          "405": {
            "description": "Only GET allowed"
          },
          "503": {
            "description": "Service temporarily unavailable"
          },
          "429": {
            "description": "Límite de Netlify: 30 solicitudes/60 segundos por IP+dominio",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "readStoredOperatorReferences",
        "description": "Lee el último catálogo almacenado (o semilla histórica indicada por retrieval). El GET no llama fuentes upstream. El recolector programado consulta cuatro fuentes directas autorizadas; no investiga competidores OTA. Con llegada/salida devuelve result/provenance por propiedad, sin garantizar inventario o precio final. Conservar retrieval, checkedAt y sourceUpdatedAt."
      }
    }
  },
  "security": [],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "PublicUrl": {
        "type": "string",
        "minLength": 1,
        "maxLength": 2048,
        "format": "uri",
        "pattern": "^https://",
        "description": "URL pública HTTPS de una propiedad. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El ejecutor de investigaciones puede consultar estas URLs; encargos y analisis no las visitan por sí mismos."
      },
      "Stay": {
        "type": "object",
        "properties": {
          "checkin": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "checkout": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "adults": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30
          },
          "children": {
            "const": 0
          },
          "currency": {
            "const": "MXN"
          },
          "unit": {
            "const": "entire_home"
          }
        },
        "required": [
          "checkin",
          "checkout",
          "adults",
          "children",
          "currency",
          "unit"
        ],
        "description": "Llegada inclusiva, salida exclusiva, entre 1 y 30 noches. Piloto de casas completas, sin menores, MXN."
      },
      "Observation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "propertyId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "channel": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "format": "uri",
            "pattern": "^https://",
            "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
          },
          "observedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          },
          "method": {
            "const": "browser_visible"
          },
          "stay": {
            "$ref": "#/components/schemas/Stay"
          },
          "status": {
            "enum": [
              "quoted",
              "unavailable",
              "blocked",
              "not_checked"
            ]
          },
          "total": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100000000,
            "description": "Total de estancia en MXN. Positivo si quoted; null para todos los demás estados."
          },
          "taxesIncluded": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "feesIncluded": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "bedrooms": {
            "type": "integer",
            "minimum": 0,
            "maximum": 50
          },
          "privatePool": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "heatedPool": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "comparableTotalComplete": {
            "type": "boolean"
          },
          "location": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1500
          },
          "cancellation": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1500
          },
          "policyUrl": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "format": "uri",
            "pattern": "^https://",
            "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
          },
          "deposit": {
            "type": "object",
            "properties": {
              "amount": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 100000000
              },
              "refundable": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "note": {
                "type": "string",
                "minLength": 1,
                "maxLength": 1500
              }
            },
            "required": []
          },
          "breakdown": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "amount": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100000000
                }
              },
              "required": [
                "name",
                "amount"
              ]
            },
            "minItems": 0,
            "maxItems": 30
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1500
            },
            "minItems": 0,
            "maxItems": 20
          },
          "timePrecision": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          },
          "pricePrecision": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          }
        },
        "required": [
          "id",
          "propertyId",
          "name",
          "channel",
          "url",
          "observedAt",
          "method",
          "stay",
          "status",
          "total",
          "taxesIncluded",
          "feesIncluded",
          "bedrooms",
          "privatePool",
          "heatedPool"
        ]
      },
      "EvidenceAxis": {
        "type": "object",
        "properties": {
          "relation": {
            "enum": [
              "match",
              "stronger",
              "weaker",
              "different",
              "unknown"
            ]
          },
          "evidence": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2500
          },
          "source": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "format": "uri",
            "pattern": "^https://",
            "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
          }
        },
        "required": [
          "relation",
          "evidence",
          "source"
        ]
      },
      "Profile": {
        "type": "object",
        "properties": {
          "propertyId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "source": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "format": "uri",
            "pattern": "^https://",
            "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
          },
          "reviewedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          },
          "unit": {
            "enum": [
              "entire_home",
              "other"
            ]
          },
          "bedrooms": {
            "type": "integer",
            "minimum": 0,
            "maximum": 50
          },
          "capacity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "privatePool": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "heatedPool": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "visualReview": {
            "type": "boolean"
          },
          "axes": {
            "type": "object",
            "properties": {
              "space": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "thermal": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "location": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "views": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "finish": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "amenities": {
                "$ref": "#/components/schemas/EvidenceAxis"
              },
              "rest": {
                "$ref": "#/components/schemas/EvidenceAxis"
              }
            },
            "required": [
              "space",
              "thermal",
              "location",
              "views",
              "finish",
              "amenities",
              "rest"
            ]
          },
          "conclusion": {
            "type": "string",
            "minLength": 1,
            "maxLength": 3000
          },
          "zone": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300
          },
          "subtitle": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "visual": {
            "type": "string",
            "minLength": 1,
            "maxLength": 3000
          },
          "conditions": {
            "type": "string",
            "minLength": 1,
            "maxLength": 3000
          },
          "marketMismatch": {
            "type": "boolean"
          },
          "photo": {
            "type": "object",
            "properties": {
              "src": {
                "type": "string",
                "pattern": "^/assets/comparables/[a-z0-9-]+\\.(jpg|avif|webp|png)$"
              },
              "alt": {
                "type": "string",
                "minLength": 1,
                "maxLength": 400
              },
              "source": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2048,
                "format": "uri",
                "pattern": "^https://",
                "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
              }
            },
            "required": [
              "src",
              "alt",
              "source"
            ]
          },
          "watch": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1500
            },
            "minItems": 0,
            "maxItems": 20
          },
          "review": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 300
              },
              "detail": {
                "type": "string",
                "minLength": 1,
                "maxLength": 1500
              },
              "verifiedCount": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1000000
              }
            },
            "required": []
          }
        },
        "required": [
          "propertyId",
          "title",
          "source",
          "reviewedAt",
          "unit",
          "bedrooms",
          "capacity",
          "privatePool",
          "heatedPool",
          "visualReview",
          "axes",
          "conclusion"
        ],
        "description": "Evaluación aportada, no verificación independiente. photo es opcional: solo recursos previamente aprobados en Radar, nunca URL remota arbitraria. Omitir photo no impide analizar."
      },
      "Study": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "browser-observations-1"
          },
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "subjectId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "stay": {
            "$ref": "#/components/schemas/Stay"
          },
          "observations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Observation"
            },
            "minItems": 1,
            "maxItems": 50
          },
          "propertyProfile": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "url": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2048,
                "format": "uri",
                "pattern": "^https://",
                "description": "URL pública HTTPS. Se rechazan credenciales, IP/locales, tokens de sesión, paneles privados y feeds iCal. El servidor no visita esta URL."
              },
              "features": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 1500
                },
                "minItems": 0,
                "maxItems": 30
              }
            },
            "required": [
              "name",
              "url"
            ]
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1500
            },
            "minItems": 0,
            "maxItems": 30
          },
          "observationWindow": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date-time",
                "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
              },
              "to": {
                "type": "string",
                "format": "date-time",
                "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
              }
            },
            "required": [
              "from",
              "to"
            ]
          }
        },
        "required": [
          "schemaVersion",
          "id",
          "subjectId",
          "stay",
          "observations"
        ]
      },
      "Assessment": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "radar-assessment-1"
          },
          "studyId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "profiles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Profile"
            },
            "minItems": 0,
            "maxItems": 21
          },
          "selectionScope": {
            "type": "string",
            "minLength": 1,
            "maxLength": 3000
          },
          "reviewedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          }
        },
        "required": [
          "schemaVersion",
          "studyId",
          "profiles"
        ]
      },
      "AssignmentInput": {
        "type": "object",
        "properties": {
          "property": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 160
              },
              "publicUrls": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublicUrl"
                },
                "minItems": 1,
                "maxItems": 5
              }
            },
            "required": [
              "name",
              "publicUrls"
            ]
          },
          "market": {
            "enum": [
              "la-paz",
              "los-cabos",
              "chihuahua"
            ],
            "description": "También acepta labels La Paz, Los Cabos y Chihuahua; responde IDs normalizados."
          },
          "checkin": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "checkout": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "adults": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30
          },
          "type": {
            "const": "entire_home"
          },
          "competitorUrls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicUrl"
            },
            "minItems": 0,
            "maxItems": 20
          },
          "maxCompetitors": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "default": 20
          }
        },
        "required": [
          "property",
          "market",
          "checkin",
          "checkout",
          "adults",
          "type"
        ]
      },
      "Assignment": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "radar-assignment-1"
          },
          "mode": {
            "const": "external_agent_handoff"
          },
          "status": {
            "const": "ready_for_external_agent"
          },
          "automaticExecution": {
            "const": false
          },
          "persistence": {
            "const": false
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          },
          "input": {
            "$ref": "#/components/schemas/AssignmentInput"
          },
          "prompt": {
            "type": "string"
          },
          "protocolUrl": {
            "type": "string",
            "format": "uri"
          },
          "analysisEndpoint": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "schemaVersion",
          "mode",
          "status",
          "automaticExecution",
          "persistence",
          "createdAt",
          "input",
          "prompt",
          "protocolUrl",
          "analysisEndpoint"
        ]
      },
      "AnalysisInput": {
        "type": "object",
        "properties": {
          "study": {
            "$ref": "#/components/schemas/Study"
          },
          "assessment": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Assessment"
              },
              {
                "type": "null"
              }
            ]
          },
          "selectedPropertyIds": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "minItems": 0,
            "maxItems": 20
          }
        },
        "required": [
          "study"
        ]
      },
      "Benchmark": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "min": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000000
          },
          "max": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000000
          },
          "median": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000000
          },
          "ownTotal": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000000
          },
          "deltaPercent": {
            "type": "number"
          },
          "kind": {
            "enum": [
              "personal",
              "radar",
              "equivalent"
            ]
          },
          "scope": {
            "enum": [
              "primary",
              "context"
            ]
          },
          "confidence": {
            "enum": [
              "limited",
              "descriptive"
            ]
          },
          "propertyIds": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "minItems": 0,
            "maxItems": 20
          },
          "quoteIds": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "minItems": 0,
            "maxItems": 20
          }
        },
        "required": []
      },
      "Analysis": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "radar-analysis-1"
          },
          "provenance": {
            "const": "external_agent_submission"
          },
          "independentlyVerified": {
            "const": false
          },
          "persisted": {
            "const": false
          },
          "analysedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          },
          "summary": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "stay": {
                "$ref": "#/components/schemas/Stay"
              },
              "nights": {
                "type": "integer"
              },
              "headline": {
                "type": "string"
              },
              "decision": {
                "type": "string"
              },
              "reason": {
                "type": "string"
              },
              "pricingDecision": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string"
                  },
                  "canRecommendPriceChange": {
                    "const": false
                  }
                },
                "required": []
              },
              "counts": {
                "type": "object"
              },
              "candidates": {
                "type": "integer"
              },
              "selected": {
                "type": "integer"
              }
            },
            "required": []
          },
          "classification": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "propertyId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "category": {
                  "enum": [
                    "equivalent",
                    "similar",
                    "superior",
                    "inferior",
                    "pending",
                    "excluded"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "knownAxes": {
                  "type": "integer"
                },
                "matchingAxes": {
                  "type": "integer"
                },
                "priceEligible": {
                  "type": "boolean"
                },
                "primaryPriceEligible": {
                  "type": "boolean"
                }
              },
              "required": []
            },
            "minItems": 0,
            "maxItems": 20
          },
          "primaryBenchmark": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Benchmark"
              },
              {
                "type": "null"
              }
            ]
          },
          "contextBenchmark": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Benchmark"
              },
              {
                "type": "null"
              }
            ]
          },
          "normalized": {
            "$ref": "#/components/schemas/AnalysisInput"
          },
          "notice": {
            "type": "string"
          }
        },
        "required": [
          "schemaVersion",
          "provenance",
          "independentlyVerified",
          "persisted",
          "summary",
          "classification",
          "primaryBenchmark",
          "contextBenchmark",
          "normalized"
        ]
      },
      "Capabilities": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "radar-capabilities-1"
          },
          "service": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "description": "Modo operativo declarado; distinguir investigación interna de las rutas opcionales de evidencia externa."
          },
          "autoBrowser": {
            "type": "boolean",
            "description": "Capacidad declarada del ejecutor; no acredita éxito con cada plataforma."
          },
          "accounts": {
            "const": false
          },
          "persistence": {
            "type": "boolean",
            "description": "Persistencia de investigaciones privadas durante su vigencia; no cuentas ni perfiles permanentes."
          },
          "scheduledMonitoring": {
            "const": false
          },
          "mcp": {
            "const": false
          },
          "icalPricing": {
            "const": false
          },
          "independentlyVerifiesSources": {
            "const": false
          },
          "automaticPriceChanges": {
            "const": false
          },
          "markets": {
            "type": "array",
            "items": {
              "enum": [
                "la-paz",
                "los-cabos",
                "chihuahua"
              ]
            },
            "minItems": 0,
            "maxItems": 3
          },
          "types": {
            "type": "array",
            "items": {
              "const": "entire_home"
            },
            "minItems": 0,
            "maxItems": 1
          },
          "currency": {
            "const": "MXN"
          },
          "children": {
            "const": 0
          },
          "limits": {
            "type": "object"
          },
          "endpoints": {
            "type": "object"
          },
          "workflow": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 0
          },
          "notice": {
            "type": "string"
          }
        },
        "required": [
          "schemaVersion",
          "mode",
          "autoBrowser",
          "accounts",
          "persistence",
          "scheduledMonitoring",
          "mcp"
        ]
      },
      "ResearchInput": {
        "type": "object",
        "properties": {
          "property": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 160
              },
              "publicUrls": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublicUrl"
                },
                "minItems": 1,
                "maxItems": 5
              }
            },
            "required": [
              "name",
              "publicUrls"
            ]
          },
          "market": {
            "enum": [
              "la-paz",
              "los-cabos",
              "chihuahua"
            ],
            "description": "También acepta labels La Paz, Los Cabos y Chihuahua; responde IDs normalizados."
          },
          "checkin": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "checkout": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "adults": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30
          },
          "type": {
            "const": "entire_home"
          },
          "maxCompetitors": {
            "const": 20,
            "default": 20,
            "description": "Modo Full del piloto: máximo de candidatos; no garantiza encontrar 20 equivalentes o cotizaciones."
          }
        },
        "required": [
          "property",
          "market",
          "checkin",
          "checkout",
          "adults",
          "type"
        ],
        "description": "Datos de la propiedad propia. Uno a cinco enlaces públicos separados. Llegada de hoy o futura, estancia de 1–30 noches, sin menores y moneda MXN. La investigación busca competidores; no se solicitan enlaces de ellos."
      },
      "ResearchHandle": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "accessToken": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "Capacidad privada de lectura. Enviar solo como Bearer; nunca añadirla a URLs ni reportes públicos."
          },
          "statusUrl": {
            "type": "string",
            "pattern": "^/api/v1/investigaciones/[a-f0-9]{32}$",
            "description": "Ruta relativa del mismo origen, sin parámetros ni fragmentos."
          }
        },
        "required": [
          "id",
          "accessToken",
          "statusUrl"
        ]
      },
      "ResearchCandidate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "$ref": "#/components/schemas/PublicUrl"
          },
          "reason": {
            "type": "string"
          },
          "category": {
            "enum": [
              "equivalent",
              "similar",
              "superior",
              "inferior",
              "pending",
              "excluded"
            ]
          },
          "status": {
            "enum": [
              "blocked",
              "quoted",
              "not_quoted"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Total observado en MXN para la estancia, cuando se pudo obtener; null nunca significa cero."
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "name",
          "url",
          "reason",
          "category",
          "status",
          "price"
        ]
      },
      "ResearchSource": {
        "type": "object",
        "properties": {
          "url": {
            "$ref": "#/components/schemas/PublicUrl"
          },
          "title": {
            "type": "string"
          },
          "observedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original de la lectura pública; no sustituir por consulta o importación."
          },
          "status": {
            "enum": [
              "read",
              "blocked"
            ]
          }
        },
        "required": [
          "url",
          "title",
          "observedAt",
          "status"
        ]
      },
      "ResearchReport": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "headline": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "property": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "url": {
                "$ref": "#/components/schemas/PublicUrl"
              },
              "features": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "name",
              "url",
              "features"
            ]
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchCandidate"
            },
            "maxItems": 20
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResearchSource"
            }
          },
          "recommendations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "title",
          "headline",
          "summary",
          "property",
          "candidates",
          "sources",
          "recommendations"
        ]
      },
      "InternalAnalysis": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "const": "radar-analysis-1"
          },
          "provenance": {
            "const": "radar_internal_research"
          },
          "independentlyVerified": {
            "const": false
          },
          "persisted": {
            "const": true,
            "description": "Dentro del trabajo privado hasta expiresAt, no en un perfil permanente ni catálogo público."
          },
          "analysedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Momento original con zona horaria; no sustituirlo por la hora de importación."
          },
          "summary": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "stay": {
                "$ref": "#/components/schemas/Stay"
              },
              "nights": {
                "type": "integer"
              },
              "headline": {
                "type": "string"
              },
              "decision": {
                "type": "string"
              },
              "reason": {
                "type": "string"
              },
              "pricingDecision": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string"
                  },
                  "canRecommendPriceChange": {
                    "const": false
                  }
                },
                "required": []
              },
              "counts": {
                "type": "object"
              },
              "candidates": {
                "type": "integer"
              },
              "selected": {
                "type": "integer"
              }
            },
            "required": []
          },
          "classification": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "propertyId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "category": {
                  "enum": [
                    "equivalent",
                    "similar",
                    "superior",
                    "inferior",
                    "pending",
                    "excluded"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "knownAxes": {
                  "type": "integer"
                },
                "matchingAxes": {
                  "type": "integer"
                },
                "priceEligible": {
                  "type": "boolean"
                },
                "primaryPriceEligible": {
                  "type": "boolean"
                }
              },
              "required": []
            },
            "minItems": 0,
            "maxItems": 20
          },
          "primaryBenchmark": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Benchmark"
              },
              {
                "type": "null"
              }
            ]
          },
          "contextBenchmark": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Benchmark"
              },
              {
                "type": "null"
              }
            ]
          },
          "normalized": {
            "$ref": "#/components/schemas/AnalysisInput"
          },
          "notice": {
            "type": "string"
          }
        },
        "required": [
          "schemaVersion",
          "provenance",
          "independentlyVerified",
          "persisted",
          "summary",
          "classification",
          "primaryBenchmark",
          "contextBenchmark",
          "normalized"
        ],
        "description": "Análisis producido con lecturas del ejecutor interno. Mantiene independentlyVerified:false: no es auditoría independiente ni garantía de inventario."
      },
      "ResearchCoverage": {
        "type": "object",
        "properties": {
          "discovered": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "visited": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "priced": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "maxCompetitors": {
            "const": 20
          },
          "ownSourceRead": {
            "type": "boolean"
          }
        },
        "required": [
          "discovered",
          "visited",
          "priced",
          "maxCompetitors"
        ]
      },
      "ResearchResult": {
        "type": "object",
        "properties": {
          "analysis": {
            "$ref": "#/components/schemas/InternalAnalysis"
          },
          "report": {
            "$ref": "#/components/schemas/ResearchReport"
          },
          "coverage": {
            "$ref": "#/components/schemas/ResearchCoverage"
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          },
          "provenance": {
            "const": "radar_internal_research"
          },
          "evidence": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "$ref": "#/components/schemas/PublicUrl"
                },
                "observedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "sha256": {
                  "type": "string",
                  "pattern": "^[a-f0-9]{64}$"
                },
                "profile": {
                  "type": "object"
                }
              }
            }
          },
          "usage": {
            "type": "object"
          }
        },
        "required": [
          "report",
          "coverage",
          "limitations"
        ],
        "description": "analysis es opcional. Si falta, mostrar report y limitaciones de esta propiedad sin precios ficticios ni el ejemplo histórico de Altamar."
      },
      "ResearchJob": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "completed",
              "partial",
              "failed",
              "expired"
            ]
          },
          "phase": {
            "type": "string",
            "description": "Fase real del ejecutor; incluye profiling, discovering, quoting, evaluating y estados operativos. No es un porcentaje."
          },
          "message": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Caducidad a las 24 horas desde la creación."
          },
          "result": {
            "$ref": "#/components/schemas/ResearchResult"
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "id",
          "status",
          "phase",
          "message",
          "createdAt",
          "updatedAt",
          "expiresAt"
        ]
      }
    },
    "securitySchemes": {
      "ResearchCapability": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "32 bytes base64url",
        "description": "accessToken devuelto al crear el trabajo. Autoriza solo su lectura durante la vigencia. Nunca enviarlo por query string."
      },
      "BetaInvite": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Radar-Invite",
        "description": "Código privado de invitación a la beta. No se publica ni se incluye en URLs. No sustituye al Bearer privado de cada resultado."
      }
    }
  }
}
