openapi: 3.0.3
info:
  title: Padrón UY · API (Evaluaprop / mapi)
  version: "1.1.0"
  description: >
    API de padrones de Uruguay: dirección → padrón, tasación AVM, comparables de
    mercado (InfoCasas + VeoCasas + REMAX, venta/alquiler) y datos de referencia
    (refdata). Todos los endpoints requieren `Authorization: Bearer <TOKEN>`
    excepto `POST /api/auth/login`.
servers:
  - url: https://mapi.evaluaprop.com
security:
  - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    Padron:
      name: padron
      in: query
      schema: { type: integer }
      description: Número de padrón (alias de `q`).
  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
    MarketComparable:
      type: object
      properties:
        id: { type: string }
        portal: { type: string, enum: [infocasas, veocasas, remax] }
        operation: { type: string, enum: [sale, rent] }
        title: { type: string }
        price_usd: { type: number }
        price_m2_usd: { type: number }
        area_built: { type: number }
        area_land: { type: number, nullable: true }
        property_type: { type: string, enum: [casa, apartamento, otro] }
        bedrooms: { type: integer, nullable: true }
        bathrooms: { type: integer, nullable: true }
        neighborhood: { type: string }
        address: { type: string, nullable: true }
        image_url: { type: string, nullable: true }
        listing_url: { type: string }
        currency: { type: string }
        lat: { type: number, nullable: true }
        lng: { type: number, nullable: true }
        distancia_m: { type: number, nullable: true }
        similitud: { type: number, nullable: true }
    CapRate:
      type: object
      properties:
        gross_yield_pct: { type: number }
        median_sale_m2_usd: { type: number }
        median_rent_m2_usd: { type: number }
        monthly_rent_listings: { type: integer }
        sale_listings: { type: integer }
    MarketValuationSummary:
      type: object
      properties:
        total_listings: { type: integer }
        median_price_m2_usd: { type: number }
        avg_price_m2_usd: { type: number }
        suggested_valuation_usd: { type: number }
        valuation_range_usd:
          type: object
          properties:
            min: { type: number }
            max: { type: number }
        confidence: { type: string, enum: [alta, media, estimada] }
        by_portal:
          type: object
          properties:
            infocasas_count: { type: integer }
            veocasas_count: { type: integer }
            remax_count: { type: integer }
            infocasas_median_m2: { type: number }
            veocasas_median_m2: { type: number }
            remax_median_m2: { type: number }
        cap_rate:
          $ref: '#/components/schemas/CapRate'
    MarketComparablesResponse:
      type: object
      properties:
        padron: { type: integer }
        depto: { type: string }
        zone: { type: string }
        operation: { type: string, enum: [sale, rent] }
        property_type: { type: string, enum: [casa, apartamento, todas] }
        summary: { $ref: '#/components/schemas/MarketValuationSummary' }
        results:
          type: array
          items: { $ref: '#/components/schemas/MarketComparable' }
    RefdataParcel:
      type: object
      properties:
        parcel_id: { type: string }
        lot_area: { type: number }
        built_area: { type: number }
        department_name: { type: string }
      additionalProperties: true
    RefdataGeoArea:
      type: object
      properties:
        type: { type: string, description: "barrios | ccz | seccional" }
        name: { type: string }
    TerrenoResidual:
      type: object
      description: Valor residual del terreno "para desarrollar" (método residual).
      properties:
        superficie_vendible_m2: { type: number }
        valor_desarrollo_bruto_usd: { type: number }
        costo_construccion_usd: { type: number }
        margen_desarrollo_usd: { type: number }
        valor_residual_terreno_usd: { type: number }
        valor_residual_m2_terreno_usd: { type: number }
        uplift_vs_mercado_pct: { type: number }
        desarrollable: { type: boolean }
    InvestmentScore:
      type: object
      properties:
        score: { type: integer, minimum: 0, maximum: 100 }
        grade: { type: string, enum: [A, B, C, D, E] }
        label: { type: string }
        components:
          type: object
          description: Subscores de rentabilidad, liquidez y potencial (0–100 con peso).
        notes:
          type: array
          items: { type: string }
    ReportData:
      type: object
      description: Datos agregados del informe (format=json).
      properties:
        padron: { type: integer }
        depto: { type: string }
        area_predio: { type: number }
        area_edificada: { type: number }
        valuation:
          type: object
          description: ValuationResult; incluye terreno_residual.
          properties:
            terreno_residual: { $ref: '#/components/schemas/TerrenoResidual' }
        market: { $ref: '#/components/schemas/MarketComparablesResponse' }
        investment_score: { $ref: '#/components/schemas/InvestmentScore' }
        price_trend:
          type: object
          description: Serie propia de $/m² de la zona y su tendencia.
          properties:
            points:
              type: array
              items:
                type: object
                properties:
                  t: { type: string, description: "Mes YYYY-MM" }
                  m2: { type: number }
                  n: { type: integer }
            trend_pct: { type: number }
            months_span: { type: integer }
            latest_m2: { type: number }
