create-bunspace 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +335 -34
- package/dist/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +1616 -40
- package/dist/templates/monorepo/MUST-FOLLOW-GUIDELINES.md +1616 -40
- package/dist/templates/react-starter/MUST-FOLLOW-GUIDELINES.md +1845 -0
- package/dist/templates/react-starter/README.md +100 -0
- package/dist/templates/react-starter/bun.lock +1298 -0
- package/dist/templates/react-starter/components.json +27 -0
- package/dist/templates/react-starter/eslint.config.js +23 -0
- package/dist/templates/react-starter/index.html +36 -0
- package/dist/templates/react-starter/package.json +57 -0
- package/dist/templates/react-starter/public/registry.json +115 -0
- package/dist/templates/react-starter/public/themes/darkmatteviolet-dark.css +34 -0
- package/dist/templates/react-starter/public/themes/darkmatteviolet-light.css +34 -0
- package/dist/templates/react-starter/public/themes/default-dark.css +33 -0
- package/dist/templates/react-starter/public/themes/default-light.css +34 -0
- package/dist/templates/react-starter/public/themes/graphite-dark.css +34 -0
- package/dist/templates/react-starter/public/themes/graphite-light.css +34 -0
- package/dist/templates/react-starter/public/themes/synthwave84-dark.css +34 -0
- package/dist/templates/react-starter/public/themes/synthwave84-light.css +34 -0
- package/dist/templates/react-starter/public/vite.svg +1 -0
- package/dist/templates/react-starter/src/App.tsx +245 -0
- package/dist/templates/react-starter/src/assets/react.svg +1 -0
- package/dist/templates/react-starter/src/components/ThemeSelector.tsx +106 -0
- package/dist/templates/react-starter/src/components/animate-ui/components/buttons/icon.tsx +86 -0
- package/dist/templates/react-starter/src/components/animate-ui/components/buttons/theme-toggler.tsx +92 -0
- package/dist/templates/react-starter/src/components/animate-ui/primitives/animate/slot.tsx +96 -0
- package/dist/templates/react-starter/src/components/animate-ui/primitives/buttons/button.tsx +31 -0
- package/dist/templates/react-starter/src/components/animate-ui/primitives/effects/particles.tsx +155 -0
- package/dist/templates/react-starter/src/components/animate-ui/primitives/effects/theme-toggler.tsx +148 -0
- package/dist/templates/react-starter/src/components/component-example.tsx +444 -0
- package/dist/templates/react-starter/src/components/example.tsx +56 -0
- package/dist/templates/react-starter/src/index.css +131 -0
- package/dist/templates/react-starter/src/main.tsx +13 -0
- package/dist/templates/react-starter/src/providers/ThemeProvider.tsx +27 -0
- package/dist/templates/react-starter/tsconfig.app.json +36 -0
- package/dist/templates/react-starter/tsconfig.json +13 -0
- package/dist/templates/react-starter/tsconfig.node.json +26 -0
- package/dist/templates/react-starter/vite.config.ts +17 -0
- package/dist/templates/telegram-bot/MUST-FOLLOW-GUIDELINES.md +1845 -0
- package/package.json +6 -3
- package/templates/fumadocs/MUST-FOLLOW-GUIDELINES.md +1616 -40
- package/templates/monorepo/MUST-FOLLOW-GUIDELINES.md +1616 -40
- package/templates/react-starter/MUST-FOLLOW-GUIDELINES.md +1845 -0
- package/templates/react-starter/README.md +100 -0
- package/templates/react-starter/bun.lock +1298 -0
- package/templates/react-starter/components.json +27 -0
- package/templates/react-starter/eslint.config.js +23 -0
- package/templates/react-starter/index.html +36 -0
- package/templates/react-starter/package.json +57 -0
- package/templates/react-starter/public/registry.json +115 -0
- package/templates/react-starter/public/themes/darkmatteviolet-dark.css +34 -0
- package/templates/react-starter/public/themes/darkmatteviolet-light.css +34 -0
- package/templates/react-starter/public/themes/default-dark.css +33 -0
- package/templates/react-starter/public/themes/default-light.css +34 -0
- package/templates/react-starter/public/themes/graphite-dark.css +34 -0
- package/templates/react-starter/public/themes/graphite-light.css +34 -0
- package/templates/react-starter/public/themes/synthwave84-dark.css +34 -0
- package/templates/react-starter/public/themes/synthwave84-light.css +34 -0
- package/templates/react-starter/public/vite.svg +1 -0
- package/templates/react-starter/src/App.tsx +245 -0
- package/templates/react-starter/src/assets/react.svg +1 -0
- package/templates/react-starter/src/components/ThemeSelector.tsx +106 -0
- package/templates/react-starter/src/components/animate-ui/components/buttons/icon.tsx +86 -0
- package/templates/react-starter/src/components/animate-ui/components/buttons/theme-toggler.tsx +92 -0
- package/templates/react-starter/src/components/animate-ui/primitives/animate/slot.tsx +96 -0
- package/templates/react-starter/src/components/animate-ui/primitives/buttons/button.tsx +31 -0
- package/templates/react-starter/src/components/animate-ui/primitives/effects/particles.tsx +155 -0
- package/templates/react-starter/src/components/animate-ui/primitives/effects/theme-toggler.tsx +148 -0
- package/templates/react-starter/src/components/component-example.tsx +444 -0
- package/templates/react-starter/src/components/example.tsx +56 -0
- package/templates/react-starter/src/index.css +131 -0
- package/templates/react-starter/src/main.tsx +13 -0
- package/templates/react-starter/src/providers/ThemeProvider.tsx +27 -0
- package/templates/react-starter/tsconfig.app.json +36 -0
- package/templates/react-starter/tsconfig.json +13 -0
- package/templates/react-starter/tsconfig.node.json +26 -0
- package/templates/react-starter/vite.config.ts +17 -0
- package/templates/telegram-bot/MUST-FOLLOW-GUIDELINES.md +1845 -0
|
@@ -18,6 +18,91 @@
|
|
|
18
18
|
| **Versioning** | Changesets v2.27.11 | Versionado de packages |
|
|
19
19
|
| **Logging** | @mks2508/better-logger v4.0.0 | Logging estructurado |
|
|
20
20
|
| **Error Handling** | @mks2508/no-throw v0.1.0 | Result pattern |
|
|
21
|
+
| **Commit Generation** | gemini-commit-wizard v1.1.3 | Commits estructurados con IA multi-provider |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Commit Workflow — gemini-commit-wizard
|
|
26
|
+
|
|
27
|
+
**OBLIGATORIO**: Usar `bun run commit` para TODOS los commits. NUNCA hacer commits manuales sin formato estructurado.
|
|
28
|
+
|
|
29
|
+
### Providers Disponibles
|
|
30
|
+
|
|
31
|
+
| Provider | Variable de Entorno | Modelo Default | Velocidad |
|
|
32
|
+
|----------|-------------------|---------------|-----------|
|
|
33
|
+
| **Gemini SDK** | `GEMINI_API_KEY` | `gemini-2.5-flash` | Rapido |
|
|
34
|
+
| **Groq** | `GROQ_API_KEY` | `llama-3.3-70b-versatile` | Ultra-rapido |
|
|
35
|
+
| **OpenRouter** | `OPENROUTER_API_KEY` | `anthropic/claude-sonnet-4` | Variable |
|
|
36
|
+
| **Gemini CLI** | _(necesita binario `gemini`)_ | CLI default | Moderado |
|
|
37
|
+
|
|
38
|
+
**Auto-deteccion**: Gemini SDK > Groq > OpenRouter > Gemini CLI. El primer provider con API key valida se usa automaticamente.
|
|
39
|
+
|
|
40
|
+
### Comandos
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Commit interactivo con IA (auto-detecta provider)
|
|
44
|
+
bun run commit
|
|
45
|
+
|
|
46
|
+
# Modo rapido sin prompts
|
|
47
|
+
bun run commit:quick
|
|
48
|
+
|
|
49
|
+
# Auto-aprobar sin push
|
|
50
|
+
bun run commit:auto
|
|
51
|
+
|
|
52
|
+
# Provider especifico
|
|
53
|
+
bun run commit -- --provider groq
|
|
54
|
+
|
|
55
|
+
# Provider + modelo especifico
|
|
56
|
+
bun run commit -- --provider openrouter --model anthropic/claude-sonnet-4
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Formato de Commit Estructurado
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
type(scope): description
|
|
63
|
+
|
|
64
|
+
Body text (en idioma configurado)
|
|
65
|
+
|
|
66
|
+
<technical>
|
|
67
|
+
- Detalles tecnicos: archivos, funciones, tipos modificados
|
|
68
|
+
</technical>
|
|
69
|
+
|
|
70
|
+
<changelog>
|
|
71
|
+
## [Type] [Emoji]
|
|
72
|
+
Entrada de changelog orientada al usuario
|
|
73
|
+
</changelog>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Types validos**: `feat`, `fix`, `refactor`, `docs`, `test`, `feat-phase` (feature incompleta)
|
|
77
|
+
|
|
78
|
+
### Configuracion por Proyecto
|
|
79
|
+
|
|
80
|
+
Crear `.commit-wizard.json` en la raiz del proyecto:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"name": "mi-proyecto",
|
|
85
|
+
"description": "Descripcion breve",
|
|
86
|
+
"techStack": ["TypeScript", "Bun"],
|
|
87
|
+
"components": [
|
|
88
|
+
{ "id": "api", "path": "src/api/", "name": "REST API" }
|
|
89
|
+
],
|
|
90
|
+
"commitFormat": {
|
|
91
|
+
"titleLanguage": "english",
|
|
92
|
+
"bodyLanguage": "spanish",
|
|
93
|
+
"includeTechnical": true,
|
|
94
|
+
"includeChangelog": true
|
|
95
|
+
},
|
|
96
|
+
"provider": "groq",
|
|
97
|
+
"model": "llama-3.3-70b-versatile"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Prohibido
|
|
102
|
+
|
|
103
|
+
- `git commit -m "mensaje"` sin formato estructurado
|
|
104
|
+
- Commits sin `<technical>` y `<changelog>` sections
|
|
105
|
+
- Usar providers sin API key configurada (el wizard valida automaticamente)
|
|
21
106
|
|
|
22
107
|
---
|
|
23
108
|
|
|
@@ -25,7 +110,7 @@
|
|
|
25
110
|
|
|
26
111
|
### Root del Monorepo
|
|
27
112
|
```
|
|
28
|
-
mks-
|
|
113
|
+
mks-dev-environment/
|
|
29
114
|
├── docs/ # Documentacion del proyecto
|
|
30
115
|
├── tools/ # Scripts y herramientas de desarrollo
|
|
31
116
|
├── core/
|
|
@@ -105,29 +190,248 @@ export async function myFunction(
|
|
|
105
190
|
|
|
106
191
|
## REGLA 2: Logging - NUNCA console.log
|
|
107
192
|
|
|
108
|
-
|
|
193
|
+
Usar `@mks2508/better-logger` para todo el logging.
|
|
194
|
+
|
|
195
|
+
### Imports y Setup Basico
|
|
109
196
|
|
|
110
197
|
```typescript
|
|
111
|
-
|
|
198
|
+
// Singleton (recomendado para la mayoria de casos)
|
|
199
|
+
import logger from '@mks2508/better-logger';
|
|
112
200
|
|
|
113
|
-
|
|
201
|
+
// O crear instancia personalizada
|
|
202
|
+
import { Logger } from '@mks2508/better-logger';
|
|
203
|
+
const log = new Logger({
|
|
204
|
+
verbosity: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
205
|
+
enableStackTrace: true, // Muestra archivo:linea
|
|
206
|
+
bufferSize: 1000, // Para exportacion de logs
|
|
207
|
+
});
|
|
208
|
+
```
|
|
114
209
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
210
|
+
### Metodos de Logging
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Niveles basicos
|
|
214
|
+
logger.debug('Debug message', { context });
|
|
215
|
+
logger.info('Info message');
|
|
216
|
+
logger.warn('Warning message');
|
|
217
|
+
logger.error('Error message', errorObject);
|
|
218
|
+
logger.success('Operation completed');
|
|
219
|
+
logger.critical('System failure!');
|
|
220
|
+
logger.trace('Trace from nested function');
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Scoped Loggers (USAR SIEMPRE en servicios)
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// ComponentLogger - Para componentes UI y servicios
|
|
227
|
+
const authLog = logger.component('AuthService');
|
|
228
|
+
authLog.info('Usuario autenticando...'); // [COMPONENT] [AuthService] Usuario...
|
|
229
|
+
authLog.success('Login exitoso');
|
|
230
|
+
authLog.lifecycle('mount', 'Component mounted');
|
|
231
|
+
authLog.stateChange('idle', 'loading');
|
|
232
|
+
|
|
233
|
+
// APILogger - Para endpoints y llamadas HTTP
|
|
234
|
+
const apiLog = logger.api('UserAPI');
|
|
235
|
+
apiLog.info('GET /users'); // [API] [UserAPI] GET /users
|
|
236
|
+
apiLog.slow('Response slow', 2500); // [API] [SLOW] Response slow (2500ms)
|
|
237
|
+
apiLog.rateLimit('Too many requests'); // [API] [RATE_LIMIT]
|
|
238
|
+
apiLog.deprecated('Use /v2/users instead');
|
|
239
|
+
|
|
240
|
+
// ScopedLogger - Generico con contextos anidados
|
|
241
|
+
const dbLog = logger.scope('Database');
|
|
242
|
+
dbLog.info('Query executing');
|
|
243
|
+
dbLog.context('transactions').run(() => {
|
|
244
|
+
dbLog.info('Inside transaction'); // [Database:transactions] Inside...
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Timing y Performance
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// Medir operaciones
|
|
252
|
+
logger.time('db-query');
|
|
253
|
+
await db.query('SELECT * FROM users');
|
|
254
|
+
logger.timeEnd('db-query'); // Timer: db-query - 234.56ms
|
|
255
|
+
|
|
256
|
+
// En scoped loggers
|
|
257
|
+
const serviceLog = logger.component('ProductService');
|
|
258
|
+
serviceLog.time('fetch-products');
|
|
259
|
+
const products = await fetchProducts();
|
|
260
|
+
serviceLog.timeEnd('fetch-products');
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Badges para Contexto
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Badges encadenables
|
|
267
|
+
logger.badges(['CACHE', 'HIT']).info('Data from cache');
|
|
268
|
+
logger.badge('v2').badge('stable').info('API response');
|
|
269
|
+
|
|
270
|
+
// En scoped loggers
|
|
271
|
+
const api = logger.api('GraphQL');
|
|
272
|
+
api.badges(['mutation', 'user']).info('createUser executed');
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Transports (Envio de Logs)
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import logger, {
|
|
279
|
+
FileTransport,
|
|
280
|
+
HttpTransport,
|
|
281
|
+
addTransport
|
|
282
|
+
} from '@mks2508/better-logger';
|
|
283
|
+
|
|
284
|
+
// Transport a archivo (solo Node.js/Bun)
|
|
285
|
+
logger.addTransport({
|
|
286
|
+
target: 'file',
|
|
287
|
+
options: {
|
|
288
|
+
destination: '/var/log/app.log',
|
|
289
|
+
batchSize: 100,
|
|
290
|
+
flushInterval: 5000 // ms
|
|
291
|
+
},
|
|
292
|
+
level: 'warn' // Solo warn+ van al archivo
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Transport HTTP (envia a servidor de logs)
|
|
296
|
+
logger.addTransport({
|
|
297
|
+
target: 'http',
|
|
298
|
+
options: {
|
|
299
|
+
url: 'https://logs.example.com/ingest',
|
|
300
|
+
headers: { 'Authorization': 'Bearer xxx' },
|
|
301
|
+
batchSize: 50,
|
|
302
|
+
flushInterval: 10000
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Flush manual antes de cerrar
|
|
307
|
+
await logger.flushTransports();
|
|
308
|
+
await logger.closeTransports();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Hooks y Middleware
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Hook beforeLog - agregar metadata
|
|
315
|
+
logger.on('beforeLog', (entry) => {
|
|
316
|
+
entry.correlationId = getCorrelationId();
|
|
317
|
+
entry.userId = getCurrentUserId();
|
|
318
|
+
return entry;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Hook afterLog - side effects
|
|
322
|
+
logger.on('afterLog', (entry) => {
|
|
323
|
+
if (entry.level === 'error') {
|
|
324
|
+
sendToErrorTracking(entry);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Middleware - pipeline de procesamiento
|
|
329
|
+
logger.use((entry, next) => {
|
|
330
|
+
// Enriquecer con request context
|
|
331
|
+
const store = asyncLocalStorage.getStore();
|
|
332
|
+
if (store?.requestId) {
|
|
333
|
+
entry.requestId = store.requestId;
|
|
334
|
+
}
|
|
335
|
+
next();
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Serializers Personalizados
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// Serializar errores de forma estructurada
|
|
343
|
+
logger.addSerializer(Error, (err) => ({
|
|
344
|
+
name: err.name,
|
|
345
|
+
message: err.message,
|
|
346
|
+
stack: err.stack?.split('\n').slice(0, 5),
|
|
347
|
+
code: (err as any).code
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
// Serializar objetos custom
|
|
351
|
+
logger.addSerializer(User, (user) => ({
|
|
352
|
+
id: user.id,
|
|
353
|
+
email: '[REDACTED]',
|
|
354
|
+
role: user.role
|
|
355
|
+
}));
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Configuracion Frontend vs Backend
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// === BACKEND (Node.js/Bun) ===
|
|
362
|
+
import logger from '@mks2508/better-logger';
|
|
363
|
+
|
|
364
|
+
// Preset optimizado para terminal
|
|
365
|
+
logger.preset('cyberpunk');
|
|
366
|
+
logger.showTimestamp();
|
|
367
|
+
logger.showLocation();
|
|
368
|
+
|
|
369
|
+
// Transport a archivo
|
|
370
|
+
logger.addTransport({
|
|
371
|
+
target: 'file',
|
|
372
|
+
options: { destination: './logs/app.log' }
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// === FRONTEND (Browser) ===
|
|
376
|
+
import logger from '@mks2508/better-logger';
|
|
377
|
+
|
|
378
|
+
// Preset con colores CSS
|
|
379
|
+
logger.preset('default');
|
|
380
|
+
logger.hideLocation(); // No util en browser
|
|
381
|
+
|
|
382
|
+
// Transport HTTP para enviar errores
|
|
383
|
+
logger.addTransport({
|
|
384
|
+
target: 'http',
|
|
385
|
+
options: { url: '/api/logs' },
|
|
386
|
+
level: 'error' // Solo errores al servidor
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Verbosity y Filtrado
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Cambiar nivel de verbosidad
|
|
394
|
+
logger.setVerbosity('warn'); // Solo warn, error, critical
|
|
395
|
+
logger.setVerbosity('silent'); // Desactiva todo
|
|
396
|
+
logger.setVerbosity('debug'); // Muestra todo
|
|
397
|
+
|
|
398
|
+
// Configuracion condicional
|
|
399
|
+
if (process.env.NODE_ENV === 'production') {
|
|
400
|
+
logger.setVerbosity('warn');
|
|
401
|
+
logger.hideLocation();
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Utilidades de Grupos y Tablas
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Tablas de datos
|
|
409
|
+
logger.table([
|
|
410
|
+
{ name: 'Alice', age: 30 },
|
|
411
|
+
{ name: 'Bob', age: 25 }
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
// Grupos colapsables
|
|
415
|
+
logger.group('Database Operations');
|
|
416
|
+
logger.info('Connecting...');
|
|
417
|
+
logger.info('Querying...');
|
|
418
|
+
logger.groupEnd();
|
|
419
|
+
|
|
420
|
+
// Grupo colapsado por defecto
|
|
421
|
+
logger.group('Debug Details', true);
|
|
422
|
+
logger.debug('Verbose info here');
|
|
423
|
+
logger.groupEnd();
|
|
121
424
|
```
|
|
122
425
|
|
|
123
426
|
### Prohibido
|
|
124
427
|
|
|
125
428
|
```typescript
|
|
126
|
-
// INCORRECTO
|
|
429
|
+
// INCORRECTO - NUNCA usar
|
|
127
430
|
console.log('Started');
|
|
128
431
|
console.error('Failed');
|
|
129
432
|
console.info('Info');
|
|
130
433
|
console.warn('Warning');
|
|
434
|
+
console.debug('Debug');
|
|
131
435
|
```
|
|
132
436
|
|
|
133
437
|
---
|
|
@@ -136,43 +440,212 @@ console.warn('Warning');
|
|
|
136
440
|
|
|
137
441
|
### Obligatorio
|
|
138
442
|
|
|
139
|
-
TODA operacion que pueda fallar DEBE usar `Result<T, E>` del package
|
|
443
|
+
TODA operacion que pueda fallar DEBE usar `Result<T, E>` del package `@mks2508/no-throw`:
|
|
140
444
|
|
|
141
445
|
```typescript
|
|
142
446
|
import {
|
|
143
|
-
ok,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
447
|
+
ok, err, fail,
|
|
448
|
+
isOk, isErr,
|
|
449
|
+
map, mapErr, flatMap,
|
|
450
|
+
match,
|
|
451
|
+
tryCatch, tryCatchAsync, fromPromise,
|
|
452
|
+
unwrap, unwrapOr, unwrapOrElse,
|
|
453
|
+
tap, tapErr,
|
|
454
|
+
collect, all,
|
|
455
|
+
type Result, type ResultError
|
|
456
|
+
} from '@mks2508/no-throw';
|
|
457
|
+
```
|
|
151
458
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
459
|
+
### Constructores
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Crear resultado exitoso
|
|
463
|
+
const success = ok(42); // Result<number, never>
|
|
464
|
+
const success2 = ok({ name: 'John' }); // Result<{name: string}, never>
|
|
465
|
+
|
|
466
|
+
// Crear resultado de error
|
|
467
|
+
const error = err('Something failed'); // Result<never, string>
|
|
468
|
+
|
|
469
|
+
// Crear error estructurado con fail()
|
|
470
|
+
const structuredError = fail(
|
|
471
|
+
'NETWORK_ERROR', // code
|
|
472
|
+
'Failed to fetch data', // message
|
|
473
|
+
originalError // cause (opcional)
|
|
474
|
+
);
|
|
475
|
+
// Retorna: Result<never, ResultError<'NETWORK_ERROR'>>
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Type Guards
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
const result = await fetchData(url);
|
|
482
|
+
|
|
483
|
+
if (isOk(result)) {
|
|
484
|
+
console.log(result.value); // Tipo: T
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (isErr(result)) {
|
|
488
|
+
console.log(result.error); // Tipo: E
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Transformaciones
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// map - transforma el valor si es Ok
|
|
496
|
+
const doubled = map(result, (n) => n * 2);
|
|
497
|
+
|
|
498
|
+
// mapErr - transforma el error si es Err
|
|
499
|
+
const mappedErr = mapErr(result, (e) => ({ ...e, timestamp: Date.now() }));
|
|
500
|
+
|
|
501
|
+
// flatMap - encadena operaciones que retornan Result
|
|
502
|
+
const chained = flatMap(result, (value) => {
|
|
503
|
+
if (value > 100) return err('Too large');
|
|
504
|
+
return ok(value * 2);
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Pattern Matching
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const message = match(result, {
|
|
512
|
+
ok: (value) => `Success: ${value}`,
|
|
513
|
+
err: (error) => `Error: ${error.message}`
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Manejo de Excepciones
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
// tryCatch - para operaciones sincronas
|
|
521
|
+
const syncResult = tryCatch(
|
|
522
|
+
() => JSON.parse(jsonString),
|
|
523
|
+
'PARSE_ERROR'
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// tryCatchAsync - para operaciones async
|
|
527
|
+
const asyncResult = await tryCatchAsync(
|
|
528
|
+
async () => {
|
|
529
|
+
const response = await fetch(url);
|
|
530
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
531
|
+
return response.json();
|
|
532
|
+
},
|
|
533
|
+
'NETWORK_ERROR'
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// fromPromise - convierte Promise a Result
|
|
537
|
+
const promiseResult = await fromPromise(
|
|
538
|
+
fetch(url).then(r => r.json()),
|
|
539
|
+
'FETCH_ERROR'
|
|
540
|
+
);
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Unwrap
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
// unwrap - obtiene valor o lanza error
|
|
547
|
+
const value = unwrap(result); // Throws si es Err
|
|
548
|
+
|
|
549
|
+
// unwrapOr - valor por defecto
|
|
550
|
+
const valueOrDefault = unwrapOr(result, 0);
|
|
551
|
+
|
|
552
|
+
// unwrapOrElse - valor calculado
|
|
553
|
+
const valueOrComputed = unwrapOrElse(result, (error) => {
|
|
554
|
+
log.error('Using fallback due to:', error);
|
|
555
|
+
return defaultValue;
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Efectos Secundarios
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// tap - ejecuta efecto si es Ok (no modifica resultado)
|
|
563
|
+
const logged = tap(result, (value) => {
|
|
564
|
+
log.info('Got value:', value);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// tapErr - ejecuta efecto si es Err
|
|
568
|
+
const errorLogged = tapErr(result, (error) => {
|
|
569
|
+
log.error('Operation failed:', error);
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Colecciones
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// collect - convierte array de Results en Result de array
|
|
577
|
+
const results: Result<number, string>[] = [ok(1), ok(2), ok(3)];
|
|
578
|
+
const collected = collect(results); // Result<number[], string>
|
|
579
|
+
|
|
580
|
+
// all - igual que collect (alias)
|
|
581
|
+
const allResults = all([ok(1), ok(2), ok(3)]);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Ejemplo Completo
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
import { ok, fail, isErr, tryCatchAsync, match } from '@mks2508/no-throw';
|
|
588
|
+
import logger from '@mks2508/better-logger';
|
|
589
|
+
|
|
590
|
+
const log = logger.component('UserService');
|
|
591
|
+
|
|
592
|
+
async function fetchUser(
|
|
593
|
+
id: string
|
|
594
|
+
): Promise<Result<IUser, ResultError<'NOT_FOUND' | 'NETWORK_ERROR'>>> {
|
|
595
|
+
const result = await tryCatchAsync(
|
|
156
596
|
async () => {
|
|
157
|
-
const response = await fetch(
|
|
597
|
+
const response = await fetch(`/api/users/${id}`);
|
|
598
|
+
if (response.status === 404) {
|
|
599
|
+
throw { code: 'NOT_FOUND' };
|
|
600
|
+
}
|
|
158
601
|
if (!response.ok) {
|
|
159
602
|
throw new Error(`HTTP ${response.status}`);
|
|
160
603
|
}
|
|
161
|
-
return await response.
|
|
604
|
+
return await response.json();
|
|
162
605
|
},
|
|
163
|
-
|
|
606
|
+
'NETWORK_ERROR'
|
|
164
607
|
);
|
|
165
608
|
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
609
|
+
if (isErr(result)) {
|
|
610
|
+
const error = result.error;
|
|
611
|
+
if (error.cause?.code === 'NOT_FOUND') {
|
|
612
|
+
return fail('NOT_FOUND', `User ${id} not found`);
|
|
613
|
+
}
|
|
614
|
+
return fail('NETWORK_ERROR', `Failed to fetch user ${id}`, error);
|
|
172
615
|
}
|
|
173
616
|
|
|
174
617
|
return ok(result.value);
|
|
175
618
|
}
|
|
619
|
+
|
|
620
|
+
// Uso
|
|
621
|
+
const userResult = await fetchUser('123');
|
|
622
|
+
const message = match(userResult, {
|
|
623
|
+
ok: (user) => {
|
|
624
|
+
log.success(`User loaded: ${user.name}`);
|
|
625
|
+
return user;
|
|
626
|
+
},
|
|
627
|
+
err: (error) => {
|
|
628
|
+
log.error(`Failed: ${error.message}`);
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Prohibido
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// INCORRECTO - NUNCA usar try/catch directo sin Result
|
|
638
|
+
try {
|
|
639
|
+
const data = await fetchData();
|
|
640
|
+
} catch (e) {
|
|
641
|
+
console.error(e);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// INCORRECTO - NUNCA lanzar excepciones
|
|
645
|
+
throw new Error('Something failed');
|
|
646
|
+
|
|
647
|
+
// CORRECTO - Siempre retornar Result
|
|
648
|
+
return fail('ERROR_CODE', 'Description', cause);
|
|
176
649
|
```
|
|
177
650
|
|
|
178
651
|
---
|
|
@@ -259,11 +732,1114 @@ Antes de hacer commit de codigo, verificar:
|
|
|
259
732
|
|
|
260
733
|
---
|
|
261
734
|
|
|
262
|
-
##
|
|
735
|
+
## REGLA 7: TransactionState - Estados de UI
|
|
263
736
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
737
|
+
### Estados Soportados
|
|
738
|
+
|
|
739
|
+
| Estado | Uso |
|
|
740
|
+
|--------|-----|
|
|
741
|
+
| `initial` | Estado inicial, sin datos |
|
|
742
|
+
| `loading` | Primera carga en progreso |
|
|
743
|
+
| `revalidating` | Recargando con datos previos |
|
|
744
|
+
| `success` | Operacion completada con exito |
|
|
745
|
+
| `failed` | Error en la operacion |
|
|
746
|
+
|
|
747
|
+
### Factories y Guards
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
// Factories
|
|
751
|
+
createInitialState()
|
|
752
|
+
createLoadingState(message?: string)
|
|
753
|
+
createRevalidatingState(previousData: T)
|
|
754
|
+
createSuccessState(data: T, message?: string)
|
|
755
|
+
createFailedState(error: E)
|
|
756
|
+
|
|
757
|
+
// Guards
|
|
758
|
+
isInitial(state) // true si initial
|
|
759
|
+
isLoading(state) // true si loading
|
|
760
|
+
isRevalidating(state)
|
|
761
|
+
isSuccess(state)
|
|
762
|
+
isFailed(state)
|
|
763
|
+
isPending(state) // loading OR revalidating
|
|
764
|
+
isCompleted(state) // success OR failed
|
|
765
|
+
hasData(state) // success OR revalidating
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Patron en Hooks
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
const [state, setState] = useState<TransactionState<T, E>>(createInitialState);
|
|
772
|
+
|
|
773
|
+
const load = useCallback(async () => {
|
|
774
|
+
setState(createLoadingState('Cargando...'));
|
|
775
|
+
const result = await service.fetch();
|
|
776
|
+
if (isSuccess(result)) {
|
|
777
|
+
setState(createSuccessState(result.data));
|
|
778
|
+
} else {
|
|
779
|
+
setState(createFailedState(result.error));
|
|
780
|
+
}
|
|
781
|
+
}, []);
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## REGLA 8: Arquitectura BLO (Business Logic Layer)
|
|
787
|
+
|
|
788
|
+
### Principios Fundamentales
|
|
789
|
+
|
|
790
|
+
| Principio | Descripcion |
|
|
791
|
+
|-----------|-------------|
|
|
792
|
+
| **Separacion clara** | UI y logica de negocio en capas distintas |
|
|
793
|
+
| **Handlers** | Clases puras TypeScript sin dependencias React |
|
|
794
|
+
| **Hooks** | Puente reactivo entre handlers y componentes |
|
|
795
|
+
| **Componentes** | Solo UI y eventos, sin logica de negocio |
|
|
796
|
+
|
|
797
|
+
### Clasificacion de Componentes
|
|
798
|
+
|
|
799
|
+
#### Componentes UI Reutilizables (`components/ui/`)
|
|
800
|
+
- **Alta reusabilidad** en diferentes contextos
|
|
801
|
+
- **Baja complejidad** y focalizacion especifica
|
|
802
|
+
- **Estructura simplificada** (archivo unico o pocos archivos)
|
|
803
|
+
- Ejemplos: Button, Input, Card, Modal
|
|
804
|
+
|
|
805
|
+
#### Componentes de Dominio (`components/`)
|
|
806
|
+
- **Alta complejidad** y logica de negocio especifica
|
|
807
|
+
- **Estructura completa** con handler y hook
|
|
808
|
+
- **Subcomponentes** si es necesario
|
|
809
|
+
- Ejemplos: ProductCard, UserProfile, OrderDetails
|
|
810
|
+
|
|
811
|
+
### Estructura Completa (Componentes Complejos)
|
|
812
|
+
|
|
813
|
+
```
|
|
814
|
+
components/
|
|
815
|
+
└── ProductCard/
|
|
816
|
+
├── index.tsx # Componente React puro
|
|
817
|
+
├── ProductCard.types.ts # Interfaces y tipos
|
|
818
|
+
├── ProductCard.styles.ts # Clases Tailwind con CVA
|
|
819
|
+
├── ProductCard.handler.ts # Logica de negocio (BLO)
|
|
820
|
+
├── ProductCard.hook.ts # Hook React con estado
|
|
821
|
+
└── components/ # Subcomponentes (opcional)
|
|
822
|
+
├── ProductImage/
|
|
823
|
+
└── ProductActions/
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Componente React (`index.tsx`)
|
|
827
|
+
|
|
828
|
+
```typescript
|
|
829
|
+
import React from 'react';
|
|
830
|
+
import { IProductCardProps } from './ProductCard.types';
|
|
831
|
+
import { styles } from './ProductCard.styles';
|
|
832
|
+
import { useProductCard } from './ProductCard.hook';
|
|
833
|
+
|
|
834
|
+
export const ProductCard: React.FC<IProductCardProps> = (props) => {
|
|
835
|
+
const { state, actions } = useProductCard(props);
|
|
836
|
+
|
|
837
|
+
return (
|
|
838
|
+
<div className={styles.container}>
|
|
839
|
+
{/* Solo UI y eventos - sin logica de negocio */}
|
|
840
|
+
<h3 className={styles.title}>{state.data?.name}</h3>
|
|
841
|
+
<button onClick={actions.addToCart}>Agregar</button>
|
|
842
|
+
</div>
|
|
843
|
+
);
|
|
844
|
+
};
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Reglas del componente:**
|
|
848
|
+
- ✅ Solo UI: renderizado y eventos
|
|
849
|
+
- ✅ Props inmutables: no modificar props directamente
|
|
850
|
+
- ✅ Estado delegado: usar hook para estado y acciones
|
|
851
|
+
- ❌ Sin logica: no business logic en el componente
|
|
852
|
+
|
|
853
|
+
### Tipos TypeScript (`.types.ts`)
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
export interface IProductCardProps {
|
|
857
|
+
productId: string;
|
|
858
|
+
initialData?: IProduct;
|
|
859
|
+
autoLoad?: boolean;
|
|
860
|
+
className?: string;
|
|
861
|
+
onAddToCart?: (id: string) => void;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export interface IProductCardState {
|
|
865
|
+
isLoading: boolean;
|
|
866
|
+
data: IProduct | null;
|
|
867
|
+
error: string | null;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export interface IProductCardActions {
|
|
871
|
+
loadProduct: () => Promise<void>;
|
|
872
|
+
addToCart: () => void;
|
|
873
|
+
reset: () => void;
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### Estilos Tailwind con CVA (`.styles.ts`)
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
import { cva } from 'class-variance-authority';
|
|
881
|
+
|
|
882
|
+
export const containerVariants = cva(
|
|
883
|
+
"w-full p-4 bg-white border rounded-lg transition-shadow",
|
|
884
|
+
{
|
|
885
|
+
variants: {
|
|
886
|
+
variant: {
|
|
887
|
+
default: "border-gray-200 hover:shadow-md",
|
|
888
|
+
featured: "border-blue-200 bg-blue-50 hover:shadow-lg",
|
|
889
|
+
compact: "p-2 border-gray-100",
|
|
890
|
+
},
|
|
891
|
+
size: {
|
|
892
|
+
sm: "max-w-xs",
|
|
893
|
+
md: "max-w-sm",
|
|
894
|
+
lg: "max-w-md",
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
defaultVariants: {
|
|
898
|
+
variant: "default",
|
|
899
|
+
size: "md",
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
export const styles = {
|
|
905
|
+
container: containerVariants,
|
|
906
|
+
header: "flex items-center justify-between mb-4",
|
|
907
|
+
title: "text-lg font-semibold text-gray-900",
|
|
908
|
+
content: "text-gray-600 min-h-[100px]",
|
|
909
|
+
actions: "flex gap-2 mt-4 pt-4 border-t border-gray-200",
|
|
910
|
+
loading: "flex items-center justify-center py-8 text-gray-500",
|
|
911
|
+
error: "flex items-center justify-center py-8 text-red-500 bg-red-50 rounded",
|
|
912
|
+
};
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### Handler BLO (`.handler.ts`)
|
|
916
|
+
|
|
917
|
+
```typescript
|
|
918
|
+
import { IProductCardState } from './ProductCard.types';
|
|
919
|
+
|
|
920
|
+
export class ProductCardHandler {
|
|
921
|
+
private state: IProductCardState = {
|
|
922
|
+
isLoading: false,
|
|
923
|
+
data: null,
|
|
924
|
+
error: null,
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
constructor(initialData?: IProduct) {
|
|
928
|
+
if (initialData) {
|
|
929
|
+
this.state.data = initialData;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
getState(): IProductCardState {
|
|
934
|
+
return { ...this.state }; // Siempre retornar copia
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async loadProduct(productId: string): Promise<void> {
|
|
938
|
+
this.state.isLoading = true;
|
|
939
|
+
this.state.error = null;
|
|
940
|
+
|
|
941
|
+
try {
|
|
942
|
+
// Logica de negocio pura - sin React
|
|
943
|
+
const response = await fetch(`/api/products/${productId}`);
|
|
944
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
945
|
+
this.state.data = await response.json();
|
|
946
|
+
} catch (error) {
|
|
947
|
+
this.state.error = error instanceof Error ? error.message : 'Error';
|
|
948
|
+
} finally {
|
|
949
|
+
this.state.isLoading = false;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
reset(): void {
|
|
954
|
+
this.state = { isLoading: false, data: null, error: null };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
**Reglas del handler:**
|
|
960
|
+
- ✅ Clase pura TypeScript: sin dependencias de React
|
|
961
|
+
- ✅ Estado inmutable: siempre retornar copias
|
|
962
|
+
- ✅ Logica de negocio: validaciones, transformaciones, API calls
|
|
963
|
+
- ✅ Testing facil: sin dependencias externas
|
|
964
|
+
- ❌ Sin React: no hooks, no JSX, no estado React
|
|
965
|
+
|
|
966
|
+
### Hook React (`.hook.ts`)
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
970
|
+
import { IProductCardProps, IProductCardState, IProductCardActions } from './ProductCard.types';
|
|
971
|
+
import { ProductCardHandler } from './ProductCard.handler';
|
|
972
|
+
|
|
973
|
+
export const useProductCard = (props: IProductCardProps) => {
|
|
974
|
+
const [handler] = useState(() => new ProductCardHandler(props.initialData));
|
|
975
|
+
const [state, setState] = useState<IProductCardState>(handler.getState());
|
|
976
|
+
|
|
977
|
+
const loadProduct = useCallback(async () => {
|
|
978
|
+
await handler.loadProduct(props.productId);
|
|
979
|
+
setState(handler.getState());
|
|
980
|
+
}, [handler, props.productId]);
|
|
981
|
+
|
|
982
|
+
const addToCart = useCallback(() => {
|
|
983
|
+
if (state.data && props.onAddToCart) {
|
|
984
|
+
props.onAddToCart(state.data.id);
|
|
985
|
+
}
|
|
986
|
+
}, [state.data, props.onAddToCart]);
|
|
987
|
+
|
|
988
|
+
const reset = useCallback(() => {
|
|
989
|
+
handler.reset();
|
|
990
|
+
setState(handler.getState());
|
|
991
|
+
}, [handler]);
|
|
992
|
+
|
|
993
|
+
useEffect(() => {
|
|
994
|
+
if (props.autoLoad) {
|
|
995
|
+
loadProduct();
|
|
996
|
+
}
|
|
997
|
+
}, [props.autoLoad, loadProduct]);
|
|
998
|
+
|
|
999
|
+
return {
|
|
1000
|
+
state,
|
|
1001
|
+
actions: { loadProduct, addToCart, reset } as IProductCardActions,
|
|
1002
|
+
};
|
|
1003
|
+
};
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
**Reglas del hook:**
|
|
1007
|
+
- ✅ Puente reactivo: entre handler y componente
|
|
1008
|
+
- ✅ Estado React: sincronizado con handler
|
|
1009
|
+
- ✅ Callbacks memorizados: useCallback para optimizacion
|
|
1010
|
+
- ✅ Efectos controlados: auto-load, dependencies
|
|
1011
|
+
- ✅ Interface consistente: siempre retorna `{ state, actions }`
|
|
1012
|
+
|
|
1013
|
+
### Nombres de Archivos
|
|
1014
|
+
|
|
1015
|
+
```typescript
|
|
1016
|
+
// CORRECTO - PascalCase
|
|
1017
|
+
ProductCard.tsx
|
|
1018
|
+
UserProfile.tsx
|
|
1019
|
+
DataTable.tsx
|
|
1020
|
+
|
|
1021
|
+
// INCORRECTO
|
|
1022
|
+
my-component.tsx
|
|
1023
|
+
user_profile.tsx
|
|
1024
|
+
dataTable.tsx
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Generacion con mks-ui CLI
|
|
1028
|
+
|
|
1029
|
+
```bash
|
|
1030
|
+
# Componente UI simple (archivo unico)
|
|
1031
|
+
mks-ui component Button --ui
|
|
1032
|
+
|
|
1033
|
+
# Componente complejo con BLO
|
|
1034
|
+
mks-ui component ProductCard --complex
|
|
1035
|
+
|
|
1036
|
+
# Vista previa sin crear
|
|
1037
|
+
mks-ui component TestComponent --dry-run
|
|
1038
|
+
|
|
1039
|
+
# Servicio backend
|
|
1040
|
+
mks-ui service ProductService
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
---
|
|
1044
|
+
|
|
1045
|
+
## REGLA 9: Zustand con Slices
|
|
1046
|
+
|
|
1047
|
+
### Store Central por Dominio
|
|
1048
|
+
|
|
1049
|
+
```typescript
|
|
1050
|
+
// stores/app.store.ts
|
|
1051
|
+
import { create } from 'zustand';
|
|
1052
|
+
|
|
1053
|
+
interface IAppStore {
|
|
1054
|
+
// UI Slice
|
|
1055
|
+
ui: { sidebarOpen: boolean; theme: 'light' | 'dark' };
|
|
1056
|
+
toggleSidebar: () => void;
|
|
1057
|
+
|
|
1058
|
+
// Data Slice
|
|
1059
|
+
users: IUser[];
|
|
1060
|
+
setUsers: (users: IUser[]) => void;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
export const useAppStore = create<IAppStore>((set) => ({
|
|
1064
|
+
ui: { sidebarOpen: true, theme: 'light' },
|
|
1065
|
+
toggleSidebar: () => set((s) => ({
|
|
1066
|
+
ui: { ...s.ui, sidebarOpen: !s.ui.sidebarOpen }
|
|
1067
|
+
})),
|
|
1068
|
+
|
|
1069
|
+
users: [],
|
|
1070
|
+
setUsers: (users) => set({ users }),
|
|
1071
|
+
}));
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### Selectores Puros
|
|
1075
|
+
|
|
1076
|
+
```typescript
|
|
1077
|
+
// CORRECTO - Selector especifico previene renders
|
|
1078
|
+
const theme = useAppStore((s) => s.ui.theme);
|
|
1079
|
+
|
|
1080
|
+
// INCORRECTO - Re-render en cualquier cambio
|
|
1081
|
+
const store = useAppStore();
|
|
1082
|
+
const theme = store.ui.theme;
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
---
|
|
1086
|
+
|
|
1087
|
+
## REGLA 10: Handler Pattern
|
|
1088
|
+
|
|
1089
|
+
### Fachada de Orquestacion
|
|
1090
|
+
|
|
1091
|
+
```typescript
|
|
1092
|
+
// handlers/useUserHandler.ts
|
|
1093
|
+
export const useUserHandler = (service: UserService) => {
|
|
1094
|
+
const { state, load } = useUserLoader(service);
|
|
1095
|
+
const setUsers = useAppStore((s) => s.setUsers);
|
|
1096
|
+
|
|
1097
|
+
const refresh = useCallback(async (id: string) => {
|
|
1098
|
+
const result = await load(id);
|
|
1099
|
+
if (isSuccess(result)) {
|
|
1100
|
+
setUsers([result.data]);
|
|
1101
|
+
}
|
|
1102
|
+
return result;
|
|
1103
|
+
}, [load, setUsers]);
|
|
1104
|
+
|
|
1105
|
+
return useMemo(() => ({
|
|
1106
|
+
isLoading: isPending(state),
|
|
1107
|
+
hasError: isFailed(state),
|
|
1108
|
+
user: hasData(state) ? state.data : null,
|
|
1109
|
+
refresh,
|
|
1110
|
+
}), [state, refresh]);
|
|
1111
|
+
};
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Reglas del Handler
|
|
1115
|
+
|
|
1116
|
+
- API publica memorizada con `useMemo`
|
|
1117
|
+
- Callbacks con `useCallback`
|
|
1118
|
+
- No exponer detalles internos (state crudo)
|
|
1119
|
+
- No usar `useEffect` salvo sincronizacion externa
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## REGLA 11: BaseService para HTTP
|
|
1124
|
+
|
|
1125
|
+
### Clase Base
|
|
1126
|
+
|
|
1127
|
+
```typescript
|
|
1128
|
+
export abstract class BaseService {
|
|
1129
|
+
constructor(
|
|
1130
|
+
protected readonly baseUrl: string,
|
|
1131
|
+
protected readonly fetchImpl = fetch
|
|
1132
|
+
) {}
|
|
1133
|
+
|
|
1134
|
+
protected async request<T>({
|
|
1135
|
+
path,
|
|
1136
|
+
method = 'GET',
|
|
1137
|
+
body,
|
|
1138
|
+
timeoutMs = 15000,
|
|
1139
|
+
signal,
|
|
1140
|
+
}: IRequestOptions): Promise<Response> {
|
|
1141
|
+
const controller = new AbortController();
|
|
1142
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
1143
|
+
|
|
1144
|
+
try {
|
|
1145
|
+
return await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
1146
|
+
method,
|
|
1147
|
+
headers: { 'content-type': 'application/json' },
|
|
1148
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1149
|
+
signal: signal ?? controller.signal,
|
|
1150
|
+
});
|
|
1151
|
+
} finally {
|
|
1152
|
+
clearTimeout(timeoutId);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
### Error Taxonomy
|
|
1159
|
+
|
|
1160
|
+
```typescript
|
|
1161
|
+
export type ServiceError =
|
|
1162
|
+
| { kind: 'timeout'; message: string }
|
|
1163
|
+
| { kind: 'unauthorized'; message: string } // 401
|
|
1164
|
+
| { kind: 'forbidden'; message: string } // 403
|
|
1165
|
+
| { kind: 'not_found'; message: string } // 404
|
|
1166
|
+
| { kind: 'conflict'; message: string } // 409
|
|
1167
|
+
| { kind: 'too_many_requests'; message: string } // 429
|
|
1168
|
+
| { kind: 'server_error'; message: string; status: number } // 5xx
|
|
1169
|
+
| { kind: 'decode_error'; message: string }
|
|
1170
|
+
| { kind: 'unexpected'; message: string };
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### Servicio Tipado
|
|
1174
|
+
|
|
1175
|
+
```typescript
|
|
1176
|
+
export class UserService extends BaseService {
|
|
1177
|
+
async getUser(id: string): Promise<Result<IUser, ServiceError>> {
|
|
1178
|
+
try {
|
|
1179
|
+
const res = await this.request({ path: `/users/${id}` });
|
|
1180
|
+
|
|
1181
|
+
if (res.status === 401) return err({ kind: 'unauthorized', message: 'No autenticado' });
|
|
1182
|
+
if (res.status === 403) return err({ kind: 'forbidden', message: 'No autorizado' });
|
|
1183
|
+
if (res.status === 404) return err({ kind: 'not_found', message: 'Usuario no encontrado' });
|
|
1184
|
+
if (!res.ok) return err({ kind: 'server_error', message: 'Error servidor', status: res.status });
|
|
1185
|
+
|
|
1186
|
+
const json = await res.json();
|
|
1187
|
+
const parsed = UserSchema.safeParse(json);
|
|
1188
|
+
if (!parsed.success) return err({ kind: 'decode_error', message: parsed.error.message });
|
|
1189
|
+
|
|
1190
|
+
return ok(parsed.data);
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
if ((e as Error)?.name === 'AbortError') {
|
|
1193
|
+
return err({ kind: 'timeout', message: 'Timeout' });
|
|
1194
|
+
}
|
|
1195
|
+
return err({ kind: 'unexpected', message: (e as Error)?.message ?? 'Error inesperado' });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
---
|
|
1202
|
+
|
|
1203
|
+
## REGLA 12: React Best Practices
|
|
1204
|
+
|
|
1205
|
+
### Minimizar useEffect
|
|
1206
|
+
|
|
1207
|
+
```typescript
|
|
1208
|
+
// CORRECTO - useMemo para derivar datos
|
|
1209
|
+
const filteredUsers = useMemo(
|
|
1210
|
+
() => users.filter(u => u.active),
|
|
1211
|
+
[users]
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
// INCORRECTO - useEffect para derivar datos
|
|
1215
|
+
const [filteredUsers, setFiltered] = useState([]);
|
|
1216
|
+
useEffect(() => {
|
|
1217
|
+
setFiltered(users.filter(u => u.active));
|
|
1218
|
+
}, [users]);
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
### memo para Componentes Puros
|
|
1222
|
+
|
|
1223
|
+
```typescript
|
|
1224
|
+
// CORRECTO
|
|
1225
|
+
export const UserCard = memo(({ user }: IUserCardProps) => (
|
|
1226
|
+
<div>{user.name}</div>
|
|
1227
|
+
));
|
|
1228
|
+
|
|
1229
|
+
// useState solo para estado efimero local
|
|
1230
|
+
const [inputValue, setInputValue] = useState('');
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
---
|
|
1234
|
+
|
|
1235
|
+
## Checklist Pre-Commit
|
|
1236
|
+
|
|
1237
|
+
Antes de hacer commit de codigo, verificar:
|
|
1238
|
+
|
|
1239
|
+
- [ ] Todo codigo nuevo tiene JSDoc completo
|
|
1240
|
+
- [ ] No hay `console.log/debug/error/info/warn`
|
|
1241
|
+
- [ ] Todo lo que puede fallar usa `Result<T, E>`
|
|
1242
|
+
- [ ] Interfaces tienen prefijo `I`
|
|
1243
|
+
- [ ] Barrel exports en todas las carpetas
|
|
1244
|
+
- [ ] Async/await en lugar de Promise chaining
|
|
1245
|
+
- [ ] Componentes UI siguen estructura de archivos
|
|
1246
|
+
- [ ] Estados de UI usan TransactionState
|
|
1247
|
+
- [ ] Stores usan selectores puros
|
|
1248
|
+
- [ ] Handlers exponen API memorizada
|
|
1249
|
+
- [ ] `bun run typecheck` pasa
|
|
1250
|
+
- [ ] `bun run lint` pasa
|
|
1251
|
+
- [ ] `bun run format` aplicado
|
|
1252
|
+
|
|
1253
|
+
---
|
|
1254
|
+
|
|
1255
|
+
## REGLA 13: UI Components - Shadcn MCP + React Bits + lucide-animated
|
|
1256
|
+
|
|
1257
|
+
### Stack de Componentes UI
|
|
1258
|
+
|
|
1259
|
+
**OBLIGATORIO**: Usar Shadcn MCP Server para instalar componentes via registries externos.
|
|
1260
|
+
|
|
1261
|
+
| Registry | URL | Tipo | Prioridad |
|
|
1262
|
+
|----------|-----|------|-----------|
|
|
1263
|
+
| **@animate-ui** | `https://animate-ui.com/r/{name}.json` | Componentes Shadcn con animaciones | **1º PREFERIDO** |
|
|
1264
|
+
| **@react-bits** | `https://reactbits.dev/r/{name}.json` | Componentes interactivos/efectos | **2º PREFERIDO** |
|
|
1265
|
+
| **@lucide-animated** | `https://lucide-animated.com/r/{name}.json` | Iconos animados (350+) | **PREFERIDO** |
|
|
1266
|
+
| **@prompt-kit** | `https://prompt-kit.com/c/{name}.json` | Chat/Prompt UI | **PREFERIDO** para agents |
|
|
1267
|
+
| **@shadcn** | Registry base | Componentes base estándar | Fallback |
|
|
1268
|
+
|
|
1269
|
+
### Configuracion MCP Server (Primera vez)
|
|
1270
|
+
|
|
1271
|
+
```bash
|
|
1272
|
+
# En el directorio del proyecto
|
|
1273
|
+
npx shadcn@latest mcp init --client claude
|
|
1274
|
+
|
|
1275
|
+
# Esto genera .mcp.json en la raiz del proyecto:
|
|
1276
|
+
{
|
|
1277
|
+
"mcpServers": {
|
|
1278
|
+
"shadcn": {
|
|
1279
|
+
"command": "npx",
|
|
1280
|
+
"args": ["shadcn@latest", "mcp"]
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
# Reiniciar Claude Code y verificar con /mcp
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
### Configuracion de components.json
|
|
1289
|
+
|
|
1290
|
+
**OBLIGATORIO**: Agregar registries externos al `components.json`:
|
|
1291
|
+
|
|
1292
|
+
```json
|
|
1293
|
+
{
|
|
1294
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
1295
|
+
"style": "new-york",
|
|
1296
|
+
"rsc": false,
|
|
1297
|
+
"tsx": true,
|
|
1298
|
+
"tailwind": {
|
|
1299
|
+
"config": "",
|
|
1300
|
+
"css": "src/app/globals.css",
|
|
1301
|
+
"baseColor": "zinc",
|
|
1302
|
+
"cssVariables": true,
|
|
1303
|
+
"prefix": ""
|
|
1304
|
+
},
|
|
1305
|
+
"iconLibrary": "lucide",
|
|
1306
|
+
"aliases": {
|
|
1307
|
+
"components": "@/components",
|
|
1308
|
+
"utils": "@/lib/utils",
|
|
1309
|
+
"ui": "@/components/ui",
|
|
1310
|
+
"lib": "@/lib",
|
|
1311
|
+
"hooks": "@/hooks"
|
|
1312
|
+
},
|
|
1313
|
+
"registries": {
|
|
1314
|
+
"@animate-ui": "https://animate-ui.com/r/{name}.json",
|
|
1315
|
+
"@react-bits": "https://reactbits.dev/r/{name}.json",
|
|
1316
|
+
"@lucide-animated": "https://lucide-animated.com/r/{name}.json",
|
|
1317
|
+
"@prompt-kit": "https://prompt-kit.com/c/{name}.json"
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
### Comandos de Instalacion
|
|
1323
|
+
|
|
1324
|
+
```bash
|
|
1325
|
+
# 1º Animate UI (componentes Shadcn con animaciones - PREFERIDO)
|
|
1326
|
+
bunx --bun shadcn@latest add @animate-ui/sliding-number
|
|
1327
|
+
bunx --bun shadcn@latest add @animate-ui/morphing-text
|
|
1328
|
+
bunx --bun shadcn@latest add @animate-ui/animated-card
|
|
1329
|
+
bunx --bun shadcn@latest add @animate-ui/progress-reveal
|
|
1330
|
+
|
|
1331
|
+
# 2º React Bits (efectos interactivos)
|
|
1332
|
+
bunx --bun shadcn@latest add @react-bits/fade-content
|
|
1333
|
+
bunx --bun shadcn@latest add @react-bits/magnet
|
|
1334
|
+
bunx --bun shadcn@latest add @react-bits/dock
|
|
1335
|
+
|
|
1336
|
+
# 3º Iconos animados lucide-animated
|
|
1337
|
+
bunx --bun shadcn@latest add @lucide-animated/check-circle
|
|
1338
|
+
bunx --bun shadcn@latest add @lucide-animated/loader
|
|
1339
|
+
bunx --bun shadcn@latest add @lucide-animated/play
|
|
1340
|
+
|
|
1341
|
+
# 4º prompt-kit (chat/agent UI)
|
|
1342
|
+
bunx --bun shadcn@latest add @prompt-kit/prompt-input
|
|
1343
|
+
bunx --bun shadcn@latest add @prompt-kit/response-stream
|
|
1344
|
+
|
|
1345
|
+
# 5º Shadcn base (solo si no hay alternativa animada)
|
|
1346
|
+
bunx --bun shadcn@latest add button card dialog
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
### Uso con Claude Code MCP
|
|
1350
|
+
|
|
1351
|
+
Despues de configurar el MCP, usar prompts naturales:
|
|
1352
|
+
|
|
1353
|
+
```
|
|
1354
|
+
# React Bits
|
|
1355
|
+
"Show me all available backgrounds from @react-bits"
|
|
1356
|
+
"Add the Dither background from React Bits to the page, make it purple"
|
|
1357
|
+
"Add FadeContent from @react-bits for scroll animations"
|
|
1358
|
+
|
|
1359
|
+
# lucide-animated
|
|
1360
|
+
"Show me all available icons from @lucide-animated"
|
|
1361
|
+
"Add the check-circle animated icon from lucide-animated"
|
|
1362
|
+
|
|
1363
|
+
# Combinados
|
|
1364
|
+
"Create a hero section with FadeContent animation and animated icons"
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
### Componentes React Bits Recomendados
|
|
1368
|
+
|
|
1369
|
+
| Componente | Categoria | Uso |
|
|
1370
|
+
|-----------|-----------|-----|
|
|
1371
|
+
| `fade-content` | Animations | Fade in/out con scroll |
|
|
1372
|
+
| `text-pressure` | Text Effects | Texto con efecto presion |
|
|
1373
|
+
| `magnet` | Interactions | Efecto magnetico cursor |
|
|
1374
|
+
| `dock` | Components | Dock estilo macOS |
|
|
1375
|
+
| `tilted-card` | Components | Cards con tilt 3D |
|
|
1376
|
+
| `spotlight-card` | Components | Cards con spotlight |
|
|
1377
|
+
| `dither` | Backgrounds | Fondo con efecto dither |
|
|
1378
|
+
| `hyperspeed` | Backgrounds | Efecto velocidad |
|
|
1379
|
+
|
|
1380
|
+
### Iconos lucide-animated Recomendados
|
|
1381
|
+
|
|
1382
|
+
| Icono | Uso |
|
|
1383
|
+
|-------|-----|
|
|
1384
|
+
| `check-circle` | Confirmacion, exito |
|
|
1385
|
+
| `x-circle` | Error, cerrar |
|
|
1386
|
+
| `loader` | Loading states |
|
|
1387
|
+
| `play` / `pause` | Media controls |
|
|
1388
|
+
| `arrow-up` / `arrow-down` | Navegacion |
|
|
1389
|
+
| `settings` | Configuracion |
|
|
1390
|
+
| `bell` | Notificaciones |
|
|
1391
|
+
|
|
1392
|
+
### Patrones de Importacion
|
|
1393
|
+
|
|
1394
|
+
```typescript
|
|
1395
|
+
// CORRECTO - Componentes React Bits
|
|
1396
|
+
import { FadeContent } from '@/components/ui/fade-content';
|
|
1397
|
+
import { TextPressure } from '@/components/ui/text-pressure';
|
|
1398
|
+
import { Dock } from '@/components/ui/dock';
|
|
1399
|
+
|
|
1400
|
+
// CORRECTO - Iconos animados lucide-animated
|
|
1401
|
+
import { CheckCircle } from '@/components/icons/check-circle';
|
|
1402
|
+
import { Loader } from '@/components/icons/loader';
|
|
1403
|
+
|
|
1404
|
+
// CORRECTO - Componentes base Shadcn
|
|
1405
|
+
import { Button } from '@/components/ui/button';
|
|
1406
|
+
import { Card } from '@/components/ui/card';
|
|
1407
|
+
|
|
1408
|
+
// CORRECTO - Iconos estaticos (fallback)
|
|
1409
|
+
import { Settings } from 'lucide-react';
|
|
1410
|
+
|
|
1411
|
+
// INCORRECTO - Importar directo desde node_modules
|
|
1412
|
+
import { FadeContent } from 'react-bits';
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
### Estructura de Carpetas
|
|
1416
|
+
|
|
1417
|
+
```
|
|
1418
|
+
components/
|
|
1419
|
+
├── ui/ # Componentes UI (Shadcn + React Bits)
|
|
1420
|
+
│ ├── button.tsx
|
|
1421
|
+
│ ├── card.tsx
|
|
1422
|
+
│ ├── fade-content.tsx # React Bits
|
|
1423
|
+
│ ├── text-pressure.tsx # React Bits
|
|
1424
|
+
│ ├── dock.tsx # React Bits
|
|
1425
|
+
│ └── index.ts # Barrel export
|
|
1426
|
+
├── icons/ # Iconos animados (lucide-animated)
|
|
1427
|
+
│ ├── check-circle.tsx
|
|
1428
|
+
│ ├── loader.tsx
|
|
1429
|
+
│ ├── play.tsx
|
|
1430
|
+
│ └── index.ts # Barrel export
|
|
1431
|
+
└── agent/ # Componentes de dominio
|
|
1432
|
+
└── ...
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
### Ejemplo Completo de Uso
|
|
1436
|
+
|
|
1437
|
+
```typescript
|
|
1438
|
+
// components/agent/MessageItem.tsx
|
|
1439
|
+
import { FadeContent } from '@/components/ui/fade-content';
|
|
1440
|
+
import { Card } from '@/components/ui/card';
|
|
1441
|
+
import { CheckCircle } from '@/components/icons/check-circle';
|
|
1442
|
+
import { Loader } from '@/components/icons/loader';
|
|
1443
|
+
import type { IMessageItemProps } from './MessageItem.types';
|
|
1444
|
+
|
|
1445
|
+
export function MessageItem({ message, isLoading }: IMessageItemProps) {
|
|
1446
|
+
return (
|
|
1447
|
+
<FadeContent direction="up" duration={0.4}>
|
|
1448
|
+
<Card className="p-4">
|
|
1449
|
+
<div className="flex items-center gap-2">
|
|
1450
|
+
{isLoading ? (
|
|
1451
|
+
<Loader className="w-5 h-5 animate-spin" />
|
|
1452
|
+
) : (
|
|
1453
|
+
<CheckCircle className="w-5 h-5 text-green-500" />
|
|
1454
|
+
)}
|
|
1455
|
+
<span>{message.content}</span>
|
|
1456
|
+
</div>
|
|
1457
|
+
</Card>
|
|
1458
|
+
</FadeContent>
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
### Debug MCP
|
|
1464
|
+
|
|
1465
|
+
```bash
|
|
1466
|
+
# En Claude Code, usar /mcp para verificar conexion
|
|
1467
|
+
/mcp
|
|
1468
|
+
|
|
1469
|
+
# Debe mostrar "shadcn" en la lista de servers activos
|
|
1470
|
+
# Si hay problemas, reiniciar Claude Code
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
### Referencias
|
|
1474
|
+
|
|
1475
|
+
- **React Bits**: https://reactbits.dev/
|
|
1476
|
+
- **lucide-animated**: https://lucide-animated.com/
|
|
1477
|
+
- **Shadcn MCP**: https://ui.shadcn.com/docs/mcp
|
|
1478
|
+
- **Shadcn Registry**: https://ui.shadcn.com/
|
|
1479
|
+
|
|
1480
|
+
---
|
|
1481
|
+
|
|
1482
|
+
## REGLA 14: Package UI Compartido
|
|
1483
|
+
|
|
1484
|
+
### Estructura del Package UI
|
|
1485
|
+
|
|
1486
|
+
El monorepo tiene un package UI compartido en `core/packages/ui/` para componentes reutilizables:
|
|
1487
|
+
|
|
1488
|
+
```
|
|
1489
|
+
core/packages/ui/
|
|
1490
|
+
├── src/
|
|
1491
|
+
│ ├── components/
|
|
1492
|
+
│ │ ├── ui/ # Componentes Shadcn + React Bits
|
|
1493
|
+
│ │ ├── icons/ # Iconos lucide-animated
|
|
1494
|
+
│ │ └── agent/ # Componentes de dominio
|
|
1495
|
+
│ ├── lib/
|
|
1496
|
+
│ │ └── utils.ts # cn() y utilidades
|
|
1497
|
+
│ ├── hooks/ # Hooks compartidos
|
|
1498
|
+
│ └── index.ts # Barrel export
|
|
1499
|
+
├── components.json # Config Shadcn con registries
|
|
1500
|
+
├── package.json
|
|
1501
|
+
├── rolldown.config.ts
|
|
1502
|
+
└── tsconfig.json
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
### Uso del Package UI
|
|
1506
|
+
|
|
1507
|
+
```typescript
|
|
1508
|
+
// Importar desde el package compartido
|
|
1509
|
+
import { Button, Card, FadeContent } from 'mks-dev-environment/ui';
|
|
1510
|
+
import { CheckCircle, Loader } from 'mks-dev-environment/ui/icons';
|
|
1511
|
+
import { cn } from 'mks-dev-environment/ui/lib/utils';
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
### Instalacion de Componentes en Package UI
|
|
1515
|
+
|
|
1516
|
+
```bash
|
|
1517
|
+
# Ir al directorio del package UI
|
|
1518
|
+
cd core/packages/ui
|
|
1519
|
+
|
|
1520
|
+
# Instalar componentes (se guardan en src/components/ui/)
|
|
1521
|
+
bunx --bun shadcn@latest add @react-bits/fade-content
|
|
1522
|
+
bunx --bun shadcn@latest add @lucide-animated/check-circle
|
|
1523
|
+
bunx --bun shadcn@latest add button card
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
### Referencias
|
|
1527
|
+
|
|
1528
|
+
- **Shadcn/UI Documentation**: https://ui.shadcn.com/
|
|
1529
|
+
- **Shadcn MCP Registry**: https://ui.shadcn.com/docs/mcp
|
|
1530
|
+
- **Animate UI Gallery**: https://animate.ui/
|
|
1531
|
+
- **Radix UI Primitives**: https://www.radix-ui.com/
|
|
1532
|
+
- **CLI Documentation**: https://ui.shadcn.com/docs/cli
|
|
1533
|
+
|
|
1534
|
+
---
|
|
1535
|
+
|
|
1536
|
+
## REGLA 15: Testing - Vitest (NO bun test)
|
|
1537
|
+
|
|
1538
|
+
### Runner Correcto
|
|
1539
|
+
|
|
1540
|
+
**OBLIGATORIO**: Usar `vitest` o `bunx vitest` para ejecutar tests. NUNCA `bun test`.
|
|
1541
|
+
|
|
1542
|
+
```bash
|
|
1543
|
+
# CORRECTO - Vitest runner
|
|
1544
|
+
vitest # Modo watch
|
|
1545
|
+
vitest run # Single run
|
|
1546
|
+
bunx vitest run # Con bunx
|
|
1547
|
+
bunx vitest run src/hooks/ # Tests específicos
|
|
1548
|
+
|
|
1549
|
+
# INCORRECTO - Bun test runner
|
|
1550
|
+
bun test # NO USAR
|
|
1551
|
+
bun test src/ # NO USAR
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
### Configuración Base
|
|
1555
|
+
|
|
1556
|
+
```typescript
|
|
1557
|
+
// vitest.config.ts
|
|
1558
|
+
import { defineConfig } from 'vitest/config';
|
|
1559
|
+
import react from '@vitejs/plugin-react';
|
|
1560
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
1561
|
+
|
|
1562
|
+
export default defineConfig({
|
|
1563
|
+
plugins: [react(), tsconfigPaths()],
|
|
1564
|
+
test: {
|
|
1565
|
+
environment: 'jsdom',
|
|
1566
|
+
setupFiles: ['./src/tests/setupTests.ts'],
|
|
1567
|
+
globals: true,
|
|
1568
|
+
},
|
|
1569
|
+
});
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
### Setup con MSW
|
|
1573
|
+
|
|
1574
|
+
```typescript
|
|
1575
|
+
// src/tests/setupTests.ts
|
|
1576
|
+
import '@testing-library/jest-dom';
|
|
1577
|
+
import { beforeAll, afterEach, afterAll } from 'vitest';
|
|
1578
|
+
import { server } from './mocks/server';
|
|
1579
|
+
|
|
1580
|
+
beforeAll(() => server.listen());
|
|
1581
|
+
afterEach(() => {
|
|
1582
|
+
server.resetHandlers();
|
|
1583
|
+
localStorage.clear();
|
|
1584
|
+
});
|
|
1585
|
+
afterAll(() => server.close());
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
### Documentación Completa
|
|
1589
|
+
|
|
1590
|
+
Ver documentación detallada en:
|
|
1591
|
+
- `~/dotfiles/mks-ui/docs/testing-architecture.md`
|
|
1592
|
+
- `~/dotfiles/mks-ui/docs/testing-strategy.md`
|
|
1593
|
+
|
|
1594
|
+
---
|
|
1595
|
+
|
|
1596
|
+
## REGLA 16: Sistema de Themes - CSS Variables + Theme Manager
|
|
1597
|
+
|
|
1598
|
+
### Stack de Theming
|
|
1599
|
+
|
|
1600
|
+
**OBLIGATORIO**: Usar sistema de CSS variables con tokens apropiados.
|
|
1601
|
+
|
|
1602
|
+
| Package | Uso | Instalación |
|
|
1603
|
+
|---------|-----|-------------|
|
|
1604
|
+
| **@mks2508/theme-manager-react** | Theme switching con animaciones | `bun add @mks2508/theme-manager-react` |
|
|
1605
|
+
| **@mks2508/shadcn-basecoat-theme-manager** | Core de gestión de temas | `bun add @mks2508/shadcn-basecoat-theme-manager` |
|
|
1606
|
+
|
|
1607
|
+
### Variables CSS Requeridas
|
|
1608
|
+
|
|
1609
|
+
```css
|
|
1610
|
+
:root {
|
|
1611
|
+
/* Colores base */
|
|
1612
|
+
--background: 0 0% 100%;
|
|
1613
|
+
--foreground: 222.2 84% 4.9%;
|
|
1614
|
+
--primary: 222.2 47.4% 11.2%;
|
|
1615
|
+
--primary-foreground: 210 40% 98%;
|
|
1616
|
+
--secondary: 210 40% 96%;
|
|
1617
|
+
--secondary-foreground: 222.2 84% 4.9%;
|
|
1618
|
+
--muted: 210 40% 96%;
|
|
1619
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
1620
|
+
--accent: 210 40% 96%;
|
|
1621
|
+
--accent-foreground: 222.2 84% 4.9%;
|
|
1622
|
+
--destructive: 0 84.2% 60.2%;
|
|
1623
|
+
--destructive-foreground: 210 40% 98%;
|
|
1624
|
+
|
|
1625
|
+
/* UI Elements */
|
|
1626
|
+
--card: var(--background);
|
|
1627
|
+
--card-foreground: var(--foreground);
|
|
1628
|
+
--popover: var(--background);
|
|
1629
|
+
--popover-foreground: var(--foreground);
|
|
1630
|
+
--border: 214.3 31.8% 91.4%;
|
|
1631
|
+
--input: 214.3 31.8% 91.4%;
|
|
1632
|
+
--ring: 222.2 84% 4.9%;
|
|
1633
|
+
--radius: 0.5rem;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.dark {
|
|
1637
|
+
--background: 222.2 84% 4.9%;
|
|
1638
|
+
--foreground: 210 40% 98%;
|
|
1639
|
+
/* ... dark variants */
|
|
1640
|
+
}
|
|
1641
|
+
```
|
|
1642
|
+
|
|
1643
|
+
### Uso con Next.js
|
|
1644
|
+
|
|
1645
|
+
```tsx
|
|
1646
|
+
// app/layout.tsx
|
|
1647
|
+
import { ThemeProvider } from '@mks2508/theme-manager-react/nextjs'
|
|
1648
|
+
|
|
1649
|
+
export default function RootLayout({ children }) {
|
|
1650
|
+
return (
|
|
1651
|
+
<html lang="en">
|
|
1652
|
+
<body>
|
|
1653
|
+
<ThemeProvider
|
|
1654
|
+
registryUrl="/themes/registry.json"
|
|
1655
|
+
defaultTheme="default"
|
|
1656
|
+
defaultMode="auto"
|
|
1657
|
+
enableTransitions={true}
|
|
1658
|
+
>
|
|
1659
|
+
{children}
|
|
1660
|
+
</ThemeProvider>
|
|
1661
|
+
</body>
|
|
1662
|
+
</html>
|
|
1663
|
+
)
|
|
1664
|
+
}
|
|
1665
|
+
```
|
|
1666
|
+
|
|
1667
|
+
### Componentes de Theme
|
|
1668
|
+
|
|
1669
|
+
```tsx
|
|
1670
|
+
import {
|
|
1671
|
+
ModeToggle, // Toggle light/dark
|
|
1672
|
+
AnimatedThemeToggler, // Toggle con animaciones
|
|
1673
|
+
ThemeSelector // Selector completo de temas
|
|
1674
|
+
} from '@mks2508/theme-manager-react/nextjs'
|
|
1675
|
+
```
|
|
1676
|
+
|
|
1677
|
+
---
|
|
1678
|
+
|
|
1679
|
+
## REGLA 17: Sidebar y Bottom Navigation - sidebar-headless
|
|
1680
|
+
|
|
1681
|
+
### Package Oficial
|
|
1682
|
+
|
|
1683
|
+
**OBLIGATORIO**: Usar `@mks2508/sidebar-headless` para sidebars y bottom navigation.
|
|
1684
|
+
|
|
1685
|
+
```bash
|
|
1686
|
+
bun add @mks2508/sidebar-headless
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
### Características
|
|
1690
|
+
|
|
1691
|
+
- Headless sidebar y mobile bottom navigation
|
|
1692
|
+
- Animaciones fluidas
|
|
1693
|
+
- Keyboard navigation completa
|
|
1694
|
+
- WAI-ARIA accessibility
|
|
1695
|
+
- Soporte glassmorphism
|
|
1696
|
+
- Mobile-first design
|
|
1697
|
+
|
|
1698
|
+
### Ejemplo de Uso
|
|
1699
|
+
|
|
1700
|
+
```tsx
|
|
1701
|
+
import {
|
|
1702
|
+
Sidebar,
|
|
1703
|
+
SidebarItem,
|
|
1704
|
+
BottomNavigation
|
|
1705
|
+
} from '@mks2508/sidebar-headless';
|
|
1706
|
+
|
|
1707
|
+
export function AppLayout({ children }) {
|
|
1708
|
+
return (
|
|
1709
|
+
<div className="flex">
|
|
1710
|
+
{/* Desktop Sidebar */}
|
|
1711
|
+
<Sidebar className="hidden md:flex">
|
|
1712
|
+
<SidebarItem icon={<Home />} label="Home" href="/" />
|
|
1713
|
+
<SidebarItem icon={<Settings />} label="Settings" href="/settings" />
|
|
1714
|
+
</Sidebar>
|
|
1715
|
+
|
|
1716
|
+
{/* Mobile Bottom Nav */}
|
|
1717
|
+
<BottomNavigation className="md:hidden">
|
|
1718
|
+
<SidebarItem icon={<Home />} label="Home" href="/" />
|
|
1719
|
+
<SidebarItem icon={<Settings />} label="Settings" href="/settings" />
|
|
1720
|
+
</BottomNavigation>
|
|
1721
|
+
|
|
1722
|
+
<main>{children}</main>
|
|
1723
|
+
</div>
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
```
|
|
1727
|
+
|
|
1728
|
+
---
|
|
1729
|
+
|
|
1730
|
+
## REGLA 18: Glassmorphism - Guía de Implementación
|
|
1731
|
+
|
|
1732
|
+
### Principios Fundamentales
|
|
1733
|
+
|
|
1734
|
+
Glassmorphism es la combinación controlada de:
|
|
1735
|
+
- **Transparencia parcial** (RGBA / alpha 10-30%)
|
|
1736
|
+
- **Backdrop blur** (difuminar lo que hay detrás)
|
|
1737
|
+
- **Bordes sutiles** (simular refracción)
|
|
1738
|
+
- **Sombras suaves** (profundidad)
|
|
1739
|
+
- **isolation: isolate** (stacking context)
|
|
1740
|
+
|
|
1741
|
+
### Receta Base (Tailwind)
|
|
1742
|
+
|
|
1743
|
+
```html
|
|
1744
|
+
<div class="
|
|
1745
|
+
isolate
|
|
1746
|
+
rounded-xl
|
|
1747
|
+
bg-white/20
|
|
1748
|
+
backdrop-blur-lg
|
|
1749
|
+
border border-white/30
|
|
1750
|
+
shadow-xl shadow-black/10
|
|
1751
|
+
">
|
|
1752
|
+
</div>
|
|
1753
|
+
```
|
|
1754
|
+
|
|
1755
|
+
### Clases Glassmorphism Disponibles
|
|
1756
|
+
|
|
1757
|
+
```css
|
|
1758
|
+
/* Paneles principales */
|
|
1759
|
+
.glass-panel /* blur-16px, borde primary */
|
|
1760
|
+
.glass-panel-subtle /* blur-8px, más transparente */
|
|
1761
|
+
.glass-panel-heavy /* blur-24px, más opaco */
|
|
1762
|
+
|
|
1763
|
+
/* Cards de producto */
|
|
1764
|
+
.product-card-glass /* blur-12px con hover */
|
|
1765
|
+
.product-card-glass-enhanced /* blur-16px con glow */
|
|
1766
|
+
|
|
1767
|
+
/* Badges */
|
|
1768
|
+
.badge-glass-featured /* Primary con glow */
|
|
1769
|
+
.badge-glass-subtle /* Sutil, borde transparente */
|
|
1770
|
+
.badge-glass-outline /* Solo borde */
|
|
1771
|
+
|
|
1772
|
+
/* Inputs y formularios */
|
|
1773
|
+
.input-glass /* Blur sutil con focus glow */
|
|
1774
|
+
|
|
1775
|
+
/* Headers */
|
|
1776
|
+
.bg-glass-header /* Desktop header */
|
|
1777
|
+
.bg-glass-header-mobile /* Mobile header más transparente */
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
### Reglas de Uso
|
|
1781
|
+
|
|
1782
|
+
| Usar en | Evitar en |
|
|
1783
|
+
|---------|-----------|
|
|
1784
|
+
| Headers flotantes | Tablas densas |
|
|
1785
|
+
| Modales | Formularios largos |
|
|
1786
|
+
| Sidebars | Texto extenso |
|
|
1787
|
+
| Cards destacadas | Listas largas |
|
|
1788
|
+
|
|
1789
|
+
### Performance
|
|
1790
|
+
|
|
1791
|
+
```css
|
|
1792
|
+
/* Fallback obligatorio */
|
|
1793
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
1794
|
+
.glass-panel { background: rgba(255,255,255,0.9); }
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/* Reduced motion */
|
|
1798
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1799
|
+
.glass-panel { transition: none; }
|
|
1800
|
+
}
|
|
1801
|
+
```
|
|
1802
|
+
|
|
1803
|
+
### Documentación Completa
|
|
1804
|
+
|
|
1805
|
+
- `~/dotfiles/mks-ui/docs/glassmorphism-guide.md` - Guía teórica
|
|
1806
|
+
- `~/dotfiles/mks-ui/docs/glassmorphism.css` - Implementación CSS completa
|
|
1807
|
+
|
|
1808
|
+
---
|
|
1809
|
+
|
|
1810
|
+
## Fuentes de Referencia
|
|
1811
|
+
|
|
1812
|
+
- **CLAUDE.md** - Guia de arquitectura del monorepo
|
|
1813
|
+
- **@mks2508/better-logger** - Documentacion del logger
|
|
1814
|
+
- **@mks2508/no-throw** - Documentacion del Result pattern
|
|
1815
|
+
- **@mks2508/theme-manager-react** - Sistema de themes
|
|
1816
|
+
- **@mks2508/sidebar-headless** - Sidebar y bottom navigation
|
|
1817
|
+
- **Arktype** - https://arktype.io/
|
|
1818
|
+
- **Rolldown** - https://rollup.rs/
|
|
1819
|
+
- **Oxlint** - https://oxlint.com/
|
|
1820
|
+
- **Zustand** - https://zustand-demo.pmnd.rs/
|
|
1821
|
+
- **Shadcn/UI** - https://ui.shadcn.com/
|
|
1822
|
+
- **Shadcn MCP** - https://ui.shadcn.com/docs/mcp
|
|
1823
|
+
- **React Bits** - https://reactbits.dev/
|
|
1824
|
+
- **lucide-animated** - https://lucide-animated.com/
|
|
1825
|
+
|
|
1826
|
+
### Documentación en Dotfiles
|
|
1827
|
+
|
|
1828
|
+
| Documento | Ubicación | Contenido |
|
|
1829
|
+
|-----------|-----------|-----------|
|
|
1830
|
+
| **Glassmorphism Guide** | `~/dotfiles/mks-ui/docs/glassmorphism-guide.md` | Teoría y best practices |
|
|
1831
|
+
| **Glassmorphism CSS** | `~/dotfiles/mks-ui/docs/glassmorphism.css` | Implementación completa |
|
|
1832
|
+
| **Testing Architecture** | `~/dotfiles/mks-ui/docs/testing-architecture.md` | Arquitectura de tests |
|
|
1833
|
+
| **Testing Strategy** | `~/dotfiles/mks-ui/docs/testing-strategy.md` | Estrategia de testing |
|
|
1834
|
+
|
|
1835
|
+
### CLI mks-ui
|
|
1836
|
+
|
|
1837
|
+
Generador de componentes disponible globalmente:
|
|
1838
|
+
|
|
1839
|
+
```bash
|
|
1840
|
+
mks-ui component Button --ui # Componente UI simple
|
|
1841
|
+
mks-ui component ProductCard --complex # BLO completo
|
|
1842
|
+
mks-ui service ProductService # Servicio Elysia
|
|
1843
|
+
mks-ui hook useProducts # Custom hook
|
|
1844
|
+
mks-ui type Product # Tipos TypeScript
|
|
1845
|
+
```
|