Koru Calculator Widget

Calculadora de producto para la PDP de VTEX. El comprador ingresa una medida y el widget calcula cuántas unidades necesita, valida precio y stock reales, y las agrega al carrito.

El Koru Calculator Widget es una app de VTEX IO que se declara como bloque de theme en la página de producto. Resuelve el caso de los productos que se venden por unidad pero se consumen por medida: pintura que rinde 10 m² por lata, alimento de 20 kg por bolsa, cerco de 10 m por rollo.

El comprador ingresa la medida que necesita (m², metros, kg, m³ o unidades) y el widget calcula redondeo_arriba(medida / rendimiento), lee precio y stock reales del Catalog de VTEX, muestra el total y agrega el ítem al carrito nativo con la cantidad exacta.

Esta app no se configura desde el panel de Koru

A diferencia de otras apps de Koru Suite, acá el panel de Koru se usa únicamente para la licencia (habilitar el website + la app). El comportamiento funcional se define en dos lugares del lado de la tienda: las especificaciones del catálogo de VTEX (qué fórmula usar y cuánto rinde cada producto) y los props del bloque (título, texto del botón, Website ID). La fórmula y el rendimiento se leen siempre del catálogo: no hay forma de fijarlos desde el bloque.

Qué no hace el Calculator Widget

No modifica precios ni stock en VTEX; no crea productos, SKUs ni especificaciones; no procesa pagos ni interviene en el checkout más allá de dejar una nota de trazabilidad en el OrderForm; no permite editar fórmulas desde un panel; y no reemplaza al buy button del theme para compras sin cálculo.

Antes de empezar

Responsables recomendados

TareaResponsable habitual
Instalar la app en la cuenta VTEX y linkear/publicar el themeAgencia VTEX, desarrollador o líder técnico
Activar la app para el sitio en Koru SuiteRed Clover / administrador de Koru Suite
Cargar el Website ID en el bloqueLíder técnico o implementador del theme
Definir en qué categorías va el widgetEcommerce manager
Cargar rendimiento y unidad de medida en el catálogoEquipo de catálogo o producto
Validar precio, stock y cantidad calculada en la PDPQA o ecommerce manager
Ajustar la apariencia por CSSFrontend del theme

Una misma persona puede cubrir varios roles en una tienda pequeña.

Accesos necesarios

Antes de instalar, verificá que contás con:

  • Una sesión válida de VTEX Admin en la cuenta de la tienda.
  • La VTEX CLI oficial instalada y actualizada.
  • Permisos para instalar apps y linkear el theme en el workspace elegido.
  • Acceso a Catálogo → Especificaciones para crear y asignar los campos de rendimiento y unidad de medida.
  • El Website ID del ecommerce en Koru Suite, con el Calculator Widget activo para ese sitio.

La app no pide ni almacena credenciales del merchant: el backend usa el authToken propio de la app para hablar con el Catalog. No hace falta generar AppKey/AppToken.

Credenciales e identificadores

Credencial¿Se ingresa?DóndeDe dónde sale
Koru Website ID requiredProp websiteId del bloque calculator-widget (Site Editor o product.jsonc)Panel de Koru, ficha del website de la tienda. Es el único dato de Koru que se carga a mano. Sin él, el widget muestra el cartel de "no disponible" y nunca renderiza la calculadora.
Koru App IDNoFijo en el código de la appYa viene incorporado en el build. No es configurable por el implementador.
URL de KoruNoFijo en el códigohttps://www.korusuite.com
AppKey / AppToken de VTEXNo se usanLa app usa su propio token de VTEX IO.

El Website ID no es un secreto ni una contraseña: viaja en el frontend como query param al validar la licencia y queda visible en el HTML del bloque. Koru solo valida que esa combinación Website ID + App ID esté activa. El Client Secret de la app nunca va al frontend.

Instalación

Esta app se instala solo como dependencia de un theme de VTEX IO (Store Framework). No existe versión por <script> ni por Google Tag Manager.

Instalá la app en la cuenta

vtex login {store-account}
vtex use {workspace} --create
vtex whoami                                  # verificá cuenta y workspace antes de seguir
vtex install soluciones4fpartnerar.calculator-widget@0.x
vtex list                                    # confirmá que aparece en la lista

