Visualización de stock en cada deposito por producto

Uso de Guest Code con Bproc de maestro

Este documento muestra como utilizar la herramienta guest code y un bproc de tipo maestro para visualizar el stock de un producto en cada depósito

Objetivo

Permitir al usuario visualizar, directamente desde cada producto, el desglose detallado del stock físico existente en cada uno de los depósitos disponibles.

Descripción del caso

La idea de este caso es poder ver desde cada producto cuánto stock hay en cada depósito. Para esto, el guest code utiliza el resumen de stock por depósito para informar las cantidades.

Configuraciones

Para poder visualizar el stock en cada depósito desde un producto se deben configurar:
1 - Guest code: Para realizar el script utilizamos la IA Fini. Se le indica la necesidad junto con el contexto necesario y luego de algunas iteraciones se obtiene el script deseado:

import json


from finnegans.scripting import request, HTTPResponse
from finnegans.api import get_reporte




def build_html(producto: str, rows: list[dict]) -> str:
    # Agrupar cantidad por depósito
    stock: dict[str, float] = {}
    for row in rows:
        deposito = row.get("DEPOSITO") or "Sin depósito"
        cantidad = float(row.get("CANTIDAD1") or 0)
        stock[deposito] = stock.get(deposito, 0) + cantidad


    if not stock:
        return f"""<!DOCTYPE html><html><body><p>Sin stock registrado para <b>{producto}</b>.</p></body></html>"""


    max_cantidad = max(stock.values()) or 1
    total = sum(stock.values())


    # Construir filas del gráfico: etiqueta arriba, barra abajo (layout compacto)
    filas_barras = ""
    for dep, cant in sorted(stock.items(), key=lambda x: -x[1]):
        pct = cant / total * 100
        filas_barras += f"""
        <div class="bar-row">
          <div class="bar-label">{dep}<span class="bar-val">{cant:g} &nbsp;<em>{pct:.1f}%</em></span></div>
          <div class="bar-track"><div class="bar-fill" style="width:{pct:.2f}%"></div></div>
        </div>"""


    return f"""<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Stock por depósito</title>
  <style>
    *, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
    body {{ font-family: system-ui, sans-serif; background: #fff; color: #1e293b; padding: 14px 16px; font-size: 13px; }}


    #titulo {{ font-size: 1rem; font-weight: 700; color: #1e3a5f; margin-bottom: 8px !important; margin-top: 8px !important; }}
    .meta {{ font-size: .78rem; color: #64748b; margin-bottom: 14px; }}
    .meta strong {{ color: #1e3a5f; }}


    .bar-row {{ margin-bottom: 10px; }}
    .bar-label {{
      display: flex; justify-content: space-between; align-items: baseline;
      font-size: .8rem; font-weight: 600; color: #334155; margin-bottom: 4px;
    }}
    .bar-val {{ font-weight: 400; color: #64748b; font-size: .78rem; }}
    .bar-val em {{ font-style: normal; color: #3b82f6; font-weight: 600; }}
    .bar-track {{
      height: 18px; background: #e2e8f0; border-radius: 6px; overflow: hidden;
    }}
    .bar-fill {{
      height: 100%; background: linear-gradient(90deg, #3b82f6, #60a5fa);
      border-radius: 6px; min-width: 4px;
    }}
  </style>
</head>
<body>
  <h1 id="titulo">Stock por depósito</h1>
  <p class="meta">{producto} &nbsp;·&nbsp; Total: <strong>{total:g}</strong> u.</p>


  {filas_barras}
</body>
</html>"""




def main():
    raw = request.get_body() or b"{}"
    try:
        body = json.loads(raw.decode())
    except Exception as e:
        return HTTPResponse(400, body=json.dumps({"error": f"Body inválido — no es JSON: {e}"}, ensure_ascii=False))


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


    try:
        result = get_reporte(
            "resumenStockPorDeposito",
            params={
                "PARAMWEBREPORT_soloStockNoCero": "true",
                "PARAMWEBREPORT_soloDepositos": "1",
                "PARAMWEBREPORT_fecha": "getCurrentDate",
                "PARAMWEBREPORT_producto": producto,
            },
        )
    except Exception as e:
        return HTTPResponse(502, body=json.dumps({"error": f"Error al consultar el reporte: {e}"}, ensure_ascii=False))


    # Si la API devuelve un dict con "error", propagamos el error
    if isinstance(result, dict) and "error" in result:
        status = int(result.get("status") or 502)
        return HTTPResponse(status, body=json.dumps({"error": result["error"]}, ensure_ascii=False))


    # Normalizar a lista de dicts
    if isinstance(result, dict):
        rows = [v for v in result.values() if isinstance(v, dict)]
    elif isinstance(result, list):
        rows = [r for r in result if isinstance(r, dict)]
    else:
        rows = []


    html = build_html(producto, rows)
    return HTTPResponse(200, headers={"Content-Type": "text/html; charset=utf-8"}, body=html)

2 - Bproc: En este caso se utilizó un bproc de tipo maestro y modo manual


En el campo URL se pega la obtenida en el guest code

Resultado

Al ingresar a un producto desde el maestro, se verá un botón en la toolbar

image

Al hacer clic se ve la siguiente información