2026-08-27 — Promociones por hito
Para el front-end y mobile del cliente y del conductor, y el panel admin.
Summary
Agüita ya puede correr promociones de enganche sin esperar un despliegue. El admin crea una campaña —a quién premia, en qué entrega, cuánto, desde cuándo, en qué ciudad y a cuánta gente como máximo— y al completarse la entrega que alcanza el hito el beneficio se otorga solo.
Al cliente se le otorga crédito, que se descuenta automáticamente al crear su próximo pedido — no hay que activarlo ni elegirlo. Al conductor se le acredita dinero real en su billetera, que sale en su próxima liquidación como una línea de ajuste.
Todo es aditivo: mientras no haya campañas activas nada cambia. Un pedido sin
crédito trae promo_credit_usd: 0 y client_charged_usd igual a client_pays_usd.
New
| Método | Endpoint | Rol | Para qué sirve |
|---|---|---|---|
| GET | /credit/me |
cliente | Ver su crédito promocional disponible. |
| GET | /credit/me/transactions |
cliente | Historial de movimientos de su crédito. |
| GET | /admin/promotions |
admin principal | Listar campañas. |
| POST | /admin/promotions |
admin principal | Crear una campaña. |
| GET | /admin/promotions/{promotionId} |
admin principal | Ver una campaña. |
| PATCH | /admin/promotions/{promotionId} |
admin principal | Editar una campaña. |
| DELETE | /admin/promotions/{promotionId} |
admin principal | Eliminar una campaña. |
| GET | /admin/promotions/{promotionId}/grants |
admin principal | Ver quiénes la recibieron. |
| POST | /admin/customers/{customerId}/credit/adjust |
admin | Acreditar o debitar crédito a mano. |
| GET | /admin/customers/{customerId}/credit/transactions |
admin | Historial de crédito de un cliente. |
Cómo se configura una campaña
milestone_orders cuenta entregas completadas, de cualquier tipo: un pedido a
domicilio y un remate suman igual. El beneficio se otorga al cerrar esa entrega,
así que el crédito se usa desde el pedido siguiente. Para "en tu 3er viaje
tienes $10", se configura milestone_orders: 2.
audience decide a quién premia. Una campaña de CLIENT debe declarar
credit_scope; una de DRIVER no puede declararlo, porque su bono es dinero, no
se canjea.
Cada persona recibe una campaña una sola vez. max_grants limita a cuánta
gente en total, y city_id la restringe a una ciudad (null = toda la plataforma).
El saldo del cliente son tres bolsas
credit_scope decide dónde puede gastarse el crédito:
| Ámbito | Se gasta en |
|---|---|
ANY |
cualquier pedido |
ORDERS |
solo pedidos a domicilio |
SURPLUS |
solo remates |
Por eso GET /credit/me devuelve las tres bolsas por separado y no un total
disponible: con anyUSD: 10 y ordersUSD: 5, un pedido normal puede descontar $15
pero un remate solo $10. Para eso están usableOnOrdersUSD y usableOnSurplusUSD,
ya calculados — no los sumes por tu cuenta.
Además vienen dos acumulados históricos: earnedUSD (todo lo que ganó en campañas,
sin importar la bolsa) y redeemedUSD (lo que ya aplicó a pedidos y no le fue
devuelto; lo reservado en un pedido en curso cuenta como usado). Sirven para el
típico "has ganado $25 en créditos". Los ajustes manuales de un admin no entran en
ninguno de los dos, así que earnedUSD − redeemedUSD no tiene por qué cuadrar con
las bolsas.
Al aplicar el crédito se gasta primero la bolsa más restringida y después la libre. Es a propósito: al revés se quemaría el dinero flexible y quedaría varado el restringido.
El crédito se consume completo, no queda vuelto
Aplicar el crédito vacía todas las bolsas utilizables en ese pedido. Lo que alcanza a descontar rebaja el total; lo que sobre se pierde, no queda para el próximo pedido.
Con $50 de crédito y un pedido de $4: se descuentan $4 y se pierden $46. En el
historial eso se ve como un movimiento ORDER_REDEMPTION de $4 y uno
ORDER_FORFEIT de $46.
Las bolsas que no aplican a ese pedido no se tocan: un crédito de remates sobrevive intacto a un pedido a domicilio.
Conviene avisarlo en la UI antes de confirmar un pedido chico con mucho crédito ("se aplicarán $50 de tu crédito"), porque el cliente no recupera la diferencia.
Si el pedido no llega a entregarse —lo cancela cualquiera de las partes, expira sin conductor o vence la ventana de pago— se devuelve todo, incluido lo que se había perdido: el excedente solo se quema en un pedido que efectivamente ocurre.
Notificaciones push
| Tipo | Rol | Cuándo |
|---|---|---|
promo_credit_granted |
cliente | Ganó crédito (el texto dice dónde puede usarlo). |
promo_bonus_granted |
conductor | Ganó un bono en su billetera. |
WebSocket
| Evento | Canal | Rol |
|---|---|---|
client.credit.updated |
users.{id} |
cliente |
Se emite en cada movimiento de crédito (otorgado, aplicado a un pedido, devuelto o
ajustado por un admin). payload: credit con las mismas siete cifras que
GET /credit/me.
Changes
El objeto de orden
Dos campos nuevos en todos los endpoints que devuelven la orden completa:
| Campo | Tipo | Para qué sirve |
|---|---|---|
promo_credit_usd |
number | Crédito promocional aplicado a este pedido. |
client_charged_usd |
number | Lo que el cliente realmente paga: client_pays_usd − promo_credit_usd. |
También aparecen en el listado GET /orders, en el recibo
GET /orders/{orderId}/receipt y en los eventos order.confirmed y
order.awaiting_payment.
Cuando hay crédito aplicado, la orden suma una línea de desglose promo_credit
(tipo adjustment) con monto negativo.
Ojo con qué campo mostrar: client_pays_usd sigue siendo el precio del pedido;
client_charged_usd es lo que hay que cobrar. Para el monto a pagar usa siempre
client_charged_usd.
Visibilidad para el conductor
En pedidos en efectivo el conductor ahora ve client_pays_usd,
promo_credit_usd y client_charged_usd, y la línea promo_credit del desglose.
Cobra en mano client_charged_usd, que puede ser menor al precio del pedido.
Su ganancia no cambia: Agüita le abona la diferencia en su billetera como un
movimiento PROMO_REIMBURSEMENT, que se paga en su próxima liquidación. En pedidos
online el conductor sigue sin ver ninguno de los tres campos.
Cobros
El monto del intent de pago (amount_cents en /payments) y la cotización de
GET /orders/{orderId}/payment/form (amount.usd y amount.bs) ahora usan
client_charged, no client_pays. Si el crédito cubre el pedido completo, la orden
pasa directo a CONFIRMED con el pago ya confirmado en 0, sin pasar por
WAITING_FOR_PAYMENT.
Devoluciones
Cancelar el pedido, que expire sin conductor, que venza la ventana de pago o que un admin lo cancele devuelven el crédito a la bolsa de la que salió, tanto lo descontado como lo que se había consumido de más. Cambiar el método de pago o aceptar una contraoferta con otro precio lo libera y lo vuelve a tomar sobre el total recalculado.
Desactivar una campaña solo corta nuevos otorgamientos: a quien ya lo ganó no se le quita el saldo, porque el crédito vive en su perfil, no en la campaña.
Billetera del conductor
Dos tipos nuevos de transacción en GET /wallet/me/transactions: PROMO_BONUS
(bono de campaña) y PROMO_REIMBURSEMENT (reintegro del crédito del cliente en un
pedido en efectivo). Ambos son CREDIT y entran a la liquidación como líneas de
tipo ADJUSTMENT, sin afectar la comisión ni el IVA del recibo.
Panel admin
GET /admin/customers/{customerId} incluye un objeto credit, con la misma forma
que GET /credit/me (bolsas, utilizables y acumulados).
GET /admin/finance/summary suma costo_promociones_usd y ya lo descuenta de
utilidad_neta_usd. En los reportes de clientes, el gasto acumulado pasa a
calcularse sobre lo realmente cobrado.