La app se publica desde la cuenta oficial soluciones4fpartnerar: el App ID de VTEX se instala tal cual en la cuenta de tu tienda.

Declará la dependencia en el theme

En el manifest.json del store-theme:

{
  "dependencies": {
    "soluciones4fpartnerar.calculator-widget": "0.x"
  }
}

Los cambios en dependencies no son hot-reload: reiniciá el vtex link del theme después de agregarla.

Declará el bloque en la PDP

El bloque se llama calculator-widget y requiere contexto de producto (vtex.product-context): tiene que estar dentro de store.product o de un bloque que provea el contexto del producto. Fuera de ahí no obtiene skuId y no renderiza nada.

En el product.jsonc del theme:

{
  "flex-layout.col#details-column": {
    "children": [
      "vtex.store-components:product-name",
      "flex-layout.row#product-prices",
      "calculator-widget",
      "flex-basic#buy-button"
    ]
  },
 
  "calculator-widget": {
    "props": {
      "title": "Calculá cuánto necesitás",
      "ctaText": "Agregar al carrito",
      "websiteId": "TU-WEBSITE-ID"
    }
  }
}
  • Si el theme referencia bloques de dependencias con prefijo completo, usá soluciones4fpartnerar.calculator-widget:calculator-widget en children y en la key de declaración.
  • El bloque está declarado con "allowed": []: no acepta bloques hijos.

Validá en la PDP

Abrí un producto de la categoría donde pusiste el bloque. Deberías ver brevemente "Verificando disponibilidad…" y después la calculadora con los campos que corresponden a la unidad de medida del producto.

Múltiples instancias en la misma tienda

El bloque admite el patrón estándar de VTEX de instancias con #id, por ejemplo para una variante en otro punto de la PDP:

{
  "calculator-widget#compacta": {
    "props": {
      "title": "Calculadora rápida",
      "websiteId": "TU-WEBSITE-ID",
      "blockClass": "compacta"
    }
  }
}
  • Cada instancia necesita su propio websiteId: el prop no se hereda entre instancias.
  • El bloque no fija una posición propia — se renderiza donde lo pongas en el árbol de bloques. Como necesita contexto de producto, todas las instancias tienen que vivir dentro de la PDP.

El componente no declara blockClass en su schema, así que el handle modificador derivado (...--compacta) no está garantizado. Si necesitás estilar instancias por separado, verificá primero en el HTML renderizado qué clases se generan antes de escribir el CSS.

Personalización visual por CSS

Los estilos por defecto salen de Tachyons con los design tokens de VTEX (t-heading-5, c-on-base, bg-action-primary, b--muted-4), así que el widget hereda automáticamente colores, tipografía y estilo de botón del styles de la cuenta. Los .css de la app no hardcodean colores.

Cada elemento lleva además su CSS handle. El archivo de override en el theme es:

styles/css/soluciones4fpartnerar.calculator-widget.css
.cta {
  border-radius: 0;
  text-transform: uppercase;
}
 
.stockAlert {
  font-weight: 600;
}

Estilá con los handles directos en el archivo de override de la app. No uses :global() apuntando a esta app desde el CSS de otra: el build de VTEX lo rechaza.

Handles disponibles:

GrupoHandles
Contenedor y títulocontainer, title
Campos de entradafields, field, fieldLabel, fieldInput, formulaSelect
Resumen del cálculosummary, summaryRow, summaryLabel, summaryValue
Estadosloading, error, stockAlert, addedMsg
Botóncta, ctaDisabled
Gate de licencia KorukoruLoading, koruError, koruErrorTitle, koruErrorText

Actualizar y desinstalar

Seleccioná la cuenta/workspace correcto, confirmalo y volvé a instalar el rango:

vtex whoami
vtex install soluciones4fpartnerar.calculator-widget@0.x
vtex list

La desinstalación se aplica al workspace actual:

vtex uninstall soluciones4fpartnerar.calculator-widget

El websiteId vive en la configuración del bloque del theme, no en app settings: si desinstalás y reinstalás la app, el prop sigue en el theme.

Activación en Koru Suite

La app está gateada. Si el website + la app no están activos en Koru, en lugar de la calculadora se muestra el cartel "Widget no disponible — Esta aplicación no está habilitada para este sitio. Comuníquese con Korusuite."

Obtené el Website ID

