Skip to content
ES

Zentto Studio — Guía para Desarrolladores

This content is not available in your language yet.

Mini-framework web component que genera aplicaciones completas desde JSON. Un StudioSchema o AppConfig → app completa con sidebar, formularios, grids, charts, botones — todo renderizado dinámicamente.

Ventana de terminal
npm install @zentto/studio @zentto/studio-core @zentto/studio-react
transpilePackages: ['@zentto/studio', '@zentto/studio-core', '@zentto/studio-react', 'lit']
ComponenteTagUso
Designer<zs-page-designer>Editor visual Figma-style
Renderer<zentto-studio-renderer>Renderiza StudioSchema
App<zentto-studio-app>App completa desde AppConfig
Wizard<zs-app-wizard>Wizard 7 pasos para crear apps
"use client";
import { useEffect, useState, useRef } from "react";
export default function Page() {
const [ready, setReady] = useState(false);
const ref = useRef<any>(null);
useEffect(() => {
import("@zentto/studio").then(() => setReady(true));
}, []);
useEffect(() => {
if (!ready || !ref.current) return;
ref.current.schema = { /* StudioSchema JSON */ };
}, [ready]);
return ready ? <zentto-studio-renderer ref={ref} /> : <div>Cargando...</div>;
}

Zentto Studio emite CustomEvents estándar del DOM con bubbles: true, composed: true. La app host los escucha y decide qué hacer.

EventoCuándo se emiteDetail
studio-actionClick en botón/link{ actionType, actionUrl, actionMethod, eventName, navigateTo }
studio-submitBotón tipo “submit”{ fieldId, url, method }
studio-navigateBotón tipo “navigate”{ path }
studio-resetBotón tipo “reset”{ fieldId }
studio-action-startInicio de apiCall{ fieldId }
studio-action-resultResultado de apiCall{ fieldId, success, data, status }
schema-changeCambio en designer{ schema }
field-changeCambio en campo{ fieldId, field, value, data }
field-actionRow-click en datagrid{ fieldId, action, row }
grid-config-changeConfig grid cambió{ columns, layout, gridId }
useEffect(() => {
const el = studioRef.current;
el.addEventListener('studio-action', async (e) => {
const { actionType, actionUrl, actionMethod } = e.detail;
if (actionType === 'apiCall') {
const res = await fetch(actionUrl, {
method: actionMethod,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(el.data),
});
const data = await res.json();
console.log('API response:', data);
}
if (actionType === 'navigate') {
router.push(e.detail.navigateTo);
}
if (actionType === 'custom') {
console.log('Custom event:', e.detail.eventName);
}
});
}, []);
TipoDescripciónProps clave
textInput textoinputType, placeholder
textareaÁrea de texto-
numberNuméricomin, max, step
currencyMonedaisCurrency, currencySymbol
percentagePorcentajeisPercentage
dateFechamode (date/time/datetime)
selectSelectoroptions: [{value, label}]
multiselectMulti selectormultiple: true
radioRadio buttonsmode: 'radio', horizontal
checkboxCheckbox-
switchToggle-
emailEmailvalidación automática
passwordPasswordinputType: 'password'
phoneTeléfonoinputType: 'tel'
TipoDescripción
colorColor picker
signatureCanvas firma digital
sliderSlider numérico
ratingEstrellas
tagsTags con autocomplete
chipsChips con colores
addressDirección compuesta
TipoDescripciónProps clave
datagridGrid embebido (<zentto-grid>)endpoint, columns, height, density, enableToolbar, enableSearch, enablePagination, enableConfigurator
reportReport viewer embebidotemplateId, height, zoom
chartGráfico SVGchartType, labelField, valueField
lookupBúsqueda asyncendpoint, valueField, displayField
treeviewÁrbol recursivomultiSelect, showCheckboxes
TipoDescripciónProps clave
buttonBotón de acciónbuttonLabel, variant, actionType, actionUrl, eventName
linkEnlacenavigateTo
headingTítulo H1-H4level
separatorLínea divisoria-
spacerEspacio vacíoheight
htmlHTML librecontent
alertAlerta visualcontent
cardTarjetacontent
iconIcono decorativoiconName, iconSize
progressBarra de progresocontent
avatarAvatar circularcontent

El botón es el componente clave para interactividad. Soporta 6 tipos de acción:

