feat(nakui): metainterfaz declarativa + 6 modulos ERP estandar

Salto cualitativo: Nakui pasa de "library + demos + read-only viewer
del event log" a plataforma ERP con UI dirigida por datos. Cada
modulo de negocio se declara como un module.json (sin codigo Rust
nuevo) y el runtime GPUI lo carga dinamicamente: sidebar de menus,
listas con columnas configurables, formularios de alta.

3 entregables:

1. Crate nakui-ui-schema (datos puros): Module, View::List/Form,
   FieldSpec con FieldKind {Text|Multiline|Number|Boolean|Date},
   Action {OpenView|SeedEntity|Morphism}. Module::from_path,
   Module::validate, load_modules_from_dir(dir). 6 tests unit + 4
   integration.

2. Crate nakui-ui (binario GPUI): carga modulos desde
   NAKUI_MODULES_DIR. Sidebar + main panel. List view con tabla
   weighted; form view con campos labeled + submit que ejecuta
   SeedEntity contra MemoryStore in-process compartido. Toast +
   error banner. 6 tests unit.

3. 6 modulos demo en examples/nakui-modules/:
   - customers (nombre, email, telefono, credito, notas)
   - products (SKU, nombre, categoria, precio, stock)
   - suppliers (razon social, ID fiscal, contacto, terminos pago)
   - inventory_movements (fecha, tipo, SKU, cantidad, costo, motivo)
   - sales_orders (numero, cliente, fechas, estado, totales)
   - invoices (numero, cliente, fechas, totales, pagado, moneda)

Filosofia: UI como datos. Persistencia universal (MemoryStore hoy,
SurrealStore manana, sin tocar module.json). Schema primero, semantica
despues.

Activacion:
  NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui

Limitaciones conocidas (proximos iters):
- Inputs sin teclado (GPUI no lo trae nativo; integrar
  yahweh-widget-text-input).
- Click handlers no propagan mutacion al estado (refactor con
  cx.listener pendiente).
- Action::Morphism queda como TODO hasta cargar Manifest junto al
  Module.
- Sin persistencia entre runs (wire con EventLog/SurrealStore para
  cuando el daemon Nakui exista).