Sale del panel de Koru, en la ficha del website de la tienda. Quien activa la app para ese website es Koru Suite; el implementador solo copia el ID.

Cargalo en el bloque

Prop websiteId de calculator-widget, en el Site Editor o directamente en product.jsonc. Se guarda como parte de la configuración del bloque del theme: no son app settings de VTEX ni Master Data.

Confirmá que quedó activa

Entrá a una PDP con el bloque. Deberías ver brevemente "Verificando disponibilidad…" y después la calculadora. Si en cambio ves el cartel "Comuníquese con Korusuite", el Website ID está mal cargado, vacío, o la app no está habilitada para ese website.

El gate corre client-side desde el navegador del comprador. No hay login de Koru ni sesión de VTEX Admin involucrada: lo único que se valida es que el par Website ID + App ID esté autorizado.

SituaciónQué hace la app
Sin websiteId en el bloqueCartel de "no disponible" (no consulta a Koru)
Koru responde autorizadoRenderiza el widget y cachea la respuesta en localStorage por 300 segundos (5 min)
Koru responde no autorizadoCartel de "no disponible". Las respuestas negativas no se cachean: apenas se activa la app en Koru, el siguiente page load ya la muestra
Falla de red o respuesta sin veredictoReintenta hasta 3 veces con backoff exponencial (1 s, 2 s). Si igual falla, muestra el mismo cartel
Preview del editor de KoruAutoriza sin consultar a la red

El backend de cálculo de la app es una ruta pública y no pasa por el gate de Koru. El gate controla si el widget se muestra, no el acceso al endpoint.

Configuración

La configuración se reparte en tres lugares, y ninguno es el panel de Koru:

  1. Props del bloque — títulos y licencia.
  2. Especificaciones del catálogo de VTEX — qué fórmula usa cada producto y cuánto rinde. Es acá donde vive la lógica.
  3. CSS del theme — apariencia (ver Instalación).

Props del bloque

Campo (Site Editor)PropObligatorio / DefaultQué controla
TítulotitleOpcional — default "Calculá cuánto necesitás"Encabezado del widget. Si se pasa vacío, no se renderiza el <h3>.
Texto del botónctaTextOpcional — default "Agregar al carrito"Texto del CTA. Durante el alta muestra "Agregando…" y al confirmar "✓ ¡Agregado!" (textos fijos).
Koru Website IDwebsiteIdObligatorio — default ""Licencia. Vacío = widget denegado.

La fórmula y el rendimiento no son props. El bloque no los expone y no hay forma de sobrescribirlos desde el Site Editor: salen del catálogo.

Creá dos campos de especificación en VTEX Admin (Catálogo → Especificaciones, o desde el grupo de especificaciones de la categoría) y asignalos a las categorías donde va el widget. La app los busca por nombre exacto, probando los candidatos en orden:

DatoNombres aceptados (en orden)TipoNivelObligatorio
Rendimiento — cuánto rinde/cubre 1 unidad de ventaValorRendimientoRendimientoNumérico, o texto con un número (decimal con punto: 2.5)SKU o productoSí en la práctica: si falta, el rendimiento cae a 1 y la cantidad calculada será la medida cruda
Unidad de medida — en qué se mide ese rendimientoUnidadMedidaUnidadRendimientoTextoProductoSí, si querés que el widget elija la fórmula solo

Reglas a tener en cuenta:

  • El nombre del campo es lo que importa, no el grupo de especificaciones.
  • El rendimiento tiene que ser un número mayor que 0. Vacío, 0 o no numérico se ignora y sigue la cascada.
  • La unidad se lee del producto, no del SKU, y se usa para dos cosas: elegir la fórmula y mostrar Rendimiento X <unidad> c/u en el resumen.

Unidad de medida → fórmula que se activa

El valor de UnidadMedida define qué fórmula usa el widget y qué campos le pide al comprador:

UnidadMedidaFórmula(s) que activaCampos que pideCálculo del bruto
kgpesoPeso necesario (kg)peso
m2area y superficie → el comprador elige en un selectorÁrea: ancho + alto · Superficie: m² directosancho * alto o superficie
mlongitudLargo (m)largo
m3volumenancho + alto + profundidadancho * alto * profundidad
uncantidadCantidad (un)cantidad
vacía o no reconocidalas 7 fórmulas en un selectorsegún la elegidasegún la elegida

