Caso de Uso - Bproc - Notificación automática de pedidos de alto importe

Notificación automática de pedidos de alto importe

Objetivo

Implementar una automatización que notifique al responsable comercial cada vez que se registre un pedido de venta cuyo importe supere un monto determinado.

Este caso demuestra cómo un BPROC en modalidad Webhook permite ejecutar procesos en segundo plano sin intervención del usuario ni modificaciones sobre el comportamiento estándar del ERP.

Problema de negocio

En muchas organizaciones los pedidos de alto importe requieren un seguimiento especial o una aprobación temprana.

Sin embargo, el proceso suele depender de que algún usuario recuerde informar manualmente al gerente comercial.

Como consecuencia:

  • Los pedidos importantes pueden permanecer varias horas sin ser revisados.

  • Se retrasan aprobaciones y decisiones comerciales.

  • Se incrementa la dependencia de tareas manuales.

Solución propuesta

Cada vez que se crea o actualiza un Pedido de Venta, GO ejecuta automáticamente un Guest Code mediante un BPROC configurado en modalidad Webhook.

El Guest Code obtiene la información del pedido, calcula su importe total y lo compara con un monto mínimo configurable.

Si el pedido supera ese valor, envía una notificación al destinatario configurado.

Arquitectura de la solución

Usuario

Pedido de Venta

BPROC Transacción (Webhook)

Guest Code

Obtención del pedido

Cálculo del importe total

Evaluación del monto

Envío de notificación

Componentes utilizados

Guest Code

El Guest Code implementa toda la lógica de negocio.

Su responsabilidad es:

  • recibir la información enviada por el BPROC;

  • obtener el detalle completo del pedido;

  • calcular el importe total;

  • comparar el resultado con el monto configurado;

  • enviar la notificación cuando corresponda.

En este caso se implementó un Guest Code denominado: pedido-importante

El endpoint generado será utilizado posteriormente por el BPROC.

import json

from pydantic import ValidationError

from finnegans.api import ApiException, obtener_entidad
from finnegans.bproc import BProcWebhook
from finnegans.mail import enviar_email
from finnegans.scripting import HTTPResponse, request

MONTO_MINIMO_DEFAULT = 400.0