{
"type": "button",
"props": {
"buttonLabel": "Guardar Cliente",
"variant": "primary",
"actionType": "apiCall",
"actionUrl": "/v1/clientes",
"actionMethod": "POST",
"confirmMessage": "¿Guardar este cliente?",
"successMessage": "Cliente guardado",
"errorMessage": "Error al guardar"
}
}

El botón emite studio-action con los datos del formulario. La app host hace el fetch real.

{
"type": "button",
"props": {
"buttonLabel": "Enviar",
"actionType": "submit",
"actionUrl": "/v1/formularios",
"actionMethod": "POST"
}
}
{
"type": "button",
"props": {
"buttonLabel": "Ver Detalle",
"variant": "ghost",
"actionType": "navigate",
"navigateTo": "/clientes/{id}"
}
}
{
"type": "button",
"props": {
"buttonLabel": "Calcular Total",
"actionType": "custom",
"eventName": "onCalculateTotal"
}
}

La app React escucha studio-action y ejecuta lógica custom cuando eventName === 'onCalculateTotal'.

El <zs-page-designer> tiene guardar/cargar integrado que usa la API de addons:

<zs-page-designer
ref={ref}
save-api-url="http://localhost:4000"
save-api-token={token}
/>
  • GET /v1/studio/addons — listar schemas
  • POST /v1/studio/addons — crear addon
  • PUT /v1/studio/addons/:id — actualizar
  • DELETE /v1/studio/addons/:id — eliminar

Cuando se agrega un DataGrid al designer, viene con todas las props:

{
"type": "datagrid",
"props": {
"endpoint": "/v1/clientes",
"columns": [],
"height": "400px",
"pageSize": 25,
"density": "compact",
"enableToolbar": true,
"enableSearch": true,
"enablePagination": true,
"enableConfigurator": true,
"enableExport": false,
"enableHeaderFilters": false,
"showTotals": false,
"gridLayout": null
}
}

El grid auto-detecta columnas al conectar un endpoint. La configuración del usuario (columnas, anchos, orden, filtros) se persiste automáticamente via evento grid-config-change.

Motor de expresiones seguro (sin eval). Sintaxis:

{precio} * {cantidad} * (1 + {iva}/100)
{role} == "admin" AND {total} > 1000
IF({activo}, UPPER({nombre}), "INACTIVO")
  • String: LEFT, RIGHT, MID, LEN, TRIM, UPPER, LOWER, REPLACE, CONTAINS, CONCAT
  • Math: ABS, ROUND, FLOOR, CEIL, MIN, MAX, SUM, AVG, SQRT, POW
  • Logic: IF, IIF, SWITCH, COALESCE, ISNULL, ISEMPTY, NOT
  • Dates: NOW, TODAY, YEAR, MONTH, DAY, DATEADD, DATEDIFF
  • Studio: FIELD, DATASOURCE, ROLE_IS, HAS_ROLE, CHANGED
const provider = {
fetchData: async (url, options) => {
return await apiGet(url); // con auth automática
},
handleAction: async (actionId, type, data) => {
if (type === 'apiCall') {
return await apiPost(data.actionUrl, data);
}
},
navigate: (path) => router.push(path),
notify: (type, title, msg) => toast[type](msg),
getUser: () => ({ id: session.userId, name: session.userName }),
getAuthHeaders: () => ({ Authorization: `Bearer ${token}` }),
};

Los addons son schemas guardados en el servidor que los usuarios pueden ejecutar como apps.

MétodoRutaDescripción
GET/v1/studio/addonsListar addons
GET/v1/studio/addons/:idObtener addon con config
POST/v1/studio/addonsCrear addon
PUT/v1/studio/addons/:idActualizar
DELETE/v1/studio/addons/:idEliminar (soft)
GET/v1/studio/addons/module/:moduleIdFiltrar por módulo
import { listAddons, getAddon, createAddon, updateAddon, deleteAddon } from '@zentto/shared-api';
// API first, localStorage como fallback
const addons = await listAddons('compras');
const addon = await getAddon('addon-123');
await createAddon({ title: 'Mi App', modules: ['compras'], config: schema });
PaqueteDescripción
@zentto/studio-coreLógica pura: types, engine, validation, providers
@zentto/studioWeb components Lit 3.x: renderer, designer, wizard
@zentto/studio-reactWrapper React con createComponent