create-lexy 0.6.2 → 0.6.3
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/README.md +7 -2
- package/assets/fonts/{OFL-NotoSans.txt → LICENSE-Geist.txt} +4 -5
- package/assets/fonts/geist-mono-variable-italic.woff2 +0 -0
- package/assets/fonts/geist-mono-variable.woff2 +0 -0
- package/assets/fonts/geist-sans-variable-italic.woff2 +0 -0
- package/assets/fonts/geist-sans-variable.woff2 +0 -0
- package/assets/r/accordion.json +3 -3
- package/assets/r/alert-dialog.json +3 -3
- package/assets/r/app-accordion.json +1 -1
- package/assets/r/app-dialog.json +3 -3
- package/assets/r/app-header-bar.json +2 -2
- package/assets/r/app-sidebar.json +2 -2
- package/assets/r/avatar.json +3 -3
- package/assets/r/badge.json +3 -3
- package/assets/r/brand-background.json +1 -1
- package/assets/r/breadcrumb.json +3 -3
- package/assets/r/button-group.json +3 -3
- package/assets/r/button.json +3 -3
- package/assets/r/calendar.json +2 -2
- package/assets/r/card.json +3 -3
- package/assets/r/chart.json +2 -2
- package/assets/r/checkbox.json +2 -2
- package/assets/r/combobox.json +3 -3
- package/assets/r/command.json +3 -3
- package/assets/r/confirmacion.json +1 -1
- package/assets/r/counter-badge.json +3 -3
- package/assets/r/crm-desk.json +1 -1
- package/assets/r/crm-detalle-caso.json +5 -5
- package/assets/r/date-picker.json +1 -1
- package/assets/r/dialog.json +3 -3
- package/assets/r/dropdown-menu.json +3 -3
- package/assets/r/empty.json +2 -2
- package/assets/r/feature-card.json +3 -3
- package/assets/r/form.json +3 -3
- package/assets/r/header-bar.json +2 -2
- package/assets/r/input.json +3 -3
- package/assets/r/intake-wizard.json +1 -1
- package/assets/r/label.json +3 -3
- package/assets/r/logo.json +2 -2
- package/assets/r/menubar.json +3 -3
- package/assets/r/navigation-menu.json +3 -3
- package/assets/r/pagination.json +2 -2
- package/assets/r/popover.json +3 -3
- package/assets/r/profile-card.json +3 -3
- package/assets/r/progress.json +2 -2
- package/assets/r/radio-group.json +2 -2
- package/assets/r/registry.json +47 -47
- package/assets/r/scroll-area.json +2 -2
- package/assets/r/searchbox.json +3 -3
- package/assets/r/select.json +3 -3
- package/assets/r/separator.json +2 -2
- package/assets/r/sheet.json +2 -2
- package/assets/r/sidebar.json +3 -3
- package/assets/r/skeleton.json +2 -2
- package/assets/r/slider.json +3 -3
- package/assets/r/snippet.json +3 -3
- package/assets/r/spinner.json +2 -2
- package/assets/r/status-dot.json +3 -3
- package/assets/r/switch.json +3 -3
- package/assets/r/table.json +3 -3
- package/assets/r/tabs.json +3 -3
- package/assets/r/tag.json +3 -3
- package/assets/r/textarea.json +3 -3
- package/assets/r/toaster.json +3 -3
- package/assets/r/tooltip.json +3 -3
- package/assets/r/tree.json +3 -3
- package/assets/registry-version +1 -1
- package/assets/theme/lexy-theme.css +562 -112
- package/dist/index.js +97 -91
- package/package.json +4 -2
- package/templates/.claude/skills/lexy-design/SKILL.md +189 -0
- package/templates/.claude/skills/lexy-dev/SKILL.md +168 -0
- package/templates/.claude/skills/lexy-mock-data/SKILL.md +50 -0
- package/templates/.github/copilot-instructions.md +50 -0
- package/templates/.mcp.json +8 -0
- package/templates/AGENTS.md +243 -0
- package/templates/CLAUDE.md +61 -0
- package/templates/ai/IMPLEMENTATION-PROTOCOL.md +148 -0
- package/templates/ai/PRODUCTION-CLEANUP.md +17 -0
- package/templates/ai/PROJECT-CONTEXT.md +65 -0
- package/templates/ai/README.md +19 -0
- package/templates/ai/TECHNICAL-USAGE.md +220 -0
- package/templates/ai/pautas/arquitectura-informacion-ux.md +243 -0
- package/templates/ai/pautas/buenas-practicas.md +236 -0
- package/templates/ai/pautas/calidad-industria.md +109 -0
- package/templates/ai/pautas/diseno-cliente.md +136 -0
- package/templates/ai/pautas/diseno-crm-lexy.md +109 -0
- package/templates/ai/pautas/patrones-de-codigo.md +234 -0
- package/templates/ai/pautas/recetas-layout.md +419 -0
- package/templates/ai/pautas/sistema-visual.md +197 -0
- package/templates/ai/pautas/ux-writing.md +214 -0
- package/templates/scripts/check-geometry.mjs +139 -0
- package/assets/fonts/noto-sans-latin.woff2 +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Sistema visual Lexy — valores y densidad
|
|
2
|
+
|
|
3
|
+
Esta pauta da los **valores concretos** (espaciado, densidad, tipografía, color de
|
|
4
|
+
estado, motion) que el tema no documenta solo. Es complemento técnico de
|
|
5
|
+
[diseno-cliente.md](diseno-cliente.md) y [diseno-crm-lexy.md](diseno-crm-lexy.md): la filosofía dice *por qué*; esto dice
|
|
6
|
+
*con qué números*. Para reglas de oficio y composición usa [buenas-practicas.md](buenas-practicas.md).
|
|
7
|
+
|
|
8
|
+
El tema sigue la convención **shadcn/Tailwind**: usa los tokens semánticos
|
|
9
|
+
(`bg-primary`, `text-muted-foreground`, `border-border`) y deja que el CSS resuelva
|
|
10
|
+
los colores. Lo que sigue cubre lo que **no** es estándar y las decisiones de
|
|
11
|
+
densidad inspiradas en Fluent, Material y Apple HIG, aterrizadas a Geist Sans y al tema Lexy.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Tokens no estándar (no inventes equivalentes crudos)
|
|
16
|
+
|
|
17
|
+
shadcn base solo trae `primary`, `secondary`, `destructive`, `muted`, `accent`. El
|
|
18
|
+
tema Lexy agrega tokens de estado. **Úsalos siempre; no uses colores crudos de
|
|
19
|
+
Tailwind como `bg-green-500` o `text-red-600` para estado.**
|
|
20
|
+
|
|
21
|
+
| Estado | Token | Texto/icono | Fondo sutil | Borde sutil |
|
|
22
|
+
|---|---|---|---|---|
|
|
23
|
+
| Éxito | `success` | `text-success-strong` | `bg-success/10` | `border-success/30` |
|
|
24
|
+
| Alerta | `warning` | `text-warning-strong` | `bg-warning/10` | `border-warning/30` |
|
|
25
|
+
| Error | `destructive` | `text-destructive` | `bg-destructive/10` | `border-destructive/20` |
|
|
26
|
+
| Información | `info` | `text-info` | `bg-info/10` | `border-info/20` |
|
|
27
|
+
| Marca | `primary` | `text-primary` | `bg-primary/10` | `border-primary/20` |
|
|
28
|
+
|
|
29
|
+
Reglas:
|
|
30
|
+
|
|
31
|
+
- **Patrón de superficie de estado:** para texto sobre fondo claro usa el paso
|
|
32
|
+
`-strong` de éxito/alerta; acompáñalo de fondo a `/10` y borde a `/30`.
|
|
33
|
+
`Tag` aplica este patrón. Los dots y rellenos usan el token base vívido.
|
|
34
|
+
- `accent` (`bg-accent` / `text-accent-foreground`) es el tinte sutil del primary;
|
|
35
|
+
úsalo para selección, hover de items y estados activos discretos, no para CTA.
|
|
36
|
+
- El color **nunca** es el único signo de estado: acompáñalo con texto o icono
|
|
37
|
+
(ver [buenas-practicas.md](buenas-practicas.md)).
|
|
38
|
+
|
|
39
|
+
## 2. Familia y pesos tipográficos (Geist)
|
|
40
|
+
|
|
41
|
+
**Geist es la familia tipográfica del producto.** Geist Sans cubre toda la interfaz
|
|
42
|
+
y Geist Mono queda reservada para código, tokens e identificadores técnicos. Ambas
|
|
43
|
+
son variables y autoalojadas. No agregues otra familia para crear jerarquía: cambia
|
|
44
|
+
de rol tipográfico, tamaño o peso dentro de Geist.
|
|
45
|
+
|
|
46
|
+
El tema usa sus pesos funcionales (`medium: 500`, `semibold: 600`, `bold: 700`).
|
|
47
|
+
**Usa las clases de utilidad, no valores numéricos:**
|
|
48
|
+
|
|
49
|
+
- `font-normal` (400) — cuerpo, captions.
|
|
50
|
+
- `font-medium` (500) — labels, items de navegación, énfasis suave.
|
|
51
|
+
- `font-semibold` (600) — títulos de sección, headers de tabla, botones.
|
|
52
|
+
- `font-bold` (700) — display editorial y énfasis excepcional fuera de la jerarquía de producto.
|
|
53
|
+
|
|
54
|
+
Los títulos de producto no se construyen combinando peso y tamaño a mano: los roles
|
|
55
|
+
`type-*` ya asignan el peso correcto. `type-page-title`, por ejemplo, usa 600.
|
|
56
|
+
|
|
57
|
+
No uses pesos arbitrarios ni `font-extrabold`: rompen la rampa calibrada para Geist.
|
|
58
|
+
|
|
59
|
+
## 3. Rampa tipográfica (roles)
|
|
60
|
+
|
|
61
|
+
La API de producto usa clases de **rol**, no combinaciones manuales de `text-*`,
|
|
62
|
+
`leading-*`, `tracking-*` y `font-*`:
|
|
63
|
+
|
|
64
|
+
| Clase | px / line-height | Rol |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `type-meta` | 12 / 16 | Caption, metadatos, ayuda |
|
|
67
|
+
| `type-supporting` | 14 / 20 | Ayuda y texto secundario |
|
|
68
|
+
| `type-action-label` | 14 / 20 | Botones, tabs y labels de acción |
|
|
69
|
+
| `type-data` | 14 / 20 | Montos, plazos, RUT y fechas (tabular) |
|
|
70
|
+
| `type-body` | 16 / 24 | Cuerpo por defecto y lectura |
|
|
71
|
+
| `type-item-title` / `type-section-title` | 16 / 24 | Ítem o encabezado de sección |
|
|
72
|
+
| `type-subsection-title` | 22 / 28 | Subtítulo de documento |
|
|
73
|
+
| `type-dialog-title` | 24 / 32 | Título de diálogo |
|
|
74
|
+
| `type-page-title` | 32 / 40 | Título de página |
|
|
75
|
+
|
|
76
|
+
- **CRM:** usa `type-body` y `type-supporting`; títulos de vista en
|
|
77
|
+
`type-subsection-title` o `type-page-title` según la jerarquía. La expresividad
|
|
78
|
+
display no pertenece a herramientas internas.
|
|
79
|
+
- **Cliente:** título de pantalla `type-page-title`, cuerpo `type-body`, ayuda
|
|
80
|
+
`type-supporting`.
|
|
81
|
+
- Un solo `h1` por página. No saltes niveles de heading para conseguir un tamaño.
|
|
82
|
+
|
|
83
|
+
## 4. Espaciado — grid de 8 pt (contrato híbrido)
|
|
84
|
+
|
|
85
|
+
Base de 8 px (con medios pasos de 4 px), convención compartida por Fluent, Material
|
|
86
|
+
y Apple HIG. Usa la escala de Tailwind, que ya es múltiplo de 4:
|
|
87
|
+
|
|
88
|
+
- Micro (dentro de un control): `gap-1` (4) · `gap-2` (8).
|
|
89
|
+
- Entre elementos relacionados: `gap-3` (12) · `gap-4` (16).
|
|
90
|
+
- Entre grupos / secciones: `gap-6` (24) · `gap-8` (32).
|
|
91
|
+
- Separación de bloques mayores: `gap-12` (48) · `gap-16` (64).
|
|
92
|
+
|
|
93
|
+
Reglas del contrato (las mismas que cumplen los componentes de la librería):
|
|
94
|
+
|
|
95
|
+
- **Grilla de 4 px para todo espaciado entre elementos**: gaps, `space-*`,
|
|
96
|
+
márgenes y paddings de superficie. Evita valores arbitrarios (`p-[13px]`,
|
|
97
|
+
`mt-[7px]`); si necesitas algo intermedio, redondea al paso de 4 más cercano.
|
|
98
|
+
La escala de Tailwind v4 es dinámica: `w-70` (280 px) o `max-h-75` (300 px)
|
|
99
|
+
son válidos y preferibles a `w-[280px]`.
|
|
100
|
+
- **Medio-pasos de 2 px solo DENTRO de un control**: padding fino de items de
|
|
101
|
+
menú (`py-1.5`), iconos pequeños (`size-3.5`) y offsets de alineación. Nunca
|
|
102
|
+
para separar hermanos o secciones.
|
|
103
|
+
- **Nudge óptico de 2 px (`m*-0.5`)**: permitido solo como compensación
|
|
104
|
+
deliberada de baseline u óptica (icono junto a texto, asterisco de label).
|
|
105
|
+
- **Caja compuesta simétrica**: en un componente o bloque con header/contenido/
|
|
106
|
+
footer, el espacio sobre el primer slot debe igualar el espacio bajo el
|
|
107
|
+
último, todos los slots comparten el mismo riel horizontal (`px`), y los gaps
|
|
108
|
+
entre slots son uniformes o decrecen con jerarquía clara. Cumplir la grilla
|
|
109
|
+
valor a valor no basta si la unidad queda asimétrica.
|
|
110
|
+
|
|
111
|
+
## 5. Densidad — cliente vs CRM (el ajuste que más cambia el resultado)
|
|
112
|
+
|
|
113
|
+
La misma jerarquía, dos densidades. No las mezcles.
|
|
114
|
+
|
|
115
|
+
### Cliente (aire = calma)
|
|
116
|
+
|
|
117
|
+
- Contenedor de contenido: `max-w-xl` (formularios) a `max-w-2xl` (lectura).
|
|
118
|
+
- Padding de sección: `p-6` a `p-8`. Padding de página: `px-4` móvil, `px-6`+ desktop.
|
|
119
|
+
- Separación entre campos de formulario: `gap-5` / `space-y-5`.
|
|
120
|
+
- Separación entre secciones: `gap-8` a `gap-12`.
|
|
121
|
+
- Altura de inputs: `h-10`. El botón usa 36 px por defecto y 40 px en `size="lg"`;
|
|
122
|
+
en flujos de cliente prefiere `lg` cuando la acción principal necesita más presencia.
|
|
123
|
+
- Una idea principal por pantalla; deja respirar.
|
|
124
|
+
|
|
125
|
+
### CRM (densidad jerarquizada = velocidad)
|
|
126
|
+
|
|
127
|
+
- Contenedor: ancho completo del área de trabajo (`w-full`), sin `max-w` estrecho.
|
|
128
|
+
- Padding de superficie: `p-4` (paneles), `px-4 py-3` (toolbars).
|
|
129
|
+
- Separación entre campos: `gap-3` / `space-y-3`.
|
|
130
|
+
- Filas de tabla: `h-10` cómoda, `h-9` compacta; celdas `px-3 py-2`.
|
|
131
|
+
- Densidad alta solo si está jerarquizada (alineación, peso, agrupación). Densidad
|
|
132
|
+
sin jerarquía es ruido, no eficiencia.
|
|
133
|
+
- Prefiere edición en línea y acciones por fila sobre navegar a otra pantalla.
|
|
134
|
+
|
|
135
|
+
## 6. Superficie, bordes y radios
|
|
136
|
+
|
|
137
|
+
- **Contención: espacio → superficie → línea.** Agrupa primero por proximidad. Si
|
|
138
|
+
hace falta un límite explícito, usa `bg-surface-container rounded-lg` con aire
|
|
139
|
+
interior. Reserva el hairline para separar ítems dentro de una misma superficie
|
|
140
|
+
cuando el espacio no baste; no cortes cada sección del documento con una línea.
|
|
141
|
+
- **Elevación semántica.** La sombra solo aparece cuando algo realmente flota.
|
|
142
|
+
Cards y paneles estáticos usan superficie, tono y borde, sin sombra por defecto.
|
|
143
|
+
Usa `shadow-raised` para elevación cercana y `shadow-overlay` para dropdowns,
|
|
144
|
+
popovers, toasts, Dialog y Sheet. No inventes escalones con `shadow-sm/md/lg`.
|
|
145
|
+
- **Radio por oficio.** La escala intermedia es 2–4px para detalles mini, 6px
|
|
146
|
+
para interacción rectangular, 8px para flotantes compactas, 10px para cards,
|
|
147
|
+
14px para overlays y 20px para media o paneles hero. Usa `rounded-control` (inputs), `rounded-button` (botones
|
|
148
|
+
de cualquier variante o tamaño), `rounded-menu-item`, `rounded-nav-item`,
|
|
149
|
+
`rounded-lg` (cards), `rounded-xl` (Dialog y Sheet) y `rounded-2xl` (media o
|
|
150
|
+
paneles hero). `rounded-sm` queda para detalle mini.
|
|
151
|
+
- **Una geometría para todos los botones.** Primary, destructive, secondary,
|
|
152
|
+
outline, ghost y los botones solo-icono comparten `rounded-button` (6px), también
|
|
153
|
+
en tamaño `lg`. La jerarquía cambia color, borde y contenido, no la silueta.
|
|
154
|
+
`rounded-full` queda para avatares, dots, toggles y conteos cuya forma circular
|
|
155
|
+
expresa su función.
|
|
156
|
+
- **Jerarquía por superficie:** `background` (canvas) < `surface-container`
|
|
157
|
+
(zona de agrupación) < `card` (contenido) < `popover` (flotante). No pongas card
|
|
158
|
+
sobre card sobre card; aplana.
|
|
159
|
+
|
|
160
|
+
## 7. Iconografía (lucide-react)
|
|
161
|
+
|
|
162
|
+
- Tamaño por defecto en botones y junto a texto: `size-4` (16 px) — el registry ya
|
|
163
|
+
lo aplica con `[&_svg]:size-4`. Iconos sueltos de acción: `size-4`/`size-5`.
|
|
164
|
+
- El icono **acompaña**, no es la única explicación: botón solo-icono requiere
|
|
165
|
+
`aria-label`. Estado por icono requiere también texto.
|
|
166
|
+
- Estilo consistente: trazo lineal de lucide, no mezcles con emoji (Lexy no usa emoji).
|
|
167
|
+
- No decores cada sección con un icono. Un icono se gana su lugar si ayuda a
|
|
168
|
+
reconocer o actuar más rápido.
|
|
169
|
+
|
|
170
|
+
## 8. Motion
|
|
171
|
+
|
|
172
|
+
Inspirado en Material/Apple: el movimiento orienta, no entretiene.
|
|
173
|
+
|
|
174
|
+
- **Duraciones por distancia:** `--duration-instant` (80 ms) para hover,
|
|
175
|
+
`--duration-fast` (120 ms) para presión, `--duration-normal` (180 ms) para
|
|
176
|
+
cambios de estado y `--duration-overlay` (220 ms) para capas.
|
|
177
|
+
- **Easing:** `--ease-enter` para entradas, `--ease-standard` para cambios y
|
|
178
|
+
`--ease-exit` para salidas.
|
|
179
|
+
- Anima `opacity` y `transform`, no `width`/`height`/`top` (evita reflow y jank).
|
|
180
|
+
- **Respeta `prefers-reduced-motion`:** envuelve animaciones no esenciales y
|
|
181
|
+
ofrece una versión sin desplazamiento. Nunca dependas del movimiento para
|
|
182
|
+
comunicar (un cambio de estado debe leerse también detenido).
|
|
183
|
+
- En CRM el movimiento es mínimo: feedback inmediato sobre animación elaborada.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Checklist visual antes de entregar
|
|
188
|
+
|
|
189
|
+
1. ¿Usaste tokens de estado (`success`/`warning`/`info`) y no colores crudos?
|
|
190
|
+
2. ¿El estado se comunica con texto o icono además del color?
|
|
191
|
+
3. ¿Pesos vía clases (`font-medium`/`font-semibold`), sin valores numéricos?
|
|
192
|
+
4. ¿Espaciado en múltiplos de 4/8, sin valores arbitrarios?
|
|
193
|
+
5. ¿Las cajas compuestas son simétricas (espacio tope = base, riel `px` único entre header/contenido/footer, gaps uniformes)?
|
|
194
|
+
6. ¿La densidad corresponde al mundo (aire en cliente, compacto en CRM)?
|
|
195
|
+
7. ¿Un solo `h1` y headings sin saltos por tamaño?
|
|
196
|
+
8. ¿La contención sigue espacio → superficie → línea y la sombra aparece solo si algo flota?
|
|
197
|
+
9. ¿Motion corto, sobre opacity/transform y con `prefers-reduced-motion`?
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Lexy — Guía de UX Writing
|
|
2
|
+
|
|
3
|
+
> Documento para IA y diseñadores. Define cómo se escribe dentro de los productos Lexy: tono, patrones de microcopy y reglas por tipo de componente. Los ejemplos están tomados de los productos Lexy (flujo de Carga de Documentos y desks del equipo).
|
|
4
|
+
|
|
5
|
+
Esta guía es operativa: no habla de filosofía de marca (eso vive en otros documentos), sino de **qué palabras poner en pantalla y cómo**. Cuando escribas un texto de interfaz para Lexy, esta es la referencia.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. La regla base: háblale de tú
|
|
10
|
+
|
|
11
|
+
Lexy siempre habla de **tú**: cercano, directo, humano.
|
|
12
|
+
|
|
13
|
+
- *"Ingresa tu clave aquí"*
|
|
14
|
+
- *"Aún puedes subir los documentos"*
|
|
15
|
+
- *"Tu contraseña se usará solo para validar tu información"*
|
|
16
|
+
- *"Completa los datos solicitados"*
|
|
17
|
+
|
|
18
|
+
Única excepción: textos legales o contractuales (términos, mandatos) donde el registro jurídico lo exige. Todo lo demás es tú.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Principios de redacción
|
|
23
|
+
|
|
24
|
+
1. **Claridad antes que completitud.** Una frase corta que se entiende le gana a una correcta pero larga. Si puedes decirlo en menos palabras sin perder sentido, hazlo.
|
|
25
|
+
2. **El usuario primero, el sistema después.** Habla de lo que la persona gana o tiene que hacer, no de cómo funciona el sistema por dentro.
|
|
26
|
+
3. **Di qué pasa después.** Todo mensaje de estado deja claro el siguiente paso. Nunca dejes a la persona sin saber qué viene.
|
|
27
|
+
4. **Honestidad sin alarmismo.** Cuando hay una consecuencia (un plazo que vence, datos que se borran), dila con claridad — pero sin asustar ni culpar.
|
|
28
|
+
5. **Lenguaje cotidiano, no jurídico.** "Documento", "deuda", "plazo" — no "instrumento", "obligación crediticia", "término perentorio". El término técnico solo cuando no hay equivalente simple.
|
|
29
|
+
6. **Mayúscula solo inicial, no Title Case.** Títulos y botones llevan mayúscula en la primera letra nada más: *"Subir documento"*, no *"Subir Documento"*. Los nombres propios de documentos sí se capitalizan: *"Certificado de Dominio Vigente"*.
|
|
30
|
+
7. **Accesible para distintas formas de uso.** El texto debe funcionar para quien lee rápido, usa lector de pantalla, navega con teclado, tiene baja visión, está cansado o necesita volver a revisar. No dependas de posición, color o iconos sin nombrar la acción o el estado.
|
|
31
|
+
8. **Escaneable antes que decorativo.** Usa títulos, subtítulos, labels y agrupaciones que permitan encontrar rápido lo importante. Evita frases genéricas que suenan bien pero no describen la tarea.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Botones y llamados a la acción
|
|
36
|
+
|
|
37
|
+
**Patrón:** verbo en infinitivo o imperativo, concreto, que dice exactamente qué va a pasar.
|
|
38
|
+
|
|
39
|
+
- `Continuar`
|
|
40
|
+
- `Subir documento` · `Subir documentos ahora`
|
|
41
|
+
- `Guardar y continuar`
|
|
42
|
+
- `Guardar y enviar para revisión`
|
|
43
|
+
- `Volver al inicio`
|
|
44
|
+
|
|
45
|
+
**Reglas:**
|
|
46
|
+
- **Di el resultado, no la mecánica.** *"Guardar y enviar para revisión"* es mejor que *"Enviar"* porque anticipa qué ocurre.
|
|
47
|
+
- **Evita "Aceptar / OK" genéricos** cuando puedes nombrar la acción.
|
|
48
|
+
- **Los botones de confirmación restatean la elección.** En vez de Sí/No, el botón repite la decisión en las palabras del usuario:
|
|
49
|
+
- Pregunta: *"¿Este archivo incluye las 3 liquidaciones?"*
|
|
50
|
+
- Botones: *"Sí, contiene las 3 liquidaciones"* / *"No, es solo una liquidación"*
|
|
51
|
+
- Así se elimina la ambigüedad de un Sí/No suelto y queda claro qué se confirma.
|
|
52
|
+
- **Máximo ~4 palabras**, salvo en los botones que restatean.
|
|
53
|
+
- **Un solo verbo por acción en todo el producto.** Para subir archivos, usa siempre **"subir documento"** (no alternes con "adjuntar archivo").
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Títulos y encabezados
|
|
58
|
+
|
|
59
|
+
- **Secciones:** sustantivo, claro y escaneable. *"Documentos financieros"*, *"Documentación personal"*, *"Bienes"*, *"Instrucciones"*.
|
|
60
|
+
- **Títulos específicos.** Un título debe describir el tema o la tarea, no decorar la pantalla. *"Datos de la deuda"* es mejor que *"Tu información"* si la sección trata de deuda.
|
|
61
|
+
- **Palabras importantes al inicio.** La persona debe poder escanear en diagonal y reconocer campos, estados y acciones sin leer párrafos completos.
|
|
62
|
+
- **Agrupa con subtítulos cuando ayuda a evitar errores.** Divide datos personales, deudas, bienes y confirmación si la persona debe revisar cada bloque por separado.
|
|
63
|
+
- **No uses Title Case.** Todo texto de interfaz usa mayúscula solo inicial: títulos, headings, labels, menú, navegación, app bars y botones. Los nombres propios y términos de marca conservan su capitalización.
|
|
64
|
+
- **Ayuda contextual como pregunta del usuario.** Encabeza la ayuda con la duda real, en primera persona:
|
|
65
|
+
- *"¿Cómo obtener este documento?"*
|
|
66
|
+
- *"¿Cómo obtener tu clave de boletín comercial?"*
|
|
67
|
+
- *"¿Necesitas ayuda visual?"*
|
|
68
|
+
- *"¿Documento rechazado o vencido?"*
|
|
69
|
+
- *"¿Problemas con un documento? Habla con el asistente virtual"*
|
|
70
|
+
- Usa la pregunta para entradas a ayuda, FAQ, asistente y estados problemáticos. Para títulos de sección operativa, usa sustantivo.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Consecuencias y confirmaciones
|
|
75
|
+
|
|
76
|
+
Cuando una acción tenga impacto, explica el resultado en lenguaje neutro, directo y verificable. No intentes empujar la decisión con miedo, culpa o dramatización.
|
|
77
|
+
|
|
78
|
+
**Patrón:** acción + consecuencia + posibilidad de deshacer o corregir, si existe.
|
|
79
|
+
|
|
80
|
+
- *"Enviar documentos para revisión"* → *"El equipo legal revisará estos documentos. Si falta algo, te pediremos corregirlo."*
|
|
81
|
+
- *"Eliminar documento"* → *"El documento saldrá de esta solicitud. Puedes volver a subirlo antes de enviar."*
|
|
82
|
+
- *"Reiniciar formulario"* → *"Se borrarán los datos ingresados en este paso. Podrás completarlos nuevamente."*
|
|
83
|
+
|
|
84
|
+
**Reglas:**
|
|
85
|
+
- Di qué pasará si la persona confirma la acción y qué podrá hacer después.
|
|
86
|
+
- Si existe una forma de deshacer, restaurar o corregir, nómbrala en el mismo mensaje.
|
|
87
|
+
- No exageres la consecuencia: evita *"perderás todo"*, *"acción irreversible"* o *"¿estás seguro?"* si puedes explicar el resultado concreto.
|
|
88
|
+
- No suavices una consecuencia real. Si un dato se borra, di que se borra; si queda en revisión, di que queda en revisión.
|
|
89
|
+
- En acciones sensibles, el botón debe repetir la acción concreta: `Eliminar documento`, `Enviar para revisión`, `Reiniciar formulario`.
|
|
90
|
+
|
|
91
|
+
**Evita:**
|
|
92
|
+
- *"¿Estás seguro?"* sin explicar qué cambia.
|
|
93
|
+
- Mensajes que intentan influir: *"No podrás avanzar si haces esto"* cuando en realidad hay una alternativa.
|
|
94
|
+
- Consecuencias ambiguas: *"Se actualizará tu caso"* sin decir qué se actualiza.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 5. Mensajes de éxito
|
|
99
|
+
|
|
100
|
+
**Patrón:** exclamación celebratoria breve + qué pasa ahora + dónde hacer seguimiento.
|
|
101
|
+
|
|
102
|
+
- *"¡Tus documentos fueron enviados con éxito!"* → *"Estamos revisando los documentos. Podrás seguir su estado en la pestaña 'Enviados'."*
|
|
103
|
+
- *"¡Documentos cargados con éxito!"*
|
|
104
|
+
- *"Documentos guardados con éxito"* → *"Tu información se guardó correctamente."*
|
|
105
|
+
|
|
106
|
+
**Reglas:**
|
|
107
|
+
- El título celebra (con `¡!`); el cuerpo informa el siguiente paso.
|
|
108
|
+
- Nunca termines en el "éxito" a secas: di qué sigue (*"estamos revisando"*, *"podrás seguir su estado"*).
|
|
109
|
+
- Si hay un lugar donde seguir el proceso, nómbralo explícitamente.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 6. Plazos, vencimientos y urgencia
|
|
114
|
+
|
|
115
|
+
El momento más delicado del tono: comunicar urgencia real **sin asustar**. Combina una consecuencia honesta con una salida siempre abierta.
|
|
116
|
+
|
|
117
|
+
- *"¡Llegaste al final del plazo!"* → *"Tienes 3 días adicionales para enviar tus documentos. Si no lo haces a tiempo, deberás reiniciar el proceso de documentación desde cero."*
|
|
118
|
+
- *"El plazo ha terminado, por favor sube tus documentos. Tu ejecutiva legal revisará tu información en conjunto con el abogado y te indicará los siguientes pasos."*
|
|
119
|
+
- *"Plazo cumplido. Aún puedes subir los documentos y estudiaremos tu caso."*
|
|
120
|
+
|
|
121
|
+
**Reglas:**
|
|
122
|
+
- **Siempre deja una puerta abierta.** Incluso con el plazo cumplido: *"Aún puedes subir los documentos y estudiaremos tu caso"*. Nunca un callejón sin salida.
|
|
123
|
+
- **La consecuencia se dice clara pero neutra:** *"deberás reiniciar el proceso desde cero"* es un hecho, no una amenaza. Sin "perderás todo", sin mayúsculas de pánico, sin exclamación en la consecuencia.
|
|
124
|
+
- **Humaniza el proceso.** Mencionar a *"tu ejecutiva legal"* y *"el abogado"* recuerda que hay personas reales acompañando — baja la ansiedad.
|
|
125
|
+
- **Cuenta regresiva con número visible** (*"15 días"*, *"3 días adicionales"*) para que la urgencia sea concreta, no vaga.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 7. Estados de documentos / ítems
|
|
130
|
+
|
|
131
|
+
Estados cortos, una o dos palabras, consistentes en todo el producto:
|
|
132
|
+
- `Documento subido` · `Aprobado` · `En revisión` · `Documento rechazado` · `Documento vencido`
|
|
133
|
+
|
|
134
|
+
**Reglas:**
|
|
135
|
+
- Un solo término por estado, en todas las pantallas. No alternes *"rechazado"* / *"no aprobado"* / *"denegado"*.
|
|
136
|
+
- Cuando un estado es problemático (rechazado/vencido), **acompáñalo siempre de la acción de salida**:
|
|
137
|
+
- *"Si un documento aparece como rechazado o vencido, haz clic en [ícono] para conocer el motivo y subirlo nuevamente."*
|
|
138
|
+
- El estado dice qué pasó; el texto de apoyo dice qué hacer.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 8. Instrucciones y ayuda contextual
|
|
143
|
+
|
|
144
|
+
Explica el *cómo* paso a paso, con lenguaje de acción directa:
|
|
145
|
+
- *"Completa los datos solicitados y descarga el PDF."*
|
|
146
|
+
- *"Puedes hacer clic en cada imagen para abrirla en tamaño completo."*
|
|
147
|
+
- Referencias concretas a sitios reales: *"www.registrocivil.cl"*.
|
|
148
|
+
|
|
149
|
+
**Reglas:**
|
|
150
|
+
- Imperativo amable en segunda persona: *"Completa"*, *"Descarga"*, *"Haz clic"*.
|
|
151
|
+
- Pasos en el orden exacto en que se ejecutan.
|
|
152
|
+
- Nombra herramientas y sitios reales sin rodeos; la persona los va a buscar.
|
|
153
|
+
- Cuando ofrezcas ayuda extra, enmárcala como beneficio: *"¿Necesitas ayuda visual?"* antes de explicar el truco.
|
|
154
|
+
- Evita abreviaturas si hay espacio para escribir la palabra completa. Usa *"por ejemplo"* en vez de *"ej."*, *"y otros documentos"* en vez de *"etc."*.
|
|
155
|
+
- Si una abreviatura es necesaria o muy conocida en el dominio, escríbela de forma consistente y con apoyo contextual cuando pueda confundirse. Por ejemplo, `RUT` puede usarse, pero el label debe ser claro: *"RUT del solicitante"*.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 9. Confianza, privacidad y datos sensibles
|
|
160
|
+
|
|
161
|
+
Cuando pidas algo sensible (una clave, un dato personal), **explica para qué y da garantía** en el mismo momento del pedido.
|
|
162
|
+
|
|
163
|
+
- *"Tu contraseña se usará solo para validar tu información en el Boletín Comercial, garantizando la confidencialidad y protección de tus datos."*
|
|
164
|
+
|
|
165
|
+
**Reglas:**
|
|
166
|
+
- Junto al campo sensible, explica **para qué se usa** y **que está protegido**.
|
|
167
|
+
- Acota el uso ("solo para…") — nada de propósitos vagos.
|
|
168
|
+
- Nunca pidas un dato sensible sin justificarlo ahí mismo.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 10. Campos de formulario
|
|
173
|
+
|
|
174
|
+
- **Placeholder = instrucción de acción:** *"Ingresa tu clave aquí"*.
|
|
175
|
+
- Label corto y claro; el placeholder ejemplifica o instruye, no repite el label.
|
|
176
|
+
- Mensajes de error: di qué pasó y cómo arreglarlo, en tono neutro (*"Ingresa un correo válido"*, no *"Error de validación"*).
|
|
177
|
+
- El label debe existir aunque haya placeholder. El placeholder no reemplaza al label.
|
|
178
|
+
- Si un campo es obligatorio, dilo de forma consistente y accesible; no dependas solo de un asterisco sin explicación.
|
|
179
|
+
- Para botones con solo icono, escribe un `aria-label` claro: `Eliminar deuda`, `Abrir ayuda`, `Cerrar`.
|
|
180
|
+
- Los estados deben leerse como texto: `En revisión`, `Documento rechazado`, `Guardado`, no solo por color.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 11. Glosario de términos preferidos
|
|
185
|
+
|
|
186
|
+
| Usa | Evita |
|
|
187
|
+
|---|---|
|
|
188
|
+
| documento | instrumento / archivo adjunto |
|
|
189
|
+
| subir un documento | adjuntar archivo |
|
|
190
|
+
| deuda | obligación crediticia |
|
|
191
|
+
| plazo | término / fecha perentoria |
|
|
192
|
+
| revisar tu caso | analizar el expediente |
|
|
193
|
+
| tu ejecutiva legal / el abogado | el equipo jurídico |
|
|
194
|
+
| contraseña | credencial |
|
|
195
|
+
| enviar para revisión | someter a evaluación |
|
|
196
|
+
| por ejemplo | ej. / e.g. |
|
|
197
|
+
| y otros documentos | etc. |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 12. Checklist antes de publicar un texto
|
|
202
|
+
|
|
203
|
+
1. ¿Está en **tú**?
|
|
204
|
+
2. ¿Se entiende en una lectura, sin jerga jurídica?
|
|
205
|
+
3. Si es un estado o acción, ¿dice **qué pasa después**?
|
|
206
|
+
4. Si hay una consecuencia, ¿está dicha **clara, neutra y sin asustar**, con opción de deshacer o corregir cuando exista?
|
|
207
|
+
5. ¿El botón dice el **resultado concreto** de la acción?
|
|
208
|
+
6. ¿Usa el **término del glosario** y es consistente con el resto del producto?
|
|
209
|
+
7. ¿Mayúscula solo inicial (no Title Case)?
|
|
210
|
+
8. ¿Los títulos y subtítulos permiten escanear la pantalla rápido?
|
|
211
|
+
9. ¿Evitaste abreviaturas innecesarias?
|
|
212
|
+
10. ¿Sobra alguna palabra? Quítala.
|
|
213
|
+
|
|
214
|
+
> La prueba final: léelo imaginando que se lo dices, de tú, a alguien que está estresado y no sabe de leyes. Si suena claro, cálido y le deja claro el siguiente paso — está listo.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chequeo del contrato de geometría Lexy — versión para proyectos.
|
|
3
|
+
* Instalado por `npx create-lexy install-ai`; el contrato se fiscaliza en cada repo.
|
|
4
|
+
*
|
|
5
|
+
* Uso:
|
|
6
|
+
* pnpm lint:geometry → valida el contrato (exit 1 si hay violaciones)
|
|
7
|
+
* pnpm lint:geometry -- --box → además imprime la tabla de caja por slot de
|
|
8
|
+
* cada componente compuesto
|
|
9
|
+
*
|
|
10
|
+
* Contrato (resumen — ver ai/pautas/sistema-visual.md):
|
|
11
|
+
* - Grilla híbrida: múltiplos de 4px para espaciado entre elementos y superficies;
|
|
12
|
+
* medio-pasos de 2px permitidos SOLO dentro de un control. Márgenes, posiciones
|
|
13
|
+
* y space-* siempre en grilla 4px.
|
|
14
|
+
* - Valores arbitrarios [..] solo funcionales (vars, %, vh/vw, calc, inherit).
|
|
15
|
+
* - Radios por rol: aliases `rounded-control`/`button`/`menu-item`/`nav-item`
|
|
16
|
+
* para interacción; `rounded-lg` para cards, `rounded-xl` para overlays,
|
|
17
|
+
* `rounded-2xl` para media/hero, `rounded-sm` para detalle mini y
|
|
18
|
+
* `rounded-full` solo para objetos cuya función es circular.
|
|
19
|
+
* - Sombras: `shadow-sm` superficies, `shadow-md` flotantes, `shadow-lg` solo
|
|
20
|
+
* overlays modales. `shadow-xl` no existe.
|
|
21
|
+
* - El theme define --radius y sus derivados (toda la escala cuelga de una perilla).
|
|
22
|
+
*/
|
|
23
|
+
import fs from "node:fs/promises";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const printBox = process.argv.includes("--box");
|
|
28
|
+
|
|
29
|
+
const lexy = JSON.parse(await fs.readFile(path.join(cwd, ".lexy"), "utf-8").catch(() => "{}"));
|
|
30
|
+
const componentsDir = path.join(cwd, lexy.paths?.components ?? "src/components/base");
|
|
31
|
+
const themeFile = path.join(cwd, "src/lexy-theme.css");
|
|
32
|
+
|
|
33
|
+
const GEOMETRIC_ARBITRARY = /^-?(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap|gap-x|gap-y|space-x|space-y|w|h|size|min-w|min-h|max-w|max-h|top|bottom|left|right|inset|rounded(?:-[tblr]{1,2})?|border(?:-[tblrxy])?|text|leading)-\[/;
|
|
34
|
+
const ARBITRARY_FUNCTIONAL = /\[(?:var\(|--|inherit|calc\(|[\d.]+(?:%|vh|vw|svh|dvh))/;
|
|
35
|
+
const SHADOW_LG_ALLOWLIST = new Set(["Dialog", "Sheet", "AppDialog"]);
|
|
36
|
+
const ROUNDED_XL_ALLOWLIST = new Set(["Dialog", "Sheet", "AlertDialog"]);
|
|
37
|
+
const HALF_STEP_ALLOWED = /^(?:p|px|py|pt|pb|pl|pr)-(?:0\.5|1\.5|2\.5)$|^gap-(?:0\.5|1\.5)$|^(?:h|w|size|min-w|min-h)-(?:0\.5|1\.5|2\.5|3\.5)$|^translate-[xy]-0\.5$|^(?:top|right|bottom|left)-(?:0\.5|1\.5|2\.5|3\.5)$|^m[trblxy]?-0\.5$/;
|
|
38
|
+
const HALF_STEP = /^-?[a-z-]+-\d+\.5$/;
|
|
39
|
+
const RADIUS_TOKENS = [
|
|
40
|
+
"--radius-sm:",
|
|
41
|
+
"--radius:",
|
|
42
|
+
"--radius-interactive:",
|
|
43
|
+
"--radius-control:",
|
|
44
|
+
"--radius-button:",
|
|
45
|
+
"--radius-button-lg:",
|
|
46
|
+
"--radius-menu-item:",
|
|
47
|
+
"--radius-nav-item:",
|
|
48
|
+
"--radius-md:",
|
|
49
|
+
"--radius-lg:",
|
|
50
|
+
"--radius-xl:",
|
|
51
|
+
"--radius-2xl:",
|
|
52
|
+
"--radius-full:",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const CLASS_STRING = /["'`]([^"'`]*)["'`]/g;
|
|
56
|
+
const tokensOf = (source) => {
|
|
57
|
+
const tokens = [];
|
|
58
|
+
for (const match of source.matchAll(CLASS_STRING)) {
|
|
59
|
+
for (const raw of match[1].split(/\s+/)) if (raw) tokens.push(raw);
|
|
60
|
+
}
|
|
61
|
+
return tokens;
|
|
62
|
+
};
|
|
63
|
+
const baseUtility = (raw) => raw.split(":").pop();
|
|
64
|
+
|
|
65
|
+
const violations = [];
|
|
66
|
+
|
|
67
|
+
const theme = await fs.readFile(themeFile, "utf-8").catch(() => null);
|
|
68
|
+
if (theme === null) {
|
|
69
|
+
violations.push({ file: "src/lexy-theme.css", token: "(archivo)", rule: "theme ausente" });
|
|
70
|
+
} else {
|
|
71
|
+
for (const token of RADIUS_TOKENS) {
|
|
72
|
+
if (!theme.includes(token)) {
|
|
73
|
+
violations.push({ file: "lexy-theme.css", token: token.replace(":", ""), rule: "token de radius ausente en el theme" });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const entries = (await fs.readdir(componentsDir).catch(() => []))
|
|
79
|
+
.filter((f) => f.endsWith(".tsx"))
|
|
80
|
+
.sort();
|
|
81
|
+
|
|
82
|
+
for (const fileName of entries) {
|
|
83
|
+
const name = path.basename(fileName, ".tsx");
|
|
84
|
+
const source = await fs.readFile(path.join(componentsDir, fileName), "utf-8");
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
|
|
87
|
+
for (const raw of tokensOf(source)) {
|
|
88
|
+
const base = baseUtility(raw);
|
|
89
|
+
const key = `${name}:${base}`;
|
|
90
|
+
if (seen.has(key)) continue;
|
|
91
|
+
seen.add(key);
|
|
92
|
+
|
|
93
|
+
if (base.includes("[")) {
|
|
94
|
+
if (!GEOMETRIC_ARBITRARY.test(base)) continue;
|
|
95
|
+
if (!ARBITRARY_FUNCTIONAL.test(base)) {
|
|
96
|
+
violations.push({ file: name, token: base, rule: "arbitrario no funcional → usar escala" });
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (HALF_STEP.test(base.replace(/^-/, "")) && !HALF_STEP_ALLOWED.test(base.replace(/^-/, ""))) {
|
|
101
|
+
violations.push({ file: name, token: base, rule: "medio-paso fuera de control → grilla 4px" });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (base === "shadow-xl") {
|
|
105
|
+
violations.push({ file: name, token: base, rule: "shadow-xl no existe en el contrato → shadow-md" });
|
|
106
|
+
} else if (base === "shadow-lg" && !SHADOW_LG_ALLOWLIST.has(name)) {
|
|
107
|
+
violations.push({ file: name, token: base, rule: "shadow-lg solo overlays modales → shadow-md" });
|
|
108
|
+
}
|
|
109
|
+
if (base === "rounded-xl" && !ROUNDED_XL_ALLOWLIST.has(name)) {
|
|
110
|
+
violations.push({ file: name, token: base, rule: "rounded-xl fuera de contrato → rounded-lg" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (printBox) {
|
|
116
|
+
console.log("── Tabla de caja por componente compuesto ──\n");
|
|
117
|
+
const BOX_TOKEN = /^-?(?:p|px|py|pt|pb|pl|pr|gap|gap-x|gap-y|space-y|space-x|m|mx|my|mt|mb|ml|mr)(?:-|$)/;
|
|
118
|
+
for (const fileName of entries) {
|
|
119
|
+
const name = path.basename(fileName, ".tsx");
|
|
120
|
+
const source = await fs.readFile(path.join(componentsDir, fileName), "utf-8");
|
|
121
|
+
const sections = source.split(/(?=^(?:export\s+)?(?:function|const)\s+[A-Z][A-Za-z0-9]*)/m);
|
|
122
|
+
const rows = [];
|
|
123
|
+
for (const section of sections) {
|
|
124
|
+
const header = section.match(/^(?:export\s+)?(?:function|const)\s+([A-Z][A-Za-z0-9]*)/);
|
|
125
|
+
if (!header) continue;
|
|
126
|
+
const boxTokens = Array.from(new Set(tokensOf(section).map(baseUtility).filter((t) => BOX_TOKEN.test(t))));
|
|
127
|
+
if (boxTokens.length > 0) rows.push(` ${header[1]}: ${boxTokens.join(" ")}`);
|
|
128
|
+
}
|
|
129
|
+
if (rows.length > 0) console.log(` ${name}\n${rows.join("\n")}\n`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (violations.length > 0) {
|
|
134
|
+
console.error(`✗ ${violations.length} violaciones del contrato de geometría:\n`);
|
|
135
|
+
for (const v of violations) console.error(` ${v.file}: ${v.token} — ${v.rule}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`✓ Contrato de geometría: sin violaciones (${entries.length} componente(s) revisados).`);
|
|
139
|
+
}
|
|
Binary file
|