KEPLIN Docs

Eventos y el SDK

Los eventos de los widgets y de la pantalla, el editor de código, las acciones predefinidas y el SDK keplin en TypeScript — datos, widgets, navegación, sesión, modales y workflows.

Hay mucha pantalla que se hace sin escribir una línea: conectar un datastore, arrastrar widgets, apuntar un botón a otra pantalla. Pero tarde o temprano aparece el «cuando esto pase, haz aquello» — guardar y volver atrás, recargar una tabla después de un filtro, confirmar antes de eliminar, abrir un modal y usar lo que devolvió.

Para eso sirven los eventos: puntos de la pantalla donde corre código tuyo, escrito en TypeScript, con un SDK — el objeto keplin — que da acceso a todo lo que la pantalla tiene.

Dónde están los eventos

En el inspector, la última categoría de un widget (y de la propia pantalla) se llama Eventos. Tiene una fila por evento disponible, y en cada fila:

  • un punto a la izquierda: lleno cuando ese evento ya tiene código, vacío cuando no;
  • un botón … a la derecha, que abre el editor.

La categoría Eventos de un Botón: un punto lleno marca los eventos que ya tienen código; el … abre el editor.
La categoría Eventos de un Botón: un punto lleno marca los eventos que ya tienen código; el … abre el editor.

Los nombres de los eventos no se traducen — son los mismos en cualquier idioma (onClick, onRowClick, onLoad), porque son también los nombres que aparecen en el código y en los registros del Radar.

Los eventos de la pantalla

Haz clic en una zona vacía del canvas para que el inspector muestre la pantalla. La categoría Eventos tiene tres:

Evento Cuándo se dispara Para qué
onLoad Una vez, cuando la pantalla se abre. Preparar estado, cargar cosas que los datastores no cargan, dar la bienvenida.
onParamsChange Siempre que los parámetros de la ruta cambian — y no en la primera apertura. Reaccionar a un cambio de registro sin reabrir la pantalla.
onUnload Cuando la pantalla sale. Limpiar estado, guardar borradores.

Los eventos de la propia pantalla — onLoad, onParamsChange y onUnload — en el inspector sin selección.
Los eventos de la propia pantalla — onLoad, onParamsChange y onUnload — en el inspector sin selección.

Los eventos de cada widget

Cada tipo de widget declara los suyos. Además del nombre, importa el payload — los datos que el evento trae consigo, y que el código lee en keplin.event.

Campos de formulario

Widget Eventos keplin.event
Caja de texto, Área de texto, Número, Sí/No, Lista, Fecha, Color onChange { value }
Archivo onChange, onUpload onUpload: { file, name }

Acciones y navegación

Widget Eventos keplin.event
Botón onClick {}
Botón con menú onClick, onMenuItem onMenuItem: { id, label }
Enlace onClick {}
Exportar onExport, onDataLoaded onExport: { rows, filename, truncated }

Estructura y contenido

Widget Eventos keplin.event
Pestañas onTabChange { tab }
Informe onLoad { report }

Widgets de datos

Widget Eventos keplin.event
Tabla onRowClick, onRowDoubleClick, onSelectionChange, onDataLoaded { row, index } · onSelectionChange: { row, rows }
Lista, Tarjetas onRowClick, onDataLoaded { row, index }
Gráfico onClick { name, seriesName, value, dataIndex }
KPI onClick, onDataLoaded { value, indicatorId }

Tableros y planificación

Widget Eventos keplin.event
Kanban onCardClick, onCardCreate, onCardMoved, onDataLoaded { row } · { column } · { row, from, to, index }
Calendario onEventClick, onDayClick, onRangeSelect, onRangeChange, onDataLoaded { row } · { date } · { start, end } · { start, end, view }
Gantt onBarClick, onEmptyClick, onDataLoaded { row } · { date }

Procesos

Widget Eventos keplin.event
Estado del proceso onDecide { task, outcome }
Mis tareas onOpen, onDecide { task, screenId }

Nota

