SDK de Visual Components
Dentro del iframe de un Visual Component, sdk es el puente con la plataforma. Cada llamada viaja al shell, que la ejecuta con los permisos del usuario que mira la página (permission sets y sharing incluidos) y devuelve el resultado. No hay fetch ni acceso directo a la red.
Datos
await sdk.fetchRecords("accounts", {
filters: [{ field: "industry", op: "eq", value: "finance" }],
limit: 20,
sort: "name:asc",
}); // → Array<record>
await sdk.getRecord("accounts", id); // → record | null
await sdk.createRecord("tasks", { subject: "Llamar", account: record.id });
await sdk.updateRecord("accounts", id, { status: "active" });
await sdk.deleteRecord("tasks", id);- Operadores de filtro:
eq,neq,contains,gt,gte,lt,lte,in,not_in,is_null,is_not_null. - Las escrituras pasan por el mismo pipeline que la UI (validación, automations, sharing, historial); un error de validación rechaza la promesa con el detalle.
- Los nombres de entidad se resuelven en el ámbito del componente (un componente de CPQ puede decir
"quote"). - Para lógica con reglas, APIs externas o credenciales, no escribas desde el iframe: usá un Logic Component.
Backend: runLogic
const res = await sdk.runLogic("enrich_account", { accountId: record.id }, record.id);
if (res.status === "success") setData(res.outputs);
else setError(res.error?.message);Invoca un Logic Component con inputs y devuelve { status, outputs } o { status: "error", error: { code, message } }. El tercer argumento (id del registro) se expone como ctx.record en el Logic.
Navegación y estado del registro
sdk.openRecord("opportunities", id); // abre el registro en una pestaña del workspace
const unsubscribe = sdk.onRecordChange((detail) => { /* { entity, id, patch, record } */ });
await sdk.refreshRecord(); // fuerza a la página a recargar el registro (header, detalles, highlights)onRecordChange avisa cuando el registro de la página se edita; llamá unsubscribe() en el cleanup del efecto. refreshRecord es lo que usás después de un runLogic que modificó el registro.
Feedback: toasts y confirmaciones
sdk.toast.success("Guardado");
sdk.toast.error("Falló", { description: String(err) });
sdk.toast.warning("Se van a archivar 3 registros");
sdk.toast.info("Sincronizando…", { duration: 2000 });
const ok = await sdk.confirm("¿Eliminar este registro?", {
description: "No se puede deshacer.",
variant: "destructive",
confirmLabel: "Eliminar",
});Los dos se dibujan en el shell (fuera del iframe): window.confirm y alert están bloqueados en el sandbox. Los callbacks no cruzan al shell (toast({ action: { onClick } }) no funciona).
Diálogos del shell
Un modal dibujado dentro del iframe queda encerrado en su rectángulo. openDialog le pide al shell un diálogo de verdad con tu mismo componente adentro:
// en la vista principal
const result = await sdk.openDialog({
view: "edit-line", // qué vista de tu componente mostrar
props: { lineId: line.id }, // serializable
title: "Editar línea",
size: "lg", // sm | md | lg | xl | fullscreen
});
if (result) applyChanges(result); // undefined si cerró con Esc / afuera
// el mismo componente, montado en el diálogo
export default function MiComponente(props) {
if (props.dialogView === "edit-line") {
return <EditorDeLinea {...props.dialogProps}
onSave={(patches) => sdk.closeDialog({ patches })}
onCancel={() => sdk.closeDialog()} />;
}
return <VistaNormal {...props} />;
}- Un diálogo a la vez: el segundo
openDialogrechaza condialog_already_open. - Los
propstienen que ser serializables; el diálogo no ve el estado en memoria del que lo abrió. - El diálogo es una instancia nueva del componente; al cerrar se desmonta.
Botones del encabezado
Montado como record_header_action, tu componente recibe record y entitySlug, y dos métodos cobran sentido:
await sdk.refreshRecord(); // después de mutar el registro via runLogic
await sdk.closeDialog(); // cierra el diálogo del botón (o termina una acción sin pantalla)Con presentation.kind = "action" no hay pantalla: el componente se monta, actúa y llama closeDialog(). Guardá un useRef para no ejecutar dos veces.
await sdk.openEmailComposer({ to: [record.email], subject: "…", relatedToType: "quote", relatedToId: record.id });Abre el composer del CRM precargado; el email sale con la inbox del workspace y queda vinculado al registro.
Steppers
Montado como bloque Componente propio de una pantalla de un Stepper, el componente escribe las variables de la pantalla con:
sdk.setStepperValues({ monto: 1500, motivo: "renovación" });Solo se aceptan las variables declaradas en el bloque (writes); cualquier otra se descarta.
Permisos del usuario
sdk.user.can("update", "quotes"); // true | falsePara mostrar u ocultar acciones según lo que el usuario puede hacer. Es orientativo: el servidor vuelve a verificar en cada llamada.
Páginas públicas
Cuando el componente se sirve como página pública, el SDK está recortado: solo sdk.publicAction(name, payload), que ejecuta una de las acciones declaradas (aprobar, rechazar) con contexto de sistema. Los datos llegan por el resolver del componente, no por fetchRecords.
Límites
- Rate limit: 30 llamadas cada 10 segundos por instancia; el exceso rechaza con
rate_limit_exceededhasta que pase la ventana. UnuseEffectcon dependencias inestables es la causa típica. - Sin
fetch, cookies nilocalStorage(iframe sinallow-same-origin). - Sin
import: React 18,sdkyNxarson globales; el resto, inline. - Los overlays Radix (
Nxar.Dialog,Popover,Tooltip) se renderizan dentro del iframe (que crece para mostrarlos); para un modal de pantalla completa,sdk.openDialogosdk.confirm.