Admin order reports

Back-office order reporting: a detailed per-order report, a cancelled/expired report, and a per-day operational consolidation. Each has a CSV export. Paths are relative to the /api/v1 prefix; these are back-office endpoints that require an admin token.

Requires an admin bearer token (see Admin auth). Admin only. Results are scoped to the admin's assigned cities.

See Common errors and the order money breakdown.

All reports are read-only. All money is reported in USD. A date range is required to span at most 30 days; a wider range returns 422. When omitted, the range defaults to the last 30 days. List endpoints are page-based; see Pagination → page-based. Every .../export endpoint accepts ?format=csv|xlsx (default csv); xlsx returns application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.

Every report accepts sort (a column key from its allowed list below) and direction (asc|desc, default desc). An unknown sort key or direction value returns 422. The list endpoints sort the full dataset before paginating, and .../export uses the same sort as its list endpoint.

Endpoints

Method URI Access Description
GET /admin/reports/orders Admin Detailed orders, one row per order
GET /admin/reports/orders/export Admin CSV export of the detailed report
GET /admin/reports/orders/cancellations Admin Cancelled & expired orders
GET /admin/reports/orders/cancellations/export Admin CSV export of the cancellations report
GET /admin/reports/orders/operational Admin Operational consolidation, one row per day
GET /admin/reports/orders/operational/export Admin CSV export of the operational report

GET /admin/reports/orders

Detailed report with one row per order, in any state.

Query

Field Type Required Rules
date_from date no start of the range; defaults to 29 days before date_to
date_to date no end of the range; >= date_from; span ≤ 30 days
city_id integer no filter by the order's city
status string no order status value (BROADCASTINGEXPIRED)
driver_id integer no
client_id integer no
water_type string no POTABLE or INDUSTRIAL
payment_method string no payment method value
payment_status string no confirmed, pending, or failed
origin string no app or remate
has_counter_offer boolean no
has_incident boolean no
rating_min integer no 1–5
rating_max integer no 1–5
sort string no one of the sortable keys below; default newest first
direction string no asc or desc (default desc)
limit integer no 1–100, default 25
page integer no ≥ 1, default 1

Sortable sort keys: id, created_at, status, water_type, city_name, client_name, driver_name, liters_requested, liters_delivered, liters_diff, base_price_usd, final_price_usd, commission_usd, driver_net_usd, acceptance_minutes, delivery_minutes, rating, incidents.

Response 200

{
  "items": [
    {
      "id": 42,
      "created_at": "2026-07-18T12:40:00+00:00",
      "status": "DELIVERED",
      "status_label": "Entregado",
      "city_id": 3,
      "city_name": "Caracas",
      "client_id": 1,
      "client_name": "Ana Martínez",
      "driver_id": 7,
      "driver_name": "Luis Pérez",
      "plate": "ABC123",
      "address": "Av. X, Caracas",
      "water_type": "POTABLE",
      "liters_requested": 1000,
      "liters_delivered": 1000,
      "liters_diff": 0,
      "liters_diff_flagged": false,
      "base_price_usd": 10.0,
      "final_price_usd": 10.0,
      "payment_method": "CASH_USD",
      "payment_method_label": "Cash USD",
      "payment_status": "No aplica",
      "commission_usd": 1.8,
      "solidary_fund_usd": 0.18,
      "driver_net_usd": 8.12,
      "origin": "app",
      "has_counter_offer": false,
      "acceptance_minutes": 3,
      "delivery_minutes": 25,
      "rating": 5,
      "incidents": 0
    }
  ],
  "meta": { "total": 137, "per_page": 25, "current_page": 1, "last_page": 6 },
  "totals": {
    "pedidos": 137,
    "litros_solicitados": 137000,
    "litros_entregados": 120000,
    "monto_bruto_usd": 1370.0,
    "comision_usd": 246.6,
    "neto_conductores_usd": 1112.4
  }
}

Errors

Status Body When
422 { "error": "ValidationError", "details": { "fieldErrors": { "date_to": ["…"] } } } Range wider than 30 days, or invalid filter

GET /admin/reports/orders/export

Streams the filtered detailed report as a CSV download (text/csv); there is no JSON body on success. Accepts the same filters as the detailed report (no pagination).

Response 200 — a text/csv stream (reporte_ordenes_<timestamp>.csv) with the columns Fecha creación, Pedido, Estado, Ciudad, Cliente, Conductor, Placa, Dirección, Tipo de agua, Litros solicitados, Litros entregados, Dif. litros, Precio base (USD), Precio final (USD), Método de pago, Estado de pago, Comisión (USD), Fondo solidario (USD), Neto conductor (USD), Origen, Contraoferta, Tiempo aceptación (min), Tiempo entrega (min), Calificación, Incidencias.

Errors

Status Body When
422 { "error": "ValidationError", … } Range wider than 30 days, or invalid filter

GET /admin/reports/orders/cancellations

Cancelled and expired orders only. The date range applies to the cancellation/expiration moment.

Query