perimetro no está mapeada a ninguna unidad. Como el bloque no permite fijar una fórmula, la única forma de ofrecerla es dejar el producto con unidad vacía o no reconocida: ahí se listan las 7 y el comprador elige.

Alias aceptados (se normalizan mayúsculas, espacios, acentos y puntos):

Se resuelve comoValores aceptados
kgkg, kgs, kilo, kilos, kilogramo, kilogramos
m2m2, , mt2, metro cuadrado, metros cuadrados
m3m3, , mt3, metro cúbico, metros cúbicos
mm, ml, mt, metro, metros, metro lineal, metros lineales
unun, u, unidad, unidades

Cualquier otro valor cuenta como "no reconocido" y se ofrecen las 7 fórmulas. Recomendación: cargar siempre la forma corta (kg, m2, m, m3, un).

Fórmulas disponibles

idEtiqueta visibleExpresiónInputs
areaÁrea (ancho × alto)ancho * altoancho (m), alto (m)
perimetroPerímetro (2 × (ancho + alto))2 * (ancho + alto)ancho (m), alto (m)
volumenVolumen (ancho × alto × profundidad)ancho * alto * profundidadancho, alto, profundidad (m)
longitudLongitud lineal (largo)largolargo (m)
pesoPeso (kg necesarios)pesopeso (kg)
superficieSuperficie a cubrir (m² directos)superficiesuperficie (m²)
cantidadCantidad (unidades)cantidadcantidad (un)

Las fórmulas no son editables desde ningún panel: viven en el código de la app. Agregar o modificar una requiere publicar una versión nueva. El cliente nunca envía la expresión: manda un formulaId que el backend valida contra el whitelist, y la evaluación corre en modo restringido (sin eval, sin new Function, sin definición de funciones ni asignaciones).

Cómo calcula

Cascada del rendimiento

El rendimiento se resuelve server-side, tomando el primer valor válido (mayor que 0) de esta secuencia:

Spec de SKU vía Catalog privado

Cubre SKUs con distinto rendimiento dentro del mismo producto (por ejemplo, alfombra chica de 1.2 m² vs grande de 1.8 m²). Lectura inmediata: no depende del reindex.

Spec especificadora de SKU del search público

Nivel item del search público del Catalog.

Spec de producto del search público

Nivel producto. Depende del reindex del Catalog.

Override del payload

El backend acepta un rendimientoUnidad en el body, pero el bloque nunca lo envía: solo es alcanzable llamando al endpoint a mano.

1 como último recurso

Si ninguna fuente devuelve un valor válido, el rendimiento es 1 y la cantidad calculada termina siendo la medida cruda.

Cálculo final

bruto          = expresión(inputs del comprador)
cantidad_final = max(0, ceil(bruto / rendimiento))
precio_total   = precio_unitario * cantidad_final
alerta_stock   = cantidad_final > stock_disponible

precio_unitario y stock_disponible salen del search público del Catalog (Price y AvailableQuantity del primer seller del SKU). Si el SKU no tiene oferta, ambos vuelven en 0.

Endpoint del backend

DatoValor
RutaPOST /_v/calculator-widget/calculate (pública, same-origin)
Body que envía el bloque{ "skuId": "123", "formulaId": "area", "inputs": { "ancho": 3, "alto": 2 } }
Overrides opcionales aceptados (no los usa el bloque)rendimientoUnidad (number), rendimientoSpecName (string), unidadSpecName (string)
Respuesta 200cantidad_final, precio_unitario, precio_total, stock_disponible, alerta_stock, rendimiento, unidad_rendimiento
Errores400 falta skuId o formulaId · 422 formulaId fuera del whitelist, faltan variables del input, o la fórmula no devuelve un número finito
CacheCache-Control: no-store (siempre precio y stock frescos)
Runtime256 MB, timeout 10 s, 2–4 réplicas

El bloque llama a este endpoint con debounce de 300 ms cada vez que cambian los inputs, el SKU seleccionado o la fórmula elegida, y cancela el request anterior. No recalcula nada en el cliente: solo pinta la respuesta.

Qué pasa al agregar al carrito

Alta del ítem

Se agrega el SKU seleccionado con la cantidad calculada usando vtex.order-items.

