create-lexy 0.6.0 → 0.6.2
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 +1959 -1723
- 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 Io}from"commander";import Co from"picocolors";import{spawn as ct}from"child_process";import Y from"fs/promises";import G from"path";import{confirm as dt,isCancel as pt,spinner as Te}from"@clack/prompts";import v 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"),_=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.")}},Ie=R.join(J,"theme/lexy-theme.css"),Ce=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 Y.readFile(e,t)}catch{return null}},Re=async(e,t)=>{let o=process.cwd(),a=await _(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(v.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(v.yellow("Operaci\xF3n cancelada \u2014 nada fue modificado.")),process.exitCode=1;return}}else{console.error(v.red(`Estos archivos existen con cambios locales:
|
|
7
|
+
`)+p+v.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 Y.mkdir(G.dirname(A),{recursive:!0}),await Y.writeFile(A,x.content,x.encoding??"utf-8")}a.installed[c.name]=l;let P=e.map(x=>x.toLowerCase()).includes(p)?"":v.dim(" (dependencia)"),U=g==="bundle"?v.yellow(" [bundle local \u2014 sin red]"):"";console.log(v.green(`${v.bold(c.title??c.name)} instalado`)+P+v.cyan(` \u2192 ${a.paths.components}/`)+U);for(let x of f){let A=x.status==="same"?v.dim(" (sin cambios)"):"";console.log(v.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 Y.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(v.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 _e,text as xt}from"@clack/prompts";import Oe 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 ht=`
|
|
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(`${Oe.cyan(ht)}
|
|
24
|
+
${Oe.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 _e({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 _e({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",vt="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,1679 @@ export const prototypeDataContract = definePrototypeDataContract({
|
|
|
509
509
|
relations: [],
|
|
510
510
|
projections: {},
|
|
511
511
|
});
|
|
512
|
-
`,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
.string()
|
|
516
|
-
.regex(/^[a-z][a-zA-Z0-9]*$/, "Debe usar camelCase y comenzar con una letra minúscula.");
|
|
517
|
-
|
|
518
|
-
const nonEmptyTextSchema = z.string().trim().min(1, "No puede quedar vacío.");
|
|
519
|
-
|
|
520
|
-
const technicalEventIdSchema = z
|
|
521
|
-
.string()
|
|
522
|
-
.regex(
|
|
523
|
-
/^[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9]+-[A-Za-z0-9]+_V[0-9]+$/,
|
|
524
|
-
"Debe usar el formato eventista Dominio_Subdominio-NombreEvento_V1.",
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
const dataReferenceSchema = z
|
|
528
|
-
.string()
|
|
529
|
-
.regex(
|
|
530
|
-
/^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)?$/,
|
|
531
|
-
"Debe referenciar una entidad o campo como entidad o entidad.campo usando camelCase.",
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
const triggerSchema = z
|
|
535
|
-
.object({
|
|
536
|
-
id: camelCaseIdSchema.optional(),
|
|
537
|
-
label: nonEmptyTextSchema,
|
|
538
|
-
source: z.enum(["screen", "userAction", "system", "external", "unknown"]),
|
|
539
|
-
description: nonEmptyTextSchema.optional(),
|
|
540
|
-
})
|
|
541
|
-
.strict();
|
|
542
|
-
|
|
543
|
-
const schemaReferenceSchema = z
|
|
544
|
-
.object({
|
|
545
|
-
schemaId: camelCaseIdSchema,
|
|
546
|
-
})
|
|
547
|
-
.strict();
|
|
548
|
-
|
|
549
|
-
export const dataLoadSpecSchema = z
|
|
550
|
-
.object({
|
|
551
|
-
id: camelCaseIdSchema,
|
|
552
|
-
name: nonEmptyTextSchema,
|
|
553
|
-
productDescription: nonEmptyTextSchema,
|
|
554
|
-
trigger: triggerSchema,
|
|
555
|
-
paramsSchema: schemaReferenceSchema.optional(),
|
|
556
|
-
responseSchema: schemaReferenceSchema.optional(),
|
|
557
|
-
reads: z
|
|
558
|
-
.object({
|
|
559
|
-
entities: z.array(camelCaseIdSchema).optional(),
|
|
560
|
-
projections: z.array(camelCaseIdSchema).optional(),
|
|
561
|
-
fields: z.array(dataReferenceSchema).optional(),
|
|
562
|
-
})
|
|
563
|
-
.strict()
|
|
564
|
-
.optional(),
|
|
565
|
-
visibleResult: nonEmptyTextSchema,
|
|
566
|
-
pendingTi: nonEmptyTextSchema.optional(),
|
|
567
|
-
})
|
|
568
|
-
.strict();
|
|
569
|
-
|
|
570
|
-
export const publishedEventSpecSchema = z
|
|
571
|
-
.object({
|
|
572
|
-
id: camelCaseIdSchema,
|
|
573
|
-
name: nonEmptyTextSchema,
|
|
574
|
-
technicalId: technicalEventIdSchema,
|
|
575
|
-
productDescription: nonEmptyTextSchema,
|
|
576
|
-
trigger: triggerSchema,
|
|
577
|
-
payloadSchema: schemaReferenceSchema.optional(),
|
|
578
|
-
receiptSchema: schemaReferenceSchema.optional(),
|
|
579
|
-
writes: z
|
|
580
|
-
.object({
|
|
581
|
-
entities: z.array(camelCaseIdSchema).optional(),
|
|
582
|
-
fields: z.array(dataReferenceSchema).optional(),
|
|
583
|
-
})
|
|
584
|
-
.strict()
|
|
585
|
-
.optional(),
|
|
586
|
-
visibleResult: nonEmptyTextSchema,
|
|
587
|
-
pendingTi: nonEmptyTextSchema.optional(),
|
|
588
|
-
})
|
|
589
|
-
.strict();
|
|
590
|
-
|
|
591
|
-
export const prototypeEventEngineContractSchema = z
|
|
592
|
-
.object({
|
|
593
|
-
contractVersion: z.literal("1"),
|
|
594
|
-
dataLoads: z.record(camelCaseIdSchema, dataLoadSpecSchema),
|
|
595
|
-
events: z.record(camelCaseIdSchema, publishedEventSpecSchema),
|
|
596
|
-
})
|
|
597
|
-
.strict()
|
|
598
|
-
.superRefine((contract, context) => {
|
|
599
|
-
for (const [loadKey, load] of Object.entries(contract.dataLoads)) {
|
|
600
|
-
if (loadKey !== load.id) {
|
|
601
|
-
context.addIssue({
|
|
602
|
-
code: "custom",
|
|
603
|
-
path: ["dataLoads", loadKey, "id"],
|
|
604
|
-
message: 'El id "' + load.id + '" debe coincidir con la key "' + loadKey + '".',
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const technicalIds = new Set<string>();
|
|
610
|
-
for (const [eventKey, event] of Object.entries(contract.events)) {
|
|
611
|
-
if (eventKey !== event.id) {
|
|
612
|
-
context.addIssue({
|
|
613
|
-
code: "custom",
|
|
614
|
-
path: ["events", eventKey, "id"],
|
|
615
|
-
message: 'El id "' + event.id + '" debe coincidir con la key "' + eventKey + '".',
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (technicalIds.has(event.technicalId)) {
|
|
620
|
-
context.addIssue({
|
|
621
|
-
code: "custom",
|
|
622
|
-
path: ["events", eventKey, "technicalId"],
|
|
623
|
-
message: 'El ID técnico "' + event.technicalId + '" está declarado más de una vez.',
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
technicalIds.add(event.technicalId);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
export type PrototypeEventEngineContract = z.infer<typeof prototypeEventEngineContractSchema>;
|
|
631
|
-
export type DataLoadSpec = z.infer<typeof dataLoadSpecSchema>;
|
|
632
|
-
export type PublishedEventSpec = z.infer<typeof publishedEventSpecSchema>;
|
|
633
|
-
|
|
634
|
-
export type EventEngineValidationIssue = {
|
|
635
|
-
path: Array<string | number>;
|
|
636
|
-
message: string;
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
type DataContractLike = {
|
|
640
|
-
entities: Record<string, { fields: Record<string, unknown> }>;
|
|
641
|
-
projections: Record<string, unknown>;
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
type PayloadSchemasLike = Record<string, unknown>;
|
|
645
|
-
|
|
646
|
-
const hasOwn = (record: Record<string, unknown>, key: string) =>
|
|
647
|
-
Object.prototype.hasOwnProperty.call(record, key);
|
|
648
|
-
|
|
649
|
-
const validateDataReference = (
|
|
650
|
-
reference: string,
|
|
651
|
-
dataContract: DataContractLike,
|
|
652
|
-
): string | null => {
|
|
653
|
-
const [entityId, fieldId] = reference.split(".");
|
|
654
|
-
const entity = dataContract.entities[entityId];
|
|
655
|
-
if (!entity) return 'La entidad "' + entityId + '" no existe en el contrato de datos.';
|
|
656
|
-
if (fieldId && !hasOwn(entity.fields, fieldId)) {
|
|
657
|
-
return 'El campo "' + reference + '" no existe en el contrato de datos.';
|
|
658
|
-
}
|
|
659
|
-
return null;
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
export const validateEventEngineReferences = (input: {
|
|
663
|
-
eventEngineContract: PrototypeEventEngineContract;
|
|
664
|
-
dataContract: DataContractLike;
|
|
665
|
-
payloadSchemas: PayloadSchemasLike;
|
|
666
|
-
}): EventEngineValidationIssue[] => {
|
|
667
|
-
const issues: EventEngineValidationIssue[] = [];
|
|
668
|
-
const { eventEngineContract, dataContract, payloadSchemas } = input;
|
|
669
|
-
|
|
670
|
-
for (const [loadId, load] of Object.entries(eventEngineContract.dataLoads)) {
|
|
671
|
-
for (const [schemaKey, schemaRef] of [
|
|
672
|
-
["paramsSchema", load.paramsSchema],
|
|
673
|
-
["responseSchema", load.responseSchema],
|
|
674
|
-
] as const) {
|
|
675
|
-
if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
|
|
676
|
-
issues.push({
|
|
677
|
-
path: ["dataLoads", loadId, schemaKey, "schemaId"],
|
|
678
|
-
message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
for (const [index, entityId] of (load.reads?.entities ?? []).entries()) {
|
|
684
|
-
if (!dataContract.entities[entityId]) {
|
|
685
|
-
issues.push({
|
|
686
|
-
path: ["dataLoads", loadId, "reads", "entities", index],
|
|
687
|
-
message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
for (const [index, projectionId] of (load.reads?.projections ?? []).entries()) {
|
|
692
|
-
if (!dataContract.projections[projectionId]) {
|
|
693
|
-
issues.push({
|
|
694
|
-
path: ["dataLoads", loadId, "reads", "projections", index],
|
|
695
|
-
message: 'La proyección "' + projectionId + '" no existe en el contrato de datos.',
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
for (const [index, reference] of (load.reads?.fields ?? []).entries()) {
|
|
700
|
-
const message = validateDataReference(reference, dataContract);
|
|
701
|
-
if (message) {
|
|
702
|
-
issues.push({
|
|
703
|
-
path: ["dataLoads", loadId, "reads", "fields", index],
|
|
704
|
-
message,
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
for (const [eventId, event] of Object.entries(eventEngineContract.events)) {
|
|
711
|
-
for (const [schemaKey, schemaRef] of [
|
|
712
|
-
["payloadSchema", event.payloadSchema],
|
|
713
|
-
["receiptSchema", event.receiptSchema],
|
|
714
|
-
] as const) {
|
|
715
|
-
if (schemaRef && !hasOwn(payloadSchemas, schemaRef.schemaId)) {
|
|
716
|
-
issues.push({
|
|
717
|
-
path: ["events", eventId, schemaKey, "schemaId"],
|
|
718
|
-
message: 'El schema "' + schemaRef.schemaId + '" no existe en payload-schema.ts.',
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
for (const [index, entityId] of (event.writes?.entities ?? []).entries()) {
|
|
724
|
-
if (!dataContract.entities[entityId]) {
|
|
725
|
-
issues.push({
|
|
726
|
-
path: ["events", eventId, "writes", "entities", index],
|
|
727
|
-
message: 'La entidad "' + entityId + '" no existe en el contrato de datos.',
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
for (const [index, reference] of (event.writes?.fields ?? []).entries()) {
|
|
732
|
-
const message = validateDataReference(reference, dataContract);
|
|
733
|
-
if (message) {
|
|
734
|
-
issues.push({
|
|
735
|
-
path: ["events", eventId, "writes", "fields", index],
|
|
736
|
-
message,
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
return issues;
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
export function definePrototypeEventEngineContract(
|
|
746
|
-
contract: PrototypeEventEngineContract,
|
|
747
|
-
): PrototypeEventEngineContract {
|
|
748
|
-
return prototypeEventEngineContractSchema.parse(contract);
|
|
749
|
-
}
|
|
750
|
-
`,jt=String.raw`import { z } from "zod";
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Schemas runtime para params, payloads, responses y receipts del motor.
|
|
754
|
-
*
|
|
755
|
-
* Los contratos de Spec 2 referencian estas keys mediante schemaId. El agente
|
|
756
|
-
* agrega schemas específicos cuando declara cargas o eventos concretos.
|
|
757
|
-
*/
|
|
758
|
-
export const unknownPayloadSchema = z.unknown();
|
|
759
|
-
export const emptyObjectSchema = z.object({}).strict();
|
|
760
|
-
export const unknownResponseSchema = z.unknown();
|
|
761
|
-
export const absentReceiptSchema = z.undefined().optional();
|
|
762
|
-
|
|
763
|
-
export const prototypePayloadSchemas = {
|
|
764
|
-
unknown: unknownPayloadSchema,
|
|
765
|
-
emptyObject: emptyObjectSchema,
|
|
766
|
-
unknownResponse: unknownResponseSchema,
|
|
767
|
-
absentReceipt: absentReceiptSchema,
|
|
768
|
-
} satisfies Record<string, z.ZodType>;
|
|
769
|
-
|
|
770
|
-
export type PrototypePayloadSchemaId = keyof typeof prototypePayloadSchemas;
|
|
771
|
-
`,Rt=String.raw`import { z } from "zod";
|
|
772
|
-
|
|
773
|
-
import { prototypeEventEngineContract } from "./prototype-event-engine-contract";
|
|
774
|
-
import { prototypePayloadSchemas } from "./payload-schema";
|
|
775
|
-
import type { PrototypeEventEngineContract } from "./event-engine-contract-schema";
|
|
776
|
-
|
|
777
|
-
export type DataLoadInput = {
|
|
778
|
-
loadId: string;
|
|
779
|
-
triggerId?: string;
|
|
780
|
-
params?: unknown;
|
|
781
|
-
signal?: AbortSignal;
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
export type PublishEventInput = {
|
|
785
|
-
eventId: string;
|
|
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(vt,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
|
+
technicalId?: string;
|
|
786
515
|
triggerId?: string;
|
|
787
|
-
|
|
516
|
+
input?: unknown;
|
|
788
517
|
};
|
|
789
518
|
|
|
790
|
-
export type
|
|
791
|
-
|
|
519
|
+
export type DesignerActivityRecord = {
|
|
520
|
+
id: string;
|
|
792
521
|
occurredAt: string;
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
export type PublishedEventResult = {
|
|
798
|
-
status: "published";
|
|
799
|
-
label: "Evento publicado";
|
|
800
|
-
eventId: string;
|
|
801
|
-
technicalId: string;
|
|
802
|
-
occurredAt: string;
|
|
803
|
-
publicationId: string;
|
|
804
|
-
receipt?: unknown;
|
|
522
|
+
kind: "load" | "event";
|
|
523
|
+
technicalId?: string;
|
|
524
|
+
triggerId?: string;
|
|
525
|
+
inputPreview?: string;
|
|
805
526
|
};
|
|
806
527
|
|
|
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>;
|
|
528
|
+
export type DesignerActivityRecorder = {
|
|
529
|
+
record(input: DesignerActivityRecordInput): void;
|
|
819
530
|
};
|
|
820
531
|
|
|
821
|
-
type
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
clock?: () => Date;
|
|
826
|
-
publicationId?: () => string;
|
|
532
|
+
export type DesignerActivityStore = DesignerActivityRecorder & {
|
|
533
|
+
getSnapshot(): readonly DesignerActivityRecord[];
|
|
534
|
+
subscribe(listener: () => void): () => void;
|
|
535
|
+
clear(): void;
|
|
827
536
|
};
|
|
828
537
|
|
|
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);
|
|
538
|
+
const preview = (value: unknown): string | undefined => {
|
|
539
|
+
if (value === undefined) return undefined;
|
|
540
|
+
try {
|
|
541
|
+
return JSON.stringify(value, null, 2);
|
|
542
|
+
} catch {
|
|
543
|
+
return "[no serializable]";
|
|
840
544
|
}
|
|
841
|
-
return result.data;
|
|
842
545
|
};
|
|
843
546
|
|
|
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;
|
|
850
|
-
|
|
851
|
-
return {
|
|
852
|
-
async loadData(input: DataLoadInput): Promise<unknown> {
|
|
853
|
-
const load = contract.dataLoads[input.loadId];
|
|
854
|
-
if (!load) throw new Error('La carga de datos "' + input.loadId + '" no está declarada.');
|
|
855
|
-
|
|
856
|
-
const params = parseWithSchema(
|
|
857
|
-
load.paramsSchema ? payloadSchemas[load.paramsSchema.schemaId] : undefined,
|
|
858
|
-
input.params,
|
|
859
|
-
'Params de la carga "' + input.loadId + '"',
|
|
860
|
-
);
|
|
861
|
-
|
|
862
|
-
const response = await options.executor.loadData({
|
|
863
|
-
loadId: input.loadId,
|
|
864
|
-
triggerId: input.triggerId,
|
|
865
|
-
params,
|
|
866
|
-
signal: input.signal,
|
|
867
|
-
});
|
|
547
|
+
const tabId = Math.random().toString(36).slice(2, 8);
|
|
868
548
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
},
|
|
549
|
+
type ChannelMessage =
|
|
550
|
+
| { type: "record"; record: DesignerActivityRecord }
|
|
551
|
+
| { type: "clear" }
|
|
552
|
+
| { type: "syncRequest" }
|
|
553
|
+
| { type: "sync"; records: DesignerActivityRecord[] };
|
|
875
554
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
555
|
+
export const createDesignerActivityStore = (maxItems = 80): DesignerActivityStore => {
|
|
556
|
+
const listeners = new Set<() => void>();
|
|
557
|
+
let records: DesignerActivityRecord[] = [];
|
|
558
|
+
let sequence = 0;
|
|
559
|
+
let synced = false;
|
|
879
560
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
);
|
|
561
|
+
const channel =
|
|
562
|
+
typeof BroadcastChannel !== "undefined"
|
|
563
|
+
? new BroadcastChannel("lexy-designer-activity")
|
|
564
|
+
: null;
|
|
885
565
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
publicationId: publicationId(),
|
|
890
|
-
payload,
|
|
891
|
-
};
|
|
566
|
+
const notify = () => {
|
|
567
|
+
for (const listener of listeners) listener();
|
|
568
|
+
};
|
|
892
569
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
});
|
|
570
|
+
const addRecord = (record: DesignerActivityRecord) => {
|
|
571
|
+
records = [record, ...records].slice(0, maxItems);
|
|
572
|
+
notify();
|
|
573
|
+
};
|
|
898
574
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
)
|
|
575
|
+
if (channel) {
|
|
576
|
+
channel.onmessage = (event: MessageEvent<ChannelMessage>) => {
|
|
577
|
+
const message = event.data;
|
|
578
|
+
if (message.type === "record") addRecord(message.record);
|
|
579
|
+
else if (message.type === "clear") {
|
|
580
|
+
records = [];
|
|
581
|
+
notify();
|
|
582
|
+
} else if (message.type === "syncRequest") {
|
|
583
|
+
channel.postMessage({ type: "sync", records });
|
|
584
|
+
} else if (message.type === "sync" && !synced && records.length === 0) {
|
|
585
|
+
synced = true;
|
|
586
|
+
records = message.records.slice(0, maxItems);
|
|
587
|
+
notify();
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
channel.postMessage({ type: "syncRequest" });
|
|
591
|
+
}
|
|
904
592
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
593
|
+
return {
|
|
594
|
+
getSnapshot: () => records,
|
|
595
|
+
subscribe: (listener) => {
|
|
596
|
+
listeners.add(listener);
|
|
597
|
+
return () => listeners.delete(listener);
|
|
598
|
+
},
|
|
599
|
+
record: (input) => {
|
|
600
|
+
sequence += 1;
|
|
601
|
+
const record: DesignerActivityRecord = {
|
|
602
|
+
id: tabId + "_" + sequence,
|
|
603
|
+
occurredAt: new Date().toISOString(),
|
|
604
|
+
kind: input.kind,
|
|
605
|
+
label: input.technicalId ?? "evento",
|
|
606
|
+
...(input.technicalId ? { technicalId: input.technicalId } : {}),
|
|
607
|
+
...(input.triggerId ? { triggerId: input.triggerId } : {}),
|
|
608
|
+
...(preview(input.input) ? { inputPreview: preview(input.input) } : {}),
|
|
913
609
|
};
|
|
610
|
+
addRecord(record);
|
|
611
|
+
channel?.postMessage({ type: "record", record });
|
|
612
|
+
},
|
|
613
|
+
clear: () => {
|
|
614
|
+
records = [];
|
|
615
|
+
notify();
|
|
616
|
+
channel?.postMessage({ type: "clear" });
|
|
914
617
|
},
|
|
915
618
|
};
|
|
916
619
|
};
|
|
917
|
-
`,Tt=String.raw`import { ZodError } from "zod";
|
|
918
620
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
621
|
+
export const designerActivity = createDesignerActivityStore();
|
|
622
|
+
`,Pt=`
|
|
623
|
+
.lexy-dp {
|
|
624
|
+
--dp-bg: #ffffff;
|
|
625
|
+
--dp-fg: #0f172a;
|
|
626
|
+
--dp-card: #ffffff;
|
|
627
|
+
--dp-popover: #ffffff;
|
|
628
|
+
--dp-primary: #4f46e5;
|
|
629
|
+
--dp-primary-fg: #ffffff;
|
|
630
|
+
--dp-muted: #f8fafc;
|
|
631
|
+
--dp-muted-fg: #475569;
|
|
632
|
+
--dp-accent: #e0e7ff;
|
|
633
|
+
--dp-accent-fg: #4f46e5;
|
|
634
|
+
--dp-border: #e2e8f0;
|
|
635
|
+
--dp-ring: #c7d2fe;
|
|
636
|
+
--dp-destructive: #ef4444;
|
|
637
|
+
--dp-success: #10b981;
|
|
638
|
+
--dp-success-fg: #ffffff;
|
|
639
|
+
--dp-warning: #f59e0b;
|
|
640
|
+
--dp-warning-fg: #ffffff;
|
|
641
|
+
--dp-info: #0ea5e9;
|
|
642
|
+
--dp-radius: 8px;
|
|
643
|
+
--dp-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
644
|
+
--dp-sans: "Inter", system-ui, -apple-system, sans-serif;
|
|
645
|
+
color: var(--dp-fg);
|
|
646
|
+
font-family: var(--dp-sans);
|
|
647
|
+
font-size: 13.5px;
|
|
648
|
+
line-height: 1.5;
|
|
649
|
+
}
|
|
650
|
+
.lexy-dp *, .lexy-dp *::before, .lexy-dp *::after { box-sizing: border-box; }
|
|
651
|
+
.lexy-dp p, .lexy-dp h1, .lexy-dp pre, .lexy-dp ul { margin: 0; }
|
|
652
|
+
.lexy-dp ul { list-style: none; padding: 0; }
|
|
653
|
+
.lexy-dp button { font-family: inherit; color: inherit; border: none; background: transparent; cursor: pointer; outline: none; }
|
|
654
|
+
.lexy-dp code { font-family: var(--dp-mono); }
|
|
655
|
+
|
|
656
|
+
/* Custom Scrollbars */
|
|
657
|
+
.lexy-dp .lexy-dp-scroll::-webkit-scrollbar,
|
|
658
|
+
.lexy-dp .lexy-dp-split__list::-webkit-scrollbar,
|
|
659
|
+
.lexy-dp .lexy-dp-split__detail::-webkit-scrollbar,
|
|
660
|
+
.lexy-dp .lexy-dp-sheet__body::-webkit-scrollbar,
|
|
661
|
+
.lexy-dp .lexy-dp-snippet::-webkit-scrollbar {
|
|
662
|
+
width: 6px;
|
|
663
|
+
height: 6px;
|
|
664
|
+
}
|
|
665
|
+
.lexy-dp .lexy-dp-scroll::-webkit-scrollbar-track,
|
|
666
|
+
.lexy-dp .lexy-dp-split__list::-webkit-scrollbar-track,
|
|
667
|
+
.lexy-dp .lexy-dp-split__detail::-webkit-scrollbar-track,
|
|
668
|
+
.lexy-dp .lexy-dp-sheet__body::-webkit-scrollbar-track,
|
|
669
|
+
.lexy-dp .lexy-dp-snippet::-webkit-scrollbar-track {
|
|
670
|
+
background: transparent;
|
|
671
|
+
}
|
|
672
|
+
.lexy-dp .lexy-dp-scroll::-webkit-scrollbar-thumb,
|
|
673
|
+
.lexy-dp .lexy-dp-split__list::-webkit-scrollbar-thumb,
|
|
674
|
+
.lexy-dp .lexy-dp-split__detail::-webkit-scrollbar-thumb,
|
|
675
|
+
.lexy-dp .lexy-dp-sheet__body::-webkit-scrollbar-thumb,
|
|
676
|
+
.lexy-dp .lexy-dp-snippet::-webkit-scrollbar-thumb {
|
|
677
|
+
background: rgba(0, 0, 0, 0.12);
|
|
678
|
+
border-radius: 9999px;
|
|
679
|
+
}
|
|
680
|
+
.lexy-dp .lexy-dp-scroll::-webkit-scrollbar-thumb:hover,
|
|
681
|
+
.lexy-dp .lexy-dp-split__list::-webkit-scrollbar-thumb:hover,
|
|
682
|
+
.lexy-dp .lexy-dp-split__detail::-webkit-scrollbar-thumb:hover,
|
|
683
|
+
.lexy-dp .lexy-dp-sheet__body::-webkit-scrollbar-thumb:hover,
|
|
684
|
+
.lexy-dp .lexy-dp-snippet::-webkit-scrollbar-thumb:hover {
|
|
685
|
+
background: rgba(0, 0, 0, 0.24);
|
|
686
|
+
}
|
|
942
687
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
);
|
|
688
|
+
/* \u2500\u2500 Full Page \u2500\u2500 */
|
|
689
|
+
.lexy-dp-page {
|
|
690
|
+
display: flex;
|
|
691
|
+
flex-direction: column;
|
|
692
|
+
height: 100vh;
|
|
693
|
+
background: var(--dp-bg);
|
|
694
|
+
}
|
|
949
695
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
696
|
+
/* \u2500\u2500 Tabs \u2500\u2500 */
|
|
697
|
+
.lexy-dp-tablist {
|
|
698
|
+
display: flex;
|
|
699
|
+
align-items: stretch;
|
|
700
|
+
border-bottom: 1px solid var(--dp-border);
|
|
701
|
+
padding: 0 16px;
|
|
702
|
+
background: #ffffff;
|
|
703
|
+
gap: 8px;
|
|
704
|
+
}
|
|
705
|
+
.lexy-dp-tab {
|
|
706
|
+
display: inline-flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
gap: 8px;
|
|
709
|
+
padding: 12px 14px;
|
|
710
|
+
font-size: 13px;
|
|
711
|
+
border-bottom: 2px solid transparent;
|
|
712
|
+
margin-bottom: -1px;
|
|
713
|
+
color: var(--dp-muted-fg);
|
|
714
|
+
font-weight: 500;
|
|
715
|
+
transition: all 0.2s ease;
|
|
716
|
+
}
|
|
717
|
+
.lexy-dp-tab:hover { color: var(--dp-fg); }
|
|
718
|
+
.lexy-dp-tab.is-active {
|
|
719
|
+
color: var(--dp-primary);
|
|
720
|
+
font-weight: 600;
|
|
721
|
+
border-bottom-color: var(--dp-primary);
|
|
722
|
+
}
|
|
723
|
+
.lexy-dp-tab svg { width: 15px; height: 15px; opacity: 0.8; }
|
|
724
|
+
.lexy-dp-tab.is-active svg { opacity: 1; color: var(--dp-primary); }
|
|
725
|
+
|
|
726
|
+
.lexy-dp-tabbadge {
|
|
727
|
+
margin-left: 4px;
|
|
728
|
+
border-radius: 9999px;
|
|
729
|
+
background: var(--dp-warning);
|
|
730
|
+
color: var(--dp-warning-fg);
|
|
731
|
+
padding: 1px 6px;
|
|
732
|
+
font-size: 10px;
|
|
733
|
+
font-weight: 600;
|
|
734
|
+
}
|
|
735
|
+
.lexy-dp-body { flex: 1; min-height: 0; }
|
|
736
|
+
.lexy-dp-panel { height: 100%; }
|
|
737
|
+
|
|
738
|
+
/* \u2500\u2500 Scroll & Padding \u2500\u2500 */
|
|
739
|
+
.lexy-dp-scroll { height: 100%; overflow-y: auto; }
|
|
740
|
+
.lexy-dp-pad { padding: 24px; }
|
|
741
|
+
|
|
742
|
+
/* \u2500\u2500 Column & Toolbar \u2500\u2500 */
|
|
743
|
+
.lexy-dp-col { display: flex; flex-direction: column; height: 100%; }
|
|
744
|
+
.lexy-dp-toolbar {
|
|
745
|
+
display: flex;
|
|
746
|
+
align-items: center;
|
|
747
|
+
gap: 8px;
|
|
748
|
+
border-bottom: 1px solid var(--dp-border);
|
|
749
|
+
background: #f8fafc;
|
|
750
|
+
padding: 8px 16px;
|
|
751
|
+
}
|
|
967
752
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
753
|
+
/* \u2500\u2500 Buttons \u2500\u2500 */
|
|
754
|
+
.lexy-dp-btn {
|
|
755
|
+
display: inline-flex;
|
|
756
|
+
align-items: center;
|
|
757
|
+
justify-content: center;
|
|
758
|
+
gap: 8px;
|
|
759
|
+
padding: 8px 16px;
|
|
760
|
+
font-size: 13px;
|
|
761
|
+
font-weight: 500;
|
|
762
|
+
border-radius: var(--dp-radius);
|
|
763
|
+
border: 1px solid transparent;
|
|
764
|
+
background: var(--dp-primary);
|
|
765
|
+
color: var(--dp-primary-fg);
|
|
766
|
+
box-shadow: 0 1px 2px rgba(79, 70, 229, 0.1);
|
|
767
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
768
|
+
}
|
|
769
|
+
.lexy-dp-btn:hover {
|
|
770
|
+
opacity: 0.95;
|
|
771
|
+
transform: translateY(-1px);
|
|
772
|
+
box-shadow: 0 4px 8px rgba(79, 70, 229, 0.15);
|
|
773
|
+
}
|
|
774
|
+
.lexy-dp-btn:active { transform: translateY(0); }
|
|
775
|
+
.lexy-dp-btn:disabled {
|
|
776
|
+
opacity: 0.4;
|
|
777
|
+
cursor: not-allowed;
|
|
778
|
+
transform: none !important;
|
|
779
|
+
box-shadow: none !important;
|
|
780
|
+
}
|
|
781
|
+
.lexy-dp-btn--outline {
|
|
782
|
+
background: transparent;
|
|
783
|
+
border-color: var(--dp-border);
|
|
784
|
+
color: var(--dp-fg);
|
|
785
|
+
box-shadow: none;
|
|
786
|
+
}
|
|
787
|
+
.lexy-dp-btn--outline:hover {
|
|
788
|
+
background: var(--dp-muted);
|
|
789
|
+
border-color: var(--dp-border);
|
|
790
|
+
box-shadow: none;
|
|
791
|
+
}
|
|
792
|
+
.lexy-dp-btn--outline:disabled:hover { background: transparent; }
|
|
793
|
+
.lexy-dp-btn--sm { padding: 5px 12px; font-size: 12px; }
|
|
794
|
+
.lexy-dp-btn svg { width: 14px; height: 14px; }
|
|
795
|
+
|
|
796
|
+
/* \u2500\u2500 Badges \u2500\u2500 */
|
|
797
|
+
.lexy-dp-badge {
|
|
798
|
+
display: inline-flex;
|
|
799
|
+
align-items: center;
|
|
800
|
+
border-radius: 9999px;
|
|
801
|
+
padding: 2px 10px;
|
|
802
|
+
font-size: 11px;
|
|
803
|
+
font-weight: 600;
|
|
804
|
+
border: 1px solid transparent;
|
|
805
|
+
background: rgba(79, 70, 229, 0.08);
|
|
806
|
+
color: var(--dp-primary);
|
|
807
|
+
white-space: nowrap;
|
|
808
|
+
}
|
|
809
|
+
.lexy-dp-badge--outline {
|
|
810
|
+
background: var(--dp-muted);
|
|
811
|
+
color: var(--dp-muted-fg);
|
|
812
|
+
border-color: var(--dp-border);
|
|
813
|
+
}
|
|
814
|
+
.lexy-dp-badge--success {
|
|
815
|
+
background: rgba(16, 185, 129, 0.1);
|
|
816
|
+
color: #047857;
|
|
817
|
+
}
|
|
818
|
+
.lexy-dp-badge--warning {
|
|
819
|
+
background: rgba(245, 158, 11, 0.1);
|
|
820
|
+
color: #b45309;
|
|
821
|
+
}
|
|
977
822
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
823
|
+
/* \u2500\u2500 Cards \u2500\u2500 */
|
|
824
|
+
.lexy-dp-card {
|
|
825
|
+
border: 1px solid var(--dp-border);
|
|
826
|
+
background: var(--dp-card);
|
|
827
|
+
border-radius: var(--dp-radius);
|
|
828
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
829
|
+
padding: 16px;
|
|
830
|
+
transition: all 0.2s ease;
|
|
831
|
+
}
|
|
832
|
+
.lexy-dp-card:hover {
|
|
833
|
+
border-color: rgba(79, 70, 229, 0.2);
|
|
834
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
835
|
+
}
|
|
836
|
+
.lexy-dp-card__title { font-size: 14.5px; font-weight: 600; color: var(--dp-fg); }
|
|
837
|
+
.lexy-dp-card__desc { font-size: 12.5px; color: var(--dp-muted-fg); margin-top: 4px; line-height: 1.4; }
|
|
838
|
+
.lexy-dp-card__content { margin-top: 14px; }
|
|
839
|
+
.lexy-dp-card__head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
840
|
+
|
|
841
|
+
/* \u2500\u2500 Searchbox \u2500\u2500 */
|
|
842
|
+
.lexy-dp-search { position: relative; display: flex; align-items: center; flex: 1; }
|
|
843
|
+
.lexy-dp-search input {
|
|
844
|
+
width: 100%;
|
|
845
|
+
padding: 6px 30px 6px 12px;
|
|
846
|
+
font-size: 12.5px;
|
|
847
|
+
font-family: inherit;
|
|
848
|
+
border: 1px solid var(--dp-border);
|
|
849
|
+
border-radius: 6px;
|
|
850
|
+
background: #ffffff;
|
|
851
|
+
color: var(--dp-fg);
|
|
852
|
+
transition: all 0.2s ease;
|
|
853
|
+
}
|
|
854
|
+
.lexy-dp-search input:focus {
|
|
855
|
+
outline: none;
|
|
856
|
+
border-color: var(--dp-primary);
|
|
857
|
+
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
|
|
858
|
+
}
|
|
859
|
+
.lexy-dp-search__clear {
|
|
860
|
+
position: absolute;
|
|
861
|
+
right: 10px;
|
|
862
|
+
border: none;
|
|
863
|
+
background: transparent;
|
|
864
|
+
color: var(--dp-muted-fg);
|
|
865
|
+
cursor: pointer;
|
|
866
|
+
padding: 2px;
|
|
867
|
+
display: inline-flex;
|
|
868
|
+
transition: color 0.15s ease;
|
|
869
|
+
}
|
|
870
|
+
.lexy-dp-search__clear:hover { color: var(--dp-fg); }
|
|
871
|
+
.lexy-dp-search__clear svg { width: 14px; height: 14px; }
|
|
872
|
+
|
|
873
|
+
/* \u2500\u2500 Snippet \u2500\u2500 */
|
|
874
|
+
.lexy-dp-snippet {
|
|
875
|
+
font-family: var(--dp-mono);
|
|
876
|
+
font-size: 12px;
|
|
877
|
+
background: #f8fafc;
|
|
878
|
+
border: 1px solid var(--dp-border);
|
|
879
|
+
border-radius: var(--dp-radius);
|
|
880
|
+
padding: 12px 14px;
|
|
881
|
+
overflow: auto;
|
|
882
|
+
white-space: pre;
|
|
883
|
+
color: #334155;
|
|
884
|
+
}
|
|
885
|
+
.lexy-dp-sep { border: none; border-top: 1px solid var(--dp-border); height: 1px; margin: 20px 0; }
|
|
886
|
+
.lexy-dp-section-label {
|
|
887
|
+
font-size: 11.5px;
|
|
888
|
+
font-weight: 600;
|
|
889
|
+
text-transform: uppercase;
|
|
890
|
+
letter-spacing: 0.05em;
|
|
891
|
+
color: #64748b;
|
|
892
|
+
margin-bottom: 8px;
|
|
893
|
+
}
|
|
894
|
+
.lexy-dp-muted { color: var(--dp-muted-fg); }
|
|
895
|
+
.lexy-dp-mono { font-family: var(--dp-mono); }
|
|
896
|
+
.lexy-dp-empty { padding: 24px 16px; color: var(--dp-muted-fg); font-size: 13px; text-align: center; }
|
|
897
|
+
|
|
898
|
+
.lexy-dp-log__event { color: var(--dp-fg); font-weight: 600; }
|
|
899
|
+
|
|
900
|
+
/* \u2500\u2500 Stacks & Grids \u2500\u2500 */
|
|
901
|
+
.lexy-dp-stack { display: flex; flex-direction: column; gap: 4px; }
|
|
902
|
+
.lexy-dp-stack-sm { display: flex; flex-direction: column; gap: 8px; }
|
|
903
|
+
.lexy-dp-stack-lg { display: flex; flex-direction: column; gap: 16px; }
|
|
904
|
+
.lexy-dp-substack { display: flex; flex-direction: column; gap: 4px; padding-left: 18px; color: var(--dp-muted-fg); }
|
|
905
|
+
.lexy-dp-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
906
|
+
|
|
907
|
+
/* \u2500\u2500 JSON Tree \u2500\u2500 */
|
|
908
|
+
.lexy-dp-json-str { color: #047857; } /* Emerald green */
|
|
909
|
+
.lexy-dp-json-num { color: #0284c7; } /* Sky blue */
|
|
910
|
+
.lexy-dp-json-row { display: flex; align-items: flex-start; gap: 6px; padding: 2px 0; }
|
|
911
|
+
.lexy-dp-json-spacer { width: 14px; height: 14px; flex-shrink: 0; }
|
|
912
|
+
.lexy-dp-json-leaf { min-width: 0; word-break: break-word; }
|
|
913
|
+
.lexy-dp-json-toggle {
|
|
914
|
+
display: flex;
|
|
915
|
+
width: 100%;
|
|
916
|
+
align-items: flex-start;
|
|
917
|
+
gap: 6px;
|
|
918
|
+
padding: 3px 6px;
|
|
919
|
+
text-align: left;
|
|
920
|
+
background: transparent;
|
|
921
|
+
border-radius: 4px;
|
|
922
|
+
transition: background 0.15s ease;
|
|
923
|
+
}
|
|
924
|
+
.lexy-dp-json-toggle:hover { background: rgba(0, 0, 0, 0.03); }
|
|
925
|
+
.lexy-dp-chevron { width: 14px; height: 14px; flex-shrink: 0; color: var(--dp-muted-fg); margin-top: 2px; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); }
|
|
926
|
+
.lexy-dp-chevron--sm { width: 12px; height: 12px; margin-top: 1px; }
|
|
927
|
+
.lexy-dp-chevron.is-open { transform: rotate(90deg); color: var(--dp-fg); }
|
|
928
|
+
|
|
929
|
+
/* \u2500\u2500 Activity Log \u2500\u2500 */
|
|
930
|
+
.lexy-dp-activitylog {
|
|
931
|
+
min-height: 100%;
|
|
932
|
+
background: var(--dp-bg);
|
|
933
|
+
padding: 8px 0;
|
|
934
|
+
font-family: var(--dp-mono);
|
|
935
|
+
font-size: 12.5px;
|
|
936
|
+
line-height: 1.6;
|
|
937
|
+
color: var(--dp-fg);
|
|
938
|
+
}
|
|
939
|
+
.lexy-dp-activitylog--scroll { flex: 1; min-height: 0; overflow-y: auto; }
|
|
940
|
+
.lexy-dp-log {
|
|
941
|
+
border-bottom: 1px solid var(--dp-border);
|
|
942
|
+
transition: background 0.15s ease;
|
|
943
|
+
}
|
|
944
|
+
.lexy-dp-log:hover {
|
|
945
|
+
background: rgba(0, 0, 0, 0.005);
|
|
946
|
+
}
|
|
947
|
+
.lexy-dp-log__head {
|
|
948
|
+
display: flex;
|
|
949
|
+
width: 100%;
|
|
950
|
+
align-items: center;
|
|
951
|
+
gap: 8px;
|
|
952
|
+
padding: 10px 16px;
|
|
953
|
+
text-align: left;
|
|
954
|
+
background: transparent;
|
|
955
|
+
cursor: pointer;
|
|
956
|
+
transition: background 0.15s ease;
|
|
957
|
+
}
|
|
958
|
+
.lexy-dp-log__head:hover { background: rgba(0, 0, 0, 0.03); }
|
|
959
|
+
.lexy-dp-log__time { margin-left: auto; flex-shrink: 0; color: var(--dp-muted-fg); font-size: 11px; }
|
|
960
|
+
.lexy-dp-log__body {
|
|
961
|
+
padding: 8px 16px 12px 16px;
|
|
962
|
+
background: #f8fafc;
|
|
963
|
+
border-top: 1px solid rgba(0, 0, 0, 0.03);
|
|
997
964
|
}
|
|
998
|
-
`,Mt=()=>`import { definePrototypeEventEngineContract } from "./event-engine-contract-schema";
|
|
999
965
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
966
|
+
/* \u2500\u2500 Split (Master-Detail) \u2500\u2500 */
|
|
967
|
+
.lexy-dp-split { display: flex; height: 100%; }
|
|
968
|
+
.lexy-dp-split__list {
|
|
969
|
+
width: 288px;
|
|
970
|
+
flex-shrink: 0;
|
|
971
|
+
overflow-y: auto;
|
|
972
|
+
border-right: 1px solid var(--dp-border);
|
|
973
|
+
padding: 12px 0;
|
|
974
|
+
display: flex;
|
|
975
|
+
flex-direction: column;
|
|
976
|
+
background: #f8fafc; /* sidebar light gray background */
|
|
977
|
+
}
|
|
978
|
+
.lexy-dp-list-section {
|
|
979
|
+
display: flex;
|
|
980
|
+
align-items: center;
|
|
981
|
+
justify-content: space-between;
|
|
982
|
+
width: 100%;
|
|
983
|
+
padding: 10px 16px;
|
|
984
|
+
font-size: 11px;
|
|
985
|
+
font-weight: 700;
|
|
986
|
+
letter-spacing: 0.05em;
|
|
987
|
+
color: #475569;
|
|
988
|
+
text-transform: uppercase;
|
|
989
|
+
background: transparent;
|
|
990
|
+
border: none;
|
|
991
|
+
cursor: pointer;
|
|
992
|
+
font-family: inherit;
|
|
993
|
+
text-align: left;
|
|
994
|
+
margin-top: 8px;
|
|
995
|
+
}
|
|
996
|
+
.lexy-dp-list-section:first-of-type { margin-top: 0; }
|
|
997
|
+
.lexy-dp-listitem {
|
|
998
|
+
width: calc(100% - 16px);
|
|
999
|
+
margin: 2px 8px;
|
|
1000
|
+
text-align: left;
|
|
1001
|
+
border: none;
|
|
1002
|
+
background: transparent;
|
|
1003
|
+
padding: 7px 12px;
|
|
1004
|
+
font-family: var(--dp-mono);
|
|
1005
|
+
font-size: 12px;
|
|
1006
|
+
color: #334155;
|
|
1007
|
+
cursor: pointer;
|
|
1008
|
+
white-space: nowrap;
|
|
1009
|
+
overflow: hidden;
|
|
1010
|
+
text-overflow: ellipsis;
|
|
1011
|
+
border-radius: 6px;
|
|
1012
|
+
transition: all 0.15s ease;
|
|
1013
|
+
}
|
|
1014
|
+
.lexy-dp-listitem:hover { background: rgba(0, 0, 0, 0.03); color: var(--dp-fg); }
|
|
1015
|
+
.lexy-dp-listitem.is-active {
|
|
1016
|
+
background: rgba(79, 70, 229, 0.08);
|
|
1017
|
+
color: var(--dp-primary);
|
|
1018
|
+
font-weight: 600;
|
|
1019
|
+
}
|
|
1020
|
+
.lexy-dp-split__detail {
|
|
1021
|
+
flex: 1;
|
|
1022
|
+
min-width: 0;
|
|
1023
|
+
min-height: 0;
|
|
1024
|
+
overflow-y: auto;
|
|
1025
|
+
padding: 0;
|
|
1026
|
+
background: #ffffff; /* details pure white background */
|
|
1027
|
+
}
|
|
1014
1028
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1029
|
+
/* \u2500\u2500 Detalle (Event / Load / Entity Detail Layout) \u2500\u2500 */
|
|
1030
|
+
.lexy-dp-eventdetail { display: flex; flex-direction: column; font-size: 13.5px; line-height: 1.6; }
|
|
1031
|
+
|
|
1032
|
+
/* The first detail body acts as the HERO banner of the selected item */
|
|
1033
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type {
|
|
1034
|
+
background: #f8fafc;
|
|
1035
|
+
border-bottom: 1px solid var(--dp-border);
|
|
1036
|
+
padding: 24px;
|
|
1037
|
+
display: flex;
|
|
1038
|
+
flex-direction: column;
|
|
1039
|
+
gap: 16px;
|
|
1040
|
+
margin-bottom: 12px;
|
|
1041
|
+
}
|
|
1042
|
+
/* Reorganize field rows inside the HERO banner */
|
|
1043
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field {
|
|
1044
|
+
display: flex;
|
|
1045
|
+
flex-direction: column;
|
|
1046
|
+
align-items: flex-start;
|
|
1047
|
+
gap: 4px;
|
|
1048
|
+
}
|
|
1049
|
+
/* Hide default "Nombre" label since it's redundant */
|
|
1050
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field:first-of-type .lexy-dp-detail-label {
|
|
1051
|
+
display: none;
|
|
1052
|
+
}
|
|
1053
|
+
/* Title of the hero banner (technical ID) */
|
|
1054
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field:first-of-type span:not(.lexy-dp-detail-label) {
|
|
1055
|
+
font-size: 20px;
|
|
1056
|
+
font-weight: 700;
|
|
1057
|
+
color: #0f172a;
|
|
1058
|
+
font-family: var(--dp-sans);
|
|
1059
|
+
}
|
|
1060
|
+
/* Styling other hero banner labels as small headers */
|
|
1061
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field:not(:first-of-type) .lexy-dp-detail-label {
|
|
1062
|
+
font-size: 11px;
|
|
1063
|
+
font-weight: 600;
|
|
1064
|
+
text-transform: uppercase;
|
|
1065
|
+
letter-spacing: 0.05em;
|
|
1066
|
+
color: #64748b;
|
|
1067
|
+
}
|
|
1068
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field:not(:first-of-type) .lexy-dp-detail-label::after {
|
|
1069
|
+
content: "";
|
|
1070
|
+
}
|
|
1071
|
+
/* Text description in the hero banner */
|
|
1072
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:first-of-type .lexy-dp-detail-field:not(:first-of-type) span:not(.lexy-dp-detail-label) {
|
|
1073
|
+
font-size: 13.5px;
|
|
1074
|
+
color: #475569;
|
|
1075
|
+
font-family: var(--dp-sans);
|
|
1076
|
+
line-height: 1.6;
|
|
1077
|
+
}
|
|
1018
1078
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1079
|
+
/* Detail sub-bodies (e.g. content underneath sections) */
|
|
1080
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body:not(:first-of-type) {
|
|
1081
|
+
padding: 12px 24px 24px 24px;
|
|
1082
|
+
display: flex;
|
|
1083
|
+
flex-direction: column;
|
|
1084
|
+
gap: 8px;
|
|
1085
|
+
}
|
|
1086
|
+
.lexy-dp-detail-field { display: flex; gap: 8px; font-family: var(--dp-mono); font-size: 12.5px; }
|
|
1087
|
+
.lexy-dp-detail-label { color: var(--dp-muted-fg); flex-shrink: 0; }
|
|
1088
|
+
.lexy-dp-detail-label::after { content: ":"; }
|
|
1089
|
+
|
|
1090
|
+
/* Section Header Buttons inside Detail Page */
|
|
1091
|
+
.lexy-dp-eventdetail > .lexy-dp-list-section {
|
|
1092
|
+
display: flex;
|
|
1093
|
+
align-items: center;
|
|
1094
|
+
justify-content: space-between;
|
|
1095
|
+
width: 100%;
|
|
1096
|
+
padding: 12px 24px;
|
|
1097
|
+
font-size: 13px;
|
|
1098
|
+
font-weight: 600;
|
|
1099
|
+
color: #0f172a;
|
|
1100
|
+
background: #ffffff;
|
|
1101
|
+
border-top: 1px solid var(--dp-border);
|
|
1102
|
+
border-bottom: none;
|
|
1103
|
+
cursor: pointer;
|
|
1104
|
+
font-family: inherit;
|
|
1105
|
+
text-align: left;
|
|
1106
|
+
margin-top: 8px;
|
|
1107
|
+
text-transform: none;
|
|
1108
|
+
letter-spacing: normal;
|
|
1109
|
+
}
|
|
1110
|
+
.lexy-dp-eventdetail > .lexy-dp-list-section:hover { background: #f8fafc; }
|
|
1111
|
+
|
|
1112
|
+
/* \u2500\u2500 Triggers List \u2500\u2500 */
|
|
1113
|
+
.lexy-dp-trigger-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
|
|
1114
|
+
.lexy-dp-trigger-item { display: flex; gap: 8px; align-items: baseline; }
|
|
1115
|
+
.lexy-dp-bullet { color: var(--dp-primary); font-weight: 700; flex-shrink: 0; }
|
|
1116
|
+
|
|
1117
|
+
/* \u2500\u2500 Fields Table (DevTools) \u2500\u2500 */
|
|
1118
|
+
.lexy-dp-field-table {
|
|
1119
|
+
width: 100%;
|
|
1120
|
+
border-collapse: collapse;
|
|
1121
|
+
font-family: var(--dp-mono);
|
|
1122
|
+
font-size: 12px;
|
|
1123
|
+
table-layout: fixed;
|
|
1124
|
+
}
|
|
1125
|
+
.lexy-dp-field-table th {
|
|
1126
|
+
text-align: left;
|
|
1127
|
+
font-weight: 600;
|
|
1128
|
+
color: #475569;
|
|
1129
|
+
padding: 8px 12px;
|
|
1130
|
+
background: #f8fafc;
|
|
1131
|
+
border-bottom: 1px solid var(--dp-border);
|
|
1132
|
+
border-right: 1px solid var(--dp-border);
|
|
1133
|
+
white-space: nowrap;
|
|
1134
|
+
overflow: hidden;
|
|
1135
|
+
text-overflow: ellipsis;
|
|
1136
|
+
font-size: 11px;
|
|
1137
|
+
text-transform: uppercase;
|
|
1138
|
+
letter-spacing: 0.05em;
|
|
1139
|
+
}
|
|
1140
|
+
.lexy-dp-field-table th:last-child { border-right: none; }
|
|
1141
|
+
.lexy-dp-field-table td {
|
|
1142
|
+
padding: 6px 12px;
|
|
1143
|
+
border-bottom: 1px solid var(--dp-border);
|
|
1144
|
+
border-right: 1px solid var(--dp-border);
|
|
1145
|
+
white-space: nowrap;
|
|
1146
|
+
overflow: hidden;
|
|
1147
|
+
text-overflow: ellipsis;
|
|
1148
|
+
vertical-align: middle;
|
|
1149
|
+
color: #334155;
|
|
1150
|
+
}
|
|
1151
|
+
.lexy-dp-field-table td:last-child { border-right: none; }
|
|
1152
|
+
.lexy-dp-field-table tbody tr { cursor: pointer; transition: background 0.15s ease; }
|
|
1153
|
+
.lexy-dp-field-table tbody tr:hover { background: rgba(0, 0, 0, 0.015); }
|
|
1154
|
+
.lexy-dp-field-table tbody tr.is-selected { background: rgba(79, 70, 229, 0.04); }
|
|
1155
|
+
.lexy-dp-field-table tbody tr.is-selected td { color: var(--dp-primary); font-weight: 600; }
|
|
1156
|
+
.lexy-dp-field-table tbody tr:last-child td { border-bottom: none; }
|
|
1157
|
+
|
|
1158
|
+
/* Wrapper to round table corners */
|
|
1159
|
+
.lexy-dp-eventdetail .lexy-dp-detail-body > div[style*="overflowX"] {
|
|
1160
|
+
border: 1px solid var(--dp-border);
|
|
1161
|
+
border-radius: 8px;
|
|
1162
|
+
overflow: hidden;
|
|
1163
|
+
}
|
|
1025
1164
|
|
|
1026
|
-
|
|
1165
|
+
/* \u2500\u2500 Floating Launcher \u2500\u2500 */
|
|
1166
|
+
.lexy-dp-launcher {
|
|
1167
|
+
position: fixed;
|
|
1168
|
+
bottom: 20px;
|
|
1169
|
+
right: 20px;
|
|
1170
|
+
z-index: 50;
|
|
1171
|
+
display: flex;
|
|
1172
|
+
align-items: center;
|
|
1173
|
+
justify-content: center;
|
|
1174
|
+
height: 48px;
|
|
1175
|
+
width: 48px;
|
|
1176
|
+
gap: 0;
|
|
1177
|
+
overflow: hidden;
|
|
1178
|
+
border: none !important;
|
|
1179
|
+
border-radius: 9999px;
|
|
1180
|
+
background: #0f172a !important;
|
|
1181
|
+
color: #ffffff !important;
|
|
1182
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15) !important;
|
|
1183
|
+
cursor: pointer;
|
|
1184
|
+
padding: 0 14px;
|
|
1185
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
1186
|
+
}
|
|
1187
|
+
.lexy-dp-launcher:hover {
|
|
1188
|
+
width: 176px;
|
|
1189
|
+
gap: 8px;
|
|
1190
|
+
background: #1e293b !important;
|
|
1191
|
+
transform: translateY(-2px);
|
|
1192
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2) !important;
|
|
1193
|
+
}
|
|
1194
|
+
.lexy-dp-launcher.is-open {
|
|
1195
|
+
width: 176px;
|
|
1196
|
+
gap: 8px;
|
|
1197
|
+
background: #0f172a !important;
|
|
1198
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2) !important;
|
|
1199
|
+
}
|
|
1200
|
+
.lexy-dp-launcher.is-open:hover {
|
|
1201
|
+
background: #1e293b !important;
|
|
1202
|
+
}
|
|
1203
|
+
.lexy-dp-launcher:focus-visible { outline: 2px solid var(--dp-primary); outline-offset: 2px; }
|
|
1204
|
+
.lexy-dp-launcher__icon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
1205
|
+
.lexy-dp-launcher__label {
|
|
1206
|
+
overflow: hidden;
|
|
1207
|
+
white-space: nowrap;
|
|
1208
|
+
font-size: 13px;
|
|
1209
|
+
font-weight: 600;
|
|
1210
|
+
max-width: 0;
|
|
1211
|
+
opacity: 0;
|
|
1212
|
+
transition: max-width 0.3s ease, opacity 0.2s ease;
|
|
1213
|
+
}
|
|
1214
|
+
.lexy-dp-launcher:hover .lexy-dp-launcher__label,
|
|
1215
|
+
.lexy-dp-launcher:focus-visible .lexy-dp-launcher__label,
|
|
1216
|
+
.lexy-dp-launcher.is-open .lexy-dp-launcher__label { max-width: 130px; opacity: 1; }
|
|
1217
|
+
|
|
1218
|
+
/* \u2500\u2500 Floating Sheet \u2500\u2500 */
|
|
1219
|
+
.lexy-dp-sheet {
|
|
1220
|
+
position: fixed;
|
|
1221
|
+
bottom: 84px;
|
|
1222
|
+
right: 20px;
|
|
1223
|
+
z-index: 50;
|
|
1224
|
+
display: flex;
|
|
1225
|
+
flex-direction: column;
|
|
1226
|
+
height: 75vh;
|
|
1227
|
+
width: min(100vw - 2.5rem, 460px);
|
|
1228
|
+
overflow: hidden;
|
|
1229
|
+
border: 1px solid var(--dp-border);
|
|
1230
|
+
border-radius: 12px;
|
|
1231
|
+
background: var(--dp-popover);
|
|
1232
|
+
color: var(--dp-fg);
|
|
1233
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
1234
|
+
animation: lexy-sheet-slide 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
1235
|
+
}
|
|
1027
1236
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1237
|
+
@keyframes lexy-sheet-slide {
|
|
1238
|
+
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
|
1239
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
1240
|
+
}
|
|
1031
1241
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1242
|
+
.lexy-dp-sheet__header {
|
|
1243
|
+
display: flex;
|
|
1244
|
+
align-items: center;
|
|
1245
|
+
justify-content: space-between;
|
|
1246
|
+
gap: 12px;
|
|
1247
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
1248
|
+
background: #0f172a;
|
|
1249
|
+
padding: 14px 18px;
|
|
1250
|
+
color: #ffffff;
|
|
1251
|
+
}
|
|
1252
|
+
.lexy-dp-sheet__title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; }
|
|
1253
|
+
.lexy-dp-sheet__actions { display: flex; align-items: center; gap: 6px; }
|
|
1254
|
+
.lexy-dp-iconbtn {
|
|
1255
|
+
display: flex;
|
|
1256
|
+
height: 30px;
|
|
1257
|
+
width: 30px;
|
|
1258
|
+
align-items: center;
|
|
1259
|
+
justify-content: center;
|
|
1260
|
+
border: none;
|
|
1261
|
+
background: transparent;
|
|
1262
|
+
border-radius: 6px;
|
|
1263
|
+
opacity: 0.6;
|
|
1264
|
+
color: #ffffff;
|
|
1265
|
+
cursor: pointer;
|
|
1266
|
+
transition: all 0.15s ease;
|
|
1267
|
+
}
|
|
1268
|
+
.lexy-dp-iconbtn:hover { opacity: 1; background: rgba(255, 255, 255, 0.1); color: #ffffff; }
|
|
1269
|
+
.lexy-dp-iconbtn svg { width: 15px; height: 15px; }
|
|
1270
|
+
.lexy-dp-sheet__body { flex: 1; min-height: 0; overflow-y: auto; background: var(--dp-bg); }
|
|
1271
|
+
`,jt=()=>String.raw`import {
|
|
1272
|
+
Activity,
|
|
1273
|
+
ArrowUpRight,
|
|
1274
|
+
ChevronRight,
|
|
1275
|
+
Eye,
|
|
1276
|
+
EyeOff,
|
|
1277
|
+
Maximize2,
|
|
1278
|
+
Settings,
|
|
1279
|
+
Trash2,
|
|
1280
|
+
X,
|
|
1281
|
+
Zap,
|
|
1282
|
+
} from "lucide-react";
|
|
1283
|
+
import { useState, useEffect, useSyncExternalStore } from "react";
|
|
1042
1284
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
"custom",
|
|
1061
|
-
]);
|
|
1285
|
+
import { prototypeDataContract } from "../data-contract/prototype-data-contract";
|
|
1286
|
+
import {
|
|
1287
|
+
catalog,
|
|
1288
|
+
type CatalogEvent,
|
|
1289
|
+
type CatalogLoad,
|
|
1290
|
+
} from "../ports/catalog";
|
|
1291
|
+
import type { DesignerActivityRecord } from "./designer-activity";
|
|
1292
|
+
import { designerActivity } from "./designer-activity";
|
|
1293
|
+
|
|
1294
|
+
// Panel autocontenido: trae su propio CSS scopeado (.lexy-dp) y no depende del
|
|
1295
|
+
// tema Tailwind del proyecto host ni de componentes del registry. Así se puede
|
|
1296
|
+
// previsualizar e iterar el diseño en cualquier entorno.
|
|
1297
|
+
const PANEL_STYLES = ${JSON.stringify(Pt)};
|
|
1298
|
+
|
|
1299
|
+
function PanelStyles() {
|
|
1300
|
+
return <style>{PANEL_STYLES}</style>;
|
|
1301
|
+
}
|
|
1062
1302
|
|
|
1063
|
-
|
|
1064
|
-
.
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
})
|
|
1079
|
-
.strict();
|
|
1303
|
+
const formatTime = (value: string) =>
|
|
1304
|
+
new Intl.DateTimeFormat("es-CL", {
|
|
1305
|
+
hour: "2-digit",
|
|
1306
|
+
minute: "2-digit",
|
|
1307
|
+
second: "2-digit",
|
|
1308
|
+
}).format(new Date(value));
|
|
1309
|
+
|
|
1310
|
+
const safeParse = (value: string | undefined): unknown => {
|
|
1311
|
+
if (value === undefined) return undefined;
|
|
1312
|
+
try {
|
|
1313
|
+
return JSON.parse(value);
|
|
1314
|
+
} catch {
|
|
1315
|
+
return value;
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1080
1318
|
|
|
1081
|
-
|
|
1082
|
-
|
|
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();
|
|
1319
|
+
const TREE_INDENT = 14;
|
|
1320
|
+
const TREE_BASE = 8;
|
|
1125
1321
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1322
|
+
const renderPrimitive = (value: unknown) => {
|
|
1323
|
+
if (typeof value === "string") return <span className="lexy-dp-json-row-str">"{value}"</span>;
|
|
1324
|
+
if (typeof value === "number") return <span className="lexy-dp-json-row-num">{value}</span>;
|
|
1325
|
+
if (typeof value === "boolean") return <span className="lexy-dp-json-row-num">{String(value)}</span>;
|
|
1326
|
+
if (value === null) return <span className="lexy-dp-muted">null</span>;
|
|
1327
|
+
return <span className="lexy-dp-json-row-str">{String(value)}</span>;
|
|
1328
|
+
};
|
|
1133
1329
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1330
|
+
function JsonNode({
|
|
1331
|
+
label,
|
|
1332
|
+
value,
|
|
1333
|
+
depth = 0,
|
|
1334
|
+
defaultOpen = false,
|
|
1335
|
+
}: {
|
|
1336
|
+
label: string;
|
|
1337
|
+
value: unknown;
|
|
1338
|
+
depth?: number;
|
|
1339
|
+
defaultOpen?: boolean;
|
|
1340
|
+
}) {
|
|
1341
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
1342
|
+
const isBranch = value !== null && typeof value === "object";
|
|
1343
|
+
const indent = { paddingLeft: (depth * TREE_INDENT + TREE_BASE) + "px" };
|
|
1344
|
+
|
|
1345
|
+
if (!isBranch) {
|
|
1346
|
+
return (
|
|
1347
|
+
<div style={indent} className="lexy-dp-json-row">
|
|
1348
|
+
<span className="lexy-dp-json-spacer" aria-hidden />
|
|
1349
|
+
<span className="lexy-dp-json-leaf">
|
|
1350
|
+
<span className="lexy-dp-muted">{label}:</span> {renderPrimitive(value)}
|
|
1351
|
+
</span>
|
|
1352
|
+
</div>
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1158
1355
|
|
|
1159
|
-
|
|
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
|
-
});
|
|
1356
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
1183
1357
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1358
|
+
return (
|
|
1359
|
+
<div>
|
|
1360
|
+
<button
|
|
1361
|
+
type="button"
|
|
1362
|
+
onClick={() => setOpen((v) => !v)}
|
|
1363
|
+
style={indent}
|
|
1364
|
+
className="lexy-dp-json-toggle"
|
|
1365
|
+
>
|
|
1366
|
+
<ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
|
|
1367
|
+
<span className="lexy-dp-json-leaf">
|
|
1368
|
+
<span className="lexy-dp-muted">{label}:</span>
|
|
1369
|
+
{!open ? (
|
|
1370
|
+
<span className="lexy-dp-muted">
|
|
1371
|
+
{" "}
|
|
1372
|
+
{Array.isArray(value) ? "Array(" + entries.length + ")" : "{…}"}
|
|
1373
|
+
</span>
|
|
1374
|
+
) : null}
|
|
1375
|
+
</span>
|
|
1376
|
+
</button>
|
|
1377
|
+
{open
|
|
1378
|
+
? entries.map(([key, child]) => (
|
|
1379
|
+
<JsonNode key={key} label={key} value={child} depth={depth + 1} defaultOpen />
|
|
1380
|
+
))
|
|
1381
|
+
: null}
|
|
1382
|
+
</div>
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1199
1385
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
-
}
|
|
1386
|
+
function LogEntry({ item }: { item: DesignerActivityRecord }) {
|
|
1387
|
+
const [open, setOpen] = useState(false);
|
|
1388
|
+
const trigger = item.triggerId ?? "desconocido";
|
|
1389
|
+
const operationName = item.technicalId ?? item.label;
|
|
1390
|
+
const input = safeParse(item.inputPreview);
|
|
1391
|
+
const inputLabel = item.kind === "load" ? "params" : "payload";
|
|
1216
1392
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1393
|
+
return (
|
|
1394
|
+
<div className="lexy-dp-log">
|
|
1395
|
+
<button type="button" onClick={() => setOpen((v) => !v)} className="lexy-dp-log__head">
|
|
1396
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (open ? " is-open" : "")} />
|
|
1397
|
+
<span className="lexy-dp-log__event">{operationName}</span>
|
|
1398
|
+
<span className="lexy-dp-log__time">{formatTime(item.occurredAt)}</span>
|
|
1399
|
+
</button>
|
|
1400
|
+
{open ? (
|
|
1401
|
+
<div className="lexy-dp-log__body">
|
|
1402
|
+
<div style={{ paddingLeft: TREE_BASE + "px" }} className="lexy-dp-json-row">
|
|
1403
|
+
<span className="lexy-dp-json-spacer" aria-hidden />
|
|
1404
|
+
<span className="lexy-dp-json-leaf">
|
|
1405
|
+
<span className="lexy-dp-muted">fuente:</span>{" "}
|
|
1406
|
+
<span className="lexy-dp-json-row-str">{trigger}</span>
|
|
1407
|
+
</span>
|
|
1408
|
+
</div>
|
|
1409
|
+
{input !== undefined ? <JsonNode label={inputLabel} value={input} defaultOpen /> : null}
|
|
1410
|
+
</div>
|
|
1411
|
+
) : null}
|
|
1412
|
+
</div>
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1227
1415
|
|
|
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
|
-
};
|
|
1416
|
+
const DESIGNER_PANEL_ROUTE = "/designer-panel";
|
|
1241
1417
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1418
|
+
function ActivityLog() {
|
|
1419
|
+
const activity = useSyncExternalStore(
|
|
1420
|
+
designerActivity.subscribe,
|
|
1421
|
+
designerActivity.getSnapshot,
|
|
1422
|
+
designerActivity.getSnapshot,
|
|
1423
|
+
);
|
|
1424
|
+
return (
|
|
1425
|
+
<div className="lexy-dp-activitylog">
|
|
1426
|
+
{activity.length === 0 ? (
|
|
1427
|
+
<p className="lexy-dp-empty">Sin actividad. Ejecuta una carga o publica un evento.</p>
|
|
1428
|
+
) : null}
|
|
1429
|
+
{activity.map((item) => (
|
|
1430
|
+
<LogEntry key={item.id} item={item} />
|
|
1431
|
+
))}
|
|
1432
|
+
</div>
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1247
1435
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1436
|
+
const matchesQuery = (item: DesignerActivityRecord, query: string): boolean => {
|
|
1437
|
+
const haystack = [
|
|
1438
|
+
item.technicalId,
|
|
1439
|
+
item.label,
|
|
1440
|
+
item.triggerId,
|
|
1441
|
+
item.inputPreview,
|
|
1442
|
+
]
|
|
1443
|
+
.filter(Boolean)
|
|
1444
|
+
.join(" ")
|
|
1445
|
+
.toLowerCase();
|
|
1446
|
+
return haystack.includes(query);
|
|
1257
1447
|
};
|
|
1258
1448
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1449
|
+
function ActivityTab() {
|
|
1450
|
+
const [query, setQuery] = useState("");
|
|
1451
|
+
const activity = useSyncExternalStore(
|
|
1452
|
+
designerActivity.subscribe,
|
|
1453
|
+
designerActivity.getSnapshot,
|
|
1454
|
+
designerActivity.getSnapshot,
|
|
1455
|
+
);
|
|
1456
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
1457
|
+
const filtered = normalizedQuery
|
|
1458
|
+
? activity.filter((item) => matchesQuery(item, normalizedQuery))
|
|
1459
|
+
: activity;
|
|
1263
1460
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1461
|
+
return (
|
|
1462
|
+
<div className="lexy-dp-col">
|
|
1463
|
+
<div className="lexy-dp-toolbar">
|
|
1464
|
+
<button
|
|
1465
|
+
type="button"
|
|
1466
|
+
aria-label="Limpiar actividad"
|
|
1467
|
+
title="Limpiar actividad"
|
|
1468
|
+
className="lexy-dp-iconbtn"
|
|
1469
|
+
onClick={() => designerActivity.clear()}
|
|
1470
|
+
>
|
|
1471
|
+
<Trash2 />
|
|
1472
|
+
</button>
|
|
1473
|
+
<div className="lexy-dp-search">
|
|
1474
|
+
<input
|
|
1475
|
+
value={query}
|
|
1476
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
1477
|
+
placeholder="Buscar en los logs…"
|
|
1478
|
+
/>
|
|
1479
|
+
{query ? (
|
|
1480
|
+
<button
|
|
1481
|
+
type="button"
|
|
1482
|
+
aria-label="Limpiar búsqueda"
|
|
1483
|
+
className="lexy-dp-search__clear"
|
|
1484
|
+
onClick={() => setQuery("")}
|
|
1485
|
+
>
|
|
1486
|
+
<X />
|
|
1487
|
+
</button>
|
|
1488
|
+
) : null}
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
<div className="lexy-dp-activitylog lexy-dp-activitylog--scroll">
|
|
1492
|
+
{filtered.length === 0 ? (
|
|
1493
|
+
<p className="lexy-dp-empty">
|
|
1494
|
+
{activity.length === 0
|
|
1495
|
+
? "Sin actividad. Ejecuta una carga o publica un evento."
|
|
1496
|
+
: "Sin coincidencias para la búsqueda."}
|
|
1497
|
+
</p>
|
|
1498
|
+
) : null}
|
|
1499
|
+
{filtered.map((item) => (
|
|
1500
|
+
<LogEntry key={item.id} item={item} />
|
|
1501
|
+
))}
|
|
1502
|
+
</div>
|
|
1503
|
+
</div>
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1268
1506
|
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
return
|
|
1272
|
-
|
|
1273
|
-
fieldId,
|
|
1274
|
-
entity: dataContract.entities[entityId],
|
|
1275
|
-
field: dataContract.entities[entityId]?.fields[fieldId],
|
|
1276
|
-
};
|
|
1507
|
+
const inferType = (value: unknown): string => {
|
|
1508
|
+
if (value === null) return "null";
|
|
1509
|
+
if (Array.isArray(value)) return "array";
|
|
1510
|
+
return typeof value;
|
|
1277
1511
|
};
|
|
1278
1512
|
|
|
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);
|
|
1513
|
+
const TYPE_COLORS: Record<string, string> = {
|
|
1514
|
+
string: "var(--dp-success)",
|
|
1515
|
+
number: "var(--dp-info)",
|
|
1516
|
+
boolean: "var(--dp-warning)",
|
|
1286
1517
|
};
|
|
1287
1518
|
|
|
1288
|
-
const
|
|
1519
|
+
const TypeLabel = ({ value }: { value: unknown }) => (
|
|
1520
|
+
<span className="lexy-dp-muted" style={{ marginLeft: "8px" }}>{inferType(value)}</span>
|
|
1521
|
+
);
|
|
1289
1522
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
const
|
|
1300
|
-
const
|
|
1301
|
-
return verifier === expected;
|
|
1302
|
-
};
|
|
1523
|
+
function PayloadKeyNode({
|
|
1524
|
+
label,
|
|
1525
|
+
value,
|
|
1526
|
+
depth = 0,
|
|
1527
|
+
}: {
|
|
1528
|
+
label: string;
|
|
1529
|
+
value: unknown;
|
|
1530
|
+
depth?: number;
|
|
1531
|
+
}) {
|
|
1532
|
+
const [open, setOpen] = useState(true);
|
|
1533
|
+
const indent = { paddingLeft: depth * TREE_INDENT + TREE_BASE + "px" };
|
|
1303
1534
|
|
|
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.";
|
|
1535
|
+
if (Array.isArray(value)) {
|
|
1536
|
+
const firstObj = value.find(
|
|
1537
|
+
(item) => item !== null && typeof item === "object" && !Array.isArray(item),
|
|
1538
|
+
);
|
|
1539
|
+
const children = firstObj ? Object.entries(firstObj as Record<string, unknown>) : [];
|
|
1540
|
+
if (children.length === 0) {
|
|
1541
|
+
return (
|
|
1542
|
+
<div style={indent} className="lexy-dp-json-row">
|
|
1543
|
+
<span className="lexy-dp-json-spacer" aria-hidden />
|
|
1544
|
+
<span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
|
|
1545
|
+
</div>
|
|
1546
|
+
);
|
|
1327
1547
|
}
|
|
1548
|
+
return (
|
|
1549
|
+
<div>
|
|
1550
|
+
<button type="button" onClick={() => setOpen((v) => !v)} style={indent} className="lexy-dp-json-toggle">
|
|
1551
|
+
<ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
|
|
1552
|
+
<span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
|
|
1553
|
+
</button>
|
|
1554
|
+
{open
|
|
1555
|
+
? children.map(([key, child]) => (
|
|
1556
|
+
<PayloadKeyNode key={key} label={key} value={child} depth={depth + 1} />
|
|
1557
|
+
))
|
|
1558
|
+
: null}
|
|
1559
|
+
</div>
|
|
1560
|
+
);
|
|
1328
1561
|
}
|
|
1329
1562
|
|
|
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.";
|
|
1563
|
+
if (value !== null && typeof value === "object") {
|
|
1564
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
1565
|
+
return (
|
|
1566
|
+
<div>
|
|
1567
|
+
<button type="button" onClick={() => setOpen((v) => !v)} style={indent} className="lexy-dp-json-toggle">
|
|
1568
|
+
<ChevronRight className={"lexy-dp-chevron" + (open ? " is-open" : "")} />
|
|
1569
|
+
<span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
|
|
1570
|
+
</button>
|
|
1571
|
+
{open
|
|
1572
|
+
? entries.map(([key, child]) => (
|
|
1573
|
+
<PayloadKeyNode key={key} label={key} value={child} depth={depth + 1} />
|
|
1574
|
+
))
|
|
1575
|
+
: null}
|
|
1576
|
+
</div>
|
|
1577
|
+
);
|
|
1346
1578
|
}
|
|
1347
1579
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1580
|
+
return (
|
|
1581
|
+
<div style={indent} className="lexy-dp-json-row">
|
|
1582
|
+
<span className="lexy-dp-json-spacer" aria-hidden />
|
|
1583
|
+
<span className="lexy-dp-json-leaf" style={{ color: TYPE_COLORS[inferType(value)] ?? "var(--dp-fg)" }}>{label}<TypeLabel value={value} /></span>
|
|
1584
|
+
</div>
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1354
1587
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1588
|
+
function EventDetail({ event }: { event: CatalogEvent }) {
|
|
1589
|
+
const [triggersOpen, setTriggersOpen] = useState(true);
|
|
1590
|
+
const [payloadOpen, setPayloadOpen] = useState(true);
|
|
1358
1591
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1592
|
+
const activity = useSyncExternalStore(
|
|
1593
|
+
designerActivity.subscribe,
|
|
1594
|
+
designerActivity.getSnapshot,
|
|
1595
|
+
designerActivity.getSnapshot,
|
|
1596
|
+
);
|
|
1597
|
+
const observedTriggers = Array.from(
|
|
1598
|
+
new Set(
|
|
1599
|
+
activity
|
|
1600
|
+
.filter((record) => record.technicalId === event.technicalId && record.triggerId)
|
|
1601
|
+
.map((record) => record.triggerId as string),
|
|
1602
|
+
),
|
|
1603
|
+
);
|
|
1604
|
+
const payload = event.lastPayload;
|
|
1367
1605
|
|
|
1368
|
-
return
|
|
1369
|
-
|
|
1606
|
+
return (
|
|
1607
|
+
<div className="lexy-dp-eventdetail">
|
|
1608
|
+
<div className="lexy-dp-detail-body">
|
|
1609
|
+
<div className="lexy-dp-detail-field">
|
|
1610
|
+
<span className="lexy-dp-detail-label">Nombre</span>
|
|
1611
|
+
<span>{event.technicalId}</span>
|
|
1612
|
+
</div>
|
|
1613
|
+
{event.description ? (
|
|
1614
|
+
<div className="lexy-dp-detail-field">
|
|
1615
|
+
<span className="lexy-dp-detail-label">Descripción</span>
|
|
1616
|
+
<span>{event.description}</span>
|
|
1617
|
+
</div>
|
|
1618
|
+
) : null}
|
|
1619
|
+
{event.lastReceipt ? (
|
|
1620
|
+
<div className="lexy-dp-detail-field">
|
|
1621
|
+
<span className="lexy-dp-detail-label">Último receipt</span>
|
|
1622
|
+
<span className="lexy-dp-mono">
|
|
1623
|
+
{event.lastReceipt.eventId} · {event.lastReceipt.status}
|
|
1624
|
+
</span>
|
|
1625
|
+
</div>
|
|
1626
|
+
) : null}
|
|
1627
|
+
</div>
|
|
1370
1628
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1629
|
+
<button type="button" className="lexy-dp-list-section" onClick={() => setTriggersOpen((v) => !v)}>
|
|
1630
|
+
Detonantes
|
|
1631
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (triggersOpen ? " is-open" : "")} />
|
|
1632
|
+
</button>
|
|
1633
|
+
{triggersOpen ? (
|
|
1634
|
+
<div className="lexy-dp-detail-body">
|
|
1635
|
+
{observedTriggers.length > 0 ? (
|
|
1636
|
+
<ul className="lexy-dp-trigger-list">
|
|
1637
|
+
{observedTriggers.map((trigger) => (
|
|
1638
|
+
<li key={trigger} className="lexy-dp-trigger-item">
|
|
1639
|
+
<span className="lexy-dp-bullet">·</span>
|
|
1640
|
+
<span className="lexy-dp-mono">{trigger}</span>
|
|
1641
|
+
</li>
|
|
1642
|
+
))}
|
|
1643
|
+
</ul>
|
|
1644
|
+
) : event.trigger ? (
|
|
1645
|
+
<span className="lexy-dp-muted">{event.trigger} · sin publicar aún</span>
|
|
1646
|
+
) : (
|
|
1647
|
+
<span className="lexy-dp-muted">Sin publicar aún.</span>
|
|
1648
|
+
)}
|
|
1649
|
+
</div>
|
|
1650
|
+
) : null}
|
|
1651
|
+
|
|
1652
|
+
<button type="button" className="lexy-dp-list-section" onClick={() => setPayloadOpen((v) => !v)}>
|
|
1653
|
+
Payload
|
|
1654
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (payloadOpen ? " is-open" : "")} />
|
|
1655
|
+
</button>
|
|
1656
|
+
{payloadOpen ? (
|
|
1657
|
+
<div className="lexy-dp-detail-body">
|
|
1658
|
+
{payload !== undefined && payload !== null ? (
|
|
1659
|
+
typeof payload === "object" ? (
|
|
1660
|
+
<div className="lexy-dp-mono" style={{ fontSize: "12px" }}>
|
|
1661
|
+
{Object.entries(payload as Record<string, unknown>).map(([key, value]) => (
|
|
1662
|
+
<PayloadKeyNode key={key} label={key} value={value} />
|
|
1663
|
+
))}
|
|
1664
|
+
</div>
|
|
1665
|
+
) : (
|
|
1666
|
+
<span className="lexy-dp-mono lexy-dp-muted">{typeof payload}</span>
|
|
1667
|
+
)
|
|
1668
|
+
) : (
|
|
1669
|
+
<span className="lexy-dp-muted">Sin datos aún — publica el evento para ver el payload.</span>
|
|
1670
|
+
)}
|
|
1671
|
+
</div>
|
|
1672
|
+
) : null}
|
|
1673
|
+
</div>
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1400
1676
|
|
|
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
|
-
};
|
|
1677
|
+
function LoadDetail({ load }: { load: CatalogLoad }) {
|
|
1678
|
+
const [paramsOpen, setParamsOpen] = useState(true);
|
|
1679
|
+
const params = load.lastParams;
|
|
1414
1680
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
-
}
|
|
1681
|
+
return (
|
|
1682
|
+
<div className="lexy-dp-eventdetail">
|
|
1683
|
+
<div className="lexy-dp-detail-body">
|
|
1684
|
+
<div className="lexy-dp-detail-field">
|
|
1685
|
+
<span className="lexy-dp-detail-label">Nombre</span>
|
|
1686
|
+
<span>{load.technicalId}</span>
|
|
1687
|
+
</div>
|
|
1688
|
+
{load.description ? (
|
|
1689
|
+
<div className="lexy-dp-detail-field">
|
|
1690
|
+
<span className="lexy-dp-detail-label">Descripción</span>
|
|
1691
|
+
<span>{load.description}</span>
|
|
1692
|
+
</div>
|
|
1693
|
+
) : null}
|
|
1694
|
+
{load.reads.entities.length > 0 ? (
|
|
1695
|
+
<div className="lexy-dp-detail-field">
|
|
1696
|
+
<span className="lexy-dp-detail-label">Lee</span>
|
|
1697
|
+
<span className="lexy-dp-mono">{load.reads.entities.join(", ")}</span>
|
|
1698
|
+
</div>
|
|
1699
|
+
) : null}
|
|
1700
|
+
</div>
|
|
1448
1701
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1702
|
+
<button type="button" className="lexy-dp-list-section" onClick={() => setParamsOpen((v) => !v)}>
|
|
1703
|
+
Parámetros
|
|
1704
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (paramsOpen ? " is-open" : "")} />
|
|
1705
|
+
</button>
|
|
1706
|
+
{paramsOpen ? (
|
|
1707
|
+
<div className="lexy-dp-detail-body">
|
|
1708
|
+
{params !== undefined && params !== null && typeof params === "object" ? (
|
|
1709
|
+
<div className="lexy-dp-mono" style={{ fontSize: "12px" }}>
|
|
1710
|
+
{Object.entries(params as Record<string, unknown>).map(([key, value]) => (
|
|
1711
|
+
<PayloadKeyNode key={key} label={key} value={value} />
|
|
1712
|
+
))}
|
|
1713
|
+
</div>
|
|
1714
|
+
) : (
|
|
1715
|
+
<span className="lexy-dp-muted">Sin parámetros — esta carga no recibe filtros.</span>
|
|
1716
|
+
)}
|
|
1717
|
+
</div>
|
|
1718
|
+
) : null}
|
|
1719
|
+
</div>
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1467
1722
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
path: ["fieldHints", reference],
|
|
1473
|
-
message: 'El campo "' + entityId + "." + fieldId + '" no existe.',
|
|
1474
|
-
});
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
if (!isSemanticCompatible(hint.semantic, field)) {
|
|
1478
|
-
issues.push({
|
|
1479
|
-
path: ["fieldHints", reference, "semantic"],
|
|
1480
|
-
message:
|
|
1481
|
-
'La semántica "' + hint.semantic + '" no es compatible con ' + field.dataType + ".",
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
if (field.dataType === "enum" && hint.constraints?.allowedValues) {
|
|
1485
|
-
for (const value of hint.constraints.allowedValues) {
|
|
1486
|
-
if (!field.enumValues?.includes(value)) {
|
|
1487
|
-
issues.push({
|
|
1488
|
-
path: ["fieldHints", reference, "constraints", "allowedValues"],
|
|
1489
|
-
message: 'El valor "' + value + '" no existe en enumValues.',
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1723
|
+
type SelectedContract =
|
|
1724
|
+
| { kind: "event"; id: string }
|
|
1725
|
+
| { kind: "load"; id: string }
|
|
1726
|
+
| { kind: "entity"; id: string };
|
|
1495
1727
|
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1728
|
+
type EntitySelectedItem =
|
|
1729
|
+
| { kind: "field"; id: string }
|
|
1730
|
+
| { kind: "relation"; id: string; reverse: boolean };
|
|
1498
1731
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
eventEngineContract: EventEngineContractLike;
|
|
1504
|
-
}): SimulatorValidationIssue[] => {
|
|
1505
|
-
const issues: SimulatorValidationIssue[] = [];
|
|
1506
|
-
const { profile, scenarios, dataContract, eventEngineContract } = input;
|
|
1507
|
-
|
|
1508
|
-
for (const [scenarioId, scenario] of Object.entries(scenarios.scenarios)) {
|
|
1509
|
-
for (const [entityId, records] of Object.entries(scenario.entities)) {
|
|
1510
|
-
const entity = dataContract.entities[entityId];
|
|
1511
|
-
if (!entity) {
|
|
1512
|
-
issues.push({
|
|
1513
|
-
path: ["scenarios", scenarioId, "entities", entityId],
|
|
1514
|
-
message: 'La entidad "' + entityId + '" no existe en la Spec 1.',
|
|
1515
|
-
});
|
|
1516
|
-
continue;
|
|
1517
|
-
}
|
|
1732
|
+
function EntityDetail({ entityId }: { entityId: string }) {
|
|
1733
|
+
const [fieldsOpen, setFieldsOpen] = useState(true);
|
|
1734
|
+
const [selected, setSelected] = useState<EntitySelectedItem | null>(null);
|
|
1735
|
+
const [itemDetailOpen, setItemDetailOpen] = useState(true);
|
|
1518
1736
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
issues.push({
|
|
1525
|
-
path: ["scenarios", scenarioId, "entities", entityId, recordIndex, fieldKey],
|
|
1526
|
-
message: 'El campo "' + fieldKey + '" no existe en la entidad "' + entityId + '".',
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1737
|
+
const entries = useSyncExternalStore(
|
|
1738
|
+
catalog.subscribe,
|
|
1739
|
+
catalog.getSnapshot,
|
|
1740
|
+
catalog.getSnapshot,
|
|
1741
|
+
);
|
|
1530
1742
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
}
|
|
1743
|
+
const entity = prototypeDataContract.entities[entityId];
|
|
1744
|
+
if (!entity) return <p className="lexy-dp-empty">Entidad no encontrada.</p>;
|
|
1745
|
+
|
|
1746
|
+
const entityRelations = [
|
|
1747
|
+
...prototypeDataContract.relations
|
|
1748
|
+
.filter((r) => r.fromEntity === entityId)
|
|
1749
|
+
.map((r) => ({ ...r, targetEntity: r.toEntity, reverse: false })),
|
|
1750
|
+
...prototypeDataContract.relations
|
|
1751
|
+
.filter((r) => r.toEntity === entityId)
|
|
1752
|
+
.map((r) => ({ ...r, targetEntity: r.fromEntity, reverse: true })),
|
|
1753
|
+
];
|
|
1754
|
+
|
|
1755
|
+
const hasRows = Object.keys(entity.fields).length > 0 || entityRelations.length > 0;
|
|
1756
|
+
|
|
1757
|
+
const selectedField = selected?.kind === "field" ? entity.fields[selected.id] : null;
|
|
1758
|
+
const selectedRelation =
|
|
1759
|
+
selected?.kind === "relation"
|
|
1760
|
+
? entityRelations.find((r) => r.id === selected.id && r.reverse === selected.reverse) ?? null
|
|
1761
|
+
: null;
|
|
1762
|
+
|
|
1763
|
+
const fieldEvents =
|
|
1764
|
+
selected?.kind === "field"
|
|
1765
|
+
? entries.filter(
|
|
1766
|
+
(entry): entry is CatalogEvent =>
|
|
1767
|
+
entry.kind === "event" &&
|
|
1768
|
+
entry.writes.fields.includes(entityId + "." + selected.id),
|
|
1769
|
+
)
|
|
1770
|
+
: [];
|
|
1771
|
+
|
|
1772
|
+
const handleClick = (item: EntitySelectedItem) => {
|
|
1773
|
+
setSelected((prev) =>
|
|
1774
|
+
prev?.kind === item.kind && prev.id === item.id ? null : item,
|
|
1775
|
+
);
|
|
1776
|
+
setItemDetailOpen(true);
|
|
1777
|
+
};
|
|
1581
1778
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
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
|
-
}
|
|
1779
|
+
const isSelected = (item: EntitySelectedItem) =>
|
|
1780
|
+
selected?.kind === item.kind &&
|
|
1781
|
+
selected.id === item.id &&
|
|
1782
|
+
(item.kind !== "relation" || (selected as { reverse: boolean }).reverse === item.reverse);
|
|
1597
1783
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
message:
|
|
1611
|
-
'Referencia "' +
|
|
1612
|
-
String(value) +
|
|
1613
|
-
'" inexistente para relationBinding "' +
|
|
1614
|
-
bindingId +
|
|
1615
|
-
'".',
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1784
|
+
return (
|
|
1785
|
+
<div className="lexy-dp-eventdetail">
|
|
1786
|
+
<div className="lexy-dp-detail-body">
|
|
1787
|
+
<div className="lexy-dp-detail-field">
|
|
1788
|
+
<span className="lexy-dp-detail-label">Nombre</span>
|
|
1789
|
+
<span>{entityId}</span>
|
|
1790
|
+
</div>
|
|
1791
|
+
<div className="lexy-dp-detail-field">
|
|
1792
|
+
<span className="lexy-dp-detail-label">Descripción</span>
|
|
1793
|
+
<span>{entity.productDescription}</span>
|
|
1794
|
+
</div>
|
|
1795
|
+
</div>
|
|
1620
1796
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1797
|
+
<button type="button" className="lexy-dp-list-section" onClick={() => setFieldsOpen((v) => !v)}>
|
|
1798
|
+
Campos
|
|
1799
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (fieldsOpen ? " is-open" : "")} />
|
|
1800
|
+
</button>
|
|
1801
|
+
{fieldsOpen ? (
|
|
1802
|
+
!hasRows ? (
|
|
1803
|
+
<div className="lexy-dp-detail-body">
|
|
1804
|
+
<span className="lexy-dp-muted">Sin campos declarados.</span>
|
|
1805
|
+
</div>
|
|
1806
|
+
) : (
|
|
1807
|
+
<div style={{ overflowX: "auto" }}>
|
|
1808
|
+
<table className="lexy-dp-field-table">
|
|
1809
|
+
<thead>
|
|
1810
|
+
<tr>
|
|
1811
|
+
<th style={{ color: "var(--dp-fg)" }}>Campo</th>
|
|
1812
|
+
<th style={{ width: "100px", color: "var(--dp-muted-fg)" }}>Tipo</th>
|
|
1813
|
+
</tr>
|
|
1814
|
+
</thead>
|
|
1815
|
+
<tbody>
|
|
1816
|
+
{Object.entries(entity.fields).map(([fieldId, field]) => {
|
|
1817
|
+
const sel = isSelected({ kind: "field", id: fieldId });
|
|
1818
|
+
return (
|
|
1819
|
+
<tr
|
|
1820
|
+
key={fieldId}
|
|
1821
|
+
className={sel ? "is-selected" : ""}
|
|
1822
|
+
onClick={() => handleClick({ kind: "field", id: fieldId })}
|
|
1823
|
+
>
|
|
1824
|
+
<td style={sel ? undefined : { color: TYPE_COLORS[field.dataType] ?? "var(--dp-fg)" }}>
|
|
1825
|
+
{fieldId}
|
|
1826
|
+
</td>
|
|
1827
|
+
<td className={sel ? undefined : "lexy-dp-muted"}>{field.dataType}</td>
|
|
1828
|
+
</tr>
|
|
1829
|
+
);
|
|
1830
|
+
})}
|
|
1831
|
+
{entityRelations.map((rel) => {
|
|
1832
|
+
const sel = isSelected({ kind: "relation", id: rel.id, reverse: rel.reverse });
|
|
1833
|
+
return (
|
|
1834
|
+
<tr
|
|
1835
|
+
key={rel.id + (rel.reverse ? "_r" : "")}
|
|
1836
|
+
className={sel ? "is-selected" : ""}
|
|
1837
|
+
onClick={() => handleClick({ kind: "relation", id: rel.id, reverse: rel.reverse })}
|
|
1838
|
+
>
|
|
1839
|
+
<td style={sel ? undefined : { color: "var(--dp-primary)" }}>
|
|
1840
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: "3px" }}>
|
|
1841
|
+
<ArrowUpRight style={{ width: "11px", height: "11px", flexShrink: 0 }} />
|
|
1842
|
+
{rel.id}
|
|
1843
|
+
</span>
|
|
1844
|
+
</td>
|
|
1845
|
+
<td style={sel ? undefined : { color: "var(--dp-primary)", opacity: 0.8 }}>
|
|
1846
|
+
{rel.targetEntity}
|
|
1847
|
+
</td>
|
|
1848
|
+
</tr>
|
|
1849
|
+
);
|
|
1850
|
+
})}
|
|
1851
|
+
</tbody>
|
|
1852
|
+
</table>
|
|
1853
|
+
</div>
|
|
1854
|
+
)
|
|
1855
|
+
) : null}
|
|
1856
|
+
|
|
1857
|
+
{selected ? (
|
|
1858
|
+
<>
|
|
1859
|
+
<button type="button" className="lexy-dp-list-section" onClick={() => setItemDetailOpen((v) => !v)}>
|
|
1860
|
+
<span>Detalle · <span className="lexy-dp-mono">{selected.id}</span></span>
|
|
1861
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (itemDetailOpen ? " is-open" : "")} />
|
|
1862
|
+
</button>
|
|
1863
|
+
{itemDetailOpen ? (
|
|
1864
|
+
<div className="lexy-dp-detail-body">
|
|
1865
|
+
{selectedField ? (
|
|
1866
|
+
<>
|
|
1867
|
+
<div className="lexy-dp-detail-field">
|
|
1868
|
+
<span className="lexy-dp-detail-label">Eventos que modifican</span>
|
|
1869
|
+
</div>
|
|
1870
|
+
{fieldEvents.length === 0 ? (
|
|
1871
|
+
<span className="lexy-dp-muted">Ningún evento modifica este campo.</span>
|
|
1872
|
+
) : (
|
|
1873
|
+
<ul className="lexy-dp-trigger-list">
|
|
1874
|
+
{fieldEvents.map((event) => (
|
|
1875
|
+
<li key={event.technicalId} className="lexy-dp-trigger-item">
|
|
1876
|
+
<span className="lexy-dp-bullet">·</span>
|
|
1877
|
+
<span className="lexy-dp-mono">{event.technicalId}</span>
|
|
1878
|
+
</li>
|
|
1879
|
+
))}
|
|
1880
|
+
</ul>
|
|
1881
|
+
)}
|
|
1882
|
+
</>
|
|
1883
|
+
) : selectedRelation ? (
|
|
1884
|
+
<>
|
|
1885
|
+
<div className="lexy-dp-detail-field">
|
|
1886
|
+
<span className="lexy-dp-detail-label">Descripción</span>
|
|
1887
|
+
<span>{selectedRelation.productDescription}</span>
|
|
1888
|
+
</div>
|
|
1889
|
+
<div className="lexy-dp-detail-field">
|
|
1890
|
+
<span className="lexy-dp-detail-label">Cardinalidad</span>
|
|
1891
|
+
<span>{selectedRelation.cardinality}</span>
|
|
1892
|
+
</div>
|
|
1893
|
+
<div className="lexy-dp-detail-field">
|
|
1894
|
+
<span className="lexy-dp-detail-label">Resolución</span>
|
|
1895
|
+
<span>{selectedRelation.resolution}</span>
|
|
1896
|
+
</div>
|
|
1897
|
+
</>
|
|
1898
|
+
) : null}
|
|
1899
|
+
</div>
|
|
1900
|
+
) : null}
|
|
1901
|
+
</>
|
|
1902
|
+
) : null}
|
|
1903
|
+
</div>
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1635
1906
|
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1907
|
+
function EventsTab() {
|
|
1908
|
+
const entries = useSyncExternalStore(
|
|
1909
|
+
catalog.subscribe,
|
|
1910
|
+
catalog.getSnapshot,
|
|
1911
|
+
catalog.getSnapshot,
|
|
1912
|
+
);
|
|
1913
|
+
const events = entries.filter((e): e is CatalogEvent => e.kind === "event");
|
|
1914
|
+
const loads = entries.filter((e): e is CatalogLoad => e.kind === "load");
|
|
1915
|
+
const entities = Object.entries(prototypeDataContract.entities);
|
|
1916
|
+
|
|
1917
|
+
const defaultSelected: SelectedContract | null =
|
|
1918
|
+
entities[0] ? { kind: "entity", id: entities[0][0] } : null;
|
|
1919
|
+
|
|
1920
|
+
const [selected, setSelected] = useState<SelectedContract | null>(defaultSelected);
|
|
1921
|
+
const [eventsOpen, setEventsOpen] = useState(true);
|
|
1922
|
+
const [loadsOpen, setLoadsOpen] = useState(true);
|
|
1923
|
+
const [entitiesOpen, setEntitiesOpen] = useState(true);
|
|
1924
|
+
|
|
1925
|
+
if (events.length === 0 && loads.length === 0 && entities.length === 0) {
|
|
1926
|
+
return (
|
|
1927
|
+
<p className="lexy-dp-empty">
|
|
1928
|
+
Publica un evento, ejecuta una carga o declara entidades para ver contenido aquí.
|
|
1929
|
+
</p>
|
|
1930
|
+
);
|
|
1644
1931
|
}
|
|
1645
1932
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1933
|
+
const selectedEvent =
|
|
1934
|
+
selected?.kind === "event"
|
|
1935
|
+
? (events.find((e) => e.technicalId === selected.id) ?? null)
|
|
1936
|
+
: null;
|
|
1937
|
+
const selectedLoad =
|
|
1938
|
+
selected?.kind === "load"
|
|
1939
|
+
? (loads.find((l) => l.technicalId === selected.id) ?? null)
|
|
1940
|
+
: null;
|
|
1941
|
+
|
|
1942
|
+
const renderGroup = (
|
|
1943
|
+
label: string,
|
|
1944
|
+
open: boolean,
|
|
1945
|
+
toggle: () => void,
|
|
1946
|
+
ids: string[],
|
|
1947
|
+
kind: "event" | "load" | "entity",
|
|
1948
|
+
) =>
|
|
1949
|
+
ids.length > 0 ? (
|
|
1950
|
+
<>
|
|
1951
|
+
<li>
|
|
1952
|
+
<button type="button" className="lexy-dp-list-section" onClick={toggle}>
|
|
1953
|
+
{label}
|
|
1954
|
+
<ChevronRight className={"lexy-dp-chevron lexy-dp-chevron--sm" + (open ? " is-open" : "")} />
|
|
1955
|
+
</button>
|
|
1956
|
+
</li>
|
|
1957
|
+
{open
|
|
1958
|
+
? ids.map((id) => (
|
|
1959
|
+
<li key={kind + ":" + id}>
|
|
1960
|
+
<button
|
|
1961
|
+
type="button"
|
|
1962
|
+
onClick={() => setSelected({ kind, id })}
|
|
1963
|
+
title={id}
|
|
1964
|
+
className={
|
|
1965
|
+
"lexy-dp-listitem" +
|
|
1966
|
+
(selected?.kind === kind && selected.id === id ? " is-active" : "")
|
|
1967
|
+
}
|
|
1968
|
+
>
|
|
1969
|
+
{id}
|
|
1970
|
+
</button>
|
|
1971
|
+
</li>
|
|
1972
|
+
))
|
|
1973
|
+
: null}
|
|
1974
|
+
</>
|
|
1975
|
+
) : null;
|
|
1648
1976
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1977
|
+
return (
|
|
1978
|
+
<div className="lexy-dp-split">
|
|
1979
|
+
<ul className="lexy-dp-split__list">
|
|
1980
|
+
{renderGroup("Eventos", eventsOpen, () => setEventsOpen((v) => !v), events.map((e) => e.technicalId), "event")}
|
|
1981
|
+
{renderGroup("Cargas de datos", loadsOpen, () => setLoadsOpen((v) => !v), loads.map((l) => l.technicalId), "load")}
|
|
1982
|
+
{renderGroup("Entidades", entitiesOpen, () => setEntitiesOpen((v) => !v), entities.map(([id]) => id), "entity")}
|
|
1983
|
+
</ul>
|
|
1984
|
+
<div className="lexy-dp-split__detail">
|
|
1985
|
+
{selected?.kind === "event" && selectedEvent ? (
|
|
1986
|
+
<EventDetail event={selectedEvent} />
|
|
1987
|
+
) : selected?.kind === "load" && selectedLoad ? (
|
|
1988
|
+
<LoadDetail load={selectedLoad} />
|
|
1989
|
+
) : selected?.kind === "entity" ? (
|
|
1990
|
+
<EntityDetail entityId={selected.id} />
|
|
1991
|
+
) : null}
|
|
1992
|
+
</div>
|
|
1993
|
+
</div>
|
|
1994
|
+
);
|
|
1651
1995
|
}
|
|
1652
1996
|
|
|
1653
|
-
|
|
1654
|
-
scenarios: PrototypeMockScenarios,
|
|
1655
|
-
): PrototypeMockScenarios {
|
|
1656
|
-
return prototypeMockScenariosSchema.parse(scenarios);
|
|
1657
|
-
}
|
|
1658
|
-
`,Ft=String.raw`import { defineMockGenerationProfile } from "./simulator-contract-schema";
|
|
1997
|
+
type DesignerTab = "activity" | "events";
|
|
1659
1998
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
locale: {
|
|
1669
|
-
language: "es",
|
|
1670
|
-
locale: "es-CL",
|
|
1671
|
-
country: "CL",
|
|
1672
|
-
timezone: "America/Santiago",
|
|
1673
|
-
currency: "CLP",
|
|
1674
|
-
dateStorage: "iso8601",
|
|
1675
|
-
dateDisplay: "dd-MM-yyyy",
|
|
1676
|
-
timeDisplay: "24h",
|
|
1677
|
-
},
|
|
1678
|
-
safety: {
|
|
1679
|
-
syntheticOnly: true,
|
|
1680
|
-
allowProductionImports: false,
|
|
1681
|
-
externalUse: false,
|
|
1682
|
-
emailDomain: "example.com",
|
|
1683
|
-
},
|
|
1684
|
-
entityKeys: {},
|
|
1685
|
-
relationBindings: {},
|
|
1686
|
-
fieldHints: {},
|
|
1687
|
-
});
|
|
1688
|
-
`,Nt=String.raw`import { definePrototypeMockScenarios } from "./simulator-contract-schema";
|
|
1999
|
+
export function DesignerPanelPage() {
|
|
2000
|
+
const [tab, setTab] = useState<DesignerTab>("activity");
|
|
2001
|
+
const [launcherVisible, setLauncherVisible] = useState(() => {
|
|
2002
|
+
if (typeof window !== "undefined") {
|
|
2003
|
+
return localStorage.getItem("lexy-dp-launcher-visible") !== "false";
|
|
2004
|
+
}
|
|
2005
|
+
return true;
|
|
2006
|
+
});
|
|
1689
2007
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
defaultScenarioId: "base",
|
|
1700
|
-
scenarios: {
|
|
1701
|
-
base: {
|
|
1702
|
-
id: "base",
|
|
1703
|
-
name: "Recorrido principal",
|
|
1704
|
-
description: "Escenario inicial pendiente de completar durante el kickoff.",
|
|
1705
|
-
entities: {},
|
|
1706
|
-
},
|
|
1707
|
-
empty: {
|
|
1708
|
-
id: "empty",
|
|
1709
|
-
name: "Estado vacío",
|
|
1710
|
-
description: "Escenario sin resultados.",
|
|
1711
|
-
entities: {},
|
|
1712
|
-
},
|
|
1713
|
-
edgeCases: {
|
|
1714
|
-
id: "edgeCases",
|
|
1715
|
-
name: "Casos límite",
|
|
1716
|
-
description: "Escenario pendiente de completar según la experiencia.",
|
|
1717
|
-
entities: {},
|
|
1718
|
-
},
|
|
1719
|
-
},
|
|
1720
|
-
});
|
|
1721
|
-
`,Ht=String.raw`import type { ProjectionResolverMap } from "./prototype-simulator";
|
|
2008
|
+
useEffect(() => {
|
|
2009
|
+
const handleStorage = (e: StorageEvent) => {
|
|
2010
|
+
if (e.key === "lexy-dp-launcher-visible") {
|
|
2011
|
+
setLauncherVisible(e.newValue !== "false");
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
window.addEventListener("storage", handleStorage);
|
|
2015
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
2016
|
+
}, []);
|
|
1722
2017
|
|
|
1723
|
-
|
|
1724
|
-
|
|
2018
|
+
return (
|
|
2019
|
+
<div className="lexy-dp lexy-dp-page">
|
|
2020
|
+
<PanelStyles />
|
|
2021
|
+
<div className="lexy-dp-tablist" role="tablist">
|
|
2022
|
+
<button
|
|
2023
|
+
type="button"
|
|
2024
|
+
role="tab"
|
|
2025
|
+
aria-selected={tab === "activity"}
|
|
2026
|
+
className={"lexy-dp-tab" + (tab === "activity" ? " is-active" : "")}
|
|
2027
|
+
onClick={() => setTab("activity")}
|
|
2028
|
+
>
|
|
2029
|
+
<Activity />
|
|
2030
|
+
Actividad
|
|
2031
|
+
</button>
|
|
2032
|
+
<button
|
|
2033
|
+
type="button"
|
|
2034
|
+
role="tab"
|
|
2035
|
+
aria-selected={tab === "events"}
|
|
2036
|
+
className={"lexy-dp-tab" + (tab === "events" ? " is-active" : "")}
|
|
2037
|
+
onClick={() => setTab("events")}
|
|
2038
|
+
>
|
|
2039
|
+
<Zap />
|
|
2040
|
+
Eventos y Datos
|
|
2041
|
+
</button>
|
|
2042
|
+
|
|
2043
|
+
<div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: "8px" }}>
|
|
2044
|
+
<span className="lexy-dp-muted" style={{ fontSize: "12px" }}>Burbuja de Asistencia:</span>
|
|
2045
|
+
<button
|
|
2046
|
+
type="button"
|
|
2047
|
+
className="lexy-dp-btn lexy-dp-btn--outline lexy-dp-btn--sm"
|
|
2048
|
+
style={{ display: "inline-flex", alignItems: "center", gap: "6px", cursor: "pointer" }}
|
|
2049
|
+
onClick={() => {
|
|
2050
|
+
const nextState = !launcherVisible;
|
|
2051
|
+
setLauncherVisible(nextState);
|
|
2052
|
+
localStorage.setItem("lexy-dp-launcher-visible", String(nextState));
|
|
2053
|
+
window.dispatchEvent(new Event("storage"));
|
|
2054
|
+
}}
|
|
2055
|
+
>
|
|
2056
|
+
{launcherVisible ? <EyeOff style={{ width: "12px", height: "12px" }} /> : <Eye style={{ width: "12px", height: "12px" }} />}
|
|
2057
|
+
{launcherVisible ? "Ocultar" : "Mostrar"}
|
|
2058
|
+
</button>
|
|
2059
|
+
</div>
|
|
2060
|
+
</div>
|
|
2061
|
+
<div className="lexy-dp-body">
|
|
2062
|
+
{tab === "activity" ? (
|
|
2063
|
+
<div className="lexy-dp-panel">
|
|
2064
|
+
<ActivityTab />
|
|
2065
|
+
</div>
|
|
2066
|
+
) : null}
|
|
2067
|
+
{tab === "events" ? (
|
|
2068
|
+
<div className="lexy-dp-panel">
|
|
2069
|
+
<EventsTab />
|
|
2070
|
+
</div>
|
|
2071
|
+
) : null}
|
|
2072
|
+
</div>
|
|
2073
|
+
</div>
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
1725
2076
|
|
|
1726
|
-
export
|
|
1727
|
-
|
|
2077
|
+
export function PrototypeDesignerPanel() {
|
|
2078
|
+
const [open, setOpen] = useState(false);
|
|
2079
|
+
const [visible, setVisible] = useState(() => {
|
|
2080
|
+
if (typeof window !== "undefined") {
|
|
2081
|
+
return localStorage.getItem("lexy-dp-launcher-visible") !== "false";
|
|
2082
|
+
}
|
|
2083
|
+
return true;
|
|
2084
|
+
});
|
|
1728
2085
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
}
|
|
2086
|
+
useEffect(() => {
|
|
2087
|
+
const handleStorage = (e: StorageEvent) => {
|
|
2088
|
+
if (e.key === "lexy-dp-launcher-visible") {
|
|
2089
|
+
setVisible(e.newValue !== "false");
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
window.addEventListener("storage", handleStorage);
|
|
2093
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
2094
|
+
}, []);
|
|
1735
2095
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2096
|
+
if (!visible) return null;
|
|
2097
|
+
|
|
2098
|
+
return (
|
|
2099
|
+
<div className="lexy-dp">
|
|
2100
|
+
<PanelStyles />
|
|
2101
|
+
{open ? (
|
|
2102
|
+
<div role="dialog" aria-label="Actividad del prototipo" className="lexy-dp-sheet">
|
|
2103
|
+
<div className="lexy-dp-sheet__header">
|
|
2104
|
+
<p className="lexy-dp-sheet__title">Panel de Actividad</p>
|
|
2105
|
+
<div className="lexy-dp-sheet__actions">
|
|
2106
|
+
<button
|
|
2107
|
+
type="button"
|
|
2108
|
+
aria-label="Ocultar burbuja de asistencia"
|
|
2109
|
+
title="Ocultar burbuja de asistencia"
|
|
2110
|
+
className="lexy-dp-iconbtn"
|
|
2111
|
+
onClick={() => {
|
|
2112
|
+
if (confirm("¿Estás seguro de que quieres ocultar la burbuja de asistencia? Podrás volver a habilitarla desde la página del panel (/designer-panel).")) {
|
|
2113
|
+
localStorage.setItem("lexy-dp-launcher-visible", "false");
|
|
2114
|
+
setVisible(false);
|
|
2115
|
+
window.dispatchEvent(new Event("storage"));
|
|
2116
|
+
}
|
|
2117
|
+
}}
|
|
2118
|
+
>
|
|
2119
|
+
<EyeOff />
|
|
2120
|
+
</button>
|
|
2121
|
+
<button
|
|
2122
|
+
type="button"
|
|
2123
|
+
aria-label="Abrir panel completo"
|
|
2124
|
+
title="Abrir panel completo"
|
|
2125
|
+
className="lexy-dp-iconbtn"
|
|
2126
|
+
onClick={() => window.open(DESIGNER_PANEL_ROUTE, "_blank", "noopener,noreferrer")}
|
|
2127
|
+
>
|
|
2128
|
+
<Maximize2 />
|
|
2129
|
+
</button>
|
|
2130
|
+
<button
|
|
2131
|
+
type="button"
|
|
2132
|
+
aria-label="Cerrar"
|
|
2133
|
+
className="lexy-dp-iconbtn"
|
|
2134
|
+
onClick={() => setOpen(false)}
|
|
2135
|
+
>
|
|
2136
|
+
<X />
|
|
2137
|
+
</button>
|
|
2138
|
+
</div>
|
|
2139
|
+
</div>
|
|
2140
|
+
<div className="lexy-dp-sheet__body">
|
|
2141
|
+
<ActivityLog />
|
|
2142
|
+
</div>
|
|
2143
|
+
</div>
|
|
2144
|
+
) : null}
|
|
2145
|
+
|
|
2146
|
+
<button
|
|
2147
|
+
type="button"
|
|
2148
|
+
aria-label="Designer Assist"
|
|
2149
|
+
aria-expanded={open}
|
|
2150
|
+
onClick={() => setOpen((value) => !value)}
|
|
2151
|
+
className={"lexy-dp-launcher" + (open ? " is-open" : "")}
|
|
2152
|
+
>
|
|
2153
|
+
<Settings className="lexy-dp-launcher__icon" />
|
|
2154
|
+
<span className="lexy-dp-launcher__label">Designer Assist</span>
|
|
2155
|
+
</button>
|
|
2156
|
+
</div>
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
`,$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",It="src/prototype/ports/types.ts",Ct="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",_t=String.raw`import { fixtures } from "./fixtures";
|
|
2160
|
+
|
|
2161
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2162
|
+
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
2163
|
+
export type MockEntityRecord = Record<string, JsonValue>;
|
|
1743
2164
|
|
|
1744
|
-
export type
|
|
1745
|
-
simulatorVersion: "1";
|
|
2165
|
+
export type PrototypeMockState = {
|
|
1746
2166
|
datasetVersion: number;
|
|
1747
|
-
activeScenarioId: string;
|
|
1748
|
-
revision: number;
|
|
1749
2167
|
entities: Record<string, MockEntityRecord[]>;
|
|
1750
2168
|
entitySequences: Record<string, number>;
|
|
1751
|
-
publicationHistory: PublishedEventHistoryItem[];
|
|
1752
2169
|
};
|
|
1753
2170
|
|
|
1754
|
-
export type
|
|
2171
|
+
export type MockStorage = {
|
|
1755
2172
|
read(): string | null;
|
|
1756
2173
|
write(value: string): void;
|
|
1757
2174
|
remove(): void;
|
|
1758
2175
|
};
|
|
1759
2176
|
|
|
1760
2177
|
export type PrototypeMockStore = {
|
|
1761
|
-
getSnapshot():
|
|
2178
|
+
getSnapshot(): PrototypeMockState;
|
|
1762
2179
|
subscribe(listener: () => void): () => void;
|
|
1763
|
-
transact(operation: (draft:
|
|
1764
|
-
switchScenario(scenarioId: string): void;
|
|
1765
|
-
resetScenario(): void;
|
|
2180
|
+
transact(operation: (draft: PrototypeMockState) => void): PrototypeMockState;
|
|
1766
2181
|
clearPersistence(): void;
|
|
1767
2182
|
};
|
|
1768
2183
|
|
|
1769
|
-
|
|
1770
|
-
projectName: string;
|
|
1771
|
-
scenarios: PrototypeMockScenarios;
|
|
1772
|
-
storage?: SimulatorStorage;
|
|
1773
|
-
onHydrationWarning?: (message: string) => void;
|
|
1774
|
-
};
|
|
2184
|
+
const STORAGE_KEY = "lexy:prototype:mock-store:v1";
|
|
1775
2185
|
|
|
1776
2186
|
const clone = <T>(value: T): T => {
|
|
1777
2187
|
if (typeof structuredClone === "function") return structuredClone(value);
|
|
@@ -1786,43 +2196,26 @@ const buildSequences = (entities: Record<string, MockEntityRecord[]>) => {
|
|
|
1786
2196
|
return sequences;
|
|
1787
2197
|
};
|
|
1788
2198
|
|
|
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);
|
|
2199
|
+
const stateFromFixtures = (): PrototypeMockState => {
|
|
2200
|
+
const entities = clone(fixtures.entities);
|
|
1796
2201
|
return {
|
|
1797
|
-
|
|
1798
|
-
datasetVersion: scenarios.datasetVersion,
|
|
1799
|
-
activeScenarioId: scenarioId,
|
|
1800
|
-
revision: 0,
|
|
2202
|
+
datasetVersion: fixtures.datasetVersion,
|
|
1801
2203
|
entities,
|
|
1802
2204
|
entitySequences: buildSequences(entities),
|
|
1803
|
-
publicationHistory: [],
|
|
1804
2205
|
};
|
|
1805
2206
|
};
|
|
1806
2207
|
|
|
1807
|
-
const
|
|
1808
|
-
value: unknown,
|
|
1809
|
-
scenarios: PrototypeMockScenarios,
|
|
1810
|
-
): value is PrototypeSimulatorState => {
|
|
2208
|
+
const isCompatible = (value: unknown): value is PrototypeMockState => {
|
|
1811
2209
|
if (!value || typeof value !== "object") return false;
|
|
1812
|
-
const state = value as Partial<
|
|
2210
|
+
const state = value as Partial<PrototypeMockState>;
|
|
1813
2211
|
return (
|
|
1814
|
-
state.
|
|
1815
|
-
state.datasetVersion === scenarios.datasetVersion &&
|
|
1816
|
-
typeof state.activeScenarioId === "string" &&
|
|
1817
|
-
Boolean(scenarios.scenarios[state.activeScenarioId]) &&
|
|
1818
|
-
typeof state.revision === "number" &&
|
|
2212
|
+
state.datasetVersion === fixtures.datasetVersion &&
|
|
1819
2213
|
Boolean(state.entities && typeof state.entities === "object") &&
|
|
1820
|
-
Boolean(state.entitySequences && typeof state.entitySequences === "object")
|
|
1821
|
-
Array.isArray(state.publicationHistory)
|
|
2214
|
+
Boolean(state.entitySequences && typeof state.entitySequences === "object")
|
|
1822
2215
|
);
|
|
1823
2216
|
};
|
|
1824
2217
|
|
|
1825
|
-
|
|
2218
|
+
const createMemoryStorage = (): MockStorage => {
|
|
1826
2219
|
let current: string | null = null;
|
|
1827
2220
|
return {
|
|
1828
2221
|
read: () => current,
|
|
@@ -1835,8 +2228,8 @@ export const createMemorySimulatorStorage = (): SimulatorStorage => {
|
|
|
1835
2228
|
};
|
|
1836
2229
|
};
|
|
1837
2230
|
|
|
1838
|
-
|
|
1839
|
-
const memory =
|
|
2231
|
+
const createLocalStorageStorage = (key: string): MockStorage => {
|
|
2232
|
+
const memory = createMemoryStorage();
|
|
1840
2233
|
const getLocalStorage = () => {
|
|
1841
2234
|
try {
|
|
1842
2235
|
return globalThis.localStorage;
|
|
@@ -1844,7 +2237,6 @@ export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorag
|
|
|
1844
2237
|
return undefined;
|
|
1845
2238
|
}
|
|
1846
2239
|
};
|
|
1847
|
-
|
|
1848
2240
|
return {
|
|
1849
2241
|
read: () => {
|
|
1850
2242
|
try {
|
|
@@ -1872,41 +2264,26 @@ export const createLocalStorageSimulatorStorage = (key: string): SimulatorStorag
|
|
|
1872
2264
|
};
|
|
1873
2265
|
};
|
|
1874
2266
|
|
|
1875
|
-
export const simulatorStorageKey = (projectName: string) =>
|
|
1876
|
-
"lexy:prototype:" + projectName + ":simulator:v1";
|
|
1877
|
-
|
|
1878
2267
|
export const createPrototypeMockStore = (
|
|
1879
|
-
|
|
2268
|
+
storage: MockStorage = createLocalStorageStorage(STORAGE_KEY),
|
|
1880
2269
|
): PrototypeMockStore => {
|
|
1881
|
-
const storage =
|
|
1882
|
-
options.storage ?? createLocalStorageSimulatorStorage(simulatorStorageKey(options.projectName));
|
|
1883
2270
|
const listeners = new Set<() => void>();
|
|
1884
2271
|
|
|
1885
|
-
const hydrate = () => {
|
|
2272
|
+
const hydrate = (): PrototypeMockState => {
|
|
1886
2273
|
const persisted = storage.read();
|
|
1887
|
-
if (!persisted)
|
|
1888
|
-
return createStateFromScenario(options.scenarios, options.scenarios.defaultScenarioId);
|
|
1889
|
-
|
|
2274
|
+
if (!persisted) return stateFromFixtures();
|
|
1890
2275
|
try {
|
|
1891
2276
|
const parsed = JSON.parse(persisted) as unknown;
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
"Estado persistido incompatible; se reinicia el simulador al escenario default.",
|
|
1895
|
-
);
|
|
2277
|
+
// Al cambiar fixtures se sube datasetVersion: el estado viejo se descarta.
|
|
2278
|
+
if (isCompatible(parsed)) return parsed;
|
|
1896
2279
|
} catch {
|
|
1897
|
-
|
|
1898
|
-
"Estado persistido corrupto; se reinicia el simulador al escenario default.",
|
|
1899
|
-
);
|
|
2280
|
+
// estado corrupto: reinicia desde fixtures
|
|
1900
2281
|
}
|
|
1901
|
-
return
|
|
2282
|
+
return stateFromFixtures();
|
|
1902
2283
|
};
|
|
1903
2284
|
|
|
1904
2285
|
let snapshot = hydrate();
|
|
1905
2286
|
|
|
1906
|
-
const persist = () => {
|
|
1907
|
-
storage.write(JSON.stringify(snapshot));
|
|
1908
|
-
};
|
|
1909
|
-
|
|
1910
2287
|
const notify = () => {
|
|
1911
2288
|
for (const listener of listeners) listener();
|
|
1912
2289
|
};
|
|
@@ -1922,497 +2299,321 @@ export const createPrototypeMockStore = (
|
|
|
1922
2299
|
transact: (operation) => {
|
|
1923
2300
|
const draft = clone(snapshot);
|
|
1924
2301
|
operation(draft);
|
|
1925
|
-
draft.revision = snapshot.revision + 1;
|
|
1926
2302
|
snapshot = draft;
|
|
1927
|
-
|
|
2303
|
+
storage.write(JSON.stringify(snapshot));
|
|
1928
2304
|
notify();
|
|
1929
2305
|
return snapshot;
|
|
1930
2306
|
},
|
|
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
|
-
},
|
|
2307
|
+
clearPersistence: () => storage.remove(),
|
|
1944
2308
|
};
|
|
1945
2309
|
};
|
|
1946
2310
|
|
|
2311
|
+
/** Instancia compartida que ven el adapter de lectura y el de escritura. */
|
|
2312
|
+
export const mockStore = createPrototypeMockStore();
|
|
2313
|
+
|
|
1947
2314
|
export const findRecordById = (
|
|
1948
2315
|
records: readonly MockEntityRecord[],
|
|
1949
2316
|
fieldId: string,
|
|
1950
2317
|
id: JsonPrimitive,
|
|
1951
2318
|
) => 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
|
-
});
|
|
2319
|
+
`,Ot=String.raw`import type { MockEntityRecord } from "./mock-store";
|
|
2016
2320
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2321
|
+
export type Fixtures = {
|
|
2322
|
+
datasetVersion: number;
|
|
2323
|
+
entities: Record<string, MockEntityRecord[]>;
|
|
2020
2324
|
};
|
|
2021
2325
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2326
|
+
/**
|
|
2327
|
+
* Datos sintéticos, deterministas y revisables en PR. Sin Math.random(),
|
|
2328
|
+
* Date.now() ni new Date(). Usa contexto chileno (es-CL, RUT con K mayúscula,
|
|
2329
|
+
* teléfonos +56, fechas ISO, CLP entero, correos example.com).
|
|
2330
|
+
*/
|
|
2331
|
+
export const fixtures: Fixtures = {
|
|
2332
|
+
datasetVersion: 1,
|
|
2333
|
+
entities: {},
|
|
2025
2334
|
};
|
|
2335
|
+
`,At=String.raw`import { z } from "zod";
|
|
2026
2336
|
|
|
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
|
-
});
|
|
2337
|
+
/** Metadata de una carga de datos (GET): describe qué lee y qué la detona. */
|
|
2338
|
+
export const readMetaSchema = z
|
|
2339
|
+
.object({
|
|
2340
|
+
description: z.string().optional(),
|
|
2341
|
+
trigger: z.string().optional(),
|
|
2342
|
+
reads: z
|
|
2343
|
+
.object({
|
|
2344
|
+
entities: z.array(z.string()).optional(),
|
|
2345
|
+
projections: z.array(z.string()).optional(),
|
|
2346
|
+
})
|
|
2347
|
+
.strict()
|
|
2348
|
+
.optional(),
|
|
2349
|
+
})
|
|
2350
|
+
.strict();
|
|
2046
2351
|
|
|
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
|
-
};
|
|
2352
|
+
export type ReadMeta = z.infer<typeof readMetaSchema>;
|
|
2063
2353
|
|
|
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
|
-
},
|
|
2354
|
+
/** Metadata de un evento publicado (POST): describe qué escribe y qué lo detona. */
|
|
2355
|
+
export const writeMetaSchema = z
|
|
2356
|
+
.object({
|
|
2357
|
+
description: z.string().optional(),
|
|
2358
|
+
trigger: z.string().optional(),
|
|
2359
|
+
writes: z
|
|
2360
|
+
.object({
|
|
2361
|
+
entities: z.array(z.string()).optional(),
|
|
2362
|
+
fields: z.array(z.string()).optional(),
|
|
2363
|
+
})
|
|
2364
|
+
.strict()
|
|
2365
|
+
.optional(),
|
|
2366
|
+
})
|
|
2367
|
+
.strict();
|
|
2096
2368
|
|
|
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
|
-
});
|
|
2369
|
+
export type WriteMeta = z.infer<typeof writeMetaSchema>;
|
|
2132
2370
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2371
|
+
/** Lo que devuelve publish(): el id del hecho creado y el código HTTP. */
|
|
2372
|
+
export const eventReceiptSchema = z.object({
|
|
2373
|
+
eventId: z.string(),
|
|
2374
|
+
status: z.number().int().min(100).max(599),
|
|
2135
2375
|
});
|
|
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
2376
|
|
|
2164
|
-
export type
|
|
2165
|
-
params: unknown;
|
|
2166
|
-
signal?: AbortSignal;
|
|
2167
|
-
store: PrototypeMockStore;
|
|
2168
|
-
projections: {
|
|
2169
|
-
resolve(projectionId: string, params?: unknown): unknown;
|
|
2170
|
-
};
|
|
2171
|
-
};
|
|
2377
|
+
export type EventReceipt = z.infer<typeof eventReceiptSchema>;
|
|
2172
2378
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2379
|
+
/** Lectura (GET): resuelve y devuelve datos; un error se lanza como excepción. */
|
|
2380
|
+
export interface LexyReadPort {
|
|
2381
|
+
load<T = unknown>(loadId: string, params?: unknown, meta?: ReadMeta): Promise<T>;
|
|
2382
|
+
}
|
|
2176
2383
|
|
|
2177
|
-
|
|
2384
|
+
/** Escritura (POST): registra un hecho y devuelve su receipt. */
|
|
2385
|
+
export interface LexyWritePort {
|
|
2386
|
+
publish(eventId: string, payload: unknown, meta?: WriteMeta): Promise<EventReceipt>;
|
|
2387
|
+
}
|
|
2388
|
+
`,Lt=String.raw`import type { EventReceipt, ReadMeta, WriteMeta } from "./types";
|
|
2178
2389
|
|
|
2179
|
-
export type
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2390
|
+
export type CatalogEvent = {
|
|
2391
|
+
kind: "event";
|
|
2392
|
+
technicalId: string;
|
|
2393
|
+
description: string;
|
|
2394
|
+
trigger: string;
|
|
2395
|
+
writes: { entities: string[]; fields: string[] };
|
|
2396
|
+
lastPayload: unknown;
|
|
2397
|
+
lastReceipt: EventReceipt | null;
|
|
2184
2398
|
};
|
|
2185
2399
|
|
|
2186
|
-
export type
|
|
2187
|
-
|
|
2400
|
+
export type CatalogLoad = {
|
|
2401
|
+
kind: "load";
|
|
2402
|
+
technicalId: string;
|
|
2403
|
+
description: string;
|
|
2404
|
+
trigger: string;
|
|
2405
|
+
reads: { entities: string[]; projections: string[] };
|
|
2406
|
+
lastParams: unknown;
|
|
2188
2407
|
};
|
|
2189
2408
|
|
|
2190
|
-
export type
|
|
2409
|
+
export type CatalogEntry = CatalogEvent | CatalogLoad;
|
|
2191
2410
|
|
|
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
|
-
};
|
|
2411
|
+
type CatalogMessage =
|
|
2412
|
+
| { type: "entry"; entry: CatalogEntry }
|
|
2413
|
+
| { type: "syncRequest" }
|
|
2414
|
+
| { type: "sync"; entries: CatalogEntry[] };
|
|
2203
2415
|
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
eventHandlers: MockEventHandlerMap;
|
|
2210
|
-
};
|
|
2416
|
+
const createCatalog = () => {
|
|
2417
|
+
const store = new Map<string, CatalogEntry>();
|
|
2418
|
+
const listeners = new Set<() => void>();
|
|
2419
|
+
let snapshot: CatalogEntry[] = [];
|
|
2420
|
+
let synced = false;
|
|
2211
2421
|
|
|
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
|
-
});
|
|
2422
|
+
const channel =
|
|
2423
|
+
typeof BroadcastChannel !== "undefined" ? new BroadcastChannel("lexy-ports-catalog") : null;
|
|
2222
2424
|
|
|
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
|
-
});
|
|
2425
|
+
const key = (entry: CatalogEntry) => entry.kind + ":" + entry.technicalId;
|
|
2231
2426
|
|
|
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(),
|
|
2427
|
+
const notify = () => {
|
|
2428
|
+
snapshot = Array.from(store.values());
|
|
2429
|
+
for (const l of listeners) l();
|
|
2245
2430
|
};
|
|
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
2431
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2432
|
+
const apply = (entry: CatalogEntry) => {
|
|
2433
|
+
store.set(key(entry), entry);
|
|
2434
|
+
notify();
|
|
2435
|
+
};
|
|
2291
2436
|
|
|
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
|
-
});
|
|
2437
|
+
if (channel) {
|
|
2438
|
+
channel.onmessage = (ev: MessageEvent<CatalogMessage>) => {
|
|
2439
|
+
const msg = ev.data;
|
|
2440
|
+
if (msg.type === "entry") apply(msg.entry);
|
|
2441
|
+
else if (msg.type === "syncRequest")
|
|
2442
|
+
channel.postMessage({ type: "sync", entries: Array.from(store.values()) });
|
|
2443
|
+
else if (msg.type === "sync" && !synced && store.size === 0) {
|
|
2444
|
+
synced = true;
|
|
2445
|
+
for (const entry of msg.entries) store.set(key(entry), entry);
|
|
2446
|
+
notify();
|
|
2314
2447
|
}
|
|
2315
|
-
}
|
|
2448
|
+
};
|
|
2449
|
+
channel.postMessage({ type: "syncRequest" });
|
|
2316
2450
|
}
|
|
2317
2451
|
|
|
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
|
-
);
|
|
2452
|
+
const publishEntry = (entry: CatalogEntry) => {
|
|
2453
|
+
store.set(key(entry), entry);
|
|
2454
|
+
notify();
|
|
2455
|
+
channel?.postMessage({ type: "entry", entry });
|
|
2456
|
+
};
|
|
2336
2457
|
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2458
|
+
return {
|
|
2459
|
+
recordEvent(technicalId: string, payload: unknown, meta: WriteMeta, receipt: EventReceipt) {
|
|
2460
|
+
const existing = store.get("event:" + technicalId);
|
|
2461
|
+
const prev = existing?.kind === "event" ? existing : undefined;
|
|
2462
|
+
publishEntry({
|
|
2463
|
+
kind: "event",
|
|
2464
|
+
technicalId,
|
|
2465
|
+
description: meta.description ?? prev?.description ?? "",
|
|
2466
|
+
trigger: meta.trigger ?? prev?.trigger ?? "",
|
|
2467
|
+
writes: {
|
|
2468
|
+
entities: meta.writes?.entities ?? prev?.writes.entities ?? [],
|
|
2469
|
+
fields: meta.writes?.fields ?? prev?.writes.fields ?? [],
|
|
2470
|
+
},
|
|
2471
|
+
lastPayload: payload,
|
|
2472
|
+
lastReceipt: receipt,
|
|
2473
|
+
});
|
|
2474
|
+
},
|
|
2475
|
+
recordLoad(technicalId: string, params: unknown, meta: ReadMeta) {
|
|
2476
|
+
const existing = store.get("load:" + technicalId);
|
|
2477
|
+
const prev = existing?.kind === "load" ? existing : undefined;
|
|
2478
|
+
publishEntry({
|
|
2479
|
+
kind: "load",
|
|
2480
|
+
technicalId,
|
|
2481
|
+
description: meta.description ?? prev?.description ?? "",
|
|
2482
|
+
trigger: meta.trigger ?? prev?.trigger ?? "",
|
|
2483
|
+
reads: {
|
|
2484
|
+
entities: meta.reads?.entities ?? prev?.reads.entities ?? [],
|
|
2485
|
+
projections: meta.reads?.projections ?? prev?.reads.projections ?? [],
|
|
2486
|
+
},
|
|
2487
|
+
lastParams: params,
|
|
2488
|
+
});
|
|
2489
|
+
},
|
|
2490
|
+
subscribe: (l: () => void) => {
|
|
2491
|
+
listeners.add(l);
|
|
2492
|
+
return () => listeners.delete(l);
|
|
2493
|
+
},
|
|
2494
|
+
getSnapshot: () => snapshot,
|
|
2495
|
+
};
|
|
2496
|
+
};
|
|
2345
2497
|
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2498
|
+
export const catalog = createCatalog();
|
|
2499
|
+
`,Dt=String.raw`import { designerActivity } from "../designer-panel/designer-activity";
|
|
2500
|
+
import { type MockEntityRecord, mockStore } from "../mock-store/mock-store";
|
|
2501
|
+
import { catalog } from "./catalog";
|
|
2502
|
+
import { type LexyReadPort, type ReadMeta, readMetaSchema } from "./types";
|
|
2503
|
+
|
|
2504
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
2505
|
+
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2506
|
+
|
|
2507
|
+
export const mockRead: LexyReadPort = {
|
|
2508
|
+
async load<T = unknown>(loadId: string, params?: unknown, rawMeta: ReadMeta = {}): Promise<T> {
|
|
2509
|
+
const parsed = readMetaSchema.safeParse(rawMeta);
|
|
2510
|
+
if (!parsed.success) {
|
|
2511
|
+
console.warn(
|
|
2512
|
+
'[lexy] Metadata de carga inválida para "' + loadId + '":',
|
|
2513
|
+
parsed.error.flatten().fieldErrors,
|
|
2514
|
+
);
|
|
2358
2515
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2516
|
+
const meta: ReadMeta = parsed.success ? parsed.data : {};
|
|
2517
|
+
catalog.recordLoad(loadId, params, meta);
|
|
2518
|
+
|
|
2519
|
+
const entityId = meta.reads?.entities?.[0];
|
|
2520
|
+
const all = entityId ? (mockStore.getSnapshot().entities[entityId] ?? []) : [];
|
|
2521
|
+
// Filtro genérico: si params es un objeto, exige coincidencia exacta de sus campos.
|
|
2522
|
+
const data: MockEntityRecord[] = isPlainObject(params)
|
|
2523
|
+
? all.filter((record) =>
|
|
2524
|
+
Object.entries(params).every(([field, value]) => record[field] === value),
|
|
2525
|
+
)
|
|
2526
|
+
: all;
|
|
2527
|
+
designerActivity.record({
|
|
2528
|
+
kind: "load",
|
|
2529
|
+
label: meta.description ?? loadId,
|
|
2530
|
+
technicalId: loadId,
|
|
2531
|
+
triggerId: meta.trigger,
|
|
2532
|
+
input: params,
|
|
2533
|
+
});
|
|
2534
|
+
return data as T;
|
|
2535
|
+
},
|
|
2536
|
+
};
|
|
2537
|
+
`,zt=String.raw`import { designerActivity } from "../designer-panel/designer-activity";
|
|
2538
|
+
import { mockStore } from "../mock-store/mock-store";
|
|
2539
|
+
import { catalog } from "./catalog";
|
|
2540
|
+
import { type EventReceipt, type LexyWritePort, type WriteMeta, writeMetaSchema } from "./types";
|
|
2541
|
+
|
|
2542
|
+
let publicationSequence = 0;
|
|
2543
|
+
|
|
2544
|
+
export const mockWrite: LexyWritePort = {
|
|
2545
|
+
async publish(eventId: string, payload: unknown, rawMeta: WriteMeta = {}): Promise<EventReceipt> {
|
|
2546
|
+
const parsed = writeMetaSchema.safeParse(rawMeta);
|
|
2547
|
+
if (!parsed.success) {
|
|
2548
|
+
console.warn(
|
|
2549
|
+
'[lexy] Metadata de evento inválida para "' + eventId + '":',
|
|
2550
|
+
parsed.error.flatten().fieldErrors,
|
|
2551
|
+
);
|
|
2367
2552
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2553
|
+
const meta: WriteMeta = parsed.success ? parsed.data : {};
|
|
2554
|
+
|
|
2555
|
+
const entityId = meta.writes?.entities?.[0];
|
|
2556
|
+
if (entityId) {
|
|
2557
|
+
mockStore.transact((draft) => {
|
|
2558
|
+
const records = (draft.entities[entityId] ??= []);
|
|
2559
|
+
const nextId = draft.entitySequences[entityId] ?? records.length + 1;
|
|
2560
|
+
records.push({ id: String(nextId), ...(payload as Record<string, unknown>) });
|
|
2561
|
+
draft.entitySequences[entityId] = nextId + 1;
|
|
2562
|
+
});
|
|
2375
2563
|
}
|
|
2376
2564
|
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2565
|
+
publicationSequence += 1;
|
|
2566
|
+
const receipt: EventReceipt = {
|
|
2567
|
+
eventId: eventId + "_" + publicationSequence,
|
|
2568
|
+
status: 201,
|
|
2569
|
+
};
|
|
2570
|
+
catalog.recordEvent(eventId, payload, meta, receipt);
|
|
2571
|
+
designerActivity.record({
|
|
2572
|
+
kind: "event",
|
|
2573
|
+
label: meta.description ?? eventId,
|
|
2574
|
+
technicalId: eventId,
|
|
2575
|
+
triggerId: meta.trigger,
|
|
2576
|
+
input: payload,
|
|
2577
|
+
});
|
|
2578
|
+
return receipt;
|
|
2579
|
+
},
|
|
2580
|
+
};
|
|
2581
|
+
`,Mt=String.raw`import type { LexyReadPort, LexyWritePort } from "./types";
|
|
2394
2582
|
|
|
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 })),
|
|
2583
|
+
export const productionRead: LexyReadPort = {
|
|
2584
|
+
async load<T = unknown>(loadId: string): Promise<T> {
|
|
2585
|
+
throw new Error(
|
|
2586
|
+
'[lexy] productionRead.load("' + loadId + '") sin implementar: conéctalo al GET de tu API.',
|
|
2407
2587
|
);
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2588
|
+
},
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2591
|
+
export const productionWrite: LexyWritePort = {
|
|
2592
|
+
async publish(eventId: string) {
|
|
2593
|
+
throw new Error(
|
|
2594
|
+
'[lexy] productionWrite.publish("' + eventId + '") sin implementar: encólalo en el worker.',
|
|
2595
|
+
);
|
|
2596
|
+
},
|
|
2597
|
+
};
|
|
2598
|
+
`,$t=String.raw`import { mockRead } from "./mock-read";
|
|
2599
|
+
import { mockWrite } from "./mock-write";
|
|
2600
|
+
import { productionRead, productionWrite } from "./production";
|
|
2601
|
+
import type { LexyReadPort, LexyWritePort } from "./types";
|
|
2602
|
+
|
|
2603
|
+
const USE_MOCK = import.meta.env.DEV;
|
|
2604
|
+
|
|
2605
|
+
export const read: LexyReadPort = USE_MOCK ? mockRead : productionRead;
|
|
2606
|
+
export const write: LexyWritePort = USE_MOCK ? mockWrite : productionWrite;
|
|
2607
|
+
|
|
2608
|
+
export type {
|
|
2609
|
+
EventReceipt,
|
|
2610
|
+
LexyReadPort,
|
|
2611
|
+
LexyWritePort,
|
|
2612
|
+
ReadMeta,
|
|
2613
|
+
WriteMeta,
|
|
2614
|
+
} from "./types";
|
|
2615
|
+
`,Ft=[[ce,_t],[de,Ot],[It,At],[Ct,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)+`
|
|
2616
|
+
`},Bt=`import path from "node:path";
|
|
2416
2617
|
|
|
2417
2618
|
import babel from "@rolldown/plugin-babel";
|
|
2418
2619
|
import tailwindcss from "@tailwindcss/vite";
|
|
@@ -2440,9 +2641,30 @@ export default defineConfig({
|
|
|
2440
2641
|
terserOptions: {
|
|
2441
2642
|
compress: { drop_console: true, drop_debugger: true },
|
|
2442
2643
|
},
|
|
2644
|
+
// Code splitting por grupos de vendor (auditor\xEDa de desarrollo, jun 2026):
|
|
2645
|
+
// node_modules se separa en bundles tem\xE1ticos para carga diferida y mejor
|
|
2646
|
+
// cach\xE9 entre deploys. Los grupos sin match simplemente no emiten chunk.
|
|
2647
|
+
rolldownOptions: {
|
|
2648
|
+
output: {
|
|
2649
|
+
codeSplitting: {
|
|
2650
|
+
groups: [
|
|
2651
|
+
{ name: "react-core", test: /node_modules[\\\\/](react|react-dom|scheduler)[\\\\/]/ },
|
|
2652
|
+
{ name: "router", test: /node_modules[\\\\/]react-router[\\\\/]/ },
|
|
2653
|
+
{ name: "query", test: /node_modules[\\\\/]@tanstack[\\\\/]/ },
|
|
2654
|
+
{ name: "state", test: /node_modules[\\\\/]jotai[\\\\/]/ },
|
|
2655
|
+
{ name: "utils", test: /node_modules[\\\\/](clsx|tailwind-merge|class-variance-authority|zod)[\\\\/]/ },
|
|
2656
|
+
{ name: "ui", test: /node_modules[\\\\/](tailwind-animations|@tailwindcss)[\\\\/]/ },
|
|
2657
|
+
{ name: "radix", test: /node_modules[\\\\/]@radix-ui[\\\\/]/ },
|
|
2658
|
+
{ name: "icons", test: /node_modules[\\\\/]lucide-react[\\\\/]/ },
|
|
2659
|
+
{ name: "vendor", test: /node_modules[\\\\/]/ },
|
|
2660
|
+
],
|
|
2661
|
+
minSize: 20000,
|
|
2662
|
+
},
|
|
2663
|
+
},
|
|
2664
|
+
},
|
|
2443
2665
|
},
|
|
2444
2666
|
});
|
|
2445
|
-
`,
|
|
2667
|
+
`,Jt=`import js from "@eslint/js";
|
|
2446
2668
|
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
2447
2669
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
2448
2670
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
@@ -2471,14 +2693,14 @@ export default tseslint.config(
|
|
|
2471
2693
|
},
|
|
2472
2694
|
},
|
|
2473
2695
|
);
|
|
2474
|
-
`,
|
|
2696
|
+
`,Wt=`{
|
|
2475
2697
|
"printWidth": 100
|
|
2476
2698
|
}
|
|
2477
|
-
`,
|
|
2699
|
+
`,Gt=`pnpm-lock.yaml
|
|
2478
2700
|
src/lexy-theme.css
|
|
2479
2701
|
public/fonts/
|
|
2480
2702
|
dist/
|
|
2481
|
-
`,
|
|
2703
|
+
`,Yt=`name: CI
|
|
2482
2704
|
|
|
2483
2705
|
on:
|
|
2484
2706
|
push:
|
|
@@ -2499,11 +2721,9 @@ jobs:
|
|
|
2499
2721
|
- run: pnpm lint
|
|
2500
2722
|
- run: pnpm exec prettier --check .
|
|
2501
2723
|
- run: pnpm check:data-contract
|
|
2502
|
-
- run: pnpm check:event-engine
|
|
2503
|
-
- run: pnpm check:simulator
|
|
2504
2724
|
- run: pnpm build:app
|
|
2505
2725
|
- run: pnpm audit --audit-level high
|
|
2506
|
-
`,
|
|
2726
|
+
`,Qt=`{
|
|
2507
2727
|
"files": [],
|
|
2508
2728
|
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
|
|
2509
2729
|
"compilerOptions": {
|
|
@@ -2511,7 +2731,7 @@ jobs:
|
|
|
2511
2731
|
"paths": { "@/*": ["./src/*"] }
|
|
2512
2732
|
}
|
|
2513
2733
|
}
|
|
2514
|
-
`,
|
|
2734
|
+
`,Xt=`{
|
|
2515
2735
|
"compilerOptions": {
|
|
2516
2736
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
2517
2737
|
"target": "ES2022",
|
|
@@ -2535,7 +2755,7 @@ jobs:
|
|
|
2535
2755
|
},
|
|
2536
2756
|
"include": ["src"]
|
|
2537
2757
|
}
|
|
2538
|
-
`,
|
|
2758
|
+
`,Zt=`{
|
|
2539
2759
|
"compilerOptions": {
|
|
2540
2760
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
2541
2761
|
"target": "ES2023",
|
|
@@ -2552,7 +2772,7 @@ jobs:
|
|
|
2552
2772
|
},
|
|
2553
2773
|
"include": ["vite.config.ts"]
|
|
2554
2774
|
}
|
|
2555
|
-
`,
|
|
2775
|
+
`,eo=e=>`<!doctype html>
|
|
2556
2776
|
<html lang="es">
|
|
2557
2777
|
<head>
|
|
2558
2778
|
<meta charset="UTF-8" />
|
|
@@ -2564,9 +2784,9 @@ jobs:
|
|
|
2564
2784
|
<script type="module" src="/src/main.tsx"></script>
|
|
2565
2785
|
</body>
|
|
2566
2786
|
</html>
|
|
2567
|
-
`,
|
|
2787
|
+
`,to=`@import "tailwindcss";
|
|
2568
2788
|
@import "./lexy-theme.css";
|
|
2569
|
-
`,
|
|
2789
|
+
`,oo=e=>e.architecture==="feature"?`import "./index.css";
|
|
2570
2790
|
|
|
2571
2791
|
import { StrictMode } from "react";
|
|
2572
2792
|
import { createRoot } from "react-dom/client";
|
|
@@ -2600,7 +2820,9 @@ createRoot(document.getElementById("root")!).render(
|
|
|
2600
2820
|
Trae tu primer componente: <code>npx create-lexy add button</code>
|
|
2601
2821
|
</p>
|
|
2602
2822
|
</div>
|
|
2603
|
-
</main>`,
|
|
2823
|
+
</main>`,ao=e=>e.architecture==="feature"?`import { Route, Routes } from "react-router";
|
|
2824
|
+
|
|
2825
|
+
import { PrototypeDesignerPanel } from "../prototype/designer-panel/PrototypeDesignerPanel";
|
|
2604
2826
|
|
|
2605
2827
|
const Home = () => {
|
|
2606
2828
|
return (
|
|
@@ -2608,62 +2830,74 @@ ${Ke}
|
|
|
2608
2830
|
);
|
|
2609
2831
|
};
|
|
2610
2832
|
|
|
2833
|
+
const showPrototypeDesignerPanel = import.meta.env.DEV;
|
|
2834
|
+
|
|
2611
2835
|
export const App = () => {
|
|
2612
2836
|
return (
|
|
2613
|
-
|
|
2614
|
-
<
|
|
2615
|
-
|
|
2837
|
+
<>
|
|
2838
|
+
<Routes>
|
|
2839
|
+
<Route path="/" element={<Home />} />
|
|
2840
|
+
</Routes>
|
|
2841
|
+
{showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
|
|
2842
|
+
</>
|
|
2616
2843
|
);
|
|
2617
2844
|
};
|
|
2618
|
-
`:`
|
|
2845
|
+
`:`import { PrototypeDesignerPanel } from "../prototype/designer-panel/PrototypeDesignerPanel";
|
|
2846
|
+
|
|
2847
|
+
const showPrototypeDesignerPanel = import.meta.env.DEV;
|
|
2848
|
+
|
|
2849
|
+
export const App = () => {
|
|
2619
2850
|
return (
|
|
2851
|
+
<>
|
|
2620
2852
|
${Ke}
|
|
2853
|
+
{showPrototypeDesignerPanel ? <PrototypeDesignerPanel /> : null}
|
|
2854
|
+
</>
|
|
2621
2855
|
);
|
|
2622
2856
|
};
|
|
2623
|
-
`,
|
|
2857
|
+
`,no=`import { type ClassValue, clsx } from "clsx";
|
|
2624
2858
|
import { twMerge } from "tailwind-merge";
|
|
2625
2859
|
|
|
2626
2860
|
export function cn(...inputs: ClassValue[]) {
|
|
2627
2861
|
return twMerge(clsx(inputs));
|
|
2628
2862
|
}
|
|
2629
|
-
`,
|
|
2863
|
+
`,ro=`node_modules/
|
|
2630
2864
|
dist/
|
|
2631
2865
|
*.log
|
|
2632
2866
|
.env
|
|
2633
|
-
`,
|
|
2867
|
+
`,io=e=>`# ${e.projectName}
|
|
2634
2868
|
|
|
2635
2869
|
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
2870
|
|
|
2637
2871
|
\`\`\`bash
|
|
2638
2872
|
pnpm dev # desarrollo
|
|
2639
|
-
pnpm check:prototype # validar datos
|
|
2873
|
+
pnpm check:prototype # validar el contrato de datos del prototipo
|
|
2640
2874
|
npx create-lexy view button # ver un componente antes de instalarlo
|
|
2641
2875
|
npx create-lexy add button # instalarlo (local y editable)
|
|
2642
2876
|
npx create-lexy doctor # divergencia con el registry
|
|
2643
2877
|
\`\`\`
|
|
2644
|
-
`,
|
|
2645
|
-
`},
|
|
2646
|
-
`),await
|
|
2878
|
+
`,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)+`
|
|
2879
|
+
`},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",Qt),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",Yt),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)+`
|
|
2880
|
+
`),await n("components.json",so(e)),await b.copyFile(Ie,w.join(t,"src/lexy-theme.css")),await b.mkdir(w.join(t,"public/fonts"),{recursive:!0}),await b.cp(Ce,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
2881
|
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
|
|
2882
|
+
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
|
|
2883
|
+
|
|
2884
|
+
`)+j.dim(`Siguientes pasos:
|
|
2885
|
+
`)+j.cyan(` pnpm dev
|
|
2886
|
+
`)+j.cyan(" npx create-lexy add button"))};import ho from"fs/promises";import vo from"path";import S from"picocolors";import me from"picocolors";var V=(e,t)=>{let o=e.split(`
|
|
2887
|
+
`),a=t.split(`
|
|
2888
|
+
`),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},Q=e=>e.some(t=>t.type!=="same"),Be=(e,t=3)=>{if(!Q(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(`
|
|
2889
|
+
`)};var Je=async e=>{let t=process.cwd(),o=await _(t),a=e.toLowerCase(),{item:n,version:r}=await k(a),i=o.installed[a];console.log(S.bold(`
|
|
2890
|
+
${n.title??a}`)+S.dim(` \xB7 instalado con registry@${i??"?"} \xB7 vigente registry@${r}
|
|
2891
|
+
`));let d=!1;for(let s of T(n,o)){let u=await ho.readFile(vo.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 O from"fs/promises";import h from"path";import I from"picocolors";var We=async e=>{let t=process.cwd(),o=0,a=c=>console.log(I.green(" \u2713 ")+c),n=c=>console.log(I.yellow(" \u2022 ")+c),r=(c,l)=>{o=1,console.log(I.red(" \u2717 ")+c),l&&console.log(I.dim(` \u2192 ${l}`))};console.log(I.bold(`
|
|
2658
2892
|
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[
|
|
2893
|
+
`));let i;try{i=await _(t),a(`.lexy v\xE1lido (arquitectura ${i.architecture}${i.world?`, mundo ${i.world}`:""})`)}catch(c){return r(c.message),1}await O.access(h.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=h.join(i.paths.lib,"utils/cn.ts");await O.access(h.join(t,s)).then(()=>!0,()=>!1)?a(`helper cn presente (${s})`):r(`falta el helper cn (${s})`);let m=await O.readFile(h.join(t,"package.json"),"utf-8").then(c=>JSON.parse(c)).catch(()=>null),y=async(c,l)=>{await O.access(h.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=h.join(h.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(h.join(h.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=h.join(t,"ai/lexy-ai-manifest.json"),p=await O.readFile(M,"utf-8").catch(()=>null);if(p===null)console.log(I.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 O.access(h.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(I.dim(`
|
|
2894
|
+
Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let c=await W();console.log(I.bold(`
|
|
2895
|
+
Componentes instalados (registry vigente: ${c})
|
|
2896
|
+
`));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 O.readFile(h.join(t,L.relPath),"utf-8").catch(()=>null);H===null?x.push(L.relPath):Q(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 ve of H){let be=await O.readFile(h.join(t,ve.relPath),"utf-8").catch(()=>null);(be===null||Q(V(be,ve.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?I.green(`
|
|
2663
2897
|
Todo en orden.
|
|
2664
|
-
`):
|
|
2898
|
+
`):I.red(`
|
|
2665
2899
|
Hay problemas que atender (exit 1).
|
|
2666
|
-
`)),o};import
|
|
2900
|
+
`)),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
2901
|
|
|
2668
2902
|
> **Para el agente (Claude Code):** este archivo es tu guion. La persona que
|
|
2669
2903
|
> tienes enfrente es **dise\xF1adora, no programadora**: no le preguntes por
|
|
@@ -2733,34 +2967,36 @@ npx @lexydesign/ai install
|
|
|
2733
2967
|
backend: lo que nazca desde usabilidad queda como
|
|
2734
2968
|
\`origin: "generatedByUsability"\` y
|
|
2735
2969
|
\`technicalValidation.status: "pendingTi"\`.
|
|
2736
|
-
3.
|
|
2737
|
-
\`src/prototype/
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2970
|
+
3. Mant\xE9n los datos mock en
|
|
2971
|
+
\`src/prototype/mock-store/fixtures.ts\`, nunca dentro de componentes. Las
|
|
2972
|
+
pantallas leen con \`read.load(loadId, params, meta)\` y publican hechos con
|
|
2973
|
+
\`write.publish(eventId, payload, meta)\`, importados desde
|
|
2974
|
+
\`src/prototype/ports\`. La metadata inline (\`reads\`/\`writes\`,
|
|
2975
|
+
\`description\`, \`trigger\`) alimenta el panel del Designer. No registres
|
|
2976
|
+
interacciones puramente locales.
|
|
2977
|
+
4. Ejecuta \`pnpm check:prototype\` y corrige cualquier inconsistencia antes de
|
|
2743
2978
|
empezar a implementar pantallas.
|
|
2744
2979
|
|
|
2745
|
-
\`PROJECT-CONTEXT.md\` es el resumen humano. El contrato TypeScript es la
|
|
2746
|
-
estructurada
|
|
2747
|
-
|
|
2980
|
+
\`PROJECT-CONTEXT.md\` es el resumen humano. El contrato de datos TypeScript es la
|
|
2981
|
+
fuente estructurada para las pr\xF3ximas sesiones. El mock-store mantiene datos
|
|
2982
|
+
coherentes y persistentes; los ports separan el adapter mock del adapter de
|
|
2983
|
+
producci\xF3n; el panel registra cargas y publicaciones para inspecci\xF3n.
|
|
2748
2984
|
|
|
2749
2985
|
## 5. Cerrar
|
|
2750
2986
|
|
|
2751
|
-
Borra este archivo (\`${
|
|
2987
|
+
Borra este archivo (\`${X}\`): su trabajo termin\xF3. Conf\xEDrmale a la
|
|
2752
2988
|
persona que su proyecto est\xE1 listo y prop\xF3n el primer paso concreto
|
|
2753
2989
|
(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
|
|
2990
|
+
`,Ye=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.
|
|
2991
|
+
|
|
2992
|
+
Abre la carpeta con Claude Code y di: `+ye.bold("\xABay\xFAdame a arrancar mi proyecto Lexy\xBB")+`.
|
|
2993
|
+
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 C from"picocolors";var xe="@lexydesign/kit",Qe=`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(`${Qe}@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 ${C.bold(a)}. Usa ${C.cyan("--force")} para sobrescribir o ${C.cyan("--dir <ruta>")} para otra carpeta.`);let r=await ko();console.log(C.dim(`
|
|
2994
|
+
Trayendo ${xe}@${r} \u2192 ${a}/
|
|
2995
|
+
`));let i=`${Qe}@${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(C.yellow(`\u26A0 no se pudo traer ${m}`)+C.dim(` (${y.message})`))}console.log(C.green("\u2713 Kit listo")+C.dim(` \u2014 ${u} archivo(s), ${d.components.length} componente(s) + ${d.blocks.length} block(s)
|
|
2996
|
+
`)),console.log(` Abre ${C.bold(`${a}/index.html`)} en el navegador.`),console.log(C.dim(` El cat\xE1logo m\xE1quina-legible est\xE1 en ${a}/manifest.json.
|
|
2997
|
+
`))};import Eo from"fs/promises";import he from"path";import E from"picocolors";var K=e=>console.log(E.bold(E.cyan(`
|
|
2762
2998
|
\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)}};
|
|
2999
|
+
`))),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(`
|
|
3000
|
+
${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 _(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(he.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+`
|
|
3001
|
+
`),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=>he.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(he.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 Io,D=e=>async()=>{try{await e()}catch(t){console.error(Co.red(`
|
|
3002
|
+
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(()=>Ye(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);
|