Files
brahman/examples/nakui-modules/sales_engine/module.json
T
Sergio 932e7464d7 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.
2026-05-09 20:41:37 +00:00

150 lines
6.0 KiB
JSON

{
"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"
}
}
}
}