Procurement

El dominio de Compras mayoristas (Procurement) gestiona el ciclo completo de abastecimiento del club: desde el pedido de insumos de cultivo (RFQ), pasando por las cotizaciones de proveedores y su comparación, hasta la adjudicación y la orden de compra resultante. En esta página vamos a repasar los distintos endpoints BFF que expone el webapp para operar programáticamente sobre proveedores, pedidos, cotizaciones, órdenes de compra y métricas.

Estas rutas viven bajo /api/procurement/* y actúan como capa BFF sobre el agregador de procurement (Strapi). Salvo las de sólo lectura (analytics, compare), las operaciones de escritura requieren permisos específicos del dominio (canManageRequests, canManageQuotes, canManageSuppliers, canManagePurchaseOrders, canAward).

The procurement flow

Loading diagram...

The SupplyRequest model

El pedido de insumos (RFQ, Request for Quotation) es el punto de partida del flujo. Agrupa una o más líneas de insumos que el club necesita comprar y controla su ciclo de vida hasta la adjudicación.

Properties

  • Name
    id
    Type
    string
    Description

    Identificador único del pedido.

  • Name
    code
    Type
    string
    Description

    Código legible del pedido (ej. RFQ-2025-0042).

  • Name
    status
    Type
    enum
    Description

    Estado del pedido. Puede ser uno de:

    • DRAFT: Borrador, aún no publicado
    • OPEN: Publicado, esperando cotizaciones
    • QUOTING: Recibiendo cotizaciones de proveedores
    • EVALUATING: Comparando y evaluando ofertas
    • AWARDED: Adjudicado a una cotización ganadora
    • CLOSED: Cerrado
    • CANCELLED: Cancelado
  • Name
    title
    Type
    string
    Description

    Título descriptivo del pedido.

  • Name
    lines
    Type
    SupplyLine[]
    Description

    Líneas de insumos solicitados.

    • Name
      id
      Type
      string
      Description

      Identificador de la línea.

    • Name
      category
      Type
      string
      Description

      Categoría del insumo (ej. Sustrato, Nutrientes).

    • Name
      description
      Type
      string
      Description

      Descripción del insumo.

    • Name
      quantity
      Type
      number
      Description

      Cantidad solicitada.

    • Name
      unit
      Type
      string
      Description

      Unidad de medida (ej. kg, L, unidad).

    • Name
      specs
      Type
      string
      Description

      Especificaciones técnicas.

    • Name
      targetUnitPrice
      Type
      number
      Description

      Precio unitario objetivo (referencia).

  • Name
    neededBy
    Type
    string
    Description

    Fecha límite en la que se necesita el insumo (ISO).

  • Name
    priority
    Type
    enum
    Description

    Prioridad del pedido. Puede ser uno de:

    • LOW
    • NORMAL
    • HIGH
    • URGENT
  • Name
    requesterUserId
    Type
    string
    Description

    ID del usuario que creó el pedido.

  • Name
    requesterName
    Type
    string
    Description

    Nombre del solicitante.

  • Name
    notes
    Type
    string
    Description

    Notas del pedido.

  • Name
    awardedQuoteId
    Type
    string
    Description

    ID de la cotización adjudicada (una vez adjudicado).

  • Name
    purchaseOrderId
    Type
    string
    Description

    ID de la orden de compra generada.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp de creación.

  • Name
    updatedAt
    Type
    string
    Description

    Timestamp de última actualización.

SupplyRequest example

{
  "id": "req_01HZR8XYZ0001",
  "code": "RFQ-2025-0042",
  "status": "QUOTING",
  "title": "Reposición sustrato y nutrientes floración",
  "lines": [
    {
      "id": "line_01",
      "category": "Sustrato",
      "description": "Sustrato coco-perlita premium",
      "quantity": 500,
      "unit": "L",
      "specs": "Mezcla 70/30, pH estabilizado",
      "targetUnitPrice": 1800
    },
    {
      "id": "line_02",
      "category": "Nutrientes",
      "description": "Nutriente base floración",
      "quantity": 40,
      "unit": "L"
    }
  ],
  "neededBy": "2025-10-15",
  "priority": "HIGH",
  "requesterUserId": "usr_01HQ8XYZABC123",
  "requesterName": "Lucía Fernández",
  "notes": "Coordinar entrega antes del cambio de ciclo",
  "createdAt": "2025-09-01T12:00:00Z",
  "updatedAt": "2025-09-05T09:30:00Z"
}

The SupplyQuote model

Una cotización representa la oferta de un proveedor para un pedido. Incluye el desglose por línea, descuentos por volumen y los totales calculados del lado del servidor.

Properties

  • Name
    id
    Type
    string
    Description

    Identificador único de la cotización.

  • Name
    supplyRequestId
    Type
    string
    Description

    ID del pedido (RFQ) al que pertenece.

  • Name
    supplierId
    Type
    string
    Description

    ID del proveedor que cotiza.

  • Name
    status
    Type
    enum
    Description

    Estado de la cotización. Puede ser uno de:

    • RECEIVED: Recibida
    • SHORTLISTED: Preseleccionada
    • SELECTED: Seleccionada / adjudicada
    • REJECTED: Rechazada
    • EXPIRED: Vencida
  • Name
    paymentTerm
    Type
    enum
    Description

    Condición de pago ofrecida. Puede ser una de:

    • CASH
    • NET_30
    • NET_60
    • NET_90
  • Name
    validUntil
    Type
    string
    Description

    Fecha de validez de la oferta (ISO).

  • Name
    receivedAt
    Type
    string
    Description

    Fecha en la que se recibió la cotización.

  • Name
    channel
    Type
    enum
    Description

    Canal por el que llegó la cotización. Puede ser uno de:

    • EMAIL
    • WHATSAPP
    • PHONE
    • IN_PERSON
    • PORTAL
    • OTHER
  • Name
    lines
    Type
    QuoteLine[]
    Description

    Desglose por línea con precios y descuentos aplicados.

    • Name
      id
      Type
      string
      Description

      ID de la línea (coincide con la línea del pedido).

    • Name
      category
      Type
      string
      Description

      Categoría del insumo.

    • Name
      description
      Type
      string
      Description

      Descripción del insumo.

    • Name
      quantity
      Type
      number
      Description

      Cantidad cotizada.

    • Name
      unit
      Type
      string
      Description

      Unidad de medida.

    • Name
      unitPrice
      Type
      number
      Description

      Precio unitario base cotizado.

    • Name
      unitPriceEffective
      Type
      number
      Description

      Precio unitario efectivo tras aplicar descuentos por volumen.

    • Name
      lineSubtotal
      Type
      number
      Description

      Subtotal de la línea (sin descuento).

    • Name
      lineDiscount
      Type
      number
      Description

      Descuento aplicado a la línea.

    • Name
      lineTotal
      Type
      number
      Description

      Total de la línea (con descuento).

  • Name
    volumeDiscounts
    Type
    VolumeDiscountsByLine[]
    Description

    Tramos de descuento por volumen, por línea.

    • Name
      lineId
      Type
      string
      Description

      ID de la línea a la que aplica.

    • Name
      tiers
      Type
      VolumeTier[]
      Description

      Tramos. Cada tramo define minQuantity y unitPrice XOR discountPercent.

  • Name
    subtotal
    Type
    number
    Description

    Subtotal de la cotización (suma de subtotales de línea).

  • Name
    discountTotal
    Type
    number
    Description

    Descuento total aplicado.

  • Name
    total
    Type
    number
    Description

    Total final de la cotización.

  • Name
    currency
    Type
    enum
    Description

    Moneda. Puede ser una de: ARS, USD, EUR.

  • Name
    notes
    Type
    string
    Description

    Notas de la cotización.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp de creación.

  • Name
    updatedAt
    Type
    string
    Description

    Timestamp de última actualización.

SupplyQuote example

{
  "id": "quo_01HZR8ABC0001",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "supplierId": "sup_01HZR8SUP0001",
  "status": "SHORTLISTED",
  "paymentTerm": "NET_30",
  "validUntil": "2025-09-30",
  "receivedAt": "2025-09-04T15:20:00Z",
  "channel": "WHATSAPP",
  "lines": [
    {
      "id": "line_01",
      "category": "Sustrato",
      "description": "Sustrato coco-perlita premium",
      "quantity": 500,
      "unit": "L",
      "unitPrice": 1750,
      "unitPriceEffective": 1650,
      "lineSubtotal": 875000,
      "lineDiscount": 50000,
      "lineTotal": 825000
    }
  ],
  "volumeDiscounts": [
    {
      "lineId": "line_01",
      "tiers": [
        { "minQuantity": 300, "discountPercent": 5 }
      ]
    }
  ],
  "subtotal": 875000,
  "discountTotal": 50000,
  "total": 825000,
  "currency": "ARS",
  "notes": "Incluye flete a CABA",
  "createdAt": "2025-09-04T15:20:00Z",
  "updatedAt": "2025-09-05T10:00:00Z"
}

The PurchaseOrder model

La orden de compra se genera al adjudicar (seleccionar) una cotización. Es un registro de sólo-lectura en cuanto a su creación (no hay endpoint de creación directo) y luego se actualiza su recepción y estado de pago.

Properties

  • Name
    id
    Type
    string
    Description

    Identificador único de la orden de compra.

  • Name
    code
    Type
    string
    Description

    Código legible (ej. PO-2025-0031).

  • Name
    supplyRequestId
    Type
    string
    Description

    ID del pedido de origen.

  • Name
    winningQuoteId
    Type
    string
    Description

    ID de la cotización ganadora.

  • Name
    supplierId
    Type
    string
    Description

    ID del proveedor adjudicado.

  • Name
    status
    Type
    enum
    Description

    Estado de la orden. Puede ser uno de:

    • ORDERED: Ordenada, pendiente de recepción
    • PARTIAL_RECEIVED: Recepción parcial
    • RECEIVED: Recibida por completo
    • CANCELLED: Cancelada
  • Name
    paymentTerm
    Type
    enum
    Description

    Condición de pago (CASH, NET_30, NET_60, NET_90).

  • Name
    paymentStatus
    Type
    enum
    Description

    Estado del pago. Puede ser uno de:

    • PENDING
    • PARTIAL
    • PAID
    • OVERDUE
  • Name
    orderedAt
    Type
    string
    Description

    Fecha en que se emitió la orden.

  • Name
    expectedDelivery
    Type
    string
    Description

    Fecha estimada de entrega.

  • Name
    dueDate
    Type
    string
    Description

    Fecha de vencimiento del pago (según condición).

  • Name
    lines
    Type
    QuoteLine[]
    Description

    Líneas heredadas de la cotización ganadora, con su desglose de precios.

  • Name
    subtotal
    Type
    number
    Description

    Subtotal de la orden.

  • Name
    discountTotal
    Type
    number
    Description

    Descuento total.

  • Name
    total
    Type
    number
    Description

    Total de la orden.

  • Name
    currency
    Type
    enum
    Description

    Moneda (ARS, USD, EUR).

  • Name
    orderedById
    Type
    string
    Description

    ID del usuario que emitió la orden.

  • Name
    orderedByName
    Type
    string
    Description

    Nombre del usuario que emitió la orden.

  • Name
    notes
    Type
    string
    Description

    Notas de la orden.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp de creación.

  • Name
    updatedAt
    Type
    string
    Description

    Timestamp de última actualización.

PurchaseOrder example

{
  "id": "po_01HZR8PO00001",
  "code": "PO-2025-0031",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "winningQuoteId": "quo_01HZR8ABC0001",
  "supplierId": "sup_01HZR8SUP0001",
  "status": "ORDERED",
  "paymentTerm": "NET_30",
  "paymentStatus": "PENDING",
  "orderedAt": "2025-09-06T11:00:00Z",
  "expectedDelivery": "2025-09-12",
  "dueDate": "2025-10-06",
  "lines": [
    {
      "id": "line_01",
      "category": "Sustrato",
      "description": "Sustrato coco-perlita premium",
      "quantity": 500,
      "unit": "L",
      "unitPrice": 1750,
      "unitPriceEffective": 1650,
      "lineSubtotal": 875000,
      "lineDiscount": 50000,
      "lineTotal": 825000
    }
  ],
  "subtotal": 875000,
  "discountTotal": 50000,
  "total": 825000,
  "currency": "ARS",
  "orderedById": "usr_01HQ8XYZABC123",
  "orderedByName": "Lucía Fernández",
  "notes": "Retira depósito Avellaneda",
  "createdAt": "2025-09-06T11:00:00Z",
  "updatedAt": "2025-09-06T11:00:00Z"
}

The WholesaleSupplier model

El proveedor mayorista al que se le solicitan cotizaciones. Incluye datos de contacto, CUIT, categorías que abastece y condiciones de pago habituales.

Properties

  • Name
    id
    Type
    string
    Description

    Identificador único del proveedor.

  • Name
    name
    Type
    string
    Description

    Razón social o nombre del proveedor.

  • Name
    contactName
    Type
    string
    Description

    Nombre de la persona de contacto.

  • Name
    email
    Type
    string
    Description

    Email de contacto.

  • Name
    phone
    Type
    string
    Description

    Teléfono de contacto.

  • Name
    cuit
    Type
    string
    Description

    CUIT (identificador fiscal argentino).

  • Name
    categories
    Type
    string[]
    Description

    Categorías de insumos que abastece.

  • Name
    defaultPaymentTerms
    Type
    enum[]
    Description

    Condiciones de pago que el proveedor suele ofrecer (CASH, NET_30, NET_60, NET_90).

  • Name
    address
    Type
    string
    Description

    Dirección del proveedor.

  • Name
    notes
    Type
    string
    Description

    Notas internas.

  • Name
    isActive
    Type
    boolean
    Description

    Si el proveedor está activo.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp de creación.

  • Name
    updatedAt
    Type
    string
    Description

    Timestamp de última actualización.

WholesaleSupplier example

{
  "id": "sup_01HZR8SUP0001",
  "name": "Insumos del Sur S.A.",
  "contactName": "Roberto Álvarez",
  "email": "ventas@insumosdelsur.com.ar",
  "phone": "+541148887766",
  "cuit": "30-71234567-9",
  "categories": ["Sustrato", "Nutrientes", "Macetas"],
  "defaultPaymentTerms": ["CASH", "NET_30"],
  "address": "Parque Industrial Avellaneda, Buenos Aires",
  "notes": "Descuento por volumen a partir de 300 L",
  "isActive": true,
  "createdAt": "2025-01-10T09:00:00Z",
  "updatedAt": "2025-08-20T14:00:00Z"
}

GET/api/procurement/requests

Listar pedidos (RFQ)

Retorna la lista de pedidos de insumos del club. Soporta filtro por estado y paginación.

Query params

  • Name
    status
    Type
    SupplyRequestStatus
    Description

    Filtra por estado (DRAFT, OPEN, QUOTING, EVALUATING, AWARDED, CLOSED, CANCELLED).

  • Name
    page
    Type
    integer
    Description

    Número de página.

  • Name
    limit
    Type
    integer
    Description

    Cantidad de resultados por página.

Request

GET
/api/procurement/requests
curl -G https://api.cannahub.tech/api/procurement/requests \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -d status=QUOTING \
  -d limit=10

Response

{
  "requests": [
    {
      "id": "req_01HZR8XYZ0001",
      "code": "RFQ-2025-0042",
      "status": "QUOTING",
      "title": "Reposición sustrato y nutrientes floración",
      "priority": "HIGH",
      "neededBy": "2025-10-15",
      "lines": [
        { "id": "line_01", "category": "Sustrato", "description": "Sustrato coco-perlita premium", "quantity": 500, "unit": "L" }
      ],
      "createdAt": "2025-09-01T12:00:00Z"
    }
  ],
  "count": 1,
  "page": 1,
  "limit": 10
}

POST/api/procurement/requests

Crear un pedido (RFQ)

Crea un nuevo pedido de insumos. Requiere el permiso canManageRequests. Se puede guardar como borrador (DRAFT) o publicar directamente (OPEN).

Atributos requeridos

  • Name
    title
    Type
    string
    Description

    Título del pedido.

  • Name
    lines
    Type
    SupplyLine[]
    Description

    Al menos una línea. Cada línea requiere category, description, quantity (> 0) y unit. Opcionalmente specs y targetUnitPrice.

Atributos opcionales

  • Name
    neededBy
    Type
    string
    Description

    Fecha límite de necesidad.

  • Name
    priority
    Type
    enum
    Description

    Prioridad (LOW, NORMAL, HIGH, URGENT).

  • Name
    notes
    Type
    string
    Description

    Notas del pedido.

  • Name
    status
    Type
    enum
    Description

    DRAFT (borrador) u OPEN (publicar).

Request

POST
/api/procurement/requests
curl -X POST https://api.cannahub.tech/api/procurement/requests \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Reposición sustrato y nutrientes floración",
    "priority": "HIGH",
    "status": "OPEN",
    "lines": [
      { "category": "Sustrato", "description": "Sustrato coco-perlita premium", "quantity": 500, "unit": "L", "targetUnitPrice": 1800 }
    ]
  }'

Response (201)

{
  "id": "req_01HZR8XYZ0001",
  "code": "RFQ-2025-0042",
  "status": "OPEN",
  "title": "Reposición sustrato y nutrientes floración",
  "priority": "HIGH",
  "lines": [
    { "id": "line_01", "category": "Sustrato", "description": "Sustrato coco-perlita premium", "quantity": 500, "unit": "L", "targetUnitPrice": 1800 }
  ],
  "createdAt": "2025-09-01T12:00:00Z"
}

GET/api/procurement/requests/:id

Detalle de un pedido

Retorna el detalle completo de un pedido: el pedido en sí, sus cotizaciones y el timeline de eventos, en una sola llamada.

Response

  • Name
    request
    Type
    SupplyRequest
    Description

    El pedido.

  • Name
    quotes
    Type
    SupplyQuote[]
    Description

    Cotizaciones asociadas al pedido.

  • Name
    events
    Type
    SupplyEvent[]
    Description

    Timeline de eventos del pedido.

Request

GET
/api/procurement/requests/:id
curl https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "request": {
    "id": "req_01HZR8XYZ0001",
    "code": "RFQ-2025-0042",
    "status": "QUOTING",
    "title": "Reposición sustrato y nutrientes floración",
    "priority": "HIGH",
    "lines": [
      { "id": "line_01", "category": "Sustrato", "description": "Sustrato coco-perlita premium", "quantity": 500, "unit": "L" }
    ]
  },
  "quotes": [
    { "id": "quo_01HZR8ABC0001", "supplierId": "sup_01HZR8SUP0001", "status": "SHORTLISTED", "total": 825000, "currency": "ARS" }
  ],
  "events": [
    { "id": "evt_01", "type": "REQUEST_CREATED", "label": "Pedido creado", "createdAt": "2025-09-01T12:00:00Z" }
  ]
}

PATCH/api/procurement/requests/:id

Actualizar un pedido

Actualiza los datos editables de un pedido. Requiere el permiso canManageRequests. Todos los campos son opcionales.

Atributos opcionales

  • Name
    title
    Type
    string
    Description

    Título del pedido.

  • Name
    lines
    Type
    SupplyLine[]
    Description

    Líneas de insumos (al menos una si se envía).

  • Name
    neededBy
    Type
    string
    Description

    Fecha límite de necesidad.

  • Name
    priority
    Type
    enum
    Description

    Prioridad (LOW, NORMAL, HIGH, URGENT).

  • Name
    notes
    Type
    string
    Description

    Notas del pedido.

Request

PATCH
/api/procurement/requests/:id
curl -X PATCH https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "priority": "URGENT", "neededBy": "2025-10-01" }'

Response

{
  "id": "req_01HZR8XYZ0001",
  "code": "RFQ-2025-0042",
  "status": "QUOTING",
  "title": "Reposición sustrato y nutrientes floración",
  "priority": "URGENT",
  "neededBy": "2025-10-01",
  "updatedAt": "2025-09-07T08:15:00Z"
}

DELETE/api/procurement/requests/:id

Eliminar un pedido

Elimina un pedido. Requiere el permiso canManageRequests.

Request

DELETE
/api/procurement/requests/:id
curl -X DELETE https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "success": true
}

PATCH/api/procurement/requests/:id/status

Cambiar estado de un pedido

Transiciona el estado de un pedido a lo largo de su máquina de estados. Requiere el permiso canManageRequests. Una transición inválida devuelve 409 con el hint validTransitions.

Atributos requeridos

  • Name
    status
    Type
    SupplyRequestStatus
    Description

    Nuevo estado (DRAFT, OPEN, QUOTING, EVALUATING, AWARDED, CLOSED, CANCELLED).

Atributos opcionales

  • Name
    supplierId
    Type
    string
    Description

    Proveedor contactado (para transiciones que registran contacto).

  • Name
    channel
    Type
    enum
    Description

    Canal de contacto (EMAIL, WHATSAPP, PHONE, IN_PERSON, PORTAL, OTHER).

  • Name
    performedById
    Type
    string
    Description

    ID del usuario que realiza la acción.

  • Name
    performedByName
    Type
    string
    Description

    Nombre del usuario que realiza la acción.

Request

PATCH
/api/procurement/requests/:id/status
curl -X PATCH https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/status \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "status": "EVALUATING", "performedByName": "Lucía Fernández" }'

Response

{
  "id": "req_01HZR8XYZ0001",
  "code": "RFQ-2025-0042",
  "status": "EVALUATING",
  "updatedAt": "2025-09-07T10:00:00Z"
}

GET/api/procurement/requests/:id/quotes

Listar cotizaciones de un pedido

Retorna las cotizaciones recibidas para un pedido.

Request

GET
/api/procurement/requests/:id/quotes
curl https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/quotes \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

[
  {
    "id": "quo_01HZR8ABC0001",
    "supplyRequestId": "req_01HZR8XYZ0001",
    "supplierId": "sup_01HZR8SUP0001",
    "status": "SHORTLISTED",
    "paymentTerm": "NET_30",
    "total": 825000,
    "currency": "ARS"
  }
]

POST/api/procurement/requests/:id/quotes

Agregar una cotización

Registra la cotización de un proveedor para un pedido. Requiere el permiso canManageQuotes. Los totales y el desglose de líneas se calculan del lado del servidor a partir de los precios y descuentos por volumen enviados.

Atributos requeridos

  • Name
    supplierId
    Type
    string
    Description

    ID del proveedor que cotiza.

  • Name
    paymentTerm
    Type
    enum
    Description

    Condición de pago (CASH, NET_30, NET_60, NET_90).

  • Name
    lines
    Type
    QuoteLinePrice[]
    Description

    Precios por línea. Cada entrada requiere lineId y unitPrice (≥ 0).

Atributos opcionales

  • Name
    volumeDiscounts
    Type
    VolumeDiscountsByLine[]
    Description

    Tramos de descuento por volumen por línea. Cada tramo define minQuantity y unitPrice XOR discountPercent.

  • Name
    validUntil
    Type
    string
    Description

    Fecha de validez de la oferta.

  • Name
    receivedAt
    Type
    string
    Description

    Fecha de recepción de la cotización.

  • Name
    channel
    Type
    enum
    Description

    Canal (EMAIL, WHATSAPP, PHONE, IN_PERSON, PORTAL, OTHER).

  • Name
    currency
    Type
    enum
    Description

    Moneda (ARS, USD, EUR).

  • Name
    notes
    Type
    string
    Description

    Notas.

Request

POST
/api/procurement/requests/:id/quotes
curl -X POST https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/quotes \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{
    "supplierId": "sup_01HZR8SUP0001",
    "paymentTerm": "NET_30",
    "currency": "ARS",
    "channel": "WHATSAPP",
    "lines": [ { "lineId": "line_01", "unitPrice": 1750 } ],
    "volumeDiscounts": [
      { "lineId": "line_01", "tiers": [ { "minQuantity": 300, "discountPercent": 5 } ] }
    ]
  }'

Response (201)

{
  "id": "quo_01HZR8ABC0001",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "supplierId": "sup_01HZR8SUP0001",
  "status": "RECEIVED",
  "paymentTerm": "NET_30",
  "channel": "WHATSAPP",
  "lines": [
    {
      "id": "line_01",
      "category": "Sustrato",
      "quantity": 500,
      "unit": "L",
      "unitPrice": 1750,
      "unitPriceEffective": 1662,
      "lineSubtotal": 875000,
      "lineDiscount": 43750,
      "lineTotal": 831250
    }
  ],
  "subtotal": 875000,
  "discountTotal": 43750,
  "total": 831250,
  "currency": "ARS"
}

GET/api/procurement/requests/:id/compare

Comparar cotizaciones

Retorna una comparación lado a lado de todas las cotizaciones de un pedido, con desglose por línea, totales y la fecha de vencimiento de pago si se ordena hoy. Ruta de sólo lectura (no requiere permiso especial más allá de la autenticación).

Response

  • Name
    requestId
    Type
    string
    Description

    ID del pedido comparado.

  • Name
    lines
    Type
    SupplyLine[]
    Description

    Líneas del pedido (base de comparación).

  • Name
    comparisons
    Type
    QuoteComparison[]
    Description

    Una entrada por cotización.

    • Name
      quoteId
      Type
      string
      Description

      ID de la cotización.

    • Name
      supplierId
      Type
      string
      Description

      ID del proveedor.

    • Name
      supplierName
      Type
      string
      Description

      Nombre del proveedor.

    • Name
      paymentTerm
      Type
      enum
      Description

      Condición de pago.

    • Name
      status
      Type
      enum
      Description

      Estado de la cotización.

    • Name
      lineBreakdown
      Type
      QuoteLine[]
      Description

      Desglose por línea.

    • Name
      subtotal
      Type
      number
      Description

      Subtotal.

    • Name
      discountTotal
      Type
      number
      Description

      Descuento total.

    • Name
      total
      Type
      number
      Description

      Total.

    • Name
      dueDateIfOrderedToday
      Type
      string
      Description

      Fecha de vencimiento de pago si se ordenara hoy.

    • Name
      validUntil
      Type
      string
      Description

      Validez de la oferta.

Request

GET
/api/procurement/requests/:id/compare
curl https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/compare \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "requestId": "req_01HZR8XYZ0001",
  "lines": [
    { "id": "line_01", "category": "Sustrato", "description": "Sustrato coco-perlita premium", "quantity": 500, "unit": "L" }
  ],
  "comparisons": [
    {
      "quoteId": "quo_01HZR8ABC0001",
      "supplierId": "sup_01HZR8SUP0001",
      "supplierName": "Insumos del Sur S.A.",
      "paymentTerm": "NET_30",
      "status": "SHORTLISTED",
      "lineBreakdown": [
        { "id": "line_01", "quantity": 500, "unitPrice": 1750, "unitPriceEffective": 1662, "lineTotal": 831250 }
      ],
      "subtotal": 875000,
      "discountTotal": 43750,
      "total": 831250,
      "dueDateIfOrderedToday": "2025-10-07",
      "validUntil": "2025-09-30"
    }
  ]
}

GET/api/procurement/requests/:id/events

Timeline de eventos de un pedido

Retorna el timeline de eventos de un pedido (auditoría del ciclo de vida).

El modelo SupplyEvent

  • Name
    id
    Type
    string
    Description

    ID del evento.

  • Name
    supplyRequestId
    Type
    string
    Description

    Pedido al que pertenece.

  • Name
    type
    Type
    enum
    Description

    Tipo de evento. Puede ser uno de:

    • REQUEST_CREATED
    • REQUEST_STATUS_CHANGED
    • SUPPLIER_CONTACTED
    • QUOTE_RECEIVED
    • QUOTE_SHORTLISTED
    • QUOTE_SELECTED
    • QUOTE_REJECTED
    • PO_CREATED
    • PO_STATUS_CHANGED
    • RECEIPT_RECORDED
    • NOTE_ADDED
  • Name
    label
    Type
    string
    Description

    Etiqueta legible del evento.

  • Name
    detail
    Type
    string
    Description

    Detalle del evento.

  • Name
    metadata
    Type
    object
    Description

    Metadata adicional.

  • Name
    performedById
    Type
    string
    Description

    ID del usuario que lo generó.

  • Name
    performedByName
    Type
    string
    Description

    Nombre del usuario que lo generó.

  • Name
    createdAt
    Type
    string
    Description

    Timestamp del evento.

Request

GET
/api/procurement/requests/:id/events
curl https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/events \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

[
  {
    "id": "evt_01",
    "supplyRequestId": "req_01HZR8XYZ0001",
    "type": "REQUEST_CREATED",
    "label": "Pedido creado",
    "performedByName": "Lucía Fernández",
    "createdAt": "2025-09-01T12:00:00Z"
  },
  {
    "id": "evt_02",
    "supplyRequestId": "req_01HZR8XYZ0001",
    "type": "QUOTE_RECEIVED",
    "label": "Cotización recibida de Insumos del Sur S.A.",
    "createdAt": "2025-09-04T15:20:00Z"
  }
]

POST/api/procurement/requests/:id/events

Agregar una nota al pedido

Agrega una nota manual (evento NOTE_ADDED) al timeline de un pedido. Requiere el permiso canManageRequests.

Atributos requeridos

  • Name
    detail
    Type
    string
    Description

    Texto de la nota (no puede estar vacío).

Atributos opcionales

  • Name
    performedById
    Type
    string
    Description

    ID del usuario que agrega la nota.

  • Name
    performedByName
    Type
    string
    Description

    Nombre del usuario que agrega la nota.

Request

POST
/api/procurement/requests/:id/events
curl -X POST https://api.cannahub.tech/api/procurement/requests/req_01HZR8XYZ0001/events \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "detail": "Proveedor confirma stock, entrega en 5 días", "performedByName": "Lucía Fernández" }'

Response (201)

{
  "id": "evt_03",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "type": "NOTE_ADDED",
  "label": "Nota agregada",
  "detail": "Proveedor confirma stock, entrega en 5 días",
  "performedByName": "Lucía Fernández",
  "createdAt": "2025-09-05T16:00:00Z"
}

GET/api/procurement/quotes/:id

Detalle de una cotización

Retorna una cotización individual con su desglose completo.

Request

GET
/api/procurement/quotes/:id
curl https://api.cannahub.tech/api/procurement/quotes/quo_01HZR8ABC0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "id": "quo_01HZR8ABC0001",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "supplierId": "sup_01HZR8SUP0001",
  "status": "SHORTLISTED",
  "paymentTerm": "NET_30",
  "validUntil": "2025-09-30",
  "channel": "WHATSAPP",
  "lines": [
    { "id": "line_01", "quantity": 500, "unit": "L", "unitPrice": 1750, "unitPriceEffective": 1662, "lineTotal": 831250 }
  ],
  "subtotal": 875000,
  "discountTotal": 43750,
  "total": 831250,
  "currency": "ARS"
}

PATCH/api/procurement/quotes/:id

Actualizar una cotización

Actualiza los datos editables de una cotización. Requiere el permiso canManageQuotes. Todos los campos son opcionales.

Atributos opcionales

  • Name
    paymentTerm
    Type
    enum
    Description

    Condición de pago (CASH, NET_30, NET_60, NET_90).

  • Name
    lines
    Type
    array
    Description

    Líneas con unitPrice (recalcula totales del lado del servidor).

  • Name
    volumeDiscounts
    Type
    VolumeDiscountsByLine[]
    Description

    Tramos de descuento por volumen.

  • Name
    validUntil
    Type
    string
    Description

    Validez de la oferta.

  • Name
    channel
    Type
    enum
    Description

    Canal (EMAIL, WHATSAPP, PHONE, IN_PERSON, PORTAL, OTHER).

  • Name
    currency
    Type
    enum
    Description

    Moneda (ARS, USD, EUR).

  • Name
    notes
    Type
    string
    Description

    Notas.

Request

PATCH
/api/procurement/quotes/:id
curl -X PATCH https://api.cannahub.tech/api/procurement/quotes/quo_01HZR8ABC0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "paymentTerm": "NET_60", "validUntil": "2025-10-15" }'

Response

{
  "id": "quo_01HZR8ABC0001",
  "paymentTerm": "NET_60",
  "validUntil": "2025-10-15",
  "total": 831250,
  "currency": "ARS",
  "updatedAt": "2025-09-06T09:00:00Z"
}

DELETE/api/procurement/quotes/:id

Eliminar una cotización

Elimina una cotización. Requiere el permiso canManageQuotes.

Request

DELETE
/api/procurement/quotes/:id
curl -X DELETE https://api.cannahub.tech/api/procurement/quotes/quo_01HZR8ABC0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "success": true
}

PATCH/api/procurement/quotes/:id/status

Cambiar estado de una cotización

Cambia el estado de una cotización (preseleccionar / rechazar / etc.). Requiere el permiso canManageQuotes.

Atributos requeridos

  • Name
    status
    Type
    SupplyQuoteStatus
    Description

    Nuevo estado (RECEIVED, SHORTLISTED, SELECTED, REJECTED, EXPIRED).

Atributos opcionales

  • Name
    reason
    Type
    string
    Description

    Motivo del cambio (ej. rechazo).

  • Name
    performedById
    Type
    string
    Description

    ID del usuario que realiza la acción.

  • Name
    performedByName
    Type
    string
    Description

    Nombre del usuario que realiza la acción.

Request

PATCH
/api/procurement/quotes/:id/status
curl -X PATCH https://api.cannahub.tech/api/procurement/quotes/quo_01HZR8ABC0001/status \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "status": "SHORTLISTED", "performedByName": "Lucía Fernández" }'

Response

{
  "id": "quo_01HZR8ABC0001",
  "status": "SHORTLISTED",
  "updatedAt": "2025-09-05T12:00:00Z"
}

POST/api/procurement/quotes/:id/select

Adjudicar una cotización

Adjudica (selecciona) una cotización como ganadora. Esta acción genera una orden de compra (PurchaseOrder) a partir de la cotización. Requiere el permiso canAward.

Atributos opcionales

  • Name
    orderedAt
    Type
    string
    Description

    Fecha de emisión de la orden.

  • Name
    expectedDelivery
    Type
    string
    Description

    Fecha estimada de entrega.

  • Name
    orderedById
    Type
    string
    Description

    ID del usuario que adjudica.

  • Name
    orderedByName
    Type
    string
    Description

    Nombre del usuario que adjudica.

  • Name
    notes
    Type
    string
    Description

    Notas para la orden.

Response

  • Name
    purchaseOrder
    Type
    PurchaseOrder
    Description

    La orden de compra generada.

  • Name
    quote
    Type
    SupplyQuote
    Description

    La cotización adjudicada (status SELECTED).

Request

POST
/api/procurement/quotes/:id/select
curl -X POST https://api.cannahub.tech/api/procurement/quotes/quo_01HZR8ABC0001/select \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "expectedDelivery": "2025-09-12", "orderedByName": "Lucía Fernández" }'

Response (201)

{
  "purchaseOrder": {
    "id": "po_01HZR8PO00001",
    "code": "PO-2025-0031",
    "supplyRequestId": "req_01HZR8XYZ0001",
    "winningQuoteId": "quo_01HZR8ABC0001",
    "supplierId": "sup_01HZR8SUP0001",
    "status": "ORDERED",
    "paymentTerm": "NET_30",
    "paymentStatus": "PENDING",
    "expectedDelivery": "2025-09-12",
    "total": 831250,
    "currency": "ARS"
  },
  "quote": {
    "id": "quo_01HZR8ABC0001",
    "status": "SELECTED"
  }
}

GET/api/procurement/purchase-orders

Listar órdenes de compra

Retorna las órdenes de compra del club. No existe endpoint de creación directa: las órdenes se generan al adjudicar una cotización. Soporta filtro por estado y paginación.

Query params

  • Name
    status
    Type
    PurchaseOrderStatus
    Description

    Filtra por estado (ORDERED, PARTIAL_RECEIVED, RECEIVED, CANCELLED).

  • Name
    page
    Type
    integer
    Description

    Número de página.

  • Name
    limit
    Type
    integer
    Description

    Resultados por página.

Request

GET
/api/procurement/purchase-orders
curl -G https://api.cannahub.tech/api/procurement/purchase-orders \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -d status=ORDERED

Response

{
  "purchaseOrders": [
    {
      "id": "po_01HZR8PO00001",
      "code": "PO-2025-0031",
      "supplierId": "sup_01HZR8SUP0001",
      "status": "ORDERED",
      "paymentStatus": "PENDING",
      "total": 831250,
      "currency": "ARS",
      "expectedDelivery": "2025-09-12"
    }
  ],
  "count": 1,
  "page": 1,
  "limit": 20
}

GET/api/procurement/purchase-orders/:id

Detalle de una orden de compra

Retorna una orden de compra individual con su desglose completo.

Request

GET
/api/procurement/purchase-orders/:id
curl https://api.cannahub.tech/api/procurement/purchase-orders/po_01HZR8PO00001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "id": "po_01HZR8PO00001",
  "code": "PO-2025-0031",
  "supplyRequestId": "req_01HZR8XYZ0001",
  "winningQuoteId": "quo_01HZR8ABC0001",
  "supplierId": "sup_01HZR8SUP0001",
  "status": "ORDERED",
  "paymentTerm": "NET_30",
  "paymentStatus": "PENDING",
  "orderedAt": "2025-09-06T11:00:00Z",
  "expectedDelivery": "2025-09-12",
  "dueDate": "2025-10-06",
  "lines": [
    { "id": "line_01", "category": "Sustrato", "quantity": 500, "unit": "L", "unitPrice": 1750, "lineTotal": 831250 }
  ],
  "subtotal": 875000,
  "discountTotal": 43750,
  "total": 831250,
  "currency": "ARS"
}

PATCH/api/procurement/purchase-orders/:id

Actualizar una orden de compra

Actualiza la entrega estimada, notas o el estado de pago de una orden. Requiere el permiso canManagePurchaseOrders. Todos los campos son opcionales.

Atributos opcionales

  • Name
    expectedDelivery
    Type
    string
    Description

    Nueva fecha estimada de entrega.

  • Name
    notes
    Type
    string
    Description

    Notas de la orden.

  • Name
    paymentStatus
    Type
    enum
    Description

    Estado del pago (PENDING, PARTIAL, PAID, OVERDUE).

Request

PATCH
/api/procurement/purchase-orders/:id
curl -X PATCH https://api.cannahub.tech/api/procurement/purchase-orders/po_01HZR8PO00001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "paymentStatus": "PAID", "notes": "Pagado por transferencia" }'

Response

{
  "id": "po_01HZR8PO00001",
  "code": "PO-2025-0031",
  "status": "ORDERED",
  "paymentStatus": "PAID",
  "notes": "Pagado por transferencia",
  "updatedAt": "2025-09-08T10:00:00Z"
}

PATCH/api/procurement/purchase-orders/:id/status

Cambiar estado de una orden de compra

Registra la recepción (parcial o total) o cancela una orden de compra. Requiere el permiso canManagePurchaseOrders. Una transición inválida devuelve 409 con el hint validTransitions.

Atributos requeridos

  • Name
    status
    Type
    PurchaseOrderStatus
    Description

    Nuevo estado (ORDERED, PARTIAL_RECEIVED, RECEIVED, CANCELLED).

Atributos opcionales

  • Name
    performedById
    Type
    string
    Description

    ID del usuario que realiza la acción.

  • Name
    performedByName
    Type
    string
    Description

    Nombre del usuario que realiza la acción.

Request

PATCH
/api/procurement/purchase-orders/:id/status
curl -X PATCH https://api.cannahub.tech/api/procurement/purchase-orders/po_01HZR8PO00001/status \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "status": "RECEIVED", "performedByName": "Lucía Fernández" }'

Response

{
  "id": "po_01HZR8PO00001",
  "code": "PO-2025-0031",
  "status": "RECEIVED",
  "updatedAt": "2025-09-12T13:00:00Z"
}

GET/api/procurement/suppliers

Listar proveedores

Retorna la lista de proveedores mayoristas. Soporta búsqueda por texto, filtro por categoría y por estado activo, más paginación.

Query params

  • Name
    q
    Type
    string
    Description

    Búsqueda por texto (alias: search).

  • Name
    category
    Type
    string
    Description

    Filtra por categoría abastecida.

  • Name
    isActive
    Type
    boolean
    Description

    Filtra por estado activo (true / false).

  • Name
    page
    Type
    integer
    Description

    Número de página.

  • Name
    limit
    Type
    integer
    Description

    Resultados por página.

Request

GET
/api/procurement/suppliers
curl -G https://api.cannahub.tech/api/procurement/suppliers \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -d q=insumos \
  -d isActive=true

Response

{
  "suppliers": [
    {
      "id": "sup_01HZR8SUP0001",
      "name": "Insumos del Sur S.A.",
      "contactName": "Roberto Álvarez",
      "cuit": "30-71234567-9",
      "categories": ["Sustrato", "Nutrientes"],
      "defaultPaymentTerms": ["CASH", "NET_30"],
      "isActive": true
    }
  ],
  "count": 1,
  "page": 1,
  "limit": 20
}

POST/api/procurement/suppliers

Crear un proveedor

Crea un nuevo proveedor mayorista. Requiere el permiso canManageSuppliers.

Atributos requeridos

  • Name
    name
    Type
    string
    Description

    Razón social o nombre del proveedor.

Atributos opcionales

  • Name
    contactName
    Type
    string
    Description

    Nombre de contacto.

  • Name
    email
    Type
    string
    Description

    Email de contacto.

  • Name
    phone
    Type
    string
    Description

    Teléfono de contacto.

  • Name
    cuit
    Type
    string
    Description

    CUIT del proveedor.

  • Name
    categories
    Type
    string[]
    Description

    Categorías que abastece.

  • Name
    defaultPaymentTerms
    Type
    enum[]
    Description

    Condiciones de pago habituales (CASH, NET_30, NET_60, NET_90).

  • Name
    address
    Type
    string
    Description

    Dirección.

  • Name
    notes
    Type
    string
    Description

    Notas internas.

  • Name
    isActive
    Type
    boolean
    Description

    Si el proveedor está activo.

Request

POST
/api/procurement/suppliers
curl -X POST https://api.cannahub.tech/api/procurement/suppliers \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Insumos del Sur S.A.",
    "contactName": "Roberto Álvarez",
    "email": "ventas@insumosdelsur.com.ar",
    "cuit": "30-71234567-9",
    "categories": ["Sustrato", "Nutrientes"],
    "defaultPaymentTerms": ["CASH", "NET_30"]
  }'

Response (201)

{
  "id": "sup_01HZR8SUP0001",
  "name": "Insumos del Sur S.A.",
  "contactName": "Roberto Álvarez",
  "email": "ventas@insumosdelsur.com.ar",
  "cuit": "30-71234567-9",
  "categories": ["Sustrato", "Nutrientes"],
  "defaultPaymentTerms": ["CASH", "NET_30"],
  "isActive": true,
  "createdAt": "2025-09-01T09:00:00Z"
}

GET/api/procurement/suppliers/:id

Detalle de un proveedor

Retorna un proveedor individual.

Request

GET
/api/procurement/suppliers/:id
curl https://api.cannahub.tech/api/procurement/suppliers/sup_01HZR8SUP0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "id": "sup_01HZR8SUP0001",
  "name": "Insumos del Sur S.A.",
  "contactName": "Roberto Álvarez",
  "email": "ventas@insumosdelsur.com.ar",
  "phone": "+541148887766",
  "cuit": "30-71234567-9",
  "categories": ["Sustrato", "Nutrientes", "Macetas"],
  "defaultPaymentTerms": ["CASH", "NET_30"],
  "address": "Parque Industrial Avellaneda, Buenos Aires",
  "isActive": true,
  "createdAt": "2025-01-10T09:00:00Z",
  "updatedAt": "2025-08-20T14:00:00Z"
}

PATCH/api/procurement/suppliers/:id

Actualizar un proveedor

Actualiza los datos de un proveedor. Requiere el permiso canManageSuppliers. Todos los campos son opcionales (mismos que en la creación).

Request

PATCH
/api/procurement/suppliers/:id
curl -X PATCH https://api.cannahub.tech/api/procurement/suppliers/sup_01HZR8SUP0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+541148887799", "isActive": true }'

Response

{
  "id": "sup_01HZR8SUP0001",
  "name": "Insumos del Sur S.A.",
  "phone": "+541148887799",
  "isActive": true,
  "updatedAt": "2025-09-08T11:00:00Z"
}

DELETE/api/procurement/suppliers/:id

Eliminar un proveedor

Elimina un proveedor. Por defecto realiza una baja lógica (soft delete, permiso canManageSuppliers). Con el query param hard=true realiza una baja física, que requiere el permiso canDeleteSupplier.

Query params

  • Name
    hard
    Type
    boolean
    Description

    Si es true, elimina definitivamente el proveedor (requiere canDeleteSupplier).

Request

DELETE
/api/procurement/suppliers/:id
# Baja lógica (soft delete)
curl -X DELETE https://api.cannahub.tech/api/procurement/suppliers/sup_01HZR8SUP0001 \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

# Baja física (hard delete)
curl -X DELETE "https://api.cannahub.tech/api/procurement/suppliers/sup_01HZR8SUP0001?hard=true" \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}"

Response

{
  "success": true
}

GET/api/procurement/analytics/*

Analytics de compras

Conjunto de endpoints de sólo lectura que agregan métricas del dominio de compras. Todos requieren Authorization (y Tenant-id en entornos no mockeados), pero no un permiso de escritura.

Endpoints

  • Name
    GET /api/procurement/analytics/summary
    Type
    ProcurementSummary
    Description

    Resumen general. Acepta query params opcionales from y to (rango de fechas). Devuelve totalSpend, openPurchaseOrders, activeRequests, volumeSavings, overduePayments, purchaseOrderCount.

  • Name
    GET /api/procurement/analytics/pipeline
    Type
    { pipeline }
    Description

    Conteo de pedidos por estado (más ORDERED de órdenes). pipeline es un mapa estado → cantidad.

  • Name
    GET /api/procurement/analytics/funnel
    Type
    ProcurementFunnel
    Description

    Embudo de conversión: published, withQuote, awarded, closed.

  • Name
    GET /api/procurement/analytics/spend-by-category
    Type
    { categories }
    Description

    Gasto agregado por categoría de insumo. Array de { category, total }.

  • Name
    GET /api/procurement/analytics/top-suppliers
    Type
    { suppliers }
    Description

    Proveedores con mayor gasto. Acepta query param opcional limit. Array de { supplierId, name, total }.

Request

GET
/api/procurement/analytics/summary
curl -G https://api.cannahub.tech/api/procurement/analytics/summary \
  -H "Authorization: Bearer {token}" \
  -H "Tenant-id: {tenantId}" \
  -d from=2025-01-01 \
  -d to=2025-09-30

curl https://api.cannahub.tech/api/procurement/analytics/pipeline \
  -H "Authorization: Bearer {token}" -H "Tenant-id: {tenantId}"

curl https://api.cannahub.tech/api/procurement/analytics/funnel \
  -H "Authorization: Bearer {token}" -H "Tenant-id: {tenantId}"

curl https://api.cannahub.tech/api/procurement/analytics/spend-by-category \
  -H "Authorization: Bearer {token}" -H "Tenant-id: {tenantId}"

curl -G https://api.cannahub.tech/api/procurement/analytics/top-suppliers \
  -H "Authorization: Bearer {token}" -H "Tenant-id: {tenantId}" \
  -d limit=5

summary — Response

{
  "totalSpend": 4821500,
  "openPurchaseOrders": 3,
  "activeRequests": 5,
  "volumeSavings": 218400,
  "overduePayments": 1,
  "purchaseOrderCount": 12
}

pipeline — Response

{
  "pipeline": {
    "DRAFT": 2,
    "OPEN": 3,
    "QUOTING": 4,
    "EVALUATING": 1,
    "AWARDED": 2,
    "CLOSED": 6,
    "ORDERED": 3
  }
}

funnel — Response

{
  "published": 18,
  "withQuote": 14,
  "awarded": 9,
  "closed": 6
}

spend-by-category — Response

{
  "categories": [
    { "category": "Sustrato", "total": 1980000 },
    { "category": "Nutrientes", "total": 1450500 },
    { "category": "Macetas", "total": 391000 }
  ]
}

top-suppliers — Response

{
  "suppliers": [
    { "supplierId": "sup_01HZR8SUP0001", "name": "Insumos del Sur S.A.", "total": 2310000 },
    { "supplierId": "sup_01HZR8SUP0002", "name": "Grow Mayorista SRL", "total": 1620500 }
  ]
}

React Query Hooks

El feature de Procurement expone hooks pre-armados que consumen estas rutas BFF y manejan caché, invalidación y errores automáticamente. Las query keys viven en @/features/Club/Procurement/hooks/keys.

Queries

import {
  useRequestsQuery,
  useRequestDetailQuery,
  useRequestQuotesQuery,
  useCompareQuery,
  useRequestEventsQuery,
} from '@/features/Club/Procurement/Requests/hooks/queries'
import { usePurchaseOrdersQuery } from '@/features/Club/Procurement/PurchaseOrders/hooks/queries'
import { useSuppliersQuery } from '@/features/Club/Procurement/Suppliers/hooks/queries'

// Listar pedidos con filtro por estado
const { data } = useRequestsQuery({ status: 'QUOTING' })

// Detalle de un pedido (request + quotes + events)
const { data: detail } = useRequestDetailQuery('req_01HZR8XYZ0001')

// Comparar cotizaciones de un pedido
const { data: comparison } = useCompareQuery('req_01HZR8XYZ0001')

Mutations

import {
  useCreateRequestMutation,
  useUpdateRequestStatusMutation,
  useAddQuoteMutation,
} from '@/features/Club/Procurement/Requests/hooks/mutations'
import { useSelectQuoteMutation } from '@/features/Club/Procurement/Quotes/hooks/mutations'
import { useUpdatePurchaseOrderStatusMutation } from '@/features/Club/Procurement/PurchaseOrders/hooks/mutations'

// Crear un pedido (RFQ)
const { mutate: createRequest } = useCreateRequestMutation()

// Adjudicar una cotización → genera la orden de compra
const { mutate: selectQuote } = useSelectQuoteMutation()

// Recepcionar una orden de compra
const { mutate: updatePoStatus } = useUpdatePurchaseOrderStatusMutation()

Máquina de estados del pedido

El ciclo de vida de un pedido (RFQ) sigue una máquina de estados. Las transiciones inválidas devuelven 409 Conflict con un hint validTransitions que indica los estados válidos desde el estado actual.

DRAFT → OPEN → QUOTING → EVALUATING → AWARDED → CLOSED
                                          ↑
                         (adjudicación de cotización)
   cualquiera → CANCELLED
ModeloEstados
SupplyRequestDRAFT, OPEN, QUOTING, EVALUATING, AWARDED, CLOSED, CANCELLED
SupplyQuoteRECEIVED, SHORTLISTED, SELECTED, REJECTED, EXPIRED
PurchaseOrderORDERED, PARTIAL_RECEIVED, RECEIVED, CANCELLED
PurchaseOrder (pago)PENDING, PARTIAL, PAID, OVERDUE

Was this page helpful?