openapi: 3.0.3
info:
  title: Limay Climatización — API de cliente
  version: 1.0.0
  description: |
    Lectura del parque y la documentación de **una** empresa.
    El token fija el cliente: no hay forma de pedir datos de otra.
    Distinta de la API interna de Limay (`/api/v1`) y del MCP de contacto de www.
    Solo GET, más `POST /mcp` (mismo token, JSON-RPC).
    Sin webhooks. 60 pedidos/minuto por token. Paginación: `pagina` (50 filas).
    Fechas de negocio `YYYY-MM-DD`. Zona America/Argentina/Buenos_Aires.
    Token: Portal → Accesos, 90 días, revocable. Header Authorization, no en la URL.
    Docs humanas: https://limayai.limayclimatizacion.com.ar/api/cliente/v1/
    MCP: https://limayai.limayclimatizacion.com.ar/api/cliente/v1/mcp
    llms.txt: https://www.limayclimatizacion.com.ar/llms.txt
servers:
  - url: https://limayai.limayclimatizacion.com.ar/api/cliente/v1
    description: Producción (solo con servicio formalizado y token vigente)
security:
  - bearerAuth: []
tags:
  - name: sesion
  - name: parque
  - name: visitas
  - name: comercial
paths:
  /me:
    get:
      tags: [sesion]
      summary: Empresa y alcances de este token
      responses:
        "200":
          description: Token válido
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Me"
        "401":
          $ref: "#/components/responses/NoAuth"
        "429":
          $ref: "#/components/responses/Demasiado"
  /sucursales:
    get:
      tags: [parque]
      summary: Sedes de esta empresa
      responses:
        "200":
          description: Listado
          content:
            application/json:
              schema:
                type: object
                required: [sucursales]
                properties:
                  sucursales:
                    type: array
                    items:
                      $ref: "#/components/schemas/Sucursal"
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
  /sucursales/{id}:
    get:
      tags: [parque]
      summary: Una sede
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Sede
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Sucursal"
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
        "404":
          $ref: "#/components/responses/NoEncontrado"
  /equipos:
    get:
      tags: [parque]
      summary: Equipos de esta empresa
      description: Filtro opcional por sede. Hasta 50 por página, orden por id.
      parameters:
        - name: sucursal_id
          in: query
          schema:
            type: integer
            minimum: 1
          description: Solo equipos de esa sede (de esta empresa)
        - $ref: "#/components/parameters/pagina"
      responses:
        "200":
          description: Listado
          content:
            application/json:
              schema:
                type: object
                required: [equipos]
                properties:
                  equipos:
                    type: array
                    items:
                      $ref: "#/components/schemas/Equipo"
              example:
                equipos:
                  - id: 10
                    sucursal_id: 12
                    marca: BGH
                    modelo: Silent Air
                    tipo: split
                    serie: ABC123
                    ubicacion: sala tablero
                    codigo_activo: HVAC-001
                    capacidad: "3000"
                    refrigerante: R32
                    estado: bueno
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
        "429":
          $ref: "#/components/responses/Demasiado"
  /equipos/{id}:
    get:
      tags: [parque]
      summary: Ficha de un equipo
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: Equipo
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Equipo"
        "401":
          $ref: "#/components/responses/NoAuth"
        "404":
          $ref: "#/components/responses/NoEncontrado"
  /ots:
    get:
      tags: [visitas]
      summary: Órdenes de trabajo de esta empresa
      parameters:
        - name: sucursal_id
          in: query
          schema:
            type: integer
        - name: desde
          in: query
          schema:
            type: string
            format: date
        - name: hasta
          in: query
          schema:
            type: string
            format: date
        - $ref: "#/components/parameters/pagina"
      responses:
        "200":
          description: Listado
          content:
            application/json:
              schema:
                type: object
                required: [ots]
                properties:
                  ots:
                    type: array
                    items:
                      $ref: "#/components/schemas/OtResumen"
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
  /ots/{id}:
    get:
      tags: [visitas]
      summary: Detalle de una OT (sin notas internas)
      parameters:
        - $ref: "#/components/parameters/id"
      responses:
        "200":
          description: OT
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OtDetalle"
        "401":
          $ref: "#/components/responses/NoAuth"
        "404":
          $ref: "#/components/responses/NoEncontrado"
  /presupuestos:
    get:
      tags: [comercial]
      summary: Presupuestos enviados o posteriores (no borradores internos)
      responses:
        "200":
          description: Listado
          content:
            application/json:
              schema:
                type: object
                required: [presupuestos]
                properties:
                  presupuestos:
                    type: array
                    items:
                      $ref: "#/components/schemas/Presupuesto"
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
  /facturas:
    get:
      tags: [comercial]
      summary: Comprobantes emitidos a esta empresa
      responses:
        "200":
          description: Listado
          content:
            application/json:
              schema:
                type: object
                required: [facturas]
                properties:
                  facturas:
                    type: array
                    items:
                      $ref: "#/components/schemas/Factura"
        "401":
          $ref: "#/components/responses/NoAuth"
        "403":
          $ref: "#/components/responses/SinAlcance"
        "429":
          $ref: "#/components/responses/Demasiado"
  /mcp:
    post:
      tags: [sesion]
      summary: MCP JSON-RPC (mismo token)
      description: |
        initialize, tools/list, tools/call, ping.
        El agente no envía cliente_id. Sin token, 401.
        El MCP de contacto en www no lee equipos.
      requestBody:
        required: true
        content:
          application/json:
            example:
              jsonrpc: "2.0"
              id: 1
              method: tools/list
              params: {}
      responses:
        "200":
          description: JSON-RPC
        "401":
          $ref: "#/components/responses/NoAuth"
        "429":
          $ref: "#/components/responses/Demasiado"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: token
      description: Token de esta empresa. Header Authorization. No va en la URL. El alcance (parque, visitas, comercial) viene en el token.
  parameters:
    id:
      name: id
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
    pagina:
      name: pagina
      in: query
      description: Página (1…). Hasta 50 filas. No hay sort query.
      schema:
        type: integer
        minimum: 1
        default: 1
  responses:
    NoAuth:
      description: Token ausente, vencido o dado de baja
    SinAlcance:
      description: Token válido pero sin ese alcance
    NoEncontrado:
      description: No existe para esta empresa
    Demasiado:
      description: Más de 60 pedidos por minuto con este token
  schemas:
    Me:
      type: object
      required: [cliente_id, alcances, vence]
      properties:
        cliente_id:
          type: integer
        razon_social:
          type: string
        alcances:
          type: array
          items:
            type: string
            enum: [parque, visitas, comercial]
        vence:
          type: string
          format: date-time
    Sucursal:
      type: object
      required: [id, nombre]
      properties:
        id:
          type: integer
        nombre:
          type: string
        domicilio:
          type: string
        localidad:
          type: string
    Equipo:
      type: object
      required: [id, sucursal_id]
      properties:
        id:
          type: integer
        sucursal_id:
          type: integer
        marca:
          type: string
        modelo:
          type: string
        tipo:
          type: string
        serie:
          type: string
        ubicacion:
          type: string
        codigo_activo:
          type: string
          description: Código de activo del inventario del cliente, si existe
        capacidad:
          type: string
        refrigerante:
          type: string
        estado:
          type: string
    OtResumen:
      type: object
      required: [id, fecha]
      properties:
        id:
          type: integer
        numero:
          type: string
        fecha:
          type: string
          format: date
        sucursal_id:
          type: integer
        firmada:
          type: boolean
    OtDetalle:
      allOf:
        - $ref: "#/components/schemas/OtResumen"
        - type: object
          properties:
            trabajo:
              type: string
              description: Texto visible para el cliente
            equipos:
              type: array
              items:
                type: object
                properties:
                  equipo_id:
                    type: integer
                  trabajo:
                    type: string
                  mediciones:
                    type: object
                    additionalProperties: true
    Presupuesto:
      type: object
      required: [id, estado]
      properties:
        id:
          type: integer
        numero:
          type: string
        estado:
          type: string
        vencimiento:
          type: string
          format: date
        oc_cliente:
          type: string
    Factura:
      type: object
      required: [id]
      properties:
        id:
          type: integer
        numero:
          type: string
        fecha:
          type: string
          format: date
        total:
          type: string
        ot_id:
          type: integer