Field Type Required Rules
date_from date no start of the range; span ≤ 30 days
date_to date no end of the range; >= date_from
city_id integer no filter by the order's city
cancelled_role string no cliente, conductor, admin, or sistema
status_at_cancel string no BROADCASTING, WAITING_FOR_PAYMENT, CONFIRMED, or IN_PROGRESS
driver_id integer no
client_id integer no
with_refund boolean no
sort string no one of the sortable keys below; default newest first
direction string no asc or desc (default desc)
limit integer no 1–100, default 25
page integer no ≥ 1, default 1

Sortable sort keys: id, date, final_status, client_name, driver_name, liters, unrealized_usd, minutes_to_cancel.

Response 200

{
  "items": [
    {
      "id": 51,
      "date": "2026-07-18T10:05:00+00:00",
      "final_status": "CANCELLED",
      "cancelled_by": "cliente",
      "status_at_cancel": "BROADCASTING",
      "reason": "Ya no lo necesito",
      "client_name": "Ana Martínez",
      "driver_name": null,
      "liters": 1000,
      "unrealized_usd": 10.0,
      "minutes_to_cancel": 4,
      "charged_before": false,
      "refunded": false,
      "recovered": true
    }
  ],
  "meta": { "total": 12, "per_page": 25, "current_page": 1, "last_page": 1 },
  "totals": {
    "cancelled_by_client": 5,
    "cancelled_by_driver": 2,
    "cancelled_by_admin": 1,
    "expired": 4,
    "unrealized_usd": 120.0,
    "recovery_rate": 41.7
  }
}

Errors

Status Body When
422 { "error": "ValidationError", … } Range wider than 30 days, or invalid filter

GET /admin/reports/orders/cancellations/export

Streams the filtered cancellations report as a CSV download (text/csv); no JSON body on success. Same filters, no pagination.

Response 200 — a text/csv stream (reporte_cancelaciones_<timestamp>.csv) with the columns Fecha, Pedido, Estado final, Canceló, Estado al cancelar, Motivo, Ciudad, Cliente, Conductor, Litros, Monto no concretado (USD), Tiempo hasta cancelar (min), Cobro previo, Reembolso, Recuperado.


GET /admin/reports/orders/operational

Operational consolidation with one row per calendar day (by creation date).

Query

Field Type Required Rules
date_from date no start of the range; span ≤ 30 days
date_to date no end of the range; >= date_from
city_id integer no filter by the order's city
client_id integer no limit to one client
driver_id integer no limit to one driver
water_type string no POTABLE or INDUSTRIAL
sort string no one of the sortable keys below; default chronological
direction string no asc or desc (default desc)

Sortable sort keys: date, published, accepted, delivered, cancelled, expired, conversion_pct, liters_requested, liters_delivered, avg_ticket_usd, avg_acceptance_minutes, avg_delivery_minutes, active_drivers, gross_income_usd, commission_usd. Without sort, rows stay in chronological order. sort reorders only the items; totals and comparison are unaffected.

Response 200

{
  "items": [
    {
      "date": "2026-07-18",
      "published": 10,
      "accepted": 8,
      "delivered": 7,
      "cancelled": 2,
      "expired": 1,
      "conversion_pct": 70,
      "liters_requested": 10000,
      "liters_delivered": 7000,
      "avg_ticket_usd": 10.0,
      "avg_acceptance_minutes": 3.2,
      "avg_delivery_minutes": 24.8,
      "active_drivers": 5,
      "gross_income_usd": 70.0,
      "commission_usd": 12.6
    }
  ],
  "totals": {
    "published": 10,
    "accepted": 8,
    "delivered": 7,
    "cancelled": 2,
    "expired": 1,
    "conversion_pct": 70,
    "liters_requested": 10000,
    "liters_delivered": 7000,
    "avg_ticket_usd": 10.0,
    "avg_acceptance_minutes": 3.2,
    "avg_delivery_minutes": 24.8,
    "active_drivers": 5,
    "gross_income_usd": 70.0,
    "commission_usd": 12.6
  },
  "comparison": {
    "previous_period": { "from": "2026-06-18T00:00:00+00:00", "to": "2026-07-17T23:59:59+00:00" },
    "previous": { "published": 8, "delivered": 6, "gross_income_usd": 60.0, "commission_usd": 10.8, "conversion_pct": 75 },
    "variation_pct": { "published": 25.0, "delivered": 16.7, "cancelled": null, "expired": null, "gross_income_usd": 16.7, "commission_usd": 16.7, "conversion_pct": -6.7 }
  }
}

variation_pct is null when the previous-period value is 0.

Errors

Status Body When
422 { "error": "ValidationError", … } Range wider than 30 days, or invalid filter

GET /admin/reports/orders/operational/export

Streams the filtered operational report as a CSV download (text/csv); no JSON body on success. Same filters.

Response 200 — a text/csv stream (reporte_operativo_<timestamp>.csv) with the columns Fecha, Publicados, Aceptados, Entregados, Cancelados, Expirados, Conversión (%), Litros solicitados, Litros entregados, Ticket promedio (USD), T. medio aceptación (min), T. medio entrega (min), Conductores activos, Ingreso bruto (USD), Comisión (USD).