paths:
  /api/auth/login:
    post:
      summary: Login (público). Devuelve el Bearer token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user: { type: string }
                pass: { type: string }
      responses:
        "200": { description: OK }
        "401": { description: Credenciales inválidas }
        "429": { description: Bloqueado por rate limit }
  /api/market-comparables:
    get:
      summary: Comparables de mercado (InfoCasas + VeoCasas + REMAX), venta o alquiler.
      parameters:
        - $ref: '#/components/parameters/Padron'
        - { name: depto, in: query, schema: { type: string } }
        - { name: loc, in: query, schema: { type: string } }
        - { name: barrio, in: query, schema: { type: string } }
        - { name: lat, in: query, schema: { type: number } }
        - { name: lng, in: query, schema: { type: number } }
        - { name: tipo, in: query, schema: { type: string, enum: [casa, apartamento, todas] } }
        - name: portal
          in: query
          description: "`duenodirecto` es opt-in (no entra en `todos`)."
          schema: { type: string, enum: [infocasas, veocasas, remax, duenodirecto, todos], default: todos }
        - name: modo
          in: query
          description: Alias de `operation` (venta=sale, alquiler=rent).
          schema: { type: string, enum: [venta, alquiler] }
        - name: operation
          in: query
          schema: { type: string, enum: [sale, rent] }
        - name: cap_rate
          in: query
          description: Si es true, calcula la rentabilidad bruta de la zona.
          schema: { type: boolean }
        - { name: area, in: query, schema: { type: number } }
        - { name: area_predio, in: query, schema: { type: number } }
        - { name: radius, in: query, schema: { type: number, default: 2000 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 50, default: 20 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MarketComparablesResponse' }
        "400": { description: Faltan parámetros, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  /api/refdata/search:
    get:
      summary: Autocompletado dirección → padrón (informes-refdata).
      parameters:
        - { name: q, in: query, required: true, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, default: 5, maximum: 20 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    parcel_id: { type: string }
                    display_text: { type: string }
  /api/refdata/parcel:
    get:
      summary: Datos de la parcela (superficies, departamento) por padrón.
      parameters: [ { $ref: '#/components/parameters/Padron' } ]
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/RefdataParcel' } } } }
        "404": { description: No encontrado }
  /api/refdata/addresses:
    get:
      summary: Direcciones registradas del padrón.
      parameters: [ { $ref: '#/components/parameters/Padron' } ]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { type: string } } } } }
  /api/refdata/geo:
    get:
      summary: Geometría (Polygon) del padrón.
      parameters: [ { $ref: '#/components/parameters/Padron' } ]
      responses:
        "200": { description: GeoJSON }
        "404": { description: No encontrado }
  /api/refdata/areas:
    get:
      summary: Áreas administrativas del padrón (barrio, CCZ, seccional).
      parameters: [ { $ref: '#/components/parameters/Padron' } ]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/RefdataGeoArea' }
  /api/refdata/lookup:
    get:
      summary: Parcela + direcciones + áreas + geometría combinadas.
      parameters: [ { $ref: '#/components/parameters/Padron' } ]
      responses:
        "200": { description: OK }
  /api/valuation:
    get:
      summary: Tasación AVM + catastro + potencial constructivo + valor residual del terreno.
      parameters: [ { $ref: '#/components/parameters/Padron' }, { name: depto, in: query, schema: { type: string } } ]
      responses:
        "200":
          description: Incluye `terreno_residual` (valor residual del terreno) cuando hay predio y edificabilidad.
  /api/report:
    get:
      summary: Informe completo (AVM + mercado + cap rate + score de inversión + intel). PDF o JSON.
      description: >
        Agrega tasación, comparables (con cap_rate), potencial, valor residual del
        terreno, score de inversión y location intel. Pensado para poblar los
        informes de Evaluaprop.
      parameters:
        - $ref: '#/components/parameters/Padron'
        - { name: depto, in: query, description: "Opcional; si falta prioriza Montevideo (V).", schema: { type: string } }
        - name: format
          in: query
          schema: { type: string, enum: [pdf, json], default: pdf }
      responses:
        "200":
          description: PDF (application/pdf) o JSON estructurado (ReportData).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ReportData' }
            application/pdf:
              schema: { type: string, format: binary }
        "404": { description: Padrón no encontrado }
  /api/padron:
    get:
      summary: Padrón → todos los datos + intel + servicios cercanos.
      parameters: [ { name: q, in: query, required: true, schema: { type: integer } } ]
      responses:
        "200": { description: OK }
  /api/direccion:
    get:
      summary: Dirección → padrón (+ intel territorial).
      parameters: [ { name: q, in: query, required: true, schema: { type: string } } ]
      responses:
        "200": { description: OK }
  /api/health:
    get:
      summary: Estado del servicio (conectividad de Redis/KV y DB).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  db: { type: string }
                  kv:
                    type: object
                    properties:
                      configured: { type: boolean }
                      mode: { type: string, enum: [connected, memory] }
                      ping: { type: boolean }
