openapi: 3.0.3
info:
  title: Gestion Moda API v1
  version: 1.1.0
  description: >
    Contrato tecnico base de la API v1. Prioriza autenticacion, scopes y el modulo
    de ventas alineado a la primera etapa de paridad funcional con LaPyme.
servers:
  - url: https://app.gestion.moda/api
    description: Produccion
  - url: http://localhost/api
    description: Desarrollo local
tags:
  - name: Auth
    description: Emision y validacion de tokens personales
  - name: Products
    description: Catalogo y costos unitarios de productos
  - name: Inventory
    description: Consulta de stock disponible
  - name: Sales
    description: Consulta, bootstrap y creacion de ventas
components:
  securitySchemes:
    sanctumBearer:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
  parameters:
    SaleId:
      name: id
      in: path
      required: true
      schema:
        type: integer
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    PerPage:
      name: per_page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    Search:
      name: search
      in: query
      required: false
      schema:
        type: string
    From:
      name: from
      in: query
      required: false
      schema:
        type: string
        format: date
    To:
      name: to
      in: query
      required: false
      schema:
        type: string
        format: date
    DateFrom:
      name: dateFrom
      in: query
      required: false
      schema:
        type: string
        format: date
    DateTo:
      name: dateTo
      in: query
      required: false
      schema:
        type: string
        format: date
    IncludeDetails:
      name: include_details
      in: query
      required: false
      schema:
        type: boolean
        default: false
    IncludePayments:
      name: include_payments
      in: query
      required: false
      schema:
        type: boolean
        default: false
    ClientId:
      name: client_id
      in: query
      required: false
      schema:
        type: integer
    ChannelId:
      name: channel_id
      in: query
      required: false
      schema:
        type: integer
    StoreId:
      name: store_id
      in: query
      required: false
      schema:
        type: integer
    SaleStateId:
      name: sale_state_id
      in: query
      required: false
      schema:
        type: integer
    ProviderId:
      name: provider_id
      in: query
      required: false
      schema:
        type: integer
    Q:
      name: q
      in: query
      required: false
      description: Búsqueda libre sobre los campos relevantes del recurso
      schema:
        type: string
    Year:
      name: year
      in: query
      required: false
      schema:
        type: integer
    Month:
      name: month
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 12
    Paid:
      name: paid
      in: query
      required: false
      schema:
        type: integer
        enum: [0, 1]
    Active:
      name: active
      in: query
      required: false
      description: Default 1 (solo activos). Pasar 0 para listar inactivos.
      schema:
        type: integer
        enum: [0, 1]
        default: 1
    DateFromBill:
      name: date_from
      in: query
      required: false
      schema:
        type: string
        format: date
    DateToBill:
      name: date_to
      in: query
      required: false
      schema:
        type: string
        format: date
  schemas:
    TokenRequest:
      type: object
      required:
        - email
        - password
        - device_name
      properties:
        email:
          type: string
          format: email
        password:
          type: string
        device_name:
          type: string
        abilities:
          type: array
          items:
            type: string
            enum:
              - prices:read
              - prices:write
              - products:read
              - products:write
              - inventory:read
              - inventory:write
              - sales:read
              - sales:write
              - costs:read
              - costs:write
              - clients:read
              - clients:write
              - categories:read
              - categories:write
              - accounts:read
              - accounts:write
              - stores:read
              - stores:write
              - production:read
              - production:write
              - supplies:read
              - supplies:write
              - providers:read
              - providers:write
    ApiMessage:
      type: object
      properties:
        message:
          type: string
    ValidationError:
      allOf:
        - $ref: '#/components/schemas/ApiMessage'
        - type: object
          properties:
            errors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
    PaginationMeta:
      type: object
      properties:
        current_page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer
        last_page:
          type: integer
        from:
          type: integer
          nullable: true
        to:
          type: integer
          nullable: true
        has_more_pages:
          type: boolean
    PaginationLinks:
      type: object
      properties:
        first:
          type: string
          nullable: true
        last:
          type: string
          nullable: true
        prev:
          type: string
          nullable: true
        next:
          type: string
          nullable: true
    SaleClient:
      type: object
      properties:
        id:
          type: integer
          nullable: true
        name:
          type: string
        email:
          type: string
        phone_number:
          type: string
        address:
          type: string
        city:
          type: string
        province:
          type: string
        postal_code:
          type: string
    SaleItem:
      type: object
      properties:
        id:
          type: integer
        product_id:
          type: integer
        product_code:
          type: string
        product_name:
          type: string
        size_id:
          type: integer
          nullable: true
        size:
          type: string
        store_id:
          type: integer
          nullable: true
        quantity:
          type: number
          format: float
        unit_price:
          type: number
          format: float
        subtotal:
          type: number
          format: float
        discount:
          type: number
          format: float
        total:
          type: number
          format: float
        vat_rate:
          type: number
          format: float
        vat_amount:
          type: number
          format: float
    SalePayment:
      type: object
      properties:
        id:
          type: integer
        date_payment:
          type: string
          format: date
        amount:
          type: number
          format: float
        description:
          type: string
        account_id:
          type: integer
          nullable: true
        account_name:
          type: string
        linked_payment_client_id:
          type: integer
          nullable: true
        account:
          $ref: '#/components/schemas/SaleReferenceItem'
    SaleCreditPayment:
      type: object
      properties:
        id:
          type: integer
        date_payment:
          type: string
          format: date
        amount:
          type: number
          format: float
        description:
          type: string
        account_id:
          type: integer
          nullable: true
        account_name:
          type: string
        linked_payment_sale_id:
          type: integer
          nullable: true
        account:
          $ref: '#/components/schemas/SaleReferenceItem'
    SaleUser:
      type: object
      properties:
        id:
          type: integer
          nullable: true
        name:
          type: string
    SaleInvoice:
      type: object
      properties:
        id:
          type: integer
          nullable: true
        cae:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
        type:
          type: string
          nullable: true
        sale_point:
          type: integer
          nullable: true
        voucher:
          type: integer
          nullable: true
        invoice_number:
          type: string
          nullable: true
    SaleTotals:
      type: object
      properties:
        gross:
          type: number
          format: float
        net:
          type: number
          format: float
        vat_amount:
          type: number
          format: float
        discount:
          type: number
          format: float
        shipping_cost:
          type: number
          format: float
        payments:
          type: number
          format: float
        client_credit_generated:
          type: number
          format: float
        due:
          type: number
          format: float
        cost:
          type: number
          format: float
        profit:
          type: number
          format: float
    SaleResource:
      type: object
      properties:
        id:
          type: integer
        number:
          type: integer
        date_sale:
          type: string
          format: date
        delivery_date:
          type: string
          format: date
          nullable: true
        client_id:
          type: integer
          nullable: true
        channel_id:
          type: integer
          nullable: true
        currency_id:
          type: integer
          nullable: true
        sale_type_id:
          type: integer
          nullable: true
        sale_state_id:
          type: integer
          nullable: true
        store_id:
          type: integer
          nullable: true
        payment_method_id:
          type: integer
          nullable: true
        shipping_method_id:
          type: integer
          nullable: true
        exchange_rate:
          type: number
          format: float
        total_price:
          type: number
          format: float
        net_price:
          type: number
          format: float
        vat_amount:
          type: number
          format: float
        total_cost:
          type: number
          format: float
        profit:
          type: number
          format: float
        total_payment:
          type: number
          format: float
        total_due:
          type: number
          format: float
        discount:
          type: number
          format: float
        shipping_cost:
          type: number
          format: float
        items_sold:
          type: number
          format: float
        items_lines:
          type: integer
        comments:
          type: string
          nullable: true
        bill_number:
          type: string
          nullable: true
        invoice_number:
          type: string
          nullable: true
        active:
          type: boolean
        budget:
          type: boolean
        archived:
          type: boolean
        direct_sale:
          type: boolean
        direct_sale_no_inventory:
          type: boolean
        import:
          type: boolean
        produced:
          type: boolean
        is_exchange:
          type: boolean
        user_id:
          type: integer
          nullable: true
        user_name:
          type: string
        integration_source:
          type: string
          nullable: true
        integration_id:
          type: string
          nullable: true
        invoice_id:
          type: integer
          nullable: true
        bill_cae:
          type: string
          nullable: true
        bill_status:
          type: string
          nullable: true
        invoice_type:
          type: string
          nullable: true
        sale_point:
          type: integer
          nullable: true
        voucher:
          type: integer
          nullable: true
        tn_order:
          type: string
          nullable: true
        tn_note:
          type: string
          nullable: true
        account_display:
          type: string
        client_credit_generated:
          type: number
          format: float
        client_name:
          type: string
        client_email:
          type: string
        client_phone:
          type: string
        client_address:
          type: string
        client_city:
          type: string
        client_province:
          type: string
        client_postal_code:
          type: string
        channel:
          type: string
        currency:
          type: string
        sale_type:
          type: string
        sale_state:
          type: string
        store:
          type: string
        payment_method:
          type: string
        shipping_method:
          type: string
        user:
          $ref: '#/components/schemas/SaleUser'
        invoice:
          $ref: '#/components/schemas/SaleInvoice'
        client:
          $ref: '#/components/schemas/SaleClient'
        totals:
          $ref: '#/components/schemas/SaleTotals'
        items:
          type: array
          items:
            $ref: '#/components/schemas/SaleItem'
        detalles:
          type: array
          items:
            $ref: '#/components/schemas/SaleItem'
        payments:
          type: array
          items:
            $ref: '#/components/schemas/SalePayment'
        credit_payments:
          type: array
          items:
            $ref: '#/components/schemas/SaleCreditPayment'
    SaleListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SaleResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    SaleReferenceItem:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
    SaleReferenceAccount:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        balance:
          type: number
          format: float
        payment_method_id:
          type: integer
          nullable: true
        payment_method:
          type: string
        commission_percentage:
          type: number
          format: float
        is_default:
          type: boolean
    SaleReferencePriceList:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        currency_id:
          type: integer
          nullable: true
        related_price_list_id:
          type: integer
          nullable: true
        is_base:
          type: boolean
        variance:
          type: number
          format: float
          nullable: true
        use_retailer_price:
          type: boolean
        use_wholesaler_price:
          type: boolean
        use_promotional_price:
          type: boolean
        use_unit_cost:
          type: boolean
    SaleReferenceSaleMode:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        comments:
          type: string
          nullable: true
        discount_percent:
          type: number
          format: float
        inventory_discount:
          type: boolean
        show_stock:
          type: boolean
        store_id:
          type: integer
          nullable: true
        channel_id:
          type: integer
          nullable: true
        price_list_id:
          type: integer
          nullable: true
        sale_state_id:
          type: integer
          nullable: true
        sale_type_id:
          type: integer
          nullable: true
        payment_method_id:
          type: integer
          nullable: true
        salesman_id:
          type: integer
          nullable: true
        account_id:
          type: integer
          nullable: true
        allow_client_search:
          type: boolean
        commission:
          type: number
          format: float
        commission_payment:
          type: number
          format: float
        retained_tax:
          type: number
          format: float
        auto_create_client:
          type: boolean
    SaleReferenceConfiguration:
      type: object
      properties:
        default_store_id:
          type: integer
          nullable: true
        default_account_id:
          type: integer
          nullable: true
        inventory_discount:
          type: boolean
        allow_negative_stock:
          type: boolean
        show_produce:
          type: boolean
        edit_sale_prices:
          type: boolean
        decimals:
          type: integer
        price_decimals:
          type: integer
    SaleReferencesResponse:
      type: object
      properties:
        canales:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        estados_venta:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        tipos_venta:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        monedas:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        depositos:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        cuentas:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceAccount'
        metodos_envio:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        listas_precio:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferencePriceList'
        modos_venta:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceSaleMode'
        asociados:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        proveedores:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        vendedores:
          type: array
          items:
            $ref: '#/components/schemas/SaleReferenceItem'
        configuracion:
          $ref: '#/components/schemas/SaleReferenceConfiguration'
    SaleCreateItemInput:
      type: object
      required:
        - product_id
        - size_id
        - quantity
        - unit_price
      properties:
        product_id:
          type: integer
        size_id:
          type: integer
        quantity:
          type: number
          format: float
        unit_price:
          type: number
          format: float
        discount:
          type: number
          format: float
    SaleCreatePaymentInput:
      type: object
      required:
        - amount
      properties:
        amount:
          type: number
          format: float
        description:
          type: string
        date_payment:
          type: string
          format: date
        account_id:
          type: integer
    SaleCreateRequest:
      type: object
      required:
        - client_id
        - channel_id
        - sale_type_id
        - currency_id
        - items
      properties:
        client_id:
          type: integer
        channel_id:
          type: integer
        sale_type_id:
          type: integer
        currency_id:
          type: integer
        date_sale:
          type: string
          format: date
        delivery_date:
          type: string
          format: date
        store_id:
          type: integer
        sale_state_id:
          type: integer
        payment_method_id:
          type: integer
        shipping_method_id:
          type: integer
        exchange_rate:
          type: number
          format: float
        comments:
          type: string
        discount_amount:
          type: number
          format: float
        shipping_cost_amount:
          type: number
          format: float
        discount_inventory:
          type: boolean
        integration_source:
          type: string
          maxLength: 100
        integration_id:
          type: string
          maxLength: 191
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/SaleCreateItemInput'
        payments:
          type: array
          items:
            $ref: '#/components/schemas/SaleCreatePaymentInput'
    SaleCreateResponse:
      allOf:
        - $ref: '#/components/schemas/ApiMessage'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/SaleResource'
    ProductCostResource:
      type: object
      properties:
        id:
          type: integer
        code:
          type: string
          nullable: true
        name:
          type: string
        unit_cost:
          type: number
          format: float
        date_unit_cost:
          type: string
          nullable: true
        active:
          type: boolean
        resale:
          type: boolean
        is_kit:
          type: boolean
    ProductCostListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProductCostResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    ProductCostDetailResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/ProductCostResource'
    InventoryResource:
      type: object
      properties:
        inventory_id:
          type: integer
        product_id:
          type: integer
        product_code:
          type: string
          nullable: true
        product_name:
          type: string
        size_id:
          type: integer
          nullable: true
        size_name:
          type: string
        store_id:
          type: integer
          nullable: true
        store_name:
          type: string
        available_quantity:
          type: number
          format: float
        barcode:
          type: string
          nullable: true
        sku:
          type: string
          nullable: true
        observation:
          type: string
          maxLength: 20
          nullable: true
        physical_quantities:
          type: number
          format: float
          nullable: true
        in_production_quantity:
          type: number
          format: float
          nullable: true
    InventoryListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/InventoryResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    CostResource:
      type: object
      properties:
        id: { type: integer }
        cost_type_id: { type: integer }
        cost_type: { type: string, nullable: true }
        sub_cost_type_id: { type: integer, nullable: true }
        sub_cost_type: { type: string, nullable: true }
        cost_category_id: { type: integer, nullable: true }
        cost_category: { type: string, nullable: true }
        provider_id: { type: integer, nullable: true }
        provider: { type: string, nullable: true }
        channel_id: { type: integer, nullable: true }
        year: { type: integer }
        month: { type: integer }
        bill_date: { type: string, format: date, nullable: true }
        amount: { type: number, format: float }
        amount_plan: { type: number, format: float }
        vat_rate: { type: number, format: float }
        vat_amount: { type: number, format: float }
        description: { type: string, nullable: true }
        paid: { type: boolean }
        created_at: { type: string, format: date-time, nullable: true }
    CostListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CostResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    ProductionResource:
      type: object
      properties:
        id: { type: integer }
        product_id: { type: integer }
        product_name: { type: string, nullable: true }
        size_id: { type: integer, nullable: true }
        size_name: { type: string, nullable: true }
        store_id: { type: integer, nullable: true }
        store_name: { type: string, nullable: true }
        date_production: { type: string, format: date, nullable: true }
        quantity: { type: number, format: float }
        planning: { type: boolean }
        inventory: { type: boolean }
        expense: { type: boolean }
        description: { type: string, nullable: true }
        total_cost_supply: { type: number, format: float }
        total_cost_service: { type: number, format: float }
        unit_cost_supply: { type: number, format: float }
        unit_cost_service: { type: number, format: float }
        created_at: { type: string, format: date-time, nullable: true }
    ProductionListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProductionResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    BuySupplyResource:
      type: object
      properties:
        id: { type: integer }
        number: { type: integer, nullable: true }
        buy_date: { type: string, format: date, nullable: true }
        provider_id: { type: integer, nullable: true }
        provider: { type: string, nullable: true }
        description: { type: string, nullable: true }
        expense: { type: boolean }
        paid: { type: boolean }
        status: { type: string, nullable: true }
        total_cost: { type: number, format: float }
        total_quantity: { type: number, format: float }
        total_vat_amount: { type: number, format: float }
        items_count: { type: integer }
        created_at: { type: string, format: date-time, nullable: true }
    BuySupplyListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/BuySupplyResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    ClientResource:
      type: object
      properties:
        id: { type: integer }
        number: { type: integer, nullable: true }
        name: { type: string }
        email: { type: string, nullable: true }
        phone_number: { type: string, nullable: true }
        cellphone_number: { type: string, nullable: true }
        address: { type: string, nullable: true }
        postal_code: { type: string, nullable: true }
        city: { type: string, nullable: true }
        province: { type: string, nullable: true }
        client_type_id: { type: integer, nullable: true }
        client_state_id: { type: integer, nullable: true }
        client_category_id: { type: integer, nullable: true }
        channel_id: { type: integer, nullable: true }
        price_list_id: { type: integer, nullable: true }
        bill_name: { type: string, nullable: true }
        bill_address: { type: string, nullable: true }
        bill_city: { type: string, nullable: true }
        bill_province: { type: string, nullable: true }
        bill_country: { type: string, nullable: true }
        bill_identification_number_type: { type: string, nullable: true }
        bill_identification_number: { type: string, nullable: true }
        kind_responsibility_id: { type: integer, nullable: true }
        kind_responsibility: { type: string, nullable: true }
        billable: { type: boolean }
        commentary: { type: string, nullable: true }
        notes: { type: string, nullable: true }
        active: { type: boolean }
        created_at: { type: string, format: date-time, nullable: true }
    ClientListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ClientResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
    ProviderResource:
      type: object
      properties:
        id: { type: integer }
        code: { type: string, nullable: true }
        number: { type: integer, nullable: true }
        name: { type: string }
        email: { type: string, nullable: true }
        contact: { type: string, nullable: true }
        web_page: { type: string, nullable: true }
        commentary: { type: string, nullable: true }
        cellphone_number: { type: string, nullable: true }
        phone_number: { type: string, nullable: true }
        address: { type: string, nullable: true }
        province: { type: string, nullable: true }
        city: { type: string, nullable: true }
        commission: { type: number, format: float }
        account_id: { type: integer, nullable: true }
        provider_type_id: { type: integer, nullable: true }
        provider_type: { type: string, nullable: true }
        provider_category_id: { type: integer, nullable: true }
        provider_category: { type: string, nullable: true }
        kind_responsibility_id: { type: integer, nullable: true }
        kind_responsibility: { type: string, nullable: true }
        active: { type: boolean }
        created_at: { type: string, format: date-time, nullable: true }
    ProviderListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ProviderResource'
        meta:
          $ref: '#/components/schemas/PaginationMeta'
        links:
          $ref: '#/components/schemas/PaginationLinks'