Evento addToCart

Se dispara vía usePixel para analítica y para que el minicart se abra (el minicart.v2 del theme abre solo si tiene openOnAddDefault: true, que es el default).

Trazabilidad best-effort

Se guarda en el customData del OrderForm el detalle del cálculo (skuId, formulaId, valores ingresados y resultado). Si falla, no bloquea el alta al carrito.

Requisito para la trazabilidad

Para que el detalle del cálculo persista en el pedido, la app tiene que estar registrada como customData app en el checkout de la cuenta, con el appId calculatorwidget. Sin ese registro el carrito funciona igual, pero el dato se descarta silenciosamente. Coordinalo con Koru antes de publicar en master.

Puesta a punto recomendada

Instalá y linkeá en un workspace de validación

Confirmá con vtex whoami antes de instalar. No arranques directamente en master.

Cargá el Website ID y confirmá la licencia

La PDP tiene que mostrar la calculadora, no el cartel de "no disponible".

Creá las especificaciones y asignalas a la categoría

ValorRendimiento (numérico) y UnidadMedida (texto), asignadas a las categorías donde va el widget.

Cargá un producto piloto

Rendimiento mayor que 0 y unidad en forma corta. Si el rendimiento cambia por variación, cargalo a nivel SKU.

Comprobá que los campos pedidos correspondan a la unidad, que la cantidad sea ceil(medida / rendimiento) y que precio y stock coincidan con el SKU.

Probá el alta al carrito

Verificá que el minicart abra con la cantidad calculada y que el ítem quede correcto en el checkout.

Recién ahí extendé las especificaciones a las demás categorías y promové a master.

Datos y privacidad

  • La app no tiene storage propio: ni Master Data, ni VBase.
  • Lo único que persiste es el customData del OrderForm con los valores que ingresó el comprador, para trazabilidad del pedido.
  • En el navegador se guarda el cache de licencia en localStorage, con la clave koru_widget_<WEBSITE_ID>_<APP_ID>, por 300 segundos.
  • No se piden ni guardan credenciales del merchant. No hay secretos de Koru en el browser.
  • El endpoint de cálculo responde con no-store y no registra datos personales: recibe medidas, no identidad del comprador.

Límites funcionales

La versión actual:

  • Requiere contexto de producto: solo funciona dentro de la PDP.
  • No permite fijar la fórmula ni el rendimiento desde el bloque o el Site Editor.
  • No permite crear ni editar fórmulas sin publicar una versión nueva de la app.
  • Agrega el ítem con un único seller: no está pensada para tiendas con múltiples sellers o marketplace.
  • Lee precio y stock del sales channel que resuelve el search público. En tiendas con varios sales channels o precios por trade policy, el precio mostrado puede no ser el de la política del comprador.
  • Muestra la alerta de stock como advertencia visual, sin bloquear la compra.
  • Los textos internos del widget están en español y no son traducibles desde el theme.
  • No existe versión por <script> ni por Google Tag Manager.

Resolución de problemas

Activaron la app en Koru y sigue el cartel "Comuníquese con Korusuite"

Las respuestas negativas no se cachean, así que una activación en Koru se refleja en el siguiente page load. Lo que sí se cachea es la respuesta positiva, por 300 segundos (5 min), en localStorage con la clave koru_widget_<WEBSITE_ID>_<APP_ID>. Para forzar: borrá esa clave del localStorage o esperá 5 minutos.

Si el cartel persiste, revisá que el websiteId del bloque sea exactamente el del panel de Koru (sin espacios) y que la app esté habilitada para ese website.

El widget no aparece en absoluto — ni la calculadora ni el cartel de error

En este orden:

  1. Contexto de producto: el bloque tiene que estar dentro de store.product. Sin SKU en el product-context el componente no renderiza nada.
  2. Dependencia del theme: la app en el manifest.json del theme, y vtex link reiniciado después de agregarla.
  3. Nombre del bloque: calculator-widget, o soluciones4fpartnerar.calculator-widget:calculator-widget si el theme usa prefijos completos.
  4. Logs del backend:
vtex logs soluciones4fpartnerar.calculator-widget

Cambié UnidadMedida o el rendimiento y el widget no se enteró

