create-lexy 0.5.1 → 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 +2421 -70
  66. package/package.json +16 -5
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as At}from"fs";import Me from"path";import{fileURLToPath as Nt}from"url";import{Command as Mt}from"commander";import Dt from"picocolors";import{spawn as Je}from"child_process";import G from"fs/promises";import B from"path";import{confirm as Ke,isCancel as Be,spinner as xe}from"@clack/prompts";import h from"picocolors";import Q from"path";var De=(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/'),_e=[["components/base/",e=>e.paths.components],["views/",e=>e.paths.views],["hooks/",e=>e.paths.hooks],["lib/",e=>e.paths.lib]],P=(e,t)=>e.files.filter(o=>typeof o.content=="string").map(o=>{let r=_e.find(([s])=>o.path.startsWith(s));return{relPath:r?Q.posix.join(r[1](t),o.path.slice(r[0].length)):Q.posix.join(t.paths.components,Q.posix.basename(o.path)),content:De(o.content,t)}});import de from"fs/promises";import qe from"path";var me=e=>qe.join(e,".lexy"),S=async e=>{let t;try{t=await de.readFile(me(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},ue=async(e,t)=>{await de.writeFile(me(e),JSON.stringify(t,null,2)+`
3
- `)};import q from"fs/promises";import Ve from"os";import E from"path";import{fileURLToPath as ze}from"url";var fe="@lexydesign/registry",M=`https://cdn.jsdelivr.net/npm/${fe}`,He=8e3,J=E.resolve(E.dirname(ze(import.meta.url)),"../assets"),Ue=E.join(Ve.homedir(),".cache","create-lexy","registry"),U=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(He)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return await t.json()},ge=async e=>{try{return JSON.parse(await q.readFile(E.join(J,"r",e),"utf-8"))}catch{return null}},Z=async()=>{try{return(await q.readFile(E.join(J,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},N=null,K=async()=>{if(N)return N;try{N=(await U(`https://registry.npmjs.org/${fe}/latest`)).version}catch{try{N=(await U(`${M}@latest/package.json`)).version}catch{N=await Z()}}return N},w=async(e,t)=>{let o=e.replace(/^@lexy\//,""),r=t??await K(),n=E.join(Ue,r,`${o}.json`);try{return{item:JSON.parse(await q.readFile(n,"utf-8")),version:r,source:"cache"}}catch{}try{let s=await U(`${M}@${r}/r/${o}.json`);return await q.mkdir(E.dirname(n),{recursive:!0}),await q.writeFile(n,JSON.stringify(s)),{item:s,version:r,source:"cdn"}}catch(s){if(s.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 i=await ge(`${o}.json`);if(i)return{item:i,version:await Z(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${o}" tampoco est\xE1 en el bundle local del CLI.`)}},ye=async()=>{try{let e=await K();return{index:await U(`${M}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await ge("registry.json");if(e)return{index:e,version:await Z(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},he=E.join(J,"theme/lexy-theme.css"),we=E.join(J,"fonts");var Ge=(e,t)=>new Promise((o,r)=>{Je("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",s=>s===0?o():r(new Error(`pnpm add fall\xF3 con c\xF3digo ${s}`)))}),We=async e=>{try{return await G.readFile(e,"utf-8")}catch{return null}},be=async(e,t)=>{let o=process.cwd(),r=await S(o),n=xe();n.start("Resolviendo componentes en el registry...");let s=new Map,i=async a=>{let p=a.toLowerCase().replace(/^@lexy\//,"");if(s.has(p))return;let g=await w(p);s.set(p,g);for(let b of g.item.registryDependencies??[])await i(b)};try{for(let a of e)await i(a)}catch(a){throw n.stop(h.red("No se pudo resolver el pedido.")),a}let l=new Map;for(let[a,{item:p}]of s){let g=[];for(let b of P(p,r)){let T=await We(B.join(o,b.relPath));g.push({...b,status:T===null?"new":T===b.content?"same":"differs"})}l.set(a,g)}let c=Array.from(l.values()).flat().filter(a=>a.status==="differs");if(n.stop(`Resueltos: ${Array.from(s.keys()).join(", ")}`),c.length>0&&!t.overwrite){let a=c.map(p=>` ${p.relPath}`).join(`
4
- `);if(process.stdout.isTTY&&process.stdin.isTTY){let p=await Ke({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
- ${a}
6
- \xBFContinuar?`});if(Be(p)||p!==!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
- `)+a+h.dim(`
8
- Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[a,p]of l){let{item:g,version:b,source:T}=s.get(a);for(let y of p){if(y.status==="same")continue;let C=B.join(o,y.relPath);await G.mkdir(B.dirname(C),{recursive:!0}),await G.writeFile(C,y.content)}r.installed[g.name]=b;let H=e.map(y=>y.toLowerCase()).includes(a)?"":h.dim(" (dependencia)"),_=T==="bundle"?h.yellow(" [bundle local \u2014 sin red]"):"";console.log(h.green(`${h.bold(g.title??g.name)} instalado`)+H+h.cyan(` \u2192 ${r.paths.components}/`)+_);for(let y of p){let C=y.status==="same"?h.dim(" (sin cambios)"):"";console.log(h.dim(` ${y.relPath}${C}`))}}await ue(o,r);let m=new Set;for(let{item:a}of s.values())for(let p of a.dependencies??[])m.add(p);let d=JSON.parse(await G.readFile(B.join(o,"package.json"),"utf-8")),u={...d.dependencies,...d.devDependencies},R=a=>{let p=a.lastIndexOf("@");return p>0?a.slice(0,p):a},f=Array.from(m).filter(a=>!u[R(a)]);if(f.length>0)if(t.install===!1)console.log(h.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${f.join(", ")}`));else{let a=xe();a.start(`Instalando dependencias: ${f.join(", ")}...`),await Ge(f,o),a.stop("Dependencias instaladas.")}};import{spawn as vt}from"child_process";import{note as jt,outro as $t,spinner as Ct}from"@clack/prompts";import v from"picocolors";import{cancel as Ye,intro as Xe,isCancel as Qe,select as je,text as Ze}from"@clack/prompts";import $e from"picocolors";var ee=[{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"}],ve=e=>ee.find(t=>t.value===e).architecture;var et=`
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,9 +20,2292 @@ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[a,p]
20
20
  ...........:====:...........
21
21
  ......................
22
22
  ..............
23
- `,te=e=>{Qe(e)&&(Ye("Operaci\xF3n cancelada por el usuario."),process.exit(0))},Ce=async e=>{console.clear(),Xe(`${$e.cyan(et)}
24
- ${$e.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let n=await Ze({message:"\xBFCu\xE1l es el nombre de tu proyecto?",placeholder:"lexy-app",validate:s=>{if(!s||s.trim()==="")return"El nombre es obligatorio.";if(!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(s))return"El nombre debe estar en min\xFAsculas, sin espacios ni caracteres especiales."}});te(n),t=n}let o=await je({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"});te(o);let r=await je({message:"\xBFC\xF3mo es lo que vas a construir?",options:ee.map(n=>({label:n.label,value:n.value,hint:n.hint})),initialValue:"app"});return te(r),{projectName:t,architecture:ve(r),world:o}};import k from"fs/promises";import I from"path";var tt={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"]},ot="10.33.3",rt=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@${ot}`,scripts:{dev:"vite",build:"tsc -b && vite build",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"},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",typescript:"~5.9.3","typescript-eslint":"^8.61.0",vite:"^8.0.12"}},null,2)+`
25
- `},nt=`import path from "node:path";
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
+
26
+ const camelCaseIdSchema = z
27
+ .string()
28
+ .regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
29
+
30
+ const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
31
+ const experienceLabelsSchema = z
32
+ .array(nonEmptyTextSchema)
33
+ .min(1, "Debe indicar al menos un uso dentro de la experiencia.");
34
+
35
+ export const dataOriginSchema = z.enum([
36
+ "lexyConfirmed",
37
+ "productAssumption",
38
+ "generatedByUsability",
39
+ ]);
40
+
41
+ export const technicalValidationStatusSchema = z.enum(["validated", "pendingTi", "rejected"]);
42
+
43
+ export const dataClassificationSchema = z.enum(["public", "internal", "sensitive", "restricted"]);
44
+
45
+ export const technicalValidationSchema = z
46
+ .object({
47
+ status: technicalValidationStatusSchema,
48
+ note: nonEmptyTextSchema.optional(),
49
+ validatedBy: nonEmptyTextSchema.optional(),
50
+ validatedAt: z.string().datetime({ offset: true }).optional(),
51
+ evidence: nonEmptyTextSchema.optional(),
52
+ })
53
+ .strict()
54
+ .superRefine((validation, context) => {
55
+ if (validation.status === "pendingTi" && !validation.note) {
56
+ context.addIssue({
57
+ code: "custom",
58
+ path: ["note"],
59
+ message: "Un pendiente de TI debe explicar qué necesita validarse.",
60
+ });
61
+ }
62
+
63
+ if (validation.status === "validated") {
64
+ if (!validation.validatedBy) {
65
+ context.addIssue({
66
+ code: "custom",
67
+ path: ["validatedBy"],
68
+ message: "Un dato validado debe indicar quién lo validó.",
69
+ });
70
+ }
71
+ if (!validation.validatedAt) {
72
+ context.addIssue({
73
+ code: "custom",
74
+ path: ["validatedAt"],
75
+ message: "Un dato validado debe indicar cuándo fue validado.",
76
+ });
77
+ }
78
+ }
79
+ });
80
+
81
+ const backendReferenceSchema = z
82
+ .string()
83
+ .regex(
84
+ /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/,
85
+ "Las referencias de Lexy deben usar snake_case, por ejemplo cliente.rut_cliente.",
86
+ );
87
+
88
+ export const sourceReferenceSchema = z
89
+ .object({
90
+ kind: z.enum(["lexyEntity", "lexyProjection", "api", "derived", "unknown"]),
91
+ reference: nonEmptyTextSchema.optional(),
92
+ })
93
+ .strict()
94
+ .superRefine((source, context) => {
95
+ if ((source.kind === "lexyEntity" || source.kind === "lexyProjection") && !source.reference) {
96
+ context.addIssue({
97
+ code: "custom",
98
+ path: ["reference"],
99
+ message: "La fuente Lexy debe indicar su referencia en snake_case.",
100
+ });
101
+ return;
102
+ }
103
+
104
+ if (source.reference && (source.kind === "lexyEntity" || source.kind === "lexyProjection")) {
105
+ const result = backendReferenceSchema.safeParse(source.reference);
106
+ if (!result.success) {
107
+ context.addIssue({
108
+ code: "custom",
109
+ path: ["reference"],
110
+ message: result.error.issues[0]?.message ?? "Referencia backend inválida.",
111
+ });
112
+ }
113
+ }
114
+ });
115
+
116
+ const usageSchema = z
117
+ .object({
118
+ visible: z.boolean(),
119
+ editable: z.boolean(),
120
+ calculated: z.boolean(),
121
+ technical: z.boolean(),
122
+ filterable: z.boolean().optional(),
123
+ sortable: z.boolean().optional(),
124
+ })
125
+ .strict();
126
+
127
+ const metadataSchema = z
128
+ .object({
129
+ origin: dataOriginSchema,
130
+ source: sourceReferenceSchema,
131
+ dataClassification: dataClassificationSchema,
132
+ technicalValidation: technicalValidationSchema,
133
+ })
134
+ .strict();
135
+
136
+ const validateUsabilityEvidence = (
137
+ value: {
138
+ origin: z.infer<typeof dataOriginSchema>;
139
+ technicalValidation: z.infer<typeof technicalValidationSchema>;
140
+ },
141
+ context: z.RefinementCtx,
142
+ ) => {
143
+ if (
144
+ value.origin === "generatedByUsability" &&
145
+ value.technicalValidation.status === "validated" &&
146
+ !value.technicalValidation.evidence
147
+ ) {
148
+ context.addIssue({
149
+ code: "custom",
150
+ path: ["technicalValidation", "evidence"],
151
+ message: "Un dato generado por usabilidad solo puede validarse con evidencia explícita.",
152
+ });
153
+ }
154
+ };
155
+
156
+ export const fieldSpecSchema = z
157
+ .object({
158
+ id: camelCaseIdSchema,
159
+ productDescription: nonEmptyTextSchema,
160
+ dataType: z.enum(["string", "number", "boolean", "date", "datetime", "enum", "identifier"]),
161
+ required: z.boolean(),
162
+ usage: usageSchema,
163
+ usedIn: experienceLabelsSchema,
164
+ enumValues: z.array(nonEmptyTextSchema).min(1).optional(),
165
+ derivation: nonEmptyTextSchema.optional(),
166
+ })
167
+ .extend(metadataSchema.shape)
168
+ .strict()
169
+ .superRefine((field, context) => {
170
+ validateUsabilityEvidence(field, context);
171
+
172
+ if (field.dataType === "enum" && !field.enumValues) {
173
+ context.addIssue({
174
+ code: "custom",
175
+ path: ["enumValues"],
176
+ message: "Un campo enum debe declarar al menos un valor.",
177
+ });
178
+ }
179
+ if (field.dataType !== "enum" && field.enumValues) {
180
+ context.addIssue({
181
+ code: "custom",
182
+ path: ["enumValues"],
183
+ message: "Solo los campos enum pueden declarar enumValues.",
184
+ });
185
+ }
186
+ if ((field.usage.calculated || field.source.kind === "derived") && !field.derivation) {
187
+ context.addIssue({
188
+ code: "custom",
189
+ path: ["derivation"],
190
+ message: "Un dato calculado debe explicar su regla de derivación.",
191
+ });
192
+ }
193
+ });
194
+
195
+ export const entityStateSpecSchema = z
196
+ .object({
197
+ id: camelCaseIdSchema,
198
+ productDescription: nonEmptyTextSchema,
199
+ activationCondition: nonEmptyTextSchema,
200
+ visualImpact: nonEmptyTextSchema,
201
+ usedIn: experienceLabelsSchema,
202
+ })
203
+ .extend(metadataSchema.shape)
204
+ .strict()
205
+ .superRefine(validateUsabilityEvidence);
206
+
207
+ export const entitySpecSchema = z
208
+ .object({
209
+ id: camelCaseIdSchema,
210
+ productDescription: nonEmptyTextSchema,
211
+ roleInExperience: nonEmptyTextSchema,
212
+ usedIn: experienceLabelsSchema,
213
+ fields: z.record(camelCaseIdSchema, fieldSpecSchema),
214
+ states: z.array(entityStateSpecSchema).optional(),
215
+ })
216
+ .extend(metadataSchema.shape)
217
+ .strict()
218
+ .superRefine(validateUsabilityEvidence);
219
+
220
+ export const relationSpecSchema = z
221
+ .object({
222
+ id: camelCaseIdSchema,
223
+ fromEntity: camelCaseIdSchema,
224
+ toEntity: camelCaseIdSchema,
225
+ cardinality: z.enum(["oneToOne", "oneToMany", "manyToMany"]),
226
+ productDescription: nonEmptyTextSchema,
227
+ requiredBy: experienceLabelsSchema,
228
+ resolution: z.enum(["embedded", "queried", "derived", "unknown"]),
229
+ derivation: nonEmptyTextSchema.optional(),
230
+ })
231
+ .extend(metadataSchema.shape)
232
+ .strict()
233
+ .superRefine((relation, context) => {
234
+ validateUsabilityEvidence(relation, context);
235
+ if (
236
+ (relation.resolution === "derived" || relation.source.kind === "derived") &&
237
+ !relation.derivation
238
+ ) {
239
+ context.addIssue({
240
+ code: "custom",
241
+ path: ["derivation"],
242
+ message: "Una relación derivada debe explicar su regla de derivación.",
243
+ });
244
+ }
245
+ });
246
+
247
+ export const projectionFieldSpecSchema = z
248
+ .object({
249
+ id: camelCaseIdSchema,
250
+ source: z
251
+ .string()
252
+ .regex(
253
+ /^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/,
254
+ "Debe referenciar un campo como entidad.campo usando camelCase.",
255
+ ),
256
+ required: z.boolean(),
257
+ })
258
+ .strict();
259
+
260
+ export const projectionSpecSchema = z
261
+ .object({
262
+ id: camelCaseIdSchema,
263
+ productDescription: nonEmptyTextSchema,
264
+ sourceEntities: experienceLabelsSchema,
265
+ consumedBy: experienceLabelsSchema,
266
+ fields: z.array(projectionFieldSpecSchema),
267
+ derivation: nonEmptyTextSchema.optional(),
268
+ })
269
+ .extend(metadataSchema.shape)
270
+ .strict()
271
+ .superRefine((projection, context) => {
272
+ validateUsabilityEvidence(projection, context);
273
+ if (projection.source.kind === "derived" && !projection.derivation) {
274
+ context.addIssue({
275
+ code: "custom",
276
+ path: ["derivation"],
277
+ message: "Una proyección derivada debe explicar su regla de derivación.",
278
+ });
279
+ }
280
+ });
281
+
282
+ const projectSchema = z
283
+ .object({
284
+ name: nonEmptyTextSchema,
285
+ description: z.string().trim(),
286
+ })
287
+ .strict();
288
+
289
+ export const prototypeDataContractSchema = z
290
+ .object({
291
+ contractVersion: z.literal("1"),
292
+ project: projectSchema,
293
+ entities: z.record(camelCaseIdSchema, entitySpecSchema),
294
+ relations: z.array(relationSpecSchema),
295
+ projections: z.record(camelCaseIdSchema, projectionSpecSchema),
296
+ })
297
+ .strict()
298
+ .superRefine((contract, context) => {
299
+ for (const [entityKey, entity] of Object.entries(contract.entities)) {
300
+ if (entityKey !== entity.id) {
301
+ context.addIssue({
302
+ code: "custom",
303
+ path: ["entities", entityKey, "id"],
304
+ message: 'El id "' + entity.id + '" debe coincidir con la key "' + entityKey + '".',
305
+ });
306
+ }
307
+
308
+ for (const [fieldKey, field] of Object.entries(entity.fields)) {
309
+ if (fieldKey !== field.id) {
310
+ context.addIssue({
311
+ code: "custom",
312
+ path: ["entities", entityKey, "fields", fieldKey, "id"],
313
+ message: 'El id "' + field.id + '" debe coincidir con la key "' + fieldKey + '".',
314
+ });
315
+ }
316
+ }
317
+
318
+ const stateIds = new Set<string>();
319
+ for (const [stateIndex, state] of (entity.states ?? []).entries()) {
320
+ if (stateIds.has(state.id)) {
321
+ context.addIssue({
322
+ code: "custom",
323
+ path: ["entities", entityKey, "states", stateIndex, "id"],
324
+ message: 'El estado "' + state.id + '" está declarado más de una vez.',
325
+ });
326
+ }
327
+ stateIds.add(state.id);
328
+ }
329
+ }
330
+
331
+ const relationIds = new Set<string>();
332
+ for (const [relationIndex, relation] of contract.relations.entries()) {
333
+ if (relationIds.has(relation.id)) {
334
+ context.addIssue({
335
+ code: "custom",
336
+ path: ["relations", relationIndex, "id"],
337
+ message: 'La relación "' + relation.id + '" está declarada más de una vez.',
338
+ });
339
+ }
340
+ relationIds.add(relation.id);
341
+
342
+ if (!contract.entities[relation.fromEntity]) {
343
+ context.addIssue({
344
+ code: "custom",
345
+ path: ["relations", relationIndex, "fromEntity"],
346
+ message: 'La entidad "' + relation.fromEntity + '" no existe.',
347
+ });
348
+ }
349
+ if (!contract.entities[relation.toEntity]) {
350
+ context.addIssue({
351
+ code: "custom",
352
+ path: ["relations", relationIndex, "toEntity"],
353
+ message: 'La entidad "' + relation.toEntity + '" no existe.',
354
+ });
355
+ }
356
+ }
357
+
358
+ for (const [projectionKey, projection] of Object.entries(contract.projections)) {
359
+ if (projectionKey !== projection.id) {
360
+ context.addIssue({
361
+ code: "custom",
362
+ path: ["projections", projectionKey, "id"],
363
+ message:
364
+ 'El id "' + projection.id + '" debe coincidir con la key "' + projectionKey + '".',
365
+ });
366
+ }
367
+
368
+ for (const [sourceIndex, entityId] of projection.sourceEntities.entries()) {
369
+ if (!contract.entities[entityId]) {
370
+ context.addIssue({
371
+ code: "custom",
372
+ path: ["projections", projectionKey, "sourceEntities", sourceIndex],
373
+ message: 'La entidad "' + entityId + '" no existe.',
374
+ });
375
+ }
376
+ }
377
+
378
+ const projectionFieldIds = new Set<string>();
379
+ for (const [fieldIndex, field] of projection.fields.entries()) {
380
+ if (projectionFieldIds.has(field.id)) {
381
+ context.addIssue({
382
+ code: "custom",
383
+ path: ["projections", projectionKey, "fields", fieldIndex, "id"],
384
+ message: 'El campo proyectado "' + field.id + '" está declarado más de una vez.',
385
+ });
386
+ }
387
+ projectionFieldIds.add(field.id);
388
+
389
+ const [entityId, fieldId] = field.source.split(".");
390
+ const entity = contract.entities[entityId];
391
+ if (!entity) {
392
+ context.addIssue({
393
+ code: "custom",
394
+ path: ["projections", projectionKey, "fields", fieldIndex, "source"],
395
+ message: 'La referencia "' + field.source + '" apunta a una entidad que no existe.',
396
+ });
397
+ continue;
398
+ }
399
+ if (!entity.fields[fieldId]) {
400
+ context.addIssue({
401
+ code: "custom",
402
+ path: ["projections", projectionKey, "fields", fieldIndex, "source"],
403
+ message: 'La referencia "' + field.source + '" apunta a un campo que no existe.',
404
+ });
405
+ }
406
+ if (!projection.sourceEntities.includes(entityId)) {
407
+ context.addIssue({
408
+ code: "custom",
409
+ path: ["projections", projectionKey, "fields", fieldIndex, "source"],
410
+ message: 'La entidad "' + entityId + '" debe declararse también en sourceEntities.',
411
+ });
412
+ }
413
+ }
414
+ }
415
+ });
416
+
417
+ export type DataOrigin = z.infer<typeof dataOriginSchema>;
418
+ export type TechnicalValidationStatus = z.infer<typeof technicalValidationStatusSchema>;
419
+ export type TechnicalValidation = z.infer<typeof technicalValidationSchema>;
420
+ export type SourceReference = z.infer<typeof sourceReferenceSchema>;
421
+ export type FieldSpec = z.infer<typeof fieldSpecSchema>;
422
+ export type EntityStateSpec = z.infer<typeof entityStateSpecSchema>;
423
+ export type EntitySpec = z.infer<typeof entitySpecSchema>;
424
+ export type RelationSpec = z.infer<typeof relationSpecSchema>;
425
+ export type ProjectionSpec = z.infer<typeof projectionSpecSchema>;
426
+ export type PrototypeDataContract = z.infer<typeof prototypeDataContractSchema>;
427
+
428
+ export function definePrototypeDataContract(
429
+ contract: PrototypeDataContract,
430
+ ): PrototypeDataContract {
431
+ return prototypeDataContractSchema.parse(contract);
432
+ }
433
+ `,St=String.raw`import { ZodError } from "zod";
434
+
435
+ import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
436
+
437
+ const formatPath = (path: PropertyKey[]) =>
438
+ path.length === 0 ? "contrato" : path.map(String).join(".");
439
+
440
+ const printIssues = (error: ZodError) => {
441
+ console.error("✗ Contrato de datos inválido");
442
+ for (const issue of error.issues) {
443
+ console.error(" " + formatPath(issue.path));
444
+ console.error(" " + issue.message);
445
+ }
446
+ };
447
+
448
+ const countPendingValidations = (value: unknown): number => {
449
+ if (Array.isArray(value)) {
450
+ return value.reduce((total, item) => total + countPendingValidations(item), 0);
451
+ }
452
+ if (!value || typeof value !== "object") return 0;
453
+
454
+ const record = value as Record<string, unknown>;
455
+ const ownPending =
456
+ record.technicalValidation &&
457
+ typeof record.technicalValidation === "object" &&
458
+ (record.technicalValidation as Record<string, unknown>).status === "pendingTi"
459
+ ? 1
460
+ : 0;
461
+
462
+ return Object.values(record).reduce(
463
+ (total, item) => total + countPendingValidations(item),
464
+ ownPending,
465
+ );
466
+ };
467
+
468
+ try {
469
+ const module = await import("../src/prototype/data-contract/prototype-data-contract");
470
+ const result = prototypeDataContractSchema.safeParse(module.prototypeDataContract);
471
+
472
+ if (!result.success) {
473
+ printIssues(result.error);
474
+ process.exitCode = 1;
475
+ } else {
476
+ const contract = result.data;
477
+ console.log("✓ Contrato de datos válido");
478
+ console.log(" " + Object.keys(contract.entities).length + " entidades");
479
+ console.log(" " + contract.relations.length + " relaciones");
480
+ console.log(" " + Object.keys(contract.projections).length + " proyecciones");
481
+ console.log(
482
+ " " + countPendingValidations(contract) + " elementos pendientes de validación con TI",
483
+ );
484
+ }
485
+ } catch (error) {
486
+ if (error instanceof ZodError) {
487
+ printIssues(error);
488
+ } else {
489
+ console.error("✗ No se pudo cargar el contrato de datos");
490
+ console.error(" " + (error instanceof Error ? error.message : String(error)));
491
+ }
492
+ process.exitCode = 1;
493
+ }
494
+ `,kt=e=>`import { definePrototypeDataContract } from "./data-contract-schema";
495
+
496
+ /**
497
+ * Fuente de verdad de los datos usados por el prototipo.
498
+ *
499
+ * El designer describe el producto en lenguaje natural; el agente mantiene este
500
+ * contrato y ejecuta \`pnpm check:data-contract\` antes de implementar la UI.
501
+ */
502
+ export const prototypeDataContract = definePrototypeDataContract({
503
+ contractVersion: "1",
504
+ project: {
505
+ name: ${JSON.stringify(e)},
506
+ description: "",
507
+ },
508
+ entities: {},
509
+ relations: [],
510
+ projections: {},
511
+ });
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;
516
+ triggerId?: string;
517
+ input?: unknown;
518
+ };
519
+
520
+ export type DesignerActivityRecord = {
521
+ id: string;
522
+ occurredAt: string;
523
+ kind: "load" | "event";
524
+ label: string;
525
+ technicalId?: string;
526
+ triggerId?: string;
527
+ inputPreview?: string;
528
+ };
529
+
530
+ export type DesignerActivityRecorder = {
531
+ record(input: DesignerActivityRecordInput): void;
532
+ };
533
+
534
+ export type DesignerActivityStore = DesignerActivityRecorder & {
535
+ getSnapshot(): readonly DesignerActivityRecord[];
536
+ subscribe(listener: () => void): () => void;
537
+ clear(): void;
538
+ };
539
+
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]";
546
+ }
547
+ };
548
+
549
+ const tabId = Math.random().toString(36).slice(2, 8);
550
+
551
+ type ChannelMessage =
552
+ | { type: "record"; record: DesignerActivityRecord }
553
+ | { type: "clear" }
554
+ | { type: "syncRequest" }
555
+ | { type: "sync"; records: DesignerActivityRecord[] };
556
+
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;
562
+
563
+ const channel =
564
+ typeof BroadcastChannel !== "undefined"
565
+ ? new BroadcastChannel("lexy-designer-activity")
566
+ : null;
567
+
568
+ const notify = () => {
569
+ for (const listener of listeners) listener();
570
+ };
571
+
572
+ const addRecord = (record: DesignerActivityRecord) => {
573
+ records = [record, ...records].slice(0, maxItems);
574
+ notify();
575
+ };
576
+
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
+ }
594
+
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) } : {}),
611
+ };
612
+ addRecord(record);
613
+ channel?.postMessage({ type: "record", record });
614
+ },
615
+ clear: () => {
616
+ records = [];
617
+ notify();
618
+ channel?.postMessage({ type: "clear" });
619
+ },
620
+ };
621
+ };
622
+
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
+ }
665
+
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
+ }
717
+
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";
1044
+
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
+ }
1062
+
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;
1076
+ }
1077
+ };
1078
+
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>
1113
+ );
1114
+ }
1115
+
1116
+ const entries = Object.entries(value as Record<string, unknown>);
1117
+
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>
1143
+ );
1144
+ }
1145
+
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";
1152
+
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
+ }
1175
+
1176
+ const DESIGNER_PANEL_ROUTE = "/designer-panel";
1177
+
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
+ }
1195
+
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);
1207
+ };
1208
+
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;
1220
+
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
+ }
1266
+
1267
+ const inferType = (value: unknown): string => {
1268
+ if (value === null) return "null";
1269
+ if (Array.isArray(value)) return "array";
1270
+ return typeof value;
1271
+ };
1272
+
1273
+ const TYPE_COLORS: Record<string, string> = {
1274
+ string: "var(--dp-success)",
1275
+ number: "var(--dp-info)",
1276
+ boolean: "var(--dp-warning)",
1277
+ };
1278
+
1279
+ const TypeLabel = ({ value }: { value: unknown }) => (
1280
+ <span className="lexy-dp-muted" style={{ marginLeft: "8px" }}>{inferType(value)}</span>
1281
+ );
1282
+
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" };
1294
+
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
+ );
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
+ );
1321
+ }
1322
+
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
+ );
1338
+ }
1339
+
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
+ }
1347
+
1348
+ function EventDetail({ event }: { event: CatalogEvent }) {
1349
+ const [triggersOpen, setTriggersOpen] = useState(true);
1350
+ const [payloadOpen, setPayloadOpen] = useState(true);
1351
+
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;
1365
+
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>
1388
+
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
+ }
1436
+
1437
+ function LoadDetail({ load }: { load: CatalogLoad }) {
1438
+ const [paramsOpen, setParamsOpen] = useState(true);
1439
+ const params = load.lastParams;
1440
+
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>
1461
+
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
+ }
1482
+
1483
+ type SelectedContract =
1484
+ | { kind: "event"; id: string }
1485
+ | { kind: "load"; id: string }
1486
+ | { kind: "entity"; id: string };
1487
+
1488
+ type EntitySelectedItem =
1489
+ | { kind: "field"; id: string }
1490
+ | { kind: "relation"; id: string; reverse: boolean };
1491
+
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);
1496
+
1497
+ const entries = useSyncExternalStore(
1498
+ catalog.subscribe,
1499
+ catalog.getSnapshot,
1500
+ catalog.getSnapshot,
1501
+ );
1502
+
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
+ };
1538
+
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);
1543
+
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>
1556
+
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
+ );
1665
+ }
1666
+
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
+ }
1692
+
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;
1736
+
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
+ }
1756
+
1757
+ type DesignerTab = "activity" | "events";
1758
+
1759
+ export function DesignerPanelPage() {
1760
+ const [tab, setTab] = useState<DesignerTab>("activity");
1761
+
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>;
1856
+
1857
+ export type PrototypeMockState = {
1858
+ datasetVersion: number;
1859
+ entities: Record<string, MockEntityRecord[]>;
1860
+ entitySequences: Record<string, number>;
1861
+ };
1862
+
1863
+ export type MockStorage = {
1864
+ read(): string | null;
1865
+ write(value: string): void;
1866
+ remove(): void;
1867
+ };
1868
+
1869
+ export type PrototypeMockStore = {
1870
+ getSnapshot(): PrototypeMockState;
1871
+ subscribe(listener: () => void): () => void;
1872
+ transact(operation: (draft: PrototypeMockState) => void): PrototypeMockState;
1873
+ clearPersistence(): void;
1874
+ };
1875
+
1876
+ const STORAGE_KEY = "lexy:prototype:mock-store:v1";
1877
+
1878
+ const clone = <T>(value: T): T => {
1879
+ if (typeof structuredClone === "function") return structuredClone(value);
1880
+ return JSON.parse(JSON.stringify(value)) as T;
1881
+ };
1882
+
1883
+ const buildSequences = (entities: Record<string, MockEntityRecord[]>) => {
1884
+ const sequences: Record<string, number> = {};
1885
+ for (const [entityId, records] of Object.entries(entities)) {
1886
+ sequences[entityId] = records.length + 1;
1887
+ }
1888
+ return sequences;
1889
+ };
1890
+
1891
+ const stateFromFixtures = (): PrototypeMockState => {
1892
+ const entities = clone(fixtures.entities);
1893
+ return {
1894
+ datasetVersion: fixtures.datasetVersion,
1895
+ entities,
1896
+ entitySequences: buildSequences(entities),
1897
+ };
1898
+ };
1899
+
1900
+ const isCompatible = (value: unknown): value is PrototypeMockState => {
1901
+ if (!value || typeof value !== "object") return false;
1902
+ const state = value as Partial<PrototypeMockState>;
1903
+ return (
1904
+ state.datasetVersion === fixtures.datasetVersion &&
1905
+ Boolean(state.entities && typeof state.entities === "object") &&
1906
+ Boolean(state.entitySequences && typeof state.entitySequences === "object")
1907
+ );
1908
+ };
1909
+
1910
+ const createMemoryStorage = (): MockStorage => {
1911
+ let current: string | null = null;
1912
+ return {
1913
+ read: () => current,
1914
+ write: (value) => {
1915
+ current = value;
1916
+ },
1917
+ remove: () => {
1918
+ current = null;
1919
+ },
1920
+ };
1921
+ };
1922
+
1923
+ const createLocalStorageStorage = (key: string): MockStorage => {
1924
+ const memory = createMemoryStorage();
1925
+ const getLocalStorage = () => {
1926
+ try {
1927
+ return globalThis.localStorage;
1928
+ } catch {
1929
+ return undefined;
1930
+ }
1931
+ };
1932
+ return {
1933
+ read: () => {
1934
+ try {
1935
+ return getLocalStorage()?.getItem(key) ?? memory.read();
1936
+ } catch {
1937
+ return memory.read();
1938
+ }
1939
+ },
1940
+ write: (value) => {
1941
+ memory.write(value);
1942
+ try {
1943
+ getLocalStorage()?.setItem(key, value);
1944
+ } catch {
1945
+ // fallback en memoria
1946
+ }
1947
+ },
1948
+ remove: () => {
1949
+ memory.remove();
1950
+ try {
1951
+ getLocalStorage()?.removeItem(key);
1952
+ } catch {
1953
+ // fallback en memoria
1954
+ }
1955
+ },
1956
+ };
1957
+ };
1958
+
1959
+ export const createPrototypeMockStore = (
1960
+ storage: MockStorage = createLocalStorageStorage(STORAGE_KEY),
1961
+ ): PrototypeMockStore => {
1962
+ const listeners = new Set<() => void>();
1963
+
1964
+ const hydrate = (): PrototypeMockState => {
1965
+ const persisted = storage.read();
1966
+ if (!persisted) return stateFromFixtures();
1967
+ try {
1968
+ const parsed = JSON.parse(persisted) as unknown;
1969
+ // Al cambiar fixtures se sube datasetVersion: el estado viejo se descarta.
1970
+ if (isCompatible(parsed)) return parsed;
1971
+ } catch {
1972
+ // estado corrupto: reinicia desde fixtures
1973
+ }
1974
+ return stateFromFixtures();
1975
+ };
1976
+
1977
+ let snapshot = hydrate();
1978
+
1979
+ const notify = () => {
1980
+ for (const listener of listeners) listener();
1981
+ };
1982
+
1983
+ return {
1984
+ getSnapshot: () => snapshot,
1985
+ subscribe: (listener) => {
1986
+ listeners.add(listener);
1987
+ return () => {
1988
+ listeners.delete(listener);
1989
+ };
1990
+ },
1991
+ transact: (operation) => {
1992
+ const draft = clone(snapshot);
1993
+ operation(draft);
1994
+ snapshot = draft;
1995
+ storage.write(JSON.stringify(snapshot));
1996
+ notify();
1997
+ return snapshot;
1998
+ },
1999
+ clearPersistence: () => storage.remove(),
2000
+ };
2001
+ };
2002
+
2003
+ /** Instancia compartida que ven el adapter de lectura y el de escritura. */
2004
+ export const mockStore = createPrototypeMockStore();
2005
+
2006
+ export const findRecordById = (
2007
+ records: readonly MockEntityRecord[],
2008
+ fieldId: string,
2009
+ id: JsonPrimitive,
2010
+ ) => records.find((record) => record[fieldId] === id);
2011
+ `,_t=String.raw`import type { MockEntityRecord } from "./mock-store";
2012
+
2013
+ export type Fixtures = {
2014
+ datasetVersion: number;
2015
+ entities: Record<string, MockEntityRecord[]>;
2016
+ };
2017
+
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: {},
2026
+ };
2027
+ `,At=String.raw`import { z } from "zod";
2028
+
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();
2043
+
2044
+ export type ReadMeta = z.infer<typeof readMetaSchema>;
2045
+
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();
2060
+
2061
+ export type WriteMeta = z.infer<typeof writeMetaSchema>;
2062
+
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),
2067
+ });
2068
+
2069
+ export type EventReceipt = z.infer<typeof eventReceiptSchema>;
2070
+
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
+ }
2075
+
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";
2081
+
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;
2090
+ };
2091
+
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;
2099
+ };
2100
+
2101
+ export type CatalogEntry = CatalogEvent | CatalogLoad;
2102
+
2103
+ type CatalogMessage =
2104
+ | { type: "entry"; entry: CatalogEntry }
2105
+ | { type: "syncRequest" }
2106
+ | { type: "sync"; entries: CatalogEntry[] };
2107
+
2108
+ const createCatalog = () => {
2109
+ const store = new Map<string, CatalogEntry>();
2110
+ const listeners = new Set<() => void>();
2111
+ let snapshot: CatalogEntry[] = [];
2112
+ let synced = false;
2113
+
2114
+ const channel =
2115
+ typeof BroadcastChannel !== "undefined" ? new BroadcastChannel("lexy-ports-catalog") : null;
2116
+
2117
+ const key = (entry: CatalogEntry) => entry.kind + ":" + entry.technicalId;
2118
+
2119
+ const notify = () => {
2120
+ snapshot = Array.from(store.values());
2121
+ for (const l of listeners) l();
2122
+ };
2123
+
2124
+ const apply = (entry: CatalogEntry) => {
2125
+ store.set(key(entry), entry);
2126
+ notify();
2127
+ };
2128
+
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();
2139
+ }
2140
+ };
2141
+ channel.postMessage({ type: "syncRequest" });
2142
+ }
2143
+
2144
+ const publishEntry = (entry: CatalogEntry) => {
2145
+ store.set(key(entry), entry);
2146
+ notify();
2147
+ channel?.postMessage({ type: "entry", entry });
2148
+ };
2149
+
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
+ };
2189
+
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
+ );
2207
+ }
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
+ );
2244
+ }
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
+ });
2255
+ }
2256
+
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";
2274
+
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.',
2279
+ );
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";
26
2309
 
