@jeffrey2423/coding-standards 1.0.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.
@@ -0,0 +1,1199 @@
1
+ # Frontend Development Standards
2
+
3
+ ## Resumen
4
+
5
+ Este documento define los estándares de desarrollo frontend para aplicaciones empresariales usando **Vite** como bundler, **TanStack Router** para enrutamiento type-safe, **Zustand** para estado global, y **Clean Architecture** con estructura modular preparada para microfrontends.
6
+
7
+ ---
8
+
9
+ ## Tabla de Contenidos
10
+
11
+ 1. [Principios de Arquitectura](#1-principios-de-arquitectura)
12
+ 2. [Stack Tecnológico](#2-stack-tecnológico)
13
+ 3. [Convenciones de Routing](#3-convenciones-de-routing)
14
+ 4. [Organización de Archivos](#4-organización-de-archivos)
15
+ 5. [Estándares de Componentes](#5-estándares-de-componentes)
16
+ 6. [Patrones de Estado](#6-patrones-de-estado)
17
+ 7. [Estándares de Testing](#7-estándares-de-testing)
18
+ 8. [Accesibilidad](#8-accesibilidad)
19
+ 9. [Rendimiento](#9-rendimiento)
20
+ 10. [Seguridad](#10-seguridad)
21
+ 11. [Manejo de Errores](#11-manejo-de-errores)
22
+ 12. [Progressive Web App](#12-progressive-web-app)
23
+ 13. [Estándares de Idioma](#13-estándares-de-idioma)
24
+ 14. [Consideraciones Generales](#14-consideraciones-generales)
25
+ 15. [Configuración Base](#15-configuración-base)
26
+ 16. [Checklist de Implementación](#16-checklist-de-implementación)
27
+
28
+ ---
29
+
30
+ ## 1. Principios de Arquitectura
31
+
32
+ ### 1.1 Implementación de Clean Architecture
33
+
34
+ ```
35
+ ┌─────────────────────────────────────────────────────────────────┐
36
+ │ PRESENTATION LAYER │
37
+ │ React components, pages, UI logic, routes │
38
+ └─────────────────────────────────────────────────────────────────┘
39
+
40
+
41
+ ┌─────────────────────────────────────────────────────────────────┐
42
+ │ APPLICATION LAYER │
43
+ │ Use cases, custom hooks, Zustand stores │
44
+ └─────────────────────────────────────────────────────────────────┘
45
+
46
+
47
+ ┌─────────────────────────────────────────────────────────────────┐
48
+ │ INFRASTRUCTURE LAYER │
49
+ │ API clients, repositories impl, external adapters │
50
+ └─────────────────────────────────────────────────────────────────┘
51
+
52
+
53
+ ┌─────────────────────────────────────────────────────────────────┐
54
+ │ DOMAIN LAYER │
55
+ │ Business entities, value objects, business rules │
56
+ └─────────────────────────────────────────────────────────────────┘
57
+ ```
58
+
59
+ | Capa | Responsabilidad | Contenido |
60
+ |------|-----------------|-----------|
61
+ | **Domain** | Reglas de negocio puras | Entities, value objects, interfaces de repositorios |
62
+ | **Application** | Orquestación de casos de uso | Use cases, hooks, stores Zustand |
63
+ | **Infrastructure** | Implementaciones externas | API clients, repositorios concretos, adapters |
64
+ | **Presentation** | Interfaz de usuario | Componentes React, páginas, estilos |
65
+
66
+ ### 1.2 Reglas de Dependencia
67
+
68
+ - Las capas internas **NO deben conocer** las capas externas
69
+ - Las dependencias apuntan hacia adentro (de externo a interno)
70
+ - Usar inversión de dependencias para concerns externos
71
+ - Domain layer no importa nada de otras capas
72
+
73
+ ---
74
+
75
+ ## 2. Stack Tecnológico
76
+
77
+ ### 2.1 Tecnologías Core
78
+
79
+ | Categoría | Tecnología | Versión | Notas |
80
+ |-----------|------------|---------|-------|
81
+ | **Bundler** | Vite | 5+ | Build tool y dev server |
82
+ | **Framework** | React | 18+ | Functional components y hooks |
83
+ | **Router** | TanStack Router | 1+ | File-based routing con type-safety |
84
+ | **Lenguaje** | TypeScript | 5+ | Strict mode, sin `any` |
85
+ | **Estilos** | TailwindCSS | 4+ | Utility-first CSS |
86
+ | **Componentes** | shadcn/ui + Radix UI | - | Base de componentes |
87
+ | **Estado** | Zustand | 4+ | Estado global por feature |
88
+ | **Data Fetching** | TanStack Query | 5+ | Cache y sincronización |
89
+
90
+ ### 2.2 Reglas de Selección de Framework
91
+
92
+ | Escenario | Framework | Razón |
93
+ |-----------|-----------|-------|
94
+ | **Default** | Vite + TanStack Router | Mejor DX, type-safety, preparado para microfrontends |
95
+ | **MFE (Default)** | Vite + Single-SPA | Aislamiento completo, CSS lifecycle, error boundaries built-in |
96
+ | **Módulo Federable** | Vite + Module Federation | SOLO para módulos transversales explícitamente marcados |
97
+
98
+ ### 2.3 Regla Crítica: Single-SPA vs Module Federation
99
+
100
+ > ⚠️ **IMPORTANTE**: Por defecto, todos los microfrontends usan **Single-SPA**. Module Federation se usa **SOLO** cuando el ingeniero define explícitamente que un módulo debe ser compartido/federable.
101
+
102
+ | Tipo | Tecnología | Uso |
103
+ |------|------------|-----|
104
+ | **MFE de Negocio** | `vite-plugin-single-spa` + `single-spa-react` | ✅ DEFAULT - finance, hr, inventory, etc. |
105
+ | **Módulo Compartido** | `@module-federation/vite` | ⚠️ SOLO cuando explícitamente requerido |
106
+
107
+ **Dependencias Single-SPA (DEFAULT):**
108
+ ```bash
109
+ npm install single-spa-react
110
+ npm install vite-plugin-single-spa --save-dev
111
+ ```
112
+
113
+ **Beneficios de Single-SPA:**
114
+ - CSS isolation automático via `cssLifecycleFactory`
115
+ - Error boundaries built-in
116
+ - Lifecycle completo (bootstrap, mount, unmount)
117
+ - Mejor hot reload
118
+ - Menor complejidad de configuración
119
+
120
+ Ver `vite-config-standard.md` para configuración detallada.
121
+
122
+ ### 2.3 Herramientas de Desarrollo
123
+
124
+ | Herramienta | Propósito |
125
+ |-------------|-----------|
126
+ | **Vite** | Build system y dev server |
127
+ | **Vitest** | Unit e integration testing |
128
+ | **React Testing Library** | Component testing |
129
+ | **MSW** | API mocking para tests |
130
+ | **ESLint + Prettier** | Code quality y formatting |
131
+ | **TypeScript** | Type checking |
132
+
133
+ ---
134
+
135
+ ## 3. Convenciones de Routing
136
+
137
+ TanStack Router usa prefijos especiales en nombres de archivo para definir comportamientos.
138
+
139
+ ### 3.1 Prefijo `_` (Underscore) - Pathless Layout Routes
140
+
141
+ **Propósito:** Agrupar rutas bajo un layout compartido **SIN agregar segmentos a la URL**.
142
+
143
+ #### ❌ El Problema (sin `_`)
144
+
145
+ ```
146
+ routes/
147
+ ├── __root.tsx
148
+ ├── auth/
149
+ │ └── login.tsx → URL: /auth/login ❌
150
+ ├── app/
151
+ │ ├── dashboard.tsx → URL: /app/dashboard ❌
152
+ │ └── orders.tsx → URL: /app/orders ❌
153
+ ```
154
+
155
+ **Resultado:** Las URLs incluyen el nombre de la carpeta, lo cual es indeseable.
156
+
157
+ #### ✅ La Solución (con `_`)
158
+
159
+ ```
160
+ routes/
161
+ ├── __root.tsx
162
+ ├── _auth.tsx → NO agrega nada a la URL (solo layout)
163
+ ├── _auth/
164
+ │ └── login.tsx → URL: /login ✅
165
+
166
+ ├── _app.tsx → NO agrega nada a la URL (solo layout)
167
+ ├── _app/
168
+ │ ├── dashboard.tsx → URL: /dashboard ✅
169
+ │ └── orders.tsx → URL: /orders ✅
170
+ ```
171
+
172
+ #### Ejemplo de Implementación
173
+
174
+ ```typescript
175
+ // routes/_app.tsx - Layout protegido
176
+ import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
177
+ import { Sidebar, TopNav } from '@/shared/components/layout';
178
+ import { useAuthStore } from '@/modules/users/authentication/login/application/store';
179
+
180
+ export const Route = createFileRoute('/_app')({
181
+ beforeLoad: () => {
182
+ const { isAuthenticated } = useAuthStore.getState();
183
+ if (!isAuthenticated) {
184
+ throw redirect({ to: '/login' });
185
+ }
186
+ },
187
+ component: AppLayout,
188
+ });
189
+
190
+ function AppLayout() {
191
+ return (
192
+ <div className="flex h-screen">
193
+ <Sidebar />
194
+ <div className="flex-1 flex flex-col">
195
+ <TopNav />
196
+ <main className="flex-1 overflow-auto p-6">
197
+ <Outlet />
198
+ </main>
199
+ </div>
200
+ </div>
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### 3.2 Prefijo `.` (Punto) - Flat Routing
206
+
207
+ **Propósito:** Definir rutas anidadas **sin crear carpetas**.
208
+
209
+ ```
210
+ routes/
211
+ ├── orders.tsx → /orders (layout)
212
+ ├── orders.index.tsx → /orders
213
+ ├── orders.$orderId.tsx → /orders/:orderId
214
+ ├── orders.$orderId.edit.tsx → /orders/:orderId/edit
215
+ ```
216
+
217
+ #### ¿Cuándo usar `.` vs Carpetas?
218
+
219
+ | Escenario | Recomendación |
220
+ |-----------|---------------|
221
+ | Pocas rutas anidadas (2-3) | Flat con `.` |
222
+ | Muchas rutas anidadas (4+) | Carpetas |
223
+ | Rutas con componentes colocados | Carpetas con `-components/` |
224
+
225
+ ### 3.3 Prefijo `-` (Guión) - Ignorar Archivos
226
+
227
+ **Propósito:** Excluir archivos/carpetas de la generación de rutas para colocación de código.
228
+
229
+ ```
230
+ routes/
231
+ ├── orders/
232
+ │ ├── $orderId.tsx → /orders/:orderId ✅
233
+ │ ├── -components/ → ❌ Ignorado por el router
234
+ │ │ └── OrderHeader.tsx
235
+ │ └── -hooks/ → ❌ Ignorado por el router
236
+ │ └── useOrderCalculations.ts
237
+ ```
238
+
239
+ ### 3.4 Prefijo `$` (Dólar) - Parámetros Dinámicos
240
+
241
+ ```typescript
242
+ // routes/_app/orders/$orderId.tsx
243
+ import { createFileRoute } from '@tanstack/react-router';
244
+
245
+ export const Route = createFileRoute('/_app/orders/$orderId')({
246
+ component: OrderDetail,
247
+ });
248
+
249
+ function OrderDetail() {
250
+ const { orderId } = Route.useParams(); // Tipado automático
251
+ return <div>Order: {orderId}</div>;
252
+ }
253
+ ```
254
+
255
+ ### 3.5 Resumen de Prefijos
256
+
257
+ | Prefijo | Nombre | Efecto en URL | Uso Principal |
258
+ |---------|--------|---------------|---------------|
259
+ | `_` | Pathless | **No aparece** | Layouts sin path |
260
+ | `.` | Flat | Crea anidamiento | Evitar carpetas |
261
+ | `-` | Ignore | **No genera ruta** | Colocación de código |
262
+ | `$` | Dynamic | Captura valor | Parámetros de URL |
263
+ | `__` | Root | Raíz del árbol | Solo `__root.tsx` |
264
+
265
+ ---
266
+
267
+ ## 4. Organización de Archivos
268
+
269
+ ### 4.1 Estructura Enterprise (Module/Domain/Feature)
270
+
271
+ ```
272
+ src/
273
+ ├── main.tsx # React entry point
274
+ ├── router.tsx # TanStack Router config
275
+ ├── routeTree.gen.ts # Auto-generado (NO EDITAR)
276
+ ├── globals.css # Estilos globales + Tailwind
277
+
278
+ ├── routes/ # 🛣️ SOLO definición de rutas
279
+ │ ├── __root.tsx # Layout raíz (providers)
280
+ │ ├── index.tsx # Redirect inicial
281
+ │ ├── _auth.tsx # Layout público
282
+ │ ├── _auth/
283
+ │ │ └── login.tsx
284
+ │ ├── _app.tsx # Layout protegido
285
+ │ └── _app/
286
+ │ ├── dashboard.tsx
287
+ │ ├── sales/
288
+ │ │ ├── quotes.tsx
289
+ │ │ └── invoices.tsx
290
+ │ └── inventory/
291
+ │ └── products.tsx
292
+
293
+ ├── modules/ # 🏢 Lógica de negocio por módulo
294
+ │ ├── sales/ # MODULE
295
+ │ │ ├── quotes/ # DOMAIN
296
+ │ │ │ ├── cart/ # FEATURE
297
+ │ │ │ │ ├── domain/
298
+ │ │ │ │ │ ├── entities/
299
+ │ │ │ │ │ │ └── CartItem.ts
300
+ │ │ │ │ │ ├── repositories/ # Interfaces
301
+ │ │ │ │ │ │ └── ICartRepository.ts
302
+ │ │ │ │ │ ├── services/ # Domain services
303
+ │ │ │ │ │ │ └── CartCalculator.ts
304
+ │ │ │ │ │ └── types/
305
+ │ │ │ │ │ └── cart.types.ts
306
+ │ │ │ │ ├── application/
307
+ │ │ │ │ │ ├── use-cases/
308
+ │ │ │ │ │ │ ├── AddToCart.ts
309
+ │ │ │ │ │ │ └── RemoveFromCart.ts
310
+ │ │ │ │ │ ├── hooks/
311
+ │ │ │ │ │ │ └── useCart.ts
312
+ │ │ │ │ │ └── store/
313
+ │ │ │ │ │ └── cart.store.ts
314
+ │ │ │ │ ├── infrastructure/
315
+ │ │ │ │ │ ├── repositories/ # Implementations
316
+ │ │ │ │ │ │ └── CartRepository.ts
317
+ │ │ │ │ │ ├── api/
318
+ │ │ │ │ │ │ └── cart.api.ts
319
+ │ │ │ │ │ └── adapters/
320
+ │ │ │ │ └── presentation/
321
+ │ │ │ │ ├── components/
322
+ │ │ │ │ │ ├── CartList.tsx
323
+ │ │ │ │ │ └── CartItem.tsx
324
+ │ │ │ │ └── pages/
325
+ │ │ │ │ └── CartPage.tsx
326
+ │ │ │ └── products/ # FEATURE
327
+ │ │ │ ├── domain/
328
+ │ │ │ ├── application/
329
+ │ │ │ ├── infrastructure/
330
+ │ │ │ └── presentation/
331
+ │ │ └── billing/ # DOMAIN
332
+ │ │ ├── invoices/ # FEATURE
333
+ │ │ └── reports/ # FEATURE
334
+ │ │
335
+ │ ├── inventory/ # MODULE
336
+ │ │ ├── products/ # DOMAIN
337
+ │ │ │ ├── catalog/ # FEATURE
338
+ │ │ │ └── stock/ # FEATURE
339
+ │ │ └── warehouses/ # DOMAIN
340
+ │ │
341
+ │ └── users/ # MODULE
342
+ │ └── authentication/ # DOMAIN
343
+ │ ├── login/ # FEATURE
344
+ │ └── registration/ # FEATURE
345
+
346
+ ├── shared/ # 🔄 Código reutilizable
347
+ │ ├── components/
348
+ │ │ └── ui/ # shadcn/ui
349
+ │ ├── hooks/
350
+ │ │ ├── useDebounce.ts
351
+ │ │ └── useLocalStorage.ts
352
+ │ ├── lib/
353
+ │ │ ├── utils.ts # cn(), formatters
354
+ │ │ ├── api-client.ts # Axios/fetch config
355
+ │ │ └── env.ts # Variables de entorno tipadas
356
+ │ ├── types/
357
+ │ │ └── common.types.ts
358
+ │ └── constants/
359
+
360
+ ├── app/ # 🎯 Configuración global
361
+ │ ├── store/ # Global store (si necesario)
362
+ │ ├── providers/ # Context providers
363
+ │ │ ├── ThemeProvider.tsx
364
+ │ │ └── QueryProvider.tsx
365
+ │ └── config/
366
+
367
+ ├── infrastructure/ # 🔌 Servicios externos globales
368
+ │ ├── api/ # API configuration
369
+ │ ├── storage/ # IndexedDB, localStorage
370
+ │ └── pwa/ # PWA configuration
371
+
372
+ ├── assets/ # 📁 Recursos estáticos
373
+ │ ├── fonts/
374
+ │ │ ├── Inter_18pt-Light.ttf
375
+ │ │ ├── Inter_18pt-Regular.ttf
376
+ │ │ └── Inter_18pt-Bold.ttf
377
+ │ └── images/
378
+ │ └── logos/
379
+
380
+ └── styles/
381
+ └── globals.css
382
+ ```
383
+
384
+ ### 4.2 Jerarquía de Carpetas
385
+
386
+ ```
387
+ MODULE (módulo de negocio)
388
+ └── DOMAIN (área funcional)
389
+ └── FEATURE (funcionalidad específica)
390
+ ├── domain/ # Reglas de negocio
391
+ ├── application/ # Casos de uso y estado
392
+ ├── infrastructure/ # Implementaciones externas
393
+ └── presentation/ # UI
394
+ ```
395
+
396
+ ### 4.3 Organización de Imports
397
+
398
+ ```typescript
399
+ // 1. Librerías externas
400
+ import React from 'react';
401
+ import { create } from 'zustand';
402
+ import { useQuery } from '@tanstack/react-query';
403
+
404
+ // 2. Módulos internos (por capa, de interno a externo)
405
+ import { CartItem } from '../domain/entities/CartItem';
406
+ import { addToCartUseCase } from '../application/use-cases/AddToCart';
407
+ import { cartRepository } from '../infrastructure/repositories/CartRepository';
408
+
409
+ // 3. Shared
410
+ import { cn } from '@/shared/lib/utils';
411
+ import { Button } from '@/shared/components/ui/button';
412
+
413
+ // 4. Types
414
+ import type { Cart } from '../domain/types/cart.types';
415
+ ```
416
+
417
+ ---
418
+
419
+ ## 5. Estándares de Componentes
420
+
421
+ ### 5.1 Estrategia de Componentes
422
+
423
+ | Prioridad | Acción |
424
+ |-----------|--------|
425
+ | **1. Si no existe** | Preguntar al usuario: [1] Usar shadcn directamente, [2] Crear para componente |
426
+ | **3. Shadcn fallback** | Solo usar registro MCP Shadcn si el usuario elige opción [1] |
427
+
428
+ > **Beneficio:** 90% menos bugs usando componentes existentes vs creación manual.
429
+
430
+ ### 5.2 Estructura de Componentes
431
+
432
+ ```typescript
433
+ interface ComponentProps {
434
+ // Required props
435
+ children: React.ReactNode;
436
+ // Optional props with defaults
437
+ className?: string;
438
+ variant?: 'default' | 'secondary';
439
+ 'data-testid'?: string;
440
+ }
441
+
442
+ export const Component = memo<ComponentProps>(({
443
+ children,
444
+ className,
445
+ variant = 'default',
446
+ 'data-testid': testId = 'component'
447
+ }) => {
448
+ return (
449
+ <div
450
+ className={cn(baseStyles, variantStyles[variant], className)}
451
+ data-testid={testId}
452
+ >
453
+ {children}
454
+ </div>
455
+ );
456
+ });
457
+
458
+ Component.displayName = 'Component';
459
+ ```
460
+
461
+ ### 5.3 Convenciones de Nombrado
462
+
463
+ | Elemento | Convención | Ejemplo |
464
+ |----------|------------|---------|
465
+ | Componentes | PascalCase | `OrderCard.tsx` |
466
+ | Archivos | kebab-case | `order-card.tsx` |
467
+ | data-testid | kebab-case | `data-testid="order-card"` |
468
+ | Props/funciones | camelCase | `onSubmit`, `isLoading` |
469
+ | Constantes | SCREAMING_SNAKE | `MAX_ITEMS` |
470
+
471
+ ### 5.4 Guidelines de Props
472
+
473
+ - Siempre definir interfaces TypeScript para props
474
+ - Usar props opcionales con defaults sensatos
475
+ - Incluir `className` para overrides de estilos
476
+ - Agregar `data-testid` para testing
477
+ - Documentar props complejas con JSDoc
478
+
479
+ ---
480
+
481
+ ## 6. Patrones de Estado
482
+
483
+ ### 6.1 Estructura de Zustand Store
484
+
485
+ ```typescript
486
+ // modules/sales/quotes/cart/application/store/cart.store.ts
487
+ import { create } from 'zustand';
488
+ import { devtools } from 'zustand/middleware';
489
+ import type { CartItem } from '../../domain/entities/CartItem';
490
+ import { addToCartUseCase } from '../use-cases/AddToCart';
491
+ import { removeFromCartUseCase } from '../use-cases/RemoveFromCart';
492
+
493
+ interface CartState {
494
+ // Domain entities
495
+ items: CartItem[];
496
+
497
+ // UI state
498
+ loading: boolean;
499
+ error: string | null;
500
+
501
+ // Actions (delegan a use cases)
502
+ addItem: (productId: string, quantity: number) => Promise<void>;
503
+ removeItem: (itemId: string) => Promise<void>;
504
+ clearCart: () => void;
505
+ clearError: () => void;
506
+ }
507
+
508
+ export const useCartStore = create<CartState>()(
509
+ devtools(
510
+ (set, get) => ({
511
+ // Initial state
512
+ items: [],
513
+ loading: false,
514
+ error: null,
515
+
516
+ // Actions
517
+ addItem: async (productId, quantity) => {
518
+ set({ loading: true, error: null });
519
+ try {
520
+ const newItem = await addToCartUseCase.execute({ productId, quantity });
521
+ set((state) => ({
522
+ items: [...state.items, newItem],
523
+ loading: false
524
+ }));
525
+ } catch (error) {
526
+ set({ error: (error as Error).message, loading: false });
527
+ }
528
+ },
529
+
530
+ removeItem: async (itemId) => {
531
+ set({ loading: true, error: null });
532
+ try {
533
+ await removeFromCartUseCase.execute(itemId);
534
+ set((state) => ({
535
+ items: state.items.filter(item => item.id !== itemId),
536
+ loading: false
537
+ }));
538
+ } catch (error) {
539
+ set({ error: (error as Error).message, loading: false });
540
+ }
541
+ },
542
+
543
+ clearCart: () => set({ items: [] }),
544
+ clearError: () => set({ error: null }),
545
+ }),
546
+ { name: 'cartStore' }
547
+ )
548
+ );
549
+ ```
550
+
551
+ ### 6.2 Cuándo Usar Cada Tipo de Estado
552
+
553
+ | Tipo | Cuándo Usar | Herramienta |
554
+ |------|-------------|-------------|
555
+ | **Server State** | Datos del API, cache | TanStack Query |
556
+ | **Global Client State** | Auth, theme, cart | Zustand |
557
+ | **Local Component State** | Forms, UI toggles | useState/useReducer |
558
+ | **URL State** | Filtros, paginación | TanStack Router search params |
559
+
560
+ ### 6.3 Integración con TanStack Query
561
+
562
+ ```typescript
563
+ // modules/sales/quotes/products/infrastructure/api/products.api.ts
564
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
565
+ import { productRepository } from '../repositories/ProductRepository';
566
+
567
+ export const productKeys = {
568
+ all: ['products'] as const,
569
+ lists: () => [...productKeys.all, 'list'] as const,
570
+ list: (filters: ProductFilters) => [...productKeys.lists(), filters] as const,
571
+ detail: (id: string) => [...productKeys.all, 'detail', id] as const,
572
+ };
573
+
574
+ export function useProducts(filters: ProductFilters) {
575
+ return useQuery({
576
+ queryKey: productKeys.list(filters),
577
+ queryFn: () => productRepository.getAll(filters),
578
+ });
579
+ }
580
+
581
+ export function useProduct(id: string) {
582
+ return useQuery({
583
+ queryKey: productKeys.detail(id),
584
+ queryFn: () => productRepository.getById(id),
585
+ enabled: !!id,
586
+ });
587
+ }
588
+ ```
589
+
590
+ ---
591
+
592
+ ## 7. Estándares de Testing
593
+
594
+ ### 7.1 Estrategia de Testing
595
+
596
+ | Tipo | Cobertura | Herramienta | Qué Testear |
597
+ |------|-----------|-------------|-------------|
598
+ | **Unit** | Alta | Vitest | Entities, use cases, utilities |
599
+ | **Integration** | Media | Vitest + MSW | Feature workflows, API integration |
600
+ | **Component** | Media | React Testing Library | User interactions, accessibility |
601
+ | **E2E** | Baja | Playwright | Critical user journeys |
602
+
603
+ ### 7.2 Estructura de Tests
604
+
605
+ ```typescript
606
+ // modules/sales/quotes/cart/application/use-cases/__tests__/AddToCart.test.ts
607
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
608
+ import { addToCartUseCase } from '../AddToCart';
609
+ import { mockCartRepository } from '../../__mocks__/cartRepository.mock';
610
+
611
+ describe('AddToCart UseCase', () => {
612
+ beforeEach(() => {
613
+ vi.clearAllMocks();
614
+ });
615
+
616
+ it('should add item to cart successfully', async () => {
617
+ const input = { productId: 'prod-1', quantity: 2 };
618
+
619
+ const result = await addToCartUseCase.execute(input);
620
+
621
+ expect(result).toMatchObject({
622
+ productId: 'prod-1',
623
+ quantity: 2,
624
+ });
625
+ expect(mockCartRepository.save).toHaveBeenCalledWith(expect.objectContaining(input));
626
+ });
627
+
628
+ it('should throw error when quantity is invalid', async () => {
629
+ const input = { productId: 'prod-1', quantity: -1 };
630
+
631
+ await expect(addToCartUseCase.execute(input)).rejects.toThrow(
632
+ 'La cantidad debe ser mayor a 0'
633
+ );
634
+ });
635
+ });
636
+ ```
637
+
638
+ ### 7.3 Component Testing
639
+
640
+ ```typescript
641
+ // modules/sales/quotes/cart/presentation/components/__tests__/CartItem.test.tsx
642
+ import { describe, it, expect } from 'vitest';
643
+ import { render, screen } from '@testing-library/react';
644
+ import userEvent from '@testing-library/user-event';
645
+ import { axe } from 'vitest-axe';
646
+ import { CartItem } from '../CartItem';
647
+
648
+ describe('CartItem', () => {
649
+ const defaultProps = {
650
+ item: { id: '1', name: 'Producto Test', quantity: 2, price: 100 },
651
+ onRemove: vi.fn(),
652
+ };
653
+
654
+ it('should render correctly with default props', () => {
655
+ render(<CartItem {...defaultProps} />);
656
+
657
+ expect(screen.getByTestId('cart-item')).toBeInTheDocument();
658
+ expect(screen.getByText('Producto Test')).toBeInTheDocument();
659
+ });
660
+
661
+ it('should handle remove action', async () => {
662
+ const user = userEvent.setup();
663
+ render(<CartItem {...defaultProps} />);
664
+
665
+ await user.click(screen.getByRole('button', { name: /eliminar/i }));
666
+
667
+ expect(defaultProps.onRemove).toHaveBeenCalledWith('1');
668
+ });
669
+
670
+ it('should be accessible', async () => {
671
+ const { container } = render(<CartItem {...defaultProps} />);
672
+ const results = await axe(container);
673
+
674
+ expect(results).toHaveNoViolations();
675
+ });
676
+ });
677
+ ```
678
+
679
+ ---
680
+
681
+ ## 8. Accesibilidad
682
+
683
+ ### 8.1 Cumplimiento WCAG 2.1 AA
684
+
685
+ | Requisito | Implementación |
686
+ |-----------|----------------|
687
+ | Elementos semánticos | Usar HTML semántico (`<nav>`, `<main>`, `<article>`) |
688
+ | Jerarquía de headings | Un solo `<h1>`, headings en orden descendente |
689
+ | Navegación por teclado | Todos los elementos interactivos accesibles con Tab |
690
+ | Screen readers | Labels descriptivos, roles ARIA cuando necesario |
691
+ | Contraste de color | Mínimo 4.5:1 para texto normal |
692
+
693
+ ### 8.2 Implementación ARIA
694
+
695
+ ```typescript
696
+ // ✅ Correcto - HTML semántico primero
697
+ <button onClick={handleSubmit}>Guardar</button>
698
+
699
+ // ✅ Correcto - ARIA cuando es necesario
700
+ <div
701
+ role="tabpanel"
702
+ aria-labelledby="tab-1"
703
+ aria-expanded={isOpen}
704
+ >
705
+ {content}
706
+ </div>
707
+
708
+ // ❌ Incorrecto - ARIA innecesario
709
+ <button role="button" aria-label="button">Guardar</button>
710
+ ```
711
+
712
+ ---
713
+
714
+ ## 9. Rendimiento
715
+
716
+ ### 9.1 Optimización de Bundle
717
+
718
+ | Estrategia | Implementación |
719
+ |------------|----------------|
720
+ | Code splitting | Por ruta (automático con TanStack Router) |
721
+ | Lazy loading | `React.lazy()` para componentes no críticos |
722
+ | Tree shaking | Imports específicos, no barrel exports en shared |
723
+ | Bundle budget | Máximo 500KB total |
724
+
725
+ ### 9.2 Rendimiento en Runtime
726
+
727
+ ```typescript
728
+ // ✅ React.memo para componentes costosos
729
+ export const ExpensiveList = memo<ListProps>(({ items }) => {
730
+ return items.map(item => <ExpensiveItem key={item.id} item={item} />);
731
+ });
732
+
733
+ // ✅ useMemo para cálculos costosos
734
+ const sortedItems = useMemo(() =>
735
+ items.sort((a, b) => a.name.localeCompare(b.name)),
736
+ [items]
737
+ );
738
+
739
+ // ✅ useCallback para handlers pasados a children
740
+ const handleClick = useCallback((id: string) => {
741
+ onSelect(id);
742
+ }, [onSelect]);
743
+ ```
744
+
745
+ ### 9.3 Loading Performance
746
+
747
+ - Optimización de imágenes y lazy loading
748
+ - Skeleton screens para estados de carga
749
+ - Prefetch de recursos críticos
750
+ - Virtual scrolling para listas grandes
751
+
752
+ ---
753
+
754
+ ## 10. Seguridad
755
+
756
+ ### 10.1 Seguridad Client-Side
757
+
758
+ | Riesgo | Mitigación |
759
+ |--------|------------|
760
+ | API keys expuestas | Nunca en código frontend, usar variables de entorno server-side |
761
+ | XSS | Sanitización de inputs, no usar `dangerouslySetInnerHTML` |
762
+ | Datos sensibles | No almacenar en localStorage sin encriptar |
763
+
764
+ ### 10.2 Autenticación
765
+
766
+ ```typescript
767
+ // Manejo seguro de tokens
768
+ const useAuthStore = create<AuthState>((set) => ({
769
+ token: null,
770
+
771
+ setToken: (token: string) => {
772
+ // Almacenar en memoria, no localStorage
773
+ set({ token });
774
+ },
775
+
776
+ logout: () => {
777
+ set({ token: null });
778
+ // Limpiar cualquier dato sensible
779
+ },
780
+ }));
781
+ ```
782
+
783
+ ---
784
+
785
+ ## 11. Manejo de Errores
786
+
787
+ ### 11.1 Error Boundaries por Feature
788
+
789
+ ```typescript
790
+ // shared/components/feedback/ErrorBoundary.tsx
791
+ import { Component, ErrorInfo, ReactNode } from 'react';
792
+
793
+ interface Props {
794
+ children: ReactNode;
795
+ fallback?: ReactNode;
796
+ }
797
+
798
+ interface State {
799
+ hasError: boolean;
800
+ error: Error | null;
801
+ }
802
+
803
+ export class ErrorBoundary extends Component<Props, State> {
804
+ state: State = { hasError: false, error: null };
805
+
806
+ static getDerivedStateFromError(error: Error): State {
807
+ return { hasError: true, error };
808
+ }
809
+
810
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
811
+ console.error('Error capturado:', error, errorInfo);
812
+ // Enviar a servicio de logging
813
+ }
814
+
815
+ render() {
816
+ if (this.state.hasError) {
817
+ return this.props.fallback || (
818
+ <div className="p-4 text-center">
819
+ <h2 className="text-lg font-semibold text-red-600">
820
+ Algo salió mal
821
+ </h2>
822
+ <p className="text-gray-600">
823
+ Por favor, intenta recargar la página
824
+ </p>
825
+ </div>
826
+ );
827
+ }
828
+
829
+ return this.props.children;
830
+ }
831
+ }
832
+ ```
833
+
834
+ ### 11.2 Manejo de Errores en API
835
+
836
+ ```typescript
837
+ // shared/lib/api-client.ts
838
+ import axios, { AxiosError } from 'axios';
839
+
840
+ const apiClient = axios.create({
841
+ baseURL: import.meta.env.VITE_API_URL,
842
+ });
843
+
844
+ apiClient.interceptors.response.use(
845
+ (response) => response,
846
+ (error: AxiosError<{ message: string }>) => {
847
+ const message = error.response?.data?.message || 'Error de conexión';
848
+
849
+ // Mensaje amigable para el usuario
850
+ return Promise.reject(new Error(message));
851
+ }
852
+ );
853
+ ```
854
+
855
+ ---
856
+
857
+ ## 12. Progressive Web App
858
+
859
+ ### 12.1 Features PWA
860
+
861
+ | Feature | Implementación |
862
+ |---------|----------------|
863
+ | Service Worker | Caching y soporte offline |
864
+ | Web App Manifest | Instalabilidad |
865
+ | Push Notifications | Cuando sea necesario |
866
+ | Background Sync | Sincronización al restaurar conexión |
867
+
868
+ ### 12.2 Estrategia Offline
869
+
870
+ | Tipo de Recurso | Estrategia |
871
+ |-----------------|------------|
872
+ | Assets estáticos | Cache-first |
873
+ | Datos dinámicos | Network-first con fallback |
874
+ | Páginas offline | Fallback pre-cacheado |
875
+ | Formularios | Queue y sync cuando online |
876
+
877
+ ---
878
+
879
+ ## 13. Estándares de Idioma
880
+
881
+ ### 13.1 Español para Todo Contenido Visible al Usuario
882
+
883
+ **REGLA CRÍTICA: Todo texto visible al usuario final DEBE estar en español.**
884
+
885
+ #### ✅ Español Requerido
886
+
887
+ - Labels de UI, botones, formularios, mensajes, notificaciones
888
+ - Respuestas de API, errores de validación, templates de email
889
+ - Cualquier texto que el usuario vea (frontend o backend)
890
+
891
+ #### ✅ Inglés Requerido
892
+
893
+ - Código (variables, funciones, clases)
894
+ - Logs técnicos, comentarios, commits de git
895
+ - Documentación técnica para desarrolladores
896
+
897
+ #### ❌ Nunca Mezclar Idiomas en Texto Visible
898
+
899
+ ```typescript
900
+ // ✅ CORRECTO
901
+ <Button>Guardar</Button>
902
+ toast.success("Datos guardados correctamente");
903
+ throw new BadRequestException('No se pudo crear el usuario');
904
+
905
+ // ❌ INCORRECTO
906
+ <Button>Save cambios</Button>
907
+ toast.error("Failed al guardar");
908
+ throw new BadRequestException('Invalid datos proporcionados');
909
+ ```
910
+
911
+ ### 13.2 Implementación
912
+
913
+ ```typescript
914
+ // shared/constants/messages.ts
915
+ export const MESSAGES = {
916
+ SUCCESS: {
917
+ SAVED: 'Datos guardados correctamente',
918
+ DELETED: 'Elemento eliminado correctamente',
919
+ UPDATED: 'Información actualizada',
920
+ },
921
+ ERROR: {
922
+ GENERIC: 'Ha ocurrido un error. Por favor, intenta de nuevo',
923
+ NOT_FOUND: 'El recurso solicitado no fue encontrado',
924
+ UNAUTHORIZED: 'No tienes permisos para realizar esta acción',
925
+ VALIDATION: 'Por favor, verifica los datos ingresados',
926
+ },
927
+ LOADING: {
928
+ DEFAULT: 'Cargando...',
929
+ SAVING: 'Guardando...',
930
+ PROCESSING: 'Procesando...',
931
+ },
932
+ } as const;
933
+ ```
934
+
935
+ ---
936
+
937
+ ## 14. Consideraciones Generales
938
+
939
+ ### 14.1 Gestor de Paquetes
940
+
941
+ **Regla:** Usar `pnpm` como gestor de paquetes por defecto, pero respetar el gestor existente en proyectos ya iniciados.
942
+
943
+ | Escenario | Acción | Razón |
944
+ |-----------|--------|-------|
945
+ | Proyecto nuevo | Usar `pnpm` | Mejor rendimiento y manejo de dependencias |
946
+ | Proyecto con `package-lock.json` | Continuar con `npm` | Evitar conflictos de lockfiles |
947
+ | Proyecto con `yarn.lock` | Continuar con `yarn` | Evitar conflictos de lockfiles |
948
+ | Proyecto con `pnpm-lock.yaml` | Continuar con `pnpm` | Ya está configurado |
949
+
950
+ #### Cómo Identificar el Gestor Actual
951
+
952
+ ```bash
953
+ ls -la | grep -E "package-lock|yarn.lock|pnpm-lock"
954
+ ```
955
+
956
+ | Archivo encontrado | Gestor a usar |
957
+ |--------------------|---------------|
958
+ | `package-lock.json` | `npm` |
959
+ | `yarn.lock` | `yarn` |
960
+ | `pnpm-lock.yaml` | `pnpm` |
961
+ | Ninguno | `pnpm` (proyecto nuevo) |
962
+
963
+ #### Comandos Equivalentes
964
+
965
+ | Acción | pnpm | npm | yarn |
966
+ |--------|------|-----|------|
967
+ | Instalar | `pnpm install` | `npm install` | `yarn` |
968
+ | Agregar dep | `pnpm add <pkg>` | `npm install <pkg>` | `yarn add <pkg>` |
969
+ | Agregar dev | `pnpm add -D <pkg>` | `npm install -D <pkg>` | `yarn add -D <pkg>` |
970
+ | Ejecutar script | `pnpm <script>` | `npm run <script>` | `yarn <script>` |
971
+ | Remover | `pnpm remove <pkg>` | `npm uninstall <pkg>` | `yarn remove <pkg>` |
972
+
973
+ > **⚠️ Importante:** Nunca mezclar gestores de paquetes en un mismo proyecto.
974
+
975
+ ---
976
+
977
+ ## 15. Configuración Base
978
+
979
+ ### 15.1 `vite.config.ts`
980
+
981
+ ```typescript
982
+ import { defineConfig } from 'vite';
983
+ import react from '@vitejs/plugin-react';
984
+ import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
985
+ import viteTsConfigPaths from 'vite-tsconfig-paths';
986
+ import tailwindcss from '@tailwindcss/vite';
987
+
988
+ export default defineConfig({
989
+ plugins: [
990
+ // TanStack Router DEBE ir primero
991
+ TanStackRouterVite({
992
+ target: 'react',
993
+ autoCodeSplitting: true,
994
+ routesDirectory: './src/routes',
995
+ generatedRouteTree: './src/routeTree.gen.ts',
996
+ routeFileIgnorePrefix: '-',
997
+ }),
998
+ react(),
999
+ viteTsConfigPaths(),
1000
+ tailwindcss(),
1001
+ ],
1002
+ resolve: {
1003
+ alias: {
1004
+ '@': '/src',
1005
+ '@modules': '/src/modules',
1006
+ '@shared': '/src/shared',
1007
+ '@app': '/src/app',
1008
+ },
1009
+ },
1010
+ server: {
1011
+ port: 3000,
1012
+ },
1013
+ build: {
1014
+ outDir: 'dist',
1015
+ },
1016
+ });
1017
+ ```
1018
+
1019
+ ### 15.2 `src/router.tsx`
1020
+
1021
+ ```typescript
1022
+ import { createRouter as createTanStackRouter } from '@tanstack/react-router';
1023
+ import { QueryClient } from '@tanstack/react-query';
1024
+ import { routerWithQueryClient } from '@tanstack/react-router-with-query';
1025
+ import { routeTree } from './routeTree.gen';
1026
+
1027
+ function NotFoundPage() {
1028
+ return (
1029
+ <div className="min-h-screen flex items-center justify-center">
1030
+ <div className="text-center">
1031
+ <h1 className="text-6xl font-bold">404</h1>
1032
+ <p className="mt-4">Página no encontrada</p>
1033
+ </div>
1034
+ </div>
1035
+ );
1036
+ }
1037
+
1038
+ function ErrorPage({ error }: { error: Error }) {
1039
+ return (
1040
+ <div className="min-h-screen flex items-center justify-center">
1041
+ <div className="text-center">
1042
+ <h1 className="text-2xl font-bold text-red-600">Error</h1>
1043
+ <p className="mt-4">{error.message}</p>
1044
+ </div>
1045
+ </div>
1046
+ );
1047
+ }
1048
+
1049
+ export function createRouter() {
1050
+ const queryClient = new QueryClient({
1051
+ defaultOptions: {
1052
+ queries: {
1053
+ staleTime: 60 * 1000,
1054
+ refetchOnWindowFocus: false,
1055
+ },
1056
+ },
1057
+ });
1058
+
1059
+ const router = createTanStackRouter({
1060
+ routeTree,
1061
+ context: { queryClient },
1062
+ defaultPreload: 'intent',
1063
+ scrollRestoration: true,
1064
+ defaultNotFoundComponent: NotFoundPage,
1065
+ defaultErrorComponent: ErrorPage,
1066
+ });
1067
+
1068
+ return routerWithQueryClient(router, queryClient);
1069
+ }
1070
+
1071
+ declare module '@tanstack/react-router' {
1072
+ interface Register {
1073
+ router: ReturnType<typeof createRouter>;
1074
+ }
1075
+ }
1076
+ ```
1077
+
1078
+ ### 15.3 `src/main.tsx`
1079
+
1080
+ ```typescript
1081
+ import { StrictMode } from 'react';
1082
+ import { createRoot } from 'react-dom/client';
1083
+ import { RouterProvider } from '@tanstack/react-router';
1084
+ import { createRouter } from './router';
1085
+ import './globals.css';
1086
+
1087
+ const router = createRouter();
1088
+
1089
+ createRoot(document.getElementById('root')!).render(
1090
+ <StrictMode>
1091
+ <RouterProvider router={router} />
1092
+ </StrictMode>
1093
+ );
1094
+ ```
1095
+
1096
+ ### 15.4 `src/routes/__root.tsx`
1097
+
1098
+ ```typescript
1099
+ import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
1100
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
1101
+ import { Toaster } from 'sonner';
1102
+
1103
+ interface RouterContext {
1104
+ queryClient: QueryClient;
1105
+ }
1106
+
1107
+ export const Route = createRootRouteWithContext<RouterContext>()({
1108
+ component: RootComponent,
1109
+ });
1110
+
1111
+ function RootComponent() {
1112
+ const { queryClient } = Route.useRouteContext();
1113
+
1114
+ return (
1115
+ <QueryClientProvider client={queryClient}>
1116
+ <Outlet />
1117
+ <Toaster position="bottom-right" />
1118
+ </QueryClientProvider>
1119
+ );
1120
+ }
1121
+ ```
1122
+
1123
+ ### 15.5 `vitest.config.ts`
1124
+
1125
+ ```typescript
1126
+ import { defineConfig } from 'vitest/config';
1127
+ import react from '@vitejs/plugin-react';
1128
+ import viteTsConfigPaths from 'vite-tsconfig-paths';
1129
+
1130
+ export default defineConfig({
1131
+ plugins: [react(), viteTsConfigPaths()],
1132
+ test: {
1133
+ globals: true,
1134
+ environment: 'jsdom',
1135
+ setupFiles: ['./src/test/setup.ts'],
1136
+ include: ['src/**/*.{test,spec}.{ts,tsx}'],
1137
+ coverage: {
1138
+ provider: 'v8',
1139
+ reporter: ['text', 'json', 'html'],
1140
+ exclude: [
1141
+ 'node_modules/',
1142
+ 'src/test/',
1143
+ '**/*.d.ts',
1144
+ 'src/routeTree.gen.ts',
1145
+ ],
1146
+ },
1147
+ },
1148
+ });
1149
+ ```
1150
+
1151
+ ---
1152
+
1153
+ ## 16. Checklist de Implementación
1154
+
1155
+ ### Configuración Inicial
1156
+
1157
+ - [ ] Instalar dependencias: `@tanstack/react-router`, `@tanstack/router-plugin`, `@tanstack/react-query`
1158
+ - [ ] Crear `vite.config.ts` con TanStackRouterVite plugin (PRIMERO)
1159
+ - [ ] Crear `src/router.tsx` con configuración del router
1160
+ - [ ] Crear `src/main.tsx` con RouterProvider
1161
+ - [ ] Crear `src/routes/__root.tsx`
1162
+ - [ ] Configurar path aliases en `tsconfig.json`
1163
+ - [ ] Agregar `routeTree.gen.ts` a `.prettierignore` y `.eslintignore`
1164
+ - [ ] Configurar Vitest
1165
+
1166
+ ### Estructura de Rutas
1167
+
1168
+ - [ ] Crear layouts pathless (`_auth.tsx`, `_app.tsx`)
1169
+ - [ ] Implementar guards de autenticación en `beforeLoad`
1170
+ - [ ] Usar `$param` para rutas dinámicas
1171
+ - [ ] Usar `-` para carpetas de colocación
1172
+ - [ ] Verificar que las URLs sean correctas
1173
+
1174
+ ### Arquitectura
1175
+
1176
+ - [ ] Organizar módulos siguiendo Module/Domain/Feature
1177
+ - [ ] Implementar capas de Clean Architecture por feature
1178
+ - [ ] Configurar stores Zustand por feature
1179
+ - [ ] Configurar TanStack Query para server state
1180
+ - [ ] Crear barrel exports (`index.ts`) por feature
1181
+
1182
+ ### Calidad
1183
+
1184
+ - [ ] Configurar Vitest con coverage
1185
+ - [ ] Agregar tests unitarios para use cases
1186
+ - [ ] Agregar tests de componentes
1187
+ - [ ] Verificar accesibilidad (axe)
1188
+ - [ ] Validar textos en español
1189
+
1190
+ ---
1191
+
1192
+ ## Referencias
1193
+
1194
+ - [TanStack Router Documentation](https://tanstack.com/router/latest)
1195
+ - [TanStack Query Documentation](https://tanstack.com/query/latest)
1196
+ - [Zustand Documentation](https://zustand-demo.pmnd.rs/)
1197
+ - [Vite Documentation](https://vitejs.dev/)
1198
+ - [Vitest Documentation](https://vitest.dev/)
1199
+ - [Clean Architecture - Robert C. Martin](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)