UnidadMedida y el rendimiento a nivel producto viajan al storefront por el índice de búsqueda: el cambio no es instantáneo, hay que esperar el reindex del Catalog y hacer hard refresh de la PDP. Podés verificar el valor vigente en:

https://{account}.vtexcommercestable.com.br/api/catalog_system/pub/products/search/?fq=productId:{id}

El rendimiento cargado a nivel SKU se lee por el Catalog privado y se toma al instante.

Me pide campos que no corresponden al producto

Por ejemplo, ancho y alto en un producto que se vende por kg: UnidadMedida está vacía, mal escrita o con un valor fuera de los alias aceptados. Cargá kg, m2, m, m3 o un.

Aparece un selector con 7 fórmulas

Es el comportamiento esperado cuando la unidad no se reconoce. Cargá una unidad válida si querés que el widget elija sola. Al revés: si necesitás ofrecer perimetro, esa es la única vía.

La cantidad calculada es igual a la medida ingresada

Como si el producto rindiera 1: falta la spec de rendimiento, o su valor es 0, no numérico, o tiene coma en lugar de punto. Cargá ValorRendimiento mayor que 0 en el producto o en el SKU.

El rendimiento no cambia al cambiar de variación

Está cargado a nivel producto. Para que varíe por variación, cargalo a nivel SKU: el widget reenvía el skuId seleccionado y recalcula solo.

Precio total en 0 y stock 0

El SKU no tiene oferta/precio en el sales channel que devuelve el search público. Revisá precio y disponibilidad del SKU. Como efecto colateral, con stock 0 la alerta de stock aparece siempre.

Dice "Stock insuficiente" pero igual deja agregar al carrito

Es intencional: la alerta de stock es una advertencia visual. El botón solo se deshabilita si la cantidad calculada es 0 o mientras está agregando el ítem.

El comprador deja un campo vacío o pone texto

Mientras falte algún campo de la fórmula, no se llama al backend y no hay resultado. Los valores no numéricos se descartan en el servidor; si eso deja una variable de la fórmula sin valor, el widget muestra "No se pudo calcular. Revisá los valores e intentá de nuevo.".

Los inputs son numéricos con min=0, pero el backend no rechaza negativos: acepta cualquier número finito y después acota la cantidad final con max(0, …).

¿Afecta la performance del sitio?

El widget es 100% client-side en su carga: no bloquea el render de la PDP, pero hace dos tipos de request desde el navegador.

  1. El gate de licencia: un fetch a korusuite.com por page load, salvo que haya cache válido de 5 minutos. Mientras responde, en el lugar del widget se ve "Verificando disponibilidad…".
  2. El cálculo: un POST same-origin al backend de la app por cada cambio de input, con debounce de 300 ms y cancelación del request anterior, respondido con no-store (no cacheable por diseño, porque devuelve precio y stock reales).

¿Depende de otras apps?

El bloque depende de vtex.product-context, vtex.order-items, vtex.order-manager, vtex.css-handles, vtex.render-runtime, vtex.format-currency y vtex.pixel-manager, más vtex.store@2.x como peer dependency. La moneda del resumen la resuelve vtex.format-currency con la configuración del store.

Checklist antes de publicar

  • App instalada en la cuenta y workspace correctos.
  • Dependencia declarada en el theme y vtex link reiniciado.
  • Bloque declarado dentro de la PDP, con contexto de producto.
  • Website ID cargado y licencia confirmada en la PDP.
  • Especificaciones ValorRendimiento y UnidadMedida creadas y asignadas a las categorías del widget.
  • Rendimiento mayor que 0 en el producto o en el SKU.
  • Unidad de medida cargada en forma corta y reconocida.
  • Rendimiento a nivel SKU cuando varía por variación.
  • Cantidad, precio y stock validados contra el catálogo en un producto piloto.
  • Alta al carrito y minicart probados end to end.
  • Registro de customData en el checkout coordinado, si se necesita trazabilidad.
  • CSS de override revisado en desktop y mobile.

Datos útiles al pedir soporte

Informá cuenta VTEX, workspace, URL de la PDP, productId y skuId, unidad de medida y rendimiento cargados, medida ingresada, cantidad que esperabas y cantidad que devolvió el widget. Evitá enviar credenciales, cookies o datos personales que no sean necesarios para diagnosticar.

Koru Calculator Widget — Developers · Koru Suite