paths:
  /sanctum/token:
    post:
      tags: [Auth]
      summary: Emitir un token personal
      description: >
        Emite un token con las abilities solicitadas que esten habilitadas para
        emision. inventory:write solo habilita actualizar la observacion de un
        item de inventario; no habilita lectura ni cambios de stock.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
      responses:
        '200':
          description: Token emitido en texto plano
          content:
            text/plain:
              schema:
                type: string
        '422':
          description: Error de validacion o credenciales invalidas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
  /v1/usuario:
    get:
      tags: [Auth]
      summary: Obtener usuario autenticado
      security:
        - sanctumBearer: []
      responses:
        '200':
          description: Usuario autenticado
          content:
            application/json:
              schema:
                type: object
        '401':
          description: No autenticado
  /v1/productos/costos:
    get:
      tags: [Products]
      summary: Listar costos unitarios de productos
      description: >
        Endpoint de lectura protegido por costs:read. No requiere products:read.
        Devuelve costos unitarios de productos de la empresa asociada al token.
      security:
        - sanctumBearer: [costs:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - name: product_id
          in: query
          schema:
            type: integer
        - name: code
          in: query
          schema:
            type: string
        - name: q
          in: query
          schema:
            type: string
        - name: category_id
          in: query
          schema:
            type: integer
        - name: provider_id
          in: query
          schema:
            type: integer
        - name: active
          in: query
          schema:
            type: integer
            enum: [0, 1]
      responses:
        '200':
          description: Listado paginado de costos unitarios
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductCostListResponse'
        '401':
          description: No autenticado
        '403':
          description: Falta costs:read
  /v1/productos/costos/{id}:
    get:
      tags: [Products]
      summary: Obtener costo unitario de un producto
      security:
        - sanctumBearer: [costs:read]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Costo unitario del producto
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductCostDetailResponse'
        '401':
          description: No autenticado
        '403':
          description: Falta costs:read
        '404':
          description: Producto no encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
  /v1/inventario/obtener:
    get:
      tags: [Inventory]
      summary: Listar inventario disponible
      description: >
        Lista stock disponible con paginacion y filtros. Requiere inventory:read;
        inventory:write no habilita este endpoint de lectura. Cada item incluye
        observation.
      security:
        - sanctumBearer: [inventory:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - name: product_id
          in: query
          schema:
            type: integer
        - name: size_id
          in: query
          schema:
            type: integer
        - name: store_id
          in: query
          schema:
            type: integer
        - name: code
          in: query
          schema:
            type: string
        - name: q
          in: query
          schema:
            type: string
        - name: include_physical
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Listado paginado de inventario
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InventoryListResponse'
        '401':
          description: No autenticado
        '403':
          description: Falta inventory:read
  /v1/inventario/{id}/observacion:
    put:
      tags: [Inventory]
      summary: Actualizar observacion de inventario
      description: >
        Actualiza exclusivamente el campo observation del item de inventario.
        No modifica cantidades de stock.
      security:
        - sanctumBearer: [inventory:write]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [observation]
              properties:
                observation:
                  type: string
                  maxLength: 20
                  nullable: true
      responses:
        '200':
          description: Observacion actualizada
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiMessage'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          inventory_id:
                            type: integer
                          product_id:
                            type: integer
                          size_id:
                            type: integer
                            nullable: true
                          store_id:
                            type: integer
                            nullable: true
                          observation:
                            type: string
                            maxLength: 20
                            nullable: true
        '403':
          description: Falta inventory:write
        '404':
          description: Inventario no encontrado
        '422':
          description: Campo invalido o intento de actualizar otro campo
    patch:
      tags: [Inventory]
      summary: Actualizar observacion de inventario
      description: Igual que PUT; acepta exclusivamente observation.
      security:
        - sanctumBearer: [inventory:write]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [observation]
              properties:
                observation:
                  type: string
                  maxLength: 20
                  nullable: true
      responses:
        '200':
          description: Observacion actualizada
        '403':
          description: Falta inventory:write
        '404':
          description: Inventario no encontrado
        '422':
          description: Campo invalido o intento de actualizar otro campo
  /v1/ventas:
    get:
      tags: [Sales]
      summary: Listar ventas
      description: >
        Ruta REST recomendada para consulta de ventas. Devuelve solo ventas activas,
        no archivadas y no presupuesto, respetando los permisos del usuario del token
        sobre depositos y visibilidad de ventas de otros usuarios.
      security:
        - sanctumBearer: [sales:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/From'
        - $ref: '#/components/parameters/To'
        - $ref: '#/components/parameters/DateFrom'
        - $ref: '#/components/parameters/DateTo'
        - $ref: '#/components/parameters/IncludeDetails'
        - $ref: '#/components/parameters/IncludePayments'
        - $ref: '#/components/parameters/ClientId'
        - $ref: '#/components/parameters/ChannelId'
        - $ref: '#/components/parameters/StoreId'
        - $ref: '#/components/parameters/SaleStateId'
      responses:
        '200':
          description: Listado paginado de ventas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleListResponse'
        '401':
          description: No autenticado
        '403':
          description: Falta sales:read
        '422':
          description: Fecha invalida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
    post:
      tags: [Sales]
      summary: Crear una venta
      description: >
        Alta simplificada de venta. El servidor calcula importes, IVA, costo, profit y saldo.
        Soporta descuento por linea en items[].discount. Si recibe pagos opcionales, registra
        el importe aplicable en payment_sales y cualquier excedente como saldo a favor del
        cliente en payment_clients. Si recibe integration_source + integration_id, aplica
        deduplicacion por company_id usando la tabla auxiliar sale_integration_references.
      security:
        - sanctumBearer: [sales:write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaleCreateRequest'
      responses:
        '201':
          description: Venta creada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleCreateResponse'
        '404':
          description: Cliente, producto, cuenta o referencia inexistente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '409':
          description: Duplicado por referencia de integracion
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '422':
          description: Validacion o stock insuficiente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
  /v1/ventas/referencias:
    get:
      tags: [Sales]
      summary: Bootstrap para ventas
      description: >
        Devuelve canales, estados, tipos, monedas, depositos, cuentas, metodos de envio,
        listas de precios, modos de venta, asociados, proveedores, vendedores y
        configuracion por defecto para bootstrap del alta simplificada.
      security:
        - sanctumBearer: [sales:read]
        - sanctumBearer: [sales:write]
      responses:
        '200':
          description: Referencias disponibles
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleReferencesResponse'
  /v1/ventas/{id}:
    get:
      tags: [Sales]
      summary: Ver una venta
      description: Ruta REST recomendada para detalle unitario.
      security:
        - sanctumBearer: [sales:read]
      parameters:
        - $ref: '#/components/parameters/SaleId'
      responses:
        '200':
          description: Venta expandida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleResource'
        '404':
          description: Venta no encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
  /v1/ventas/obtener:
    get:
      tags: [Sales]
      summary: Listar ventas (legacy)
      description: Alias legacy soportado de /v1/ventas.
      security:
        - sanctumBearer: [sales:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/From'
        - $ref: '#/components/parameters/To'
        - $ref: '#/components/parameters/IncludeDetails'
        - $ref: '#/components/parameters/IncludePayments'
        - $ref: '#/components/parameters/ClientId'
        - $ref: '#/components/parameters/ChannelId'
        - $ref: '#/components/parameters/StoreId'
        - $ref: '#/components/parameters/SaleStateId'
      responses:
        '200':
          description: Listado paginado de ventas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleListResponse'
  /v1/ventas/ver/{id}:
    get:
      tags: [Sales]
      summary: Ver una venta (legacy)
      description: Alias legacy soportado de /v1/ventas/{id}.
      security:
        - sanctumBearer: [sales:read]
      parameters:
        - $ref: '#/components/parameters/SaleId'
      responses:
        '200':
          description: Venta expandida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SaleResource'
        '404':
          description: Venta no encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
  /v1/gastos:
    get:
      tags: [Costs]
      summary: Listar gastos
      description: Lista gastos activos de la empresa con paginación y filtros. La respuesta no incluye el detalle de pagos asociados.
      security:
        - sanctumBearer: [costs:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Q'
        - name: cost_type_id
          in: query
          required: false
          schema: { type: integer }
        - name: sub_cost_type_id
          in: query
          required: false
          schema: { type: integer }
        - name: cost_category_id
          in: query
          required: false
          schema: { type: integer }
        - $ref: '#/components/parameters/ProviderId'
        - $ref: '#/components/parameters/ChannelId'
        - $ref: '#/components/parameters/Year'
        - $ref: '#/components/parameters/Month'
        - $ref: '#/components/parameters/Paid'
        - $ref: '#/components/parameters/DateFromBill'
        - $ref: '#/components/parameters/DateToBill'
      responses:
        '200':
          description: Listado paginado de gastos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CostListResponse'
        '401': { description: No autenticado }
        '403': { description: Falta costs:read }
    post:
      tags: [Costs]
      summary: Crear un gasto
      description: Registra un gasto con categoría, proveedor opcional, IVA y pagos. Si la suma de payments cubre el amount, el gasto queda marcado como paid=true.
      security:
        - sanctumBearer: [costs:write]
      responses:
        '201':
          description: Gasto creado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '404': { description: Recurso referenciado inexistente }
        '409': { description: Conflicto al registrar pagos }
        '422': { description: Validación }
  /v1/produccion:
    get:
      tags: [Production]
      summary: Listar producciones
      description: Lista producciones activas de la empresa con paginación y filtros. La respuesta no incluye el detalle de insumos ni servicios.
      security:
        - sanctumBearer: [production:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Q'
        - name: product_id
          in: query
          required: false
          schema: { type: integer }
        - $ref: '#/components/parameters/StoreId'
        - name: size_id
          in: query
          required: false
          schema: { type: integer }
        - $ref: '#/components/parameters/Year'
        - $ref: '#/components/parameters/Month'
        - name: planning
          in: query
          required: false
          schema: { type: integer, enum: [0, 1] }
        - name: inventory
          in: query
          required: false
          schema: { type: integer, enum: [0, 1] }
        - name: expense
          in: query
          required: false
          schema: { type: integer, enum: [0, 1] }
        - $ref: '#/components/parameters/DateFromBill'
        - $ref: '#/components/parameters/DateToBill'
      responses:
        '200':
          description: Listado paginado de producciones
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductionListResponse'
        '401': { description: No autenticado }
        '403': { description: Falta production:read }
    post:
      tags: [Production]
      summary: Registrar una producción
      description: Crea una producción y descuenta automáticamente los insumos del stock cuando planning=false.
      security:
        - sanctumBearer: [production:write]
      responses:
        '201':
          description: Producción creada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '404': { description: Recurso referenciado inexistente }
        '409': { description: Stock insuficiente }
        '422': { description: Validación }
  /v1/compras/insumos:
    get:
      tags: [BuySupplies]
      summary: Listar compras de insumos
      description: Lista cabeceras de compras de insumos activas con paginación y filtros. Devuelve totales agregados sin items[] ni payments[].
      security:
        - sanctumBearer: [supplies:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Q'
        - $ref: '#/components/parameters/ProviderId'
        - $ref: '#/components/parameters/Paid'
        - name: expense
          in: query
          required: false
          schema: { type: integer, enum: [0, 1] }
        - name: status
          in: query
          required: false
          schema: { type: string }
        - $ref: '#/components/parameters/DateFromBill'
        - $ref: '#/components/parameters/DateToBill'
      responses:
        '200':
          description: Listado paginado de compras de insumos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BuySupplyListResponse'
        '401': { description: No autenticado }
        '403': { description: Falta supplies:read }
    post:
      tags: [BuySupplies]
      summary: Registrar una compra de insumos
      description: Registra una compra con sus items y opcionalmente actualiza el stock de insumos y los pagos.
      security:
        - sanctumBearer: [supplies:write]
      responses:
        '201':
          description: Compra creada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '404': { description: Insumo o proveedor inexistente }
        '409': { description: Conflicto al registrar pagos }
        '422': { description: Validación }
  /v1/clientes:
    get:
      tags: [Clients]
      summary: Listar clientes
      description: Lista clientes de la empresa con paginación y filtros. Por defecto solo muestra clientes activos.
      security:
        - sanctumBearer: [clients:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Q'
        - name: client_type_id
          in: query
          required: false
          schema: { type: integer }
        - name: client_state_id
          in: query
          required: false
          schema: { type: integer }
        - name: client_category_id
          in: query
          required: false
          schema: { type: integer }
        - $ref: '#/components/parameters/ChannelId'
        - name: kind_responsbility_id
          in: query
          required: false
          schema: { type: integer }
        - name: billable
          in: query
          required: false
          schema: { type: integer, enum: [0, 1] }
        - $ref: '#/components/parameters/Active'
      responses:
        '200':
          description: Listado paginado de clientes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientListResponse'
        '401': { description: No autenticado }
        '403': { description: Falta clients:read }
    post:
      tags: [Clients]
      summary: Crear un cliente
      description: Crea un cliente con datos fiscales opcionales. Si ya existe otro cliente activo con el mismo nombre en la empresa devuelve 409.
      security:
        - sanctumBearer: [clients:write]
      responses:
        '201':
          description: Cliente creado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiMessage'
        '409': { description: Cliente duplicado por nombre }
        '422': { description: Validación }
  /v1/proveedores:
    get:
      tags: [Providers]
      summary: Listar proveedores
      description: Lista proveedores de la empresa con paginación y filtros. Útil para resolver provider_id a nombre desde otros endpoints.
      security:
        - sanctumBearer: [providers:read]
      parameters:
        - $ref: '#/components/parameters/PerPage'
        - $ref: '#/components/parameters/Q'
        - name: provider_type_id
          in: query
          required: false
          schema: { type: integer }
        - name: provider_category_id
          in: query
          required: false
          schema: { type: integer }
        - name: kind_responsbility_id
          in: query
          required: false
          schema: { type: integer }
        - $ref: '#/components/parameters/Active'
      responses:
        '200':
          description: Listado paginado de proveedores
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProviderListResponse'
        '401': { description: No autenticado }
        '403': { description: Falta providers:read }
