Documentación del Proceso de Creación de una Orden
Proceso funcional de creación de una orden en el sistema
Este documento describe el proceso funcional mediante el cual un usuario crea una nueva orden en el sistema, detallando el flujo principal y las variaciones lógicas basadas en el país, la marca y la tienda.
1. Flujo Básico del Proceso
El proceso de creación de una orden es una secuencia de validaciones y acciones que aseguran que cada pedido se procese de manera correcta y segura.
Diagrama de Flujo General:
Inicio (Usuario confirma su carrito)
|
V
1. Validaciones Iniciales
| - ¿Tienda abierta? ¿App compatible? ¿Método de pago permitido? ¿Cupón válido? ¿Carrito no vacío? ¿País correcto?
| - ¿Límites de compra por usuario/marca? ¿Restricciones especiales por tienda o país? ¿Versión de app compatible?
| - ¿Validaciones de fraude (ej. México)?
|
V (Si falla alguna validación, el proceso se detiene y se notifica con error claro)
2. Creación y Verificación de la Orden
| - Se crea un borrador de la orden.
| - Se verifica y reserva el inventario (stock) de los productos.
| - Si falla la reserva de inventario, rollback y notificación al usuario.
|
V
3. Cálculo y Proceso de Pago
| - Se calcula el total (productos + envío - créditos - descuentos - promociones).
| - Se valida que el método de pago sea aceptado.
| - Se procesa el cobro a través del proveedor de pagos (Stripe, MercadoPago, Conekta).
| - Si el pago falla, rollback de inventario y notificación clara al usuario.
|
V
4. Configuración de Entrega e Integraciones
| - Se coordina la entrega con servicios logísticos (Uber Direct, Deliverect si aplica).
| - Si falla la integración logística, rollback y notificación.
|
V
5. Finalización y Notificaciones
| - Se confirma la orden y se actualiza su estado.
| - Se marca el cupón como usado.
| - Se envía la orden a sistemas externos (Deliverect, Firebase, Drip).
| - Se guarda la localización del usuario asociada a la orden.
| - Se notifica al usuario y/o marketing.
|
V
Fin del Proceso (Orden creada exitosamente)2. Flujo Detallado del Proceso
A continuación se detalla cada fase del proceso.
Fase 1: Validaciones Iniciales
Antes de crear la orden, el sistema realiza una serie de comprobaciones rápidas para asegurar que la solicitud es válida.
-
Verificación del Método de Pago: Se comprueba si el método de pago seleccionado por el usuario es compatible con las políticas de la tienda. Las tiendas pueden configurarse para aceptar:
0: Todos los métodos (tarjeta y efectivo).1: Solo tarjeta.2: Solo efectivo.
Si el método de pago del pedido no está alineado con la configuración de la tienda, la transacción se rechaza.
-
Validación de Cupón: Si el usuario ingresa un cupón, el sistema verifica que el cupón exista, esté asignado al usuario, no haya expirado y sea válido para la tienda.
- Ejemplo: Si se usa un cupón "NEWMX20230401ABCD" que ya fue utilizado, se genera un error.
-
Verificación de Carrito de Compras: Se asegura que existan productos en el carrito asociados a la orden. Si está vacío, el proceso se detiene.
-
Compatibilidad de la App (Específico para Circle K y Falabella): Para tiendas de las marcas Circle K y Falabella, se verifica que la versión de la aplicación del usuario cumpla con la versión mínima requerida por el sistema. Si la versión es anterior, se notifica al usuario y se impide continuar con la orden.
-
Validación de Entrega: Si se solicita entrega, se comprueba que la tienda ofrezca este servicio y que el método de pago sea válido para ello.
-
Disponibilidad de la Tienda: Se confirma que la tienda esté abierta. La orden se rechaza si la tienda está cerrada o a punto de cerrar (ej. dentro de los 30 segundos previos al cierre).
-
Coherencia de País: Se valida que el país del usuario y el de la tienda coincidan.
Fase 2: Creación de la Orden
Si las validaciones iniciales son exitosas, el sistema procede a crear la orden.
-
Inicialización: Se crea un registro interno para la orden, asociándola al usuario y la tienda.
-
Verificación de Límite de Paquetes: Si la marca de la tienda tiene configurado un límite de compra, el sistema realiza la siguiente validación:
- Disparador: La regla solo se activa si la tienda pertenece a una marca con una configuración de límite de paquetes (
package_limit_config). - Período del Límite: El límite se puede establecer por día o por semana.
- Cálculo: El sistema suma todos los paquetes de las órdenes completadas o recogidas por el usuario (identificado por su cuenta o el id del dispositivo) durante el período vigente (diario o semanal) para esa marca específica.
- Validación: Se comprueba si la suma de los paquetes ya comprados más los de la orden actual supera el límite establecido. Si lo hace, la orden se rechaza y se informa al usuario cuántos paquetes le quedan disponibles.
- Disparador: La regla solo se activa si la tienda pertenece a una marca con una configuración de límite de paquetes (
-
Generación de Código de Barras: Si la marca de la tienda está configurada para permitirlo (
allow_create_barcode_order), el sistema genera un código de barras para la orden. El proceso es el siguiente:- Creación del Código: Se genera un código único de 13 dígitos bajo el estándar EAN-13.
- Verificación de Duplicados: Una vez creado, el sistema valida que este código no exista en ninguna otra orden.
- Manejo de Fallos: En el caso excepcional de que se detecte un duplicado, la creación de la orden se interrumpe para asegurar que cada orden tenga un identificador único.
-
Procesamiento de Inventario (Stock): Este proceso se ejecuta dentro de una transacción atómica, lo que garantiza que todas las operaciones de stock se completen con éxito o ninguna lo haga. El flujo es el siguiente:
- Iteración sobre el Carrito: El sistema recorre cada producto en el carrito de compras.
- Bloqueo del Producto: Para evitar conflictos si varios usuarios intentan comprar el mismo producto simultáneamente, se bloquea temporalmente el registro del producto en la base de datos (
select_for_update). - Verificación de Disponibilidad (
is_available_stock): Se comprueba si la cantidad solicitada por el usuario es menor o igual al stock disponible del producto. - Actualización de Stock (
remove_menu_stock): Si hay stock suficiente, se descuenta la cantidad comprada del inventario total del producto. - Manejo de Falta de Stock: Si en algún momento un producto no tiene stock suficiente, la transacción completa se revierte (ningún producto del carrito se descuenta), la orden se marca como fallida (
STATUS_NO_STOCK), y el proceso se detiene.
Fase 3: Proceso de Pago
Esta fase se centra en el cálculo y cobro del monto final.
-
Cálculo del Total: El monto final a pagar se determina mediante un proceso secuencial. El orden y la lógica son los siguientes:
- Costo Base de Productos: Se parte del subtotal inicial, que es la suma del precio de todos los artículos en el carrito (
order.total(carts)). - Descuentos Directos (aplica para ahorra o nunca): Se aplican descuentos preestablecidos directamente sobre los productos (
order.total_with_discount()). La diferencia se conoce comodirect_discounty se resta del subtotal. - Aplicación de Cupón (si se utiliza uno):
- Se valida el cupón: existencia, si está asignado al usuario, no ha expirado y es válido para la tienda y la orden actual (
coupon.valid_coupon(order)). Si no es válido, la orden puede marcarse con un estado de error y detenerse el proceso. - Se calcula el
coupon_discountsegún el tipo de cupón (Coupon.TOTAL,Coupon.REFERRAL,Coupon.PERCENT).- Para cupones de porcentaje, el descuento se calcula sobre el subtotal actual y puede estar limitado por un
coupon_limit.
- Para cupones de porcentaje, el descuento se calcula sobre el subtotal actual y puede estar limitado por un
- El
coupon_discountse resta del subtotal.
- Se valida el cupón: existencia, si está asignado al usuario, no ha expirado y es válido para la tienda y la orden actual (
- Uso de Créditos Cheaf (si el usuario opta por ello):
- Se determina la cantidad de
cheaf_creditsa aplicar (use_cheaf_credits). Esta cantidad no puede ser mayor que el subtotal actual (después de descuentos de cupón) ni mayor que el saldo de créditos disponibles del usuario. - Los
cheaf_creditsaplicados se restan del subtotal. El subtotal resultante no puede ser menor que cero.
- Se determina la cantidad de
- Costo de Entrega (si el pedido es para entrega):
- Se determina el
delivery_feed(costo de envío original). - Cobertura de Entrega con Créditos: Se intenta utilizar los créditos Cheaf restantes del usuario para cubrir este costo (
credits_used_for_delivery):- Si el método de pago es efectivo (
order.payment_type == "1"): El usuario debe tener suficientes créditos para cubrir el costo de los productos (después de cupones) más el costo total de envío. Si no alcanza, la orden puede fallar por créditos insuficientes. - Si el método de pago es tarjeta (
order.payment_type == "0"): Los créditos restantes (después de cubrir el costo de los productos) se utilizan para reducir o cubrir completamente eldelivery_feed.
- Si el método de pago es efectivo (
- Se determina el
- Monto Final a Pagar (
card_chargeo monto en efectivo): Es el subtotal de productos (después de todos los descuentos y créditos aplicados a los productos) más eldelivery_feedrestante (después de aplicarcredits_used_for_deliverya la entrega). Este es el importe final que se intentará cobrar.
- Costo Base de Productos: Se parte del subtotal inicial, que es la suma del precio de todos los artículos en el carrito (
-
Revisión Adicional de Promoción (para Pagos en Efectivo): Después de calcular el total y antes de procesar el pago, se aplica una validación específica para cupones en órdenes con pago en efectivo:
- Condiciones: Esta revisión se activa si se cumplen todos los siguientes puntos:
- Se está utilizando un cupón en la orden.
- La tienda tiene la configuración
promo_plus_0_allowedestablecida enFalse(esto implica que, para pagos en efectivo en esta tienda, si se usa un cupón, este debe cubrir el costo total de los productos; no se permiten cupones que dejen un saldo positivo a pagar en efectivo). - El monto de los productos (calculado como
payment.get("total"), que es el subtotal después de todos los descuentos, incluyendo el cupón y créditos, pero antes de sumar el costo de envío para el cobro final) es mayor a cero. - El método de pago seleccionado es efectivo (
order.payment_type == "1").
- Resultado: Si todas estas condiciones son verdaderas, el cupón se considera inválido para esta transacción específica. La orden se marca con un estado de error (
16), se revierte el stock de los productos del carrito (return_removed_carts_stock) y se lanza una excepciónInvalidCoupon, lo que detiene el proceso de creación de la orden.
- Condiciones: Esta revisión se activa si se cumplen todos los siguientes puntos:
-
Procesamiento del Pago: Este paso se ejecuta solo si la orden es con pago de tarjeta (
order.is_card_payment()) y el monto total a cobrar (incluyendo costo de envío) es mayor a cero. El proceso es el siguiente:- Selección y Validación del Proveedor de Pago:
- Se identifica el proveedor de pagos configurado para el usuario (
user.get_payment_provider(provider_name)). - Si el usuario no tiene un proveedor de pagos válido asociado, la orden se marca como no pagada (
STATUS_UNPAID), se revierte el stock de los productos del carrito y se detiene el proceso con un error (ProviderNotFoundError).
- Se identifica el proveedor de pagos configurado para el usuario (
- Obtención del Procesador de Pago Específico:
- Se utiliza una fábrica (
PaymentProviderFactory) para obtener la implementación concreta del procesador de pagos (ej. Stripe, MercadoPago, Conekta) basado en el nombre del proveedor.
- Se utiliza una fábrica (
- Intento de Cobro (
pay_order):- Se invoca la función
pay_ordercon todos los detalles necesarios:order: La orden actual.transaction_amount: El monto final a cobrar (subtotal de productos + costo de envío restante).card_id: El token de la tarjeta del usuario.customer_id: El ID del cliente en la plataforma del proveedor de pagos.processor: El procesador de pago obtenido en el paso anterior.coupon_value: El monto del descuento por cupón.credits_amount: El monto total de créditos Cheaf utilizados (para productos + para envío).delivery_fee: El costo original del envío (antes de aplicar créditos).direct_discount: El monto del descuento directo.
- Se invoca la función
- Manejo del Resultado del Pago:
- Si el pago es exitoso:
- Se genera un
payment_id. Para MercadoPago, este ID es directamente el ID de la transacción de MercadoPago (para compatibilidad con webhooks). Para otros, es una combinación del nombre del proveedor, ID del cliente y ID de la transacción. - La orden se actualiza con este
payment_idy su estado cambia a solicitado (STATUS_REQUESTED).
- Se genera un
- Si el pago falla (debido a
PaymentError, errores específicos de MercadoPago, o cualquier otra excepción durante el cobro):- Se revierte el stock de los productos del carrito (
return_removed_carts_stock). - La orden se marca como no pagada (
STATUS_UNPAID). - Se propaga la excepción original, lo que detiene el flujo y permite que el sistema maneje el error (generalmente informando al usuario).
- Se revierte el stock de los productos del carrito (
- Si el pago es exitoso:
- Selección y Validación del Proveedor de Pago:
Fase 4: Configuración de Entrega (Delivery)
Esta fase se activa únicamente si la orden es para entrega a domicilio (for_delivery es verdadero) y se ha proporcionado una ubicación (location). El proceso es el siguiente:
- Obtención de Credenciales del Proveedor de Logística:
- Se recupera la configuración de integración para el proveedor de logística (ej. Uber Direct) correspondiente al país de la tienda (
IntegrationsConfig).
- Se recupera la configuración de integración para el proveedor de logística (ej. Uber Direct) correspondiente al país de la tienda (
- Creación de la Solicitud de Entrega:
- Se instancia un cliente del proveedor de logística (ej.
UberDirectClient) con las credenciales obtenidas. - Se llama al método para crear la entrega (ej.
uber_direct_client.create_delivery(order, location)), enviando la información de la orden y la ubicación del usuario.
- Se instancia un cliente del proveedor de logística (ej.
- Manejo del Resultado de la Solicitud de Entrega:
- Si la solicitud de entrega es exitosa:
- La orden se actualiza con la información relevante:
for_deliveryse confirma comoTrue.delivery_cost: Costo original del envío (antes de aplicar créditos).delivery_fee: Costo final del envío a cobrar (después de aplicar créditos).tracking_url: URL de seguimiento proporcionada por el proveedor de logística.credits_used_for_delivery: Monto de créditos Cheaf que se usaron para cubrir el envío.
- La orden continúa al siguiente paso.
- La orden se actualiza con la información relevante:
- Si la solicitud de entrega falla (ocurre una excepción):
- Se registra el error.
- La orden se marca como cancelada (
order.status = STATUS_CANCELLED). - Se revierte el stock de los productos del carrito (
return_removed_carts_stock). - Lógica de Reembolso por Falla en Entrega:
- Si el método de pago original fue con tarjeta (
order.payment_type == "0"):- El monto total que se habría cobrado a la tarjeta del usuario (
payment_description["card_charge"]) se reembolsa al usuario en forma de créditos Cheaf. - Se envía una notificación al usuario informando sobre la falla en la configuración de la entrega y el reembolso de créditos (
setup_delivery_fails_notification).
- El monto total que se habría cobrado a la tarjeta del usuario (
- Si el método de pago original fue con tarjeta (
- Se envía un correo electrónico de alerta al equipo de soporte (
support_email_when_error_occurs_in_picker). - Se lanza una excepción específica (
SetupDeliveryException), lo que detiene el flujo de creación de la orden.
- Si la solicitud de entrega es exitosa:
Fase 5: Finalización de la Orden
Una vez que el pago ha sido confirmado, se completan los últimos pasos:
-
Actualización y Cierre de la Orden (
close_order):- Se registra internamente la información final del pago (créditos usados, descuento de cupón, total).
- Si se usaron créditos Cheaf (
cheaf_credits > 0):- La orden se marca como
used_credits = True. - Se almacena en la orden
credits_used(créditos para productos) ycredits_used_for_delivery(créditos para envío). - Se descuenta el monto total de créditos utilizados (
cheaf_credits + credits_used_for_delivery) del saldo del usuario (user.remove_credits), registrando el origen comoCreditsLogs.NEW_ORDER.
- La orden se marca como
- Si se aplicó un cupón (
coupon_discount > 0):- La orden se marca como
used_coupon = True. - Se almacena el valor del descuento del cupón en
order.coupon_value.
- La orden se marca como
- Se establece el costo final de la orden (
order.cost) al monto total de los productos (después de descuentos y créditos, antes de sumar el envío para el cobro final). - El estado de la orden se actualiza a "3" (generalmente significa completada o confirmada).
- Se guardan los cambios en la orden y en el perfil del usuario.
-
Notificación a Sistema de Marketing (Drip):
- Regla: Después de procesar la orden, se envía una notificación a Drip, una plataforma externa de automatización de marketing.
- Flujo (
send_order_booked):- Se inicializa un cliente para la API de Drip (
DripClient). - Se llama al método
update_suscriberenviando el email del usuario (order.app_user_id.email) y el ID de la orden (order.id).
- Se inicializa un cliente para la API de Drip (
- Propósito: Registrar el evento de compra en el perfil del usuario dentro de Drip, permitiendo su uso en campañas de marketing, segmentación de clientes y análisis de comportamiento. .
-
Gestión de Deuda del Usuario (
close_user_debt):- Si el usuario tiene deuda, usó créditos y el pago es en efectivo (
order.payment_type == "1"):- Se calcula la cantidad de deuda que se puede pagar con los créditos disponibles del usuario (el mínimo entre la deuda actual y los créditos actuales).
- Se reduce la deuda del usuario (
user.remove_debt) por esta cantidad, registrando la fuente comoDebtLogs.ORDER_PAID. - Se descuentan los créditos correspondientes del saldo del usuario (
user.remove_credits), registrando la fuente comoCreditsLogs.ORDER_PAID. Los registros de movimiento de deuda y créditos se enlazan.
- Si el usuario tiene deuda y el pago es con tarjeta (
order.is_card_payment()):- Se asume que la deuda completa del usuario se paga con esta orden.
- El monto total de la deuda se registra en
order.user_debt_payment. - Se reduce la deuda del usuario (
user.remove_debt) por el monto total de su deuda, registrando la fuente comoDebtLogs.ORDER_PAID.
- Si el usuario tiene deuda, usó créditos y el pago es en efectivo (
-
Gestión de Cupón Utilizado (
close_coupon):- Si se utilizó un cupón en la orden:
- Se busca el registro de historial del cupón (
CouponHistory) que estaba asignado al usuario y aún no se había marcado como usado. - Se actualiza el estado de este historial a "1" (usado).
- Se asocia el ID y número de la orden al historial del cupón.
- Se envía un evento "CouponUsed" a Pushwoosh con detalles del cupón y la orden.
- Si el cupón es de tipo ilimitado (
coupon.is_unlimited): Se vuelve a asignar el mismo cupón al usuario para que pueda utilizarlo nuevamente en el futuro (user.reassign_unlimited_coupon).
- Se busca el registro de historial del cupón (
- Si se utilizó un cupón en la orden:
-
Registro del Último Método de Pago Utilizado (
save_last_payment_method):- Se determina el método de pago fuente: si se usó una tarjeta, es el token de la tarjeta; si fue efectivo, se registra como "cash".
- Este método de pago se guarda en el perfil del usuario (
account.last_payment_method) para referencia futura (por ejemplo, para preseleccionar el método de pago en la siguiente compra).
-
Almacenamiento y Sincronización: La información de la orden se guarda en sistemas externos como Firebase (para actualizaciones en tiempo real en la app) y Deliverect (si la tienda lo usa para gestionar sus pedidos).
-
Notificaciones: Se envían notificaciones al usuario y a la tienda, y correos de alerta internos en casos de pedidos muy grandes o sospechosos de fraude.
-
Registro de Evento: Se registra un evento interno para confirmar que la orden fue creada exitosamente.
-
Registro de la Ubicación de la Orden (
OrderLocations):- Regla: Después de crear la orden exitosamente, el sistema asocia la ubicación del usuario con esa orden para fines de análisis y registro.
- Flujo:
- Si la orden es para entrega a domicilio (
for_deliveryesTrue): Se utiliza la ubicación específica que el usuario seleccionó para esa entrega, obteniéndola a través dellocation_idenviado en la solicitud. - Si la orden es para recoger en tienda (
for_deliveryesFalse): Se utiliza la última ubicación actual registrada para el usuario (current=True).
- Si la orden es para entrega a domicilio (
- Acción: Se crea un nuevo registro en la tabla
OrderLocationsque vincula la orden (order) con la ubicación del usuario (app_user_location). - Manejo de Errores: Si no se encuentra una ubicación para el usuario (por ejemplo, un usuario nuevo sin ubicación registrada que hace un pedido para recoger), se registra una advertencia (
Location.DoesNotExist) pero el proceso general no falla
3. Lógica por País
El comportamiento del proceso varía según el país de la transacción.
- Moneda y Proveedor de Pago:
- Regla: La moneda de la transacción y el proveedor de pago se determinan por el país de la tienda.
- Ejemplo: Una tienda en México usará Pesos (MXN) y podría usar Conekta, mientras que una en EE.UU. usará Dólares (USD) y Stripe.
- Validación de Coincidencia de País:
- Regla: El país del usuario debe coincidir con el de la tienda para poder procesar la orden.
- Ejemplo: Un usuario con país "MX" no puede ordenar de una tienda con país "US". Si lo intenta, el sistema muestra un error
InvalidCountryCode.
- Configuración de Alertas de Fraude y Soporte (Específico para México):
- Regla: Para México, existen umbrales y condiciones específicas para el envío de correos de alerta al equipo de soporte (
support@cheaf.com). - Ejemplos de Configuración para México:
- Alerta por Tamaño Elevado del Carrito: Se activa si una orden pagada con tarjeta (
payment_type == "0") tiene un costo (order.cost) mayor o igual a $1,000 MXN. - Alerta General de Monitoreo de Fraude: Se activa si una orden en México cumple alguna de las siguientes condiciones:
- Es pagada con tarjeta y su costo es mayor o igual a $500 MXN.
- El usuario ha gastado $500 MXN o más en las últimas 48 horas.
- Alerta por Tamaño Elevado del Carrito: Se activa si una orden pagada con tarjeta (
- Nota: Aunque estas son las configuraciones actuales para México, la arquitectura permite que otros países puedan tener umbrales y criterios diferentes si se implementan.
- Regla: Para México, existen umbrales y condiciones específicas para el envío de correos de alerta al equipo de soporte (
4. Lógica por Marca
Ciertas marcas tienen reglas de negocio especiales que se aplican durante la creación de la orden.
- Marca "JOKR":
- Regla: Un usuario solo puede comprar un máximo de 5 "cajas" (productos específicos) de JOKR por día.
- Flujo: Antes de crear la orden, el sistema cuenta cuántas cajas de JOKR ha comprado el usuario en el día actual y las suma a las del carrito. Si el total supera 5, la orden se bloquea y se notifica al usuario.
- Marca "El Globo" (ID de tienda 622):
- Regla: Esta tienda tiene una restricción temporal o permanente sobre los métodos de pago.
- Flujo: Si un usuario intenta pagar con tarjeta, usar cupones o créditos en El Globo, el sistema rechaza la orden y muestra el mensaje: "Por ahora El Globo sólo acepta pagos directamente en sucursal".
- Marca "Donar es Ayudar" (ID de restaurant_name 770):
- Regla: Las órdenes de tiendas pertenecientes a esta marca tienen un manejo temporal de estado durante su creación (
complete_order_temp). - Flujo: Si la tienda de la orden pertenece a la marca con
restaurant_name.id == 770, el estado de la orden (order.status) se establece temporalmente a "1"(Completada).
- Regla: Las órdenes de tiendas pertenecientes a esta marca tienen un manejo temporal de estado durante su creación (
- Marca "Krispy Kreme" (ID de restaurant_name 825) - Notificaciones Push Personalizadas:
- Regla: Se envían notificaciones push personalizadas a los usuarios después de completar una orden en tiendas de esta marca (
send_custom_push_kk). - **Flujo (si
order.store_id.restaurant_name.id == 825yorder.status == "3"):- Se obtiene el horario de la tienda y la hora de cierre para recolección (
close_hour). - Si la orden es para entrega a domicilio (
order.for_delivery): Se envía una notificación push específica para entregas (push_kk_custom_notification_delivery). - Si la orden es para recoger en tienda: Se envía una notificación push estándar para esta marca, incluyendo la hora de cierre (
push_kk_custom_notification(order.app_user_id, close_hour)).
- Se obtiene el horario de la tienda y la hora de cierre para recolección (
- Regla: Se envían notificaciones push personalizadas a los usuarios después de completar una orden en tiendas de esta marca (
- Marcas "Circle K" y "Falabella" - Generación de Códigos de Promoción (Gift Cards):
- Regla: Para las tiendas que pertenecen a las marcas Circle K (
is_circle_K_account) o Falabella (is_falabella_account), se activa un proceso especial para generar y asignar códigos de promoción (similares a gift cards) a cada artículo de la orden. Esto ocurre a través del métodoorder.generate_barcodes_from_carts()llamado desdeOrderAction.generate_packages_barcodes(). - Flujo General: El objetivo es asignar un
PromotionBarcode(código de promoción) a cada unidad de producto en el carrito. La lógica varía significativamente según el método de pago y si se usaron créditos. - Flujo 1: Pago con Tarjeta o Uso de Créditos (
self.is_card_payment() or self.used_credits):- Para cada
cart(tipo de producto) en la orden: - Para cada unidad (
cart.amount) de ese producto:- Se busca un
PromotionBarcodedisponible cuyo valor (amount) coincida exactamente con el precio del producto (cart.price) y pertenezca a la cuenta principal de la tienda (main_account). - Si se encuentra, se crea un registro
PromotionCodeCartOrderque vincula el carrito, la orden, el código de barras encontrado y el monto. - El
PromotionBarcodeutilizado se marca como usado (promotion_code.mark_as_used()). - Si no se encuentra un código de promoción adecuado para algún artículo, el proceso puede fallar (ver Manejo de Errores).
- Se busca un
- Para cada
- Flujo 2: Otros Métodos de Pago (Ej. Efectivo con Cupón -
elseblock ingenerate_barcodes_from_carts):- Este flujo intenta aplicar el valor de un cupón (
self.coupon_value), almacenado comoremaining_coupon_discount, utilizando losPromotionBarcodedisponibles para la cuenta principal de la tienda (main_account). - Validación Inicial Crucial: Antes de procesar los artículos, si el valor inicial del cupón (
remaining_coupon_discount) es mayor a cero Y este valor exacto no corresponde a una denominación dePromotionBarcodedisponible globalmente (remaining_coupon_discount not in available_amounts), se lanza una excepciónBarCodeNotFound. Esto implica que el valor total del cupón debe ser, por sí mismo, un monto de código de promoción potencialmente utilizable para ser considerado. - Proceso de Aplicación Iterativo por Unidad de Artículo (si la validación inicial pasa):
- Los
cart(tipos de producto en la orden) se procesan ordenados por precio, de mayor a menor. - Para cada
unidadindividual dentro de cadacart(ej. si uncarttieneamount = 3, el siguiente bucle interno se ejecuta hasta 3 veces para ese tipo de producto, una vez por cada unidad):- Bucle Interno por Unidad (condición:
remaining_coupon_discount > 0): Para la unidad de artículo actual, el sistema intentará aplicar códigos promocionales. Este bucle se repite para la misma unidad si un código aplicado no consume todo elremaining_coupon_discounty aún queda valor por aplicar.se_aplico_codigo_en_esta_pasada_del_bucle_interno = False- Paso 1: Evaluar y (si aplica) Ejecutar Estrategia A
- Condiciones para Estrategia A:
- El precio del artículo (
cart.price) es mayor o igual alremaining_coupon_discountactual. - El valor del
remaining_coupon_discountactual es una denominación dePromotionBarcodedisponible globalmente (remaining_coupon_discount in available_amounts).
- El precio del artículo (
- Si AMBAS condiciones para Estrategia A se cumplen:
- Se intenta la Acción de Estrategia A: Buscar y asignar un
PromotionBarcode(filtrado pormain_account) por el monto exacto delremaining_coupon_discount. - Si se asigna un código exitosamente:
remaining_coupon_discountse establece a0(cupón totalmente consumido), yse_aplico_codigo_en_esta_pasada_del_bucle_interno = True. - (Estrategia B se omite en esta pasada del bucle interno).
- Se intenta la Acción de Estrategia A: Buscar y asignar un
- Condiciones para Estrategia A:
- Paso 2: Si las condiciones de Estrategia A NO se cumplieron, Evaluar y (si aplica) Ejecutar Estrategia B (bloque
elsedel código Python):- Condiciones para Estrategia B: El
cart.pricedel artículo actual es una denominación dePromotionBarcodedisponible globalmente (cart.price in available_amounts). - Si la condición para Estrategia B se cumple:
- Se intenta la Acción de Estrategia B: Buscar y asignar un
PromotionBarcode(filtrado pormain_account) por el monto delcart.price. - Si se asigna un código exitosamente:
remaining_coupon_discountse reduce encart.price, yse_aplico_codigo_en_esta_pasada_del_bucle_interno = True.
- Se intenta la Acción de Estrategia B: Buscar y asignar un
- Condiciones para Estrategia B: El
- Paso 3: Condición de Salida del Bucle Interno para la unidad actual:
- Si
se_aplico_codigo_en_esta_pasada_del_bucle_internosigue siendoFalse(es decir, ni Estrategia A ni Estrategia B lograron aplicar un código en esta pasada del bucle), el bucle interno para esta unidad de artículo se interrumpe. Esto evita un ciclo infinito si no se puede progresar. - Si
remaining_coupon_discountes0o menos, el bucle interno también termina para esta unidad.
- Si
- (Fin del Bucle Interno por Unidad)
- Bucle Interno por Unidad (condición:
- (Fin del bucle por cada unidad de artículo)
- Los
- Este flujo intenta aplicar el valor de un cupón (
- **Manejo de Errores (si
generate_barcodes_from_cartsfalla, por ejemplo, porBarCodeNotFound):- Se revierte el stock de los productos que se habían descontado para la orden (
return_removed_carts_stock). - El estado de la orden (
order.status) se cambia a "7" (Error en la orden). - Se guarda la orden con el nuevo estado.
- Se envía un correo electrónico al equipo de soporte (
no_barcode_find_for_order) notificando el problema. - Se relanza la excepción, deteniendo el proceso de creación de la orden.
- Se revierte el stock de los productos que se habían descontado para la orden (
- Regla: Para las tiendas que pertenecen a las marcas Circle K (
5. Lógica por Store (Tienda Individual)
Cada tienda puede tener configuraciones únicas que alteran el flujo.
- Integración con Deliverect (Sistema de Gestión de Pedidos):
- Propósito: Enviar la información de la orden a Deliverect, una plataforma que centraliza los pedidos de diferentes canales (como Cheaf, Uber Eats, Rappi) y los inyecta directamente en el sistema de punto de venta (POS) de la tienda. Esto automatiza la recepción de pedidos en el restaurante.
- Condiciones para la Activación: Este flujo se ejecuta solo si se cumplen todas las siguientes condiciones al final del proceso de creación de la orden:
- La tienda tiene la integración habilitada (
store_id.use_deliverect_integrationesTrue). - La tienda tiene un ID de canal de Deliverect configurado (
store_id.deliverect_channel_idno es nulo). - El estado de la orden es "3" (confirmada/pagada).
- La tienda tiene la integración habilitada (
- Proceso de Envío (
create_deliverect_order):- Inicialización del Cliente: Se crea un cliente de API para Deliverect (
DeliverectClient) utilizando las credenciales seguras de la aplicación (ID de cliente, secreto de cliente, URL de la API y nombre del canal) definidas en la configuración del sistema. - Llamada a la API: Se invoca la función
create_deliverect_order, pasándole los siguientes parámetros:channel_link_id: El identificador único del canal de la tienda en Deliverect.order: El objeto completo de la orden, que contiene todos los detalles (productos, precios, información del cliente, etc.).client: El cliente de API recién creado.
- Resultado: Esta función se encarga de formatear los datos de la orden al estándar de Deliverect y enviarlos a través de su API. El manejo de errores dentro de esta función no se detalla aquí, pero generalmente implicaría registrar fallos de comunicación con Deliverect.
- Inicialización del Cliente: Se crea un cliente de API para Deliverect (
Manejo de Errores en la Creación de Órdenes
Durante el proceso de creación de una orden, el sistema implementa un manejo robusto de errores para asegurar la integridad de los datos y una experiencia clara para el usuario y el equipo de soporte:
-
Errores de Validación de Negocio:
Si ocurren errores como cupones inválidos, métodos de pago no permitidos, carrito vacío, versión de app no compatible, límites de compra superados, o reglas específicas de marca/tienda (ej. Globo, JOKR), el sistema:- Registra el error en los logs.
- Devuelve un mensaje de error claro al usuario.
- Lanza una excepción específica para mostrar mensajes personalizados en frontend.
-
Errores de Stock e Inventario:
Si se detecta falta de stock:- Se revierte cualquier cambio en el stock (rollback).
- La orden se marca como fallida.
- Se reembolsan créditos y cupones si corresponde.
- Se informa al usuario.
-
Errores de Pago:
Si ocurre un error durante el cobro:- Se registra el error.
- Se revierte el stock si ya había sido reservado.
- Se informa al usuario y la orden no se crea o se marca como no pagada.
-
Errores de Entrega/Logística:
Si falla la integración con el proveedor de logística:- Se cancela la orden y se revierte el stock.
- Si el usuario pagó con tarjeta, se le reembolsan créditos Cheaf.
- Se notifica al usuario y al equipo de soporte.
-
Errores de Integración o Excepciones No Controladas:
Si ocurre cualquier otro error inesperado:- Se envía un correo al equipo de soporte con detalles del error y el número de orden.
- Se registra el error en los logs.
- Se devuelve un error genérico al usuario.
-
Trazabilidad:
Todos los errores relevantes quedan registrados en los logs del sistema, incluyendo el tipo de error, el usuario afectado y el motivo.
Notificaciones
El sistema envía varias notificaciones durante el proceso:
- Notificación de orden creada: Se envía al usuario y a la tienda cuando se crea una orden exitosamente.
- Notificación de error: Se envía al usuario si hay algún problema con la orden.
- Notificaciones personalizadas: Para tiendas específicas, se pueden enviar notificaciones personalizadas.
Flujos Especiales
Existen algunos flujos especiales para tiendas o situaciones específicas:
- Flujo temporal Globo: Validaciones específicas para la tienda El Globo.
- Monitoreo de Fraude y Alertas por Correo Electrónico (Específico para México): Para las órdenes generadas en México, durante la finalización de la orden, se activan los siguientes mecanismos de monitoreo que pueden resultar en el envío de correos electrónicos al equipo de soporte (
support@cheaf.com):- Alerta por Tamaño Elevado del Carrito (
send_email_when_high_basket_size):- **Condiciones de Activación (todas deben cumplirse):
- País de la tienda: México (
order.store_id.cities.country == "MX"). - Estado de la orden: "3" (completada/confirmada).
- Método de pago: Tarjeta (
order.payment_type == "0"). - Costo de la orden: Mayor o igual a $1,000 MXN (
order.cost >= 1000).
- País de la tienda: México (
- Asunto del Correo: "Pedido id pagado con tarjeta mayor a $1,000"
- Propósito: Notificar al equipo de soporte sobre un pedido de alto valor pagado con tarjeta para verificar que no sea fraude.
- **Condiciones de Activación (todas deben cumplirse):
- Alerta General de Monitoreo de Fraude (
send_email_fraud_monitor_support):- **Condiciones de Activación (el país debe ser México y al menos una de las sub-condiciones de riesgo debe cumplirse):
- País de la tienda: México (
order.store_id.cities.country == "MX"). - Estado de la orden: Debe estar en la lista de estados activos o recientemente completados (ej. "1", "2", "3", "4", "8", "12", "9", "10", "11", "13", "14").
- Y al menos una de las siguientes condiciones de riesgo:
- La orden fue pagada con tarjeta (
order.is_card_payment()) Y el costo de la orden es mayor o igual a $500 MXN (order.cost >= 500). - El usuario ha gastado $500 MXN o más en todas sus órdenes en las últimas 48 horas (
order.app_user_id.get_cost_orders_48_hours() >= 500).
- La orden fue pagada con tarjeta (
- País de la tienda: México (
- Asunto del Correo: "Riesgo de contracargo user_id"
- Propósito: Alertar al equipo de soporte sobre un posible riesgo de contracargo debido a un gasto significativo con tarjeta en un corto período, para que se revise la confiabilidad del usuario.
- **Condiciones de Activación (el país debe ser México y al menos una de las sub-condiciones de riesgo debe cumplirse):
- Alerta por Tamaño Elevado del Carrito (
- Notificaciones personalizadas KK: Para la tienda KK, se envían notificaciones personalizadas.
Este documento proporciona una visión general del proceso de creación de órdenes. Para detalles técnicos específicos, consulte el código fuente en app/orders/v1/actions.py y app/orders/v1/views.py.