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