27
2310
  import babel from "@rolldown/plugin-babel";
28
2311
  import tailwindcss from "@tailwindcss/vite";
@@ -50,9 +2333,30 @@ export default defineConfig({
50
2333
  terserOptions: {
51
2334
  compress: { drop_console: true, drop_debugger: true },
52
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
+ },
53
2357
  },
54
2358
  });
55
- `,st=`import js from "@eslint/js";
2359
+ `,Jt=`import js from "@eslint/js";
56
2360
  import jsxA11y from "eslint-plugin-jsx-a11y";
57
2361
  import reactHooks from "eslint-plugin-react-hooks";
58
2362
  import simpleImportSort from "eslint-plugin-simple-import-sort";
@@ -81,14 +2385,14 @@ export default tseslint.config(
81
2385
  },
82
2386
  },
83
2387
  );
84
- `,it=`{
2388
+ `,Wt=`{
85
2389
  "printWidth": 100
86
2390
  }
87
- `,at=`pnpm-lock.yaml
2391
+ `,Gt=`pnpm-lock.yaml
88
2392
  src/lexy-theme.css
89
2393
  public/fonts/
90
2394
  dist/
91
- `,ct=`name: CI
2395
+ `,Qt=`name: CI
92
2396
 
93
2397
  on:
94
2398
  push:
@@ -108,9 +2412,10 @@ jobs:
108
2412
  - run: pnpm install --frozen-lockfile
109
2413
  - run: pnpm lint
110
2414
  - run: pnpm exec prettier --check .
111
- - run: pnpm build
2415
+ - run: pnpm check:data-contract
2416
+ - run: pnpm build:app
112
2417
  - run: pnpm audit --audit-level high
113
- `,lt=`{
2418
+ `,Yt=`{
114
2419
  "files": [],
115
2420
  "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
116
2421
  "compilerOptions": {
@@ -118,7 +2423,7 @@ jobs:
118
2423
  "paths": { "@/*": ["./src/*"] }
119
2424
  }
120
2425
  }
121
- `,pt=`{
2426
+ `,Xt=`{
122
2427
  "compilerOptions": {
123
2428
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
124
2429
  "target": "ES2022",
@@ -142,7 +2447,7 @@ jobs:
142
2447
  },
143
2448
  "include": ["src"]
144
2449
  }
145
- `,dt=`{
2450
+ `,Zt=`{
146
2451
  "compilerOptions": {
147
2452
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
148
2453
  "target": "ES2023",
@@ -159,7 +2464,7 @@ jobs:
159
2464
  },
160
2465
  "include": ["vite.config.ts"]
161
2466
  }
162
- `,mt=e=>`<!doctype html>
2467
+ `,eo=e=>`<!doctype html>
163
2468
  <html lang="es">
164
2469
  <head>
165
2470
  <meta charset="UTF-8" />
@@ -171,9 +2476,9 @@ jobs:
171
2476
  <script type="module" src="/src/main.tsx"></script>
172
2477
  </body>
173
2478
  </html>
174
- `,ut=`@import "tailwindcss";
2479
+ `,to=`@import "tailwindcss";
175
2480
  @import "./lexy-theme.css";
176
- `,ft=e=>e.architecture==="feature"?`import "./index.css";
2481
+ `,oo=e=>e.architecture==="feature"?`import "./index.css";
177
2482
 
178
2483
  import { StrictMode } from "react";
179
2484
  import { createRoot } from "react-dom/client";
@@ -200,76 +2505,91 @@ createRoot(document.getElementById("root")!).render(
200
2505
  <App />
201
2506
  </StrictMode>,
202
2507
  );
203
- `,Pe=` <main className="flex min-h-screen items-center justify-center bg-background text-foreground">
2508
+ `,Ke=` <main className="flex min-h-screen items-center justify-center bg-background text-foreground">
204
2509
  <div className="text-center">
205
2510
  <h1 className="text-2xl font-semibold">Proyecto Lexy listo</h1>
206
2511
  <p className="mt-2 text-sm text-muted-foreground">
207
2512
  Trae tu primer componente: <code>npx create-lexy add button</code>
208
2513
  </p>
209
2514
  </div>
210
- </main>`,gt=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";
211
2518
 
212
2519
  const Home = () => {
213
2520
  return (
214
- ${Pe}
2521
+ ${Ke}
215
2522
  );
216
2523
  };
217
2524
 
2525
+ const showPrototypeDesignerPanel = import.meta.env.DEV;
2526
+
218
2527
  export const App = () => {
219
2528
  return (
220
- <Routes>
221
- <Route path="/" element={<Home />} />
222
- </Routes>
2529
+ <>
2530
+ <Routes>
2531
+ <Route path="/" element={<Home />} />
2532
+ </Routes>
2533
+ {showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
2534
+ </>
223
2535
  );
224
2536
  };
225
- `:`export const App = () => {
2537
+ `:`import { PrototypeDesignerPanel } from "../prototype/designer-panel/PrototypeDesignerPanel";
2538
+
2539
+ const showPrototypeDesignerPanel = import.meta.env.DEV;
2540
+
2541
+ export const App = () => {
226
2542
  return (
227
- ${Pe}
2543
+ <>
2544
+ ${Ke}
2545
+ {showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
2546
+ </>
228
2547
  );
229
2548
  };
230
- `,yt=`import { type ClassValue, clsx } from "clsx";
2549
+ `,no=`import { type ClassValue, clsx } from "clsx";
231
2550
  import { twMerge } from "tailwind-merge";
232
2551
 
233
2552
  export function cn(...inputs: ClassValue[]) {
234
2553
  return twMerge(clsx(inputs));
235
2554
  }
236
- `,ht=`node_modules/
2555
+ `,ro=`node_modules/
237
2556
  dist/
238
2557
  *.log
239
2558
  .env
240
- `,wt=e=>`# ${e.projectName}
2559
+ `,io=e=>`# ${e.projectName}
241
2560
 
242
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.
243
2562
 
244
2563
  \`\`\`bash
245
2564
  pnpm dev # desarrollo
2565
+ pnpm check:prototype # validar el contrato de datos del prototipo
246
2566
  npx create-lexy view button # ver un componente antes de instalarlo
247
2567
  npx create-lexy add button # instalarlo (local y editable)
248
2568
  npx create-lexy doctor # divergencia con el registry
249
2569
  \`\`\`
250
- `,xt=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":`${M}@latest/r/{name}.json`}},null,2)+`
251
- `},bt=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:M},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"},installed:{}}},Ee=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],r=[];for(let c of o)try{await k.access(I.join(t,c)),r.push(c)}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 c of tt[e.architecture])await k.mkdir(I.join(t,c),{recursive:!0});let n=(c,m)=>k.writeFile(I.join(t,c),m);await n("package.json",rt(e)),await n("vite.config.ts",nt),await n("tsconfig.json",lt),await n("tsconfig.app.json",pt),await n("tsconfig.node.json",dt),await n("eslint.config.js",st),await n(".prettierrc",it),await n(".prettierignore",at),await k.mkdir(I.join(t,".github/workflows"),{recursive:!0}),await n(".github/workflows/ci.yml",ct),await n("index.html",mt(e)),await n(".gitignore",ht),await n("README.md",wt(e)),await n("src/index.css",ut),await n("src/main.tsx",ft(e));let s=e.architecture==="feature"?"src/app":"src/views";await n(`${s}/App.tsx`,gt(e));let i=bt(e);await n(".lexy",JSON.stringify(i,null,2)+`
252
- `),await n("components.json",xt(e)),await k.copyFile(he,I.join(t,"src/lexy-theme.css")),await k.mkdir(I.join(t,"public/fonts"),{recursive:!0}),await k.cp(we,I.join(t,"public/fonts"),{recursive:!0});let l=I.join(t,i.paths.lib,"utils");await k.mkdir(l,{recursive:!0}),await k.writeFile(I.join(l,"cn.ts"),yt)};var Pt=["feature","layer"],Et=["cliente","crm","mixto"],kt=e=>new Promise((t,o)=>{vt("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}`)))}),ke=async(e,t)=>{let o;!!(e&&t.type)?(Pt.includes(t.type)||(console.error(v.red(`Error: la arquitectura "${t.type}" no es v\xE1lida. Usa "feature" o "layer".`)),process.exit(1)),t.world&&!Et.includes(t.world)&&(console.error(v.red(`Error: el mundo "${t.world}" no es v\xE1lido. Usa "cliente", "crm" o "mixto".`)),process.exit(1)),t.world||console.warn(v.yellow('Aviso: sin --world; se asume "mixto". ')+v.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(v.blue("Modo automatizado detectado. Saltando interfaz interactiva..."))):o=await Ce(e),jt(`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}
253
2573
  Arquitectura: ${o.architecture}
254
- Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let n=Ct();n.start("Construyendo tu proyecto...");let s=process.cwd();try{await Ee(o,s)}catch(i){throw n.stop(v.red("No se pudo crear el proyecto.")),i}t.install!==!1&&(n.message("Instalando dependencias base..."),await kt(s)),n.stop("\xA1Proyecto construido con \xE9xito!"),$t(v.green(`Todo listo en el directorio actual
255
-
256
- `)+v.dim(`Siguientes pasos:
257
- `)+v.cyan(` pnpm dev
258
- `)+v.cyan(" npx create-lexy add button"))};import It from"fs/promises";import Lt from"path";import F from"picocolors";import oe from"picocolors";var V=(e,t)=>{let o=e.split(`
259
- `),r=t.split(`
260
- `),n=o.length,s=r.length,i=Array.from({length:n+1},()=>new Array(s+1).fill(0));for(let d=n-1;d>=0;d--)for(let u=s-1;u>=0;u--)i[d][u]=o[d]===r[u]?i[d+1][u+1]+1:Math.max(i[d+1][u],i[d][u+1]);let l=[],c=0,m=0;for(;c<n&&m<s;)o[c]===r[m]?(l.push({type:"same",text:o[c]}),c++,m++):i[c+1][m]>=i[c][m+1]?l.push({type:"del",text:o[c++]}):l.push({type:"add",text:r[m++]});for(;c<n;)l.push({type:"del",text:o[c++]});for(;m<s;)l.push({type:"add",text:r[m++]});return l},W=e=>e.some(t=>t.type!=="same"),Ie=(e,t=3)=>{if(!W(e))return null;let o=new Array(e.length).fill(!1);e.forEach((s,i)=>{if(s.type!=="same")for(let l=Math.max(0,i-t);l<=Math.min(e.length-1,i+t);l++)o[l]=!0});let r=[],n=!1;for(let s=0;s<e.length;s++){if(!o[s]){n||(r.push(oe.dim(" \u22EF")),n=!0);continue}n=!1;let i=e[s];i.type==="same"?r.push(` ${i.text}`):i.type==="del"?r.push(oe.red(`- ${i.text}`)):r.push(oe.green(`+ ${i.text}`))}return r.join(`
261
- `)};var Le=async e=>{let t=process.cwd(),o=await S(t),r=e.toLowerCase(),{item:n,version:s}=await w(r),i=o.installed[r];console.log(F.bold(`
262
- ${n.title??r}`)+F.dim(` \xB7 instalado con registry@${i??"?"} \xB7 vigente registry@${s}
263
- `));let l=!1;for(let c of P(n,o)){let m=await It.readFile(Lt.join(t,c.relPath),"utf-8").catch(()=>null);if(m===null){console.log(F.red(`\u2717 ${c.relPath} no existe localmente`)+F.dim(` \u2014 cr\xE9alo con \`create-lexy add ${r}\``)),l=!0;continue}let d=Ie(V(m,c.content));d===null?console.log(F.green(`\u2713 ${c.relPath}`)+F.dim(" sin diferencias")):(l=!0,console.log(F.yellow(`\xB1 ${c.relPath}`)+F.dim(" (\u2212 local \xB7 + registry)")),console.log(d))}l&&(process.exitCode=1)};import D from"fs/promises";import O from"path";import j from"picocolors";var Te=async e=>{let t=process.cwd(),o=0,r=f=>console.log(j.green(" \u2713 ")+f),n=f=>console.log(j.yellow(" \u2022 ")+f),s=(f,a)=>{o=1,console.log(j.red(" \u2717 ")+f),a&&console.log(j.dim(` \u2192 ${a}`))};console.log(j.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(`
264
2584
  Diagn\xF3stico del proyecto Lexy
265
- `));let i;try{i=await S(t),r(`.lexy v\xE1lido (arquitectura ${i.architecture}${i.world?`, mundo ${i.world}`:""})`)}catch(f){return s(f.message),1}await D.access(O.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?r("theme presente (src/lexy-theme.css)"):s("falta src/lexy-theme.css","re-cr\xE9alo desde un proyecto nuevo o copia el theme del registry");let c=O.join(i.paths.lib,"utils/cn.ts");await D.access(O.join(t,c)).then(()=>!0,()=>!1)?r(`helper cn presente (${c})`):s(`falta el helper cn (${c})`);let d=O.join(t,"ai/lexy-ai-manifest.json"),u=await D.readFile(d,"utf-8").catch(()=>null);if(u===null)console.log(j.dim(" \xB7 capa de IA no instalada (npx @lexydesign/ai install) \u2014 opcional"));else try{let f=JSON.parse(u),a=f.files??[],p=[];for(let g of a)await D.access(O.join(t,g)).then(()=>!0,()=>!1)||p.push(g);p.length>0?s(`capa de IA ${f.aiVersion??"?"} incompleta \u2014 faltan ${p.length} archivo(s) (${p.slice(0,3).join(", ")}${p.length>3?", \u2026":""})`,"npx @lexydesign/ai install"):r(`capa de IA ${f.aiVersion??"?"} completa (${a.length} archivos)`)}catch{s("ai/lexy-ai-manifest.json no es JSON v\xE1lido","regenera con `npx @lexydesign/ai install`")}let R=Object.entries(i.installed);if(R.length===0)console.log(j.dim(`
266
- Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let f=await K();console.log(j.bold(`
267
- Componentes instalados (registry vigente: ${f})
268
- `));for(let[a,p]of R){let g;try{g=await w(a)}catch(y){s(`${a}: ${y.message}`);continue}let b=P(g.item,i),T=[],H=!1;for(let y of b){let C=await D.readFile(O.join(t,y.relPath),"utf-8").catch(()=>null);C===null?T.push(y.relPath):W(V(C,y.content))&&(H=!0)}if(T.length>0){s(`${a}: archivos faltantes (${T.join(", ")})`,`create-lexy add ${a}`);continue}if(!H){r(`${a} al d\xEDa (${g.version})`);continue}let _=!0;if(p&&p!==g.version)try{let y=await w(a,p),C=P(y.item,i);_=!1;for(let le of C){let pe=await D.readFile(O.join(t,le.relPath),"utf-8").catch(()=>null);(pe===null||W(V(pe,le.content)))&&(_=!0)}}catch{}_?e.strict?s(`${a}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${a}`):n(`${a}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${a}\`)`):s(`${a}: desactualizado (instalado con ${p}, vigente ${g.version})`,`create-lexy diff ${a} \xB7 create-lexy add ${a} --overwrite`)}}return console.log(o===0?j.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(`
269
2589
  Todo en orden.
270
- `):j.red(`
2590
+ `):C.red(`
271
2591
  Hay problemas que atender (exit 1).
272
- `)),o};import re from"fs/promises";import Se from"path";import{note as Tt,outro as St}from"@clack/prompts";import ne from"picocolors";var Y="LEXY-KICKOFF.md",Ft=`# 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
273
2593
 
274
2594
  > **Para el agente (Claude Code):** este archivo es tu guion. La persona que
275
2595
  > tienes enfrente es **dise\xF1adora, no programadora**: no le preguntes por
@@ -291,8 +2611,20 @@ hazlas de a poco, repregunta y resume lo que entendiste. Cubre:
291
2611
  dashboard, listados, vistas de detalle)?
292
2612
  - **Pantallas y flujos clave** \u2014 \xBFqu\xE9 vistas importan y qu\xE9 hace la persona en
293
2613
  cada una? \xBFC\xF3mo se mueve entre ellas?
294
- - **Datos y referencias** \u2014 \xBFqu\xE9 datos maneja? \xBFHay un Figma, capturas o un
295
- producto de referencia que mande sobre composiciones gen\xE9ricas?
2614
+ - **Datos de la experiencia** \u2014 \xBFqu\xE9 objetos principales existen? \xBFQu\xE9 necesita
2615
+ ver o modificar la persona? \xBFQu\xE9 relaciones necesita entender? \xBFQu\xE9 estados
2616
+ cambian lo que aparece en pantalla? \xBFQu\xE9 supuestos todav\xEDa debe validar TI?
2617
+ - **Cargas de datos** \u2014 \xBFqu\xE9 informaci\xF3n debe llegar desde backend para cada
2618
+ pantalla? \xBFQu\xE9 significa que una carga venga vac\xEDa? \xBFQu\xE9 errores o latencias
2619
+ vale la pena poder probar?
2620
+ - **Eventos publicados** \u2014 \xBFqu\xE9 escrituras hace la interfaz? \xBFQu\xE9 hecho ocurri\xF3
2621
+ cuando se publican? \xBFQu\xE9 entidad cambia visualmente despu\xE9s de ver "Evento
2622
+ publicado"? \xBFQu\xE9 debe mantenerse intacto si falla?
2623
+ - **Datos mock y escenarios** \u2014 \xBFqu\xE9 situaciones reales debe representar el
2624
+ prototipo? \xBFQu\xE9 casos l\xEDmite importan? \xBFQu\xE9 datos deben verse chilenos? \xBFQu\xE9
2625
+ campos son sensibles aunque sean sint\xE9ticos?
2626
+ - **Referencias** \u2014 \xBFhay un Figma, capturas o un producto de referencia que
2627
+ mande sobre composiciones gen\xE9ricas?
296
2628
 
297
2629
  ## 2. Traducir las respuestas a flags (no se lo muestres a la persona)
298
2630
 
@@ -318,26 +2650,45 @@ npx @lexydesign/ai install
318
2650
 
319
2651
  ## 4. Volcar la entrevista en el brief vivo
320
2652
 
321
- Abre \`ai/PROJECT-CONTEXT.md\` (lo cre\xF3 \`@lexydesign/ai install\`) y compl\xE9talo
322
- con lo que recopilaste: **qu\xE9 se construye**, **para qui\xE9n**, **pantallas y
323
- flujos clave**, **referencias** y **decisiones/restricciones** ya tomadas. Ese
324
- archivo es el plan inicial del proyecto y la memoria de las pr\xF3ximas sesiones.
2653
+ 1. Abre \`ai/PROJECT-CONTEXT.md\` (lo cre\xF3 \`@lexydesign/ai install\`) y
2654
+ compl\xE9talo con lo que recopilaste: **qu\xE9 se construye**, **para qui\xE9n**,
2655
+ **pantallas y flujos clave**, un resumen de **datos principales**,
2656
+ **referencias** y **decisiones/restricciones** ya tomadas.
2657
+ 2. Traduce los objetos, datos, relaciones, estados y supuestos conversados a
2658
+ \`src/prototype/data-contract/prototype-data-contract.ts\`. No inventes
2659
+ backend: lo que nazca desde usabilidad queda como
2660
+ \`origin: "generatedByUsability"\` y
2661
+ \`technicalValidation.status: "pendingTi"\`.
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
2670
+ empezar a implementar pantallas.
2671
+
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.
325
2676
 
326
2677
  ## 5. Cerrar
327
2678
 
328
- 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
329
2680
  persona que su proyecto est\xE1 listo y prop\xF3n el primer paso concreto
330
2681
  (p. ej. "\xBFDise\xF1amos la pantalla de inicio?").
331
- `,Fe=async e=>{let t=process.cwd();for(let n of["package.json",".lexy"])if(await re.access(Se.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=Se.join(t,Y);if(await re.access(o).then(()=>!0,()=>!1)&&!e.force)throw new Error(`${Y} ya existe. Usa --force para regenerarlo.`);await re.writeFile(o,Ft),Tt(`Se cre\xF3 ${ne.cyan(Y)} en esta carpeta.
332
-
333
- Abre la carpeta con Claude Code y di: `+ne.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
334
- El agente te entrevistar\xE1 y montar\xE1 el proyecto por ti.`,"Kickoff listo"),St(ne.green("Sin flags, sin prompts t\xE9cnicos \u2014 solo conversa con el agente."))};import se from"fs/promises";import X from"path";import $ from"picocolors";var ae="@lexydesign/kit",Re=`https://cdn.jsdelivr.net/npm/${ae}`,Oe=8e3,ie=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Oe)});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())},Rt=async()=>{try{let t=await fetch(`https://registry.npmjs.org/${ae}/latest`,{signal:AbortSignal.timeout(Oe)});if(t.ok)return(await t.json()).version}catch{}return JSON.parse((await ie(`${Re}@latest/package.json`)).toString("utf-8")).version},Ae=async e=>{let t=process.cwd(),o=X.resolve(t,e.dir??"lexy-kit"),r=X.relative(t,o)||".";if(await se.access(o).then(()=>!0).catch(()=>!1)&&!e.force)throw new Error(`Ya existe ${$.bold(r)}. Usa ${$.cyan("--force")} para sobrescribir o ${$.cyan("--dir <ruta>")} para otra carpeta.`);let s=await Rt();console.log($.dim(`
335
- Trayendo ${ae}@${s} \u2192 ${r}/
336
- `));let i=`${Re}@${s}/kit`,l=JSON.parse((await ie(`${i}/manifest.json`)).toString("utf-8")),c=["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",...l.guides?.pautas??[],...l.components.map(d=>d.file),...l.blocks.map(d=>d.file)],m=0;for(let d of c)try{let u=await ie(`${i}/${d}`),R=X.join(o,d);await se.mkdir(X.dirname(R),{recursive:!0}),await se.writeFile(R,u),m+=1}catch(u){console.warn($.yellow(`\u26A0 no se pudo traer ${d}`)+$.dim(` (${u.message})`))}console.log($.green("\u2713 Kit listo")+$.dim(` \u2014 ${m} archivo(s), ${l.components.length} componente(s) + ${l.blocks.length} block(s)
337
- `)),console.log(` Abre ${$.bold(`${r}/index.html`)} en el navegador.`),console.log($.dim(` El cat\xE1logo m\xE1quina-legible est\xE1 en ${r}/manifest.json.
338
- `))};import Ot from"fs/promises";import ce from"path";import x from"picocolors";var z=e=>console.log(x.bold(x.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(`
339
2690
  \u2500\u2500 ${e} \u2500\u2500
340
- `))),Ne=async(e,t)=>{if(t.list||!e){let{index:i,version:l,source:c}=await ye();z(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${l}${c==="bundle"?" (bundle local)":""}`);for(let m of i.items)console.log(` ${x.bold(m.name.padEnd(20))} ${x.dim(m.description??"")}`);console.log(x.dim(`
341
- ${i.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let i=process.cwd(),l=await S(i),c=l.installed[o];if(!c){console.error(x.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:m}=await w(o,c).catch(()=>w(o));z(`${m.title??o} \xB7 copia local (instalado con registry@${c})`);for(let d of P(m,l)){let u=await Ot.readFile(ce.join(i,d.relPath),"utf-8").catch(()=>null);z(u===null?`${d.relPath} (FALTANTE)`:d.relPath),u!==null&&console.log(u)}return}let{item:r,version:n,source:s}=await w(o);z(`${r.title??r.name} \xB7 @lexydesign/registry@${n}${s==="bundle"?" (bundle local)":""}`),r.description&&console.log(r.description+`
342
- `),console.log(x.dim("tipo: ")+r.type),console.log(x.dim("deps npm: ")+((r.dependencies??[]).join(", ")||"\u2014")),console.log(x.dim("deps registry: ")+((r.registryDependencies??[]).join(", ")||"\u2014")),console.log(x.dim("archivos: ")+r.files.map(i=>ce.posix.basename(i.path)).join(", ")),console.log(x.dim("instalar: ")+`create-lexy add ${r.name}`);for(let i of r.files)typeof i.content=="string"&&(z(ce.posix.basename(i.path)),console.log(i.content))};var _t=Me.dirname(Nt(import.meta.url)),qt=JSON.parse(At(Me.join(_t,"../package.json"),"utf-8")),L=new Mt,A=e=>async()=>{try{await e()}catch(t){console.error(Dt.red(`
343
- Error: `)+t.message),process.exit(1)}};L.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(qt.version);L.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)=>A(()=>ke(e,t))());L.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=>A(()=>Fe(e))());L.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)=>A(()=>be(e,t))());L.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)=>A(()=>Ne(e,t))());L.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=>A(()=>Ae(e))());L.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>A(()=>Le(e))());L.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=>A(async()=>{process.exitCode=await Te(e)})());L.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);