Tests: 16 totales nuevos. Lo que esto desbloquea: cualquiera puede
escribir un module.json para su dominio (pacientes, alumnos,
reservaciones) y aparece en la UI sin recompilar.
This commit is contained in:
Sergio
2026-05-09 19:54:21 +00:00
parent 5b8f71e0de
commit 06c4fb9130
14 changed files with 1919 additions and 0 deletions
@@ -0,0 +1,59 @@
{
"id": "customers",
"label": "Clientes",
"description": "Gestión de clientes (alta, listado, búsqueda).",
"entities": [
{
"name": "customer",
"label": "Cliente",
"fields": [
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
{ "name": "email", "label": "Email", "kind": "text", "required": false, "help": "Opcional, formato libre por ahora" },
{ "name": "phone", "label": "Teléfono", "kind": "text", "required": false },
{ "name": "active", "label": "Activo", "kind": "boolean", "default": "true" },
{ "name": "credit_limit", "label": "Límite de crédito", "kind": "number", "default": "0" },
{ "name": "notes", "label": "Notas", "kind": "multiline", "required": false }
]
}
],
"menu": [
{ "label": "Listar", "view": "list", "icon": "📋" },
{ "label": "Nuevo", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Clientes",
"entity": "customer",
"columns": [
{ "field": "name", "label": "Nombre", "weight": 2.0 },
{ "field": "email", "label": "Email", "weight": 3.0 },
{ "field": "phone", "label": "Teléfono", "weight": 1.5 },
{ "field": "active", "label": "Activo", "weight": 0.7 },
{ "field": "credit_limit", "label": "Límite", "weight": 1.0 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Nuevo" }
],
"search_in": ["name", "email", "phone"]
},
"form": {
"kind": "form",
"title": "Nuevo cliente",
"entity": "customer",
"fields": [
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
{ "name": "email", "label": "Email", "kind": "text", "required": false },
{ "name": "phone", "label": "Teléfono", "kind": "text", "required": false },
{ "name": "active", "label": "Activo", "kind": "boolean", "default": "true" },
{ "name": "credit_limit", "label": "Límite de crédito", "kind": "number", "default": "0" },
{ "name": "notes", "label": "Notas", "kind": "multiline" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "customer",
"next_view": "list"
}
}
}
}
@@ -0,0 +1,63 @@
{
"id": "inventory_movements",
"label": "Inventario",
"description": "Movimientos de stock: entradas, salidas, ajustes.",
"entities": [
{
"name": "stock_movement",
"label": "Movimiento",
"fields": [
{ "name": "occurred_at", "label": "Fecha", "kind": "date", "required": true },
{ "name": "movement_type", "label": "Tipo", "kind": "text", "required": true, "help": "in / out / adjustment" },
{ "name": "product_sku", "label": "SKU producto", "kind": "text", "required": true },
{ "name": "quantity", "label": "Cantidad", "kind": "number", "required": true },
{ "name": "unit_cost", "label": "Costo unitario", "kind": "number", "default": "0" },
{ "name": "reason", "label": "Motivo", "kind": "text", "help": "Compra, venta, merma, ajuste por inventario..." },
{ "name": "reference", "label": "Doc. referencia", "kind": "text", "help": "Factura, orden, conteo..." }
]
}
],
"menu": [
{ "label": "Movimientos", "view": "list", "icon": "📊" },
{ "label": "Registrar", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Movimientos de stock",
"entity": "stock_movement",
"columns": [
{ "field": "occurred_at", "label": "Fecha", "weight": 1.0 },
{ "field": "movement_type", "label": "Tipo", "weight": 0.7 },
{ "field": "product_sku", "label": "SKU", "weight": 1.0 },
{ "field": "quantity", "label": "Cantidad", "weight": 0.8 },
{ "field": "unit_cost", "label": "Costo", "weight": 0.8 },
{ "field": "reason", "label": "Motivo", "weight": 1.5 },
{ "field": "reference", "label": "Ref.", "weight": 1.0 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Registrar" }
],
"search_in": ["product_sku", "reason", "reference"]
},
"form": {
"kind": "form",
"title": "Registrar movimiento",
"entity": "stock_movement",
"fields": [
{ "name": "occurred_at", "label": "Fecha", "kind": "date", "required": true },
{ "name": "movement_type", "label": "Tipo (in/out/adjustment)", "kind": "text", "required": true, "default": "in" },
{ "name": "product_sku", "label": "SKU producto", "kind": "text", "required": true },
{ "name": "quantity", "label": "Cantidad", "kind": "number", "required": true },
{ "name": "unit_cost", "label": "Costo unitario", "kind": "number", "default": "0" },
{ "name": "reason", "label": "Motivo", "kind": "text" },
{ "name": "reference", "label": "Documento de referencia", "kind": "text" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "stock_movement",
"next_view": "list"
}
}
}
}
@@ -0,0 +1,72 @@
{
"id": "invoices",
"label": "Facturación",
"description": "Facturas emitidas y su seguimiento de pago.",
"entities": [
{
"name": "invoice",
"label": "Factura",
"fields": [
{ "name": "invoice_number", "label": "N° factura", "kind": "text", "required": true },
{ "name": "customer_name", "label": "Cliente", "kind": "text", "required": true },
{ "name": "issued_at", "label": "Emitida", "kind": "date", "required": true },
{ "name": "due_at", "label": "Vencimiento", "kind": "date", "required": true },
{ "name": "subtotal", "label": "Subtotal", "kind": "number", "default": "0" },
{ "name": "tax", "label": "Impuestos", "kind": "number", "default": "0" },
{ "name": "total", "label": "Total", "kind": "number", "default": "0" },
{ "name": "amount_paid", "label": "Pagado", "kind": "number", "default": "0" },
{ "name": "status", "label": "Estado", "kind": "text", "required": true, "default": "issued", "help": "draft / issued / partially_paid / paid / overdue / void" },
{ "name": "currency", "label": "Moneda", "kind": "text", "default": "USD" },
{ "name": "reference_order", "label": "Orden referencia", "kind": "text" }
]
}
],
"menu": [
{ "label": "Facturas", "view": "list", "icon": "💳" },
{ "label": "Nueva factura", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Facturas",
"entity": "invoice",
"columns": [
{ "field": "invoice_number", "label": "N°", "weight": 1.0 },
{ "field": "customer_name", "label": "Cliente", "weight": 2.0 },
{ "field": "issued_at", "label": "Emitida", "weight": 1.0 },
{ "field": "due_at", "label": "Vence", "weight": 1.0 },
{ "field": "total", "label": "Total", "weight": 1.0 },
{ "field": "amount_paid", "label": "Pagado", "weight": 1.0 },
{ "field": "currency", "label": "Mon.", "weight": 0.5 },
{ "field": "status", "label": "Estado", "weight": 0.9 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Nueva" }
],
"search_in": ["invoice_number", "customer_name", "reference_order"]
},
"form": {
"kind": "form",
"title": "Nueva factura",
"entity": "invoice",
"fields": [
{ "name": "invoice_number", "label": "Número de factura", "kind": "text", "required": true },
{ "name": "customer_name", "label": "Cliente", "kind": "text", "required": true },
{ "name": "issued_at", "label": "Fecha de emisión", "kind": "date", "required": true },
{ "name": "due_at", "label": "Fecha de vencimiento", "kind": "date", "required": true },
{ "name": "subtotal", "label": "Subtotal", "kind": "number", "default": "0" },
{ "name": "tax", "label": "Impuestos", "kind": "number", "default": "0" },
{ "name": "total", "label": "Total", "kind": "number", "default": "0" },
{ "name": "amount_paid", "label": "Pagado", "kind": "number", "default": "0" },
{ "name": "status", "label": "Estado", "kind": "text", "required": true, "default": "issued" },
{ "name": "currency", "label": "Moneda", "kind": "text", "default": "USD" },
{ "name": "reference_order", "label": "Orden de venta referenciada", "kind": "text" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "invoice",
"next_view": "list"
}
}
}
}
@@ -0,0 +1,60 @@
{
"id": "products",
"label": "Productos",
"description": "Catálogo de productos y precios.",
"entities": [
{
"name": "product",
"label": "Producto",
"fields": [
{ "name": "sku", "label": "SKU", "kind": "text", "required": true, "help": "Código único" },
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
{ "name": "category", "label": "Categoría", "kind": "text", "required": false },
{ "name": "price", "label": "Precio unitario", "kind": "number", "required": true, "default": "0" },
{ "name": "stock", "label": "Stock disponible", "kind": "number", "default": "0" },
{ "name": "active", "label": "En venta", "kind": "boolean", "default": "true" }
]
}
],
"menu": [
{ "label": "Catálogo", "view": "list", "icon": "📦" },
{ "label": "Nuevo producto", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Catálogo de productos",
"entity": "product",
"columns": [
{ "field": "sku", "label": "SKU", "weight": 1.0 },
{ "field": "name", "label": "Nombre", "weight": 2.5 },
{ "field": "category", "label": "Categoría", "weight": 1.5 },
{ "field": "price", "label": "Precio", "weight": 1.0 },
{ "field": "stock", "label": "Stock", "weight": 0.8 },
{ "field": "active", "label": "Activo", "weight": 0.6 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Nuevo" }
],
"search_in": ["sku", "name", "category"]
},
"form": {
"kind": "form",
"title": "Nuevo producto",
"entity": "product",
"fields": [
{ "name": "sku", "label": "SKU", "kind": "text", "required": true },
{ "name": "name", "label": "Nombre", "kind": "text", "required": true },
{ "name": "category", "label": "Categoría", "kind": "text" },
{ "name": "price", "label": "Precio", "kind": "number", "required": true, "default": "0" },
{ "name": "stock", "label": "Stock inicial", "kind": "number", "default": "0" },
{ "name": "active", "label": "En venta", "kind": "boolean", "default": "true" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "product",
"next_view": "list"
}
}
}
}
@@ -0,0 +1,65 @@
{
"id": "sales_orders",
"label": "Ventas",
"description": "Órdenes de venta y sus líneas.",
"entities": [
{
"name": "sales_order",
"label": "Orden de venta",
"fields": [
{ "name": "order_number", "label": "Número", "kind": "text", "required": true },
{ "name": "customer_name", "label": "Cliente", "kind": "text", "required": true },
{ "name": "issued_at", "label": "Fecha de emisión", "kind": "date", "required": true },
{ "name": "due_at", "label": "Fecha de vencimiento", "kind": "date" },
{ "name": "status", "label": "Estado", "kind": "text", "required": true, "default": "draft", "help": "draft / confirmed / shipped / closed / cancelled" },
{ "name": "subtotal", "label": "Subtotal", "kind": "number", "default": "0" },
{ "name": "tax", "label": "Impuestos", "kind": "number", "default": "0" },
{ "name": "total", "label": "Total", "kind": "number", "default": "0" },
{ "name": "notes", "label": "Notas", "kind": "multiline" }
]
}
],
"menu": [
{ "label": "Órdenes", "view": "list", "icon": "🧾" },
{ "label": "Nueva orden", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Órdenes de venta",
"entity": "sales_order",
"columns": [
{ "field": "order_number", "label": "N°", "weight": 0.8 },
{ "field": "customer_name", "label": "Cliente", "weight": 2.0 },
{ "field": "issued_at", "label": "Emitida", "weight": 1.0 },
{ "field": "status", "label": "Estado", "weight": 0.8 },
{ "field": "total", "label": "Total", "weight": 1.0 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Nueva orden" }
],
"search_in": ["order_number", "customer_name"]
},
"form": {
"kind": "form",
"title": "Nueva orden de venta",
"entity": "sales_order",
"fields": [
{ "name": "order_number", "label": "Número de orden", "kind": "text", "required": true },
{ "name": "customer_name", "label": "Cliente", "kind": "text", "required": true },
{ "name": "issued_at", "label": "Fecha de emisión", "kind": "date", "required": true },
{ "name": "due_at", "label": "Fecha de vencimiento", "kind": "date" },
{ "name": "status", "label": "Estado", "kind": "text", "required": true, "default": "draft" },
{ "name": "subtotal", "label": "Subtotal", "kind": "number", "default": "0" },
{ "name": "tax", "label": "Impuestos", "kind": "number", "default": "0" },
{ "name": "total", "label": "Total", "kind": "number", "default": "0" },
{ "name": "notes", "label": "Notas", "kind": "multiline" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "sales_order",
"next_view": "list"
}
}
}
}
@@ -0,0 +1,62 @@
{
"id": "suppliers",
"label": "Proveedores",
"description": "Proveedores que abastecen el catálogo.",
"entities": [
{
"name": "supplier",
"label": "Proveedor",
"fields": [
{ "name": "name", "label": "Razón social", "kind": "text", "required": true },
{ "name": "tax_id", "label": "ID fiscal", "kind": "text", "required": true, "help": "RUT/RFC/NIT/EIN según país" },
{ "name": "contact", "label": "Contacto", "kind": "text" },
{ "name": "email", "label": "Email", "kind": "text" },
{ "name": "phone", "label": "Teléfono", "kind": "text" },
{ "name": "payment_terms_days", "label": "Términos de pago (días)", "kind": "number", "default": "30" },
{ "name": "active", "label": "Activo", "kind": "boolean", "default": "true" }
]
}
],
"menu": [
{ "label": "Listar", "view": "list", "icon": "🏭" },
{ "label": "Nuevo", "view": "form", "icon": "✚" }
],
"views": {
"list": {
"kind": "list",
"title": "Proveedores",
"entity": "supplier",
"columns": [
{ "field": "name", "label": "Razón social", "weight": 2.5 },
{ "field": "tax_id", "label": "ID fiscal", "weight": 1.2 },
{ "field": "contact", "label": "Contacto", "weight": 1.5 },
{ "field": "email", "label": "Email", "weight": 2.0 },
{ "field": "payment_terms_days", "label": "Términos", "weight": 0.8 },
{ "field": "active", "label": "Activo", "weight": 0.5 }
],
"actions": [
{ "kind": "open_view", "view": "form", "label": "✚ Nuevo" }
],
"search_in": ["name", "tax_id", "contact", "email"]
},
"form": {
"kind": "form",
"title": "Nuevo proveedor",
"entity": "supplier",
"fields": [
{ "name": "name", "label": "Razón social", "kind": "text", "required": true },
{ "name": "tax_id", "label": "ID fiscal", "kind": "text", "required": true },
{ "name": "contact", "label": "Persona de contacto", "kind": "text" },
{ "name": "email", "label": "Email", "kind": "text" },
{ "name": "phone", "label": "Teléfono", "kind": "text" },
{ "name": "payment_terms_days", "label": "Términos de pago (días)", "kind": "number", "default": "30" },
{ "name": "active", "label": "Activo", "kind": "boolean", "default": "true" }
],
"on_submit": {
"kind": "seed_entity",
"entity": "supplier",
"next_view": "list"
}
}
}
}