Los widgets programados por ti (los que aparecen en la paleta en Custom) declaran sus propios eventos, y aparecen aquí como cualesquiera otros.

El editor de código

El botón … de un evento abre el editor en un modal. El título dice dónde estás: el id del widget (o el nombre de la pantalla) y el nombre del evento — w_fic_sav1 · onClick.

El editor del evento onClick del botón Guardar: el código guarda el datastore y, si fue bien, avisa y vuelve a la lista.
El editor del evento onClick del botón Guardar: el código guarda el datastore y, si fue bien, avisa y vuelve a la lista.

Botón Qué hace
Insertar acción Escribe por ti el código de una tarea común (a continuación).
Quitar handler Borra el código de este evento. El punto vuelve a vacío.
Cancelar Cierra sin guardar.
Guardar Verifica y guarda.

El editor tiene sugerencias mientras escribes (Ctrl+Espacio): el keplin entero está declarado, con los tipos correctos — y, mejor aún, los ids de los widgets de esta pantalla están ahí dentro. Escribir keplin.widgets.get(" muestra la lista de los widgets de la pantalla, y un id que no existe se señala como error antes de guardar.

Atención

Al guardar, el código se compila. Si no es ejecutable, la plataforma lo rechaza — El evento no se guardó: el código no es ejecutable — y el modal queda abierto para corregir. Una pantalla nunca se queda con código roto dentro.

Las acciones predefinidas

Insertar acción abre una lista de las tareas más comunes. Eliges una y el código se escribe al final de lo que ya está, ya con los nombres reales de tu pantalla — el primer datastore de registro, la primera tabla, la primera caja de texto.

Insertar acción — las acciones predefinidas que escriben el código por ti, de los datastores a los workflows.
Insertar acción — las acciones predefinidas que escriben el código por ti, de los datastores a los workflows.

Acción Qué escribe
Guardar datastore Valida y guarda el registro, con aviso de éxito.
Recargar datastore Vuelve a leer los datos de un datastore.
Filtrar datastore (por texto) Lee el texto de una caja y lo aplica como filtro.
Navegar a una pantalla Salta a otra ruta de la app.
Recargar una tabla Refresca los datos de un widget de tabla.
Filtrar tabla por el texto de un campo El filtro interactivo clásico.
Mostrar/ocultar un widget Alterna la visibilidad de un widget.
Confirmar y mostrar toast Pregunta antes de actuar y avisa al final.
Arrancar un workflow Pone un proceso en marcha sobre el registro actual.
Ver y concluir tareas Lista las tareas de quien está usando y decide una.
Mandar una señal a un workflow Despierta procesos que estaban esperando.
Cerrar sesión Sale de la app.

El código insertado es un punto de partida: queda tuyo, y es para editarlo. No vuelve a generarse.

El código se guarda formateado: dividido en líneas, indentado y con los espacios correctos, sin cambiar lo que hace. Vale para todos los editores de código de la plataforma, y para el código que los agentes guardan por MCP. Formatear código, en el menú del editor (botón derecho) o con Shift+Alt+F, formatea al momento, y Ctrl+Z lo deshace. Un código con errores de sintaxis no se formatea: queda como está hasta que lo corrijas.

Cómo corre el código

Cada evento es una función asíncrona que recibe una sola cosa: el keplin. De ahí salen tres consecuencias prácticas:

  • await funciona en el nivel superior del código. No hace falta envolver nada.
  • return sale del evento. Es la forma normal de desistir a medias (por ejemplo, cuando una confirmación fue rechazada).
  • No hay parámetros. El contexto viene dentro del propio keplin: keplin.event trae el payload y keplin.ctx dice dónde estás (ctx.widget es el widget que disparó — null en los eventos de pantalla —, ctx.event es el nombre del evento y ctx.screen la pantalla).

Mientras el código de un botón no termina, el botón muestra tres puntos animados: quien está usando entiende que la app está trabajando. Si el código revienta, la pantalla no se rompe: aparece un aviso y el error queda registrado en el Radar, con la pantalla, el widget y el evento donde ocurrió.

El SDK keplin

Todo lo que el código puede hacer está bajo keplin. Estas son las áreas:

Área Para qué
keplin.event / keplin.ctx El payload del evento y el contexto donde está corriendo.
keplin.widgets Hablar con los widgets de la pantalla.
keplin.data Los datastores: leer, escribir, filtrar, guardar.
keplin.nav Navegar y leer los parámetros de la ruta.
keplin.ui Avisos, confirmaciones y modales.
keplin.state Estado compartido entre pantallas.
keplin.session Quién está usando la app, y qué puede hacer.
keplin.auth Login, registro y recuperación de contraseña (pantallas de sistema).
keplin.i18n Frases traducidas, el idioma en uso y el cambio de idioma.
keplin.storage Preferencias guardadas en el dispositivo.
keplin.api Llamar a las APIs de la app directamente.
keplin.reports Abrir y descargar informes.
keplin.workflow Arrancar procesos, listar y concluir tareas.

Los widgets

keplin.widgets.get("id") devuelve el handle de un widget. Todos los handles tienen lo mismo básico:

const w = keplin.widgets.get("w_fic_tel1");
w.show();               // mostrar
w.hide();               // ocultar
w.setEnabled(false);    // desactivar
w.set("label", "Telemóvel");   // cambiar cualquier propiedad del inspector
w.get("label");         // leer el valor efectivo
w.reset();              // olvidar los cambios hechos en ejecución

Y después cada familia añade lo suyo:

Familia Qué añade
Campos de formulario getValue(), setValue(v), validate(), error
Widgets de datos (Tabla, Lista, Tarjetas, Gráfico, KPI, Kanban, Calendario, Gantt) rows, total, refresh(), setFilter(where), setSort(sort)
Tabla selectedRow, selectedRows, clearSelection()
KPI value, values, valueOf(indicadorId)
Kanban columns, moveCard(id, columna, índice?)
Calendario view, start, end, goTo(fecha), setView(vista)
Gantt zoom, setZoom(z)
Pestañas activeTab, tab("id") — y, en la pestaña, activate(), show(), hide(), setEnabled()
Etiqueta / Botón / Enlace / Breadcrumb setText(t) / setLabel(t)
Markdown setContent(md)
Página externa setUrl(url), reload()
Exportar export()
Informe url, download()

Nota

Las sugerencias del editor ofrecen todos los verbos de todas las familias, porque el editor no sabe de antemano qué widget es ese id. En ejecución solo existen los del tipo real — moveCard en un Botón no hace nada útil.

Los datos

keplin.data.store("nombre") devuelve un datastore de la pantalla por el nombre (ver Datastores y datos).

En un datastore de registro:

const conta = keplin.data.store("conta");
conta.get("nome");                 // leer un campo
conta.set("estado", "ativo");      // escribir un campo (queda por guardar)
conta.record();                    // el registro entero
conta.isDirty();                   // ¿hay cambios por guardar?
conta.reset();                     // tirar los cambios
const ok = await conta.save();     // valida y guarda; true si guardó

En un datastore de lista:

const contas = keplin.data.store("contas");
contas.rows();                     // las filas cargadas
contas.total();                    // el total (cuando el servidor lo da)
await contas.reload();             // volver a leer
contas.setWhere({ estado: { eq: "ativo" } });   // filtro extra; null limpia
contas.setSort([{ field: "nome", direction: "ASC" }]);
contas.goToPage(2);

En ambos, status() dice en qué punto está la carga (idle, loading, ready, error).

reload() devuelve una promesa: con await, las filas nuevas ya están en rows(). En una lista sin Cargar automáticamente, es el reload() el que la hace leer.

keplin.nav.go("/ficha-de-conta/17");   // ir a una ruta (con parámetros)
keplin.nav.back();                      // volver atrás
keplin.nav.params;                      // los parámetros de la pantalla actual, por nombre

Avisos, confirmaciones y modales

keplin.ui.toast("Gravado.", "success");        // "success" | "error" | "info"
const ok = await keplin.ui.confirm("Apagar o registo?");
if (!ok) return;

La confirmación es un diálogo con el tema de la app — nunca la caja gris del navegador.

Estado, sesión y preferencias

keplin.state.set("filtroContas", "activas");   // vive mientras la pestaña esté abierta
keplin.state.get("filtroContas");
keplin.state.remove("filtroContas");

keplin.session.user;              // { id, username, name } — null en pantallas públicas
keplin.session.roles;             // los papeles de quien está usando
keplin.session.can("contas.editar");   // ¿tiene esta acción? (Ajustes ▸ Permisos)
await keplin.session.logout();

keplin.storage.set("colunasContas", ["nome", "cidade"]);   // queda en el dispositivo
keplin.storage.get("colunasContas");

Consejo

Para decidir qué puede hacer alguien, pregunta keplin.session.can("...") y no hasRole("gestor"). Las acciones se declaran en Ajustes ▸ Permisos y sobreviven a reorganizaciones de papeles; el nombre de un papel, no.

Las APIs y los informes

const linhas = await keplin.api.query("contas", { estado: "ativo" }, ["id", "nome"]);
await keplin.api.mutate("criarConta", { nome: "Nova" }, ["id"]);

keplin.reports.open("Contactos da conta", { contaId: 17 });
keplin.reports.download("Contactos da conta", { contaId: 17 }, "xlsx");

En un argumento de enum, pasa el valor tal como está guardado ({ estado: "Em curso" }). Un argumento JSON acepta listas y objetos; en una consulta SQL a SQL Server, SQLite u Oracle llegan como texto JSON, que se lee con OPENJSON, json_each o JSON_TABLE.

Los valores pueden ir como los guardan los campos: el texto «12» de un cuadro de texto sirve en un argumento numérico, un número sirve en un argumento de texto, «true» sirve en un booleano, y una fecha (Date) va en ISO.

En una consulta, la lista de campos es obligatoria — es ella la que dice qué quieres traer.

Atención

keplin.reports.open abre una pestaña nueva y por eso no puede quedar detrás de un await: fuera del gesto del usuario, el navegador bloquea la ventana. Abre primero, haz el resto después.

Workflows

const registo = keplin.data.store("oportunidade").get("id");
await keplin.workflow.start("wf_aprovacao", registo);

const tarefas = await keplin.workflow.tasks();
await keplin.workflow.complete(tarefas[0].id, "aprovar");

const { woken } = await keplin.workflow.signal("documento-recebido", registo);

Frases traducidas

keplin.i18n.t("{n} contas activas", { n: linhas.length });
keplin.i18n.locale;
keplin.i18n.available;
await keplin.i18n.setLocale("en");

Pantallas modales

Una pantalla de Keplin no es modal porque se abrió de cierta manera — es modal porque fue configurada así. La decisión está en el inspector de la pantalla, en la categoría Presentación:

La sección Presentación de la pantalla: aquí es donde una pantalla pasa a Modal (centro) o a Panel lateral (derecha).
La sección Presentación de la pantalla: aquí es donde una pantalla pasa a Modal (centro) o a Panel lateral (derecha).

Opción Qué hace
Modo Pantalla (una página normal), Modal (centro) o Panel lateral (derecha). El modo Barra (área de widgets) no se abre encima: es el contenido de un área de widgets de la Navegación de la app.
Ancho (px) / Alto (px) El tamaño del modal. El panel lateral usa toda la altura.
Botón de cerrar Muestra la × en la esquina.
Clic fuera cierra / Esc cierra Las dos salidas habituales. Con una lista o un popover abiertos, el primer clic fuera o Esc cierra solo eso.
Refrescar la pantalla de atrás al cerrar Al cerrar, los datastores de la pantalla que lo llamó vuelven a leer. Lo que esté escrito y sin guardar en un formulario de esa pantalla se mantiene.

Con cambios sin guardar, cerrar con la ×, con Esc o con un clic fuera pide confirmación primero. Cuentan los campos ligados a un datastore de registro que la persona editó y aún no guardó; escribir y borrar no cuenta. Cerrar por código, con keplin.ui.closeModal, no pregunta.

¿Cuál de los dos? El Modal (centro) sirve para fichas simples — un catálogo con descripción y estado, una confirmación. El Panel lateral (derecha) se abre a la derecha a toda la altura y es la elección para fichas con muchos campos, paneles y listas dentro (un cliente con sus direcciones y contactos): la misma ficha en un modal central queda pequeña y apretada, y la lista de atrás sigue visible al lado. El Ancho (px) es el del panel.

La pista de la propia sección lo resume: Se abre POR ENCIMA de la pantalla que lo llama (Enlace, eventos o keplin.ui.openModal). Sale de la navegación directa.

Abrir y cerrar por código

const resultado = await keplin.ui.openModal("/nova-conta", { setor: "banca" });
if (resultado) {
  keplin.data.store("contas").reload();
}
  • openModal recibe la ruta (o el id) de la pantalla y, opcionalmente, los parámetros.
  • La promesa solo se resuelve cuando el modal cierra, y trae el valor que el modal devolvió.
  • Dentro del modal, keplin.ui.closeModal(valor) cierra y devuelve ese valor.
  • Los modales se apilan: un modal puede abrir otro. Con varios paneles laterales abiertos, cada panel de abajo queda visible en una franja a la izquierda del panel de arriba; con Clic fuera cierra activado, un clic en esa franja cierra el panel de arriba.
  • Pedir la pantalla que ya está arriba del todo, con los mismos parámetros, no abre otra: la llamada devuelve la promesa de la que está abierta. Es lo que impide que un doble clic abra dos fichas iguales.

Nota

keplin.nav.go("/ruta") hacia una pantalla configurada como Modal (centro) o Panel lateral (derecha) la abre como modal en vez de navegar. Es a propósito: una pantalla modal no tiene dirección propia en la navegación.

Recetas

Guardar y volver (el onClick del botón Guardar de la Ficha de Conta):

const ok = await keplin.data.store("conta").save();
if (ok) {
  keplin.ui.toast("Conta guardada");
  keplin.nav.go("/contas");
}

Abrir la ficha de la fila clicada (onRowClick de una Tabla):

keplin.nav.go(`/ficha-de-conta/${keplin.event.row["id"]}`);

Filtrar una tabla por una caja de texto (onChange de la caja — sustituye los ids por los de tu pantalla):

const texto = keplin.widgets.get("w_pesquisa").getValue();
keplin.widgets.get("w_cta_tab1").setFilter(texto ? { nome: { contains: texto } } : null);

Confirmar antes de una acción destructiva (onClick de un botón):

if (!(await keplin.ui.confirm("Apagar esta conta?"))) return;

Ocultar un botón a quien no puede (onLoad de la pantalla):

if (!keplin.session.can("contas.eliminar")) {
  keplin.widgets.get("w_apagar").hide();
}

¿Por qué no…?

  • ¿Por qué no me deja guardar el evento? El código no compila. El mensaje es El evento no se guardó: el código no es ejecutable — corrige y guarda.
  • ¿Por qué keplin.widgets.get("...") da error? El id no existe en esta pantalla. Confírmalo en la parte superior del inspector, con el widget seleccionado; y recuerda que cada dispositivo es un árbol propio (ver Layouts y diseño por dispositivo).
  • ¿Por qué el onParamsChange no se disparó al abrir? Es a propósito: solo se dispara en cambios. Para el arranque, usa el onLoad.
  • ¿Por qué el modal no devuelve nada? O la pantalla de destino no existe, o quien está usando no tiene permiso para abrirla — en los dos casos la promesa se resuelve sin valor. Confirma la ruta y los permisos.
  • ¿Por qué no se abre la ventana del informe? Pusiste el open después de un await. Abre primero.
  • ¿Por qué mi evento parece no correr? Mira el Radar: los errores del código de los eventos quedan ahí, con pantalla, widget y evento — y el Radar te lleva directamente al editor de ese evento.