Cheaf Docs
Orders

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.
  • 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:

    1. Iteración sobre el Carrito: El sistema recorre cada producto en el carrito de compras.
    2. 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).
    3. 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.
    4. Actualización de Stock (remove_menu_stock): Si hay stock suficiente, se descuenta la cantidad comprada del inventario total del producto.
    5. 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:

    1. 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)).
    2. Descuentos Directos (aplica para ahorra o nunca): Se aplican descuentos preestablecidos directamente sobre los productos (order.total_with_discount()). La diferencia se conoce como direct_discount y se resta del subtotal.
    3. 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_discount segú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.
      • El coupon_discount se resta del subtotal.
    4. Uso de Créditos Cheaf (si el usuario opta por ello):
      • Se determina la cantidad de cheaf_credits a 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_credits aplicados se restan del subtotal. El subtotal resultante no puede ser menor que cero.
    5. 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 el delivery_feed.
    6. Monto Final a Pagar (card_charge o monto en efectivo): Es el subtotal de productos (después de todos los descuentos y créditos aplicados a los productos) más el delivery_feed restante (después de aplicar credits_used_for_delivery a la entrega). Este es el importe final que se intentará cobrar.
  • 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:
      1. Se está utilizando un cupón en la orden.
      2. La tienda tiene la configuración promo_plus_0_allowed establecida en False (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).
      3. 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.
      4. 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ón InvalidCoupon, lo que detiene el proceso de creación de la orden.
  • 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:

    1. 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).
    2. 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.
    3. Intento de Cobro (pay_order):
      • Se invoca la función pay_order con 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.
    4. 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_id y su estado cambia a solicitado (STATUS_REQUESTED).
      • 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).

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:

  1. 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).
  2. 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.
  3. 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_delivery se confirma como True.
        • 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.
    • 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).
      • 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.

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) y credits_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 como CreditsLogs.NEW_ORDER.
    • 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.
    • 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_suscriber enviando el email del usuario (order.app_user_id.email) y el ID de la orden (order.id).
    • 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 como DebtLogs.ORDER_PAID.
      • Se descuentan los créditos correspondientes del saldo del usuario (user.remove_credits), registrando la fuente como CreditsLogs.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 como DebtLogs.ORDER_PAID.
  • 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).
  • 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_delivery es True): Se utiliza la ubicación específica que el usuario seleccionó para esa entrega, obteniéndola a través del location_id enviado en la solicitud.
      • Si la orden es para recoger en tienda (for_delivery es False): Se utiliza la última ubicación actual registrada para el usuario (current=True).
    • Acción: Se crea un nuevo registro en la tabla OrderLocations que 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.
    • 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.

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).
  • 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 == 825 y order.status == "3"):
      1. Se obtiene el horario de la tienda y la hora de cierre para recolección (close_hour).
      2. 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).
      3. 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)).
  • 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étodo order.generate_barcodes_from_carts() llamado desde OrderAction.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):
      1. Para cada cart (tipo de producto) en la orden:
      2. Para cada unidad (cart.amount) de ese producto:
        • Se busca un PromotionBarcode disponible 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 PromotionCodeCartOrder que vincula el carrito, la orden, el código de barras encontrado y el monto.
        • El PromotionBarcode utilizado 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).
    • Flujo 2: Otros Métodos de Pago (Ej. Efectivo con Cupón - else block in generate_barcodes_from_carts):
      • Este flujo intenta aplicar el valor de un cupón (self.coupon_value), almacenado como remaining_coupon_discount, utilizando los PromotionBarcode disponibles 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 de PromotionBarcode disponible globalmente (remaining_coupon_discount not in available_amounts), se lanza una excepción BarCodeNotFound. 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):
        1. Los cart (tipos de producto en la orden) se procesan ordenados por precio, de mayor a menor.
        2. Para cada unidad individual dentro de cada cart (ej. si un cart tiene amount = 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 el remaining_coupon_discount y 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:
                1. El precio del artículo (cart.price) es mayor o igual al remaining_coupon_discount actual.
                2. El valor del remaining_coupon_discount actual es una denominación de PromotionBarcode disponible globalmente (remaining_coupon_discount in available_amounts).
              • Si AMBAS condiciones para Estrategia A se cumplen:
                • Se intenta la Acción de Estrategia A: Buscar y asignar un PromotionBarcode (filtrado por main_account) por el monto exacto del remaining_coupon_discount.
                • Si se asigna un código exitosamente: remaining_coupon_discount se establece a 0 (cupón totalmente consumido), y se_aplico_codigo_en_esta_pasada_del_bucle_interno = True.
                • (Estrategia B se omite en esta pasada del bucle interno).
            • Paso 2: Si las condiciones de Estrategia A NO se cumplieron, Evaluar y (si aplica) Ejecutar Estrategia B (bloque else del código Python):
              • Condiciones para Estrategia B: El cart.price del artículo actual es una denominación de PromotionBarcode disponible 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 por main_account) por el monto del cart.price.
                • Si se asigna un código exitosamente: remaining_coupon_discount se reduce en cart.price, y se_aplico_codigo_en_esta_pasada_del_bucle_interno = True.
            • Paso 3: Condición de Salida del Bucle Interno para la unidad actual:
              • Si se_aplico_codigo_en_esta_pasada_del_bucle_interno sigue siendo False (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_discount es 0 o menos, el bucle interno también termina para esta unidad.
          • (Fin del Bucle Interno por Unidad)
        • (Fin del bucle por cada unidad de artículo)
    • **Manejo de Errores (si generate_barcodes_from_carts falla, por ejemplo, por BarCodeNotFound):
      1. Se revierte el stock de los productos que se habían descontado para la orden (return_removed_carts_stock).
      2. El estado de la orden (order.status) se cambia a "7" (Error en la orden).
      3. Se guarda la orden con el nuevo estado.
      4. Se envía un correo electrónico al equipo de soporte (no_barcode_find_for_order) notificando el problema.
      5. Se relanza la excepción, deteniendo el proceso de creación de la orden.

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:
      1. La tienda tiene la integración habilitada (store_id.use_deliverect_integration es True).
      2. La tienda tiene un ID de canal de Deliverect configurado (store_id.deliverect_channel_id no es nulo).
      3. El estado de la orden es "3" (confirmada/pagada).
    • Proceso de Envío (create_deliverect_order):
      1. 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.
      2. 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.
      3. 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.

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:

  1. Notificación de orden creada: Se envía al usuario y a la tienda cuando se crea una orden exitosamente.
  2. Notificación de error: Se envía al usuario si hay algún problema con la orden.
  3. Notificaciones personalizadas: Para tiendas específicas, se pueden enviar notificaciones personalizadas.

Flujos Especiales

Existen algunos flujos especiales para tiendas o situaciones específicas:

  1. Flujo temporal Globo: Validaciones específicas para la tienda El Globo.
  2. 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).
      • 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.
    • 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).
      • 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.
  3. 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.