create-lexy 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/assets/r/accordion.json +1 -1
  2. package/assets/r/alert-dialog.json +1 -1
  3. package/assets/r/app-accordion.json +1 -1
  4. package/assets/r/app-dialog.json +1 -1
  5. package/assets/r/app-header-bar.json +1 -1
  6. package/assets/r/app-sidebar.json +1 -1
  7. package/assets/r/avatar.json +1 -1
  8. package/assets/r/badge.json +1 -1
  9. package/assets/r/brand-background.json +135 -0
  10. package/assets/r/breadcrumb.json +1 -1
  11. package/assets/r/button-group.json +1 -1
  12. package/assets/r/button.json +1 -1
  13. package/assets/r/calendar.json +1 -1
  14. package/assets/r/card.json +1 -1
  15. package/assets/r/chart.json +1 -1
  16. package/assets/r/checkbox.json +1 -1
  17. package/assets/r/combobox.json +1 -1
  18. package/assets/r/command.json +1 -1
  19. package/assets/r/confirmacion.json +2 -2
  20. package/assets/r/counter-badge.json +1 -1
  21. package/assets/r/crm-app-layout.json +2 -2
  22. package/assets/r/crm-desk.json +2 -2
  23. package/assets/r/crm-detalle-caso.json +2 -2
  24. package/assets/r/date-picker.json +1 -1
  25. package/assets/r/dialog.json +1 -1
  26. package/assets/r/dropdown-menu.json +1 -1
  27. package/assets/r/empty.json +1 -1
  28. package/assets/r/feature-card.json +1 -1
  29. package/assets/r/form.json +1 -1
  30. package/assets/r/header-bar.json +1 -1
  31. package/assets/r/input.json +1 -1
  32. package/assets/r/intake-wizard.json +2 -2
  33. package/assets/r/label.json +1 -1
  34. package/assets/r/login.json +2 -2
  35. package/assets/r/logo.json +1 -1
  36. package/assets/r/menubar.json +1 -1
  37. package/assets/r/navigation-menu.json +1 -1
  38. package/assets/r/pagination.json +1 -1
  39. package/assets/r/popover.json +1 -1
  40. package/assets/r/profile-card.json +1 -1
  41. package/assets/r/progress.json +1 -1
  42. package/assets/r/radio-group.json +1 -1
  43. package/assets/r/registry.json +116 -0
  44. package/assets/r/scroll-area.json +1 -1
  45. package/assets/r/searchbox.json +1 -1
  46. package/assets/r/select.json +1 -1
  47. package/assets/r/separator.json +1 -1
  48. package/assets/r/sheet.json +1 -1
  49. package/assets/r/sidebar.json +2 -2
  50. package/assets/r/skeleton.json +1 -1
  51. package/assets/r/slider.json +1 -1
  52. package/assets/r/snippet.json +1 -1
  53. package/assets/r/spinner.json +1 -1
  54. package/assets/r/status-dot.json +1 -1
  55. package/assets/r/switch.json +1 -1
  56. package/assets/r/table.json +1 -1
  57. package/assets/r/tabs.json +1 -1
  58. package/assets/r/tag.json +1 -1
  59. package/assets/r/textarea.json +1 -1
  60. package/assets/r/toaster.json +1 -1
  61. package/assets/r/tooltip.json +1 -1
  62. package/assets/r/tree.json +1 -1
  63. package/assets/registry-version +1 -1
  64. package/assets/theme/lexy-theme.css +15 -4
  65. package/dist/index.js +1663 -1735
  66. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as To}from"fs";import tt from"path";import{fileURLToPath as Mo}from"url";import{Command as Lo}from"commander";import zo from"picocolors";import{spawn as lt}from"child_process";import W from"fs/promises";import Z from"path";import{confirm as dt,isCancel as pt,spinner as je}from"@clack/prompts";import S from"picocolors";import te from"path";var ot=(e,t)=>t.architecture!=="feature"?e:e.replace(/from ["']@\/lib\/utils\/cn["']/g,'from "@/shared/lib/utils/cn"').replace(/from ["']@\/components\/base\//g,'from "@/shared/components/base/'),nt=[["components/base/",e=>e.paths.components],["views/",e=>e.paths.views],["hooks/",e=>e.paths.hooks],["lib/",e=>e.paths.lib]],C=(e,t)=>e.files.filter(o=>typeof o.content=="string").map(o=>{let r=nt.find(([i])=>o.path.startsWith(i));return{relPath:r?te.posix.join(r[1](t),o.path.slice(r[0].length)):te.posix.join(t.paths.components,te.posix.basename(o.path)),content:ot(o.content,t)}});import xe from"fs/promises";import at from"path";var be=e=>at.join(e,".lexy"),L=async e=>{let t;try{t=await xe.readFile(be(e),"utf-8")}catch{throw new Error("No se encontr\xF3 el archivo .lexy. \xBFEst\xE1s en la ra\xEDz de un proyecto Lexy? (cr\xE9alo con `npx create-lexy create`)")}let o=JSON.parse(t);if(!o.paths?.components)throw new Error("El .lexy no tiene paths.components \u2014 archivo corrupto o de una versi\xF3n incompatible.");return o.installed??={},o},Ie=async(e,t)=>{await xe.writeFile(be(e),JSON.stringify(t,null,2)+`
3
- `)};import N from"fs/promises";import rt from"os";import j from"path";import{fileURLToPath as it}from"url";var Ee="@lexydesign/registry",F=`https://cdn.jsdelivr.net/npm/${Ee}`,st=8e3,J=j.resolve(j.dirname(it(import.meta.url)),"../assets"),ct=j.join(rt.homedir(),".cache","create-lexy","registry"),B=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(st)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return await t.json()},ke=async e=>{try{return JSON.parse(await N.readFile(j.join(J,"r",e),"utf-8"))}catch{return null}},oe=async()=>{try{return(await N.readFile(j.join(J,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},_=null,G=async()=>{if(_)return _;try{_=(await B(`https://registry.npmjs.org/${Ee}/latest`)).version}catch{try{_=(await B(`${F}@latest/package.json`)).version}catch{_=await oe()}}return _},x=async(e,t)=>{let o=e.replace(/^@lexy\//,""),r=t??await G(),a=j.join(ct,r,`${o}.json`);try{return{item:JSON.parse(await N.readFile(a,"utf-8")),version:r,source:"cache"}}catch{}try{let i=await B(`${F}@${r}/r/${o}.json`);return await N.mkdir(j.dirname(a),{recursive:!0}),await N.writeFile(a,JSON.stringify(i)),{item:i,version:r,source:"cdn"}}catch(i){if(i.status===404)throw new Error(`El componente "${o}" no existe en el registry (versi\xF3n ${r}). Mira el cat\xE1logo con \`create-lexy view --list\`.`);let n=await ke(`${o}.json`);if(n)return{item:n,version:await oe(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${o}" tampoco est\xE1 en el bundle local del CLI.`)}},we=async()=>{try{let e=await G();return{index:await B(`${F}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await ke("registry.json");if(e)return{index:e,version:await oe(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},Pe=j.join(J,"theme/lexy-theme.css"),Ce=j.join(J,"fonts");var mt=(e,t)=>new Promise((o,r)=>{lt("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",i=>i===0?o():r(new Error(`pnpm add fall\xF3 con c\xF3digo ${i}`)))}),ut=async e=>{try{return await W.readFile(e,"utf-8")}catch{return null}},Re=async(e,t)=>{let o=process.cwd(),r=await L(o),a=je();a.start("Resolviendo componentes en el registry...");let i=new Map,n=async m=>{let h=m.toLowerCase().replace(/^@lexy\//,"");if(i.has(h))return;let s=await x(h);i.set(h,s);for(let c of s.item.registryDependencies??[])await n(c)};try{for(let m of e)await n(m)}catch(m){throw a.stop(S.red("No se pudo resolver el pedido.")),m}let p=new Map;for(let[m,{item:h}]of i){let s=[];for(let c of C(h,r)){let g=await ut(Z.join(o,c.relPath));s.push({...c,status:g===null?"new":g===c.content?"same":"differs"})}p.set(m,s)}let l=Array.from(p.values()).flat().filter(m=>m.status==="differs");if(a.stop(`Resueltos: ${Array.from(i.keys()).join(", ")}`),l.length>0&&!t.overwrite){let m=l.map(h=>` ${h.relPath}`).join(`
4
- `);if(process.stdout.isTTY&&process.stdin.isTTY){let h=await dt({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
- ${m}
6
- \xBFContinuar?`});if(pt(h)||h!==!0){console.log(S.yellow("Operaci\xF3n cancelada \u2014 nada fue modificado.")),process.exitCode=1;return}}else{console.error(S.red(`Estos archivos existen con cambios locales:
7
- `)+m+S.dim(`
8
- Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[m,h]of p){let{item:s,version:c,source:g}=i.get(m);for(let v of h){if(v.status==="same")continue;let O=Z.join(o,v.relPath);await W.mkdir(Z.dirname(O),{recursive:!0}),await W.writeFile(O,v.content)}r.installed[s.name]=c;let I=e.map(v=>v.toLowerCase()).includes(m)?"":S.dim(" (dependencia)"),q=g==="bundle"?S.yellow(" [bundle local \u2014 sin red]"):"";console.log(S.green(`${S.bold(s.title??s.name)} instalado`)+I+S.cyan(` \u2192 ${r.paths.components}/`)+q);for(let v of h){let O=v.status==="same"?S.dim(" (sin cambios)"):"";console.log(S.dim(` ${v.relPath}${O}`))}}await Ie(o,r);let y=new Set;for(let{item:m}of i.values())for(let h of m.dependencies??[])y.add(h);let u=JSON.parse(await W.readFile(Z.join(o,"package.json"),"utf-8")),d={...u.dependencies,...u.devDependencies},P=m=>{let h=m.lastIndexOf("@");return h>0?m.slice(0,h):m},V=Array.from(y).filter(m=>!d[P(m)]);if(V.length>0)if(t.install===!1)console.log(S.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${V.join(", ")}`));else{let m=je();m.start(`Instalando dependencias: ${V.join(", ")}...`),await mt(V,o),m.stop("Dependencias instaladas.")}};import{spawn as ho}from"child_process";import{note as go,outro as vo,spinner as So}from"@clack/prompts";import E from"picocolors";import{cancel as ft,intro as yt,isCancel as ht,select as Me,text as gt}from"@clack/prompts";import Le from"picocolors";var ne=[{value:"screen",label:"Una pantalla o flujo corto",hint:"landing, formulario, demo \u2014 sin navegaci\xF3n entre secciones",architecture:"layer"},{value:"app",label:"Una app con secciones y navegaci\xF3n",hint:"dashboard, listados, detalle \u2014 varias vistas con router",architecture:"feature"}],Te=e=>ne.find(t=>t.value===e).architecture;var vt=`
2
+ import{readFileSync as Po}from"fs";import tt from"path";import{fileURLToPath as jo}from"url";import{Command as Co}from"commander";import Io from"picocolors";import{spawn as ct}from"child_process";import Q from"fs/promises";import G from"path";import{confirm as dt,isCancel as pt,spinner as Te}from"@clack/prompts";import h from"picocolors";import te from"path";var ot=(e,t)=>t.architecture!=="feature"?e:e.replace(/from ["']@\/lib\/utils\/cn["']/g,'from "@/shared/lib/utils/cn"').replace(/from ["']@\/components\/base\//g,'from "@/shared/components/base/'),at=[["components/base/",e=>e.paths.components],["views/",e=>e.paths.views],["hooks/",e=>e.paths.hooks],["lib/",e=>e.paths.lib]],T=(e,t)=>e.files.filter(o=>typeof o.content=="string").map(o=>{let a=at.find(([r])=>o.path.startsWith(r)),n=a?te.posix.join(a[1](t),o.path.slice(a[0].length)):te.posix.join(t.paths.components,te.posix.basename(o.path));return o.encoding==="base64"?{relPath:n,content:o.content,encoding:"base64"}:{relPath:n,content:ot(o.content,t)}});import we from"fs/promises";import nt from"path";var Se=e=>nt.join(e,".lexy"),O=async e=>{let t;try{t=await we.readFile(Se(e),"utf-8")}catch{throw new Error("No se encontr\xF3 el archivo .lexy. \xBFEst\xE1s en la ra\xEDz de un proyecto Lexy? (cr\xE9alo con `npx create-lexy create`)")}let o=JSON.parse(t);if(!o.paths?.components)throw new Error("El .lexy no tiene paths.components \u2014 archivo corrupto o de una versi\xF3n incompatible.");return o.installed??={},o},ke=async(e,t)=>{await we.writeFile(Se(e),JSON.stringify(t,null,2)+`
3
+ `)};import q from"fs/promises";import rt from"os";import R from"path";import{fileURLToPath as it}from"url";var Ee="@lexydesign/registry",F=`https://cdn.jsdelivr.net/npm/${Ee}`,st=8e3,J=R.resolve(R.dirname(it(import.meta.url)),"../assets"),lt=R.join(rt.homedir(),".cache","create-lexy","registry"),B=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(st)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return await t.json()},Pe=async e=>{try{return JSON.parse(await q.readFile(R.join(J,"r",e),"utf-8"))}catch{return null}},oe=async()=>{try{return(await q.readFile(R.join(J,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},$=null,W=async()=>{if($)return $;try{$=(await B(`https://registry.npmjs.org/${Ee}/latest`)).version}catch{try{$=(await B(`${F}@latest/package.json`)).version}catch{$=await oe()}}return $},k=async(e,t)=>{let o=e.replace(/^@lexy\//,""),a=t??await W(),n=R.join(lt,a,`${o}.json`);try{return{item:JSON.parse(await q.readFile(n,"utf-8")),version:a,source:"cache"}}catch{}try{let r=await B(`${F}@${a}/r/${o}.json`);return await q.mkdir(R.dirname(n),{recursive:!0}),await q.writeFile(n,JSON.stringify(r)),{item:r,version:a,source:"cdn"}}catch(r){if(r.status===404)throw new Error(`El componente "${o}" no existe en el registry (versi\xF3n ${a}). Mira el cat\xE1logo con \`create-lexy view --list\`.`);let i=await Pe(`${o}.json`);if(i)return{item:i,version:await oe(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${o}" tampoco est\xE1 en el bundle local del CLI.`)}},je=async()=>{try{let e=await W();return{index:await B(`${F}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await Pe("registry.json");if(e)return{index:e,version:await oe(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},Ce=R.join(J,"theme/lexy-theme.css"),Ie=R.join(J,"fonts");var mt=(e,t)=>new Promise((o,a)=>{ct("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",r=>r===0?o():a(new Error(`pnpm add fall\xF3 con c\xF3digo ${r}`)))}),ut=async(e,t="utf-8")=>{try{return await Q.readFile(e,t)}catch{return null}},Re=async(e,t)=>{let o=process.cwd(),a=await O(o),n=Te();n.start("Resolviendo componentes en el registry...");let r=new Map,i=async p=>{let f=p.toLowerCase().replace(/^@lexy\//,"");if(r.has(f))return;let c=await k(f);r.set(f,c);for(let l of c.item.registryDependencies??[])await i(l)};try{for(let p of e)await i(p)}catch(p){throw n.stop(h.red("No se pudo resolver el pedido.")),p}let d=new Map;for(let[p,{item:f}]of r){let c=[];for(let l of T(f,a)){let g=await ut(G.join(o,l.relPath),l.encoding??"utf-8");c.push({...l,status:g===null?"new":g===l.content?"same":"differs"})}d.set(p,c)}let s=Array.from(d.values()).flat().filter(p=>p.status==="differs");if(n.stop(`Resueltos: ${Array.from(r.keys()).join(", ")}`),s.length>0&&!t.overwrite){let p=s.map(f=>` ${f.relPath}`).join(`
4
+ `);if(process.stdout.isTTY&&process.stdin.isTTY){let f=await dt({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
+ ${p}
6
+ \xBFContinuar?`});if(pt(f)||f!==!0){console.log(h.yellow("Operaci\xF3n cancelada \u2014 nada fue modificado.")),process.exitCode=1;return}}else{console.error(h.red(`Estos archivos existen con cambios locales:
7
+ `)+p+h.dim(`
8
+ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[p,f]of d){let{item:c,version:l,source:g}=r.get(p);for(let x of f){if(x.status==="same")continue;let A=G.join(o,x.relPath);await Q.mkdir(G.dirname(A),{recursive:!0}),await Q.writeFile(A,x.content,x.encoding??"utf-8")}a.installed[c.name]=l;let P=e.map(x=>x.toLowerCase()).includes(p)?"":h.dim(" (dependencia)"),U=g==="bundle"?h.yellow(" [bundle local \u2014 sin red]"):"";console.log(h.green(`${h.bold(c.title??c.name)} instalado`)+P+h.cyan(` \u2192 ${a.paths.components}/`)+U);for(let x of f){let A=x.status==="same"?h.dim(" (sin cambios)"):"";console.log(h.dim(` ${x.relPath}${A}`))}}await ke(o,a);let u=new Set;for(let{item:p}of r.values())for(let f of p.dependencies??[])u.add(f);let m=JSON.parse(await Q.readFile(G.join(o,"package.json"),"utf-8")),y={...m.dependencies,...m.devDependencies},z=p=>{let f=p.lastIndexOf("@");return f>0?p.slice(0,f):p},M=Array.from(u).filter(p=>!y[z(p)]);if(M.length>0)if(t.install===!1)console.log(h.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${M.join(", ")}`));else{let p=Te();p.start(`Instalando dependencias: ${M.join(", ")}...`),await mt(M,o),p.stop("Dependencias instaladas.")}};import{spawn as po}from"child_process";import{note as mo,outro as uo,spinner as yo}from"@clack/prompts";import j from"picocolors";import{cancel as yt,intro as ft,isCancel as gt,select as Oe,text as xt}from"@clack/prompts";import _e from"picocolors";var ae=[{value:"screen",label:"Una pantalla o flujo corto",hint:"landing, formulario, demo \u2014 sin navegaci\xF3n entre secciones",architecture:"layer"},{value:"app",label:"Una app con secciones y navegaci\xF3n",hint:"dashboard, listados, detalle \u2014 varias vistas con router",architecture:"feature"}],Ne=e=>ae.find(t=>t.value===e).architecture;var vt=`
9
9
  ###### ######
10
10
  ##::+#### ####+::##
11
11
  #-..:+################+:..:#
@@ -20,8 +20,8 @@ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[m,h]
20
20
  ...........:====:...........
21
21
  ......................
22
22
  ..............
23
- `,ae=e=>{ht(e)&&(ft("Operaci\xF3n cancelada por el usuario."),process.exit(0))},ze=async e=>{console.clear(),yt(`${Le.cyan(vt)}
24
- ${Le.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let a=await gt({message:"\xBFCu\xE1l es el nombre de tu proyecto?",placeholder:"lexy-app",validate:i=>{if(!i||i.trim()==="")return"El nombre es obligatorio.";if(!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(i))return"El nombre debe estar en min\xFAsculas, sin espacios ni caracteres especiales."}});ae(a),t=a}let o=await Me({message:"\xBFPara qui\xE9n es este proyecto?",options:[{label:"Clientes de Lexy",value:"cliente",hint:"Producto de cara al cliente final"},{label:"Equipo interno / CRM",value:"crm",hint:"Herramienta densa para el equipo Lexy"},{label:"Ambos o a\xFAn no lo s\xE9",value:"mixto",hint:"Se decide por pantalla m\xE1s adelante"}],initialValue:"mixto"});ae(o);let r=await Me({message:"\xBFC\xF3mo es lo que vas a construir?",options:ne.map(a=>({label:a.label,value:a.value,hint:a.hint})),initialValue:"app"});return ae(r),{projectName:t,architecture:Te(r),world:o}};import R from"fs/promises";import T from"path";import Ae from"fs/promises";import Oe from"path";var re="src/prototype/data-contract/prototype-data-contract.ts",St="src/prototype/data-contract/data-contract-schema.ts",xt="scripts/check-data-contract.ts",bt=String.raw`import { z } from "zod";
23
+ `,ne=e=>{gt(e)&&(yt("Operaci\xF3n cancelada por el usuario."),process.exit(0))},Ae=async e=>{console.clear(),ft(`${_e.cyan(vt)}
24
+ ${_e.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let n=await xt({message:"\xBFCu\xE1l es el nombre de tu proyecto?",placeholder:"lexy-app",validate:r=>{if(!r||r.trim()==="")return"El nombre es obligatorio.";if(!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(r))return"El nombre debe estar en min\xFAsculas, sin espacios ni caracteres especiales."}});ne(n),t=n}let o=await Oe({message:"\xBFPara qui\xE9n es este proyecto?",options:[{label:"Clientes de Lexy",value:"cliente",hint:"Producto de cara al cliente final"},{label:"Equipo interno / CRM",value:"crm",hint:"Herramienta densa para el equipo Lexy"},{label:"Ambos o a\xFAn no lo s\xE9",value:"mixto",hint:"Se decide por pantalla m\xE1s adelante"}],initialValue:"mixto"});ne(o);let a=await Oe({message:"\xBFC\xF3mo es lo que vas a construir?",options:ae.map(n=>({label:n.label,value:n.value,hint:n.hint})),initialValue:"app"});return ne(a),{projectName:t,architecture:Ne(a),world:o}};import b from"fs/promises";import w from"path";import{format as qt,resolveConfig as Vt}from"prettier";import Le from"fs/promises";import De from"path";var re="src/prototype/data-contract/prototype-data-contract.ts",ht="src/prototype/data-contract/data-contract-schema.ts",bt="scripts/check-data-contract.ts",wt=String.raw`import { z } from "zod";
25
25
 
26
26
  const camelCaseIdSchema = z
27
27
  .string()
@@ -430,7 +430,7 @@ export function definePrototypeDataContract(
430
430
  ): PrototypeDataContract {
431
431
  return prototypeDataContractSchema.parse(contract);
432
432
  }
433
- `,It=String.raw`import { ZodError } from "zod";
433
+ `,St=String.raw`import { ZodError } from "zod";
434
434
 
435
435
  import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
436
436
 
@@ -491,7 +491,7 @@ try {
491
491
  }
492
492
  process.exitCode = 1;
493
493
  }
494
- `,Et=e=>`import { definePrototypeDataContract } from "./data-contract-schema";
494
+ `,kt=e=>`import { definePrototypeDataContract } from "./data-contract-schema";
495
495
 
496
496
  /**
497
497
  * Fuente de verdad de los datos usados por el prototipo.
@@ -509,1269 +509,1371 @@ export const prototypeDataContract = definePrototypeDataContract({
509
509
  relations: [],
510
510
  projections: {},
511
511
  });
512
- `,De=async(e,t)=>{let o=async(r,a)=>{let i=Oe.join(e,r);await Ae.mkdir(Oe.dirname(i),{recursive:!0}),await Ae.writeFile(i,a)};await o(St,bt),await o(re,Et(t)),await o(xt,It)};import $e from"fs/promises";import Ve from"path";var ie="src/prototype/event-engine/prototype-event-engine-contract.ts",kt="src/prototype/event-engine/event-engine-contract-schema.ts",wt="src/prototype/event-engine/payload-schema.ts",se="src/prototype/event-engine/event-engine.ts",Pt="scripts/check-event-engine.ts",Ct=String.raw`import { z } from "zod";
513
-
514
- const camelCaseIdSchema = z
515
- .string()
516
- .regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
517
-
518
- const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
519
-
520
- const technicalEventIdSchema = z
521
- .string()
522
- .regex(
523
- /^[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9]+-[A-Za-z0-9]+_V[0-9]+$/,
524
- "Debe usar el formato eventista Dominio_Subdominio-NombreEvento_V1.",
525
- );
526
-
527
- const dataReferenceSchema = z
528
- .string()
529
- .regex(
530
- /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)?$/,
531
- "Debe referenciar una entidad o campo como entidad o entidad.campo usando camelCase.",
532
- );
533
-
534
- const triggerSchema = z
535
- .object({
536
- id: camelCaseIdSchema.optional(),
537
- label: nonEmptyTextSchema,
538
- source: z.enum(["screen", "userAction", "system", "external", "unknown"]),
539
- description: nonEmptyTextSchema.optional(),
540
- })
541
- .strict();
542
-
543
- const schemaReferenceSchema = z
544
- .object({
545
- schemaId: camelCaseIdSchema,
546
- })
547
- .strict();
548
-
549
- export const dataLoadSpecSchema = z
550
- .object({
551
- id: camelCaseIdSchema,
552
- name: nonEmptyTextSchema,
553
- productDescription: nonEmptyTextSchema,
554
- trigger: triggerSchema,
555
- paramsSchema: schemaReferenceSchema.optional(),
556
- responseSchema: schemaReferenceSchema.optional(),
557
- reads: z
558
- .object({
559
- entities: z.array(camelCaseIdSchema).optional(),
560
- projections: z.array(camelCaseIdSchema).optional(),
561
- fields: z.array(dataReferenceSchema).optional(),
562
- })
563
- .strict()
564
- .optional(),
565
- visibleResult: nonEmptyTextSchema,
566
- pendingTi: nonEmptyTextSchema.optional(),
567
- })
568
- .strict();
569
-
570
- export const publishedEventSpecSchema = z
571
- .object({
572
- id: camelCaseIdSchema,
573
- name: nonEmptyTextSchema,
574
- technicalId: technicalEventIdSchema,
575
- productDescription: nonEmptyTextSchema,
576
- trigger: triggerSchema,
577
- payloadSchema: schemaReferenceSchema.optional(),
578
- receiptSchema: schemaReferenceSchema.optional(),
579
- writes: z
580
- .object({
581
- entities: z.array(camelCaseIdSchema).optional(),
582
- fields: z.array(dataReferenceSchema).optional(),
583
- })
584
- .strict()
585
- .optional(),
586
- visibleResult: nonEmptyTextSchema,
587
- pendingTi: nonEmptyTextSchema.optional(),
588
- })
589
- .strict();
590
-
591
- export const prototypeEventEngineContractSchema = z
592
- .object({
593
- contractVersion: z.literal("1"),
594
- dataLoads: z.record(camelCaseIdSchema, dataLoadSpecSchema),
595
- events: z.record(camelCaseIdSchema, publishedEventSpecSchema),
596
- })
597
- .strict()
598
- .superRefine((contract, context) => {
599
- for (const [loadKey, load] of Object.entries(contract.dataLoads)) {
600
- if (loadKey !== load.id) {
601
- context.addIssue({
602
- code: "custom",
603
- path: ["dataLoads", loadKey, "id"],
604
- message: 'El id "' + load.id + '" debe coincidir con la key "' + loadKey + '".',
605
- });
606
- }
607
- }
608
-
609
- const technicalIds = new Set<string>();
610
- for (const [eventKey, event] of Object.entries(contract.events)) {
611
- if (eventKey !== event.id) {
612
- context.addIssue({
613
- code: "custom",
614
- path: ["events", eventKey, "id"],
615
- message: 'El id "' + event.id + '" debe coincidir con la key "' + eventKey + '".',
616
- });
617
- }
618
-
619
- if (technicalIds.has(event.technicalId)) {
620
- context.addIssue({
621
- code: "custom",
622
- path: ["events", eventKey, "technicalId"],
623
- message: 'El ID técnico "' + event.technicalId + '" está declarado más de una vez.',
624
- });
625
- }
626
- technicalIds.add(event.technicalId);
627
- }
628
- });
629
-
630
- export type PrototypeEventEngineContract = z.infer<typeof prototypeEventEngineContractSchema>;
631
- export type DataLoadSpec = z.infer<typeof dataLoadSpecSchema>;
632
- export type PublishedEventSpec = z.infer<typeof publishedEventSpecSchema>;
633
-
634
- export type EventEngineValidationIssue = {
635
- path: Array<string | number>;
636
- message: string;
637
- };
638
-
639
- type DataContractLike = {
640
- entities: Record<string, { fields: Record<string, unknown> }>;
641
- projections: Record<string, unknown>;
642
- };
643
-
644
- type PayloadSchemasLike = Record<string, unknown>;
645
-
646
- const hasOwn = (record: Record<string, unknown>, key: string) =>
647
- Object.prototype.hasOwnProperty.call(record, key);
648
-
649
- const validateDataReference = (
650
- reference: string,
651
- dataContract: DataContractLike,
652
- ): string | null => {
653
- const [entityId, fieldId] = reference.split(".");
654
- const entity = dataContract.entities[entityId];
655
- if (!entity) return 'La entidad "' + entityId + '" no existe en el contrato de datos.';
656
- if (fieldId && !hasOwn(entity.fields, fieldId)) {
657
- return 'El campo "' + reference + '" no existe en el contrato de datos.';
658
- }
659
- return null;
660
- };
661
-
662
- export const validateEventEngineReferences = (input: {
663
- eventEngineContract: PrototypeEventEngineContract;
664
- dataContract: DataContractLike;
665
- payloadSchemas: PayloadSchemasLike;
666
- }): EventEngineValidationIssue[] => {
667
- const issues: EventEngineValidationIssue[] = [];
668
- const { eventEngineContract, dataContract, payloadSchemas } = input;
669
-
670
- for (const [loadId, load] of Object.entries(eventEngineContract.dataLoads)) {
671
- for (const [schemaKey, schemaRef] of [
672
- ["paramsSchema", load.paramsSchema],
673
- ["responseSchema", load.responseSchema],
674
- ] as const) {
675
- if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
676
- issues.push({
677
- path: ["dataLoads", loadId, schemaKey, "schemaId"],
678
- message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
679
- });
680
- }
681
- }
682
-
683
- for (const [index, entityId] of (load.reads?.entities ?? []).entries()) {
684
- if (!dataContract.entities[entityId]) {
685
- issues.push({
686
- path: ["dataLoads", loadId, "reads", "entities", index],
687
- message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
688
- });
689
- }
690
- }
691
- for (const [index, projectionId] of (load.reads?.projections ?? []).entries()) {
692
- if (!dataContract.projections[projectionId]) {
693
- issues.push({
694
- path: ["dataLoads", loadId, "reads", "projections", index],
695
- message: 'La proyección "' + projectionId + '" no existe en el contrato de datos.',
696
- });
697
- }
698
- }
699
- for (const [index, reference] of (load.reads?.fields ?? []).entries()) {
700
- const message = validateDataReference(reference, dataContract);
701
- if (message) {
702
- issues.push({
703
- path: ["dataLoads", loadId, "reads", "fields", index],
704
- message,
705
- });
706
- }
707
- }
708
- }
709
-
710
- for (const [eventId, event] of Object.entries(eventEngineContract.events)) {
711
- for (const [schemaKey, schemaRef] of [
712
- ["payloadSchema", event.payloadSchema],
713
- ["receiptSchema", event.receiptSchema],
714
- ] as const) {
715
- if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
716
- issues.push({
717
- path: ["events", eventId, schemaKey, "schemaId"],
718
- message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
719
- });
720
- }
721
- }
722
-
723
- for (const [index, entityId] of (event.writes?.entities ?? []).entries()) {
724
- if (!dataContract.entities[entityId]) {
725
- issues.push({
726
- path: ["events", eventId, "writes", "entities", index],
727
- message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
728
- });
729
- }
730
- }
731
- for (const [index, reference] of (event.writes?.fields ?? []).entries()) {
732
- const message = validateDataReference(reference, dataContract);
733
- if (message) {
734
- issues.push({
735
- path: ["events", eventId, "writes", "fields", index],
736
- message,
737
- });
738
- }
739
- }
740
- }
741
-
742
- return issues;
743
- };
744
-
745
- export function definePrototypeEventEngineContract(
746
- contract: PrototypeEventEngineContract,
747
- ): PrototypeEventEngineContract {
748
- return prototypeEventEngineContractSchema.parse(contract);
749
- }
750
- `,jt=String.raw`import { z } from "zod";
751
-
752
- /**
753
- * Schemas runtime para params, payloads, responses y receipts del motor.
754
- *
755
- * Los contratos de Spec 2 referencian estas keys mediante schemaId. El agente
756
- * agrega schemas específicos cuando declara cargas o eventos concretos.
757
- */
758
- export const unknownPayloadSchema = z.unknown();
759
- export const emptyObjectSchema = z.object({}).strict();
760
- export const unknownResponseSchema = z.unknown();
761
- export const absentReceiptSchema = z.undefined().optional();
762
-
763
- export const prototypePayloadSchemas = {
764
- unknown: unknownPayloadSchema,
765
- emptyObject: emptyObjectSchema,
766
- unknownResponse: unknownResponseSchema,
767
- absentReceipt: absentReceiptSchema,
768
- } satisfies Record<string, z.ZodType>;
769
-
770
- export type PrototypePayloadSchemaId = keyof typeof prototypePayloadSchemas;
771
- `,Rt=String.raw`import { z } from "zod";
772
-
773
- import { prototypeEventEngineContract } from "./prototype-event-engine-contract";
774
- import { prototypePayloadSchemas } from "./payload-schema";
775
- import type { PrototypeEventEngineContract } from "./event-engine-contract-schema";
776
-
777
- export type DataLoadInput = {
778
- loadId: string;
512
+ `,ze=async(e,t)=>{let o=async(a,n)=>{let r=De.join(e,a);await Le.mkdir(De.dirname(r),{recursive:!0}),await Le.writeFile(r,n)};await o(ht,wt),await o(re,kt(t)),await o(bt,St)};import Me from"fs/promises";import ie from"path";var se="src/prototype/designer-panel/designer-activity.ts",le="src/prototype/designer-panel/PrototypeDesignerPanel.tsx",Et=String.raw`export type DesignerActivityRecordInput = {
513
+ kind: "load" | "event";
514
+ label: string;
515
+ technicalId?: string;
779
516
  triggerId?: string;
780
- params?: unknown;
781
- signal?: AbortSignal;
782
- };
783
-
784
- export type PublishEventInput = {
785
- eventId: string;
786
- triggerId?: string;
787
- payload?: unknown;
788
- };
789
-
790
- export type EventPublicationEnvelope = {
791
- technicalId: string;
792
- occurredAt: string;
793
- publicationId: string;
794
- payload: unknown;
517
+ input?: unknown;
795
518
  };
796
519
 
797
- export type PublishedEventResult = {
798
- status: "published";
799
- label: "Evento publicado";
800
- eventId: string;
801
- technicalId: string;
520
+ export type DesignerActivityRecord = {
521
+ id: string;
802
522
  occurredAt: string;
803
- publicationId: string;
804
- receipt?: unknown;
523
+ kind: "load" | "event";
524
+ label: string;
525
+ technicalId?: string;
526
+ triggerId?: string;
527
+ inputPreview?: string;
805
528
  };
806
529
 
807
- export type PrototypeBackendExecutor = {
808
- loadData(input: {
809
- loadId: string;
810
- triggerId?: string;
811
- params: unknown;
812
- signal?: AbortSignal;
813
- }): Promise<unknown>;
814
- publishEvent(input: {
815
- eventId: string;
816
- triggerId?: string;
817
- envelope: EventPublicationEnvelope;
818
- }): Promise<{ receipt?: unknown } | void>;
530
+ export type DesignerActivityRecorder = {
531
+ record(input: DesignerActivityRecordInput): void;
819
532
  };
820
533
 
821
- type PrototypeEventEngineOptions = {
822
- contract?: PrototypeEventEngineContract;
823
- payloadSchemas?: Record<string, z.ZodType>;
824
- executor: PrototypeBackendExecutor;
825
- clock?: () => Date;
826
- publicationId?: () => string;
534
+ export type DesignerActivityStore = DesignerActivityRecorder & {
535
+ getSnapshot(): readonly DesignerActivityRecord[];
536
+ subscribe(listener: () => void): () => void;
537
+ clear(): void;
827
538
  };
828
539
 
829
- const createDefaultPublicationId = () =>
830
- "pub_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 10);
831
-
832
- const parseWithSchema = (schema: z.ZodType | undefined, value: unknown, label: string) => {
833
- if (!schema) return value;
834
- const result = schema.safeParse(value);
835
- if (!result.success) {
836
- const message = result.error.issues
837
- .map((issue) => (issue.path.length > 0 ? issue.path.join(".") + ": " : "") + issue.message)
838
- .join("; ");
839
- throw new Error(label + " inválido: " + message);
540
+ const preview = (value: unknown): string | undefined => {
541
+ if (value === undefined) return undefined;
542
+ try {
543
+ return JSON.stringify(value, null, 2);
544
+ } catch {
545
+ return "[no serializable]";
840
546
  }
841
- return result.data;
842
547
  };
843
548
 
844
- export const createPrototypeEventEngine = (options: PrototypeEventEngineOptions) => {
845
- const contract = options.contract ?? prototypeEventEngineContract;
846
- const payloadSchemas: Record<string, z.ZodType> =
847
- options.payloadSchemas ?? prototypePayloadSchemas;
848
- const clock = options.clock ?? (() => new Date());
849
- const publicationId = options.publicationId ?? createDefaultPublicationId;
549
+ const tabId = Math.random().toString(36).slice(2, 8);
850
550
 
851
- return {
852
- async loadData(input: DataLoadInput): Promise<unknown> {
853
- const load = contract.dataLoads[input.loadId];
854
- if (!load) throw new Error('La carga de datos "' + input.loadId + '" no está declarada.');
855
-
856
- const params = parseWithSchema(
857
- load.paramsSchema ? payloadSchemas[load.paramsSchema.schemaId] : undefined,
858
- input.params,
859
- 'Params de la carga "' + input.loadId + '"',
860
- );
861
-
862
- const response = await options.executor.loadData({
863
- loadId: input.loadId,
864
- triggerId: input.triggerId,
865
- params,
866
- signal: input.signal,
867
- });
868
-
869
- return parseWithSchema(
870
- load.responseSchema ? payloadSchemas[load.responseSchema.schemaId] : undefined,
871
- response,
872
- 'Response de la carga "' + input.loadId + '"',
873
- );
874
- },
551
+ type ChannelMessage =
552
+ | { type: "record"; record: DesignerActivityRecord }
553
+ | { type: "clear" }
554
+ | { type: "syncRequest" }
555
+ | { type: "sync"; records: DesignerActivityRecord[] };
875
556
 
876
- async publishEvent(input: PublishEventInput): Promise<PublishedEventResult> {
877
- const event = contract.events[input.eventId];
878
- if (!event) throw new Error('El evento publicado "' + input.eventId + '" no está declarado.');
557
+ export const createDesignerActivityStore = (maxItems = 80): DesignerActivityStore => {
558
+ const listeners = new Set<() => void>();
559
+ let records: DesignerActivityRecord[] = [];
560
+ let sequence = 0;
561
+ let synced = false;
879
562
 
880
- const payload = parseWithSchema(
881
- event.payloadSchema ? payloadSchemas[event.payloadSchema.schemaId] : undefined,
882
- input.payload,
883
- 'Payload del evento "' + input.eventId + '"',
884
- );
563
+ const channel =
564
+ typeof BroadcastChannel !== "undefined"
565
+ ? new BroadcastChannel("lexy-designer-activity")
566
+ : null;
885
567
 
886
- const envelope: EventPublicationEnvelope = {
887
- technicalId: event.technicalId,
888
- occurredAt: clock().toISOString(),
889
- publicationId: publicationId(),
890
- payload,
891
- };
568
+ const notify = () => {
569
+ for (const listener of listeners) listener();
570
+ };
892
571
 
893
- const executorResult = await options.executor.publishEvent({
894
- eventId: input.eventId,
895
- triggerId: input.triggerId,
896
- envelope,
897
- });
572
+ const addRecord = (record: DesignerActivityRecord) => {
573
+ records = [record, ...records].slice(0, maxItems);
574
+ notify();
575
+ };
898
576
 
899
- const receipt = parseWithSchema(
900
- event.receiptSchema ? payloadSchemas[event.receiptSchema.schemaId] : undefined,
901
- executorResult?.receipt,
902
- 'Receipt del evento "' + input.eventId + '"',
903
- );
577
+ if (channel) {
578
+ channel.onmessage = (event: MessageEvent<ChannelMessage>) => {
579
+ const message = event.data;
580
+ if (message.type === "record") addRecord(message.record);
581
+ else if (message.type === "clear") {
582
+ records = [];
583
+ notify();
584
+ } else if (message.type === "syncRequest") {
585
+ channel.postMessage({ type: "sync", records });
586
+ } else if (message.type === "sync" && !synced && records.length === 0) {
587
+ synced = true;
588
+ records = message.records.slice(0, maxItems);
589
+ notify();
590
+ }
591
+ };
592
+ channel.postMessage({ type: "syncRequest" });
593
+ }
904
594
 
905
- return {
906
- status: "published",
907
- label: "Evento publicado",
908
- eventId: input.eventId,
909
- technicalId: event.technicalId,
910
- occurredAt: envelope.occurredAt,
911
- publicationId: envelope.publicationId,
912
- ...(receipt === undefined ? {} : { receipt }),
595
+ return {
596
+ getSnapshot: () => records,
597
+ subscribe: (listener) => {
598
+ listeners.add(listener);
599
+ return () => listeners.delete(listener);
600
+ },
601
+ record: (input) => {
602
+ sequence += 1;
603
+ const record: DesignerActivityRecord = {
604
+ id: tabId + "_" + sequence,
605
+ occurredAt: new Date().toISOString(),
606
+ kind: input.kind,
607
+ label: input.label,
608
+ ...(input.technicalId ? { technicalId: input.technicalId } : {}),
609
+ ...(input.triggerId ? { triggerId: input.triggerId } : {}),
610
+ ...(preview(input.input) ? { inputPreview: preview(input.input) } : {}),
913
611
  };
612
+ addRecord(record);
613
+ channel?.postMessage({ type: "record", record });
614
+ },
615
+ clear: () => {
616
+ records = [];
617
+ notify();
618
+ channel?.postMessage({ type: "clear" });
914
619
  },
915
620
  };
916
621
  };
917
- `,Tt=String.raw`import { ZodError } from "zod";
918
622
 
919
- import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
920
- import {
921
- prototypeEventEngineContractSchema,
922
- validateEventEngineReferences,
923
- type EventEngineValidationIssue,
924
- } from "../src/prototype/event-engine/event-engine-contract-schema";
925
- import { prototypePayloadSchemas } from "../src/prototype/event-engine/payload-schema";
926
-
927
- const formatPath = (path: Array<string | number>) =>
928
- path.length === 0 ? "eventEngine" : path.map(String).join(".");
929
-
930
- const printIssues = (issues: EventEngineValidationIssue[]) => {
931
- console.error("✗ Motor de eventos inválido");
932
- for (const issue of issues) {
933
- console.error(" " + formatPath(issue.path));
934
- console.error(" " + issue.message);
935
- }
936
- };
623
+ export const designerActivity = createDesignerActivityStore();
624
+ `,Pt=`
625
+ .lexy-dp {
626
+ --dp-bg: #ffffff;
627
+ --dp-fg: #242424;
628
+ --dp-card: #fafafa;
629
+ --dp-popover: #ffffff;
630
+ --dp-primary: #4648d4;
631
+ --dp-primary-fg: #ffffff;
632
+ --dp-muted: #f5f5f5;
633
+ --dp-muted-fg: #616161;
634
+ --dp-accent: #ebf3fc;
635
+ --dp-accent-fg: #4648d4;
636
+ --dp-border: #d1d1d1;
637
+ --dp-ring: #4648d4;
638
+ --dp-destructive: #d13438;
639
+ --dp-success: #107c10;
640
+ --dp-success-fg: #ffffff;
641
+ --dp-warning: #f7630c;
642
+ --dp-warning-fg: #ffffff;
643
+ --dp-info: #0f6cbd;
644
+ --dp-radius: 0.5rem;
645
+ --dp-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
646
+ --dp-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
647
+ color: var(--dp-fg);
648
+ font-family: var(--dp-sans);
649
+ font-size: 14px;
650
+ line-height: 1.4;
651
+ }
652
+ .lexy-dp *, .lexy-dp *::before, .lexy-dp *::after { box-sizing: border-box; }
653
+ .lexy-dp p, .lexy-dp h1, .lexy-dp pre, .lexy-dp ul { margin: 0; }
654
+ .lexy-dp ul { list-style: none; padding: 0; }
655
+ .lexy-dp button { font-family: inherit; color: inherit; }
656
+ .lexy-dp code { font-family: var(--dp-mono); }
657
+
658
+ /* \u2500\u2500 P\xE1gina completa \u2500\u2500 */
659
+ .lexy-dp-page {
660
+ display: flex;
661
+ flex-direction: column;
662
+ height: 100vh;
663
+ background: var(--dp-bg);
664
+ }
937
665
 
938
- try {
939
- const dataContractModule = await import("../src/prototype/data-contract/prototype-data-contract");
940
- const eventEngineModule =
941
- await import("../src/prototype/event-engine/prototype-event-engine-contract");
666
+ /* \u2500\u2500 Tabs \u2500\u2500 */
667
+ .lexy-dp-tablist {
668
+ display: flex;
669
+ align-items: stretch;
670
+ border-bottom: 1px solid var(--dp-border);
671
+ padding: 0 12px;
672
+ background: var(--dp-bg);
673
+ }
674
+ .lexy-dp-tab {
675
+ display: inline-flex;
676
+ align-items: center;
677
+ gap: 6px;
678
+ padding: 8px 12px;
679
+ font-size: 12px;
680
+ border: none;
681
+ border-bottom: 2px solid transparent;
682
+ margin-bottom: -1px;
683
+ background: transparent;
684
+ color: var(--dp-muted-fg);
685
+ border-radius: 0;
686
+ cursor: pointer;
687
+ }
688
+ .lexy-dp-tab:hover { color: var(--dp-fg); }
689
+ .lexy-dp-tab.is-active { color: var(--dp-primary); font-weight: 500; border-bottom-color: var(--dp-primary); }
690
+ .lexy-dp-tab svg { width: 14px; height: 14px; }
691
+ .lexy-dp-tabbadge {
692
+ margin-left: 4px;
693
+ border-radius: 9999px;
694
+ background: var(--dp-warning);
695
+ color: var(--dp-warning-fg);
696
+ padding: 0 6px;
697
+ font-size: 10px;
698
+ font-weight: 500;
699
+ }
700
+ .lexy-dp-body { flex: 1; min-height: 0; }
701
+ .lexy-dp-panel { height: 100%; }
702
+
703
+ /* \u2500\u2500 Scroll y padding \u2500\u2500 */
704
+ .lexy-dp-scroll { height: 100%; overflow-y: auto; }
705
+ .lexy-dp-pad { padding: 20px 24px; }
706
+
707
+ /* \u2500\u2500 Columna y toolbar \u2500\u2500 */
708
+ .lexy-dp-col { display: flex; flex-direction: column; height: 100%; }
709
+ .lexy-dp-toolbar {
710
+ display: flex;
711
+ align-items: center;
712
+ gap: 4px;
713
+ border-bottom: 1px solid var(--dp-border);
714
+ background: var(--dp-bg);
715
+ padding: 4px 8px;
716
+ }
942
717
 
943
- const dataContractResult = prototypeDataContractSchema.safeParse(
944
- dataContractModule.prototypeDataContract,
945
- );
946
- const eventEngineResult = prototypeEventEngineContractSchema.safeParse(
947
- eventEngineModule.prototypeEventEngineContract,
948
- );
718
+ /* \u2500\u2500 Botones \u2500\u2500 */
719
+ .lexy-dp-btn {
720
+ display: inline-flex;
721
+ align-items: center;
722
+ justify-content: center;
723
+ gap: 6px;
724
+ padding: 6px 12px;
725
+ font-size: 13px;
726
+ border-radius: var(--dp-radius);
727
+ border: 1px solid transparent;
728
+ background: var(--dp-primary);
729
+ color: var(--dp-primary-fg);
730
+ cursor: pointer;
731
+ }
732
+ .lexy-dp-btn:hover { opacity: 0.92; }
733
+ .lexy-dp-btn:disabled { opacity: 0.5; cursor: not-allowed; }
734
+ .lexy-dp-btn--outline { background: transparent; border-color: var(--dp-border); color: var(--dp-fg); }
735
+ .lexy-dp-btn--outline:hover { background: var(--dp-muted); opacity: 1; }
736
+ .lexy-dp-btn--outline:disabled:hover { background: transparent; }
737
+ .lexy-dp-btn--sm { padding: 4px 10px; font-size: 12px; }
738
+ .lexy-dp-btn svg { width: 14px; height: 14px; }
739
+
740
+ /* \u2500\u2500 Badges \u2500\u2500 */
741
+ .lexy-dp-badge {
742
+ display: inline-flex;
743
+ align-items: center;
744
+ border-radius: 9999px;
745
+ padding: 1px 8px;
746
+ font-size: 11px;
747
+ font-weight: 500;
748
+ border: 1px solid transparent;
749
+ background: var(--dp-primary);
750
+ color: var(--dp-primary-fg);
751
+ white-space: nowrap;
752
+ }
753
+ .lexy-dp-badge--outline { background: var(--dp-muted); color: var(--dp-muted-fg); border-color: var(--dp-border); }
754
+ .lexy-dp-badge--success { background: var(--dp-success); color: var(--dp-success-fg); }
755
+ .lexy-dp-badge--warning { background: var(--dp-warning); color: var(--dp-warning-fg); }
756
+
757
+ /* \u2500\u2500 Cards \u2500\u2500 */
758
+ .lexy-dp-card {
759
+ border: 1px solid var(--dp-border);
760
+ background: var(--dp-card);
761
+ border-radius: var(--dp-radius);
762
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
763
+ padding: 12px 14px;
764
+ }
765
+ .lexy-dp-card__title { font-size: 14px; font-weight: 600; }
766
+ .lexy-dp-card__desc { font-size: 13px; color: var(--dp-muted-fg); margin-top: 2px; }
767
+ .lexy-dp-card__content { margin-top: 10px; }
768
+ .lexy-dp-card__head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
769
+
770
+ /* \u2500\u2500 Searchbox \u2500\u2500 */
771
+ .lexy-dp-search { position: relative; display: flex; align-items: center; flex: 1; }
772
+ .lexy-dp-search input {
773
+ width: 100%;
774
+ padding: 3px 26px 3px 8px;
775
+ font-size: 12px;
776
+ font-family: inherit;
777
+ border: 1px solid var(--dp-border);
778
+ border-radius: 3px;
779
+ background: var(--dp-bg);
780
+ color: var(--dp-fg);
781
+ }
782
+ .lexy-dp-search input:focus { outline: 2px solid var(--dp-ring); outline-offset: -1px; }
783
+ .lexy-dp-search__clear {
784
+ position: absolute;
785
+ right: 6px;
786
+ border: none;
787
+ background: transparent;
788
+ color: var(--dp-muted-fg);
789
+ cursor: pointer;
790
+ padding: 2px;
791
+ display: inline-flex;
792
+ }
793
+ .lexy-dp-search__clear svg { width: 14px; height: 14px; }
794
+
795
+ /* \u2500\u2500 Snippet / separador / etiquetas \u2500\u2500 */
796
+ .lexy-dp-snippet {
797
+ font-family: var(--dp-mono);
798
+ font-size: 12px;
799
+ background: var(--dp-muted);
800
+ border: 1px solid var(--dp-border);
801
+ border-radius: var(--dp-radius);
802
+ padding: 10px 12px;
803
+ overflow: auto;
804
+ white-space: pre;
805
+ color: var(--dp-fg);
806
+ }
807
+ .lexy-dp-sep { border: none; border-top: 1px solid var(--dp-border); height: 1px; }
808
+ .lexy-dp-section-label {
809
+ font-size: 12px;
810
+ font-weight: 500;
811
+ text-transform: uppercase;
812
+ letter-spacing: 0.04em;
813
+ color: var(--dp-muted-fg);
814
+ }
815
+ .lexy-dp-muted { color: var(--dp-muted-fg); }
816
+ .lexy-dp-mono { font-family: var(--dp-mono); }
817
+ .lexy-dp-empty { padding: 16px 12px; color: var(--dp-muted-fg); font-size: 13px; }
818
+
819
+ .lexy-dp-log__event { color: var(--dp-fg); }
820
+
821
+ /* \u2500\u2500 Stacks y grids \u2500\u2500 */
822
+ .lexy-dp-stack { display: flex; flex-direction: column; gap: 2px; }
823
+ .lexy-dp-stack-sm { display: flex; flex-direction: column; gap: 6px; }
824
+ .lexy-dp-stack-lg { display: flex; flex-direction: column; gap: 12px; }
825
+ .lexy-dp-substack { display: flex; flex-direction: column; gap: 2px; padding-left: 16px; color: var(--dp-muted-fg); }
826
+ .lexy-dp-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
827
+
828
+ /* \u2500\u2500 \xC1rbol JSON \u2500\u2500 */
829
+ .lexy-dp-json-str { color: var(--dp-fg); }
830
+ .lexy-dp-json-num { color: var(--dp-info); }
831
+ .lexy-dp-json-row { display: flex; align-items: flex-start; gap: 4px; padding: 1px 0; }
832
+ .lexy-dp-json-spacer { width: 14px; height: 14px; flex-shrink: 0; }
833
+ .lexy-dp-json-leaf { min-width: 0; word-break: break-word; }
834
+ .lexy-dp-json-toggle {
835
+ display: flex;
836
+ width: 100%;
837
+ align-items: flex-start;
838
+ gap: 4px;
839
+ padding: 1px 0;
840
+ text-align: left;
841
+ background: transparent;
842
+ border: none;
843
+ cursor: pointer;
844
+ }
845
+ .lexy-dp-json-toggle:hover { background: var(--dp-muted); }
846
+ .lexy-dp-chevron { width: 14px; height: 14px; flex-shrink: 0; color: var(--dp-muted-fg); margin-top: 1px; }
847
+ .lexy-dp-chevron--sm { width: 12px; height: 12px; margin-top: 0; }
848
+ .lexy-dp-chevron.is-open { transform: rotate(90deg); }
849
+
850
+ /* \u2500\u2500 Log de actividad \u2500\u2500 */
851
+ .lexy-dp-activitylog {
852
+ min-height: 100%;
853
+ background: var(--dp-bg);
854
+ padding: 4px 0;
855
+ font-family: var(--dp-mono);
856
+ font-size: 12px;
857
+ line-height: 1.5;
858
+ color: var(--dp-fg);
859
+ }
860
+ .lexy-dp-activitylog--scroll { flex: 1; min-height: 0; overflow-y: auto; }
861
+ .lexy-dp-log { border-bottom: 1px solid var(--dp-border); }
862
+ .lexy-dp-log__head {
863
+ display: flex;
864
+ width: 100%;
865
+ align-items: center;
866
+ gap: 6px;
867
+ padding: 4px 8px;
868
+ text-align: left;
869
+ background: transparent;
870
+ border: none;
871
+ cursor: pointer;
872
+ }
873
+ .lexy-dp-log__head:hover { background: var(--dp-muted); }
874
+ .lexy-dp-log__time { margin-left: auto; flex-shrink: 0; color: var(--dp-muted-fg); }
875
+ .lexy-dp-log__body { padding-bottom: 6px; }
876
+
877
+ /* \u2500\u2500 Detalle (event/entity detail) \u2500\u2500 */
878
+ .lexy-dp-eventdetail { display: flex; flex-direction: column; font-size: 12px; line-height: 1.5; }
879
+ .lexy-dp-detail-body { padding: 12px 14px; display: flex; flex-direction: column; gap: 6px; }
880
+ .lexy-dp-detail-field { display: flex; gap: 4px; font-family: var(--dp-mono); font-size: 12px; }
881
+ .lexy-dp-detail-label { color: var(--dp-muted-fg); flex-shrink: 0; }
882
+ .lexy-dp-detail-label::after { content: ":"; }
883
+
884
+ /* \u2500\u2500 Triggers list \u2500\u2500 */
885
+ .lexy-dp-trigger-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; }
886
+ .lexy-dp-trigger-item { display: flex; gap: 8px; align-items: baseline; }
887
+ .lexy-dp-bullet { color: var(--dp-primary); font-weight: 700; flex-shrink: 0; }
888
+
889
+
890
+ /* \u2500\u2500 Split (lista + detalle) \u2500\u2500 */
891
+ .lexy-dp-split { display: flex; height: 100%; }
892
+ .lexy-dp-split__list {
893
+ width: 288px;
894
+ flex-shrink: 0;
895
+ overflow-y: auto;
896
+ border-right: 1px solid var(--dp-border);
897
+ padding: 8px 0;
898
+ display: flex;
899
+ flex-direction: column;
900
+ gap: 1px;
901
+ }
902
+ .lexy-dp-list-section {
903
+ display: flex;
904
+ align-items: center;
905
+ justify-content: space-between;
906
+ width: 100%;
907
+ padding: 5px 10px;
908
+ font-size: 12px;
909
+ font-weight: 500;
910
+ color: var(--dp-fg);
911
+ background: var(--dp-muted);
912
+ border: none;
913
+ border-top: 1px solid var(--dp-border);
914
+ border-bottom: 1px solid var(--dp-border);
915
+ cursor: pointer;
916
+ font-family: inherit;
917
+ text-align: left;
918
+ }
919
+ .lexy-dp-list-section:hover { background: var(--dp-border); }
920
+ .lexy-dp-listitem {
921
+ width: 100%;
922
+ text-align: left;
923
+ border: none;
924
+ background: transparent;
925
+ border-radius: 0;
926
+ padding: 5px 12px;
927
+ font-family: var(--dp-mono);
928
+ font-size: 12px;
929
+ color: var(--dp-fg);
930
+ cursor: pointer;
931
+ white-space: nowrap;
932
+ overflow: hidden;
933
+ text-overflow: ellipsis;
934
+ }
935
+ .lexy-dp-listitem:hover { background: var(--dp-muted); }
936
+ .lexy-dp-listitem.is-active { background: var(--dp-accent); color: var(--dp-accent-fg); font-weight: 500; }
937
+ .lexy-dp-split__detail { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: 0; }
938
+
939
+ /* \u2500\u2500 Tabla de campos (devtools) \u2500\u2500 */
940
+ .lexy-dp-field-table { width: 100%; border-collapse: collapse; font-family: var(--dp-mono); font-size: 11px; table-layout: fixed; }
941
+ .lexy-dp-field-table th { text-align: left; font-weight: 500; color: var(--dp-fg); padding: 3px 8px; background: var(--dp-muted); border-bottom: 1px solid var(--dp-border); border-right: 1px solid var(--dp-border); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
942
+ .lexy-dp-field-table th:last-child { border-right: none; }
943
+ .lexy-dp-field-table td { padding: 2px 8px; border-bottom: 1px solid var(--dp-border); border-right: 1px solid var(--dp-border); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top; }
944
+ .lexy-dp-field-table td:last-child { border-right: none; }
945
+ .lexy-dp-field-table tbody tr { cursor: pointer; }
946
+ .lexy-dp-field-table tbody tr:hover { background: var(--dp-muted); }
947
+ .lexy-dp-field-table tbody tr.is-selected { background: var(--dp-accent); color: var(--dp-accent-fg); }
948
+ .lexy-dp-field-table tbody tr.is-selected td { color: var(--dp-accent-fg); }
949
+ .lexy-dp-field-table tbody tr:last-child td { border-bottom: none; }
950
+
951
+ /* \u2500\u2500 Launcher flotante \u2500\u2500 */
952
+ .lexy-dp-launcher {
953
+ position: fixed;
954
+ bottom: 16px;
955
+ right: 16px;
956
+ z-index: 40;
957
+ display: flex;
958
+ align-items: center;
959
+ justify-content: center;
960
+ height: 48px;
961
+ width: 48px;
962
+ gap: 0;
963
+ overflow: hidden;
964
+ border: none;
965
+ border-radius: 9999px;
966
+ background: var(--dp-fg);
967
+ color: var(--dp-primary-fg);
968
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
969
+ cursor: pointer;
970
+ padding: 0 12px;
971
+ }
972
+ .lexy-dp-launcher:hover,
973
+ .lexy-dp-launcher:focus-visible,
974
+ .lexy-dp-launcher.is-open { width: 176px; gap: 8px; }
975
+ .lexy-dp-launcher:focus-visible { outline: 2px solid var(--dp-ring); outline-offset: 2px; }
976
+ .lexy-dp-launcher__icon { width: 20px; height: 20px; flex-shrink: 0; }
977
+ .lexy-dp-launcher__label {
978
+ overflow: hidden;
979
+ white-space: nowrap;
980
+ font-size: 13px;
981
+ font-weight: 500;
982
+ max-width: 0;
983
+ opacity: 0;
984
+ }
985
+ .lexy-dp-launcher:hover .lexy-dp-launcher__label,
986
+ .lexy-dp-launcher:focus-visible .lexy-dp-launcher__label,
987
+ .lexy-dp-launcher.is-open .lexy-dp-launcher__label { max-width: 160px; opacity: 1; }
988
+
989
+ /* \u2500\u2500 Sheet flotante \u2500\u2500 */
990
+ .lexy-dp-sheet {
991
+ position: fixed;
992
+ bottom: 80px;
993
+ right: 16px;
994
+ z-index: 40;
995
+ display: flex;
996
+ flex-direction: column;
997
+ height: 70vh;
998
+ width: min(100vw - 2rem, 400px);
999
+ overflow: hidden;
1000
+ border: 1px solid var(--dp-border);
1001
+ border-radius: 12px;
1002
+ background: var(--dp-popover);
1003
+ color: var(--dp-fg);
1004
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
1005
+ }
1006
+ .lexy-dp-sheet__header {
1007
+ display: flex;
1008
+ align-items: center;
1009
+ justify-content: space-between;
1010
+ gap: 12px;
1011
+ border-bottom: 1px solid var(--dp-border);
1012
+ background: var(--dp-fg);
1013
+ padding: 12px 16px;
1014
+ color: var(--dp-primary-fg);
1015
+ }
1016
+ .lexy-dp-sheet__title { font-size: 14px; font-weight: 500; }
1017
+ .lexy-dp-sheet__actions { display: flex; align-items: center; gap: 4px; }
1018
+ .lexy-dp-iconbtn {
1019
+ display: flex;
1020
+ height: 28px;
1021
+ width: 28px;
1022
+ align-items: center;
1023
+ justify-content: center;
1024
+ border: none;
1025
+ background: transparent;
1026
+ border-radius: 6px;
1027
+ opacity: 0.7;
1028
+ cursor: pointer;
1029
+ }
1030
+ .lexy-dp-iconbtn:hover { opacity: 1; }
1031
+ .lexy-dp-iconbtn svg { width: 16px; height: 16px; }
1032
+ .lexy-dp-sheet__body { flex: 1; min-height: 0; overflow-y: auto; background: var(--dp-bg); }
1033
+ `,jt=()=>String.raw`import {
1034
+ Activity,
1035
+ ArrowUpRight,
1036
+ ChevronRight,
1037
+ Maximize2,
1038
+ Settings,
1039
+ Trash2,
1040
+ X,
1041
+ Zap,
1042
+ } from "lucide-react";
1043
+ import { useState, useSyncExternalStore } from "react";
949
1044
 
950
- const issues: EventEngineValidationIssue[] = [];
951
- if (!dataContractResult.success) {
952
- for (const issue of dataContractResult.error.issues) {
953
- issues.push({
954
- path: ["dataContract", ...issue.path.map(String)],
955
- message: issue.message,
956
- });
957
- }
958
- }
959
- if (!eventEngineResult.success) {
960
- for (const issue of eventEngineResult.error.issues) {
961
- issues.push({
962
- path: issue.path.map(String),
963
- message: issue.message,
964
- });
965
- }
966
- }
1045
+ import { prototypeDataContract } from "../data-contract/prototype-data-contract";
1046
+ import {
1047
+ catalog,
1048
+ type CatalogEvent,
1049
+ type CatalogLoad,
1050
+ } from "../ports/catalog";
1051
+ import type { DesignerActivityRecord } from "./designer-activity";
1052
+ import { designerActivity } from "./designer-activity";
1053
+
1054
+ // Panel autocontenido: trae su propio CSS scopeado (.lexy-dp) y no depende del
1055
+ // tema Tailwind del proyecto host ni de componentes del registry. Así se puede
1056
+ // previsualizar e iterar el diseño en cualquier entorno.
1057
+ const PANEL_STYLES = ${JSON.stringify(Pt)};
1058
+
1059
+ function PanelStyles() {
1060
+ return <style>{PANEL_STYLES}</style>;
1061
+ }
967
1062
 
968
- if (dataContractResult.success && eventEngineResult.success) {
969
- issues.push(
970
- ...validateEventEngineReferences({
971
- eventEngineContract: eventEngineResult.data,
972
- dataContract: dataContractResult.data,
973
- payloadSchemas: prototypePayloadSchemas,
974
- }),
975
- );
1063
+ const formatTime = (value: string) =>
1064
+ new Intl.DateTimeFormat("es-CL", {
1065
+ hour: "2-digit",
1066
+ minute: "2-digit",
1067
+ second: "2-digit",
1068
+ }).format(new Date(value));
1069
+
1070
+ const safeParse = (value: string | undefined): unknown => {
1071
+ if (value === undefined) return undefined;
1072
+ try {
1073
+ return JSON.parse(value);
1074
+ } catch {
1075
+ return value;
976
1076
  }
1077
+ };
977
1078
 
978
- if (issues.length > 0) {
979
- printIssues(issues);
980
- process.exitCode = 1;
981
- } else {
982
- const contract = eventEngineResult.data!;
983
- console.log(" Motor de eventos válido");
984
- console.log(" " + Object.keys(contract.dataLoads).length + " cargas de datos");
985
- console.log(" " + Object.keys(contract.events).length + " eventos publicados");
986
- }
987
- } catch (error) {
988
- if (error instanceof ZodError) {
989
- printIssues(
990
- error.issues.map((issue) => ({ path: issue.path.map(String), message: issue.message })),
1079
+ const TREE_INDENT = 14;
1080
+ const TREE_BASE = 8;
1081
+
1082
+ const renderPrimitive = (value: unknown) => {
1083
+ if (typeof value === "string") return <span className="lexy-dp-json-str">"{value}"</span>;
1084
+ if (typeof value === "number") return <span className="lexy-dp-json-num">{value}</span>;
1085
+ if (typeof value === "boolean") return <span className="lexy-dp-json-num">{String(value)}</span>;
1086
+ if (value === null) return <span className="lexy-dp-muted">null</span>;
1087
+ return <span className="lexy-dp-json-str">{String(value)}</span>;
1088
+ };
1089
+
1090
+ function JsonNode({
1091
+ label,
1092
+ value,
1093
+ depth = 0,
1094
+ defaultOpen = false,
1095
+ }: {
1096
+ label: string;
1097
+ value: unknown;
1098
+ depth?: number;
1099
+ defaultOpen?: boolean;
1100
+ }) {
1101
+ const [open, setOpen] = useState(defaultOpen);
1102
+ const isBranch = value !== null && typeof value === "object";
1103
+ const indent = { paddingLeft: (depth * TREE_INDENT + TREE_BASE) + "px" };
1104
+
1105
+ if (!isBranch) {
1106
+ return (
1107
+ <div style={indent} className="lexy-dp-json-row">
1108
+ <span className="lexy-dp-json-spacer" aria-hidden />
1109
+ <span className="lexy-dp-json-leaf">
1110
+ <span className="lexy-dp-muted">{label}:</span> {renderPrimitive(value)}
1111
+ </span>
1112
+ </div>
991
1113
  );
992
- } else {
993
- console.error("✗ No se pudo cargar el motor de eventos");
994
- console.error(" " + (error instanceof Error ? error.message : String(error)));
995
1114
  }
996
- process.exitCode = 1;
997
- }
998
- `,Mt=()=>`import { definePrototypeEventEngineContract } from "./event-engine-contract-schema";
999
-
1000
- /**
1001
- * Cat\xE1logo de comunicaciones externas del frontend.
1002
- *
1003
- * - dataLoads: lecturas remotas para alimentar la UI.
1004
- * - events: escrituras publicadas como hechos Lexy.
1005
- *
1006
- * Las interacciones locales no se declaran ac\xE1.
1007
- */
1008
- export const prototypeEventEngineContract = definePrototypeEventEngineContract({
1009
- contractVersion: "1",
1010
- dataLoads: {},
1011
- events: {},
1012
- });
1013
- `,_e=async e=>{let t=async(o,r)=>{let a=Ve.join(e,o);await $e.mkdir(Ve.dirname(a),{recursive:!0}),await $e.writeFile(a,r)};await t(kt,Ct),await t(ie,Mt()),await t(wt,jt),await t(se,Rt),await t(Pt,Tt)};import Fe from"fs/promises";import Ne from"path";var Lt="src/prototype/simulator/simulator-contract-schema.ts",ce="src/prototype/simulator/mock-generation-profile.ts",le="src/prototype/simulator/mock-scenarios.ts",zt="src/prototype/simulator/projection-resolvers.ts",At="src/prototype/simulator/data-load-handlers.ts",Ot="src/prototype/simulator/event-handlers.ts",Dt="src/prototype/simulator/mock-store.ts",$t="src/prototype/simulator/mock-backend-executor.ts",de="src/prototype/simulator/prototype-simulator.ts",Vt="scripts/check-simulator.ts",_t=String.raw`import { z } from "zod";
1014
1115
 
1015
- const camelCaseIdSchema = z
1016
- .string()
1017
- .regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
1116
+ const entries = Object.entries(value as Record<string, unknown>);
1018
1117
 
1019
- const dataReferenceSchema = z
1020
- .string()
1021
- .regex(
1022
- /^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/,
1023
- "Debe referenciar un campo como entidad.campo usando camelCase.",
1118
+ return (
1119
+ <div>
1120
+ <button
1121
+ type="button"
1122
+ onClick={() => setOpen((v) => !v)}
1123
+ style={indent}
1124
+ className="lexy-dp-json-toggle"
1125
+ >
1126
+ <ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
1127
+ <span className="lexy-dp-json-leaf">
1128
+ <span className="lexy-dp-muted">{label}:</span>
1129
+ {!open ? (
1130
+ <span className="lexy-dp-muted">
1131
+ {" "}
1132
+ {Array.isArray(value) ? "Array(" + entries.length + ")" : "{…}"}
1133
+ </span>
1134
+ ) : null}
1135
+ </span>
1136
+ </button>
1137
+ {open
1138
+ ? entries.map(([key, child]) => (
1139
+ <JsonNode key={key} label={key} value={child} depth={depth + 1} defaultOpen />
1140
+ ))
1141
+ : null}
1142
+ </div>
1024
1143
  );
1144
+ }
1025
1145
 
1026
- const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
1027
-
1028
- export type JsonPrimitive = string | number | boolean | null;
1029
- export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
1030
- export type MockEntityRecord = Record<string, JsonValue>;
1031
-
1032
- export const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
1033
- z.union([
1034
- z.string(),
1035
- z.number(),
1036
- z.boolean(),
1037
- z.null(),
1038
- z.array(jsonValueSchema),
1039
- z.record(z.string(), jsonValueSchema),
1040
- ]),
1041
- );
1042
-
1043
- export const mockEntityRecordSchema = z.record(z.string(), jsonValueSchema);
1044
-
1045
- export const mockSemanticSchema = z.enum([
1046
- "personName",
1047
- "rutCl",
1048
- "mobilePhoneCl",
1049
- "landlinePhoneCl",
1050
- "email",
1051
- "clpAmount",
1052
- "dateCl",
1053
- "datetimeCl",
1054
- "regionCl",
1055
- "communeCl",
1056
- "addressCl",
1057
- "companyNameCl",
1058
- "legalCaseReferenceCl",
1059
- "freeText",
1060
- "custom",
1061
- ]);
1062
-
1063
- export const fieldMockHintSchema = z
1064
- .object({
1065
- semantic: mockSemanticSchema,
1066
- description: nonEmptyTextSchema.optional(),
1067
- examples: z.array(nonEmptyTextSchema).optional(),
1068
- constraints: z
1069
- .object({
1070
- min: z.number().optional(),
1071
- max: z.number().optional(),
1072
- pattern: nonEmptyTextSchema.optional(),
1073
- allowedValues: z.array(nonEmptyTextSchema).optional(),
1074
- })
1075
- .strict()
1076
- .optional(),
1077
- rutValidation: z.enum(["formatOnly", "validChecksum"]).optional(),
1078
- })
1079
- .strict();
1080
-
1081
- export const mockGenerationProfileSchema = z
1082
- .object({
1083
- profileVersion: z.literal("1"),
1084
- locale: z
1085
- .object({
1086
- language: z.literal("es"),
1087
- locale: z.literal("es-CL"),
1088
- country: z.literal("CL"),
1089
- timezone: z.literal("America/Santiago"),
1090
- currency: z.literal("CLP"),
1091
- dateStorage: z.literal("iso8601"),
1092
- dateDisplay: z.literal("dd-MM-yyyy"),
1093
- timeDisplay: z.literal("24h"),
1094
- })
1095
- .strict(),
1096
- safety: z
1097
- .object({
1098
- syntheticOnly: z.literal(true),
1099
- allowProductionImports: z.literal(false),
1100
- externalUse: z.literal(false),
1101
- emailDomain: z.literal("example.com"),
1102
- })
1103
- .strict(),
1104
- entityKeys: z.record(
1105
- camelCaseIdSchema,
1106
- z
1107
- .object({
1108
- field: dataReferenceSchema,
1109
- })
1110
- .strict(),
1111
- ),
1112
- relationBindings: z.record(
1113
- camelCaseIdSchema,
1114
- z
1115
- .object({
1116
- relation: camelCaseIdSchema,
1117
- fromField: dataReferenceSchema,
1118
- toField: dataReferenceSchema,
1119
- })
1120
- .strict(),
1121
- ),
1122
- fieldHints: z.record(dataReferenceSchema, fieldMockHintSchema),
1123
- })
1124
- .strict();
1125
-
1126
- export const mockOperationErrorSchema = z
1127
- .object({
1128
- code: camelCaseIdSchema,
1129
- message: nonEmptyTextSchema,
1130
- recoverable: z.boolean(),
1131
- })
1132
- .strict();
1133
-
1134
- export const mockDataLoadBehaviorSchema = z
1135
- .object({
1136
- latencyMs: z.number().int().min(0).optional(),
1137
- outcome: z.enum(["normal", "empty", "error"]).optional(),
1138
- error: mockOperationErrorSchema.optional(),
1139
- })
1140
- .strict()
1141
- .superRefine((behavior, context) => {
1142
- const outcome = behavior.outcome ?? "normal";
1143
- if (outcome === "error" && !behavior.error) {
1144
- context.addIssue({
1145
- code: "custom",
1146
- path: ["error"],
1147
- message: "Un behavior error debe declarar el error controlado.",
1148
- });
1149
- }
1150
- if (outcome !== "error" && behavior.error) {
1151
- context.addIssue({
1152
- code: "custom",
1153
- path: ["error"],
1154
- message: "Solo un behavior error puede declarar error.",
1155
- });
1156
- }
1157
- });
1158
-
1159
- export const mockEventBehaviorSchema = z
1160
- .object({
1161
- latencyMs: z.number().int().min(0).optional(),
1162
- outcome: z.enum(["normal", "error"]).optional(),
1163
- error: mockOperationErrorSchema.optional(),
1164
- })
1165
- .strict()
1166
- .superRefine((behavior, context) => {
1167
- const outcome = behavior.outcome ?? "normal";
1168
- if (outcome === "error" && !behavior.error) {
1169
- context.addIssue({
1170
- code: "custom",
1171
- path: ["error"],
1172
- message: "Un behavior error debe declarar el error controlado.",
1173
- });
1174
- }
1175
- if (outcome !== "error" && behavior.error) {
1176
- context.addIssue({
1177
- code: "custom",
1178
- path: ["error"],
1179
- message: "Solo un behavior error puede declarar error.",
1180
- });
1181
- }
1182
- });
1183
-
1184
- export const mockScenarioSchema = z
1185
- .object({
1186
- id: camelCaseIdSchema,
1187
- name: nonEmptyTextSchema,
1188
- description: nonEmptyTextSchema,
1189
- entities: z.record(camelCaseIdSchema, z.array(mockEntityRecordSchema)),
1190
- behaviors: z
1191
- .object({
1192
- dataLoads: z.record(camelCaseIdSchema, mockDataLoadBehaviorSchema).optional(),
1193
- events: z.record(camelCaseIdSchema, mockEventBehaviorSchema).optional(),
1194
- })
1195
- .strict()
1196
- .optional(),
1197
- })
1198
- .strict();
1199
-
1200
- export const prototypeMockScenariosSchema = z
1201
- .object({
1202
- scenariosVersion: z.literal("1"),
1203
- datasetVersion: z.number().int().positive(),
1204
- defaultScenarioId: camelCaseIdSchema,
1205
- scenarios: z.record(camelCaseIdSchema, mockScenarioSchema),
1206
- })
1207
- .strict()
1208
- .superRefine((catalog, context) => {
1209
- if (!catalog.scenarios[catalog.defaultScenarioId]) {
1210
- context.addIssue({
1211
- code: "custom",
1212
- path: ["defaultScenarioId"],
1213
- message: 'El escenario default "' + catalog.defaultScenarioId + '" no existe.',
1214
- });
1215
- }
1146
+ function LogEntry({ item }: { item: DesignerActivityRecord }) {
1147
+ const [open, setOpen] = useState(false);
1148
+ const trigger = item.triggerId ?? "desconocido";
1149
+ const operationName = item.technicalId ?? item.label;
1150
+ const input = safeParse(item.inputPreview);
1151
+ const inputLabel = item.kind === "load" ? "params" : "payload";
1216
1152
 
1217
- for (const [scenarioKey, scenario] of Object.entries(catalog.scenarios)) {
1218
- if (scenarioKey !== scenario.id) {
1219
- context.addIssue({
1220
- code: "custom",
1221
- path: ["scenarios", scenarioKey, "id"],
1222
- message: 'El id "' + scenario.id + '" debe coincidir con la key "' + scenarioKey + '".',
1223
- });
1224
- }
1225
- }
1226
- });
1153
+ return (
1154
+ <div className="lexy-dp-log">
1155
+ <button type="button" onClick={() => setOpen((v) => !v)} className="lexy-dp-log__head">
1156
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (open ? " is-open" : "")} />
1157
+ <span className="lexy-dp-log__event">{operationName}</span>
1158
+ <span className="lexy-dp-log__time">{formatTime(item.occurredAt)}</span>
1159
+ </button>
1160
+ {open ? (
1161
+ <div className="lexy-dp-log__body">
1162
+ <div style={{ paddingLeft: TREE_BASE + "px" }} className="lexy-dp-json-row">
1163
+ <span className="lexy-dp-json-spacer" aria-hidden />
1164
+ <span className="lexy-dp-json-leaf">
1165
+ <span className="lexy-dp-muted">fuente:</span>{" "}
1166
+ <span className="lexy-dp-json-str">{trigger}</span>
1167
+ </span>
1168
+ </div>
1169
+ {input !== undefined ? <JsonNode label={inputLabel} value={input} defaultOpen /> : null}
1170
+ </div>
1171
+ ) : null}
1172
+ </div>
1173
+ );
1174
+ }
1227
1175
 
1228
- export type MockSemantic = z.infer<typeof mockSemanticSchema>;
1229
- export type FieldMockHint = z.infer<typeof fieldMockHintSchema>;
1230
- export type MockGenerationProfile = z.infer<typeof mockGenerationProfileSchema>;
1231
- export type MockOperationError = z.infer<typeof mockOperationErrorSchema>;
1232
- export type MockDataLoadBehavior = z.infer<typeof mockDataLoadBehaviorSchema>;
1233
- export type MockEventBehavior = z.infer<typeof mockEventBehaviorSchema>;
1234
- export type MockScenario = z.infer<typeof mockScenarioSchema>;
1235
- export type PrototypeMockScenarios = z.infer<typeof prototypeMockScenariosSchema>;
1236
-
1237
- export type SimulatorValidationIssue = {
1238
- path: Array<string | number>;
1239
- message: string;
1240
- };
1176
+ const DESIGNER_PANEL_ROUTE = "/designer-panel";
1241
1177
 
1242
- type DataContractFieldLike = {
1243
- dataType: "string" | "number" | "boolean" | "date" | "datetime" | "enum" | "identifier";
1244
- required: boolean;
1245
- enumValues?: string[];
1246
- };
1178
+ function ActivityLog() {
1179
+ const activity = useSyncExternalStore(
1180
+ designerActivity.subscribe,
1181
+ designerActivity.getSnapshot,
1182
+ designerActivity.getSnapshot,
1183
+ );
1184
+ return (
1185
+ <div className="lexy-dp-activitylog">
1186
+ {activity.length === 0 ? (
1187
+ <p className="lexy-dp-empty">Sin actividad. Ejecuta una carga o publica un evento.</p>
1188
+ ) : null}
1189
+ {activity.map((item) => (
1190
+ <LogEntry key={item.id} item={item} />
1191
+ ))}
1192
+ </div>
1193
+ );
1194
+ }
1247
1195
 
1248
- type DataContractLike = {
1249
- entities: Record<
1250
- string,
1251
- {
1252
- fields: Record<string, DataContractFieldLike>;
1253
- }
1254
- >;
1255
- relations: Array<{ id: string; fromEntity: string; toEntity: string }>;
1256
- projections: Record<string, unknown>;
1196
+ const matchesQuery = (item: DesignerActivityRecord, query: string): boolean => {
1197
+ const haystack = [
1198
+ item.technicalId,
1199
+ item.label,
1200
+ item.triggerId,
1201
+ item.inputPreview,
1202
+ ]
1203
+ .filter(Boolean)
1204
+ .join(" ")
1205
+ .toLowerCase();
1206
+ return haystack.includes(query);
1257
1207
  };
1258
1208
 
1259
- type EventEngineContractLike = {
1260
- dataLoads: Record<string, unknown>;
1261
- events: Record<string, unknown>;
1262
- };
1209
+ function ActivityTab() {
1210
+ const [query, setQuery] = useState("");
1211
+ const activity = useSyncExternalStore(
1212
+ designerActivity.subscribe,
1213
+ designerActivity.getSnapshot,
1214
+ designerActivity.getSnapshot,
1215
+ );
1216
+ const normalizedQuery = query.trim().toLowerCase();
1217
+ const filtered = normalizedQuery
1218
+ ? activity.filter((item) => matchesQuery(item, normalizedQuery))
1219
+ : activity;
1263
1220
 
1264
- const splitReference = (reference: string) => {
1265
- const [entityId, fieldId] = reference.split(".");
1266
- return { entityId, fieldId };
1267
- };
1221
+ return (
1222
+ <div className="lexy-dp-col">
1223
+ <div className="lexy-dp-toolbar">
1224
+ <button
1225
+ type="button"
1226
+ aria-label="Limpiar actividad"
1227
+ title="Limpiar actividad"
1228
+ className="lexy-dp-iconbtn"
1229
+ onClick={() => designerActivity.clear()}
1230
+ >
1231
+ <Trash2 />
1232
+ </button>
1233
+ <div className="lexy-dp-search">
1234
+ <input
1235
+ value={query}
1236
+ onChange={(event) => setQuery(event.target.value)}
1237
+ placeholder="Buscar en los logs…"
1238
+ />
1239
+ {query ? (
1240
+ <button
1241
+ type="button"
1242
+ aria-label="Limpiar búsqueda"
1243
+ className="lexy-dp-search__clear"
1244
+ onClick={() => setQuery("")}
1245
+ >
1246
+ <X />
1247
+ </button>
1248
+ ) : null}
1249
+ </div>
1250
+ </div>
1251
+ <div className="lexy-dp-activitylog lexy-dp-activitylog--scroll">
1252
+ {filtered.length === 0 ? (
1253
+ <p className="lexy-dp-empty">
1254
+ {activity.length === 0
1255
+ ? "Sin actividad. Ejecuta una carga o publica un evento."
1256
+ : "Sin coincidencias para la búsqueda."}
1257
+ </p>
1258
+ ) : null}
1259
+ {filtered.map((item) => (
1260
+ <LogEntry key={item.id} item={item} />
1261
+ ))}
1262
+ </div>
1263
+ </div>
1264
+ );
1265
+ }
1268
1266
 
1269
- const getField = (dataContract: DataContractLike, reference: string) => {
1270
- const { entityId, fieldId } = splitReference(reference);
1271
- return {
1272
- entityId,
1273
- fieldId,
1274
- entity: dataContract.entities[entityId],
1275
- field: dataContract.entities[entityId]?.fields[fieldId],
1276
- };
1267
+ const inferType = (value: unknown): string => {
1268
+ if (value === null) return "null";
1269
+ if (Array.isArray(value)) return "array";
1270
+ return typeof value;
1277
1271
  };
1278
1272
 
1279
- const isSemanticCompatible = (semantic: MockSemantic, field: DataContractFieldLike): boolean => {
1280
- if (semantic === "custom") return true;
1281
- if (semantic === "clpAmount") return field.dataType === "number";
1282
- if (semantic === "dateCl") return field.dataType === "date";
1283
- if (semantic === "datetimeCl") return field.dataType === "datetime";
1284
- if (field.dataType === "enum") return ["regionCl", "communeCl", "custom"].includes(semantic);
1285
- return ["string", "identifier"].includes(field.dataType);
1273
+ const TYPE_COLORS: Record<string, string> = {
1274
+ string: "var(--dp-success)",
1275
+ number: "var(--dp-info)",
1276
+ boolean: "var(--dp-warning)",
1286
1277
  };
1287
1278
 
1288
- const rutFormat = /^[0-9]{7,8}-[0-9K]$/;
1279
+ const TypeLabel = ({ value }: { value: unknown }) => (
1280
+ <span className="lexy-dp-muted" style={{ marginLeft: "8px" }}>{inferType(value)}</span>
1281
+ );
1289
1282
 
1290
- const isValidRutChecksum = (rut: string) => {
1291
- if (!rutFormat.test(rut)) return false;
1292
- const [body, verifier] = rut.split("-");
1293
- let sum = 0;
1294
- let multiplier = 2;
1295
- for (let index = body.length - 1; index >= 0; index -= 1) {
1296
- sum += Number(body[index]) * multiplier;
1297
- multiplier = multiplier === 7 ? 2 : multiplier + 1;
1298
- }
1299
- const result = 11 - (sum % 11);
1300
- const expected = result === 11 ? "0" : result === 10 ? "K" : String(result);
1301
- return verifier === expected;
1302
- };
1283
+ function PayloadKeyNode({
1284
+ label,
1285
+ value,
1286
+ depth = 0,
1287
+ }: {
1288
+ label: string;
1289
+ value: unknown;
1290
+ depth?: number;
1291
+ }) {
1292
+ const [open, setOpen] = useState(true);
1293
+ const indent = { paddingLeft: depth * TREE_INDENT + TREE_BASE + "px" };
1303
1294
 
1304
- const mobilePhoneFormat = /^\+56 9 [0-9]{4} [0-9]{4}$/;
1305
- const landlinePhoneFormat = /^\+56 [2-9] [0-9]{4} [0-9]{4}$/;
1306
- const isoDateFormat = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
1307
- const datetimeWithOffsetFormat =
1308
- /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{3})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
1309
- const emailFormat = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1310
- const secretLikeFormat =
1311
- /(sk-[a-z0-9_-]{12,}|ghp_[a-z0-9_]{12,}|api[_-]?key|secret|token|password|BEGIN PRIVATE KEY)/i;
1312
-
1313
- const validateChileanValue = (input: {
1314
- value: JsonValue;
1315
- hint: FieldMockHint;
1316
- emailDomain: string;
1317
- }): string | null => {
1318
- const { value, hint, emailDomain } = input;
1319
- if (value === null) return null;
1320
-
1321
- if (hint.semantic === "rutCl") {
1322
- if (typeof value !== "string" || !rutFormat.test(value)) {
1323
- return "El RUT debe usar formato 12345678-9 y K mayúscula.";
1324
- }
1325
- if (hint.rutValidation === "validChecksum" && !isValidRutChecksum(value)) {
1326
- return "El RUT no cumple el dígito verificador módulo 11.";
1295
+ if (Array.isArray(value)) {
1296
+ const firstObj = value.find(
1297
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item),
1298
+ );
1299
+ const children = firstObj ? Object.entries(firstObj as Record<string, unknown>) : [];
1300
+ if (children.length === 0) {
1301
+ return (
1302
+ <div style={indent} className="lexy-dp-json-row">
1303
+ <span className="lexy-dp-json-spacer" aria-hidden />
1304
+ <span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
1305
+ </div>
1306
+ );
1327
1307
  }
1308
+ return (
1309
+ <div>
1310
+ <button type="button" onClick={() => setOpen((v) => !v)} style={indent} className="lexy-dp-json-toggle">
1311
+ <ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
1312
+ <span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
1313
+ </button>
1314
+ {open
1315
+ ? children.map(([key, child]) => (
1316
+ <PayloadKeyNode key={key} label={key} value={child} depth={depth + 1} />
1317
+ ))
1318
+ : null}
1319
+ </div>
1320
+ );
1328
1321
  }
1329
1322
 
1330
- if (
1331
- hint.semantic === "mobilePhoneCl" &&
1332
- (typeof value !== "string" || !mobilePhoneFormat.test(value))
1333
- ) {
1334
- return "El móvil chileno debe usar formato +56 9 XXXX XXXX.";
1335
- }
1336
-
1337
- if (
1338
- hint.semantic === "landlinePhoneCl" &&
1339
- (typeof value !== "string" || !landlinePhoneFormat.test(value))
1340
- ) {
1341
- return "El teléfono fijo chileno debe usar formato +56 X XXXX XXXX.";
1342
- }
1343
-
1344
- if (hint.semantic === "dateCl" && (typeof value !== "string" || !isoDateFormat.test(value))) {
1345
- return "La fecha de storage debe usar ISO YYYY-MM-DD.";
1346
- }
1347
-
1348
- if (
1349
- hint.semantic === "datetimeCl" &&
1350
- (typeof value !== "string" || !datetimeWithOffsetFormat.test(value))
1351
- ) {
1352
- return "El datetime debe usar ISO 8601 con offset o Z.";
1353
- }
1354
-
1355
- if (hint.semantic === "clpAmount" && (!Number.isInteger(value) || typeof value !== "number")) {
1356
- return "Los montos CLP deben almacenarse como enteros sin decimales.";
1357
- }
1358
-
1359
- if (hint.semantic === "email") {
1360
- if (typeof value !== "string" || !emailFormat.test(value)) {
1361
- return "El correo debe tener formato válido.";
1362
- }
1363
- if (!value.endsWith("@" + emailDomain)) {
1364
- return 'Los correos mock deben usar el dominio "' + emailDomain + '".';
1365
- }
1323
+ if (value !== null && typeof value === "object") {
1324
+ const entries = Object.entries(value as Record<string, unknown>);
1325
+ return (
1326
+ <div>
1327
+ <button type="button" onClick={() => setOpen((v) => !v)} style={indent} className="lexy-dp-json-toggle">
1328
+ <ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
1329
+ <span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
1330
+ </button>
1331
+ {open
1332
+ ? entries.map(([key, child]) => (
1333
+ <PayloadKeyNode key={key} label={key} value={child} depth={depth + 1} />
1334
+ ))
1335
+ : null}
1336
+ </div>
1337
+ );
1366
1338
  }
1367
1339
 
1368
- return null;
1369
- };
1370
-
1371
- const validateValueType = (field: DataContractFieldLike, value: JsonValue): string | null => {
1372
- if (value === null) return null;
1373
- if (field.dataType === "number" && typeof value !== "number") return "Debe ser number.";
1374
- if (field.dataType === "boolean" && typeof value !== "boolean") return "Debe ser boolean.";
1375
- if (
1376
- ["string", "identifier", "date", "datetime", "enum"].includes(field.dataType) &&
1377
- typeof value !== "string"
1378
- ) {
1379
- return "Debe ser string.";
1380
- }
1381
- if (field.dataType === "date" && typeof value === "string" && !isoDateFormat.test(value)) {
1382
- return "Debe usar fecha ISO YYYY-MM-DD.";
1383
- }
1384
- if (
1385
- field.dataType === "datetime" &&
1386
- typeof value === "string" &&
1387
- !datetimeWithOffsetFormat.test(value)
1388
- ) {
1389
- return "Debe usar datetime ISO 8601 con offset o Z.";
1390
- }
1391
- if (
1392
- field.dataType === "enum" &&
1393
- typeof value === "string" &&
1394
- !field.enumValues?.includes(value)
1395
- ) {
1396
- return 'El valor "' + value + '" no existe en enumValues.';
1397
- }
1398
- return null;
1399
- };
1340
+ return (
1341
+ <div style={indent} className="lexy-dp-json-row">
1342
+ <span className="lexy-dp-json-spacer" aria-hidden />
1343
+ <span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
1344
+ </div>
1345
+ );
1346
+ }
1400
1347
 
1401
- const visitStringValues = (value: JsonValue, visitor: (text: string) => void): void => {
1402
- if (typeof value === "string") {
1403
- visitor(value);
1404
- return;
1405
- }
1406
- if (Array.isArray(value)) {
1407
- for (const item of value) visitStringValues(item, visitor);
1408
- return;
1409
- }
1410
- if (value && typeof value === "object") {
1411
- for (const item of Object.values(value)) visitStringValues(item, visitor);
1412
- }
1413
- };
1348
+ function EventDetail({ event }: { event: CatalogEvent }) {
1349
+ const [triggersOpen, setTriggersOpen] = useState(true);
1350
+ const [payloadOpen, setPayloadOpen] = useState(true);
1414
1351
 
1415
- export const validateMockProfileReferences = (input: {
1416
- profile: MockGenerationProfile;
1417
- dataContract: DataContractLike;
1418
- }): SimulatorValidationIssue[] => {
1419
- const issues: SimulatorValidationIssue[] = [];
1420
- const { profile, dataContract } = input;
1421
-
1422
- for (const [entityId, binding] of Object.entries(profile.entityKeys)) {
1423
- const { entityId: fieldEntityId, fieldId, field } = getField(dataContract, binding.field);
1424
- if (!dataContract.entities[entityId]) {
1425
- issues.push({
1426
- path: ["entityKeys", entityId],
1427
- message: 'La entidad "' + entityId + '" no existe en la Spec 1.',
1428
- });
1429
- }
1430
- if (fieldEntityId !== entityId) {
1431
- issues.push({
1432
- path: ["entityKeys", entityId, "field"],
1433
- message: "La key de entidad debe apuntar a un campo de la misma entidad.",
1434
- });
1435
- }
1436
- if (!field) {
1437
- issues.push({
1438
- path: ["entityKeys", entityId, "field"],
1439
- message: 'El campo "' + fieldEntityId + "." + fieldId + '" no existe.',
1440
- });
1441
- } else if (!["identifier", "string", "number"].includes(field.dataType)) {
1442
- issues.push({
1443
- path: ["entityKeys", entityId, "field"],
1444
- message: "La key debe ser identifier, string o number.",
1445
- });
1446
- }
1447
- }
1352
+ const activity = useSyncExternalStore(
1353
+ designerActivity.subscribe,
1354
+ designerActivity.getSnapshot,
1355
+ designerActivity.getSnapshot,
1356
+ );
1357
+ const observedTriggers = Array.from(
1358
+ new Set(
1359
+ activity
1360
+ .filter((record) => record.technicalId === event.technicalId && record.triggerId)
1361
+ .map((record) => record.triggerId as string),
1362
+ ),
1363
+ );
1364
+ const payload = event.lastPayload;
1448
1365
 
1449
- for (const [bindingId, binding] of Object.entries(profile.relationBindings)) {
1450
- const relation = dataContract.relations.find((candidate) => candidate.id === binding.relation);
1451
- if (!relation) {
1452
- issues.push({
1453
- path: ["relationBindings", bindingId, "relation"],
1454
- message: 'La relación "' + binding.relation + '" no existe en la Spec 1.',
1455
- });
1456
- }
1457
- for (const key of ["fromField", "toField"] as const) {
1458
- const { entityId, fieldId, field } = getField(dataContract, binding[key]);
1459
- if (!field) {
1460
- issues.push({
1461
- path: ["relationBindings", bindingId, key],
1462
- message: 'El campo "' + entityId + "." + fieldId + '" no existe.',
1463
- });
1464
- }
1465
- }
1466
- }
1366
+ return (
1367
+ <div className="lexy-dp-eventdetail">
1368
+ <div className="lexy-dp-detail-body">
1369
+ <div className="lexy-dp-detail-field">
1370
+ <span className="lexy-dp-detail-label">Nombre</span>
1371
+ <span>{event.technicalId}</span>
1372
+ </div>
1373
+ {event.description ? (
1374
+ <div className="lexy-dp-detail-field">
1375
+ <span className="lexy-dp-detail-label">Descripción</span>
1376
+ <span>{event.description}</span>
1377
+ </div>
1378
+ ) : null}
1379
+ {event.lastReceipt ? (
1380
+ <div className="lexy-dp-detail-field">
1381
+ <span className="lexy-dp-detail-label">Último receipt</span>
1382
+ <span className="lexy-dp-mono">
1383
+ {event.lastReceipt.eventId} · {event.lastReceipt.status}
1384
+ </span>
1385
+ </div>
1386
+ ) : null}
1387
+ </div>
1467
1388
 
1468
- for (const [reference, hint] of Object.entries(profile.fieldHints)) {
1469
- const { entityId, fieldId, field } = getField(dataContract, reference);
1470
- if (!field) {
1471
- issues.push({
1472
- path: ["fieldHints", reference],
1473
- message: 'El campo "' + entityId + "." + fieldId + '" no existe.',
1474
- });
1475
- continue;
1476
- }
1477
- if (!isSemanticCompatible(hint.semantic, field)) {
1478
- issues.push({
1479
- path: ["fieldHints", reference, "semantic"],
1480
- message:
1481
- 'La semántica "' + hint.semantic + '" no es compatible con ' + field.dataType + ".",
1482
- });
1483
- }
1484
- if (field.dataType === "enum" && hint.constraints?.allowedValues) {
1485
- for (const value of hint.constraints.allowedValues) {
1486
- if (!field.enumValues?.includes(value)) {
1487
- issues.push({
1488
- path: ["fieldHints", reference, "constraints", "allowedValues"],
1489
- message: 'El valor "' + value + '" no existe en enumValues.',
1490
- });
1491
- }
1492
- }
1493
- }
1494
- }
1389
+ <button type="button" className="lexy-dp-list-section" onClick={() => setTriggersOpen((v) => !v)}>
1390
+ Detonantes
1391
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (triggersOpen ? " is-open" : "")} />
1392
+ </button>
1393
+ {triggersOpen ? (
1394
+ <div className="lexy-dp-detail-body">
1395
+ {observedTriggers.length > 0 ? (
1396
+ <ul className="lexy-dp-trigger-list">
1397
+ {observedTriggers.map((trigger) => (
1398
+ <li key={trigger} className="lexy-dp-trigger-item">
1399
+ <span className="lexy-dp-bullet">·</span>
1400
+ <span className="lexy-dp-mono">{trigger}</span>
1401
+ </li>
1402
+ ))}
1403
+ </ul>
1404
+ ) : event.trigger ? (
1405
+ <span className="lexy-dp-muted">{event.trigger} · sin publicar aún</span>
1406
+ ) : (
1407
+ <span className="lexy-dp-muted">Sin publicar aún.</span>
1408
+ )}
1409
+ </div>
1410
+ ) : null}
1411
+
1412
+ <button type="button" className="lexy-dp-list-section" onClick={() => setPayloadOpen((v) => !v)}>
1413
+ Payload
1414
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (payloadOpen ? " is-open" : "")} />
1415
+ </button>
1416
+ {payloadOpen ? (
1417
+ <div className="lexy-dp-detail-body">
1418
+ {payload !== undefined && payload !== null ? (
1419
+ typeof payload === "object" ? (
1420
+ <div className="lexy-dp-mono" style={{ fontSize: "12px" }}>
1421
+ {Object.entries(payload as Record<string, unknown>).map(([key, value]) => (
1422
+ <PayloadKeyNode key={key} label={key} value={value} />
1423
+ ))}
1424
+ </div>
1425
+ ) : (
1426
+ <span className="lexy-dp-mono lexy-dp-muted">{typeof payload}</span>
1427
+ )
1428
+ ) : (
1429
+ <span className="lexy-dp-muted">Sin datos aún — publica el evento para ver el payload.</span>
1430
+ )}
1431
+ </div>
1432
+ ) : null}
1433
+ </div>
1434
+ );
1435
+ }
1495
1436
 
1496
- return issues;
1497
- };
1437
+ function LoadDetail({ load }: { load: CatalogLoad }) {
1438
+ const [paramsOpen, setParamsOpen] = useState(true);
1439
+ const params = load.lastParams;
1498
1440
 
1499
- export const validateMockScenarios = (input: {
1500
- profile: MockGenerationProfile;
1501
- scenarios: PrototypeMockScenarios;
1502
- dataContract: DataContractLike;
1503
- eventEngineContract: EventEngineContractLike;
1504
- }): SimulatorValidationIssue[] => {
1505
- const issues: SimulatorValidationIssue[] = [];
1506
- const { profile, scenarios, dataContract, eventEngineContract } = input;
1507
-
1508
- for (const [scenarioId, scenario] of Object.entries(scenarios.scenarios)) {
1509
- for (const [entityId, records] of Object.entries(scenario.entities)) {
1510
- const entity = dataContract.entities[entityId];
1511
- if (!entity) {
1512
- issues.push({
1513
- path: ["scenarios", scenarioId, "entities", entityId],
1514
- message: 'La entidad "' + entityId + '" no existe en la Spec 1.',
1515
- });
1516
- continue;
1517
- }
1441
+ return (
1442
+ <div className="lexy-dp-eventdetail">
1443
+ <div className="lexy-dp-detail-body">
1444
+ <div className="lexy-dp-detail-field">
1445
+ <span className="lexy-dp-detail-label">Nombre</span>
1446
+ <span>{load.technicalId}</span>
1447
+ </div>
1448
+ {load.description ? (
1449
+ <div className="lexy-dp-detail-field">
1450
+ <span className="lexy-dp-detail-label">Descripción</span>
1451
+ <span>{load.description}</span>
1452
+ </div>
1453
+ ) : null}
1454
+ {load.reads.entities.length > 0 ? (
1455
+ <div className="lexy-dp-detail-field">
1456
+ <span className="lexy-dp-detail-label">Lee</span>
1457
+ <span className="lexy-dp-mono">{load.reads.entities.join(", ")}</span>
1458
+ </div>
1459
+ ) : null}
1460
+ </div>
1518
1461
 
1519
- const keyBinding = profile.entityKeys[entityId];
1520
- const seenKeys = new Set<JsonValue>();
1521
- for (const [recordIndex, record] of records.entries()) {
1522
- for (const fieldKey of Object.keys(record)) {
1523
- if (!entity.fields[fieldKey]) {
1524
- issues.push({
1525
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1526
- message: 'El campo "' + fieldKey + '" no existe en la entidad "' + entityId + '".',
1527
- });
1528
- }
1529
- }
1462
+ <button type="button" className="lexy-dp-list-section" onClick={() => setParamsOpen((v) => !v)}>
1463
+ Parámetros
1464
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (paramsOpen ? " is-open" : "")} />
1465
+ </button>
1466
+ {paramsOpen ? (
1467
+ <div className="lexy-dp-detail-body">
1468
+ {params !== undefined && params !== null && typeof params === "object" ? (
1469
+ <div className="lexy-dp-mono" style={{ fontSize: "12px" }}>
1470
+ {Object.entries(params as Record<string, unknown>).map(([key, value]) => (
1471
+ <PayloadKeyNode key={key} label={key} value={value} />
1472
+ ))}
1473
+ </div>
1474
+ ) : (
1475
+ <span className="lexy-dp-muted">Sin parámetros — esta carga no recibe filtros.</span>
1476
+ )}
1477
+ </div>
1478
+ ) : null}
1479
+ </div>
1480
+ );
1481
+ }
1530
1482
 
1531
- for (const [fieldKey, field] of Object.entries(entity.fields)) {
1532
- const value = record[fieldKey];
1533
- if (field.required && value === undefined) {
1534
- issues.push({
1535
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1536
- message: "El campo requerido está ausente.",
1537
- });
1538
- continue;
1539
- }
1540
- if (value !== undefined) {
1541
- const typeMessage = validateValueType(field, value);
1542
- if (typeMessage) {
1543
- issues.push({
1544
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1545
- message: typeMessage,
1546
- });
1547
- }
1548
-
1549
- const hint = profile.fieldHints[entityId + "." + fieldKey];
1550
- if (hint) {
1551
- const chileMessage = validateChileanValue({
1552
- value,
1553
- hint,
1554
- emailDomain: profile.safety.emailDomain,
1555
- });
1556
- if (chileMessage) {
1557
- issues.push({
1558
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1559
- message: chileMessage,
1560
- });
1561
- }
1562
- }
1563
-
1564
- visitStringValues(value, (text) => {
1565
- if (emailFormat.test(text) && !text.endsWith("@" + profile.safety.emailDomain)) {
1566
- issues.push({
1567
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1568
- message:
1569
- 'Los correos mock deben usar el dominio "' + profile.safety.emailDomain + '".',
1570
- });
1571
- }
1572
- if (secretLikeFormat.test(text)) {
1573
- issues.push({
1574
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1575
- message: "El valor parece contener un secreto, token o credencial.",
1576
- });
1577
- }
1578
- });
1579
- }
1580
- }
1483
+ type SelectedContract =
1484
+ | { kind: "event"; id: string }
1485
+ | { kind: "load"; id: string }
1486
+ | { kind: "entity"; id: string };
1581
1487
 
1582
- if (keyBinding) {
1583
- const { fieldId } = splitReference(keyBinding.field);
1584
- const keyValue = record[fieldId];
1585
- if (keyValue !== undefined) {
1586
- if (seenKeys.has(keyValue)) {
1587
- issues.push({
1588
- path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldId],
1589
- message: 'La key "' + String(keyValue) + '" está duplicada.',
1590
- });
1591
- }
1592
- seenKeys.add(keyValue);
1593
- }
1594
- }
1595
- }
1596
- }
1488
+ type EntitySelectedItem =
1489
+ | { kind: "field"; id: string }
1490
+ | { kind: "relation"; id: string; reverse: boolean };
1597
1491
 
1598
- for (const [bindingId, binding] of Object.entries(profile.relationBindings)) {
1599
- const from = splitReference(binding.fromField);
1600
- const to = splitReference(binding.toField);
1601
- const fromRecords = scenario.entities[from.entityId] ?? [];
1602
- const toRecords = scenario.entities[to.entityId] ?? [];
1603
- const targetValues = new Set(toRecords.map((record) => record[to.fieldId]));
1604
-
1605
- for (const [recordIndex, record] of fromRecords.entries()) {
1606
- const value = record[from.fieldId];
1607
- if (value !== undefined && value !== null && !targetValues.has(value)) {
1608
- issues.push({
1609
- path: ["scenarios", scenarioId, "entities", from.entityId, recordIndex, from.fieldId],
1610
- message:
1611
- 'Referencia "' +
1612
- String(value) +
1613
- '" inexistente para relationBinding "' +
1614
- bindingId +
1615
- '".',
1616
- });
1617
- }
1618
- }
1619
- }
1492
+ function EntityDetail({ entityId }: { entityId: string }) {
1493
+ const [fieldsOpen, setFieldsOpen] = useState(true);
1494
+ const [selected, setSelected] = useState<EntitySelectedItem | null>(null);
1495
+ const [itemDetailOpen, setItemDetailOpen] = useState(true);
1620
1496
 
1621
- for (const [loadId, behavior] of Object.entries(scenario.behaviors?.dataLoads ?? {})) {
1622
- if (!eventEngineContract.dataLoads[loadId]) {
1623
- issues.push({
1624
- path: ["scenarios", scenarioId, "behaviors", "dataLoads", loadId],
1625
- message: 'La carga "' + loadId + '" no existe en la Spec 2.',
1626
- });
1627
- }
1628
- if ((behavior.outcome ?? "normal") === "empty" && behavior.error) {
1629
- issues.push({
1630
- path: ["scenarios", scenarioId, "behaviors", "dataLoads", loadId, "error"],
1631
- message: "El outcome empty no puede incluir error.",
1632
- });
1633
- }
1634
- }
1497
+ const entries = useSyncExternalStore(
1498
+ catalog.subscribe,
1499
+ catalog.getSnapshot,
1500
+ catalog.getSnapshot,
1501
+ );
1635
1502
 
1636
- for (const eventId of Object.keys(scenario.behaviors?.events ?? {})) {
1637
- if (!eventEngineContract.events[eventId]) {
1638
- issues.push({
1639
- path: ["scenarios", scenarioId, "behaviors", "events", eventId],
1640
- message: 'El evento "' + eventId + '" no existe en la Spec 2.',
1641
- });
1642
- }
1643
- }
1644
- }
1503
+ const entity = prototypeDataContract.entities[entityId];
1504
+ if (!entity) return <p className="lexy-dp-empty">Entidad no encontrada.</p>;
1505
+
1506
+ const entityRelations = [
1507
+ ...prototypeDataContract.relations
1508
+ .filter((r) => r.fromEntity === entityId)
1509
+ .map((r) => ({ ...r, targetEntity: r.toEntity, reverse: false })),
1510
+ ...prototypeDataContract.relations
1511
+ .filter((r) => r.toEntity === entityId)
1512
+ .map((r) => ({ ...r, targetEntity: r.fromEntity, reverse: true })),
1513
+ ];
1514
+
1515
+ const hasRows = Object.keys(entity.fields).length > 0 || entityRelations.length > 0;
1516
+
1517
+ const selectedField = selected?.kind === "field" ? entity.fields[selected.id] : null;
1518
+ const selectedRelation =
1519
+ selected?.kind === "relation"
1520
+ ? entityRelations.find((r) => r.id === selected.id && r.reverse === selected.reverse) ?? null
1521
+ : null;
1522
+
1523
+ const fieldEvents =
1524
+ selected?.kind === "field"
1525
+ ? entries.filter(
1526
+ (entry): entry is CatalogEvent =>
1527
+ entry.kind === "event" &&
1528
+ entry.writes.fields.includes(entityId + "." + selected.id),
1529
+ )
1530
+ : [];
1531
+
1532
+ const handleClick = (item: EntitySelectedItem) => {
1533
+ setSelected((prev) =>
1534
+ prev?.kind === item.kind && prev.id === item.id ? null : item,
1535
+ );
1536
+ setItemDetailOpen(true);
1537
+ };
1645
1538
 
1646
- return issues;
1647
- };
1539
+ const isSelected = (item: EntitySelectedItem) =>
1540
+ selected?.kind === item.kind &&
1541
+ selected.id === item.id &&
1542
+ (item.kind !== "relation" || (selected as { reverse: boolean }).reverse === item.reverse);
1648
1543
 
1649
- export function defineMockGenerationProfile(profile: MockGenerationProfile): MockGenerationProfile {
1650
- return mockGenerationProfileSchema.parse(profile);
1651
- }
1544
+ return (
1545
+ <div className="lexy-dp-eventdetail">
1546
+ <div className="lexy-dp-detail-body">
1547
+ <div className="lexy-dp-detail-field">
1548
+ <span className="lexy-dp-detail-label">Nombre</span>
1549
+ <span>{entityId}</span>
1550
+ </div>
1551
+ <div className="lexy-dp-detail-field">
1552
+ <span className="lexy-dp-detail-label">Descripción</span>
1553
+ <span>{entity.productDescription}</span>
1554
+ </div>
1555
+ </div>
1652
1556
 
1653
- export function definePrototypeMockScenarios(
1654
- scenarios: PrototypeMockScenarios,
1655
- ): PrototypeMockScenarios {
1656
- return prototypeMockScenariosSchema.parse(scenarios);
1557
+ <button type="button" className="lexy-dp-list-section" onClick={() => setFieldsOpen((v) => !v)}>
1558
+ Campos
1559
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (fieldsOpen ? " is-open" : "")} />
1560
+ </button>
1561
+ {fieldsOpen ? (
1562
+ !hasRows ? (
1563
+ <div className="lexy-dp-detail-body">
1564
+ <span className="lexy-dp-muted">Sin campos declarados.</span>
1565
+ </div>
1566
+ ) : (
1567
+ <div style={{ overflowX: "auto" }}>
1568
+ <table className="lexy-dp-field-table">
1569
+ <thead>
1570
+ <tr>
1571
+ <th style={{ color: "var(--dp-fg)" }}>Campo</th>
1572
+ <th style={{ width: "100px", color: "var(--dp-muted-fg)" }}>Tipo</th>
1573
+ </tr>
1574
+ </thead>
1575
+ <tbody>
1576
+ {Object.entries(entity.fields).map(([fieldId, field]) => {
1577
+ const sel = isSelected({ kind: "field", id: fieldId });
1578
+ return (
1579
+ <tr
1580
+ key={fieldId}
1581
+ className={sel ? "is-selected" : ""}
1582
+ onClick={() => handleClick({ kind: "field", id: fieldId })}
1583
+ >
1584
+ <td style={sel ? undefined : { color: TYPE_COLORS[field.dataType] ?? "var(--dp-fg)" }}>
1585
+ {fieldId}
1586
+ </td>
1587
+ <td className={sel ? undefined : "lexy-dp-muted"}>{field.dataType}</td>
1588
+ </tr>
1589
+ );
1590
+ })}
1591
+ {entityRelations.map((rel) => {
1592
+ const sel = isSelected({ kind: "relation", id: rel.id, reverse: rel.reverse });
1593
+ return (
1594
+ <tr
1595
+ key={rel.id + (rel.reverse ? "_r" : "")}
1596
+ className={sel ? "is-selected" : ""}
1597
+ onClick={() => handleClick({ kind: "relation", id: rel.id, reverse: rel.reverse })}
1598
+ >
1599
+ <td style={sel ? undefined : { color: "var(--dp-primary)" }}>
1600
+ <span style={{ display: "inline-flex", alignItems: "center", gap: "3px" }}>
1601
+ <ArrowUpRight style={{ width: "11px", height: "11px", flexShrink: 0 }} />
1602
+ {rel.id}
1603
+ </span>
1604
+ </td>
1605
+ <td style={sel ? undefined : { color: "var(--dp-primary)", opacity: 0.8 }}>
1606
+ {rel.targetEntity}
1607
+ </td>
1608
+ </tr>
1609
+ );
1610
+ })}
1611
+ </tbody>
1612
+ </table>
1613
+ </div>
1614
+ )
1615
+ ) : null}
1616
+
1617
+ {selected ? (
1618
+ <>
1619
+ <button type="button" className="lexy-dp-list-section" onClick={() => setItemDetailOpen((v) => !v)}>
1620
+ <span>Detalle · <span className="lexy-dp-mono">{selected.id}</span></span>
1621
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (itemDetailOpen ? " is-open" : "")} />
1622
+ </button>
1623
+ {itemDetailOpen ? (
1624
+ <div className="lexy-dp-detail-body">
1625
+ {selectedField ? (
1626
+ <>
1627
+ <div className="lexy-dp-detail-field">
1628
+ <span className="lexy-dp-detail-label">Eventos que modifican</span>
1629
+ </div>
1630
+ {fieldEvents.length === 0 ? (
1631
+ <span className="lexy-dp-muted">Ningún evento modifica este campo.</span>
1632
+ ) : (
1633
+ <ul className="lexy-dp-trigger-list">
1634
+ {fieldEvents.map((event) => (
1635
+ <li key={event.technicalId} className="lexy-dp-trigger-item">
1636
+ <span className="lexy-dp-bullet">·</span>
1637
+ <span className="lexy-dp-mono">{event.technicalId}</span>
1638
+ </li>
1639
+ ))}
1640
+ </ul>
1641
+ )}
1642
+ </>
1643
+ ) : selectedRelation ? (
1644
+ <>
1645
+ <div className="lexy-dp-detail-field">
1646
+ <span className="lexy-dp-detail-label">Descripción</span>
1647
+ <span>{selectedRelation.productDescription}</span>
1648
+ </div>
1649
+ <div className="lexy-dp-detail-field">
1650
+ <span className="lexy-dp-detail-label">Cardinalidad</span>
1651
+ <span>{selectedRelation.cardinality}</span>
1652
+ </div>
1653
+ <div className="lexy-dp-detail-field">
1654
+ <span className="lexy-dp-detail-label">Resolución</span>
1655
+ <span>{selectedRelation.resolution}</span>
1656
+ </div>
1657
+ </>
1658
+ ) : null}
1659
+ </div>
1660
+ ) : null}
1661
+ </>
1662
+ ) : null}
1663
+ </div>
1664
+ );
1657
1665
  }
1658
- `,Ft=String.raw`import { defineMockGenerationProfile } from "./simulator-contract-schema";
1659
1666
 
1660
- /**
1661
- * Perfil para que el agente genere data mock coherente con Chile.
1662
- *
1663
- * La IA lo completa en autoría. El runtime solo consume objetos TypeScript
1664
- * explícitos y deterministas.
1665
- */
1666
- export const mockGenerationProfile = defineMockGenerationProfile({
1667
- profileVersion: "1",
1668
- locale: {
1669
- language: "es",
1670
- locale: "es-CL",
1671
- country: "CL",
1672
- timezone: "America/Santiago",
1673
- currency: "CLP",
1674
- dateStorage: "iso8601",
1675
- dateDisplay: "dd-MM-yyyy",
1676
- timeDisplay: "24h",
1677
- },
1678
- safety: {
1679
- syntheticOnly: true,
1680
- allowProductionImports: false,
1681
- externalUse: false,
1682
- emailDomain: "example.com",
1683
- },
1684
- entityKeys: {},
1685
- relationBindings: {},
1686
- fieldHints: {},
1687
- });
1688
- `,Nt=String.raw`import { definePrototypeMockScenarios } from "./simulator-contract-schema";
1667
+ function EventsTab() {
1668
+ const entries = useSyncExternalStore(
1669
+ catalog.subscribe,
1670
+ catalog.getSnapshot,
1671
+ catalog.getSnapshot,
1672
+ );
1673
+ const events = entries.filter((e): e is CatalogEvent => e.kind === "event");
1674
+ const loads = entries.filter((e): e is CatalogLoad => e.kind === "load");
1675
+ const entities = Object.entries(prototypeDataContract.entities);
1676
+
1677
+ const defaultSelected: SelectedContract | null =
1678
+ entities[0] ? { kind: "entity", id: entities[0][0] } : null;
1679
+
1680
+ const [selected, setSelected] = useState<SelectedContract | null>(defaultSelected);
1681
+ const [eventsOpen, setEventsOpen] = useState(true);
1682
+ const [loadsOpen, setLoadsOpen] = useState(true);
1683
+ const [entitiesOpen, setEntitiesOpen] = useState(true);
1684
+
1685
+ if (events.length === 0 && loads.length === 0 && entities.length === 0) {
1686
+ return (
1687
+ <p className="lexy-dp-empty">
1688
+ Publica un evento, ejecuta una carga o declara entidades para ver contenido aquí.
1689
+ </p>
1690
+ );
1691
+ }
1689
1692
 
1690
- /**
1691
- * Escenarios mock persistibles.
1692
- *
1693
- * Un proyecto fresco nace vacío y válido. El agente agrega fixtures explícitos
1694
- * después del kickoff y sube datasetVersion cuando cambia el dataset.
1695
- */
1696
- export const prototypeMockScenarios = definePrototypeMockScenarios({
1697
- scenariosVersion: "1",
1698
- datasetVersion: 1,
1699
- defaultScenarioId: "base",
1700
- scenarios: {
1701
- base: {
1702
- id: "base",
1703
- name: "Recorrido principal",
1704
- description: "Escenario inicial pendiente de completar durante el kickoff.",
1705
- entities: {},
1706
- },
1707
- empty: {
1708
- id: "empty",
1709
- name: "Estado vacío",
1710
- description: "Escenario sin resultados.",
1711
- entities: {},
1712
- },
1713
- edgeCases: {
1714
- id: "edgeCases",
1715
- name: "Casos límite",
1716
- description: "Escenario pendiente de completar según la experiencia.",
1717
- entities: {},
1718
- },
1719
- },
1720
- });
1721
- `,Ht=String.raw`import type { ProjectionResolverMap } from "./prototype-simulator";
1693
+ const selectedEvent =
1694
+ selected?.kind === "event"
1695
+ ? (events.find((e) => e.technicalId === selected.id) ?? null)
1696
+ : null;
1697
+ const selectedLoad =
1698
+ selected?.kind === "load"
1699
+ ? (loads.find((l) => l.technicalId === selected.id) ?? null)
1700
+ : null;
1701
+
1702
+ const renderGroup = (
1703
+ label: string,
1704
+ open: boolean,
1705
+ toggle: () => void,
1706
+ ids: string[],
1707
+ kind: "event" | "load" | "entity",
1708
+ ) =>
1709
+ ids.length > 0 ? (
1710
+ <>
1711
+ <li>
1712
+ <button type="button" className="lexy-dp-list-section" onClick={toggle}>
1713
+ {label}
1714
+ <ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (open ? " is-open" : "")} />
1715
+ </button>
1716
+ </li>
1717
+ {open
1718
+ ? ids.map((id) => (
1719
+ <li key={kind + ":" + id}>
1720
+ <button
1721
+ type="button"
1722
+ onClick={() => setSelected({ kind, id })}
1723
+ title={id}
1724
+ className={
1725
+ "lexy-dp-listitem" +
1726
+ (selected?.kind === kind && selected.id === id ? " is-active" : "")
1727
+ }
1728
+ >
1729
+ {id}
1730
+ </button>
1731
+ </li>
1732
+ ))
1733
+ : null}
1734
+ </>
1735
+ ) : null;
1722
1736
 
1723
- export const projectionResolvers: ProjectionResolverMap = {};
1724
- `,Kt=String.raw`import type { MockDataLoadHandlerMap } from "./prototype-simulator";
1737
+ return (
1738
+ <div className="lexy-dp-split">
1739
+ <ul className="lexy-dp-split__list">
1740
+ {renderGroup("Eventos", eventsOpen, () => setEventsOpen((v) => !v), events.map((e) => e.technicalId), "event")}
1741
+ {renderGroup("Cargas de datos", loadsOpen, () => setLoadsOpen((v) => !v), loads.map((l) => l.technicalId), "load")}
1742
+ {renderGroup("Entidades", entitiesOpen, () => setEntitiesOpen((v) => !v), entities.map(([id]) => id), "entity")}
1743
+ </ul>
1744
+ <div className="lexy-dp-split__detail">
1745
+ {selected?.kind === "event" && selectedEvent ? (
1746
+ <EventDetail event={selectedEvent} />
1747
+ ) : selected?.kind === "load" && selectedLoad ? (
1748
+ <LoadDetail load={selectedLoad} />
1749
+ ) : selected?.kind === "entity" ? (
1750
+ <EntityDetail entityId={selected.id} />
1751
+ ) : null}
1752
+ </div>
1753
+ </div>
1754
+ );
1755
+ }
1725
1756
 
1726
- export const dataLoadHandlers: MockDataLoadHandlerMap = {};
1727
- `,qt=String.raw`import type { MockEventHandlerMap } from "./prototype-simulator";
1757
+ type DesignerTab = "activity" | "events";
1728
1758
 
1729
- export const eventHandlers: MockEventHandlerMap = {};
1730
- `,Ut=String.raw`import type {
1731
- JsonPrimitive,
1732
- MockEntityRecord,
1733
- PrototypeMockScenarios,
1734
- } from "./simulator-contract-schema";
1759
+ export function DesignerPanelPage() {
1760
+ const [tab, setTab] = useState<DesignerTab>("activity");
1735
1761
 
1736
- export type PublishedEventHistoryItem = {
1737
- publicationId: string;
1738
- eventId: string;
1739
- technicalId: string;
1740
- occurredAt: string;
1741
- status: "published";
1742
- };
1762
+ return (
1763
+ <div className="lexy-dp lexy-dp-page">
1764
+ <PanelStyles />
1765
+ <div className="lexy-dp-tablist" role="tablist">
1766
+ <button
1767
+ type="button"
1768
+ role="tab"
1769
+ aria-selected={tab === "activity"}
1770
+ className={"lexy-dp-tab" + (tab === "activity" ? " is-active" : "")}
1771
+ onClick={() => setTab("activity")}
1772
+ >
1773
+ <Activity />
1774
+ Actividad
1775
+ </button>
1776
+ <button
1777
+ type="button"
1778
+ role="tab"
1779
+ aria-selected={tab === "events"}
1780
+ className={"lexy-dp-tab" + (tab === "events" ? " is-active" : "")}
1781
+ onClick={() => setTab("events")}
1782
+ >
1783
+ <Zap />
1784
+ Eventos y Datos
1785
+ </button>
1786
+ </div>
1787
+ <div className="lexy-dp-body">
1788
+ {tab === "activity" ? (
1789
+ <div className="lexy-dp-panel">
1790
+ <ActivityTab />
1791
+ </div>
1792
+ ) : null}
1793
+ {tab === "events" ? (
1794
+ <div className="lexy-dp-panel">
1795
+ <EventsTab />
1796
+ </div>
1797
+ ) : null}
1798
+ </div>
1799
+ </div>
1800
+ );
1801
+ }
1802
+
1803
+ export function PrototypeDesignerPanel() {
1804
+ const [open, setOpen] = useState(false);
1805
+ return (
1806
+ <div className="lexy-dp">
1807
+ <PanelStyles />
1808
+ {open ? (
1809
+ <div role="dialog" aria-label="Actividad del prototipo" className="lexy-dp-sheet">
1810
+ <div className="lexy-dp-sheet__header">
1811
+ <p className="lexy-dp-sheet__title">Panel de Actividad</p>
1812
+ <div className="lexy-dp-sheet__actions">
1813
+ <button
1814
+ type="button"
1815
+ aria-label="Abrir panel completo"
1816
+ title="Abrir panel completo"
1817
+ className="lexy-dp-iconbtn"
1818
+ onClick={() => window.open(DESIGNER_PANEL_ROUTE, "_blank", "noopener,noreferrer")}
1819
+ >
1820
+ <Maximize2 />
1821
+ </button>
1822
+ <button
1823
+ type="button"
1824
+ aria-label="Cerrar"
1825
+ className="lexy-dp-iconbtn"
1826
+ onClick={() => setOpen(false)}
1827
+ >
1828
+ <X />
1829
+ </button>
1830
+ </div>
1831
+ </div>
1832
+ <div className="lexy-dp-sheet__body">
1833
+ <ActivityLog />
1834
+ </div>
1835
+ </div>
1836
+ ) : null}
1837
+
1838
+ <button
1839
+ type="button"
1840
+ aria-label="Designer Assist"
1841
+ aria-expanded={open}
1842
+ onClick={() => setOpen((value) => !value)}
1843
+ className={"lexy-dp-launcher" + (open ? " is-open" : "")}
1844
+ >
1845
+ <Settings className="lexy-dp-launcher__icon" />
1846
+ <span className="lexy-dp-launcher__label">Designer Assist</span>
1847
+ </button>
1848
+ </div>
1849
+ );
1850
+ }
1851
+ `,$e=async(e,t)=>{let o=async(a,n)=>{await Me.mkdir(ie.dirname(ie.join(e,a)),{recursive:!0}),await Me.writeFile(ie.join(e,a),n)};await o(se,Et),await o(le,jt())};import Fe from"fs/promises";import qe from"path";var ce="src/prototype/mock-store/mock-store.ts",de="src/prototype/mock-store/fixtures.ts",Ct="src/prototype/ports/types.ts",It="src/prototype/ports/catalog.ts",Tt="src/prototype/ports/mock-read.ts",Rt="src/prototype/ports/mock-write.ts",Nt="src/prototype/ports/production.ts",pe="src/prototype/ports/index.ts",Ot=String.raw`import { fixtures } from "./fixtures";
1852
+
1853
+ export type JsonPrimitive = string | number | boolean | null;
1854
+ export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
1855
+ export type MockEntityRecord = Record<string, JsonValue>;
1743
1856
 
1744
- export type PrototypeSimulatorState = {
1745
- simulatorVersion: "1";
1857
+ export type PrototypeMockState = {
1746
1858
  datasetVersion: number;
1747
- activeScenarioId: string;
1748
- revision: number;
1749
1859
  entities: Record<string, MockEntityRecord[]>;
1750
1860
  entitySequences: Record<string, number>;
1751
- publicationHistory: PublishedEventHistoryItem[];
1752
1861
  };
1753
1862
 
1754
- export type SimulatorStorage = {
1863
+ export type MockStorage = {
1755
1864
  read(): string | null;
1756
1865
  write(value: string): void;
1757
1866
  remove(): void;
1758
1867
  };
1759
1868
 
1760
1869
  export type PrototypeMockStore = {
1761
- getSnapshot(): PrototypeSimulatorState;
1870
+ getSnapshot(): PrototypeMockState;
1762
1871
  subscribe(listener: () => void): () => void;
1763
- transact(operation: (draft: PrototypeSimulatorState) => void): PrototypeSimulatorState;
1764
- switchScenario(scenarioId: string): void;
1765
- resetScenario(): void;
1872
+ transact(operation: (draft: PrototypeMockState) => void): PrototypeMockState;
1766
1873
  clearPersistence(): void;
1767
1874
  };
1768
1875
 
1769
- type CreatePrototypeMockStoreOptions = {
1770
- projectName: string;
1771
- scenarios: PrototypeMockScenarios;
1772
- storage?: SimulatorStorage;
1773
- onHydrationWarning?: (message: string) => void;
1774
- };
1876
+ const STORAGE_KEY = "lexy:prototype:mock-store:v1";
1775
1877
 
1776
1878
  const clone = <T>(value: T): T => {
1777
1879
  if (typeof structuredClone === "function") return structuredClone(value);
@@ -1786,43 +1888,26 @@ const buildSequences = (entities: Record<string, MockEntityRecord[]>) => {
1786
1888
  return sequences;
1787
1889
  };
1788
1890
 
1789
- const createStateFromScenario = (
1790
- scenarios: PrototypeMockScenarios,
1791
- scenarioId: string,
1792
- ): PrototypeSimulatorState => {
1793
- const scenario = scenarios.scenarios[scenarioId];
1794
- if (!scenario) throw new Error('El escenario "' + scenarioId + '" no existe.');
1795
- const entities = clone(scenario.entities);
1891
+ const stateFromFixtures = (): PrototypeMockState => {
1892
+ const entities = clone(fixtures.entities);
1796
1893
  return {
1797
- simulatorVersion: "1",
1798
- datasetVersion: scenarios.datasetVersion,
1799
- activeScenarioId: scenarioId,
1800
- revision: 0,
1894
+ datasetVersion: fixtures.datasetVersion,
1801
1895
  entities,
1802
1896
  entitySequences: buildSequences(entities),
1803
- publicationHistory: [],
1804
1897
  };
1805
1898
  };
1806
1899
 
1807
- const isPersistedStateCompatible = (
1808
- value: unknown,
1809
- scenarios: PrototypeMockScenarios,
1810
- ): value is PrototypeSimulatorState => {
1900
+ const isCompatible = (value: unknown): value is PrototypeMockState => {
1811
1901
  if (!value || typeof value !== "object") return false;
1812
- const state = value as Partial<PrototypeSimulatorState>;
1902
+ const state = value as Partial<PrototypeMockState>;
1813
1903
  return (
1814
- state.simulatorVersion === "1" &&
1815
- state.datasetVersion === scenarios.datasetVersion &&
1816
- typeof state.activeScenarioId === "string" &&
1817
- Boolean(scenarios.scenarios[state.activeScenarioId]) &&
1818
- typeof state.revision === "number" &&
1904
+ state.datasetVersion === fixtures.datasetVersion &&
1819
1905
  Boolean(state.entities && typeof state.entities === "object") &&
1820
- Boolean(state.entitySequences && typeof state.entitySequences === "object") &&
1821
- Array.isArray(state.publicationHistory)
1906
+ Boolean(state.entitySequences && typeof state.entitySequences === "object")
1822
1907
  );
1823
1908
  };
1824
1909
 
1825
- export const createMemorySimulatorStorage = (): SimulatorStorage => {
1910
+ const createMemoryStorage = (): MockStorage => {
1826
1911
  let current: string | null = null;
1827
1912
  return {
1828
1913
  read: () => current,
@@ -1835,8 +1920,8 @@ export const createMemorySimulatorStorage = (): SimulatorStorage => {
1835
1920
  };
1836
1921
  };
1837
1922
 
1838
- export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorage => {
1839
- const memory = createMemorySimulatorStorage();
1923
+ const createLocalStorageStorage = (key: string): MockStorage => {
1924
+ const memory = createMemoryStorage();
1840
1925
  const getLocalStorage = () => {
1841
1926
  try {
1842
1927
  return globalThis.localStorage;
@@ -1844,7 +1929,6 @@ export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorag
1844
1929
  return undefined;
1845
1930
  }
1846
1931
  };
1847
-
1848
1932
  return {
1849
1933
  read: () => {
1850
1934
  try {
@@ -1872,41 +1956,26 @@ export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorag
1872
1956
  };
1873
1957
  };
1874
1958
 
1875
- export const simulatorStorageKey = (projectName: string) =>
1876
- "lexy:prototype:" + projectName + ":simulator:v1";
1877
-
1878
1959
  export const createPrototypeMockStore = (
1879
- options: CreatePrototypeMockStoreOptions,
1960
+ storage: MockStorage = createLocalStorageStorage(STORAGE_KEY),
1880
1961
  ): PrototypeMockStore => {
1881
- const storage =
1882
- options.storage ?? createLocalStorageSimulatorStorage(simulatorStorageKey(options.projectName));
1883
1962
  const listeners = new Set<() => void>();
1884
1963
 
1885
- const hydrate = () => {
1964
+ const hydrate = (): PrototypeMockState => {
1886
1965
  const persisted = storage.read();
1887
- if (!persisted)
1888
- return createStateFromScenario(options.scenarios, options.scenarios.defaultScenarioId);
1889
-
1966
+ if (!persisted) return stateFromFixtures();
1890
1967
  try {
1891
1968
  const parsed = JSON.parse(persisted) as unknown;
1892
- if (isPersistedStateCompatible(parsed, options.scenarios)) return parsed;
1893
- options.onHydrationWarning?.(
1894
- "Estado persistido incompatible; se reinicia el simulador al escenario default.",
1895
- );
1969
+ // Al cambiar fixtures se sube datasetVersion: el estado viejo se descarta.
1970
+ if (isCompatible(parsed)) return parsed;
1896
1971
  } catch {
1897
- options.onHydrationWarning?.(
1898
- "Estado persistido corrupto; se reinicia el simulador al escenario default.",
1899
- );
1972
+ // estado corrupto: reinicia desde fixtures
1900
1973
  }
1901
- return createStateFromScenario(options.scenarios, options.scenarios.defaultScenarioId);
1974
+ return stateFromFixtures();
1902
1975
  };
1903
1976
 
1904
1977
  let snapshot = hydrate();
1905
1978
 
1906
- const persist = () => {
1907
- storage.write(JSON.stringify(snapshot));
1908
- };
1909
-
1910
1979
  const notify = () => {
1911
1980
  for (const listener of listeners) listener();
1912
1981
  };
@@ -1922,497 +1991,321 @@ export const createPrototypeMockStore = (
1922
1991
  transact: (operation) => {
1923
1992
  const draft = clone(snapshot);
1924
1993
  operation(draft);
1925
- draft.revision = snapshot.revision + 1;
1926
1994
  snapshot = draft;
1927
- persist();
1995
+ storage.write(JSON.stringify(snapshot));
1928
1996
  notify();
1929
1997
  return snapshot;
1930
1998
  },
1931
- switchScenario: (scenarioId) => {
1932
- snapshot = createStateFromScenario(options.scenarios, scenarioId);
1933
- persist();
1934
- notify();
1935
- },
1936
- resetScenario: () => {
1937
- snapshot = createStateFromScenario(options.scenarios, snapshot.activeScenarioId);
1938
- persist();
1939
- notify();
1940
- },
1941
- clearPersistence: () => {
1942
- storage.remove();
1943
- },
1999
+ clearPersistence: () => storage.remove(),
1944
2000
  };
1945
2001
  };
1946
2002
 
2003
+ /** Instancia compartida que ven el adapter de lectura y el de escritura. */
2004
+ export const mockStore = createPrototypeMockStore();
2005
+
1947
2006
  export const findRecordById = (
1948
2007
  records: readonly MockEntityRecord[],
1949
2008
  fieldId: string,
1950
2009
  id: JsonPrimitive,
1951
2010
  ) => records.find((record) => record[fieldId] === id);
1952
- `,Bt=String.raw`import type {
1953
- EventPublicationEnvelope,
1954
- PrototypeBackendExecutor,
1955
- } from "../event-engine/event-engine";
1956
- import type {
1957
- JsonPrimitive,
1958
- MockEntityRecord,
1959
- MockGenerationProfile,
1960
- MockOperationError,
1961
- PrototypeMockScenarios,
1962
- } from "./simulator-contract-schema";
1963
- import {
1964
- findRecordById,
1965
- type PrototypeMockStore,
1966
- type PrototypeSimulatorState,
1967
- } from "./mock-store";
1968
- import type {
1969
- MockDataLoadHandlerMap,
1970
- MockEventHandlerMap,
1971
- ProjectionResolverContext,
1972
- ProjectionResolverMap,
1973
- } from "./prototype-simulator";
1974
-
1975
- export class MockBackendError extends Error {
1976
- code: string;
1977
- recoverable: boolean;
1978
-
1979
- constructor(error: MockOperationError) {
1980
- super(error.message);
1981
- this.name = "MockBackendError";
1982
- this.code = error.code;
1983
- this.recoverable = error.recoverable;
1984
- }
1985
- }
1986
-
1987
- type CreateMockBackendExecutorOptions = {
1988
- profile: MockGenerationProfile;
1989
- scenarios: PrototypeMockScenarios;
1990
- store: PrototypeMockStore;
1991
- projectionResolvers: ProjectionResolverMap;
1992
- dataLoadHandlers: MockDataLoadHandlerMap;
1993
- eventHandlers: MockEventHandlerMap;
1994
- };
1995
-
1996
- const delay = (latencyMs: number | undefined, signal?: AbortSignal) =>
1997
- new Promise<void>((resolve, reject) => {
1998
- if (!latencyMs) {
1999
- resolve();
2000
- return;
2001
- }
2002
- if (signal?.aborted) {
2003
- reject(new DOMException("Operación abortada.", "AbortError"));
2004
- return;
2005
- }
2006
- const timeout = globalThis.setTimeout(resolve, latencyMs);
2007
- signal?.addEventListener(
2008
- "abort",
2009
- () => {
2010
- globalThis.clearTimeout(timeout);
2011
- reject(new DOMException("Operación abortada.", "AbortError"));
2012
- },
2013
- { once: true },
2014
- );
2015
- });
2011
+ `,_t=String.raw`import type { MockEntityRecord } from "./mock-store";
2016
2012
 
2017
- const splitReference = (reference: string) => {
2018
- const [entityId, fieldId] = reference.split(".");
2019
- return { entityId, fieldId };
2013
+ export type Fixtures = {
2014
+ datasetVersion: number;
2015
+ entities: Record<string, MockEntityRecord[]>;
2020
2016
  };
2021
2017
 
2022
- const getEntityKeyField = (profile: MockGenerationProfile, entityId: string) => {
2023
- const field = profile.entityKeys[entityId]?.field;
2024
- return field ? splitReference(field).fieldId : "id";
2018
+ /**
2019
+ * Datos sintéticos, deterministas y revisables en PR. Sin Math.random(),
2020
+ * Date.now() ni new Date(). Usa contexto chileno (es-CL, RUT con K mayúscula,
2021
+ * teléfonos +56, fechas ISO, CLP entero, correos example.com).
2022
+ */
2023
+ export const fixtures: Fixtures = {
2024
+ datasetVersion: 1,
2025
+ entities: {},
2025
2026
  };
2027
+ `,At=String.raw`import { z } from "zod";
2026
2028
 
2027
- const createProjectionContext = (
2028
- profile: MockGenerationProfile,
2029
- state: PrototypeSimulatorState,
2030
- ): ProjectionResolverContext => ({
2031
- entities: state.entities,
2032
- getEntity: (entityId) => state.entities[entityId] ?? [],
2033
- getById: (entityId, id) =>
2034
- findRecordById(state.entities[entityId] ?? [], getEntityKeyField(profile, entityId), id),
2035
- getRelated: (relationId, sourceId) => {
2036
- const binding = Object.values(profile.relationBindings).find(
2037
- (candidate) => candidate.relation === relationId,
2038
- );
2039
- if (!binding) return [];
2040
- const target = splitReference(binding.toField);
2041
- return (state.entities[target.entityId] ?? []).filter(
2042
- (record) => record[target.fieldId] === sourceId,
2043
- );
2044
- },
2045
- });
2029
+ /** Metadata de una carga de datos (GET): describe qué lee y qué la detona. */
2030
+ export const readMetaSchema = z
2031
+ .object({
2032
+ description: z.string().optional(),
2033
+ trigger: z.string().optional(),
2034
+ reads: z
2035
+ .object({
2036
+ entities: z.array(z.string()).optional(),
2037
+ projections: z.array(z.string()).optional(),
2038
+ })
2039
+ .strict()
2040
+ .optional(),
2041
+ })
2042
+ .strict();
2046
2043
 
2047
- const nextEntityId = (
2048
- profile: MockGenerationProfile,
2049
- draft: PrototypeSimulatorState,
2050
- entityId: string,
2051
- ): JsonPrimitive => {
2052
- const keyField = getEntityKeyField(profile, entityId);
2053
- const records = draft.entities[entityId] ?? [];
2054
- const numericIds = records
2055
- .map((record) => record[keyField])
2056
- .filter((value): value is number => typeof value === "number");
2057
- if (numericIds.length > 0) return Math.max(...numericIds) + 1;
2058
-
2059
- const sequence = draft.entitySequences[entityId] ?? records.length + 1;
2060
- draft.entitySequences[entityId] = sequence + 1;
2061
- return entityId + "-" + sequence;
2062
- };
2044
+ export type ReadMeta = z.infer<typeof readMetaSchema>;
2063
2045
 
2064
- export const createMockBackendExecutor = (
2065
- options: CreateMockBackendExecutorOptions,
2066
- ): PrototypeBackendExecutor => ({
2067
- async loadData(input) {
2068
- const state = options.store.getSnapshot();
2069
- const scenario = options.scenarios.scenarios[state.activeScenarioId];
2070
- const behavior = scenario.behaviors?.dataLoads?.[input.loadId];
2071
- await delay(behavior?.latencyMs, input.signal);
2072
-
2073
- if (behavior?.outcome === "error" && behavior.error) throw new MockBackendError(behavior.error);
2074
- if (behavior?.outcome === "empty") return [];
2075
-
2076
- const handler = options.dataLoadHandlers[input.loadId];
2077
- if (!handler) throw new Error('Falta handler para la carga "' + input.loadId + '".');
2078
-
2079
- return handler({
2080
- params: input.params,
2081
- signal: input.signal,
2082
- store: options.store,
2083
- projections: {
2084
- resolve: (projectionId, params) => {
2085
- const resolver = options.projectionResolvers[projectionId];
2086
- if (!resolver)
2087
- throw new Error('Falta resolver para la proyección "' + projectionId + '".');
2088
- return resolver(
2089
- createProjectionContext(options.profile, options.store.getSnapshot()),
2090
- params,
2091
- );
2092
- },
2093
- },
2094
- });
2095
- },
2046
+ /** Metadata de un evento publicado (POST): describe qué escribe y qué lo detona. */
2047
+ export const writeMetaSchema = z
2048
+ .object({
2049
+ description: z.string().optional(),
2050
+ trigger: z.string().optional(),
2051
+ writes: z
2052
+ .object({
2053
+ entities: z.array(z.string()).optional(),
2054
+ fields: z.array(z.string()).optional(),
2055
+ })
2056
+ .strict()
2057
+ .optional(),
2058
+ })
2059
+ .strict();
2096
2060
 
2097
- async publishEvent(input) {
2098
- const state = options.store.getSnapshot();
2099
- const scenario = options.scenarios.scenarios[state.activeScenarioId];
2100
- const behavior = scenario.behaviors?.events?.[input.eventId];
2101
- await delay(behavior?.latencyMs);
2102
-
2103
- if (behavior?.outcome === "error" && behavior.error) throw new MockBackendError(behavior.error);
2104
-
2105
- const handler = options.eventHandlers[input.eventId];
2106
- if (!handler) throw new Error('Falta handler para el evento "' + input.eventId + '".');
2107
-
2108
- let receipt: unknown;
2109
- options.store.transact((draft) => {
2110
- const getById = (entityId: string, id: JsonPrimitive): MockEntityRecord | undefined =>
2111
- findRecordById(
2112
- draft.entities[entityId] ?? [],
2113
- getEntityKeyField(options.profile, entityId),
2114
- id,
2115
- );
2116
-
2117
- const result = handler({
2118
- draft,
2119
- envelope: input.envelope as EventPublicationEnvelope,
2120
- getById,
2121
- nextEntityId: (entityId) => nextEntityId(options.profile, draft, entityId),
2122
- });
2123
- receipt = result?.receipt;
2124
- draft.publicationHistory.push({
2125
- publicationId: input.envelope.publicationId,
2126
- eventId: input.eventId,
2127
- technicalId: input.envelope.technicalId,
2128
- occurredAt: input.envelope.occurredAt,
2129
- status: "published",
2130
- });
2131
- });
2061
+ export type WriteMeta = z.infer<typeof writeMetaSchema>;
2132
2062
 
2133
- return receipt === undefined ? {} : { receipt };
2134
- },
2063
+ /** Lo que devuelve publish(): el id del hecho creado y el código HTTP. */
2064
+ export const eventReceiptSchema = z.object({
2065
+ eventId: z.string(),
2066
+ status: z.number().int().min(100).max(599),
2135
2067
  });
2136
- `,Jt=String.raw`import type { EventPublicationEnvelope } from "../event-engine/event-engine";
2137
- import { createPrototypeEventEngine } from "../event-engine/event-engine";
2138
- import { prototypeDataContract } from "../data-contract/prototype-data-contract";
2139
- import { dataLoadHandlers } from "./data-load-handlers";
2140
- import { eventHandlers } from "./event-handlers";
2141
- import { mockGenerationProfile } from "./mock-generation-profile";
2142
- import { createMockBackendExecutor } from "./mock-backend-executor";
2143
- import { createPrototypeMockStore, type PrototypeMockStore } from "./mock-store";
2144
- import { prototypeMockScenarios } from "./mock-scenarios";
2145
- import { projectionResolvers } from "./projection-resolvers";
2146
- import type {
2147
- JsonPrimitive,
2148
- MockEntityRecord,
2149
- MockGenerationProfile,
2150
- PrototypeMockScenarios,
2151
- } from "./simulator-contract-schema";
2152
-
2153
- export type ProjectionResolverContext = {
2154
- entities: Readonly<Record<string, readonly MockEntityRecord[]>>;
2155
- getEntity(entityId: string): readonly MockEntityRecord[];
2156
- getById(entityId: string, id: JsonPrimitive): MockEntityRecord | undefined;
2157
- getRelated(relationId: string, sourceId: JsonPrimitive): readonly MockEntityRecord[];
2158
- };
2159
-
2160
- export type ProjectionResolver = (context: ProjectionResolverContext, params?: unknown) => unknown;
2161
-
2162
- export type ProjectionResolverMap = Record<string, ProjectionResolver>;
2163
2068
 
2164
- export type MockDataLoadHandlerContext = {
2165
- params: unknown;
2166
- signal?: AbortSignal;
2167
- store: PrototypeMockStore;
2168
- projections: {
2169
- resolve(projectionId: string, params?: unknown): unknown;
2170
- };
2171
- };
2069
+ export type EventReceipt = z.infer<typeof eventReceiptSchema>;
2172
2070
 
2173
- export type MockDataLoadHandler = (
2174
- context: MockDataLoadHandlerContext,
2175
- ) => unknown | Promise<unknown>;
2071
+ /** Lectura (GET): resuelve y devuelve datos; un error se lanza como excepción. */
2072
+ export interface LexyReadPort {
2073
+ load<T = unknown>(loadId: string, params?: unknown, meta?: ReadMeta): Promise<T>;
2074
+ }
2176
2075
 
2177
- export type MockDataLoadHandlerMap = Record<string, MockDataLoadHandler>;
2076
+ /** Escritura (POST): registra un hecho y devuelve su receipt. */
2077
+ export interface LexyWritePort {
2078
+ publish(eventId: string, payload: unknown, meta?: WriteMeta): Promise<EventReceipt>;
2079
+ }
2080
+ `,Lt=String.raw`import type { EventReceipt, ReadMeta, WriteMeta } from "./types";
2178
2081
 
2179
- export type MockEventHandlerContext = {
2180
- draft: import("./mock-store").PrototypeSimulatorState;
2181
- envelope: EventPublicationEnvelope;
2182
- getById(entityId: string, id: JsonPrimitive): MockEntityRecord | undefined;
2183
- nextEntityId(entityId: string): JsonPrimitive;
2082
+ export type CatalogEvent = {
2083
+ kind: "event";
2084
+ technicalId: string;
2085
+ description: string;
2086
+ trigger: string;
2087
+ writes: { entities: string[]; fields: string[] };
2088
+ lastPayload: unknown;
2089
+ lastReceipt: EventReceipt | null;
2184
2090
  };
2185
2091
 
2186
- export type MockEventHandlerResult = {
2187
- receipt?: unknown;
2092
+ export type CatalogLoad = {
2093
+ kind: "load";
2094
+ technicalId: string;
2095
+ description: string;
2096
+ trigger: string;
2097
+ reads: { entities: string[]; projections: string[] };
2098
+ lastParams: unknown;
2188
2099
  };
2189
2100
 
2190
- export type MockEventHandler = (context: MockEventHandlerContext) => MockEventHandlerResult | void;
2101
+ export type CatalogEntry = CatalogEvent | CatalogLoad;
2191
2102
 
2192
- export type MockEventHandlerMap = Record<string, MockEventHandler>;
2193
-
2194
- export type PrototypeSimulator = {
2195
- executor: ReturnType<typeof createMockBackendExecutor>;
2196
- store: PrototypeMockStore;
2197
- getActiveScenario(): PrototypeMockScenarios["scenarios"][string];
2198
- listScenarios(): Array<{ id: string; name: string; description: string }>;
2199
- switchScenario(scenarioId: string): void;
2200
- reset(): void;
2201
- clearPersistence(): void;
2202
- };
2103
+ type CatalogMessage =
2104
+ | { type: "entry"; entry: CatalogEntry }
2105
+ | { type: "syncRequest" }
2106
+ | { type: "sync"; entries: CatalogEntry[] };
2203
2107
 
2204
- type CreatePrototypeSimulatorOptions = {
2205
- profile: MockGenerationProfile;
2206
- scenarios: PrototypeMockScenarios;
2207
- projectionResolvers: ProjectionResolverMap;
2208
- dataLoadHandlers: MockDataLoadHandlerMap;
2209
- eventHandlers: MockEventHandlerMap;
2210
- };
2108
+ const createCatalog = () => {
2109
+ const store = new Map<string, CatalogEntry>();
2110
+ const listeners = new Set<() => void>();
2111
+ let snapshot: CatalogEntry[] = [];
2112
+ let synced = false;
2211
2113
 
2212
- export const createPrototypeSimulator = (
2213
- options: CreatePrototypeSimulatorOptions,
2214
- ): PrototypeSimulator => {
2215
- const store = createPrototypeMockStore({
2216
- projectName: prototypeDataContract.project.name,
2217
- scenarios: options.scenarios,
2218
- onHydrationWarning: (message) => {
2219
- console.warn("[Lexy simulator] " + message);
2220
- },
2221
- });
2114
+ const channel =
2115
+ typeof BroadcastChannel !== "undefined" ? new BroadcastChannel("lexy-ports-catalog") : null;
2222
2116
 
2223
- const executor = createMockBackendExecutor({
2224
- profile: options.profile,
2225
- scenarios: options.scenarios,
2226
- store,
2227
- projectionResolvers: options.projectionResolvers,
2228
- dataLoadHandlers: options.dataLoadHandlers,
2229
- eventHandlers: options.eventHandlers,
2230
- });
2117
+ const key = (entry: CatalogEntry) => entry.kind + ":" + entry.technicalId;
2231
2118
 
2232
- return {
2233
- executor,
2234
- store,
2235
- getActiveScenario: () => options.scenarios.scenarios[store.getSnapshot().activeScenarioId],
2236
- listScenarios: () =>
2237
- Object.values(options.scenarios.scenarios).map((scenario) => ({
2238
- id: scenario.id,
2239
- name: scenario.name,
2240
- description: scenario.description,
2241
- })),
2242
- switchScenario: (scenarioId) => store.switchScenario(scenarioId),
2243
- reset: () => store.resetScenario(),
2244
- clearPersistence: () => store.clearPersistence(),
2119
+ const notify = () => {
2120
+ snapshot = Array.from(store.values());
2121
+ for (const l of listeners) l();
2245
2122
  };
2246
- };
2247
-
2248
- export const prototypeSimulator = createPrototypeSimulator({
2249
- profile: mockGenerationProfile,
2250
- scenarios: prototypeMockScenarios,
2251
- projectionResolvers,
2252
- dataLoadHandlers,
2253
- eventHandlers,
2254
- });
2255
-
2256
- export const prototypeEventEngine = createPrototypeEventEngine({
2257
- executor: prototypeSimulator.executor,
2258
- });
2259
- `,Gt=String.raw`import { ZodError } from "zod";
2260
-
2261
- import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
2262
- import { prototypeEventEngineContractSchema } from "../src/prototype/event-engine/event-engine-contract-schema";
2263
- import { dataLoadHandlers } from "../src/prototype/simulator/data-load-handlers";
2264
- import { eventHandlers } from "../src/prototype/simulator/event-handlers";
2265
- import { mockGenerationProfile } from "../src/prototype/simulator/mock-generation-profile";
2266
- import { prototypeMockScenarios } from "../src/prototype/simulator/mock-scenarios";
2267
- import { projectionResolvers } from "../src/prototype/simulator/projection-resolvers";
2268
- import {
2269
- mockGenerationProfileSchema,
2270
- prototypeMockScenariosSchema,
2271
- validateMockProfileReferences,
2272
- validateMockScenarios,
2273
- type SimulatorValidationIssue,
2274
- } from "../src/prototype/simulator/simulator-contract-schema";
2275
-
2276
- const formatPath = (path: Array<string | number>) =>
2277
- path.length === 0 ? "simulator" : path.map(String).join(".");
2278
-
2279
- const printIssues = (issues: SimulatorValidationIssue[]) => {
2280
- console.error("✗ Simulador inválido");
2281
- for (const issue of issues) {
2282
- console.error(" " + formatPath(issue.path));
2283
- console.error(" " + issue.message);
2284
- }
2285
- };
2286
2123
 
2287
- try {
2288
- const dataContractModule = await import("../src/prototype/data-contract/prototype-data-contract");
2289
- const eventEngineModule =
2290
- await import("../src/prototype/event-engine/prototype-event-engine-contract");
2124
+ const apply = (entry: CatalogEntry) => {
2125
+ store.set(key(entry), entry);
2126
+ notify();
2127
+ };
2291
2128
 
2292
- const dataContractResult = prototypeDataContractSchema.safeParse(
2293
- dataContractModule.prototypeDataContract,
2294
- );
2295
- const eventEngineResult = prototypeEventEngineContractSchema.safeParse(
2296
- eventEngineModule.prototypeEventEngineContract,
2297
- );
2298
- const profileResult = mockGenerationProfileSchema.safeParse(mockGenerationProfile);
2299
- const scenariosResult = prototypeMockScenariosSchema.safeParse(prototypeMockScenarios);
2300
-
2301
- const issues: SimulatorValidationIssue[] = [];
2302
- for (const [prefix, result] of [
2303
- ["dataContract", dataContractResult],
2304
- ["eventEngine", eventEngineResult],
2305
- ["profile", profileResult],
2306
- ["scenarios", scenariosResult],
2307
- ] as const) {
2308
- if (!result.success) {
2309
- for (const issue of result.error.issues) {
2310
- issues.push({
2311
- path: [prefix, ...issue.path.map(String)],
2312
- message: issue.message,
2313
- });
2129
+ if (channel) {
2130
+ channel.onmessage = (ev: MessageEvent<CatalogMessage>) => {
2131
+ const msg = ev.data;
2132
+ if (msg.type === "entry") apply(msg.entry);
2133
+ else if (msg.type === "syncRequest")
2134
+ channel.postMessage({ type: "sync", entries: Array.from(store.values()) });
2135
+ else if (msg.type === "sync" && !synced && store.size === 0) {
2136
+ synced = true;
2137
+ for (const entry of msg.entries) store.set(key(entry), entry);
2138
+ notify();
2314
2139
  }
2315
- }
2140
+ };
2141
+ channel.postMessage({ type: "syncRequest" });
2316
2142
  }
2317
2143
 
2318
- if (
2319
- dataContractResult.success &&
2320
- eventEngineResult.success &&
2321
- profileResult.success &&
2322
- scenariosResult.success
2323
- ) {
2324
- issues.push(
2325
- ...validateMockProfileReferences({
2326
- profile: profileResult.data,
2327
- dataContract: dataContractResult.data,
2328
- }),
2329
- ...validateMockScenarios({
2330
- profile: profileResult.data,
2331
- scenarios: scenariosResult.data,
2332
- dataContract: dataContractResult.data,
2333
- eventEngineContract: eventEngineResult.data,
2334
- }),
2335
- );
2144
+ const publishEntry = (entry: CatalogEntry) => {
2145
+ store.set(key(entry), entry);
2146
+ notify();
2147
+ channel?.postMessage({ type: "entry", entry });
2148
+ };
2336
2149
 
2337
- for (const projectionId of Object.keys(projectionResolvers)) {
2338
- if (!dataContractResult.data.projections[projectionId]) {
2339
- issues.push({
2340
- path: ["projectionResolvers", projectionId],
2341
- message: 'La proyección "' + projectionId + '" no existe en la Spec 1.',
2342
- });
2343
- }
2344
- }
2150
+ return {
2151
+ recordEvent(technicalId: string, payload: unknown, meta: WriteMeta, receipt: EventReceipt) {
2152
+ const existing = store.get("event:" + technicalId);
2153
+ const prev = existing?.kind === "event" ? existing : undefined;
2154
+ publishEntry({
2155
+ kind: "event",
2156
+ technicalId,
2157
+ description: meta.description ?? prev?.description ?? "",
2158
+ trigger: meta.trigger ?? prev?.trigger ?? "",
2159
+ writes: {
2160
+ entities: meta.writes?.entities ?? prev?.writes.entities ?? [],
2161
+ fields: meta.writes?.fields ?? prev?.writes.fields ?? [],
2162
+ },
2163
+ lastPayload: payload,
2164
+ lastReceipt: receipt,
2165
+ });
2166
+ },
2167
+ recordLoad(technicalId: string, params: unknown, meta: ReadMeta) {
2168
+ const existing = store.get("load:" + technicalId);
2169
+ const prev = existing?.kind === "load" ? existing : undefined;
2170
+ publishEntry({
2171
+ kind: "load",
2172
+ technicalId,
2173
+ description: meta.description ?? prev?.description ?? "",
2174
+ trigger: meta.trigger ?? prev?.trigger ?? "",
2175
+ reads: {
2176
+ entities: meta.reads?.entities ?? prev?.reads.entities ?? [],
2177
+ projections: meta.reads?.projections ?? prev?.reads.projections ?? [],
2178
+ },
2179
+ lastParams: params,
2180
+ });
2181
+ },
2182
+ subscribe: (l: () => void) => {
2183
+ listeners.add(l);
2184
+ return () => listeners.delete(l);
2185
+ },
2186
+ getSnapshot: () => snapshot,
2187
+ };
2188
+ };
2345
2189
 
2346
- const requiredProjections = new Set<string>();
2347
- for (const load of Object.values(eventEngineResult.data.dataLoads)) {
2348
- for (const projectionId of load.reads?.projections ?? [])
2349
- requiredProjections.add(projectionId);
2350
- }
2351
- for (const projectionId of requiredProjections) {
2352
- if (!projectionResolvers[projectionId]) {
2353
- issues.push({
2354
- path: ["projectionResolvers", projectionId],
2355
- message: 'Falta resolver para la proyección consumida "' + projectionId + '".',
2356
- });
2357
- }
2190
+ export const catalog = createCatalog();
2191
+ `,Dt=String.raw`import { designerActivity } from "../designer-panel/designer-activity";
2192
+ import { type MockEntityRecord, mockStore } from "../mock-store/mock-store";
2193
+ import { catalog } from "./catalog";
2194
+ import { type LexyReadPort, type ReadMeta, readMetaSchema } from "./types";
2195
+
2196
+ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
2197
+ Boolean(value) && typeof value === "object" && !Array.isArray(value);
2198
+
2199
+ export const mockRead: LexyReadPort = {
2200
+ async load<T = unknown>(loadId: string, params?: unknown, rawMeta: ReadMeta = {}): Promise<T> {
2201
+ const parsed = readMetaSchema.safeParse(rawMeta);
2202
+ if (!parsed.success) {
2203
+ console.warn(
2204
+ '[lexy] Metadata de carga inválida para "' + loadId + '":',
2205
+ parsed.error.flatten().fieldErrors,
2206
+ );
2358
2207
  }
2359
-
2360
- for (const loadId of Object.keys(eventEngineResult.data.dataLoads)) {
2361
- if (!dataLoadHandlers[loadId]) {
2362
- issues.push({
2363
- path: ["dataLoadHandlers", loadId],
2364
- message: 'Falta handler para la carga "' + loadId + '".',
2365
- });
2366
- }
2208
+ const meta: ReadMeta = parsed.success ? parsed.data : {};
2209
+ catalog.recordLoad(loadId, params, meta);
2210
+
2211
+ const entityId = meta.reads?.entities?.[0];
2212
+ const all = entityId ? (mockStore.getSnapshot().entities[entityId] ?? []) : [];
2213
+ // Filtro genérico: si params es un objeto, exige coincidencia exacta de sus campos.
2214
+ const data: MockEntityRecord[] = isPlainObject(params)
2215
+ ? all.filter((record) =>
2216
+ Object.entries(params).every(([field, value]) => record[field] === value),
2217
+ )
2218
+ : all;
2219
+ designerActivity.record({
2220
+ kind: "load",
2221
+ label: meta.description ?? loadId,
2222
+ technicalId: loadId,
2223
+ triggerId: meta.trigger,
2224
+ input: params,
2225
+ });
2226
+ return data as T;
2227
+ },
2228
+ };
2229
+ `,zt=String.raw`import { designerActivity } from "../designer-panel/designer-activity";
2230
+ import { mockStore } from "../mock-store/mock-store";
2231
+ import { catalog } from "./catalog";
2232
+ import { type EventReceipt, type LexyWritePort, type WriteMeta, writeMetaSchema } from "./types";
2233
+
2234
+ let publicationSequence = 0;
2235
+
2236
+ export const mockWrite: LexyWritePort = {
2237
+ async publish(eventId: string, payload: unknown, rawMeta: WriteMeta = {}): Promise<EventReceipt> {
2238
+ const parsed = writeMetaSchema.safeParse(rawMeta);
2239
+ if (!parsed.success) {
2240
+ console.warn(
2241
+ '[lexy] Metadata de evento inválida para "' + eventId + '":',
2242
+ parsed.error.flatten().fieldErrors,
2243
+ );
2367
2244
  }
2368
- for (const handlerId of Object.keys(dataLoadHandlers)) {
2369
- if (!eventEngineResult.data.dataLoads[handlerId]) {
2370
- issues.push({
2371
- path: ["dataLoadHandlers", handlerId],
2372
- message: 'El handler "' + handlerId + '" no corresponde a ninguna carga.',
2373
- });
2374
- }
2245
+ const meta: WriteMeta = parsed.success ? parsed.data : {};
2246
+
2247
+ const entityId = meta.writes?.entities?.[0];
2248
+ if (entityId) {
2249
+ mockStore.transact((draft) => {
2250
+ const records = (draft.entities[entityId] ??= []);
2251
+ const nextId = draft.entitySequences[entityId] ?? records.length + 1;
2252
+ records.push({ id: String(nextId), ...(payload as Record<string, unknown>) });
2253
+ draft.entitySequences[entityId] = nextId + 1;
2254
+ });
2375
2255
  }
2376
2256
 
2377
- for (const eventId of Object.keys(eventEngineResult.data.events)) {
2378
- if (!eventHandlers[eventId]) {
2379
- issues.push({
2380
- path: ["eventHandlers", eventId],
2381
- message: 'Falta handler para el evento "' + eventId + '".',
2382
- });
2383
- }
2384
- }
2385
- for (const handlerId of Object.keys(eventHandlers)) {
2386
- if (!eventEngineResult.data.events[handlerId]) {
2387
- issues.push({
2388
- path: ["eventHandlers", handlerId],
2389
- message: 'El handler "' + handlerId + '" no corresponde a ningún evento.',
2390
- });
2391
- }
2392
- }
2393
- }
2257
+ publicationSequence += 1;
2258
+ const receipt: EventReceipt = {
2259
+ eventId: eventId + "_" + publicationSequence,
2260
+ status: 201,
2261
+ };
2262
+ catalog.recordEvent(eventId, payload, meta, receipt);
2263
+ designerActivity.record({
2264
+ kind: "event",
2265
+ label: meta.description ?? eventId,
2266
+ technicalId: eventId,
2267
+ triggerId: meta.trigger,
2268
+ input: payload,
2269
+ });
2270
+ return receipt;
2271
+ },
2272
+ };
2273
+ `,Mt=String.raw`import type { LexyReadPort, LexyWritePort } from "./types";
2394
2274
 
2395
- if (issues.length > 0) {
2396
- printIssues(issues);
2397
- process.exitCode = 1;
2398
- } else {
2399
- console.log("✓ Simulador válido");
2400
- console.log(" " + Object.keys(prototypeMockScenarios.scenarios).length + " escenarios");
2401
- console.log(" datasetVersion " + prototypeMockScenarios.datasetVersion);
2402
- }
2403
- } catch (error) {
2404
- if (error instanceof ZodError) {
2405
- printIssues(
2406
- error.issues.map((issue) => ({ path: issue.path.map(String), message: issue.message })),
2275
+ export const productionRead: LexyReadPort = {
2276
+ async load<T = unknown>(loadId: string): Promise<T> {
2277
+ throw new Error(
2278
+ '[lexy] productionRead.load("' + loadId + '") sin implementar: conéctalo al GET de tu API.',
2407
2279
  );
2408
- } else {
2409
- console.error("✗ No se pudo cargar el simulador");
2410
- console.error(" " + (error instanceof Error ? error.message : String(error)));
2411
- }
2412
- process.exitCode = 1;
2413
- }
2414
- `,He=async e=>{let t=async(o,r)=>{let a=Ne.join(e,o);await Fe.mkdir(Ne.dirname(a),{recursive:!0}),await Fe.writeFile(a,r)};await t(Lt,_t),await t(ce,Ft),await t(le,Nt),await t(zt,Ht),await t(At,Kt),await t(Ot,qt),await t(Dt,Ut),await t($t,Bt),await t(de,Jt),await t(Vt,Gt)};var Zt={feature:["src/app","src/features","src/shared/assets","src/shared/components/base","src/shared/hooks","src/shared/services","src/shared/lib","src/shared/types"],layer:["src/assets","src/components/base","src/hooks","src/services","src/lib","src/types","src/views","src/stores"]},Wt="10.33.3",Xt=e=>{let t=e.architecture==="feature"?{"react-router":"^7.9.0"}:{};return JSON.stringify({name:e.projectName,private:!0,version:"0.0.0",type:"module",packageManager:`pnpm@${Wt}`,scripts:{dev:"vite","check:data-contract":"tsx scripts/check-data-contract.ts","check:event-engine":"tsx scripts/check-event-engine.ts","check:simulator":"tsx scripts/check-simulator.ts","check:prototype":"pnpm check:data-contract && pnpm check:event-engine && pnpm check:simulator","build:app":"tsc -b && vite build",build:"pnpm check:prototype && pnpm build:app",preview:"vite preview",lint:"eslint .",format:"prettier --write ."},dependencies:{clsx:"^2.1.1",react:"^19.2.6","react-dom":"^19.2.6",...t,"tailwind-merge":"^3.3.1",zod:"^4.1.0"},devDependencies:{"@babel/core":"^7.29.7","@eslint/js":"^10.0.1","@rolldown/plugin-babel":"^0.2.3","@tailwindcss/vite":"^4.3.0","@types/babel__core":"^7.20.5","@types/node":"^24.12.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","babel-plugin-react-compiler":"^1.0.0",eslint:"^10.4.1","eslint-plugin-jsx-a11y":"^6.10.2","eslint-plugin-react-hooks":"^7.1.1","eslint-plugin-simple-import-sort":"^13.0.0",globals:"^17.6.0",prettier:"^3.8.4",tailwindcss:"^4.3.0",terser:"^5.48.0",tsx:"^4.19.0",typescript:"~5.9.3","typescript-eslint":"^8.61.0",vite:"^8.0.12"}},null,2)+`
2415
- `},Yt=`import path from "node:path";
2280
+ },
2281
+ };
2282
+
2283
+ export const productionWrite: LexyWritePort = {
2284
+ async publish(eventId: string) {
2285
+ throw new Error(
2286
+ '[lexy] productionWrite.publish("' + eventId + '") sin implementar: encólalo en el worker.',
2287
+ );
2288
+ },
2289
+ };
2290
+ `,$t=String.raw`import { mockRead } from "./mock-read";
2291
+ import { mockWrite } from "./mock-write";
2292
+ import { productionRead, productionWrite } from "./production";
2293
+ import type { LexyReadPort, LexyWritePort } from "./types";
2294
+
2295
+ const USE_MOCK = import.meta.env.DEV;
2296
+
2297
+ export const read: LexyReadPort = USE_MOCK ? mockRead : productionRead;
2298
+ export const write: LexyWritePort = USE_MOCK ? mockWrite : productionWrite;
2299
+
2300
+ export type {
2301
+ EventReceipt,
2302
+ LexyReadPort,
2303
+ LexyWritePort,
2304
+ ReadMeta,
2305
+ WriteMeta,
2306
+ } from "./types";
2307
+ `,Ft=[[ce,Ot],[de,_t],[Ct,At],[It,Lt],[Tt,Dt],[Rt,zt],[Nt,Mt],[pe,$t]],Ve=async e=>{for(let[t,o]of Ft){let a=qe.join(e,t);await Fe.mkdir(qe.dirname(a),{recursive:!0}),await Fe.writeFile(a,o)}};var Kt={feature:["src/app","src/features","src/shared/assets","src/shared/components/base","src/shared/hooks","src/shared/services","src/shared/lib","src/shared/types"],layer:["src/assets","src/components/base","src/hooks","src/services","src/lib","src/types","src/views","src/stores"]},Ut="10.33.3",Ht=e=>{let t=e.architecture==="feature"?{"react-router":"^7.9.0"}:{};return JSON.stringify({name:e.projectName,private:!0,version:"0.0.0",type:"module",packageManager:`pnpm@${Ut}`,scripts:{dev:"vite","check:data-contract":"tsx scripts/check-data-contract.ts","check:prototype":"pnpm check:data-contract","build:app":"tsc -b && vite build",build:"pnpm check:prototype && pnpm build:app",preview:"vite preview",lint:"eslint .",format:"prettier --write ."},dependencies:{clsx:"^2.1.1","lucide-react":"^0.460.0",react:"^19.2.6","react-dom":"^19.2.6",...t,"tailwind-merge":"^3.3.1",zod:"^4.1.0"},devDependencies:{"@babel/core":"^7.29.7","@eslint/js":"^10.0.1","@rolldown/plugin-babel":"^0.2.3","@tailwindcss/vite":"^4.3.0","@types/babel__core":"^7.20.5","@types/node":"^24.12.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","babel-plugin-react-compiler":"^1.0.0",eslint:"^10.4.1","eslint-plugin-jsx-a11y":"^6.10.2","eslint-plugin-react-hooks":"^7.1.1","eslint-plugin-simple-import-sort":"^13.0.0",globals:"^17.6.0",prettier:"^3.8.4",tailwindcss:"^4.3.0",terser:"^5.48.0",tsx:"^4.19.0",typescript:"~5.9.3","typescript-eslint":"^8.61.0",vite:"^8.0.12"}},null,2)+`
2308
+ `},Bt=`import path from "node:path";
2416
2309
 
2417
2310
  import babel from "@rolldown/plugin-babel";
2418
2311
  import tailwindcss from "@tailwindcss/vite";
@@ -2440,9 +2333,30 @@ export default defineConfig({
2440
2333
  terserOptions: {
2441
2334
  compress: { drop_console: true, drop_debugger: true },
2442
2335
  },
2336
+ // Code splitting por grupos de vendor (auditor\xEDa de desarrollo, jun 2026):
2337
+ // node_modules se separa en bundles tem\xE1ticos para carga diferida y mejor
2338
+ // cach\xE9 entre deploys. Los grupos sin match simplemente no emiten chunk.
2339
+ rolldownOptions: {
2340
+ output: {
2341
+ codeSplitting: {
2342
+ groups: [
2343
+ { name: "react-core", test: /node_modules[\\\\/](react|react-dom|scheduler)[\\\\/]/ },
2344
+ { name: "router", test: /node_modules[\\\\/]react-router[\\\\/]/ },
2345
+ { name: "query", test: /node_modules[\\\\/]@tanstack[\\\\/]/ },
2346
+ { name: "state", test: /node_modules[\\\\/]jotai[\\\\/]/ },
2347
+ { name: "utils", test: /node_modules[\\\\/](clsx|tailwind-merge|class-variance-authority|zod)[\\\\/]/ },
2348
+ { name: "ui", test: /node_modules[\\\\/](tailwind-animations|@tailwindcss)[\\\\/]/ },
2349
+ { name: "radix", test: /node_modules[\\\\/]@radix-ui[\\\\/]/ },
2350
+ { name: "icons", test: /node_modules[\\\\/]lucide-react[\\\\/]/ },
2351
+ { name: "vendor", test: /node_modules[\\\\/]/ },
2352
+ ],
2353
+ minSize: 20000,
2354
+ },
2355
+ },
2356
+ },
2443
2357
  },
2444
2358
  });
2445
- `,Qt=`import js from "@eslint/js";
2359
+ `,Jt=`import js from "@eslint/js";
2446
2360
  import jsxA11y from "eslint-plugin-jsx-a11y";
2447
2361
  import reactHooks from "eslint-plugin-react-hooks";
2448
2362
  import simpleImportSort from "eslint-plugin-simple-import-sort";
@@ -2471,14 +2385,14 @@ export default tseslint.config(
2471
2385
  },
2472
2386
  },
2473
2387
  );
2474
- `,eo=`{
2388
+ `,Wt=`{
2475
2389
  "printWidth": 100
2476
2390
  }
2477
- `,to=`pnpm-lock.yaml
2391
+ `,Gt=`pnpm-lock.yaml
2478
2392
  src/lexy-theme.css
2479
2393
  public/fonts/
2480
2394
  dist/
2481
- `,oo=`name: CI
2395
+ `,Qt=`name: CI
2482
2396
 
2483
2397
  on:
2484
2398
  push:
@@ -2499,11 +2413,9 @@ jobs:
2499
2413
  - run: pnpm lint
2500
2414
  - run: pnpm exec prettier --check .
2501
2415
  - run: pnpm check:data-contract
2502
- - run: pnpm check:event-engine
2503
- - run: pnpm check:simulator
2504
2416
  - run: pnpm build:app
2505
2417
  - run: pnpm audit --audit-level high
2506
- `,no=`{
2418
+ `,Yt=`{
2507
2419
  "files": [],
2508
2420
  "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
2509
2421
  "compilerOptions": {
@@ -2511,7 +2423,7 @@ jobs:
2511
2423
  "paths": { "@/*": ["./src/*"] }
2512
2424
  }
2513
2425
  }
2514
- `,ao=`{
2426
+ `,Xt=`{
2515
2427
  "compilerOptions": {
2516
2428
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
2517
2429
  "target": "ES2022",
@@ -2535,7 +2447,7 @@ jobs:
2535
2447
  },
2536
2448
  "include": ["src"]
2537
2449
  }
2538
- `,ro=`{
2450
+ `,Zt=`{
2539
2451
  "compilerOptions": {
2540
2452
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
2541
2453
  "target": "ES2023",
@@ -2552,7 +2464,7 @@ jobs:
2552
2464
  },
2553
2465
  "include": ["vite.config.ts"]
2554
2466
  }
2555
- `,io=e=>`<!doctype html>
2467
+ `,eo=e=>`<!doctype html>
2556
2468
  <html lang="es">
2557
2469
  <head>
2558
2470
  <meta charset="UTF-8" />
@@ -2564,9 +2476,9 @@ jobs:
2564
2476
  <script type="module" src="/src/main.tsx"></script>
2565
2477
  </body>
2566
2478
  </html>
2567
- `,so=`@import "tailwindcss";
2479
+ `,to=`@import "tailwindcss";
2568
2480
  @import "./lexy-theme.css";
2569
- `,co=e=>e.architecture==="feature"?`import "./index.css";
2481
+ `,oo=e=>e.architecture==="feature"?`import "./index.css";
2570
2482
 
2571
2483
  import { StrictMode } from "react";
2572
2484
  import { createRoot } from "react-dom/client";
@@ -2600,7 +2512,9 @@ createRoot(document.getElementById("root")!).render(
2600
2512
  Trae tu primer componente: <code>npx create-lexy add button</code>
2601
2513
  </p>
2602
2514
  </div>
2603
- </main>`,lo=e=>e.architecture==="feature"?`import { Route, Routes } from "react-router";
2515
+ </main>`,ao=e=>e.architecture==="feature"?`import { Route, Routes } from "react-router";
2516
+
2517
+ import { PrototypeDesignerPanel } from "../prototype/designer-panel/PrototypeDesignerPanel";
2604
2518
 
2605
2519
  const Home = () => {
2606
2520
  return (
@@ -2608,62 +2522,74 @@ ${Ke}
2608
2522
  );
2609
2523
  };
2610
2524
 
2525
+ const showPrototypeDesignerPanel = import.meta.env.DEV;
2526
+
2611
2527
  export const App = () => {
2612
2528
  return (
2613
- <Routes>
2614
- <Route path="/" element={<Home />} />
2615
- </Routes>
2529
+ <>
2530
+ <Routes>
2531
+ <Route path="/" element={<Home />} />
2532
+ </Routes>
2533
+ {showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
2534
+ </>
2616
2535
  );
2617
2536
  };
2618
- `:`export const App = () => {
2537
+ `:`import { PrototypeDesignerPanel } from "../prototype/designer-panel/PrototypeDesignerPanel";
2538
+
2539
+ const showPrototypeDesignerPanel = import.meta.env.DEV;
2540
+
2541
+ export const App = () => {
2619
2542
  return (
2543
+ <>
2620
2544
  ${Ke}
2545
+ {showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
2546
+ </>
2621
2547
  );
2622
2548
  };
2623
- `,po=`import { type ClassValue, clsx } from "clsx";
2549
+ `,no=`import { type ClassValue, clsx } from "clsx";
2624
2550
  import { twMerge } from "tailwind-merge";
2625
2551
 
2626
2552
  export function cn(...inputs: ClassValue[]) {
2627
2553
  return twMerge(clsx(inputs));
2628
2554
  }
2629
- `,mo=`node_modules/
2555
+ `,ro=`node_modules/
2630
2556
  dist/
2631
2557
  *.log
2632
2558
  .env
2633
- `,uo=e=>`# ${e.projectName}
2559
+ `,io=e=>`# ${e.projectName}
2634
2560
 
2635
2561
  Proyecto Lexy (React + Vite + TS + theme Lexy). Los componentes viven en tu proyecto: m\xEDralos con \`view\`, inst\xE1lalos con \`add\`, ed\xEDtalos con libertad.
2636
2562
 
2637
2563
  \`\`\`bash
2638
2564
  pnpm dev # desarrollo
2639
- pnpm check:prototype # validar datos, cargas/eventos y simulador
2565
+ pnpm check:prototype # validar el contrato de datos del prototipo
2640
2566
  npx create-lexy view button # ver un componente antes de instalarlo
2641
2567
  npx create-lexy add button # instalarlo (local y editable)
2642
2568
  npx create-lexy doctor # divergencia con el registry
2643
2569
  \`\`\`
2644
- `,fo=e=>{let o=e.architecture==="feature"?"@/shared":"@";return JSON.stringify({$schema:"https://ui.shadcn.com/schema.json",style:"new-york",rsc:!1,tsx:!0,tailwind:{config:"",css:"src/index.css",baseColor:"neutral",cssVariables:!0},aliases:{components:`${o}/components`,ui:`${o}/components/base`,utils:`${o}/lib/utils/cn`,lib:`${o}/lib`,hooks:`${o}/hooks`},registries:{"@lexy":`${F}@latest/r/{name}.json`}},null,2)+`
2645
- `},yo=e=>{let t=e.architecture==="feature";return{version:"2.0.0",generatedAt:new Date().toISOString(),architecture:e.architecture,world:e.world,registry:{package:"@lexydesign/registry",cdn:F},componentImportPattern:t?"@/shared/components/base/{Component}":"@/components/base/{Component}",paths:{components:t?"src/shared/components/base":"src/components/base",hooks:t?"src/shared/hooks":"src/hooks",services:t?"src/shared/services":"src/services",lib:t?"src/shared/lib":"src/lib",views:t?"src/features":"src/views"},prototype:{dataContractVersion:"1",dataContractPath:re,eventEngineVersion:"1",eventEngineContractPath:ie,eventEnginePath:se,simulatorVersion:"1",simulatorProfilePath:ce,simulatorScenariosPath:le,simulatorPath:de,defaultScenarioId:"base"},installed:{}}},qe=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],r=[];for(let l of o)try{await R.access(T.join(t,l)),r.push(l)}catch{}if(r.length>0)throw new Error(`El directorio actual ya contiene archivos de un proyecto (${r.join(", ")}). Ejecuta el comando dentro de una carpeta vac\xEDa.`);for(let l of Zt[e.architecture])await R.mkdir(T.join(t,l),{recursive:!0});let a=(l,y)=>R.writeFile(T.join(t,l),y);await a("package.json",Xt(e)),await a("vite.config.ts",Yt),await a("tsconfig.json",no),await a("tsconfig.app.json",ao),await a("tsconfig.node.json",ro),await a("eslint.config.js",Qt),await a(".prettierrc",eo),await a(".prettierignore",to),await R.mkdir(T.join(t,".github/workflows"),{recursive:!0}),await a(".github/workflows/ci.yml",oo),await a("index.html",io(e)),await a(".gitignore",mo),await a("README.md",uo(e)),await a("src/index.css",so),await a("src/main.tsx",co(e)),await De(t,e.projectName),await _e(t),await He(t);let i=e.architecture==="feature"?"src/app":"src/views";await a(`${i}/App.tsx`,lo(e));let n=yo(e);await a(".lexy",JSON.stringify(n,null,2)+`
2646
- `),await a("components.json",fo(e)),await R.copyFile(Pe,T.join(t,"src/lexy-theme.css")),await R.mkdir(T.join(t,"public/fonts"),{recursive:!0}),await R.cp(Ce,T.join(t,"public/fonts"),{recursive:!0});let p=T.join(t,n.paths.lib,"utils");await R.mkdir(p,{recursive:!0}),await R.writeFile(T.join(p,"cn.ts"),po)};var xo=["feature","layer"],bo=["cliente","crm","mixto"],Io=e=>new Promise((t,o)=>{ho("pnpm",["install"],{cwd:e,stdio:"ignore",shell:!0}).on("close",a=>a===0?t():o(new Error(`pnpm install fall\xF3 con c\xF3digo ${a}`)))}),Ue=async(e,t)=>{let o;!!(e&&t.type)?(xo.includes(t.type)||(console.error(E.red(`Error: la arquitectura "${t.type}" no es v\xE1lida. Usa "feature" o "layer".`)),process.exit(1)),t.world&&!bo.includes(t.world)&&(console.error(E.red(`Error: el mundo "${t.world}" no es v\xE1lido. Usa "cliente", "crm" o "mixto".`)),process.exit(1)),t.world||console.warn(E.yellow('Aviso: sin --world; se asume "mixto". ')+E.dim("Pasa --world cliente|crm para fijar densidad y voz desde el inicio.")),o={projectName:e,architecture:t.type,world:t.world??"mixto"},console.log(E.blue("Modo automatizado detectado. Saltando interfaz interactiva..."))):o=await ze(e),go(`Nombre: ${o.projectName}
2570
+ `,so=e=>{let o=e.architecture==="feature"?"@/shared":"@";return JSON.stringify({$schema:"https://ui.shadcn.com/schema.json",style:"new-york",rsc:!1,tsx:!0,tailwind:{config:"",css:"src/index.css",baseColor:"neutral",cssVariables:!0},aliases:{components:`${o}/components`,ui:`${o}/components/base`,utils:`${o}/lib/utils/cn`,lib:`${o}/lib`,hooks:`${o}/hooks`},registries:{"@lexy":`${F}@latest/r/{name}.json`}},null,2)+`
2571
+ `},lo=e=>{let t=e.architecture==="feature";return{version:"2.0.0",generatedAt:new Date().toISOString(),architecture:e.architecture,world:e.world,registry:{package:"@lexydesign/registry",cdn:F},componentImportPattern:t?"@/shared/components/base/{Component}":"@/components/base/{Component}",paths:{components:t?"src/shared/components/base":"src/components/base",hooks:t?"src/shared/hooks":"src/hooks",services:t?"src/shared/services":"src/services",lib:t?"src/shared/lib":"src/lib",views:t?"src/features":"src/views"},prototype:{dataContractVersion:"1",dataContractPath:re,runtimeVersion:"1",mockStorePath:ce,fixturesPath:de,portsPath:pe,designerPanelVersion:"1",designerPanelPath:le,designerActivityStorePath:se},installed:{}}},Ue=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],a=[];for(let s of o)try{await b.access(w.join(t,s)),a.push(s)}catch{}if(a.length>0)throw new Error(`El directorio actual ya contiene archivos de un proyecto (${a.join(", ")}). Ejecuta el comando dentro de una carpeta vac\xEDa.`);for(let s of Kt[e.architecture])await b.mkdir(w.join(t,s),{recursive:!0});let n=(s,u)=>b.writeFile(w.join(t,s),u);await n("package.json",Ht(e)),await n("vite.config.ts",Bt),await n("tsconfig.json",Yt),await n("tsconfig.app.json",Xt),await n("tsconfig.node.json",Zt),await n("eslint.config.js",Jt),await n(".prettierrc",Wt),await n(".prettierignore",Gt),await b.mkdir(w.join(t,".github/workflows"),{recursive:!0}),await n(".github/workflows/ci.yml",Qt),await n("index.html",eo(e)),await n(".gitignore",ro),await n("README.md",io(e)),await n("src/index.css",to),await n("src/main.tsx",oo(e)),await ze(t,e.projectName),await Ve(t);let r=lo(e);await $e(t,r);let i=e.architecture==="feature"?"src/app":"src/views";await n(`${i}/App.tsx`,ao(e)),await n(".lexy",JSON.stringify(r,null,2)+`
2572
+ `),await n("components.json",so(e)),await b.copyFile(Ce,w.join(t,"src/lexy-theme.css")),await b.mkdir(w.join(t,"public/fonts"),{recursive:!0}),await b.cp(Ie,w.join(t,"public/fonts"),{recursive:!0});let d=w.join(t,r.paths.lib,"utils");await b.mkdir(d,{recursive:!0}),await b.writeFile(w.join(d,"cn.ts"),no),await co(t)},co=async e=>{let t=[],o=async a=>{let n;try{n=await b.readdir(a,{withFileTypes:!0})}catch{return}for(let r of n){let i=w.join(a,r.name);r.isDirectory()?await o(i):/\.(ts|tsx)$/.test(r.name)&&t.push(i)}};await o(w.join(e,"src")),await o(w.join(e,"scripts"));for(let a of t){let n=await b.readFile(a,"utf-8"),r=await Vt(a)??{},i=await qt(n,{...r,filepath:a});i!==n&&await b.writeFile(a,i)}};var fo=["feature","layer"],go=["cliente","crm","mixto"],xo=e=>new Promise((t,o)=>{po("pnpm",["install"],{cwd:e,stdio:"ignore",shell:!0}).on("close",n=>n===0?t():o(new Error(`pnpm install fall\xF3 con c\xF3digo ${n}`)))}),He=async(e,t)=>{let o;!!(e&&t.type)?(fo.includes(t.type)||(console.error(j.red(`Error: la arquitectura "${t.type}" no es v\xE1lida. Usa "feature" o "layer".`)),process.exit(1)),t.world&&!go.includes(t.world)&&(console.error(j.red(`Error: el mundo "${t.world}" no es v\xE1lido. Usa "cliente", "crm" o "mixto".`)),process.exit(1)),t.world||console.warn(j.yellow('Aviso: sin --world; se asume "mixto". ')+j.dim("Pasa --world cliente|crm para fijar densidad y voz desde el inicio.")),o={projectName:e,architecture:t.type,world:t.world??"mixto"},console.log(j.blue("Modo automatizado detectado. Saltando interfaz interactiva..."))):o=await Ae(e),mo(`Nombre: ${o.projectName}
2647
2573
  Arquitectura: ${o.architecture}
2648
- Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let a=So();a.start("Construyendo tu proyecto...");let i=process.cwd();try{await qe(o,i)}catch(n){throw a.stop(E.red("No se pudo crear el proyecto.")),n}t.install!==!1&&(a.message("Instalando dependencias base..."),await Io(i)),a.stop("\xA1Proyecto construido con \xE9xito!"),vo(E.green(`Todo listo en el directorio actual
2649
-
2650
- `)+E.dim(`Siguientes pasos:
2651
- `)+E.cyan(` pnpm dev
2652
- `)+E.cyan(" npx create-lexy add button"))};import Eo from"fs/promises";import ko from"path";import z from"picocolors";import pe from"picocolors";var H=(e,t)=>{let o=e.split(`
2653
- `),r=t.split(`
2654
- `),a=o.length,i=r.length,n=Array.from({length:a+1},()=>new Array(i+1).fill(0));for(let u=a-1;u>=0;u--)for(let d=i-1;d>=0;d--)n[u][d]=o[u]===r[d]?n[u+1][d+1]+1:Math.max(n[u+1][d],n[u][d+1]);let p=[],l=0,y=0;for(;l<a&&y<i;)o[l]===r[y]?(p.push({type:"same",text:o[l]}),l++,y++):n[l+1][y]>=n[l][y+1]?p.push({type:"del",text:o[l++]}):p.push({type:"add",text:r[y++]});for(;l<a;)p.push({type:"del",text:o[l++]});for(;y<i;)p.push({type:"add",text:r[y++]});return p},X=e=>e.some(t=>t.type!=="same"),Be=(e,t=3)=>{if(!X(e))return null;let o=new Array(e.length).fill(!1);e.forEach((i,n)=>{if(i.type!=="same")for(let p=Math.max(0,n-t);p<=Math.min(e.length-1,n+t);p++)o[p]=!0});let r=[],a=!1;for(let i=0;i<e.length;i++){if(!o[i]){a||(r.push(pe.dim(" \u22EF")),a=!0);continue}a=!1;let n=e[i];n.type==="same"?r.push(` ${n.text}`):n.type==="del"?r.push(pe.red(`- ${n.text}`)):r.push(pe.green(`+ ${n.text}`))}return r.join(`
2655
- `)};var Je=async e=>{let t=process.cwd(),o=await L(t),r=e.toLowerCase(),{item:a,version:i}=await x(r),n=o.installed[r];console.log(z.bold(`
2656
- ${a.title??r}`)+z.dim(` \xB7 instalado con registry@${n??"?"} \xB7 vigente registry@${i}
2657
- `));let p=!1;for(let l of C(a,o)){let y=await Eo.readFile(ko.join(t,l.relPath),"utf-8").catch(()=>null);if(y===null){console.log(z.red(`\u2717 ${l.relPath} no existe localmente`)+z.dim(` \u2014 cr\xE9alo con \`create-lexy add ${r}\``)),p=!0;continue}let u=Be(H(y,l.content));u===null?console.log(z.green(`\u2713 ${l.relPath}`)+z.dim(" sin diferencias")):(p=!0,console.log(z.yellow(`\xB1 ${l.relPath}`)+z.dim(" (\u2212 local \xB7 + registry)")),console.log(u))}p&&(process.exitCode=1)};import A from"fs/promises";import f from"path";import k from"picocolors";var Ge=async e=>{let t=process.cwd(),o=0,r=s=>console.log(k.green(" \u2713 ")+s),a=s=>console.log(k.yellow(" \u2022 ")+s),i=(s,c)=>{o=1,console.log(k.red(" \u2717 ")+s),c&&console.log(k.dim(` \u2192 ${c}`))};console.log(k.bold(`
2574
+ Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let n=yo();n.start("Construyendo tu proyecto...");let r=process.cwd();try{await Ue(o,r)}catch(i){throw n.stop(j.red("No se pudo crear el proyecto.")),i}t.install!==!1&&(n.message("Instalando dependencias base..."),await xo(r)),n.stop("\xA1Proyecto construido con \xE9xito!"),uo(j.green(`Todo listo en el directorio actual
2575
+
2576
+ `)+j.dim(`Siguientes pasos:
2577
+ `)+j.cyan(` pnpm dev
2578
+ `)+j.cyan(" npx create-lexy add button"))};import vo from"fs/promises";import ho from"path";import S from"picocolors";import me from"picocolors";var V=(e,t)=>{let o=e.split(`
2579
+ `),a=t.split(`
2580
+ `),n=o.length,r=a.length,i=Array.from({length:n+1},()=>new Array(r+1).fill(0));for(let m=n-1;m>=0;m--)for(let y=r-1;y>=0;y--)i[m][y]=o[m]===a[y]?i[m+1][y+1]+1:Math.max(i[m+1][y],i[m][y+1]);let d=[],s=0,u=0;for(;s<n&&u<r;)o[s]===a[u]?(d.push({type:"same",text:o[s]}),s++,u++):i[s+1][u]>=i[s][u+1]?d.push({type:"del",text:o[s++]}):d.push({type:"add",text:a[u++]});for(;s<n;)d.push({type:"del",text:o[s++]});for(;u<r;)d.push({type:"add",text:a[u++]});return d},Y=e=>e.some(t=>t.type!=="same"),Be=(e,t=3)=>{if(!Y(e))return null;let o=new Array(e.length).fill(!1);e.forEach((r,i)=>{if(r.type!=="same")for(let d=Math.max(0,i-t);d<=Math.min(e.length-1,i+t);d++)o[d]=!0});let a=[],n=!1;for(let r=0;r<e.length;r++){if(!o[r]){n||(a.push(me.dim(" \u22EF")),n=!0);continue}n=!1;let i=e[r];i.type==="same"?a.push(` ${i.text}`):i.type==="del"?a.push(me.red(`- ${i.text}`)):a.push(me.green(`+ ${i.text}`))}return a.join(`
2581
+ `)};var Je=async e=>{let t=process.cwd(),o=await O(t),a=e.toLowerCase(),{item:n,version:r}=await k(a),i=o.installed[a];console.log(S.bold(`
2582
+ ${n.title??a}`)+S.dim(` \xB7 instalado con registry@${i??"?"} \xB7 vigente registry@${r}
2583
+ `));let d=!1;for(let s of T(n,o)){let u=await vo.readFile(ho.join(t,s.relPath),s.encoding??"utf-8").catch(()=>null);if(u===null){console.log(S.red(`\u2717 ${s.relPath} no existe localmente`)+S.dim(` \u2014 cr\xE9alo con \`create-lexy add ${a}\``)),d=!0;continue}if(s.encoding==="base64"){u===s.content?console.log(S.green(`\u2713 ${s.relPath}`)+S.dim(" sin diferencias")):(d=!0,console.log(S.yellow(`\xB1 ${s.relPath}`)+S.dim(" (binario difiere del registry)")));continue}let m=Be(V(u,s.content));m===null?console.log(S.green(`\u2713 ${s.relPath}`)+S.dim(" sin diferencias")):(d=!0,console.log(S.yellow(`\xB1 ${s.relPath}`)+S.dim(" (\u2212 local \xB7 + registry)")),console.log(m))}d&&(process.exitCode=1)};import _ from"fs/promises";import v from"path";import C from"picocolors";var We=async e=>{let t=process.cwd(),o=0,a=c=>console.log(C.green(" \u2713 ")+c),n=c=>console.log(C.yellow(" \u2022 ")+c),r=(c,l)=>{o=1,console.log(C.red(" \u2717 ")+c),l&&console.log(C.dim(` \u2192 ${l}`))};console.log(C.bold(`
2658
2584
  Diagn\xF3stico del proyecto Lexy
2659
- `));let n;try{n=await L(t),r(`.lexy v\xE1lido (arquitectura ${n.architecture}${n.world?`, mundo ${n.world}`:""})`)}catch(s){return i(s.message),1}await A.access(f.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?r("theme presente (src/lexy-theme.css)"):i("falta src/lexy-theme.css","re-cr\xE9alo desde un proyecto nuevo o copia el theme del registry");let l=f.join(n.paths.lib,"utils/cn.ts");await A.access(f.join(t,l)).then(()=>!0,()=>!1)?r(`helper cn presente (${l})`):i(`falta el helper cn (${l})`);let u=await A.readFile(f.join(t,"package.json"),"utf-8").then(s=>JSON.parse(s)).catch(()=>null),d=async(s,c)=>{await A.access(f.join(t,s)).then(()=>!0,()=>!1)?r(`${c} presente (${s})`):i(`falta ${c} (${s})`)},P=(s,c)=>{if(!u){i("package.json no se pudo leer para validar scripts del prototipo");return}let g=u.scripts?.[s];if(!g){i(`falta el script ${s} en package.json`,c?`agrega \`${c}\``:void 0);return}if(c&&!g.includes(c)){i(`el script ${s} no ejecuta ${c}`);return}r(`script ${s} configurado`)};if(!n.prototype)a("prototipo funcional no habilitado \u2014 proyecto anterior a las specs de datos/eventos/simulador");else{let s=n.prototype.dataContractPath;if(!s)a("contrato de datos no configurado \u2014 proyecto anterior a la Spec 1");else{await d(s,"contrato de datos");let c=f.join(f.dirname(s),"data-contract-schema.ts");await d(c,"schema del contrato"),await d("scripts/check-data-contract.ts","validador del contrato"),P("check:data-contract","tsx scripts/check-data-contract.ts")}n.prototype.eventEngineVersion?(n.prototype.eventEngineContractPath?(await d(n.prototype.eventEngineContractPath,"contrato del motor de eventos"),await d(f.join(f.dirname(n.prototype.eventEngineContractPath),"event-engine-contract-schema.ts"),"schema del motor de eventos"),await d(f.join(f.dirname(n.prototype.eventEngineContractPath),"payload-schema.ts"),"schemas runtime del motor de eventos")):i("eventEngineContractPath no est\xE1 configurado en .lexy"),n.prototype.eventEnginePath?await d(n.prototype.eventEnginePath,"engine de eventos"):i("eventEnginePath no est\xE1 configurado en .lexy"),await d("scripts/check-event-engine.ts","validador del motor de eventos"),P("check:event-engine","tsx scripts/check-event-engine.ts")):a("motor de eventos no habilitado \u2014 proyecto anterior a la Spec 2"),n.prototype.simulatorVersion?(n.prototype.simulatorProfilePath?(await d(n.prototype.simulatorProfilePath,"perfil del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"simulator-contract-schema.ts"),"schema del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"projection-resolvers.ts"),"resolvers de proyecci\xF3n del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"data-load-handlers.ts"),"handlers de carga del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"event-handlers.ts"),"handlers de evento del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"mock-store.ts"),"store persistente del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"mock-backend-executor.ts"),"executor mock del simulador")):i("simulatorProfilePath no est\xE1 configurado en .lexy"),n.prototype.simulatorScenariosPath?await d(n.prototype.simulatorScenariosPath,"escenarios del simulador"):i("simulatorScenariosPath no est\xE1 configurado en .lexy"),n.prototype.simulatorPath?await d(n.prototype.simulatorPath,"ensamblaje del simulador"):i("simulatorPath no est\xE1 configurado en .lexy"),await d("scripts/check-simulator.ts","validador del simulador"),P("check:simulator","tsx scripts/check-simulator.ts"),P("check:prototype","check:simulator")):a("simulador persistente no habilitado \u2014 proyecto anterior a la Spec 3")}let V=f.join(t,"ai/lexy-ai-manifest.json"),m=await A.readFile(V,"utf-8").catch(()=>null);if(m===null)console.log(k.dim(" \xB7 capa de IA no instalada (npx @lexydesign/ai install) \u2014 opcional"));else try{let s=JSON.parse(m),c=s.files??[],g=[];for(let I of c)await A.access(f.join(t,I)).then(()=>!0,()=>!1)||g.push(I);g.length>0?i(`capa de IA ${s.aiVersion??"?"} incompleta \u2014 faltan ${g.length} archivo(s) (${g.slice(0,3).join(", ")}${g.length>3?", \u2026":""})`,"npx @lexydesign/ai install"):r(`capa de IA ${s.aiVersion??"?"} completa (${c.length} archivos)`)}catch{i("ai/lexy-ai-manifest.json no es JSON v\xE1lido","regenera con `npx @lexydesign/ai install`")}let h=Object.entries(n.installed);if(h.length===0)console.log(k.dim(`
2660
- Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let s=await G();console.log(k.bold(`
2661
- Componentes instalados (registry vigente: ${s})
2662
- `));for(let[c,g]of h){let I;try{I=await x(c)}catch(D){i(`${c}: ${D.message}`);continue}let q=C(I.item,n),v=[],O=!1;for(let D of q){let U=await A.readFile(f.join(t,D.relPath),"utf-8").catch(()=>null);U===null?v.push(D.relPath):X(H(U,D.content))&&(O=!0)}if(v.length>0){i(`${c}: archivos faltantes (${v.join(", ")})`,`create-lexy add ${c}`);continue}if(!O){r(`${c} al d\xEDa (${I.version})`);continue}let ee=!0;if(g&&g!==I.version)try{let D=await x(c,g),U=C(D.item,n);ee=!1;for(let ve of U){let Se=await A.readFile(f.join(t,ve.relPath),"utf-8").catch(()=>null);(Se===null||X(H(Se,ve.content)))&&(ee=!0)}}catch{}ee?e.strict?i(`${c}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${c}`):a(`${c}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${c}\`)`):i(`${c}: desactualizado (instalado con ${g}, vigente ${I.version})`,`create-lexy diff ${c} \xB7 create-lexy add ${c} --overwrite`)}}return console.log(o===0?k.green(`
2585
+ `));let i;try{i=await O(t),a(`.lexy v\xE1lido (arquitectura ${i.architecture}${i.world?`, mundo ${i.world}`:""})`)}catch(c){return r(c.message),1}await _.access(v.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?a("theme presente (src/lexy-theme.css)"):r("falta src/lexy-theme.css","re-cr\xE9alo desde un proyecto nuevo o copia el theme del registry");let s=v.join(i.paths.lib,"utils/cn.ts");await _.access(v.join(t,s)).then(()=>!0,()=>!1)?a(`helper cn presente (${s})`):r(`falta el helper cn (${s})`);let m=await _.readFile(v.join(t,"package.json"),"utf-8").then(c=>JSON.parse(c)).catch(()=>null),y=async(c,l)=>{await _.access(v.join(t,c)).then(()=>!0,()=>!1)?a(`${l} presente (${c})`):r(`falta ${l} (${c})`)},z=(c,l)=>{if(!m){r("package.json no se pudo leer para validar scripts del prototipo");return}let g=m.scripts?.[c];if(!g){r(`falta el script ${c} en package.json`,l?`agrega \`${l}\``:void 0);return}if(l&&!g.includes(l)){r(`el script ${c} no ejecuta ${l}`);return}a(`script ${c} configurado`)};if(!i.prototype)n("prototipo funcional no habilitado \u2014 proyecto anterior al runtime de datos y ports");else{let c=i.prototype.dataContractPath;if(!c)n("contrato de datos no configurado \u2014 proyecto anterior a la Spec 1");else{await y(c,"contrato de datos");let l=v.join(v.dirname(c),"data-contract-schema.ts");await y(l,"schema del contrato"),await y("scripts/check-data-contract.ts","validador del contrato"),z("check:data-contract","tsx scripts/check-data-contract.ts")}i.prototype.runtimeVersion?(i.prototype.mockStorePath?(await y(i.prototype.mockStorePath,"mock-store del prototipo"),await y(v.join(v.dirname(i.prototype.mockStorePath),"fixtures.ts"),"fixtures del prototipo")):r("mockStorePath no est\xE1 configurado en .lexy"),i.prototype.portsPath?await y(i.prototype.portsPath,"ports del prototipo"):r("portsPath no est\xE1 configurado en .lexy")):n("runtime del prototipo no habilitado \u2014 proyecto anterior a la Spec 2"),i.prototype.designerPanelVersion?(i.prototype.designerPanelPath?await y(i.prototype.designerPanelPath,"panel de asistencia al Designer"):r("designerPanelPath no est\xE1 configurado en .lexy"),i.prototype.designerActivityStorePath?await y(i.prototype.designerActivityStorePath,"store de actividad del Designer"):r("designerActivityStorePath no est\xE1 configurado en .lexy")):n("panel de asistencia al Designer no habilitado \u2014 proyecto anterior a la Spec 4")}let M=v.join(t,"ai/lexy-ai-manifest.json"),p=await _.readFile(M,"utf-8").catch(()=>null);if(p===null)console.log(C.dim(" \xB7 capa de IA no instalada (npx @lexydesign/ai install) \u2014 opcional"));else try{let c=JSON.parse(p),l=c.files??[],g=[];for(let P of l)await _.access(v.join(t,P)).then(()=>!0,()=>!1)||g.push(P);g.length>0?r(`capa de IA ${c.aiVersion??"?"} incompleta \u2014 faltan ${g.length} archivo(s) (${g.slice(0,3).join(", ")}${g.length>3?", \u2026":""})`,"npx @lexydesign/ai install"):a(`capa de IA ${c.aiVersion??"?"} completa (${l.length} archivos)`)}catch{r("ai/lexy-ai-manifest.json no es JSON v\xE1lido","regenera con `npx @lexydesign/ai install`")}let f=Object.entries(i.installed);if(f.length===0)console.log(C.dim(`
2586
+ Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let c=await W();console.log(C.bold(`
2587
+ Componentes instalados (registry vigente: ${c})
2588
+ `));for(let[l,g]of f){let P;try{P=await k(l)}catch(L){r(`${l}: ${L.message}`);continue}let U=T(P.item,i),x=[],A=!1;for(let L of U){let H=await _.readFile(v.join(t,L.relPath),"utf-8").catch(()=>null);H===null?x.push(L.relPath):Y(V(H,L.content))&&(A=!0)}if(x.length>0){r(`${l}: archivos faltantes (${x.join(", ")})`,`create-lexy add ${l}`);continue}if(!A){a(`${l} al d\xEDa (${P.version})`);continue}let ee=!0;if(g&&g!==P.version)try{let L=await k(l,g),H=T(L.item,i);ee=!1;for(let he of H){let be=await _.readFile(v.join(t,he.relPath),"utf-8").catch(()=>null);(be===null||Y(V(be,he.content)))&&(ee=!0)}}catch{}ee?e.strict?r(`${l}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${l}`):n(`${l}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${l}\`)`):r(`${l}: desactualizado (instalado con ${g}, vigente ${P.version})`,`create-lexy diff ${l} \xB7 create-lexy add ${l} --overwrite`)}}return console.log(o===0?C.green(`
2663
2589
  Todo en orden.
2664
- `):k.red(`
2590
+ `):C.red(`
2665
2591
  Hay problemas que atender (exit 1).
2666
- `)),o};import me from"fs/promises";import Ze from"path";import{note as wo,outro as Po}from"@clack/prompts";import ue from"picocolors";var Y="LEXY-KICKOFF.md",Co=`# Kickoff de proyecto Lexy \u2014 protocolo de entrevista
2592
+ `)),o};import ue from"fs/promises";import Ge from"path";import{note as bo,outro as wo}from"@clack/prompts";import ye from"picocolors";var X="LEXY-KICKOFF.md",So=`# Kickoff de proyecto Lexy \u2014 protocolo de entrevista
2667
2593
 
2668
2594
  > **Para el agente (Claude Code):** este archivo es tu guion. La persona que
2669
2595
  > tienes enfrente es **dise\xF1adora, no programadora**: no le preguntes por
@@ -2733,34 +2659,36 @@ npx @lexydesign/ai install
2733
2659
  backend: lo que nazca desde usabilidad queda como
2734
2660
  \`origin: "generatedByUsability"\` y
2735
2661
  \`technicalValidation.status: "pendingTi"\`.
2736
- 3. Declara las comunicaciones externas en
2737
- \`src/prototype/event-engine/prototype-event-engine-contract.ts\`: lecturas
2738
- remotas como **cargas de datos** y escrituras como **eventos publicados**.
2739
- No declares interacciones locales.
2740
- 4. Completa \`src/prototype/simulator/\` con perfil, escenarios, resolvers y
2741
- handlers m\xEDnimos. Usa datos sint\xE9ticos, deterministas y formato chileno.
2742
- 5. Ejecuta \`pnpm check:prototype\` y corrige cualquier inconsistencia antes de
2662
+ 3. Mant\xE9n los datos mock en
2663
+ \`src/prototype/mock-store/fixtures.ts\`, nunca dentro de componentes. Las
2664
+ pantallas leen con \`read.load(loadId, params, meta)\` y publican hechos con
2665
+ \`write.publish(eventId, payload, meta)\`, importados desde
2666
+ \`src/prototype/ports\`. La metadata inline (\`reads\`/\`writes\`,
2667
+ \`description\`, \`trigger\`) alimenta el panel del Designer. No registres
2668
+ interacciones puramente locales.
2669
+ 4. Ejecuta \`pnpm check:prototype\` y corrige cualquier inconsistencia antes de
2743
2670
  empezar a implementar pantallas.
2744
2671
 
2745
- \`PROJECT-CONTEXT.md\` es el resumen humano. El contrato TypeScript es la fuente
2746
- estructurada de datos para las pr\xF3ximas sesiones. El motor explica qu\xE9 se carga
2747
- o publica y el simulador entrega datos mock persistentes sin backend real.
2672
+ \`PROJECT-CONTEXT.md\` es el resumen humano. El contrato de datos TypeScript es la
2673
+ fuente estructurada para las pr\xF3ximas sesiones. El mock-store mantiene datos
2674
+ coherentes y persistentes; los ports separan el adapter mock del adapter de
2675
+ producci\xF3n; el panel registra cargas y publicaciones para inspecci\xF3n.
2748
2676
 
2749
2677
  ## 5. Cerrar
2750
2678
 
2751
- Borra este archivo (\`${Y}\`): su trabajo termin\xF3. Conf\xEDrmale a la
2679
+ Borra este archivo (\`${X}\`): su trabajo termin\xF3. Conf\xEDrmale a la
2752
2680
  persona que su proyecto est\xE1 listo y prop\xF3n el primer paso concreto
2753
2681
  (p. ej. "\xBFDise\xF1amos la pantalla de inicio?").
2754
- `,We=async e=>{let t=process.cwd();for(let a of["package.json",".lexy"])if(await me.access(Ze.join(t,a)).then(()=>!0,()=>!1))throw new Error(`El directorio actual ya contiene un proyecto (${a}). \`kickoff\` es para empezar de cero en una carpeta vac\xEDa.`);let o=Ze.join(t,Y);if(await me.access(o).then(()=>!0,()=>!1)&&!e.force)throw new Error(`${Y} ya existe. Usa --force para regenerarlo.`);await me.writeFile(o,Co),wo(`Se cre\xF3 ${ue.cyan(Y)} en esta carpeta.
2755
-
2756
- Abre la carpeta con Claude Code y di: `+ue.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
2757
- El agente te entrevistar\xE1 y montar\xE1 el proyecto por ti.`,"Kickoff listo"),Po(ue.green("Sin flags, sin prompts t\xE9cnicos \u2014 solo conversa con el agente."))};import fe from"fs/promises";import Q from"path";import w from"picocolors";var he="@lexydesign/kit",Xe=`https://cdn.jsdelivr.net/npm/${he}`,Ye=8e3,ye=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Ye)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return Buffer.from(await t.arrayBuffer())},jo=async()=>{try{let t=await fetch(`https://registry.npmjs.org/${he}/latest`,{signal:AbortSignal.timeout(Ye)});if(t.ok)return(await t.json()).version}catch{}return JSON.parse((await ye(`${Xe}@latest/package.json`)).toString("utf-8")).version},Qe=async e=>{let t=process.cwd(),o=Q.resolve(t,e.dir??"lexy-kit"),r=Q.relative(t,o)||".";if(await fe.access(o).then(()=>!0).catch(()=>!1)&&!e.force)throw new Error(`Ya existe ${w.bold(r)}. Usa ${w.cyan("--force")} para sobrescribir o ${w.cyan("--dir <ruta>")} para otra carpeta.`);let i=await jo();console.log(w.dim(`
2758
- Trayendo ${he}@${i} \u2192 ${r}/
2759
- `));let n=`${Xe}@${i}/kit`,p=JSON.parse((await ye(`${n}/manifest.json`)).toString("utf-8")),l=["index.html","kit.css","kit.js","manifest.json","README.md","AGENTS.md","llms.txt","llms-full.txt","fonts/noto-sans-latin.woff2","fonts/OFL-NotoSans.txt",...p.guides?.pautas??[],...p.components.map(u=>u.file),...p.blocks.map(u=>u.file)],y=0;for(let u of l)try{let d=await ye(`${n}/${u}`),P=Q.join(o,u);await fe.mkdir(Q.dirname(P),{recursive:!0}),await fe.writeFile(P,d),y+=1}catch(d){console.warn(w.yellow(`\u26A0 no se pudo traer ${u}`)+w.dim(` (${d.message})`))}console.log(w.green("\u2713 Kit listo")+w.dim(` \u2014 ${y} archivo(s), ${p.components.length} componente(s) + ${p.blocks.length} block(s)
2760
- `)),console.log(` Abre ${w.bold(`${r}/index.html`)} en el navegador.`),console.log(w.dim(` El cat\xE1logo m\xE1quina-legible est\xE1 en ${r}/manifest.json.
2761
- `))};import Ro from"fs/promises";import ge from"path";import b from"picocolors";var K=e=>console.log(b.bold(b.cyan(`
2682
+ `,Qe=async e=>{let t=process.cwd();for(let n of["package.json",".lexy"])if(await ue.access(Ge.join(t,n)).then(()=>!0,()=>!1))throw new Error(`El directorio actual ya contiene un proyecto (${n}). \`kickoff\` es para empezar de cero en una carpeta vac\xEDa.`);let o=Ge.join(t,X);if(await ue.access(o).then(()=>!0,()=>!1)&&!e.force)throw new Error(`${X} ya existe. Usa --force para regenerarlo.`);await ue.writeFile(o,So),bo(`Se cre\xF3 ${ye.cyan(X)} en esta carpeta.
2683
+
2684
+ Abre la carpeta con Claude Code y di: `+ye.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
2685
+ El agente te entrevistar\xE1 y montar\xE1 el proyecto por ti.`,"Kickoff listo"),wo(ye.green("Sin flags, sin prompts t\xE9cnicos \u2014 solo conversa con el agente."))};import fe from"fs/promises";import Z from"path";import I from"picocolors";var xe="@lexydesign/kit",Ye=`https://cdn.jsdelivr.net/npm/${xe}`,Xe=8e3,ge=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Xe)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return Buffer.from(await t.arrayBuffer())},ko=async()=>{try{let t=await fetch(`https://registry.npmjs.org/${xe}/latest`,{signal:AbortSignal.timeout(Xe)});if(t.ok)return(await t.json()).version}catch{}return JSON.parse((await ge(`${Ye}@latest/package.json`)).toString("utf-8")).version},Ze=async e=>{let t=process.cwd(),o=Z.resolve(t,e.dir??"lexy-kit"),a=Z.relative(t,o)||".";if(await fe.access(o).then(()=>!0).catch(()=>!1)&&!e.force)throw new Error(`Ya existe ${I.bold(a)}. Usa ${I.cyan("--force")} para sobrescribir o ${I.cyan("--dir <ruta>")} para otra carpeta.`);let r=await ko();console.log(I.dim(`
2686
+ Trayendo ${xe}@${r} \u2192 ${a}/
2687
+ `));let i=`${Ye}@${r}/kit`,d=JSON.parse((await ge(`${i}/manifest.json`)).toString("utf-8")),s=["index.html","kit.css","kit.js","manifest.json","README.md","AGENTS.md","llms.txt","llms-full.txt","fonts/noto-sans-latin.woff2","fonts/OFL-NotoSans.txt",...d.guides?.pautas??[],...d.components.map(m=>m.file),...d.blocks.map(m=>m.file)],u=0;for(let m of s)try{let y=await ge(`${i}/${m}`),z=Z.join(o,m);await fe.mkdir(Z.dirname(z),{recursive:!0}),await fe.writeFile(z,y),u+=1}catch(y){console.warn(I.yellow(`\u26A0 no se pudo traer ${m}`)+I.dim(` (${y.message})`))}console.log(I.green("\u2713 Kit listo")+I.dim(` \u2014 ${u} archivo(s), ${d.components.length} componente(s) + ${d.blocks.length} block(s)
2688
+ `)),console.log(` Abre ${I.bold(`${a}/index.html`)} en el navegador.`),console.log(I.dim(` El cat\xE1logo m\xE1quina-legible est\xE1 en ${a}/manifest.json.
2689
+ `))};import Eo from"fs/promises";import ve from"path";import E from"picocolors";var K=e=>console.log(E.bold(E.cyan(`
2762
2690
  \u2500\u2500 ${e} \u2500\u2500
2763
- `))),et=async(e,t)=>{if(t.list||!e){let{index:n,version:p,source:l}=await we();K(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${p}${l==="bundle"?" (bundle local)":""}`);for(let y of n.items)console.log(` ${b.bold(y.name.padEnd(20))} ${b.dim(y.description??"")}`);console.log(b.dim(`
2764
- ${n.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let n=process.cwd(),p=await L(n),l=p.installed[o];if(!l){console.error(b.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:y}=await x(o,l).catch(()=>x(o));K(`${y.title??o} \xB7 copia local (instalado con registry@${l})`);for(let u of C(y,p)){let d=await Ro.readFile(ge.join(n,u.relPath),"utf-8").catch(()=>null);K(d===null?`${u.relPath} (FALTANTE)`:u.relPath),d!==null&&console.log(d)}return}let{item:r,version:a,source:i}=await x(o);K(`${r.title??r.name} \xB7 @lexydesign/registry@${a}${i==="bundle"?" (bundle local)":""}`),r.description&&console.log(r.description+`
2765
- `),console.log(b.dim("tipo: ")+r.type),console.log(b.dim("deps npm: ")+((r.dependencies??[]).join(", ")||"\u2014")),console.log(b.dim("deps registry: ")+((r.registryDependencies??[]).join(", ")||"\u2014")),console.log(b.dim("archivos: ")+r.files.map(n=>ge.posix.basename(n.path)).join(", ")),console.log(b.dim("instalar: ")+`create-lexy add ${r.name}`);for(let n of r.files)typeof n.content=="string"&&(K(ge.posix.basename(n.path)),console.log(n.content))};var Ao=tt.dirname(Mo(import.meta.url)),Oo=JSON.parse(To(tt.join(Ao,"../package.json"),"utf-8")),M=new Lo,$=e=>async()=>{try{await e()}catch(t){console.error(zo.red(`
2766
- Error: `)+t.message),process.exit(1)}};M.name("create-lexy").description("CLI del Lexy Design System: crea proyectos y trae componentes a demanda.\nLos componentes viven en tu proyecto \u2014 m\xEDralos con `view`, inst\xE1lalos con `add`, ed\xEDtalos con libertad.").version(Oo.version);M.command("create",{isDefault:!0}).alias("init").description("Crea un proyecto Lexy vac\xEDo (React + Vite + TS + theme); los componentes llegan con `add`").argument("[project-name]","Nombre del proyecto").option("-t, --type <architecture>","Arquitectura (feature o layer) \u2014 activa el modo no interactivo").option("-w, --world <world>","Mundo de dise\xF1o (cliente, crm o mixto)").option("--no-install","No correr pnpm install al final").action((e,t)=>$(()=>Ue(e,t))());M.command("kickoff").description("Deja un .md de entrevista para que el agente conduzca el arranque de un dise\xF1ador (carpeta vac\xEDa)").option("--force","Regenerar el .md de kickoff si ya existe").action(e=>$(()=>We(e))());M.command("add").description("Instala componentes del registry en tu proyecto (local y editable), con sus dependencias").argument("<components...>","Nombres en el registry (ej: button card)").option("--overwrite","Sobrescribir copias locales con cambios sin preguntar").option("--no-install","No instalar dependencias npm").action((e,t)=>$(()=>Re(e,t))());M.command("view").description("Muestra c\xF3digo + doc + metadata de un componente antes de instalarlo").argument("[component]","Nombre en el registry (sin nombre: lista el cat\xE1logo)").option("--installed","Mostrar la copia local instalada en vez de la del registry").option("--list","Listar el cat\xE1logo completo").action((e,t)=>$(()=>et(e,t))());M.command("kit").description("Trae el kit HTML/low-fi (mismos componentes y blocks en HTML plano) a una carpeta").option("--dir <path>","Carpeta destino (por defecto: lexy-kit)").option("--force","Sobrescribir la carpeta si ya existe").action(e=>$(()=>Qe(e))());M.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>$(()=>Je(e))());M.command("doctor").description("Drift de todo lo instalado contra el registry (solo lectura; exit 1 con problemas)").option("--strict","Las ediciones locales tambi\xE9n hacen fallar").action(e=>$(async()=>{process.exitCode=await Ge(e)})());M.parse(process.argv);
2691
+ `))),et=async(e,t)=>{if(t.list||!e){let{index:i,version:d,source:s}=await je();K(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${d}${s==="bundle"?" (bundle local)":""}`);for(let u of i.items)console.log(` ${E.bold(u.name.padEnd(20))} ${E.dim(u.description??"")}`);console.log(E.dim(`
2692
+ ${i.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let i=process.cwd(),d=await O(i),s=d.installed[o];if(!s){console.error(E.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:u}=await k(o,s).catch(()=>k(o));K(`${u.title??o} \xB7 copia local (instalado con registry@${s})`);for(let m of T(u,d)){let y=await Eo.readFile(ve.join(i,m.relPath),"utf-8").catch(()=>null);K(y===null?`${m.relPath} (FALTANTE)`:m.relPath),y!==null&&console.log(y)}return}let{item:a,version:n,source:r}=await k(o);K(`${a.title??a.name} \xB7 @lexydesign/registry@${n}${r==="bundle"?" (bundle local)":""}`),a.description&&console.log(a.description+`
2693
+ `),console.log(E.dim("tipo: ")+a.type),console.log(E.dim("deps npm: ")+((a.dependencies??[]).join(", ")||"\u2014")),console.log(E.dim("deps registry: ")+((a.registryDependencies??[]).join(", ")||"\u2014")),console.log(E.dim("archivos: ")+a.files.map(i=>ve.posix.basename(i.path)).join(", ")),console.log(E.dim("instalar: ")+`create-lexy add ${a.name}`);for(let i of a.files)typeof i.content=="string"&&(K(ve.posix.basename(i.path)),console.log(i.content))};var To=tt.dirname(jo(import.meta.url)),Ro=JSON.parse(Po(tt.join(To,"../package.json"),"utf-8")),N=new Co,D=e=>async()=>{try{await e()}catch(t){console.error(Io.red(`
2694
+ Error: `)+t.message),process.exit(1)}};N.name("create-lexy").description("CLI del Lexy Design System: crea proyectos y trae componentes a demanda.\nLos componentes viven en tu proyecto \u2014 m\xEDralos con `view`, inst\xE1lalos con `add`, ed\xEDtalos con libertad.").version(Ro.version);N.command("create",{isDefault:!0}).alias("init").description("Crea un proyecto Lexy vac\xEDo (React + Vite + TS + theme); los componentes llegan con `add`").argument("[project-name]","Nombre del proyecto").option("-t, --type <architecture>","Arquitectura (feature o layer) \u2014 activa el modo no interactivo").option("-w, --world <world>","Mundo de dise\xF1o (cliente, crm o mixto)").option("--no-install","No correr pnpm install al final").action((e,t)=>D(()=>He(e,t))());N.command("kickoff").description("Deja un .md de entrevista para que el agente conduzca el arranque de un dise\xF1ador (carpeta vac\xEDa)").option("--force","Regenerar el .md de kickoff si ya existe").action(e=>D(()=>Qe(e))());N.command("add").description("Instala componentes del registry en tu proyecto (local y editable), con sus dependencias").argument("<components...>","Nombres en el registry (ej: button card)").option("--overwrite","Sobrescribir copias locales con cambios sin preguntar").option("--no-install","No instalar dependencias npm").action((e,t)=>D(()=>Re(e,t))());N.command("view").description("Muestra c\xF3digo + doc + metadata de un componente antes de instalarlo").argument("[component]","Nombre en el registry (sin nombre: lista el cat\xE1logo)").option("--installed","Mostrar la copia local instalada en vez de la del registry").option("--list","Listar el cat\xE1logo completo").action((e,t)=>D(()=>et(e,t))());N.command("kit").description("Trae el kit HTML/low-fi (mismos componentes y blocks en HTML plano) a una carpeta").option("--dir <path>","Carpeta destino (por defecto: lexy-kit)").option("--force","Sobrescribir la carpeta si ya existe").action(e=>D(()=>Ze(e))());N.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>D(()=>Je(e))());N.command("doctor").description("Drift de todo lo instalado contra el registry (solo lectura; exit 1 con problemas)").option("--strict","Las ediciones locales tambi\xE9n hacen fallar").action(e=>D(async()=>{process.exitCode=await We(e)})());N.parse(process.argv);