feat(nakui-ui): Action::Morphism wired al pipeline real (compute -> log -> apply)
Cierra el ultimo gran TODO de la metainterfaz Nakui: las acciones
Action::Morphism ya no son un toast informativo; despachan al
Executor cargado del manifest nakui-core (nsmc.json + schemas KCL +
scripts Rhai), pasando por el pipeline completo: compute (con
dry-run + KCL post-checks) -> log append -> store apply.
Schema nakui-ui-schema extendido:
- Module.nakui_module_dir: Option<String> nuevo. Path al modulo
nakui-core. Sin esto, Action::Morphism quedan no-op con toast.
SeedEntity sigue funcionando (alta administrativa sin manifest).
- Action::Morphism gano dos campos opcionales:
- inputs: BTreeMap<String, String> — mapeo role -> field_name.
- params: Vec<String> — fields cuyos values van al params JSON.
Si vacio, todos los fields no-input van a params.
Runtime nakui-ui:
- MetaUi.executors: BTreeMap<String, Arc<Executor>> nuevo. Carga
Executor::load_module(nakui_module_dir) en MetaUi::new.
- commit_morphism: resuelve inputs (parsea UUIDs), arma params
(Value object con tipos inferidos), llama
execute_and_log_with_recovery. Toast con count de ops o error.
- infer_param_value: heuristica i64 -> f64 -> bool -> string.
Tests: 2 nuevos. E2E morphism_pipeline_executes_real_sales_vender
carga el modulo real crates/modules/nakui/modules/sales, ejecuta
"vender" con inputs Stock+Caja y params (cantidad=5, precio=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacio).
- stock.cantidad: 100 -> 95.
- caja.saldo: 1_000_000 -> 1_001_000.
12 tests verdes en nakui-ui (+1). Schema extension no rompio nada
(6 unit + 5 integration siguen verdes).
Demo nuevo: examples/nakui-modules/sales_engine/module.json apunta
al sales real via nakui_module_dir. 6 vistas (list+form para Stock/
Caja/Venta + "Vender" con Action::Morphism). El user crea Stocks +
Cajas con seed_entity, copia los UUIDs a los inputs de "Vender", y
ejecuta el morphism real con KCL post-checks.
Activacion:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
Trade-offs:
- Inputs UUID a mano (no dropdown). Nice-to-have: FieldKind::EntityRef
que renderee selector.
- Inferencia de tipo en params es heuristica.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"id": "sales_engine",
|
||||
"label": "Ventas (con morphism)",
|
||||
"description": "Módulo conectado al manifest nakui-core 'sales': el form 'Vender' dispara el morphism que valida + mueve stock y caja con KCL post-checks reales.",
|
||||
"nakui_module_dir": "../../../crates/modules/nakui/modules/sales",
|
||||
"entities": [
|
||||
{
|
||||
"name": "Stock",
|
||||
"label": "Stock",
|
||||
"fields": [
|
||||
{ "name": "id", "label": "ID", "kind": "text" },
|
||||
{ "name": "sku_id", "label": "SKU", "kind": "text", "required": true },
|
||||
{ "name": "ubicacion", "label": "Ubicación", "kind": "text" },
|
||||
{ "name": "cantidad", "label": "Cantidad", "kind": "number", "required": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Caja",
|
||||
"label": "Caja",
|
||||
"fields": [
|
||||
{ "name": "id", "label": "ID", "kind": "text" },
|
||||
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
|
||||
{ "name": "currency", "label": "Moneda", "kind": "text", "default": "USD" },
|
||||
{ "name": "saldo", "label": "Saldo", "kind": "number", "default": "0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Venta",
|
||||
"label": "Venta",
|
||||
"fields": [
|
||||
{ "name": "id", "label": "ID", "kind": "text" },
|
||||
{ "name": "stock_id", "label": "Stock ref", "kind": "text" },
|
||||
{ "name": "caja_id", "label": "Caja ref", "kind": "text" },
|
||||
{ "name": "cantidad", "label": "Cantidad vendida", "kind": "number" },
|
||||
{ "name": "precio_unitario", "label": "Precio unit.", "kind": "number" },
|
||||
{ "name": "total", "label": "Total", "kind": "number" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu": [
|
||||
{ "label": "Stock", "view": "stock_list", "icon": "📦" },
|
||||
{ "label": "+ Stock", "view": "stock_form", "icon": "✚" },
|
||||
{ "label": "Cajas", "view": "caja_list", "icon": "💰" },
|
||||
{ "label": "+ Caja", "view": "caja_form", "icon": "✚" },
|
||||
{ "label": "Ventas registradas", "view": "venta_list", "icon": "🧾" },
|
||||
{ "label": "Vender", "view": "vender_form", "icon": "⚡" }
|
||||
],
|
||||
"views": {
|
||||
"stock_list": {
|
||||
"kind": "list",
|
||||
"title": "Stock",
|
||||
"entity": "Stock",
|
||||
"columns": [
|
||||
{ "field": "sku_id", "label": "SKU", "weight": 1.5 },
|
||||
{ "field": "ubicacion", "label": "Ubicación", "weight": 1.5 },
|
||||
{ "field": "cantidad", "label": "Cantidad", "weight": 1.0 }
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "open_view", "view": "stock_form", "label": "✚ Stock" }
|
||||
],
|
||||
"search_in": ["sku_id", "ubicacion"]
|
||||
},
|
||||
"stock_form": {
|
||||
"kind": "form",
|
||||
"title": "Nuevo stock",
|
||||
"entity": "Stock",
|
||||
"fields": [
|
||||
{ "name": "id", "label": "ID interno", "kind": "text", "help": "El UUID lo genera el runtime; este field es para el campo `id` de la entity (Nakui lo usa)." },
|
||||
{ "name": "sku_id", "label": "SKU", "kind": "text", "required": true },
|
||||
{ "name": "ubicacion", "label": "Ubicación", "kind": "text", "required": true, "default": "loc-default" },
|
||||
{ "name": "cantidad", "label": "Cantidad inicial", "kind": "number", "required": true, "default": "0" }
|
||||
],
|
||||
"on_submit": {
|
||||
"kind": "seed_entity",
|
||||
"entity": "Stock",
|
||||
"next_view": "stock_list"
|
||||
}
|
||||
},
|
||||
"caja_list": {
|
||||
"kind": "list",
|
||||
"title": "Cajas",
|
||||
"entity": "Caja",
|
||||
"columns": [
|
||||
{ "field": "name", "label": "Nombre", "weight": 2.0 },
|
||||
{ "field": "currency", "label": "Moneda", "weight": 0.5 },
|
||||
{ "field": "saldo", "label": "Saldo", "weight": 1.0 }
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "open_view", "view": "caja_form", "label": "✚ Caja" }
|
||||
],
|
||||
"search_in": ["name", "currency"]
|
||||
},
|
||||
"caja_form": {
|
||||
"kind": "form",
|
||||
"title": "Nueva caja",
|
||||
"entity": "Caja",
|
||||
"fields": [
|
||||
{ "name": "id", "label": "ID interno", "kind": "text" },
|
||||
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
|
||||
{ "name": "currency", "label": "Moneda", "kind": "text", "default": "USD" },
|
||||
{ "name": "saldo", "label": "Saldo inicial", "kind": "number", "default": "0" }
|
||||
],
|
||||
"on_submit": {
|
||||
"kind": "seed_entity",
|
||||
"entity": "Caja",
|
||||
"next_view": "caja_list"
|
||||
}
|
||||
},
|
||||
"venta_list": {
|
||||
"kind": "list",
|
||||
"title": "Ventas",
|
||||
"entity": "Venta",
|
||||
"columns": [
|
||||
{ "field": "stock_id", "label": "Stock ref", "weight": 1.0 },
|
||||
{ "field": "caja_id", "label": "Caja ref", "weight": 1.0 },
|
||||
{ "field": "cantidad", "label": "Cant.", "weight": 0.6 },
|
||||
{ "field": "precio_unitario", "label": "Precio", "weight": 0.8 },
|
||||
{ "field": "total", "label": "Total", "weight": 1.0 }
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "open_view", "view": "vender_form", "label": "⚡ Vender" }
|
||||
],
|
||||
"search_in": ["stock_id", "caja_id"]
|
||||
},
|
||||
"vender_form": {
|
||||
"kind": "form",
|
||||
"title": "Vender (morphism)",
|
||||
"entity": "Venta",
|
||||
"fields": [
|
||||
{ "name": "stock_id_input", "label": "Stock UUID", "kind": "text", "required": true, "help": "Copiá el UUID corto de la lista de Stock — el runtime lo parsea como Uuid completo si es válido." },
|
||||
{ "name": "caja_id_input", "label": "Caja UUID", "kind": "text", "required": true, "help": "Idem para Caja." },
|
||||
{ "name": "venta_id", "label": "Venta UUID (idempotencia)", "kind": "text", "required": true, "help": "UUID nuevo por cada intento; mismo UUID = idempotente." },
|
||||
{ "name": "cantidad", "label": "Cantidad a vender", "kind": "number", "required": true, "default": "1" },
|
||||
{ "name": "precio_unitario", "label": "Precio unitario", "kind": "number", "required": true, "default": "0" },
|
||||
{ "name": "timestamp", "label": "Timestamp ISO", "kind": "text", "required": true, "default": "2026-05-09T12:00:00Z" }
|
||||
],
|
||||
"on_submit": {
|
||||
"kind": "morphism",
|
||||
"name": "vender",
|
||||
"inputs": {
|
||||
"stock": "stock_id_input",
|
||||
"caja": "caja_id_input"
|
||||
},
|
||||
"params": ["venta_id", "cantidad", "precio_unitario", "timestamp"],
|
||||
"next_view": "venta_list"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user