def main():
    # 1. Parsear body del webhook
    raw = request.get_body() or b"{}"
    try:
        bproc = BProcWebhook(json.loads(raw.decode()))
    except json.JSONDecodeError as e:
        return HTTPResponse(400, body=json.dumps({"error": f"Body inválido — no es JSON: {e}"}, ensure_ascii=False))
    except ValidationError as e:
        return HTTPResponse(400, body=json.dumps({"error": f"Payload de BProc inválido: {e}"}, ensure_ascii=False))

    codigo = bproc.object.code.strip()
    if not codigo:
        return HTTPResponse(400, body=json.dumps({"error": "El campo 'object.code' es requerido y está vacío."}, ensure_ascii=False))

    notify_to = (bproc.params.get("notify-to") or bproc.params.get("notifyTo") or "").strip()
    if not notify_to:
        return HTTPResponse(400, body=json.dumps({"error": "El parámetro 'notify-to' es requerido en params."}, ensure_ascii=False))

    monto_minimo_raw = bproc.params.get("monto-minimo") or bproc.params.get("montoMinimo")
    if monto_minimo_raw is None or str(monto_minimo_raw).strip() == "":
        umbral = MONTO_MINIMO_DEFAULT
    else:
        try:
            umbral = float(monto_minimo_raw)
        except (TypeError, ValueError) as e:
            return HTTPResponse(400, body=json.dumps({"error": f"Parámetro 'monto-minimo' inválido: {e}"}, ensure_ascii=False))

    # 2. Obtener pedido de venta
    try:
        pedido = obtener_entidad("pedidoVenta", codigo)
    except ApiException as e:
        return HTTPResponse(502, body=json.dumps({"error": str(e)}, ensure_ascii=False))

    # 3. Calcular monto total (ítems + conceptos)
    subtotal = sum(
        float(item.get("Cantidad") or 0) * float(item.get("Precio") or 0)
        for item in (pedido.get("Items") or [])
    )
    impuestos = sum(float(c.get("ConceptoImporte") or 0) for c in (pedido.get("Conceptos") or []))
    monto_total = subtotal + impuestos

    if monto_total < umbral:
        return HTTPResponse(200, body=json.dumps({
            "notificado": False,
            "codigo": codigo,
            "monto_total": monto_total,
            "umbral": umbral,
            "mensaje": "El pedido no supera el monto mínimo configurado.",
        }, ensure_ascii=False))

    # 4. Armar y enviar email
    moneda = pedido.get("MonedaCodigo") or ""
    filas_items = ""
    for item in pedido.get("Items") or []:
        cantidad = float(item.get("Cantidad") or 0)
        precio = float(item.get("Precio") or 0)
        filas_items += f"""
        <tr>
            <td style="border:1px solid #ccc; padding:6px;">{item.get("ProductoCodigo", "")}</td>
            <td style="border:1px solid #ccc; padding:6px; text-align:right;">{cantidad:g}</td>
            <td style="border:1px solid #ccc; padding:6px; text-align:right;">{precio:,.2f}</td>
            <td style="border:1px solid #ccc; padding:6px; text-align:right;">{cantidad * precio:,.2f}</td>
        </tr>"""

    html = f"""
    <html>
    <body style="font-family: Arial, sans-serif; color: #333;">
        <h2 style="color: #c0392b;">Pedido de venta con monto importante</h2>
        <p>Se registró un pedido cuyo monto total supera el umbral configurado ({umbral:,.2f} {moneda}).</p>
        <table style="border-collapse:collapse; margin-bottom:16px;">
            <tr><td style="padding:4px 12px 4px 0;"><strong>Pedido:</strong></td><td>{pedido.get("Nombre", "")}</td></tr>
            <tr><td style="padding:4px 12px 4px 0;"><strong>Cliente:</strong></td><td>{pedido.get("Cliente", "")}</td></tr>
            <tr><td style="padding:4px 12px 4px 0;"><strong>Comprobante:</strong></td><td>{pedido.get("NumeroComprobante", "")}</td></tr>
            <tr><td style="padding:4px 12px 4px 0;"><strong>Fecha:</strong></td><td>{pedido.get("Fecha", "")}</td></tr>
            <tr><td style="padding:4px 12px 4px 0;"><strong>Empresa:</strong></td><td>{pedido.get("EmpresaCodigo", "")}</td></tr>
            <tr><td style="padding:4px 12px 4px 0;"><strong>Monto total:</strong></td><td><strong>{monto_total:,.2f} {moneda}</strong></td></tr>
        </table>
        <p><strong>Descripción:</strong> {pedido.get("Descripcion", "") or "—"}</p>
        <div style="overflow-x:auto;">
        <table style="border-collapse:collapse; width:100%; font-size:12px; margin-top:12px;">
            <thead>
                <tr style="background-color:#2c3e50; color:#fff;">
                    <th style="border:1px solid #ccc; padding:6px;">Producto</th>
                    <th style="border:1px solid #ccc; padding:6px;">Cantidad</th>
                    <th style="border:1px solid #ccc; padding:6px;">Precio</th>
                    <th style="border:1px solid #ccc; padding:6px;">Subtotal</th>
                </tr>
            </thead>
            <tbody>{filas_items}</tbody>
        </table>
        </div>
        <br/>
        <p style="font-size:11px; color:#999;">Notificación generada automáticamente por Finnegans GO · pedido_importante</p>
    </body>
    </html>
    """

    asunto = f"Pedido importante: {pedido.get('Nombre', codigo)} — {monto_total:,.2f} {moneda}"
    try:
        mail_response = enviar_email(asunto=asunto, body=html, to=notify_to, html=True)
    except ApiException as e:
        return HTTPResponse(502, body=json.dumps({"error": f"Error al enviar email: {e}"}, ensure_ascii=False))

    return HTTPResponse(200, body=json.dumps({
        "notificado": True,
        "codigo": codigo,
        "monto_total": monto_total,
        "umbral": umbral,
        "destinatario": notify_to,
        "mail": mail_response,
    }, ensure_ascii=False, default=str))

