create-lexy 0.5.0 → 0.6.0

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 (2) hide show
  1. package/dist/index.js +2488 -61
  2. package/package.json +13 -3
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as Et}from"fs";import ke from"path";import{fileURLToPath as It}from"url";import{Command as Lt}from"commander";import kt from"picocolors";import{spawn as De}from"child_process";import K from"fs/promises";import B from"path";import{confirm as Me,isCancel as qe,spinner as fe}from"@clack/prompts";import h from"picocolors";import Y from"path";var Fe=(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/'),Te=[["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 n=Te.find(([s])=>o.path.startsWith(s));return{relPath:n?Y.posix.join(n[1](t),o.path.slice(n[0].length)):Y.posix.join(t.paths.components,Y.posix.basename(o.path)),content:Fe(o.content,t)}});import ie from"fs/promises";import Se from"path";var ae=e=>Se.join(e,".lexy"),k=async e=>{let t;try{t=await ie.readFile(ae(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},ce=async(e,t)=>{await ie.writeFile(ae(e),JSON.stringify(t,null,2)+`
3
- `)};import M from"fs/promises";import Re from"os";import $ from"path";import{fileURLToPath as Oe}from"url";var le="@lexydesign/registry",O=`https://cdn.jsdelivr.net/npm/${le}`,Ae=8e3,H=$.resolve($.dirname(Oe(import.meta.url)),"../assets"),Ne=$.join(Re.homedir(),".cache","create-lexy","registry"),U=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Ae)});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 M.readFile($.join(H,"r",e),"utf-8"))}catch{return null}},X=async()=>{try{return(await M.readFile($.join(H,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},R=null,J=async()=>{if(R)return R;try{R=(await U(`https://registry.npmjs.org/${le}/latest`)).version}catch{try{R=(await U(`${O}@latest/package.json`)).version}catch{R=await X()}}return R},x=async(e,t)=>{let o=e.replace(/^@lexy\//,""),n=t??await J(),r=$.join(Ne,n,`${o}.json`);try{return{item:JSON.parse(await M.readFile(r,"utf-8")),version:n,source:"cache"}}catch{}try{let s=await U(`${O}@${n}/r/${o}.json`);return await M.mkdir($.dirname(r),{recursive:!0}),await M.writeFile(r,JSON.stringify(s)),{item:s,version:n,source:"cdn"}}catch(s){if(s.status===404)throw new Error(`El componente "${o}" no existe en el registry (versi\xF3n ${n}). Mira el cat\xE1logo con \`create-lexy view --list\`.`);let i=await pe(`${o}.json`);if(i)return{item:i,version:await X(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${o}" tampoco est\xE1 en el bundle local del CLI.`)}},de=async()=>{try{let e=await J();return{index:await U(`${O}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await pe("registry.json");if(e)return{index:e,version:await X(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},me=$.join(H,"theme/lexy-theme.css"),ue=$.join(H,"fonts");var _e=(e,t)=>new Promise((o,n)=>{De("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",s=>s===0?o():n(new Error(`pnpm add fall\xF3 con c\xF3digo ${s}`)))}),ze=async e=>{try{return await K.readFile(e,"utf-8")}catch{return null}},ge=async(e,t)=>{let o=process.cwd(),n=await k(o),r=fe();r.start("Resolviendo componentes en el registry...");let s=new Map,i=async a=>{let l=a.toLowerCase().replace(/^@lexy\//,"");if(s.has(l))return;let f=await x(l);s.set(l,f);for(let b of f.item.registryDependencies??[])await i(b)};try{for(let a of e)await i(a)}catch(a){throw r.stop(h.red("No se pudo resolver el pedido.")),a}let p=new Map;for(let[a,{item:l}]of s){let f=[];for(let b of P(l,n)){let L=await ze(B.join(o,b.relPath));f.push({...b,status:L===null?"new":L===b.content?"same":"differs"})}p.set(a,f)}let c=Array.from(p.values()).flat().filter(a=>a.status==="differs");if(r.stop(`Resueltos: ${Array.from(s.keys()).join(", ")}`),c.length>0&&!t.overwrite){let a=c.map(l=>` ${l.relPath}`).join(`
4
- `);if(process.stdout.isTTY&&process.stdin.isTTY){let l=await Me({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
- ${a}
6
- \xBFContinuar?`});if(qe(l)||l!==!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,l]of p){let{item:f,version:b,source:L}=s.get(a);for(let y of l){if(y.status==="same")continue;let C=B.join(o,y.relPath);await K.mkdir(B.dirname(C),{recursive:!0}),await K.writeFile(C,y.content)}n.installed[f.name]=b;let V=e.map(y=>y.toLowerCase()).includes(a)?"":h.dim(" (dependencia)"),D=L==="bundle"?h.yellow(" [bundle local \u2014 sin red]"):"";console.log(h.green(`${h.bold(f.title??f.name)} instalado`)+V+h.cyan(` \u2192 ${n.paths.components}/`)+D);for(let y of l){let C=y.status==="same"?h.dim(" (sin cambios)"):"";console.log(h.dim(` ${y.relPath}${C}`))}}await ce(o,n);let d=new Set;for(let{item:a}of s.values())for(let l of a.dependencies??[])d.add(l);let m=JSON.parse(await K.readFile(B.join(o,"package.json"),"utf-8")),g={...m.dependencies,...m.devDependencies},z=a=>{let l=a.lastIndexOf("@");return l>0?a.slice(0,l):a},u=Array.from(d).filter(a=>!g[z(a)]);if(u.length>0)if(t.install===!1)console.log(h.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${u.join(", ")}`));else{let a=fe();a.start(`Instalando dependencias: ${u.join(", ")}...`),await _e(u,o),a.stop("Dependencias instaladas.")}};import{spawn as ut}from"child_process";import{note as ft,outro as gt,spinner as yt}from"@clack/prompts";import v from"picocolors";import{cancel as Ve,intro as Ue,isCancel as He,select as he,text as Je}from"@clack/prompts";import xe from"picocolors";var Q=[{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"}],ye=e=>Q.find(t=>t.value===e).architecture;var Be=`
2
+ import{readFileSync as To}from"fs";import tt from"path";import{fileURLToPath as Mo}from"url";import{Command as Lo}from"commander";import zo from"picocolors";import{spawn as lt}from"child_process";import W from"fs/promises";import Z from"path";import{confirm as dt,isCancel as pt,spinner as je}from"@clack/prompts";import S from"picocolors";import te from"path";var ot=(e,t)=>t.architecture!=="feature"?e:e.replace(/from ["']@\/lib\/utils\/cn["']/g,'from "@/shared/lib/utils/cn"').replace(/from ["']@\/components\/base\//g,'from "@/shared/components/base/'),nt=[["components/base/",e=>e.paths.components],["views/",e=>e.paths.views],["hooks/",e=>e.paths.hooks],["lib/",e=>e.paths.lib]],C=(e,t)=>e.files.filter(o=>typeof o.content=="string").map(o=>{let r=nt.find(([i])=>o.path.startsWith(i));return{relPath:r?te.posix.join(r[1](t),o.path.slice(r[0].length)):te.posix.join(t.paths.components,te.posix.basename(o.path)),content:ot(o.content,t)}});import xe from"fs/promises";import at from"path";var be=e=>at.join(e,".lexy"),L=async e=>{let t;try{t=await xe.readFile(be(e),"utf-8")}catch{throw new Error("No se encontr\xF3 el archivo .lexy. \xBFEst\xE1s en la ra\xEDz de un proyecto Lexy? (cr\xE9alo con `npx create-lexy create`)")}let o=JSON.parse(t);if(!o.paths?.components)throw new Error("El .lexy no tiene paths.components \u2014 archivo corrupto o de una versi\xF3n incompatible.");return o.installed??={},o},Ie=async(e,t)=>{await xe.writeFile(be(e),JSON.stringify(t,null,2)+`
3
+ `)};import N from"fs/promises";import rt from"os";import j from"path";import{fileURLToPath as it}from"url";var Ee="@lexydesign/registry",F=`https://cdn.jsdelivr.net/npm/${Ee}`,st=8e3,J=j.resolve(j.dirname(it(import.meta.url)),"../assets"),ct=j.join(rt.homedir(),".cache","create-lexy","registry"),B=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(st)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return await t.json()},ke=async e=>{try{return JSON.parse(await N.readFile(j.join(J,"r",e),"utf-8"))}catch{return null}},oe=async()=>{try{return(await N.readFile(j.join(J,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},_=null,G=async()=>{if(_)return _;try{_=(await B(`https://registry.npmjs.org/${Ee}/latest`)).version}catch{try{_=(await B(`${F}@latest/package.json`)).version}catch{_=await oe()}}return _},x=async(e,t)=>{let o=e.replace(/^@lexy\//,""),r=t??await G(),a=j.join(ct,r,`${o}.json`);try{return{item:JSON.parse(await N.readFile(a,"utf-8")),version:r,source:"cache"}}catch{}try{let i=await B(`${F}@${r}/r/${o}.json`);return await N.mkdir(j.dirname(a),{recursive:!0}),await N.writeFile(a,JSON.stringify(i)),{item:i,version:r,source:"cdn"}}catch(i){if(i.status===404)throw new Error(`El componente "${o}" no existe en el registry (versi\xF3n ${r}). Mira el cat\xE1logo con \`create-lexy view --list\`.`);let n=await ke(`${o}.json`);if(n)return{item:n,version:await oe(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${o}" tampoco est\xE1 en el bundle local del CLI.`)}},we=async()=>{try{let e=await G();return{index:await B(`${F}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await ke("registry.json");if(e)return{index:e,version:await oe(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},Pe=j.join(J,"theme/lexy-theme.css"),Ce=j.join(J,"fonts");var mt=(e,t)=>new Promise((o,r)=>{lt("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",i=>i===0?o():r(new Error(`pnpm add fall\xF3 con c\xF3digo ${i}`)))}),ut=async e=>{try{return await W.readFile(e,"utf-8")}catch{return null}},Re=async(e,t)=>{let o=process.cwd(),r=await L(o),a=je();a.start("Resolviendo componentes en el registry...");let i=new Map,n=async m=>{let h=m.toLowerCase().replace(/^@lexy\//,"");if(i.has(h))return;let s=await x(h);i.set(h,s);for(let c of s.item.registryDependencies??[])await n(c)};try{for(let m of e)await n(m)}catch(m){throw a.stop(S.red("No se pudo resolver el pedido.")),m}let p=new Map;for(let[m,{item:h}]of i){let s=[];for(let c of C(h,r)){let g=await ut(Z.join(o,c.relPath));s.push({...c,status:g===null?"new":g===c.content?"same":"differs"})}p.set(m,s)}let l=Array.from(p.values()).flat().filter(m=>m.status==="differs");if(a.stop(`Resueltos: ${Array.from(i.keys()).join(", ")}`),l.length>0&&!t.overwrite){let m=l.map(h=>` ${h.relPath}`).join(`
4
+ `);if(process.stdout.isTTY&&process.stdin.isTTY){let h=await dt({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
+ ${m}
6
+ \xBFContinuar?`});if(pt(h)||h!==!0){console.log(S.yellow("Operaci\xF3n cancelada \u2014 nada fue modificado.")),process.exitCode=1;return}}else{console.error(S.red(`Estos archivos existen con cambios locales:
7
+ `)+m+S.dim(`
8
+ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[m,h]of p){let{item:s,version:c,source:g}=i.get(m);for(let v of h){if(v.status==="same")continue;let O=Z.join(o,v.relPath);await W.mkdir(Z.dirname(O),{recursive:!0}),await W.writeFile(O,v.content)}r.installed[s.name]=c;let I=e.map(v=>v.toLowerCase()).includes(m)?"":S.dim(" (dependencia)"),q=g==="bundle"?S.yellow(" [bundle local \u2014 sin red]"):"";console.log(S.green(`${S.bold(s.title??s.name)} instalado`)+I+S.cyan(` \u2192 ${r.paths.components}/`)+q);for(let v of h){let O=v.status==="same"?S.dim(" (sin cambios)"):"";console.log(S.dim(` ${v.relPath}${O}`))}}await Ie(o,r);let y=new Set;for(let{item:m}of i.values())for(let h of m.dependencies??[])y.add(h);let u=JSON.parse(await W.readFile(Z.join(o,"package.json"),"utf-8")),d={...u.dependencies,...u.devDependencies},P=m=>{let h=m.lastIndexOf("@");return h>0?m.slice(0,h):m},V=Array.from(y).filter(m=>!d[P(m)]);if(V.length>0)if(t.install===!1)console.log(S.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${V.join(", ")}`));else{let m=je();m.start(`Instalando dependencias: ${V.join(", ")}...`),await mt(V,o),m.stop("Dependencias instaladas.")}};import{spawn as ho}from"child_process";import{note as go,outro as vo,spinner as So}from"@clack/prompts";import E from"picocolors";import{cancel as ft,intro as yt,isCancel as ht,select as Me,text as gt}from"@clack/prompts";import Le from"picocolors";var ne=[{value:"screen",label:"Una pantalla o flujo corto",hint:"landing, formulario, demo \u2014 sin navegaci\xF3n entre secciones",architecture:"layer"},{value:"app",label:"Una app con secciones y navegaci\xF3n",hint:"dashboard, listados, detalle \u2014 varias vistas con router",architecture:"feature"}],Te=e=>ne.find(t=>t.value===e).architecture;var vt=`
9
9
  ###### ######
10
10
  ##::+#### ####+::##
11
11
  #-..:+################+:..:#
@@ -20,9 +20,2399 @@ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[a,l]
20
20
  ...........:====:...........
21
21
  ......................
22
22
  ..............
23
- `,Z=e=>{He(e)&&(Ve("Operaci\xF3n cancelada por el usuario."),process.exit(0))},we=async e=>{console.clear(),Ue(`${xe.cyan(Be)}
24
- ${xe.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let r=await Je({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."}});Z(r),t=r}let o=await he({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"});Z(o);let n=await he({message:"\xBFC\xF3mo es lo que vas a construir?",options:Q.map(r=>({label:r.label,value:r.value,hint:r.hint})),initialValue:"app"});return Z(n),{projectName:t,architecture:ye(n),world:o}};import E from"fs/promises";import I from"path";var Ke={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"]},Ge="10.33.3",We=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@${Ge}`,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
- `},Ye=`import path from "node:path";
23
+ `,ae=e=>{ht(e)&&(ft("Operaci\xF3n cancelada por el usuario."),process.exit(0))},ze=async e=>{console.clear(),yt(`${Le.cyan(vt)}
24
+ ${Le.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let a=await gt({message:"\xBFCu\xE1l es el nombre de tu proyecto?",placeholder:"lexy-app",validate:i=>{if(!i||i.trim()==="")return"El nombre es obligatorio.";if(!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(i))return"El nombre debe estar en min\xFAsculas, sin espacios ni caracteres especiales."}});ae(a),t=a}let o=await Me({message:"\xBFPara qui\xE9n es este proyecto?",options:[{label:"Clientes de Lexy",value:"cliente",hint:"Producto de cara al cliente final"},{label:"Equipo interno / CRM",value:"crm",hint:"Herramienta densa para el equipo Lexy"},{label:"Ambos o a\xFAn no lo s\xE9",value:"mixto",hint:"Se decide por pantalla m\xE1s adelante"}],initialValue:"mixto"});ae(o);let r=await Me({message:"\xBFC\xF3mo es lo que vas a construir?",options:ne.map(a=>({label:a.label,value:a.value,hint:a.hint})),initialValue:"app"});return ae(r),{projectName:t,architecture:Te(r),world:o}};import R from"fs/promises";import T from"path";import Ae from"fs/promises";import Oe from"path";var re="src/prototype/data-contract/prototype-data-contract.ts",St="src/prototype/data-contract/data-contract-schema.ts",xt="scripts/check-data-contract.ts",bt=String.raw`import { z } from "zod";
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
+ `,It=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
+ `,Et=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
+ `,De=async(e,t)=>{let o=async(r,a)=>{let i=Oe.join(e,r);await Ae.mkdir(Oe.dirname(i),{recursive:!0}),await Ae.writeFile(i,a)};await o(St,bt),await o(re,Et(t)),await o(xt,It)};import $e from"fs/promises";import Ve from"path";var ie="src/prototype/event-engine/prototype-event-engine-contract.ts",kt="src/prototype/event-engine/event-engine-contract-schema.ts",wt="src/prototype/event-engine/payload-schema.ts",se="src/prototype/event-engine/event-engine.ts",Pt="scripts/check-event-engine.ts",Ct=String.raw`import { z } from "zod";
513
+
514
+ const camelCaseIdSchema = z
515
+ .string()
516
+ .regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
517
+
518
+ const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
519
+
520
+ const technicalEventIdSchema = z
521
+ .string()
522
+ .regex(
523
+ /^[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9]+-[A-Za-z0-9]+_V[0-9]+$/,
524
+ "Debe usar el formato eventista Dominio_Subdominio-NombreEvento_V1.",
525
+ );
526
+
527
+ const dataReferenceSchema = z
528
+ .string()
529
+ .regex(
530
+ /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)?$/,
531
+ "Debe referenciar una entidad o campo como entidad o entidad.campo usando camelCase.",
532
+ );
533
+
534
+ const triggerSchema = z
535
+ .object({
536
+ id: camelCaseIdSchema.optional(),
537
+ label: nonEmptyTextSchema,
538
+ source: z.enum(["screen", "userAction", "system", "external", "unknown"]),
539
+ description: nonEmptyTextSchema.optional(),
540
+ })
541
+ .strict();
542
+
543
+ const schemaReferenceSchema = z
544
+ .object({
545
+ schemaId: camelCaseIdSchema,
546
+ })
547
+ .strict();
548
+
549
+ export const dataLoadSpecSchema = z
550
+ .object({
551
+ id: camelCaseIdSchema,
552
+ name: nonEmptyTextSchema,
553
+ productDescription: nonEmptyTextSchema,
554
+ trigger: triggerSchema,
555
+ paramsSchema: schemaReferenceSchema.optional(),
556
+ responseSchema: schemaReferenceSchema.optional(),
557
+ reads: z
558
+ .object({
559
+ entities: z.array(camelCaseIdSchema).optional(),
560
+ projections: z.array(camelCaseIdSchema).optional(),
561
+ fields: z.array(dataReferenceSchema).optional(),
562
+ })
563
+ .strict()
564
+ .optional(),
565
+ visibleResult: nonEmptyTextSchema,
566
+ pendingTi: nonEmptyTextSchema.optional(),
567
+ })
568
+ .strict();
569
+
570
+ export const publishedEventSpecSchema = z
571
+ .object({
572
+ id: camelCaseIdSchema,
573
+ name: nonEmptyTextSchema,
574
+ technicalId: technicalEventIdSchema,
575
+ productDescription: nonEmptyTextSchema,
576
+ trigger: triggerSchema,
577
+ payloadSchema: schemaReferenceSchema.optional(),
578
+ receiptSchema: schemaReferenceSchema.optional(),
579
+ writes: z
580
+ .object({
581
+ entities: z.array(camelCaseIdSchema).optional(),
582
+ fields: z.array(dataReferenceSchema).optional(),
583
+ })
584
+ .strict()
585
+ .optional(),
586
+ visibleResult: nonEmptyTextSchema,
587
+ pendingTi: nonEmptyTextSchema.optional(),
588
+ })
589
+ .strict();
590
+
591
+ export const prototypeEventEngineContractSchema = z
592
+ .object({
593
+ contractVersion: z.literal("1"),
594
+ dataLoads: z.record(camelCaseIdSchema, dataLoadSpecSchema),
595
+ events: z.record(camelCaseIdSchema, publishedEventSpecSchema),
596
+ })
597
+ .strict()
598
+ .superRefine((contract, context) => {
599
+ for (const [loadKey, load] of Object.entries(contract.dataLoads)) {
600
+ if (loadKey !== load.id) {
601
+ context.addIssue({
602
+ code: "custom",
603
+ path: ["dataLoads", loadKey, "id"],
604
+ message: 'El id "' + load.id + '" debe coincidir con la key "' + loadKey + '".',
605
+ });
606
+ }
607
+ }
608
+
609
+ const technicalIds = new Set<string>();
610
+ for (const [eventKey, event] of Object.entries(contract.events)) {
611
+ if (eventKey !== event.id) {
612
+ context.addIssue({
613
+ code: "custom",
614
+ path: ["events", eventKey, "id"],
615
+ message: 'El id "' + event.id + '" debe coincidir con la key "' + eventKey + '".',
616
+ });
617
+ }
618
+
619
+ if (technicalIds.has(event.technicalId)) {
620
+ context.addIssue({
621
+ code: "custom",
622
+ path: ["events", eventKey, "technicalId"],
623
+ message: 'El ID técnico "' + event.technicalId + '" está declarado más de una vez.',
624
+ });
625
+ }
626
+ technicalIds.add(event.technicalId);
627
+ }
628
+ });
629
+
630
+ export type PrototypeEventEngineContract = z.infer<typeof prototypeEventEngineContractSchema>;
631
+ export type DataLoadSpec = z.infer<typeof dataLoadSpecSchema>;
632
+ export type PublishedEventSpec = z.infer<typeof publishedEventSpecSchema>;
633
+
634
+ export type EventEngineValidationIssue = {
635
+ path: Array<string | number>;
636
+ message: string;
637
+ };
638
+
639
+ type DataContractLike = {
640
+ entities: Record<string, { fields: Record<string, unknown> }>;
641
+ projections: Record<string, unknown>;
642
+ };
643
+
644
+ type PayloadSchemasLike = Record<string, unknown>;
645
+
646
+ const hasOwn = (record: Record<string, unknown>, key: string) =>
647
+ Object.prototype.hasOwnProperty.call(record, key);
648
+
649
+ const validateDataReference = (
650
+ reference: string,
651
+ dataContract: DataContractLike,
652
+ ): string | null => {
653
+ const [entityId, fieldId] = reference.split(".");
654
+ const entity = dataContract.entities[entityId];
655
+ if (!entity) return 'La entidad "' + entityId + '" no existe en el contrato de datos.';
656
+ if (fieldId && !hasOwn(entity.fields, fieldId)) {
657
+ return 'El campo "' + reference + '" no existe en el contrato de datos.';
658
+ }
659
+ return null;
660
+ };
661
+
662
+ export const validateEventEngineReferences = (input: {
663
+ eventEngineContract: PrototypeEventEngineContract;
664
+ dataContract: DataContractLike;
665
+ payloadSchemas: PayloadSchemasLike;
666
+ }): EventEngineValidationIssue[] => {
667
+ const issues: EventEngineValidationIssue[] = [];
668
+ const { eventEngineContract, dataContract, payloadSchemas } = input;
669
+
670
+ for (const [loadId, load] of Object.entries(eventEngineContract.dataLoads)) {
671
+ for (const [schemaKey, schemaRef] of [
672
+ ["paramsSchema", load.paramsSchema],
673
+ ["responseSchema", load.responseSchema],
674
+ ] as const) {
675
+ if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
676
+ issues.push({
677
+ path: ["dataLoads", loadId, schemaKey, "schemaId"],
678
+ message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
679
+ });
680
+ }
681
+ }
682
+
683
+ for (const [index, entityId] of (load.reads?.entities ?? []).entries()) {
684
+ if (!dataContract.entities[entityId]) {
685
+ issues.push({
686
+ path: ["dataLoads", loadId, "reads", "entities", index],
687
+ message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
688
+ });
689
+ }
690
+ }
691
+ for (const [index, projectionId] of (load.reads?.projections ?? []).entries()) {
692
+ if (!dataContract.projections[projectionId]) {
693
+ issues.push({
694
+ path: ["dataLoads", loadId, "reads", "projections", index],
695
+ message: 'La proyección "' + projectionId + '" no existe en el contrato de datos.',
696
+ });
697
+ }
698
+ }
699
+ for (const [index, reference] of (load.reads?.fields ?? []).entries()) {
700
+ const message = validateDataReference(reference, dataContract);
701
+ if (message) {
702
+ issues.push({
703
+ path: ["dataLoads", loadId, "reads", "fields", index],
704
+ message,
705
+ });
706
+ }
707
+ }
708
+ }
709
+
710
+ for (const [eventId, event] of Object.entries(eventEngineContract.events)) {
711
+ for (const [schemaKey, schemaRef] of [
712
+ ["payloadSchema", event.payloadSchema],
713
+ ["receiptSchema", event.receiptSchema],
714
+ ] as const) {
715
+ if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
716
+ issues.push({
717
+ path: ["events", eventId, schemaKey, "schemaId"],
718
+ message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
719
+ });
720
+ }
721
+ }
722
+
723
+ for (const [index, entityId] of (event.writes?.entities ?? []).entries()) {
724
+ if (!dataContract.entities[entityId]) {
725
+ issues.push({
726
+ path: ["events", eventId, "writes", "entities", index],
727
+ message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
728
+ });
729
+ }
730
+ }
731
+ for (const [index, reference] of (event.writes?.fields ?? []).entries()) {
732
+ const message = validateDataReference(reference, dataContract);
733
+ if (message) {
734
+ issues.push({
735
+ path: ["events", eventId, "writes", "fields", index],
736
+ message,
737
+ });
738
+ }
739
+ }
740
+ }
741
+
742
+ return issues;
743
+ };
744
+
745
+ export function definePrototypeEventEngineContract(
746
+ contract: PrototypeEventEngineContract,
747
+ ): PrototypeEventEngineContract {
748
+ return prototypeEventEngineContractSchema.parse(contract);
749
+ }
750
+ `,jt=String.raw`import { z } from "zod";
751
+
752
+ /**
753
+ * Schemas runtime para params, payloads, responses y receipts del motor.
754
+ *
755
+ * Los contratos de Spec 2 referencian estas keys mediante schemaId. El agente
756
+ * agrega schemas específicos cuando declara cargas o eventos concretos.
757
+ */
758
+ export const unknownPayloadSchema = z.unknown();
759
+ export const emptyObjectSchema = z.object({}).strict();
760
+ export const unknownResponseSchema = z.unknown();
761
+ export const absentReceiptSchema = z.undefined().optional();
762
+
763
+ export const prototypePayloadSchemas = {
764
+ unknown: unknownPayloadSchema,
765
+ emptyObject: emptyObjectSchema,
766
+ unknownResponse: unknownResponseSchema,
767
+ absentReceipt: absentReceiptSchema,
768
+ } satisfies Record<string, z.ZodType>;
769
+
770
+ export type PrototypePayloadSchemaId = keyof typeof prototypePayloadSchemas;
771
+ `,Rt=String.raw`import { z } from "zod";
772
+
773
+ import { prototypeEventEngineContract } from "./prototype-event-engine-contract";
774
+ import { prototypePayloadSchemas } from "./payload-schema";
775
+ import type { PrototypeEventEngineContract } from "./event-engine-contract-schema";
776
+
777
+ export type DataLoadInput = {
778
+ loadId: string;
779
+ triggerId?: string;
780
+ params?: unknown;
781
+ signal?: AbortSignal;
782
+ };
783
+
784
+ export type PublishEventInput = {
785
+ eventId: string;
786
+ triggerId?: string;
787
+ payload?: unknown;
788
+ };
789
+
790
+ export type EventPublicationEnvelope = {
791
+ technicalId: string;
792
+ occurredAt: string;
793
+ publicationId: string;
794
+ payload: unknown;
795
+ };
796
+
797
+ export type PublishedEventResult = {
798
+ status: "published";
799
+ label: "Evento publicado";
800
+ eventId: string;
801
+ technicalId: string;
802
+ occurredAt: string;
803
+ publicationId: string;
804
+ receipt?: unknown;
805
+ };
806
+
807
+ export type PrototypeBackendExecutor = {
808
+ loadData(input: {
809
+ loadId: string;
810
+ triggerId?: string;
811
+ params: unknown;
812
+ signal?: AbortSignal;
813
+ }): Promise<unknown>;
814
+ publishEvent(input: {
815
+ eventId: string;
816
+ triggerId?: string;
817
+ envelope: EventPublicationEnvelope;
818
+ }): Promise<{ receipt?: unknown } | void>;
819
+ };
820
+
821
+ type PrototypeEventEngineOptions = {
822
+ contract?: PrototypeEventEngineContract;
823
+ payloadSchemas?: Record<string, z.ZodType>;
824
+ executor: PrototypeBackendExecutor;
825
+ clock?: () => Date;
826
+ publicationId?: () => string;
827
+ };
828
+
829
+ const createDefaultPublicationId = () =>
830
+ "pub_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 10);
831
+
832
+ const parseWithSchema = (schema: z.ZodType | undefined, value: unknown, label: string) => {
833
+ if (!schema) return value;
834
+ const result = schema.safeParse(value);
835
+ if (!result.success) {
836
+ const message = result.error.issues
837
+ .map((issue) => (issue.path.length > 0 ? issue.path.join(".") + ": " : "") + issue.message)
838
+ .join("; ");
839
+ throw new Error(label + " inválido: " + message);
840
+ }
841
+ return result.data;
842
+ };
843
+
844
+ export const createPrototypeEventEngine = (options: PrototypeEventEngineOptions) => {
845
+ const contract = options.contract ?? prototypeEventEngineContract;
846
+ const payloadSchemas: Record<string, z.ZodType> =
847
+ options.payloadSchemas ?? prototypePayloadSchemas;
848
+ const clock = options.clock ?? (() => new Date());
849
+ const publicationId = options.publicationId ?? createDefaultPublicationId;
850
+
851
+ return {
852
+ async loadData(input: DataLoadInput): Promise<unknown> {
853
+ const load = contract.dataLoads[input.loadId];
854
+ if (!load) throw new Error('La carga de datos "' + input.loadId + '" no está declarada.');
855
+
856
+ const params = parseWithSchema(
857
+ load.paramsSchema ? payloadSchemas[load.paramsSchema.schemaId] : undefined,
858
+ input.params,
859
+ 'Params de la carga "' + input.loadId + '"',
860
+ );
861
+
862
+ const response = await options.executor.loadData({
863
+ loadId: input.loadId,
864
+ triggerId: input.triggerId,
865
+ params,
866
+ signal: input.signal,
867
+ });
868
+
869
+ return parseWithSchema(
870
+ load.responseSchema ? payloadSchemas[load.responseSchema.schemaId] : undefined,
871
+ response,
872
+ 'Response de la carga "' + input.loadId + '"',
873
+ );
874
+ },
875
+
876
+ async publishEvent(input: PublishEventInput): Promise<PublishedEventResult> {
877
+ const event = contract.events[input.eventId];
878
+ if (!event) throw new Error('El evento publicado "' + input.eventId + '" no está declarado.');
879
+
880
+ const payload = parseWithSchema(
881
+ event.payloadSchema ? payloadSchemas[event.payloadSchema.schemaId] : undefined,
882
+ input.payload,
883
+ 'Payload del evento "' + input.eventId + '"',
884
+ );
885
+
886
+ const envelope: EventPublicationEnvelope = {
887
+ technicalId: event.technicalId,
888
+ occurredAt: clock().toISOString(),
889
+ publicationId: publicationId(),
890
+ payload,
891
+ };
892
+
893
+ const executorResult = await options.executor.publishEvent({
894
+ eventId: input.eventId,
895
+ triggerId: input.triggerId,
896
+ envelope,
897
+ });
898
+
899
+ const receipt = parseWithSchema(
900
+ event.receiptSchema ? payloadSchemas[event.receiptSchema.schemaId] : undefined,
901
+ executorResult?.receipt,
902
+ 'Receipt del evento "' + input.eventId + '"',
903
+ );
904
+
905
+ return {
906
+ status: "published",
907
+ label: "Evento publicado",
908
+ eventId: input.eventId,
909
+ technicalId: event.technicalId,
910
+ occurredAt: envelope.occurredAt,
911
+ publicationId: envelope.publicationId,
912
+ ...(receipt === undefined ? {} : { receipt }),
913
+ };
914
+ },
915
+ };
916
+ };
917
+ `,Tt=String.raw`import { ZodError } from "zod";
918
+
919
+ import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
920
+ import {
921
+ prototypeEventEngineContractSchema,
922
+ validateEventEngineReferences,
923
+ type EventEngineValidationIssue,
924
+ } from "../src/prototype/event-engine/event-engine-contract-schema";
925
+ import { prototypePayloadSchemas } from "../src/prototype/event-engine/payload-schema";
926
+
927
+ const formatPath = (path: Array<string | number>) =>
928
+ path.length === 0 ? "eventEngine" : path.map(String).join(".");
929
+
930
+ const printIssues = (issues: EventEngineValidationIssue[]) => {
931
+ console.error("✗ Motor de eventos inválido");
932
+ for (const issue of issues) {
933
+ console.error(" " + formatPath(issue.path));
934
+ console.error(" " + issue.message);
935
+ }
936
+ };
937
+
938
+ try {
939
+ const dataContractModule = await import("../src/prototype/data-contract/prototype-data-contract");
940
+ const eventEngineModule =
941
+ await import("../src/prototype/event-engine/prototype-event-engine-contract");
942
+
943
+ const dataContractResult = prototypeDataContractSchema.safeParse(
944
+ dataContractModule.prototypeDataContract,
945
+ );
946
+ const eventEngineResult = prototypeEventEngineContractSchema.safeParse(
947
+ eventEngineModule.prototypeEventEngineContract,
948
+ );
949
+
950
+ const issues: EventEngineValidationIssue[] = [];
951
+ if (!dataContractResult.success) {
952
+ for (const issue of dataContractResult.error.issues) {
953
+ issues.push({
954
+ path: ["dataContract", ...issue.path.map(String)],
955
+ message: issue.message,
956
+ });
957
+ }
958
+ }
959
+ if (!eventEngineResult.success) {
960
+ for (const issue of eventEngineResult.error.issues) {
961
+ issues.push({
962
+ path: issue.path.map(String),
963
+ message: issue.message,
964
+ });
965
+ }
966
+ }
967
+
968
+ if (dataContractResult.success && eventEngineResult.success) {
969
+ issues.push(
970
+ ...validateEventEngineReferences({
971
+ eventEngineContract: eventEngineResult.data,
972
+ dataContract: dataContractResult.data,
973
+ payloadSchemas: prototypePayloadSchemas,
974
+ }),
975
+ );
976
+ }
977
+
978
+ if (issues.length > 0) {
979
+ printIssues(issues);
980
+ process.exitCode = 1;
981
+ } else {
982
+ const contract = eventEngineResult.data!;
983
+ console.log("✓ Motor de eventos válido");
984
+ console.log(" " + Object.keys(contract.dataLoads).length + " cargas de datos");
985
+ console.log(" " + Object.keys(contract.events).length + " eventos publicados");
986
+ }
987
+ } catch (error) {
988
+ if (error instanceof ZodError) {
989
+ printIssues(
990
+ error.issues.map((issue) => ({ path: issue.path.map(String), message: issue.message })),
991
+ );
992
+ } else {
993
+ console.error("✗ No se pudo cargar el motor de eventos");
994
+ console.error(" " + (error instanceof Error ? error.message : String(error)));
995
+ }
996
+ process.exitCode = 1;
997
+ }
998
+ `,Mt=()=>`import { definePrototypeEventEngineContract } from "./event-engine-contract-schema";
999
+
1000
+ /**
1001
+ * Cat\xE1logo de comunicaciones externas del frontend.
1002
+ *
1003
+ * - dataLoads: lecturas remotas para alimentar la UI.
1004
+ * - events: escrituras publicadas como hechos Lexy.
1005
+ *
1006
+ * Las interacciones locales no se declaran ac\xE1.
1007
+ */
1008
+ export const prototypeEventEngineContract = definePrototypeEventEngineContract({
1009
+ contractVersion: "1",
1010
+ dataLoads: {},
1011
+ events: {},
1012
+ });
1013
+ `,_e=async e=>{let t=async(o,r)=>{let a=Ve.join(e,o);await $e.mkdir(Ve.dirname(a),{recursive:!0}),await $e.writeFile(a,r)};await t(kt,Ct),await t(ie,Mt()),await t(wt,jt),await t(se,Rt),await t(Pt,Tt)};import Fe from"fs/promises";import Ne from"path";var Lt="src/prototype/simulator/simulator-contract-schema.ts",ce="src/prototype/simulator/mock-generation-profile.ts",le="src/prototype/simulator/mock-scenarios.ts",zt="src/prototype/simulator/projection-resolvers.ts",At="src/prototype/simulator/data-load-handlers.ts",Ot="src/prototype/simulator/event-handlers.ts",Dt="src/prototype/simulator/mock-store.ts",$t="src/prototype/simulator/mock-backend-executor.ts",de="src/prototype/simulator/prototype-simulator.ts",Vt="scripts/check-simulator.ts",_t=String.raw`import { z } from "zod";
1014
+
1015
+ const camelCaseIdSchema = z
1016
+ .string()
1017
+ .regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
1018
+
1019
+ const dataReferenceSchema = z
1020
+ .string()
1021
+ .regex(
1022
+ /^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/,
1023
+ "Debe referenciar un campo como entidad.campo usando camelCase.",
1024
+ );
1025
+
1026
+ const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
1027
+
1028
+ export type JsonPrimitive = string | number | boolean | null;
1029
+ export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
1030
+ export type MockEntityRecord = Record<string, JsonValue>;
1031
+
1032
+ export const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
1033
+ z.union([
1034
+ z.string(),
1035
+ z.number(),
1036
+ z.boolean(),
1037
+ z.null(),
1038
+ z.array(jsonValueSchema),
1039
+ z.record(z.string(), jsonValueSchema),
1040
+ ]),
1041
+ );
1042
+
1043
+ export const mockEntityRecordSchema = z.record(z.string(), jsonValueSchema);
1044
+
1045
+ export const mockSemanticSchema = z.enum([
1046
+ "personName",
1047
+ "rutCl",
1048
+ "mobilePhoneCl",
1049
+ "landlinePhoneCl",
1050
+ "email",
1051
+ "clpAmount",
1052
+ "dateCl",
1053
+ "datetimeCl",
1054
+ "regionCl",
1055
+ "communeCl",
1056
+ "addressCl",
1057
+ "companyNameCl",
1058
+ "legalCaseReferenceCl",
1059
+ "freeText",
1060
+ "custom",
1061
+ ]);
1062
+
1063
+ export const fieldMockHintSchema = z
1064
+ .object({
1065
+ semantic: mockSemanticSchema,
1066
+ description: nonEmptyTextSchema.optional(),
1067
+ examples: z.array(nonEmptyTextSchema).optional(),
1068
+ constraints: z
1069
+ .object({
1070
+ min: z.number().optional(),
1071
+ max: z.number().optional(),
1072
+ pattern: nonEmptyTextSchema.optional(),
1073
+ allowedValues: z.array(nonEmptyTextSchema).optional(),
1074
+ })
1075
+ .strict()
1076
+ .optional(),
1077
+ rutValidation: z.enum(["formatOnly", "validChecksum"]).optional(),
1078
+ })
1079
+ .strict();
1080
+
1081
+ export const mockGenerationProfileSchema = z
1082
+ .object({
1083
+ profileVersion: z.literal("1"),
1084
+ locale: z
1085
+ .object({
1086
+ language: z.literal("es"),
1087
+ locale: z.literal("es-CL"),
1088
+ country: z.literal("CL"),
1089
+ timezone: z.literal("America/Santiago"),
1090
+ currency: z.literal("CLP"),
1091
+ dateStorage: z.literal("iso8601"),
1092
+ dateDisplay: z.literal("dd-MM-yyyy"),
1093
+ timeDisplay: z.literal("24h"),
1094
+ })
1095
+ .strict(),
1096
+ safety: z
1097
+ .object({
1098
+ syntheticOnly: z.literal(true),
1099
+ allowProductionImports: z.literal(false),
1100
+ externalUse: z.literal(false),
1101
+ emailDomain: z.literal("example.com"),
1102
+ })
1103
+ .strict(),
1104
+ entityKeys: z.record(
1105
+ camelCaseIdSchema,
1106
+ z
1107
+ .object({
1108
+ field: dataReferenceSchema,
1109
+ })
1110
+ .strict(),
1111
+ ),
1112
+ relationBindings: z.record(
1113
+ camelCaseIdSchema,
1114
+ z
1115
+ .object({
1116
+ relation: camelCaseIdSchema,
1117
+ fromField: dataReferenceSchema,
1118
+ toField: dataReferenceSchema,
1119
+ })
1120
+ .strict(),
1121
+ ),
1122
+ fieldHints: z.record(dataReferenceSchema, fieldMockHintSchema),
1123
+ })
1124
+ .strict();
1125
+
1126
+ export const mockOperationErrorSchema = z
1127
+ .object({
1128
+ code: camelCaseIdSchema,
1129
+ message: nonEmptyTextSchema,
1130
+ recoverable: z.boolean(),
1131
+ })
1132
+ .strict();
1133
+
1134
+ export const mockDataLoadBehaviorSchema = z
1135
+ .object({
1136
+ latencyMs: z.number().int().min(0).optional(),
1137
+ outcome: z.enum(["normal", "empty", "error"]).optional(),
1138
+ error: mockOperationErrorSchema.optional(),
1139
+ })
1140
+ .strict()
1141
+ .superRefine((behavior, context) => {
1142
+ const outcome = behavior.outcome ?? "normal";
1143
+ if (outcome === "error" && !behavior.error) {
1144
+ context.addIssue({
1145
+ code: "custom",
1146
+ path: ["error"],
1147
+ message: "Un behavior error debe declarar el error controlado.",
1148
+ });
1149
+ }
1150
+ if (outcome !== "error" && behavior.error) {
1151
+ context.addIssue({
1152
+ code: "custom",
1153
+ path: ["error"],
1154
+ message: "Solo un behavior error puede declarar error.",
1155
+ });
1156
+ }
1157
+ });
1158
+
1159
+ export const mockEventBehaviorSchema = z
1160
+ .object({
1161
+ latencyMs: z.number().int().min(0).optional(),
1162
+ outcome: z.enum(["normal", "error"]).optional(),
1163
+ error: mockOperationErrorSchema.optional(),
1164
+ })
1165
+ .strict()
1166
+ .superRefine((behavior, context) => {
1167
+ const outcome = behavior.outcome ?? "normal";
1168
+ if (outcome === "error" && !behavior.error) {
1169
+ context.addIssue({
1170
+ code: "custom",
1171
+ path: ["error"],
1172
+ message: "Un behavior error debe declarar el error controlado.",
1173
+ });
1174
+ }
1175
+ if (outcome !== "error" && behavior.error) {
1176
+ context.addIssue({
1177
+ code: "custom",
1178
+ path: ["error"],
1179
+ message: "Solo un behavior error puede declarar error.",
1180
+ });
1181
+ }
1182
+ });
1183
+
1184
+ export const mockScenarioSchema = z
1185
+ .object({
1186
+ id: camelCaseIdSchema,
1187
+ name: nonEmptyTextSchema,
1188
+ description: nonEmptyTextSchema,
1189
+ entities: z.record(camelCaseIdSchema, z.array(mockEntityRecordSchema)),
1190
+ behaviors: z
1191
+ .object({
1192
+ dataLoads: z.record(camelCaseIdSchema, mockDataLoadBehaviorSchema).optional(),
1193
+ events: z.record(camelCaseIdSchema, mockEventBehaviorSchema).optional(),
1194
+ })
1195
+ .strict()
1196
+ .optional(),
1197
+ })
1198
+ .strict();
1199
+
1200
+ export const prototypeMockScenariosSchema = z
1201
+ .object({
1202
+ scenariosVersion: z.literal("1"),
1203
+ datasetVersion: z.number().int().positive(),
1204
+ defaultScenarioId: camelCaseIdSchema,
1205
+ scenarios: z.record(camelCaseIdSchema, mockScenarioSchema),
1206
+ })
1207
+ .strict()
1208
+ .superRefine((catalog, context) => {
1209
+ if (!catalog.scenarios[catalog.defaultScenarioId]) {
1210
+ context.addIssue({
1211
+ code: "custom",
1212
+ path: ["defaultScenarioId"],
1213
+ message: 'El escenario default "' + catalog.defaultScenarioId + '" no existe.',
1214
+ });
1215
+ }
1216
+
1217
+ for (const [scenarioKey, scenario] of Object.entries(catalog.scenarios)) {
1218
+ if (scenarioKey !== scenario.id) {
1219
+ context.addIssue({
1220
+ code: "custom",
1221
+ path: ["scenarios", scenarioKey, "id"],
1222
+ message: 'El id "' + scenario.id + '" debe coincidir con la key "' + scenarioKey + '".',
1223
+ });
1224
+ }
1225
+ }
1226
+ });
1227
+
1228
+ export type MockSemantic = z.infer<typeof mockSemanticSchema>;
1229
+ export type FieldMockHint = z.infer<typeof fieldMockHintSchema>;
1230
+ export type MockGenerationProfile = z.infer<typeof mockGenerationProfileSchema>;
1231
+ export type MockOperationError = z.infer<typeof mockOperationErrorSchema>;
1232
+ export type MockDataLoadBehavior = z.infer<typeof mockDataLoadBehaviorSchema>;
1233
+ export type MockEventBehavior = z.infer<typeof mockEventBehaviorSchema>;
1234
+ export type MockScenario = z.infer<typeof mockScenarioSchema>;
1235
+ export type PrototypeMockScenarios = z.infer<typeof prototypeMockScenariosSchema>;
1236
+
1237
+ export type SimulatorValidationIssue = {
1238
+ path: Array<string | number>;
1239
+ message: string;
1240
+ };
1241
+
1242
+ type DataContractFieldLike = {
1243
+ dataType: "string" | "number" | "boolean" | "date" | "datetime" | "enum" | "identifier";
1244
+ required: boolean;
1245
+ enumValues?: string[];
1246
+ };
1247
+
1248
+ type DataContractLike = {
1249
+ entities: Record<
1250
+ string,
1251
+ {
1252
+ fields: Record<string, DataContractFieldLike>;
1253
+ }
1254
+ >;
1255
+ relations: Array<{ id: string; fromEntity: string; toEntity: string }>;
1256
+ projections: Record<string, unknown>;
1257
+ };
1258
+
1259
+ type EventEngineContractLike = {
1260
+ dataLoads: Record<string, unknown>;
1261
+ events: Record<string, unknown>;
1262
+ };
1263
+
1264
+ const splitReference = (reference: string) => {
1265
+ const [entityId, fieldId] = reference.split(".");
1266
+ return { entityId, fieldId };
1267
+ };
1268
+
1269
+ const getField = (dataContract: DataContractLike, reference: string) => {
1270
+ const { entityId, fieldId } = splitReference(reference);
1271
+ return {
1272
+ entityId,
1273
+ fieldId,
1274
+ entity: dataContract.entities[entityId],
1275
+ field: dataContract.entities[entityId]?.fields[fieldId],
1276
+ };
1277
+ };
1278
+
1279
+ const isSemanticCompatible = (semantic: MockSemantic, field: DataContractFieldLike): boolean => {
1280
+ if (semantic === "custom") return true;
1281
+ if (semantic === "clpAmount") return field.dataType === "number";
1282
+ if (semantic === "dateCl") return field.dataType === "date";
1283
+ if (semantic === "datetimeCl") return field.dataType === "datetime";
1284
+ if (field.dataType === "enum") return ["regionCl", "communeCl", "custom"].includes(semantic);
1285
+ return ["string", "identifier"].includes(field.dataType);
1286
+ };
1287
+
1288
+ const rutFormat = /^[0-9]{7,8}-[0-9K]$/;
1289
+
1290
+ const isValidRutChecksum = (rut: string) => {
1291
+ if (!rutFormat.test(rut)) return false;
1292
+ const [body, verifier] = rut.split("-");
1293
+ let sum = 0;
1294
+ let multiplier = 2;
1295
+ for (let index = body.length - 1; index >= 0; index -= 1) {
1296
+ sum += Number(body[index]) * multiplier;
1297
+ multiplier = multiplier === 7 ? 2 : multiplier + 1;
1298
+ }
1299
+ const result = 11 - (sum % 11);
1300
+ const expected = result === 11 ? "0" : result === 10 ? "K" : String(result);
1301
+ return verifier === expected;
1302
+ };
1303
+
1304
+ const mobilePhoneFormat = /^\+56 9 [0-9]{4} [0-9]{4}$/;
1305
+ const landlinePhoneFormat = /^\+56 [2-9] [0-9]{4} [0-9]{4}$/;
1306
+ const isoDateFormat = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
1307
+ const datetimeWithOffsetFormat =
1308
+ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{3})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
1309
+ const emailFormat = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1310
+ const secretLikeFormat =
1311
+ /(sk-[a-z0-9_-]{12,}|ghp_[a-z0-9_]{12,}|api[_-]?key|secret|token|password|BEGIN PRIVATE KEY)/i;
1312
+
1313
+ const validateChileanValue = (input: {
1314
+ value: JsonValue;
1315
+ hint: FieldMockHint;
1316
+ emailDomain: string;
1317
+ }): string | null => {
1318
+ const { value, hint, emailDomain } = input;
1319
+ if (value === null) return null;
1320
+
1321
+ if (hint.semantic === "rutCl") {
1322
+ if (typeof value !== "string" || !rutFormat.test(value)) {
1323
+ return "El RUT debe usar formato 12345678-9 y K mayúscula.";
1324
+ }
1325
+ if (hint.rutValidation === "validChecksum" && !isValidRutChecksum(value)) {
1326
+ return "El RUT no cumple el dígito verificador módulo 11.";
1327
+ }
1328
+ }
1329
+
1330
+ if (
1331
+ hint.semantic === "mobilePhoneCl" &&
1332
+ (typeof value !== "string" || !mobilePhoneFormat.test(value))
1333
+ ) {
1334
+ return "El móvil chileno debe usar formato +56 9 XXXX XXXX.";
1335
+ }
1336
+
1337
+ if (
1338
+ hint.semantic === "landlinePhoneCl" &&
1339
+ (typeof value !== "string" || !landlinePhoneFormat.test(value))
1340
+ ) {
1341
+ return "El teléfono fijo chileno debe usar formato +56 X XXXX XXXX.";
1342
+ }
1343
+
1344
+ if (hint.semantic === "dateCl" && (typeof value !== "string" || !isoDateFormat.test(value))) {
1345
+ return "La fecha de storage debe usar ISO YYYY-MM-DD.";
1346
+ }
1347
+
1348
+ if (
1349
+ hint.semantic === "datetimeCl" &&
1350
+ (typeof value !== "string" || !datetimeWithOffsetFormat.test(value))
1351
+ ) {
1352
+ return "El datetime debe usar ISO 8601 con offset o Z.";
1353
+ }
1354
+
1355
+ if (hint.semantic === "clpAmount" && (!Number.isInteger(value) || typeof value !== "number")) {
1356
+ return "Los montos CLP deben almacenarse como enteros sin decimales.";
1357
+ }
1358
+
1359
+ if (hint.semantic === "email") {
1360
+ if (typeof value !== "string" || !emailFormat.test(value)) {
1361
+ return "El correo debe tener formato válido.";
1362
+ }
1363
+ if (!value.endsWith("@" + emailDomain)) {
1364
+ return 'Los correos mock deben usar el dominio "' + emailDomain + '".';
1365
+ }
1366
+ }
1367
+
1368
+ return null;
1369
+ };
1370
+
1371
+ const validateValueType = (field: DataContractFieldLike, value: JsonValue): string | null => {
1372
+ if (value === null) return null;
1373
+ if (field.dataType === "number" && typeof value !== "number") return "Debe ser number.";
1374
+ if (field.dataType === "boolean" && typeof value !== "boolean") return "Debe ser boolean.";
1375
+ if (
1376
+ ["string", "identifier", "date", "datetime", "enum"].includes(field.dataType) &&
1377
+ typeof value !== "string"
1378
+ ) {
1379
+ return "Debe ser string.";
1380
+ }
1381
+ if (field.dataType === "date" && typeof value === "string" && !isoDateFormat.test(value)) {
1382
+ return "Debe usar fecha ISO YYYY-MM-DD.";
1383
+ }
1384
+ if (
1385
+ field.dataType === "datetime" &&
1386
+ typeof value === "string" &&
1387
+ !datetimeWithOffsetFormat.test(value)
1388
+ ) {
1389
+ return "Debe usar datetime ISO 8601 con offset o Z.";
1390
+ }
1391
+ if (
1392
+ field.dataType === "enum" &&
1393
+ typeof value === "string" &&
1394
+ !field.enumValues?.includes(value)
1395
+ ) {
1396
+ return 'El valor "' + value + '" no existe en enumValues.';
1397
+ }
1398
+ return null;
1399
+ };
1400
+
1401
+ const visitStringValues = (value: JsonValue, visitor: (text: string) => void): void => {
1402
+ if (typeof value === "string") {
1403
+ visitor(value);
1404
+ return;
1405
+ }
1406
+ if (Array.isArray(value)) {
1407
+ for (const item of value) visitStringValues(item, visitor);
1408
+ return;
1409
+ }
1410
+ if (value && typeof value === "object") {
1411
+ for (const item of Object.values(value)) visitStringValues(item, visitor);
1412
+ }
1413
+ };
1414
+
1415
+ export const validateMockProfileReferences = (input: {
1416
+ profile: MockGenerationProfile;
1417
+ dataContract: DataContractLike;
1418
+ }): SimulatorValidationIssue[] => {
1419
+ const issues: SimulatorValidationIssue[] = [];
1420
+ const { profile, dataContract } = input;
1421
+
1422
+ for (const [entityId, binding] of Object.entries(profile.entityKeys)) {
1423
+ const { entityId: fieldEntityId, fieldId, field } = getField(dataContract, binding.field);
1424
+ if (!dataContract.entities[entityId]) {
1425
+ issues.push({
1426
+ path: ["entityKeys", entityId],
1427
+ message: 'La entidad "' + entityId + '" no existe en la Spec 1.',
1428
+ });
1429
+ }
1430
+ if (fieldEntityId !== entityId) {
1431
+ issues.push({
1432
+ path: ["entityKeys", entityId, "field"],
1433
+ message: "La key de entidad debe apuntar a un campo de la misma entidad.",
1434
+ });
1435
+ }
1436
+ if (!field) {
1437
+ issues.push({
1438
+ path: ["entityKeys", entityId, "field"],
1439
+ message: 'El campo "' + fieldEntityId + "." + fieldId + '" no existe.',
1440
+ });
1441
+ } else if (!["identifier", "string", "number"].includes(field.dataType)) {
1442
+ issues.push({
1443
+ path: ["entityKeys", entityId, "field"],
1444
+ message: "La key debe ser identifier, string o number.",
1445
+ });
1446
+ }
1447
+ }
1448
+
1449
+ for (const [bindingId, binding] of Object.entries(profile.relationBindings)) {
1450
+ const relation = dataContract.relations.find((candidate) => candidate.id === binding.relation);
1451
+ if (!relation) {
1452
+ issues.push({
1453
+ path: ["relationBindings", bindingId, "relation"],
1454
+ message: 'La relación "' + binding.relation + '" no existe en la Spec 1.',
1455
+ });
1456
+ }
1457
+ for (const key of ["fromField", "toField"] as const) {
1458
+ const { entityId, fieldId, field } = getField(dataContract, binding[key]);
1459
+ if (!field) {
1460
+ issues.push({
1461
+ path: ["relationBindings", bindingId, key],
1462
+ message: 'El campo "' + entityId + "." + fieldId + '" no existe.',
1463
+ });
1464
+ }
1465
+ }
1466
+ }
1467
+
1468
+ for (const [reference, hint] of Object.entries(profile.fieldHints)) {
1469
+ const { entityId, fieldId, field } = getField(dataContract, reference);
1470
+ if (!field) {
1471
+ issues.push({
1472
+ path: ["fieldHints", reference],
1473
+ message: 'El campo "' + entityId + "." + fieldId + '" no existe.',
1474
+ });
1475
+ continue;
1476
+ }
1477
+ if (!isSemanticCompatible(hint.semantic, field)) {
1478
+ issues.push({
1479
+ path: ["fieldHints", reference, "semantic"],
1480
+ message:
1481
+ 'La semántica "' + hint.semantic + '" no es compatible con ' + field.dataType + ".",
1482
+ });
1483
+ }
1484
+ if (field.dataType === "enum" && hint.constraints?.allowedValues) {
1485
+ for (const value of hint.constraints.allowedValues) {
1486
+ if (!field.enumValues?.includes(value)) {
1487
+ issues.push({
1488
+ path: ["fieldHints", reference, "constraints", "allowedValues"],
1489
+ message: 'El valor "' + value + '" no existe en enumValues.',
1490
+ });
1491
+ }
1492
+ }
1493
+ }
1494
+ }
1495
+
1496
+ return issues;
1497
+ };
1498
+
1499
+ export const validateMockScenarios = (input: {
1500
+ profile: MockGenerationProfile;
1501
+ scenarios: PrototypeMockScenarios;
1502
+ dataContract: DataContractLike;
1503
+ eventEngineContract: EventEngineContractLike;
1504
+ }): SimulatorValidationIssue[] => {
1505
+ const issues: SimulatorValidationIssue[] = [];
1506
+ const { profile, scenarios, dataContract, eventEngineContract } = input;
1507
+
1508
+ for (const [scenarioId, scenario] of Object.entries(scenarios.scenarios)) {
1509
+ for (const [entityId, records] of Object.entries(scenario.entities)) {
1510
+ const entity = dataContract.entities[entityId];
1511
+ if (!entity) {
1512
+ issues.push({
1513
+ path: ["scenarios", scenarioId, "entities", entityId],
1514
+ message: 'La entidad "' + entityId + '" no existe en la Spec 1.',
1515
+ });
1516
+ continue;
1517
+ }
1518
+
1519
+ const keyBinding = profile.entityKeys[entityId];
1520
+ const seenKeys = new Set<JsonValue>();
1521
+ for (const [recordIndex, record] of records.entries()) {
1522
+ for (const fieldKey of Object.keys(record)) {
1523
+ if (!entity.fields[fieldKey]) {
1524
+ issues.push({
1525
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1526
+ message: 'El campo "' + fieldKey + '" no existe en la entidad "' + entityId + '".',
1527
+ });
1528
+ }
1529
+ }
1530
+
1531
+ for (const [fieldKey, field] of Object.entries(entity.fields)) {
1532
+ const value = record[fieldKey];
1533
+ if (field.required && value === undefined) {
1534
+ issues.push({
1535
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1536
+ message: "El campo requerido está ausente.",
1537
+ });
1538
+ continue;
1539
+ }
1540
+ if (value !== undefined) {
1541
+ const typeMessage = validateValueType(field, value);
1542
+ if (typeMessage) {
1543
+ issues.push({
1544
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1545
+ message: typeMessage,
1546
+ });
1547
+ }
1548
+
1549
+ const hint = profile.fieldHints[entityId + "." + fieldKey];
1550
+ if (hint) {
1551
+ const chileMessage = validateChileanValue({
1552
+ value,
1553
+ hint,
1554
+ emailDomain: profile.safety.emailDomain,
1555
+ });
1556
+ if (chileMessage) {
1557
+ issues.push({
1558
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1559
+ message: chileMessage,
1560
+ });
1561
+ }
1562
+ }
1563
+
1564
+ visitStringValues(value, (text) => {
1565
+ if (emailFormat.test(text) && !text.endsWith("@" + profile.safety.emailDomain)) {
1566
+ issues.push({
1567
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1568
+ message:
1569
+ 'Los correos mock deben usar el dominio "' + profile.safety.emailDomain + '".',
1570
+ });
1571
+ }
1572
+ if (secretLikeFormat.test(text)) {
1573
+ issues.push({
1574
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
1575
+ message: "El valor parece contener un secreto, token o credencial.",
1576
+ });
1577
+ }
1578
+ });
1579
+ }
1580
+ }
1581
+
1582
+ if (keyBinding) {
1583
+ const { fieldId } = splitReference(keyBinding.field);
1584
+ const keyValue = record[fieldId];
1585
+ if (keyValue !== undefined) {
1586
+ if (seenKeys.has(keyValue)) {
1587
+ issues.push({
1588
+ path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldId],
1589
+ message: 'La key "' + String(keyValue) + '" está duplicada.',
1590
+ });
1591
+ }
1592
+ seenKeys.add(keyValue);
1593
+ }
1594
+ }
1595
+ }
1596
+ }
1597
+
1598
+ for (const [bindingId, binding] of Object.entries(profile.relationBindings)) {
1599
+ const from = splitReference(binding.fromField);
1600
+ const to = splitReference(binding.toField);
1601
+ const fromRecords = scenario.entities[from.entityId] ?? [];
1602
+ const toRecords = scenario.entities[to.entityId] ?? [];
1603
+ const targetValues = new Set(toRecords.map((record) => record[to.fieldId]));
1604
+
1605
+ for (const [recordIndex, record] of fromRecords.entries()) {
1606
+ const value = record[from.fieldId];
1607
+ if (value !== undefined && value !== null && !targetValues.has(value)) {
1608
+ issues.push({
1609
+ path: ["scenarios", scenarioId, "entities", from.entityId, recordIndex, from.fieldId],
1610
+ message:
1611
+ 'Referencia "' +
1612
+ String(value) +
1613
+ '" inexistente para relationBinding "' +
1614
+ bindingId +
1615
+ '".',
1616
+ });
1617
+ }
1618
+ }
1619
+ }
1620
+
1621
+ for (const [loadId, behavior] of Object.entries(scenario.behaviors?.dataLoads ?? {})) {
1622
+ if (!eventEngineContract.dataLoads[loadId]) {
1623
+ issues.push({
1624
+ path: ["scenarios", scenarioId, "behaviors", "dataLoads", loadId],
1625
+ message: 'La carga "' + loadId + '" no existe en la Spec 2.',
1626
+ });
1627
+ }
1628
+ if ((behavior.outcome ?? "normal") === "empty" && behavior.error) {
1629
+ issues.push({
1630
+ path: ["scenarios", scenarioId, "behaviors", "dataLoads", loadId, "error"],
1631
+ message: "El outcome empty no puede incluir error.",
1632
+ });
1633
+ }
1634
+ }
1635
+
1636
+ for (const eventId of Object.keys(scenario.behaviors?.events ?? {})) {
1637
+ if (!eventEngineContract.events[eventId]) {
1638
+ issues.push({
1639
+ path: ["scenarios", scenarioId, "behaviors", "events", eventId],
1640
+ message: 'El evento "' + eventId + '" no existe en la Spec 2.',
1641
+ });
1642
+ }
1643
+ }
1644
+ }
1645
+
1646
+ return issues;
1647
+ };
1648
+
1649
+ export function defineMockGenerationProfile(profile: MockGenerationProfile): MockGenerationProfile {
1650
+ return mockGenerationProfileSchema.parse(profile);
1651
+ }
1652
+
1653
+ export function definePrototypeMockScenarios(
1654
+ scenarios: PrototypeMockScenarios,
1655
+ ): PrototypeMockScenarios {
1656
+ return prototypeMockScenariosSchema.parse(scenarios);
1657
+ }
1658
+ `,Ft=String.raw`import { defineMockGenerationProfile } from "./simulator-contract-schema";
1659
+
1660
+ /**
1661
+ * Perfil para que el agente genere data mock coherente con Chile.
1662
+ *
1663
+ * La IA lo completa en autoría. El runtime solo consume objetos TypeScript
1664
+ * explícitos y deterministas.
1665
+ */
1666
+ export const mockGenerationProfile = defineMockGenerationProfile({
1667
+ profileVersion: "1",
1668
+ locale: {
1669
+ language: "es",
1670
+ locale: "es-CL",
1671
+ country: "CL",
1672
+ timezone: "America/Santiago",
1673
+ currency: "CLP",
1674
+ dateStorage: "iso8601",
1675
+ dateDisplay: "dd-MM-yyyy",
1676
+ timeDisplay: "24h",
1677
+ },
1678
+ safety: {
1679
+ syntheticOnly: true,
1680
+ allowProductionImports: false,
1681
+ externalUse: false,
1682
+ emailDomain: "example.com",
1683
+ },
1684
+ entityKeys: {},
1685
+ relationBindings: {},
1686
+ fieldHints: {},
1687
+ });
1688
+ `,Nt=String.raw`import { definePrototypeMockScenarios } from "./simulator-contract-schema";
1689
+
1690
+ /**
1691
+ * Escenarios mock persistibles.
1692
+ *
1693
+ * Un proyecto fresco nace vacío y válido. El agente agrega fixtures explícitos
1694
+ * después del kickoff y sube datasetVersion cuando cambia el dataset.
1695
+ */
1696
+ export const prototypeMockScenarios = definePrototypeMockScenarios({
1697
+ scenariosVersion: "1",
1698
+ datasetVersion: 1,
1699
+ defaultScenarioId: "base",
1700
+ scenarios: {
1701
+ base: {
1702
+ id: "base",
1703
+ name: "Recorrido principal",
1704
+ description: "Escenario inicial pendiente de completar durante el kickoff.",
1705
+ entities: {},
1706
+ },
1707
+ empty: {
1708
+ id: "empty",
1709
+ name: "Estado vacío",
1710
+ description: "Escenario sin resultados.",
1711
+ entities: {},
1712
+ },
1713
+ edgeCases: {
1714
+ id: "edgeCases",
1715
+ name: "Casos límite",
1716
+ description: "Escenario pendiente de completar según la experiencia.",
1717
+ entities: {},
1718
+ },
1719
+ },
1720
+ });
1721
+ `,Ht=String.raw`import type { ProjectionResolverMap } from "./prototype-simulator";
1722
+
1723
+ export const projectionResolvers: ProjectionResolverMap = {};
1724
+ `,Kt=String.raw`import type { MockDataLoadHandlerMap } from "./prototype-simulator";
1725
+
1726
+ export const dataLoadHandlers: MockDataLoadHandlerMap = {};
1727
+ `,qt=String.raw`import type { MockEventHandlerMap } from "./prototype-simulator";
1728
+
1729
+ export const eventHandlers: MockEventHandlerMap = {};
1730
+ `,Ut=String.raw`import type {
1731
+ JsonPrimitive,
1732
+ MockEntityRecord,
1733
+ PrototypeMockScenarios,
1734
+ } from "./simulator-contract-schema";
1735
+
1736
+ export type PublishedEventHistoryItem = {
1737
+ publicationId: string;
1738
+ eventId: string;
1739
+ technicalId: string;
1740
+ occurredAt: string;
1741
+ status: "published";
1742
+ };
1743
+
1744
+ export type PrototypeSimulatorState = {
1745
+ simulatorVersion: "1";
1746
+ datasetVersion: number;
1747
+ activeScenarioId: string;
1748
+ revision: number;
1749
+ entities: Record<string, MockEntityRecord[]>;
1750
+ entitySequences: Record<string, number>;
1751
+ publicationHistory: PublishedEventHistoryItem[];
1752
+ };
1753
+
1754
+ export type SimulatorStorage = {
1755
+ read(): string | null;
1756
+ write(value: string): void;
1757
+ remove(): void;
1758
+ };
1759
+
1760
+ export type PrototypeMockStore = {
1761
+ getSnapshot(): PrototypeSimulatorState;
1762
+ subscribe(listener: () => void): () => void;
1763
+ transact(operation: (draft: PrototypeSimulatorState) => void): PrototypeSimulatorState;
1764
+ switchScenario(scenarioId: string): void;
1765
+ resetScenario(): void;
1766
+ clearPersistence(): void;
1767
+ };
1768
+
1769
+ type CreatePrototypeMockStoreOptions = {
1770
+ projectName: string;
1771
+ scenarios: PrototypeMockScenarios;
1772
+ storage?: SimulatorStorage;
1773
+ onHydrationWarning?: (message: string) => void;
1774
+ };
1775
+
1776
+ const clone = <T>(value: T): T => {
1777
+ if (typeof structuredClone === "function") return structuredClone(value);
1778
+ return JSON.parse(JSON.stringify(value)) as T;
1779
+ };
1780
+
1781
+ const buildSequences = (entities: Record<string, MockEntityRecord[]>) => {
1782
+ const sequences: Record<string, number> = {};
1783
+ for (const [entityId, records] of Object.entries(entities)) {
1784
+ sequences[entityId] = records.length + 1;
1785
+ }
1786
+ return sequences;
1787
+ };
1788
+
1789
+ const createStateFromScenario = (
1790
+ scenarios: PrototypeMockScenarios,
1791
+ scenarioId: string,
1792
+ ): PrototypeSimulatorState => {
1793
+ const scenario = scenarios.scenarios[scenarioId];
1794
+ if (!scenario) throw new Error('El escenario "' + scenarioId + '" no existe.');
1795
+ const entities = clone(scenario.entities);
1796
+ return {
1797
+ simulatorVersion: "1",
1798
+ datasetVersion: scenarios.datasetVersion,
1799
+ activeScenarioId: scenarioId,
1800
+ revision: 0,
1801
+ entities,
1802
+ entitySequences: buildSequences(entities),
1803
+ publicationHistory: [],
1804
+ };
1805
+ };
1806
+
1807
+ const isPersistedStateCompatible = (
1808
+ value: unknown,
1809
+ scenarios: PrototypeMockScenarios,
1810
+ ): value is PrototypeSimulatorState => {
1811
+ if (!value || typeof value !== "object") return false;
1812
+ const state = value as Partial<PrototypeSimulatorState>;
1813
+ return (
1814
+ state.simulatorVersion === "1" &&
1815
+ state.datasetVersion === scenarios.datasetVersion &&
1816
+ typeof state.activeScenarioId === "string" &&
1817
+ Boolean(scenarios.scenarios[state.activeScenarioId]) &&
1818
+ typeof state.revision === "number" &&
1819
+ Boolean(state.entities && typeof state.entities === "object") &&
1820
+ Boolean(state.entitySequences && typeof state.entitySequences === "object") &&
1821
+ Array.isArray(state.publicationHistory)
1822
+ );
1823
+ };
1824
+
1825
+ export const createMemorySimulatorStorage = (): SimulatorStorage => {
1826
+ let current: string | null = null;
1827
+ return {
1828
+ read: () => current,
1829
+ write: (value) => {
1830
+ current = value;
1831
+ },
1832
+ remove: () => {
1833
+ current = null;
1834
+ },
1835
+ };
1836
+ };
1837
+
1838
+ export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorage => {
1839
+ const memory = createMemorySimulatorStorage();
1840
+ const getLocalStorage = () => {
1841
+ try {
1842
+ return globalThis.localStorage;
1843
+ } catch {
1844
+ return undefined;
1845
+ }
1846
+ };
1847
+
1848
+ return {
1849
+ read: () => {
1850
+ try {
1851
+ return getLocalStorage()?.getItem(key) ?? memory.read();
1852
+ } catch {
1853
+ return memory.read();
1854
+ }
1855
+ },
1856
+ write: (value) => {
1857
+ memory.write(value);
1858
+ try {
1859
+ getLocalStorage()?.setItem(key, value);
1860
+ } catch {
1861
+ // fallback en memoria
1862
+ }
1863
+ },
1864
+ remove: () => {
1865
+ memory.remove();
1866
+ try {
1867
+ getLocalStorage()?.removeItem(key);
1868
+ } catch {
1869
+ // fallback en memoria
1870
+ }
1871
+ },
1872
+ };
1873
+ };
1874
+
1875
+ export const simulatorStorageKey = (projectName: string) =>
1876
+ "lexy:prototype:" + projectName + ":simulator:v1";
1877
+
1878
+ export const createPrototypeMockStore = (
1879
+ options: CreatePrototypeMockStoreOptions,
1880
+ ): PrototypeMockStore => {
1881
+ const storage =
1882
+ options.storage ?? createLocalStorageSimulatorStorage(simulatorStorageKey(options.projectName));
1883
+ const listeners = new Set<() => void>();
1884
+
1885
+ const hydrate = () => {
1886
+ const persisted = storage.read();
1887
+ if (!persisted)
1888
+ return createStateFromScenario(options.scenarios, options.scenarios.defaultScenarioId);
1889
+
1890
+ try {
1891
+ const parsed = JSON.parse(persisted) as unknown;
1892
+ if (isPersistedStateCompatible(parsed, options.scenarios)) return parsed;
1893
+ options.onHydrationWarning?.(
1894
+ "Estado persistido incompatible; se reinicia el simulador al escenario default.",
1895
+ );
1896
+ } catch {
1897
+ options.onHydrationWarning?.(
1898
+ "Estado persistido corrupto; se reinicia el simulador al escenario default.",
1899
+ );
1900
+ }
1901
+ return createStateFromScenario(options.scenarios, options.scenarios.defaultScenarioId);
1902
+ };
1903
+
1904
+ let snapshot = hydrate();
1905
+
1906
+ const persist = () => {
1907
+ storage.write(JSON.stringify(snapshot));
1908
+ };
1909
+
1910
+ const notify = () => {
1911
+ for (const listener of listeners) listener();
1912
+ };
1913
+
1914
+ return {
1915
+ getSnapshot: () => snapshot,
1916
+ subscribe: (listener) => {
1917
+ listeners.add(listener);
1918
+ return () => {
1919
+ listeners.delete(listener);
1920
+ };
1921
+ },
1922
+ transact: (operation) => {
1923
+ const draft = clone(snapshot);
1924
+ operation(draft);
1925
+ draft.revision = snapshot.revision + 1;
1926
+ snapshot = draft;
1927
+ persist();
1928
+ notify();
1929
+ return snapshot;
1930
+ },
1931
+ switchScenario: (scenarioId) => {
1932
+ snapshot = createStateFromScenario(options.scenarios, scenarioId);
1933
+ persist();
1934
+ notify();
1935
+ },
1936
+ resetScenario: () => {
1937
+ snapshot = createStateFromScenario(options.scenarios, snapshot.activeScenarioId);
1938
+ persist();
1939
+ notify();
1940
+ },
1941
+ clearPersistence: () => {
1942
+ storage.remove();
1943
+ },
1944
+ };
1945
+ };
1946
+
1947
+ export const findRecordById = (
1948
+ records: readonly MockEntityRecord[],
1949
+ fieldId: string,
1950
+ id: JsonPrimitive,
1951
+ ) => records.find((record) => record[fieldId] === id);
1952
+ `,Bt=String.raw`import type {
1953
+ EventPublicationEnvelope,
1954
+ PrototypeBackendExecutor,
1955
+ } from "../event-engine/event-engine";
1956
+ import type {
1957
+ JsonPrimitive,
1958
+ MockEntityRecord,
1959
+ MockGenerationProfile,
1960
+ MockOperationError,
1961
+ PrototypeMockScenarios,
1962
+ } from "./simulator-contract-schema";
1963
+ import {
1964
+ findRecordById,
1965
+ type PrototypeMockStore,
1966
+ type PrototypeSimulatorState,
1967
+ } from "./mock-store";
1968
+ import type {
1969
+ MockDataLoadHandlerMap,
1970
+ MockEventHandlerMap,
1971
+ ProjectionResolverContext,
1972
+ ProjectionResolverMap,
1973
+ } from "./prototype-simulator";
1974
+
1975
+ export class MockBackendError extends Error {
1976
+ code: string;
1977
+ recoverable: boolean;
1978
+
1979
+ constructor(error: MockOperationError) {
1980
+ super(error.message);
1981
+ this.name = "MockBackendError";
1982
+ this.code = error.code;
1983
+ this.recoverable = error.recoverable;
1984
+ }
1985
+ }
1986
+
1987
+ type CreateMockBackendExecutorOptions = {
1988
+ profile: MockGenerationProfile;
1989
+ scenarios: PrototypeMockScenarios;
1990
+ store: PrototypeMockStore;
1991
+ projectionResolvers: ProjectionResolverMap;
1992
+ dataLoadHandlers: MockDataLoadHandlerMap;
1993
+ eventHandlers: MockEventHandlerMap;
1994
+ };
1995
+
1996
+ const delay = (latencyMs: number | undefined, signal?: AbortSignal) =>
1997
+ new Promise<void>((resolve, reject) => {
1998
+ if (!latencyMs) {
1999
+ resolve();
2000
+ return;
2001
+ }
2002
+ if (signal?.aborted) {
2003
+ reject(new DOMException("Operación abortada.", "AbortError"));
2004
+ return;
2005
+ }
2006
+ const timeout = globalThis.setTimeout(resolve, latencyMs);
2007
+ signal?.addEventListener(
2008
+ "abort",
2009
+ () => {
2010
+ globalThis.clearTimeout(timeout);
2011
+ reject(new DOMException("Operación abortada.", "AbortError"));
2012
+ },
2013
+ { once: true },
2014
+ );
2015
+ });
2016
+
2017
+ const splitReference = (reference: string) => {
2018
+ const [entityId, fieldId] = reference.split(".");
2019
+ return { entityId, fieldId };
2020
+ };
2021
+
2022
+ const getEntityKeyField = (profile: MockGenerationProfile, entityId: string) => {
2023
+ const field = profile.entityKeys[entityId]?.field;
2024
+ return field ? splitReference(field).fieldId : "id";
2025
+ };
2026
+
2027
+ const createProjectionContext = (
2028
+ profile: MockGenerationProfile,
2029
+ state: PrototypeSimulatorState,
2030
+ ): ProjectionResolverContext => ({
2031
+ entities: state.entities,
2032
+ getEntity: (entityId) => state.entities[entityId] ?? [],
2033
+ getById: (entityId, id) =>
2034
+ findRecordById(state.entities[entityId] ?? [], getEntityKeyField(profile, entityId), id),
2035
+ getRelated: (relationId, sourceId) => {
2036
+ const binding = Object.values(profile.relationBindings).find(
2037
+ (candidate) => candidate.relation === relationId,
2038
+ );
2039
+ if (!binding) return [];
2040
+ const target = splitReference(binding.toField);
2041
+ return (state.entities[target.entityId] ?? []).filter(
2042
+ (record) => record[target.fieldId] === sourceId,
2043
+ );
2044
+ },
2045
+ });
2046
+
2047
+ const nextEntityId = (
2048
+ profile: MockGenerationProfile,
2049
+ draft: PrototypeSimulatorState,
2050
+ entityId: string,
2051
+ ): JsonPrimitive => {
2052
+ const keyField = getEntityKeyField(profile, entityId);
2053
+ const records = draft.entities[entityId] ?? [];
2054
+ const numericIds = records
2055
+ .map((record) => record[keyField])
2056
+ .filter((value): value is number => typeof value === "number");
2057
+ if (numericIds.length > 0) return Math.max(...numericIds) + 1;
2058
+
2059
+ const sequence = draft.entitySequences[entityId] ?? records.length + 1;
2060
+ draft.entitySequences[entityId] = sequence + 1;
2061
+ return entityId + "-" + sequence;
2062
+ };
2063
+
2064
+ export const createMockBackendExecutor = (
2065
+ options: CreateMockBackendExecutorOptions,
2066
+ ): PrototypeBackendExecutor => ({
2067
+ async loadData(input) {
2068
+ const state = options.store.getSnapshot();
2069
+ const scenario = options.scenarios.scenarios[state.activeScenarioId];
2070
+ const behavior = scenario.behaviors?.dataLoads?.[input.loadId];
2071
+ await delay(behavior?.latencyMs, input.signal);
2072
+
2073
+ if (behavior?.outcome === "error" && behavior.error) throw new MockBackendError(behavior.error);
2074
+ if (behavior?.outcome === "empty") return [];
2075
+
2076
+ const handler = options.dataLoadHandlers[input.loadId];
2077
+ if (!handler) throw new Error('Falta handler para la carga "' + input.loadId + '".');
2078
+
2079
+ return handler({
2080
+ params: input.params,
2081
+ signal: input.signal,
2082
+ store: options.store,
2083
+ projections: {
2084
+ resolve: (projectionId, params) => {
2085
+ const resolver = options.projectionResolvers[projectionId];
2086
+ if (!resolver)
2087
+ throw new Error('Falta resolver para la proyección "' + projectionId + '".');
2088
+ return resolver(
2089
+ createProjectionContext(options.profile, options.store.getSnapshot()),
2090
+ params,
2091
+ );
2092
+ },
2093
+ },
2094
+ });
2095
+ },
2096
+
2097
+ async publishEvent(input) {
2098
+ const state = options.store.getSnapshot();
2099
+ const scenario = options.scenarios.scenarios[state.activeScenarioId];
2100
+ const behavior = scenario.behaviors?.events?.[input.eventId];
2101
+ await delay(behavior?.latencyMs);
2102
+
2103
+ if (behavior?.outcome === "error" && behavior.error) throw new MockBackendError(behavior.error);
2104
+
2105
+ const handler = options.eventHandlers[input.eventId];
2106
+ if (!handler) throw new Error('Falta handler para el evento "' + input.eventId + '".');
2107
+
2108
+ let receipt: unknown;
2109
+ options.store.transact((draft) => {
2110
+ const getById = (entityId: string, id: JsonPrimitive): MockEntityRecord | undefined =>
2111
+ findRecordById(
2112
+ draft.entities[entityId] ?? [],
2113
+ getEntityKeyField(options.profile, entityId),
2114
+ id,
2115
+ );
2116
+
2117
+ const result = handler({
2118
+ draft,
2119
+ envelope: input.envelope as EventPublicationEnvelope,
2120
+ getById,
2121
+ nextEntityId: (entityId) => nextEntityId(options.profile, draft, entityId),
2122
+ });
2123
+ receipt = result?.receipt;
2124
+ draft.publicationHistory.push({
2125
+ publicationId: input.envelope.publicationId,
2126
+ eventId: input.eventId,
2127
+ technicalId: input.envelope.technicalId,
2128
+ occurredAt: input.envelope.occurredAt,
2129
+ status: "published",
2130
+ });
2131
+ });
2132
+
2133
+ return receipt === undefined ? {} : { receipt };
2134
+ },
2135
+ });
2136
+ `,Jt=String.raw`import type { EventPublicationEnvelope } from "../event-engine/event-engine";
2137
+ import { createPrototypeEventEngine } from "../event-engine/event-engine";
2138
+ import { prototypeDataContract } from "../data-contract/prototype-data-contract";
2139
+ import { dataLoadHandlers } from "./data-load-handlers";
2140
+ import { eventHandlers } from "./event-handlers";
2141
+ import { mockGenerationProfile } from "./mock-generation-profile";
2142
+ import { createMockBackendExecutor } from "./mock-backend-executor";
2143
+ import { createPrototypeMockStore, type PrototypeMockStore } from "./mock-store";
2144
+ import { prototypeMockScenarios } from "./mock-scenarios";
2145
+ import { projectionResolvers } from "./projection-resolvers";
2146
+ import type {
2147
+ JsonPrimitive,
2148
+ MockEntityRecord,
2149
+ MockGenerationProfile,
2150
+ PrototypeMockScenarios,
2151
+ } from "./simulator-contract-schema";
2152
+
2153
+ export type ProjectionResolverContext = {
2154
+ entities: Readonly<Record<string, readonly MockEntityRecord[]>>;
2155
+ getEntity(entityId: string): readonly MockEntityRecord[];
2156
+ getById(entityId: string, id: JsonPrimitive): MockEntityRecord | undefined;
2157
+ getRelated(relationId: string, sourceId: JsonPrimitive): readonly MockEntityRecord[];
2158
+ };
2159
+
2160
+ export type ProjectionResolver = (context: ProjectionResolverContext, params?: unknown) => unknown;
2161
+
2162
+ export type ProjectionResolverMap = Record<string, ProjectionResolver>;
2163
+
2164
+ export type MockDataLoadHandlerContext = {
2165
+ params: unknown;
2166
+ signal?: AbortSignal;
2167
+ store: PrototypeMockStore;
2168
+ projections: {
2169
+ resolve(projectionId: string, params?: unknown): unknown;
2170
+ };
2171
+ };
2172
+
2173
+ export type MockDataLoadHandler = (
2174
+ context: MockDataLoadHandlerContext,
2175
+ ) => unknown | Promise<unknown>;
2176
+
2177
+ export type MockDataLoadHandlerMap = Record<string, MockDataLoadHandler>;
2178
+
2179
+ export type MockEventHandlerContext = {
2180
+ draft: import("./mock-store").PrototypeSimulatorState;
2181
+ envelope: EventPublicationEnvelope;
2182
+ getById(entityId: string, id: JsonPrimitive): MockEntityRecord | undefined;
2183
+ nextEntityId(entityId: string): JsonPrimitive;
2184
+ };
2185
+
2186
+ export type MockEventHandlerResult = {
2187
+ receipt?: unknown;
2188
+ };
2189
+
2190
+ export type MockEventHandler = (context: MockEventHandlerContext) => MockEventHandlerResult | void;
2191
+
2192
+ export type MockEventHandlerMap = Record<string, MockEventHandler>;
2193
+
2194
+ export type PrototypeSimulator = {
2195
+ executor: ReturnType<typeof createMockBackendExecutor>;
2196
+ store: PrototypeMockStore;
2197
+ getActiveScenario(): PrototypeMockScenarios["scenarios"][string];
2198
+ listScenarios(): Array<{ id: string; name: string; description: string }>;
2199
+ switchScenario(scenarioId: string): void;
2200
+ reset(): void;
2201
+ clearPersistence(): void;
2202
+ };
2203
+
2204
+ type CreatePrototypeSimulatorOptions = {
2205
+ profile: MockGenerationProfile;
2206
+ scenarios: PrototypeMockScenarios;
2207
+ projectionResolvers: ProjectionResolverMap;
2208
+ dataLoadHandlers: MockDataLoadHandlerMap;
2209
+ eventHandlers: MockEventHandlerMap;
2210
+ };
2211
+
2212
+ export const createPrototypeSimulator = (
2213
+ options: CreatePrototypeSimulatorOptions,
2214
+ ): PrototypeSimulator => {
2215
+ const store = createPrototypeMockStore({
2216
+ projectName: prototypeDataContract.project.name,
2217
+ scenarios: options.scenarios,
2218
+ onHydrationWarning: (message) => {
2219
+ console.warn("[Lexy simulator] " + message);
2220
+ },
2221
+ });
2222
+
2223
+ const executor = createMockBackendExecutor({
2224
+ profile: options.profile,
2225
+ scenarios: options.scenarios,
2226
+ store,
2227
+ projectionResolvers: options.projectionResolvers,
2228
+ dataLoadHandlers: options.dataLoadHandlers,
2229
+ eventHandlers: options.eventHandlers,
2230
+ });
2231
+
2232
+ return {
2233
+ executor,
2234
+ store,
2235
+ getActiveScenario: () => options.scenarios.scenarios[store.getSnapshot().activeScenarioId],
2236
+ listScenarios: () =>
2237
+ Object.values(options.scenarios.scenarios).map((scenario) => ({
2238
+ id: scenario.id,
2239
+ name: scenario.name,
2240
+ description: scenario.description,
2241
+ })),
2242
+ switchScenario: (scenarioId) => store.switchScenario(scenarioId),
2243
+ reset: () => store.resetScenario(),
2244
+ clearPersistence: () => store.clearPersistence(),
2245
+ };
2246
+ };
2247
+
2248
+ export const prototypeSimulator = createPrototypeSimulator({
2249
+ profile: mockGenerationProfile,
2250
+ scenarios: prototypeMockScenarios,
2251
+ projectionResolvers,
2252
+ dataLoadHandlers,
2253
+ eventHandlers,
2254
+ });
2255
+
2256
+ export const prototypeEventEngine = createPrototypeEventEngine({
2257
+ executor: prototypeSimulator.executor,
2258
+ });
2259
+ `,Gt=String.raw`import { ZodError } from "zod";
2260
+
2261
+ import { prototypeDataContractSchema } from "../src/prototype/data-contract/data-contract-schema";
2262
+ import { prototypeEventEngineContractSchema } from "../src/prototype/event-engine/event-engine-contract-schema";
2263
+ import { dataLoadHandlers } from "../src/prototype/simulator/data-load-handlers";
2264
+ import { eventHandlers } from "../src/prototype/simulator/event-handlers";
2265
+ import { mockGenerationProfile } from "../src/prototype/simulator/mock-generation-profile";
2266
+ import { prototypeMockScenarios } from "../src/prototype/simulator/mock-scenarios";
2267
+ import { projectionResolvers } from "../src/prototype/simulator/projection-resolvers";
2268
+ import {
2269
+ mockGenerationProfileSchema,
2270
+ prototypeMockScenariosSchema,
2271
+ validateMockProfileReferences,
2272
+ validateMockScenarios,
2273
+ type SimulatorValidationIssue,
2274
+ } from "../src/prototype/simulator/simulator-contract-schema";
2275
+
2276
+ const formatPath = (path: Array<string | number>) =>
2277
+ path.length === 0 ? "simulator" : path.map(String).join(".");
2278
+
2279
+ const printIssues = (issues: SimulatorValidationIssue[]) => {
2280
+ console.error("✗ Simulador inválido");
2281
+ for (const issue of issues) {
2282
+ console.error(" " + formatPath(issue.path));
2283
+ console.error(" " + issue.message);
2284
+ }
2285
+ };
2286
+
2287
+ try {
2288
+ const dataContractModule = await import("../src/prototype/data-contract/prototype-data-contract");
2289
+ const eventEngineModule =
2290
+ await import("../src/prototype/event-engine/prototype-event-engine-contract");
2291
+
2292
+ const dataContractResult = prototypeDataContractSchema.safeParse(
2293
+ dataContractModule.prototypeDataContract,
2294
+ );
2295
+ const eventEngineResult = prototypeEventEngineContractSchema.safeParse(
2296
+ eventEngineModule.prototypeEventEngineContract,
2297
+ );
2298
+ const profileResult = mockGenerationProfileSchema.safeParse(mockGenerationProfile);
2299
+ const scenariosResult = prototypeMockScenariosSchema.safeParse(prototypeMockScenarios);
2300
+
2301
+ const issues: SimulatorValidationIssue[] = [];
2302
+ for (const [prefix, result] of [
2303
+ ["dataContract", dataContractResult],
2304
+ ["eventEngine", eventEngineResult],
2305
+ ["profile", profileResult],
2306
+ ["scenarios", scenariosResult],
2307
+ ] as const) {
2308
+ if (!result.success) {
2309
+ for (const issue of result.error.issues) {
2310
+ issues.push({
2311
+ path: [prefix, ...issue.path.map(String)],
2312
+ message: issue.message,
2313
+ });
2314
+ }
2315
+ }
2316
+ }
2317
+
2318
+ if (
2319
+ dataContractResult.success &&
2320
+ eventEngineResult.success &&
2321
+ profileResult.success &&
2322
+ scenariosResult.success
2323
+ ) {
2324
+ issues.push(
2325
+ ...validateMockProfileReferences({
2326
+ profile: profileResult.data,
2327
+ dataContract: dataContractResult.data,
2328
+ }),
2329
+ ...validateMockScenarios({
2330
+ profile: profileResult.data,
2331
+ scenarios: scenariosResult.data,
2332
+ dataContract: dataContractResult.data,
2333
+ eventEngineContract: eventEngineResult.data,
2334
+ }),
2335
+ );
2336
+
2337
+ for (const projectionId of Object.keys(projectionResolvers)) {
2338
+ if (!dataContractResult.data.projections[projectionId]) {
2339
+ issues.push({
2340
+ path: ["projectionResolvers", projectionId],
2341
+ message: 'La proyección "' + projectionId + '" no existe en la Spec 1.',
2342
+ });
2343
+ }
2344
+ }
2345
+
2346
+ const requiredProjections = new Set<string>();
2347
+ for (const load of Object.values(eventEngineResult.data.dataLoads)) {
2348
+ for (const projectionId of load.reads?.projections ?? [])
2349
+ requiredProjections.add(projectionId);
2350
+ }
2351
+ for (const projectionId of requiredProjections) {
2352
+ if (!projectionResolvers[projectionId]) {
2353
+ issues.push({
2354
+ path: ["projectionResolvers", projectionId],
2355
+ message: 'Falta resolver para la proyección consumida "' + projectionId + '".',
2356
+ });
2357
+ }
2358
+ }
2359
+
2360
+ for (const loadId of Object.keys(eventEngineResult.data.dataLoads)) {
2361
+ if (!dataLoadHandlers[loadId]) {
2362
+ issues.push({
2363
+ path: ["dataLoadHandlers", loadId],
2364
+ message: 'Falta handler para la carga "' + loadId + '".',
2365
+ });
2366
+ }
2367
+ }
2368
+ for (const handlerId of Object.keys(dataLoadHandlers)) {
2369
+ if (!eventEngineResult.data.dataLoads[handlerId]) {
2370
+ issues.push({
2371
+ path: ["dataLoadHandlers", handlerId],
2372
+ message: 'El handler "' + handlerId + '" no corresponde a ninguna carga.',
2373
+ });
2374
+ }
2375
+ }
2376
+
2377
+ for (const eventId of Object.keys(eventEngineResult.data.events)) {
2378
+ if (!eventHandlers[eventId]) {
2379
+ issues.push({
2380
+ path: ["eventHandlers", eventId],
2381
+ message: 'Falta handler para el evento "' + eventId + '".',
2382
+ });
2383
+ }
2384
+ }
2385
+ for (const handlerId of Object.keys(eventHandlers)) {
2386
+ if (!eventEngineResult.data.events[handlerId]) {
2387
+ issues.push({
2388
+ path: ["eventHandlers", handlerId],
2389
+ message: 'El handler "' + handlerId + '" no corresponde a ningún evento.',
2390
+ });
2391
+ }
2392
+ }
2393
+ }
2394
+
2395
+ if (issues.length > 0) {
2396
+ printIssues(issues);
2397
+ process.exitCode = 1;
2398
+ } else {
2399
+ console.log("✓ Simulador válido");
2400
+ console.log(" " + Object.keys(prototypeMockScenarios.scenarios).length + " escenarios");
2401
+ console.log(" datasetVersion " + prototypeMockScenarios.datasetVersion);
2402
+ }
2403
+ } catch (error) {
2404
+ if (error instanceof ZodError) {
2405
+ printIssues(
2406
+ error.issues.map((issue) => ({ path: issue.path.map(String), message: issue.message })),
2407
+ );
2408
+ } else {
2409
+ console.error("✗ No se pudo cargar el simulador");
2410
+ console.error(" " + (error instanceof Error ? error.message : String(error)));
2411
+ }
2412
+ process.exitCode = 1;
2413
+ }
2414
+ `,He=async e=>{let t=async(o,r)=>{let a=Ne.join(e,o);await Fe.mkdir(Ne.dirname(a),{recursive:!0}),await Fe.writeFile(a,r)};await t(Lt,_t),await t(ce,Ft),await t(le,Nt),await t(zt,Ht),await t(At,Kt),await t(Ot,qt),await t(Dt,Ut),await t($t,Bt),await t(de,Jt),await t(Vt,Gt)};var Zt={feature:["src/app","src/features","src/shared/assets","src/shared/components/base","src/shared/hooks","src/shared/services","src/shared/lib","src/shared/types"],layer:["src/assets","src/components/base","src/hooks","src/services","src/lib","src/types","src/views","src/stores"]},Wt="10.33.3",Xt=e=>{let t=e.architecture==="feature"?{"react-router":"^7.9.0"}:{};return JSON.stringify({name:e.projectName,private:!0,version:"0.0.0",type:"module",packageManager:`pnpm@${Wt}`,scripts:{dev:"vite","check:data-contract":"tsx scripts/check-data-contract.ts","check:event-engine":"tsx scripts/check-event-engine.ts","check:simulator":"tsx scripts/check-simulator.ts","check:prototype":"pnpm check:data-contract && pnpm check:event-engine && pnpm check:simulator","build:app":"tsc -b && vite build",build:"pnpm check:prototype && pnpm build:app",preview:"vite preview",lint:"eslint .",format:"prettier --write ."},dependencies:{clsx:"^2.1.1",react:"^19.2.6","react-dom":"^19.2.6",...t,"tailwind-merge":"^3.3.1",zod:"^4.1.0"},devDependencies:{"@babel/core":"^7.29.7","@eslint/js":"^10.0.1","@rolldown/plugin-babel":"^0.2.3","@tailwindcss/vite":"^4.3.0","@types/babel__core":"^7.20.5","@types/node":"^24.12.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","babel-plugin-react-compiler":"^1.0.0",eslint:"^10.4.1","eslint-plugin-jsx-a11y":"^6.10.2","eslint-plugin-react-hooks":"^7.1.1","eslint-plugin-simple-import-sort":"^13.0.0",globals:"^17.6.0",prettier:"^3.8.4",tailwindcss:"^4.3.0",terser:"^5.48.0",tsx:"^4.19.0",typescript:"~5.9.3","typescript-eslint":"^8.61.0",vite:"^8.0.12"}},null,2)+`
2415
+ `},Yt=`import path from "node:path";
26
2416
 
27
2417
  import babel from "@rolldown/plugin-babel";
28
2418
  import tailwindcss from "@tailwindcss/vite";
@@ -52,7 +2442,7 @@ export default defineConfig({
52
2442
  },
53
2443
  },
54
2444
  });
55
- `,Xe=`import js from "@eslint/js";
2445
+ `,Qt=`import js from "@eslint/js";
56
2446
  import jsxA11y from "eslint-plugin-jsx-a11y";
57
2447
  import reactHooks from "eslint-plugin-react-hooks";
58
2448
  import simpleImportSort from "eslint-plugin-simple-import-sort";
@@ -81,14 +2471,14 @@ export default tseslint.config(
81
2471
  },
82
2472
  },
83
2473
  );
84
- `,Qe=`{
2474
+ `,eo=`{
85
2475
  "printWidth": 100
86
2476
  }
87
- `,Ze=`pnpm-lock.yaml
2477
+ `,to=`pnpm-lock.yaml
88
2478
  src/lexy-theme.css
89
2479
  public/fonts/
90
2480
  dist/
91
- `,et=`name: CI
2481
+ `,oo=`name: CI
92
2482
 
93
2483
  on:
94
2484
  push:
@@ -108,9 +2498,12 @@ jobs:
108
2498
  - run: pnpm install --frozen-lockfile
109
2499
  - run: pnpm lint
110
2500
  - run: pnpm exec prettier --check .
111
- - run: pnpm build
2501
+ - run: pnpm check:data-contract
2502
+ - run: pnpm check:event-engine
2503
+ - run: pnpm check:simulator
2504
+ - run: pnpm build:app
112
2505
  - run: pnpm audit --audit-level high
113
- `,tt=`{
2506
+ `,no=`{
114
2507
  "files": [],
115
2508
  "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
116
2509
  "compilerOptions": {
@@ -118,7 +2511,7 @@ jobs:
118
2511
  "paths": { "@/*": ["./src/*"] }
119
2512
  }
120
2513
  }
121
- `,ot=`{
2514
+ `,ao=`{
122
2515
  "compilerOptions": {
123
2516
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
124
2517
  "target": "ES2022",
@@ -142,7 +2535,7 @@ jobs:
142
2535
  },
143
2536
  "include": ["src"]
144
2537
  }
145
- `,rt=`{
2538
+ `,ro=`{
146
2539
  "compilerOptions": {
147
2540
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
148
2541
  "target": "ES2023",
@@ -159,7 +2552,7 @@ jobs:
159
2552
  },
160
2553
  "include": ["vite.config.ts"]
161
2554
  }
162
- `,nt=e=>`<!doctype html>
2555
+ `,io=e=>`<!doctype html>
163
2556
  <html lang="es">
164
2557
  <head>
165
2558
  <meta charset="UTF-8" />
@@ -171,9 +2564,9 @@ jobs:
171
2564
  <script type="module" src="/src/main.tsx"></script>
172
2565
  </body>
173
2566
  </html>
174
- `,st=`@import "tailwindcss";
2567
+ `,so=`@import "tailwindcss";
175
2568
  @import "./lexy-theme.css";
176
- `,it=e=>e.architecture==="feature"?`import "./index.css";
2569
+ `,co=e=>e.architecture==="feature"?`import "./index.css";
177
2570
 
178
2571
  import { StrictMode } from "react";
179
2572
  import { createRoot } from "react-dom/client";
@@ -200,18 +2593,18 @@ createRoot(document.getElementById("root")!).render(
200
2593
  <App />
201
2594
  </StrictMode>,
202
2595
  );
203
- `,be=` <main className="flex min-h-screen items-center justify-center bg-background text-foreground">
2596
+ `,Ke=` <main className="flex min-h-screen items-center justify-center bg-background text-foreground">
204
2597
  <div className="text-center">
205
2598
  <h1 className="text-2xl font-semibold">Proyecto Lexy listo</h1>
206
2599
  <p className="mt-2 text-sm text-muted-foreground">
207
2600
  Trae tu primer componente: <code>npx create-lexy add button</code>
208
2601
  </p>
209
2602
  </div>
210
- </main>`,at=e=>e.architecture==="feature"?`import { Route, Routes } from "react-router";
2603
+ </main>`,lo=e=>e.architecture==="feature"?`import { Route, Routes } from "react-router";
211
2604
 
212
2605
  const Home = () => {
213
2606
  return (
214
- ${be}
2607
+ ${Ke}
215
2608
  );
216
2609
  };
217
2610
 
@@ -224,52 +2617,53 @@ export const App = () => {
224
2617
  };
225
2618
  `:`export const App = () => {
226
2619
  return (
227
- ${be}
2620
+ ${Ke}
228
2621
  );
229
2622
  };
230
- `,ct=`import { type ClassValue, clsx } from "clsx";
2623
+ `,po=`import { type ClassValue, clsx } from "clsx";
231
2624
  import { twMerge } from "tailwind-merge";
232
2625
 
233
2626
  export function cn(...inputs: ClassValue[]) {
234
2627
  return twMerge(clsx(inputs));
235
2628
  }
236
- `,lt=`node_modules/
2629
+ `,mo=`node_modules/
237
2630
  dist/
238
2631
  *.log
239
2632
  .env
240
- `,pt=e=>`# ${e.projectName}
2633
+ `,uo=e=>`# ${e.projectName}
241
2634
 
242
2635
  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
2636
 
244
2637
  \`\`\`bash
245
2638
  pnpm dev # desarrollo
2639
+ pnpm check:prototype # validar datos, cargas/eventos y simulador
246
2640
  npx create-lexy view button # ver un componente antes de instalarlo
247
2641
  npx create-lexy add button # instalarlo (local y editable)
248
2642
  npx create-lexy doctor # divergencia con el registry
249
2643
  \`\`\`
250
- `,dt=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":`${O}@latest/r/{name}.json`}},null,2)+`
251
- `},mt=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:O},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:{}}},ve=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],n=[];for(let c of o)try{await E.access(I.join(t,c)),n.push(c)}catch{}if(n.length>0)throw new Error(`El directorio actual ya contiene archivos de un proyecto (${n.join(", ")}). Ejecuta el comando dentro de una carpeta vac\xEDa.`);for(let c of Ke[e.architecture])await E.mkdir(I.join(t,c),{recursive:!0});let r=(c,d)=>E.writeFile(I.join(t,c),d);await r("package.json",We(e)),await r("vite.config.ts",Ye),await r("tsconfig.json",tt),await r("tsconfig.app.json",ot),await r("tsconfig.node.json",rt),await r("eslint.config.js",Xe),await r(".prettierrc",Qe),await r(".prettierignore",Ze),await E.mkdir(I.join(t,".github/workflows"),{recursive:!0}),await r(".github/workflows/ci.yml",et),await r("index.html",nt(e)),await r(".gitignore",lt),await r("README.md",pt(e)),await r("src/index.css",st),await r("src/main.tsx",it(e));let s=e.architecture==="feature"?"src/app":"src/views";await r(`${s}/App.tsx`,at(e));let i=mt(e);await r(".lexy",JSON.stringify(i,null,2)+`
252
- `),await r("components.json",dt(e)),await E.copyFile(me,I.join(t,"src/lexy-theme.css")),await E.mkdir(I.join(t,"public/fonts"),{recursive:!0}),await E.cp(ue,I.join(t,"public/fonts"),{recursive:!0});let p=I.join(t,i.paths.lib,"utils");await E.mkdir(p,{recursive:!0}),await E.writeFile(I.join(p,"cn.ts"),ct)};var ht=["feature","layer"],xt=["cliente","crm","mixto"],wt=e=>new Promise((t,o)=>{ut("pnpm",["install"],{cwd:e,stdio:"ignore",shell:!0}).on("close",r=>r===0?t():o(new Error(`pnpm install fall\xF3 con c\xF3digo ${r}`)))}),je=async(e,t)=>{let o;!!(e&&t.type)?(ht.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&&!xt.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 we(e),ft(`Nombre: ${o.projectName}
2644
+ `,fo=e=>{let o=e.architecture==="feature"?"@/shared":"@";return JSON.stringify({$schema:"https://ui.shadcn.com/schema.json",style:"new-york",rsc:!1,tsx:!0,tailwind:{config:"",css:"src/index.css",baseColor:"neutral",cssVariables:!0},aliases:{components:`${o}/components`,ui:`${o}/components/base`,utils:`${o}/lib/utils/cn`,lib:`${o}/lib`,hooks:`${o}/hooks`},registries:{"@lexy":`${F}@latest/r/{name}.json`}},null,2)+`
2645
+ `},yo=e=>{let t=e.architecture==="feature";return{version:"2.0.0",generatedAt:new Date().toISOString(),architecture:e.architecture,world:e.world,registry:{package:"@lexydesign/registry",cdn:F},componentImportPattern:t?"@/shared/components/base/{Component}":"@/components/base/{Component}",paths:{components:t?"src/shared/components/base":"src/components/base",hooks:t?"src/shared/hooks":"src/hooks",services:t?"src/shared/services":"src/services",lib:t?"src/shared/lib":"src/lib",views:t?"src/features":"src/views"},prototype:{dataContractVersion:"1",dataContractPath:re,eventEngineVersion:"1",eventEngineContractPath:ie,eventEnginePath:se,simulatorVersion:"1",simulatorProfilePath:ce,simulatorScenariosPath:le,simulatorPath:de,defaultScenarioId:"base"},installed:{}}},qe=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],r=[];for(let l of o)try{await R.access(T.join(t,l)),r.push(l)}catch{}if(r.length>0)throw new Error(`El directorio actual ya contiene archivos de un proyecto (${r.join(", ")}). Ejecuta el comando dentro de una carpeta vac\xEDa.`);for(let l of Zt[e.architecture])await R.mkdir(T.join(t,l),{recursive:!0});let a=(l,y)=>R.writeFile(T.join(t,l),y);await a("package.json",Xt(e)),await a("vite.config.ts",Yt),await a("tsconfig.json",no),await a("tsconfig.app.json",ao),await a("tsconfig.node.json",ro),await a("eslint.config.js",Qt),await a(".prettierrc",eo),await a(".prettierignore",to),await R.mkdir(T.join(t,".github/workflows"),{recursive:!0}),await a(".github/workflows/ci.yml",oo),await a("index.html",io(e)),await a(".gitignore",mo),await a("README.md",uo(e)),await a("src/index.css",so),await a("src/main.tsx",co(e)),await De(t,e.projectName),await _e(t),await He(t);let i=e.architecture==="feature"?"src/app":"src/views";await a(`${i}/App.tsx`,lo(e));let n=yo(e);await a(".lexy",JSON.stringify(n,null,2)+`
2646
+ `),await a("components.json",fo(e)),await R.copyFile(Pe,T.join(t,"src/lexy-theme.css")),await R.mkdir(T.join(t,"public/fonts"),{recursive:!0}),await R.cp(Ce,T.join(t,"public/fonts"),{recursive:!0});let p=T.join(t,n.paths.lib,"utils");await R.mkdir(p,{recursive:!0}),await R.writeFile(T.join(p,"cn.ts"),po)};var xo=["feature","layer"],bo=["cliente","crm","mixto"],Io=e=>new Promise((t,o)=>{ho("pnpm",["install"],{cwd:e,stdio:"ignore",shell:!0}).on("close",a=>a===0?t():o(new Error(`pnpm install fall\xF3 con c\xF3digo ${a}`)))}),Ue=async(e,t)=>{let o;!!(e&&t.type)?(xo.includes(t.type)||(console.error(E.red(`Error: la arquitectura "${t.type}" no es v\xE1lida. Usa "feature" o "layer".`)),process.exit(1)),t.world&&!bo.includes(t.world)&&(console.error(E.red(`Error: el mundo "${t.world}" no es v\xE1lido. Usa "cliente", "crm" o "mixto".`)),process.exit(1)),t.world||console.warn(E.yellow('Aviso: sin --world; se asume "mixto". ')+E.dim("Pasa --world cliente|crm para fijar densidad y voz desde el inicio.")),o={projectName:e,architecture:t.type,world:t.world??"mixto"},console.log(E.blue("Modo automatizado detectado. Saltando interfaz interactiva..."))):o=await ze(e),go(`Nombre: ${o.projectName}
253
2647
  Arquitectura: ${o.architecture}
254
- Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let r=yt();r.start("Construyendo tu proyecto...");let s=process.cwd();try{await ve(o,s)}catch(i){throw r.stop(v.red("No se pudo crear el proyecto.")),i}t.install!==!1&&(r.message("Instalando dependencias base..."),await wt(s)),r.stop("\xA1Proyecto construido con \xE9xito!"),gt(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 bt from"fs/promises";import vt from"path";import F from"picocolors";import ee from"picocolors";var q=(e,t)=>{let o=e.split(`
259
- `),n=t.split(`
260
- `),r=o.length,s=n.length,i=Array.from({length:r+1},()=>new Array(s+1).fill(0));for(let m=r-1;m>=0;m--)for(let g=s-1;g>=0;g--)i[m][g]=o[m]===n[g]?i[m+1][g+1]+1:Math.max(i[m+1][g],i[m][g+1]);let p=[],c=0,d=0;for(;c<r&&d<s;)o[c]===n[d]?(p.push({type:"same",text:o[c]}),c++,d++):i[c+1][d]>=i[c][d+1]?p.push({type:"del",text:o[c++]}):p.push({type:"add",text:n[d++]});for(;c<r;)p.push({type:"del",text:o[c++]});for(;d<s;)p.push({type:"add",text:n[d++]});return p},G=e=>e.some(t=>t.type!=="same"),Ce=(e,t=3)=>{if(!G(e))return null;let o=new Array(e.length).fill(!1);e.forEach((s,i)=>{if(s.type!=="same")for(let p=Math.max(0,i-t);p<=Math.min(e.length-1,i+t);p++)o[p]=!0});let n=[],r=!1;for(let s=0;s<e.length;s++){if(!o[s]){r||(n.push(ee.dim(" \u22EF")),r=!0);continue}r=!1;let i=e[s];i.type==="same"?n.push(` ${i.text}`):i.type==="del"?n.push(ee.red(`- ${i.text}`)):n.push(ee.green(`+ ${i.text}`))}return n.join(`
261
- `)};var Pe=async e=>{let t=process.cwd(),o=await k(t),n=e.toLowerCase(),{item:r,version:s}=await x(n),i=o.installed[n];console.log(F.bold(`
262
- ${r.title??n}`)+F.dim(` \xB7 instalado con registry@${i??"?"} \xB7 vigente registry@${s}
263
- `));let p=!1;for(let c of P(r,o)){let d=await bt.readFile(vt.join(t,c.relPath),"utf-8").catch(()=>null);if(d===null){console.log(F.red(`\u2717 ${c.relPath} no existe localmente`)+F.dim(` \u2014 cr\xE9alo con \`create-lexy add ${n}\``)),p=!0;continue}let m=Ce(q(d,c.content));m===null?console.log(F.green(`\u2713 ${c.relPath}`)+F.dim(" sin diferencias")):(p=!0,console.log(F.yellow(`\xB1 ${c.relPath}`)+F.dim(" (\u2212 local \xB7 + registry)")),console.log(m))}p&&(process.exitCode=1)};import A from"fs/promises";import S from"path";import j from"picocolors";var $e=async e=>{let t=process.cwd(),o=0,n=u=>console.log(j.green(" \u2713 ")+u),r=u=>console.log(j.yellow(" \u2022 ")+u),s=(u,a)=>{o=1,console.log(j.red(" \u2717 ")+u),a&&console.log(j.dim(` \u2192 ${a}`))};console.log(j.bold(`
2648
+ Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let a=So();a.start("Construyendo tu proyecto...");let i=process.cwd();try{await qe(o,i)}catch(n){throw a.stop(E.red("No se pudo crear el proyecto.")),n}t.install!==!1&&(a.message("Instalando dependencias base..."),await Io(i)),a.stop("\xA1Proyecto construido con \xE9xito!"),vo(E.green(`Todo listo en el directorio actual
2649
+
2650
+ `)+E.dim(`Siguientes pasos:
2651
+ `)+E.cyan(` pnpm dev
2652
+ `)+E.cyan(" npx create-lexy add button"))};import Eo from"fs/promises";import ko from"path";import z from"picocolors";import pe from"picocolors";var H=(e,t)=>{let o=e.split(`
2653
+ `),r=t.split(`
2654
+ `),a=o.length,i=r.length,n=Array.from({length:a+1},()=>new Array(i+1).fill(0));for(let u=a-1;u>=0;u--)for(let d=i-1;d>=0;d--)n[u][d]=o[u]===r[d]?n[u+1][d+1]+1:Math.max(n[u+1][d],n[u][d+1]);let p=[],l=0,y=0;for(;l<a&&y<i;)o[l]===r[y]?(p.push({type:"same",text:o[l]}),l++,y++):n[l+1][y]>=n[l][y+1]?p.push({type:"del",text:o[l++]}):p.push({type:"add",text:r[y++]});for(;l<a;)p.push({type:"del",text:o[l++]});for(;y<i;)p.push({type:"add",text:r[y++]});return p},X=e=>e.some(t=>t.type!=="same"),Be=(e,t=3)=>{if(!X(e))return null;let o=new Array(e.length).fill(!1);e.forEach((i,n)=>{if(i.type!=="same")for(let p=Math.max(0,n-t);p<=Math.min(e.length-1,n+t);p++)o[p]=!0});let r=[],a=!1;for(let i=0;i<e.length;i++){if(!o[i]){a||(r.push(pe.dim(" \u22EF")),a=!0);continue}a=!1;let n=e[i];n.type==="same"?r.push(` ${n.text}`):n.type==="del"?r.push(pe.red(`- ${n.text}`)):r.push(pe.green(`+ ${n.text}`))}return r.join(`
2655
+ `)};var Je=async e=>{let t=process.cwd(),o=await L(t),r=e.toLowerCase(),{item:a,version:i}=await x(r),n=o.installed[r];console.log(z.bold(`
2656
+ ${a.title??r}`)+z.dim(` \xB7 instalado con registry@${n??"?"} \xB7 vigente registry@${i}
2657
+ `));let p=!1;for(let l of C(a,o)){let y=await Eo.readFile(ko.join(t,l.relPath),"utf-8").catch(()=>null);if(y===null){console.log(z.red(`\u2717 ${l.relPath} no existe localmente`)+z.dim(` \u2014 cr\xE9alo con \`create-lexy add ${r}\``)),p=!0;continue}let u=Be(H(y,l.content));u===null?console.log(z.green(`\u2713 ${l.relPath}`)+z.dim(" sin diferencias")):(p=!0,console.log(z.yellow(`\xB1 ${l.relPath}`)+z.dim(" (\u2212 local \xB7 + registry)")),console.log(u))}p&&(process.exitCode=1)};import A from"fs/promises";import f from"path";import k from"picocolors";var Ge=async e=>{let t=process.cwd(),o=0,r=s=>console.log(k.green(" \u2713 ")+s),a=s=>console.log(k.yellow(" \u2022 ")+s),i=(s,c)=>{o=1,console.log(k.red(" \u2717 ")+s),c&&console.log(k.dim(` \u2192 ${c}`))};console.log(k.bold(`
264
2658
  Diagn\xF3stico del proyecto Lexy
265
- `));let i;try{i=await k(t),n(`.lexy v\xE1lido (arquitectura ${i.architecture}${i.world?`, mundo ${i.world}`:""})`)}catch(u){return s(u.message),1}await A.access(S.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?n("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=S.join(i.paths.lib,"utils/cn.ts");await A.access(S.join(t,c)).then(()=>!0,()=>!1)?n(`helper cn presente (${c})`):s(`falta el helper cn (${c})`);let m=S.join(t,"ai/lexy-ai-manifest.json"),g=await A.readFile(m,"utf-8").catch(()=>null);if(g===null)console.log(j.dim(" \xB7 capa de IA no instalada (npx @lexydesign/ai install) \u2014 opcional"));else try{let u=JSON.parse(g),a=u.files??[],l=[];for(let f of a)await A.access(S.join(t,f)).then(()=>!0,()=>!1)||l.push(f);l.length>0?s(`capa de IA ${u.aiVersion??"?"} incompleta \u2014 faltan ${l.length} archivo(s) (${l.slice(0,3).join(", ")}${l.length>3?", \u2026":""})`,"npx @lexydesign/ai install"):n(`capa de IA ${u.aiVersion??"?"} completa (${a.length} archivos)`)}catch{s("ai/lexy-ai-manifest.json no es JSON v\xE1lido","regenera con `npx @lexydesign/ai install`")}let z=Object.entries(i.installed);if(z.length===0)console.log(j.dim(`
266
- Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let u=await J();console.log(j.bold(`
267
- Componentes instalados (registry vigente: ${u})
268
- `));for(let[a,l]of z){let f;try{f=await x(a)}catch(y){s(`${a}: ${y.message}`);continue}let b=P(f.item,i),L=[],V=!1;for(let y of b){let C=await A.readFile(S.join(t,y.relPath),"utf-8").catch(()=>null);C===null?L.push(y.relPath):G(q(C,y.content))&&(V=!0)}if(L.length>0){s(`${a}: archivos faltantes (${L.join(", ")})`,`create-lexy add ${a}`);continue}if(!V){n(`${a} al d\xEDa (${f.version})`);continue}let D=!0;if(l&&l!==f.version)try{let y=await x(a,l),C=P(y.item,i);D=!1;for(let ne of C){let se=await A.readFile(S.join(t,ne.relPath),"utf-8").catch(()=>null);(se===null||G(q(se,ne.content)))&&(D=!0)}}catch{}D?e.strict?s(`${a}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${a}`):r(`${a}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${a}\`)`):s(`${a}: desactualizado (instalado con ${l}, vigente ${f.version})`,`create-lexy diff ${a} \xB7 create-lexy add ${a} --overwrite`)}}return console.log(o===0?j.green(`
2659
+ `));let n;try{n=await L(t),r(`.lexy v\xE1lido (arquitectura ${n.architecture}${n.world?`, mundo ${n.world}`:""})`)}catch(s){return i(s.message),1}await A.access(f.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?r("theme presente (src/lexy-theme.css)"):i("falta src/lexy-theme.css","re-cr\xE9alo desde un proyecto nuevo o copia el theme del registry");let l=f.join(n.paths.lib,"utils/cn.ts");await A.access(f.join(t,l)).then(()=>!0,()=>!1)?r(`helper cn presente (${l})`):i(`falta el helper cn (${l})`);let u=await A.readFile(f.join(t,"package.json"),"utf-8").then(s=>JSON.parse(s)).catch(()=>null),d=async(s,c)=>{await A.access(f.join(t,s)).then(()=>!0,()=>!1)?r(`${c} presente (${s})`):i(`falta ${c} (${s})`)},P=(s,c)=>{if(!u){i("package.json no se pudo leer para validar scripts del prototipo");return}let g=u.scripts?.[s];if(!g){i(`falta el script ${s} en package.json`,c?`agrega \`${c}\``:void 0);return}if(c&&!g.includes(c)){i(`el script ${s} no ejecuta ${c}`);return}r(`script ${s} configurado`)};if(!n.prototype)a("prototipo funcional no habilitado \u2014 proyecto anterior a las specs de datos/eventos/simulador");else{let s=n.prototype.dataContractPath;if(!s)a("contrato de datos no configurado \u2014 proyecto anterior a la Spec 1");else{await d(s,"contrato de datos");let c=f.join(f.dirname(s),"data-contract-schema.ts");await d(c,"schema del contrato"),await d("scripts/check-data-contract.ts","validador del contrato"),P("check:data-contract","tsx scripts/check-data-contract.ts")}n.prototype.eventEngineVersion?(n.prototype.eventEngineContractPath?(await d(n.prototype.eventEngineContractPath,"contrato del motor de eventos"),await d(f.join(f.dirname(n.prototype.eventEngineContractPath),"event-engine-contract-schema.ts"),"schema del motor de eventos"),await d(f.join(f.dirname(n.prototype.eventEngineContractPath),"payload-schema.ts"),"schemas runtime del motor de eventos")):i("eventEngineContractPath no est\xE1 configurado en .lexy"),n.prototype.eventEnginePath?await d(n.prototype.eventEnginePath,"engine de eventos"):i("eventEnginePath no est\xE1 configurado en .lexy"),await d("scripts/check-event-engine.ts","validador del motor de eventos"),P("check:event-engine","tsx scripts/check-event-engine.ts")):a("motor de eventos no habilitado \u2014 proyecto anterior a la Spec 2"),n.prototype.simulatorVersion?(n.prototype.simulatorProfilePath?(await d(n.prototype.simulatorProfilePath,"perfil del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"simulator-contract-schema.ts"),"schema del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"projection-resolvers.ts"),"resolvers de proyecci\xF3n del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"data-load-handlers.ts"),"handlers de carga del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"event-handlers.ts"),"handlers de evento del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"mock-store.ts"),"store persistente del simulador"),await d(f.join(f.dirname(n.prototype.simulatorProfilePath),"mock-backend-executor.ts"),"executor mock del simulador")):i("simulatorProfilePath no est\xE1 configurado en .lexy"),n.prototype.simulatorScenariosPath?await d(n.prototype.simulatorScenariosPath,"escenarios del simulador"):i("simulatorScenariosPath no est\xE1 configurado en .lexy"),n.prototype.simulatorPath?await d(n.prototype.simulatorPath,"ensamblaje del simulador"):i("simulatorPath no est\xE1 configurado en .lexy"),await d("scripts/check-simulator.ts","validador del simulador"),P("check:simulator","tsx scripts/check-simulator.ts"),P("check:prototype","check:simulator")):a("simulador persistente no habilitado \u2014 proyecto anterior a la Spec 3")}let V=f.join(t,"ai/lexy-ai-manifest.json"),m=await A.readFile(V,"utf-8").catch(()=>null);if(m===null)console.log(k.dim(" \xB7 capa de IA no instalada (npx @lexydesign/ai install) \u2014 opcional"));else try{let s=JSON.parse(m),c=s.files??[],g=[];for(let I of c)await A.access(f.join(t,I)).then(()=>!0,()=>!1)||g.push(I);g.length>0?i(`capa de IA ${s.aiVersion??"?"} incompleta \u2014 faltan ${g.length} archivo(s) (${g.slice(0,3).join(", ")}${g.length>3?", \u2026":""})`,"npx @lexydesign/ai install"):r(`capa de IA ${s.aiVersion??"?"} completa (${c.length} archivos)`)}catch{i("ai/lexy-ai-manifest.json no es JSON v\xE1lido","regenera con `npx @lexydesign/ai install`")}let h=Object.entries(n.installed);if(h.length===0)console.log(k.dim(`
2660
+ Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let s=await G();console.log(k.bold(`
2661
+ Componentes instalados (registry vigente: ${s})
2662
+ `));for(let[c,g]of h){let I;try{I=await x(c)}catch(D){i(`${c}: ${D.message}`);continue}let q=C(I.item,n),v=[],O=!1;for(let D of q){let U=await A.readFile(f.join(t,D.relPath),"utf-8").catch(()=>null);U===null?v.push(D.relPath):X(H(U,D.content))&&(O=!0)}if(v.length>0){i(`${c}: archivos faltantes (${v.join(", ")})`,`create-lexy add ${c}`);continue}if(!O){r(`${c} al d\xEDa (${I.version})`);continue}let ee=!0;if(g&&g!==I.version)try{let D=await x(c,g),U=C(D.item,n);ee=!1;for(let ve of U){let Se=await A.readFile(f.join(t,ve.relPath),"utf-8").catch(()=>null);(Se===null||X(H(Se,ve.content)))&&(ee=!0)}}catch{}ee?e.strict?i(`${c}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${c}`):a(`${c}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${c}\`)`):i(`${c}: desactualizado (instalado con ${g}, vigente ${I.version})`,`create-lexy diff ${c} \xB7 create-lexy add ${c} --overwrite`)}}return console.log(o===0?k.green(`
269
2663
  Todo en orden.
270
- `):j.red(`
2664
+ `):k.red(`
271
2665
  Hay problemas que atender (exit 1).
272
- `)),o};import te from"fs/promises";import Ee from"path";import{note as jt,outro as Ct}from"@clack/prompts";import oe from"picocolors";var W="LEXY-KICKOFF.md",Pt=`# Kickoff de proyecto Lexy \u2014 protocolo de entrevista
2666
+ `)),o};import me from"fs/promises";import Ze from"path";import{note as wo,outro as Po}from"@clack/prompts";import ue from"picocolors";var Y="LEXY-KICKOFF.md",Co=`# Kickoff de proyecto Lexy \u2014 protocolo de entrevista
273
2667
 
274
2668
  > **Para el agente (Claude Code):** este archivo es tu guion. La persona que
275
2669
  > tienes enfrente es **dise\xF1adora, no programadora**: no le preguntes por
@@ -291,8 +2685,20 @@ hazlas de a poco, repregunta y resume lo que entendiste. Cubre:
291
2685
  dashboard, listados, vistas de detalle)?
292
2686
  - **Pantallas y flujos clave** \u2014 \xBFqu\xE9 vistas importan y qu\xE9 hace la persona en
293
2687
  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?
2688
+ - **Datos de la experiencia** \u2014 \xBFqu\xE9 objetos principales existen? \xBFQu\xE9 necesita
2689
+ ver o modificar la persona? \xBFQu\xE9 relaciones necesita entender? \xBFQu\xE9 estados
2690
+ cambian lo que aparece en pantalla? \xBFQu\xE9 supuestos todav\xEDa debe validar TI?
2691
+ - **Cargas de datos** \u2014 \xBFqu\xE9 informaci\xF3n debe llegar desde backend para cada
2692
+ pantalla? \xBFQu\xE9 significa que una carga venga vac\xEDa? \xBFQu\xE9 errores o latencias
2693
+ vale la pena poder probar?
2694
+ - **Eventos publicados** \u2014 \xBFqu\xE9 escrituras hace la interfaz? \xBFQu\xE9 hecho ocurri\xF3
2695
+ cuando se publican? \xBFQu\xE9 entidad cambia visualmente despu\xE9s de ver "Evento
2696
+ publicado"? \xBFQu\xE9 debe mantenerse intacto si falla?
2697
+ - **Datos mock y escenarios** \u2014 \xBFqu\xE9 situaciones reales debe representar el
2698
+ prototipo? \xBFQu\xE9 casos l\xEDmite importan? \xBFQu\xE9 datos deben verse chilenos? \xBFQu\xE9
2699
+ campos son sensibles aunque sean sint\xE9ticos?
2700
+ - **Referencias** \u2014 \xBFhay un Figma, capturas o un producto de referencia que
2701
+ mande sobre composiciones gen\xE9ricas?
296
2702
 
297
2703
  ## 2. Traducir las respuestas a flags (no se lo muestres a la persona)
298
2704
 
@@ -318,22 +2724,43 @@ npx @lexydesign/ai install
318
2724
 
319
2725
  ## 4. Volcar la entrevista en el brief vivo
320
2726
 
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.
2727
+ 1. Abre \`ai/PROJECT-CONTEXT.md\` (lo cre\xF3 \`@lexydesign/ai install\`) y
2728
+ compl\xE9talo con lo que recopilaste: **qu\xE9 se construye**, **para qui\xE9n**,
2729
+ **pantallas y flujos clave**, un resumen de **datos principales**,
2730
+ **referencias** y **decisiones/restricciones** ya tomadas.
2731
+ 2. Traduce los objetos, datos, relaciones, estados y supuestos conversados a
2732
+ \`src/prototype/data-contract/prototype-data-contract.ts\`. No inventes
2733
+ backend: lo que nazca desde usabilidad queda como
2734
+ \`origin: "generatedByUsability"\` y
2735
+ \`technicalValidation.status: "pendingTi"\`.
2736
+ 3. Declara las comunicaciones externas en
2737
+ \`src/prototype/event-engine/prototype-event-engine-contract.ts\`: lecturas
2738
+ remotas como **cargas de datos** y escrituras como **eventos publicados**.
2739
+ No declares interacciones locales.
2740
+ 4. Completa \`src/prototype/simulator/\` con perfil, escenarios, resolvers y
2741
+ handlers m\xEDnimos. Usa datos sint\xE9ticos, deterministas y formato chileno.
2742
+ 5. Ejecuta \`pnpm check:prototype\` y corrige cualquier inconsistencia antes de
2743
+ empezar a implementar pantallas.
2744
+
2745
+ \`PROJECT-CONTEXT.md\` es el resumen humano. El contrato TypeScript es la fuente
2746
+ estructurada de datos para las pr\xF3ximas sesiones. El motor explica qu\xE9 se carga
2747
+ o publica y el simulador entrega datos mock persistentes sin backend real.
325
2748
 
326
2749
  ## 5. Cerrar
327
2750
 
328
- Borra este archivo (\`${W}\`): su trabajo termin\xF3. Conf\xEDrmale a la
2751
+ Borra este archivo (\`${Y}\`): su trabajo termin\xF3. Conf\xEDrmale a la
329
2752
  persona que su proyecto est\xE1 listo y prop\xF3n el primer paso concreto
330
2753
  (p. ej. "\xBFDise\xF1amos la pantalla de inicio?").
331
- `,Ie=async e=>{let t=process.cwd();for(let r of["package.json",".lexy"])if(await te.access(Ee.join(t,r)).then(()=>!0,()=>!1))throw new Error(`El directorio actual ya contiene un proyecto (${r}). \`kickoff\` es para empezar de cero en una carpeta vac\xEDa.`);let o=Ee.join(t,W);if(await te.access(o).then(()=>!0,()=>!1)&&!e.force)throw new Error(`${W} ya existe. Usa --force para regenerarlo.`);await te.writeFile(o,Pt),jt(`Se cre\xF3 ${oe.cyan(W)} en esta carpeta.
2754
+ `,We=async e=>{let t=process.cwd();for(let a of["package.json",".lexy"])if(await me.access(Ze.join(t,a)).then(()=>!0,()=>!1))throw new Error(`El directorio actual ya contiene un proyecto (${a}). \`kickoff\` es para empezar de cero en una carpeta vac\xEDa.`);let o=Ze.join(t,Y);if(await me.access(o).then(()=>!0,()=>!1)&&!e.force)throw new Error(`${Y} ya existe. Usa --force para regenerarlo.`);await me.writeFile(o,Co),wo(`Se cre\xF3 ${ue.cyan(Y)} en esta carpeta.
332
2755
 
333
- Abre la carpeta con Claude Code y di: `+oe.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
334
- El agente te entrevistar\xE1 y montar\xE1 el proyecto por ti.`,"Kickoff listo"),Ct(oe.green("Sin flags, sin prompts t\xE9cnicos \u2014 solo conversa con el agente."))};import $t from"fs/promises";import re from"path";import w from"picocolors";var _=e=>console.log(w.bold(w.cyan(`
2756
+ Abre la carpeta con Claude Code y di: `+ue.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
2757
+ El agente te entrevistar\xE1 y montar\xE1 el proyecto por ti.`,"Kickoff listo"),Po(ue.green("Sin flags, sin prompts t\xE9cnicos \u2014 solo conversa con el agente."))};import fe from"fs/promises";import Q from"path";import w from"picocolors";var he="@lexydesign/kit",Xe=`https://cdn.jsdelivr.net/npm/${he}`,Ye=8e3,ye=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Ye)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return Buffer.from(await t.arrayBuffer())},jo=async()=>{try{let t=await fetch(`https://registry.npmjs.org/${he}/latest`,{signal:AbortSignal.timeout(Ye)});if(t.ok)return(await t.json()).version}catch{}return JSON.parse((await ye(`${Xe}@latest/package.json`)).toString("utf-8")).version},Qe=async e=>{let t=process.cwd(),o=Q.resolve(t,e.dir??"lexy-kit"),r=Q.relative(t,o)||".";if(await fe.access(o).then(()=>!0).catch(()=>!1)&&!e.force)throw new Error(`Ya existe ${w.bold(r)}. Usa ${w.cyan("--force")} para sobrescribir o ${w.cyan("--dir <ruta>")} para otra carpeta.`);let i=await jo();console.log(w.dim(`
2758
+ Trayendo ${he}@${i} \u2192 ${r}/
2759
+ `));let n=`${Xe}@${i}/kit`,p=JSON.parse((await ye(`${n}/manifest.json`)).toString("utf-8")),l=["index.html","kit.css","kit.js","manifest.json","README.md","AGENTS.md","llms.txt","llms-full.txt","fonts/noto-sans-latin.woff2","fonts/OFL-NotoSans.txt",...p.guides?.pautas??[],...p.components.map(u=>u.file),...p.blocks.map(u=>u.file)],y=0;for(let u of l)try{let d=await ye(`${n}/${u}`),P=Q.join(o,u);await fe.mkdir(Q.dirname(P),{recursive:!0}),await fe.writeFile(P,d),y+=1}catch(d){console.warn(w.yellow(`\u26A0 no se pudo traer ${u}`)+w.dim(` (${d.message})`))}console.log(w.green("\u2713 Kit listo")+w.dim(` \u2014 ${y} archivo(s), ${p.components.length} componente(s) + ${p.blocks.length} block(s)
2760
+ `)),console.log(` Abre ${w.bold(`${r}/index.html`)} en el navegador.`),console.log(w.dim(` El cat\xE1logo m\xE1quina-legible est\xE1 en ${r}/manifest.json.
2761
+ `))};import Ro from"fs/promises";import ge from"path";import b from"picocolors";var K=e=>console.log(b.bold(b.cyan(`
335
2762
  \u2500\u2500 ${e} \u2500\u2500
336
- `))),Le=async(e,t)=>{if(t.list||!e){let{index:i,version:p,source:c}=await de();_(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${p}${c==="bundle"?" (bundle local)":""}`);for(let d of i.items)console.log(` ${w.bold(d.name.padEnd(20))} ${w.dim(d.description??"")}`);console.log(w.dim(`
337
- ${i.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let i=process.cwd(),p=await k(i),c=p.installed[o];if(!c){console.error(w.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:d}=await x(o,c).catch(()=>x(o));_(`${d.title??o} \xB7 copia local (instalado con registry@${c})`);for(let m of P(d,p)){let g=await $t.readFile(re.join(i,m.relPath),"utf-8").catch(()=>null);_(g===null?`${m.relPath} (FALTANTE)`:m.relPath),g!==null&&console.log(g)}return}let{item:n,version:r,source:s}=await x(o);_(`${n.title??n.name} \xB7 @lexydesign/registry@${r}${s==="bundle"?" (bundle local)":""}`),n.description&&console.log(n.description+`
338
- `),console.log(w.dim("tipo: ")+n.type),console.log(w.dim("deps npm: ")+((n.dependencies??[]).join(", ")||"\u2014")),console.log(w.dim("deps registry: ")+((n.registryDependencies??[]).join(", ")||"\u2014")),console.log(w.dim("archivos: ")+n.files.map(i=>re.posix.basename(i.path)).join(", ")),console.log(w.dim("instalar: ")+`create-lexy add ${n.name}`);for(let i of n.files)typeof i.content=="string"&&(_(re.posix.basename(i.path)),console.log(i.content))};var Ft=ke.dirname(It(import.meta.url)),Tt=JSON.parse(Et(ke.join(Ft,"../package.json"),"utf-8")),T=new Lt,N=e=>async()=>{try{await e()}catch(t){console.error(kt.red(`
339
- Error: `)+t.message),process.exit(1)}};T.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(Tt.version);T.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)=>N(()=>je(e,t))());T.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=>N(()=>Ie(e))());T.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)=>N(()=>ge(e,t))());T.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)=>N(()=>Le(e,t))());T.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>N(()=>Pe(e))());T.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=>N(async()=>{process.exitCode=await $e(e)})());T.parse(process.argv);
2763
+ `))),et=async(e,t)=>{if(t.list||!e){let{index:n,version:p,source:l}=await we();K(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${p}${l==="bundle"?" (bundle local)":""}`);for(let y of n.items)console.log(` ${b.bold(y.name.padEnd(20))} ${b.dim(y.description??"")}`);console.log(b.dim(`
2764
+ ${n.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let n=process.cwd(),p=await L(n),l=p.installed[o];if(!l){console.error(b.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:y}=await x(o,l).catch(()=>x(o));K(`${y.title??o} \xB7 copia local (instalado con registry@${l})`);for(let u of C(y,p)){let d=await Ro.readFile(ge.join(n,u.relPath),"utf-8").catch(()=>null);K(d===null?`${u.relPath} (FALTANTE)`:u.relPath),d!==null&&console.log(d)}return}let{item:r,version:a,source:i}=await x(o);K(`${r.title??r.name} \xB7 @lexydesign/registry@${a}${i==="bundle"?" (bundle local)":""}`),r.description&&console.log(r.description+`
2765
+ `),console.log(b.dim("tipo: ")+r.type),console.log(b.dim("deps npm: ")+((r.dependencies??[]).join(", ")||"\u2014")),console.log(b.dim("deps registry: ")+((r.registryDependencies??[]).join(", ")||"\u2014")),console.log(b.dim("archivos: ")+r.files.map(n=>ge.posix.basename(n.path)).join(", ")),console.log(b.dim("instalar: ")+`create-lexy add ${r.name}`);for(let n of r.files)typeof n.content=="string"&&(K(ge.posix.basename(n.path)),console.log(n.content))};var Ao=tt.dirname(Mo(import.meta.url)),Oo=JSON.parse(To(tt.join(Ao,"../package.json"),"utf-8")),M=new Lo,$=e=>async()=>{try{await e()}catch(t){console.error(zo.red(`
2766
+ Error: `)+t.message),process.exit(1)}};M.name("create-lexy").description("CLI del Lexy Design System: crea proyectos y trae componentes a demanda.\nLos componentes viven en tu proyecto \u2014 m\xEDralos con `view`, inst\xE1lalos con `add`, ed\xEDtalos con libertad.").version(Oo.version);M.command("create",{isDefault:!0}).alias("init").description("Crea un proyecto Lexy vac\xEDo (React + Vite + TS + theme); los componentes llegan con `add`").argument("[project-name]","Nombre del proyecto").option("-t, --type <architecture>","Arquitectura (feature o layer) \u2014 activa el modo no interactivo").option("-w, --world <world>","Mundo de dise\xF1o (cliente, crm o mixto)").option("--no-install","No correr pnpm install al final").action((e,t)=>$(()=>Ue(e,t))());M.command("kickoff").description("Deja un .md de entrevista para que el agente conduzca el arranque de un dise\xF1ador (carpeta vac\xEDa)").option("--force","Regenerar el .md de kickoff si ya existe").action(e=>$(()=>We(e))());M.command("add").description("Instala componentes del registry en tu proyecto (local y editable), con sus dependencias").argument("<components...>","Nombres en el registry (ej: button card)").option("--overwrite","Sobrescribir copias locales con cambios sin preguntar").option("--no-install","No instalar dependencias npm").action((e,t)=>$(()=>Re(e,t))());M.command("view").description("Muestra c\xF3digo + doc + metadata de un componente antes de instalarlo").argument("[component]","Nombre en el registry (sin nombre: lista el cat\xE1logo)").option("--installed","Mostrar la copia local instalada en vez de la del registry").option("--list","Listar el cat\xE1logo completo").action((e,t)=>$(()=>et(e,t))());M.command("kit").description("Trae el kit HTML/low-fi (mismos componentes y blocks en HTML plano) a una carpeta").option("--dir <path>","Carpeta destino (por defecto: lexy-kit)").option("--force","Sobrescribir la carpeta si ya existe").action(e=>$(()=>Qe(e))());M.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>$(()=>Je(e))());M.command("doctor").description("Drift de todo lo instalado contra el registry (solo lectura; exit 1 con problemas)").option("--strict","Las ediciones locales tambi\xE9n hacen fallar").action(e=>$(async()=>{process.exitCode=await Ge(e)})());M.parse(process.argv);