Parámetros configurables

La solución permite parametrizar determinados valores sin necesidad de modificar el código.

En este caso se utilizan:

  • dirección de correo electrónico del destinatario;

  • monto mínimo que determina cuándo debe enviarse la notificación.

Esto permite reutilizar la misma lógica en distintos escenarios simplemente modificando la configuración del BPROC.

Configuración del BPROC

Información general

Se crea un nuevo BPROC con las siguientes características.
Campo
Valor
Nombre
Pedido_importante
Código
pedido_importante
Tipo
Transacción
Aplicación
Aplicación Custom


Configuración de ejecución

El BPROC se configura para ejecutarse:

  • sobre el documento Pedido de Venta;

  • en modalidad Webhook;

  • durante los eventos Crear y Actualizar.

La URL configurada corresponde al endpoint generado por el Guest Code.

Además, se define un parámetro que permitirá indicar el destinatario de la notificación.

Finalmente, el BPROC se habilita para el tipo de documento correspondiente mediante la configuración del documento.

¿Por qué se utiliza Webhook?

En este escenario no se requiere ninguna acción por parte del usuario.

La notificación debe ejecutarse automáticamente cada vez que el sistema detecte un pedido que cumpla las condiciones definidas.

Al tratarse de un proceso automático:

  • no es necesario agregar botones ni actividades;

  • el usuario continúa trabajando normalmente;

  • el procesamiento ocurre en segundo plano.

La automatización garantiza que ningún pedido importante dependa de una acción manual para ser informado.

Flujo de ejecución

  1. El usuario crea o actualiza un Pedido de Venta.

  2. GO detecta el evento configurado.

  3. Se ejecuta automáticamente el BPROC.

  4. GO construye el payload correspondiente.

  5. El payload se envía al Guest Code.

  6. El Guest Code obtiene la información completa del pedido.

  7. Se calcula el importe total.

  8. Se compara el resultado con el monto configurado.

  9. Si la condición se cumple, se envía la notificación al destinatario definido.

  10. La ejecución finaliza sin interrumpir la operación del usuario.

Responsabilidad de cada componente

GO

  • Detecta el evento.

  • Construye el payload.

  • Ejecuta automáticamente el BPROC.

BPROC

  • Define cuándo debe ejecutarse la automatización.

  • Envía la información al servicio externo.

No implementa reglas de negocio.

Guest Code

  • Recupera la información necesaria.

  • Calcula el importe total del pedido.

  • Evalúa la condición definida.

  • Ejecuta el envío de la notificación.

Toda la lógica funcional reside en este componente.

Resultado

Cada vez que un pedido supera el monto establecido, la notificación se genera automáticamente sin intervención del usuario.

La organización incorpora un mecanismo de seguimiento para operaciones relevantes utilizando BPROC y Guest Code como herramienta de automatización, evitando procesos manuales y reduciendo el riesgo de que pedidos importantes pasen desapercibidos.


¿Qué aprendemos de este caso?

Este caso permite comprender el funcionamiento de un BPROC en modalidad Webhook.

El BPROC no contiene la lógica de negocio ni decide cuándo enviar una notificación; únicamente detecta el evento configurado y envía el contexto de la operación al Guest Code.

Es el Guest Code quien interpreta la información recibida, aplica las reglas definidas por el negocio y determina si corresponde ejecutar la acción.

Este patrón puede reutilizarse para cualquier automatización que deba ejecutarse de forma automática ante eventos del sistema, sin modificar el código estándar del ERP.

1 me gusta