# PATRON DE PERFORMANCE **Version:** 1.0.0 **Fecha:** 2025-12-08 **Prioridad:** RECOMENDADA - Seguir para optimizacion **Sistema:** SIMCO + CAPVED --- ## PROPOSITO Definir patrones de optimizacion de rendimiento para todas las capas del sistema, asegurando tiempos de respuesta aceptables y uso eficiente de recursos. --- ## 1. METRICAS OBJETIVO ``` ╔══════════════════════════════════════════════════════════════════════╗ ║ OBJETIVOS DE PERFORMANCE ║ ╠══════════════════════════════════════════════════════════════════════╣ ║ ║ ║ API Response Time: ║ ║ • P50: < 100ms ║ ║ • P95: < 500ms ║ ║ • P99: < 1000ms ║ ║ ║ ║ Database Queries: ║ ║ • Simple query: < 10ms ║ ║ • Complex query: < 100ms ║ ║ • Report query: < 1000ms ║ ║ ║ ║ Frontend: ║ ║ • First Contentful Paint: < 1.5s ║ ║ • Time to Interactive: < 3s ║ ║ • Largest Contentful Paint: < 2.5s ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════╝ ``` --- ## 2. DATABASE PERFORMANCE ### 2.1 Indices Efectivos ```sql -- Indices para columnas frecuentemente filtradas CREATE INDEX idx_users_email ON auth.users(email); CREATE INDEX idx_users_status ON auth.users(status); -- Indice compuesto para queries frecuentes CREATE INDEX idx_orders_user_created ON core.orders(user_id, created_at DESC); -- Indice parcial para datos activos CREATE INDEX idx_users_active ON auth.users(email) WHERE status = 'active'; -- Indice para busqueda de texto CREATE INDEX idx_products_name_gin ON core.products USING gin(to_tsvector('spanish', name)); ``` ### 2.2 Analisis de Queries ```sql -- Ver plan de ejecucion EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'uuid-123' AND created_at > NOW() - INTERVAL '30 days'; -- Identificar queries lentas SELECT query, calls, mean_time, total_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10; ``` ### 2.3 Evitar N+1 Queries ```typescript // ❌ INCORRECTO: N+1 queries async findAllWithOrders(): Promise { const users = await this.userRepository.find(); // N queries adicionales para cargar orders for (const user of users) { user.orders = await this.orderRepository.find({ where: { userId: user.id } }); } return users; } // ✅ CORRECTO: Join en una query async findAllWithOrders(): Promise { return this.userRepository.find({ relations: ['orders'], // TypeORM hace JOIN }); } // ✅ CORRECTO: QueryBuilder con control async findAllWithOrders(): Promise { return this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.orders', 'order') .where('user.status = :status', { status: 'active' }) .orderBy('user.createdAt', 'DESC') .getMany(); } ``` ### 2.4 Paginacion Eficiente ```typescript // ❌ INCORRECTO: OFFSET para paginas grandes async findPaginated(page: number, limit: number) { return this.repository.find({ skip: (page - 1) * limit, // Lento en paginas grandes take: limit, }); } // ✅ CORRECTO: Cursor-based pagination async findPaginatedByCursor(cursor?: string, limit: number = 20) { const qb = this.repository .createQueryBuilder('item') .orderBy('item.createdAt', 'DESC') .take(limit + 1); // +1 para saber si hay mas if (cursor) { const decodedCursor = this.decodeCursor(cursor); qb.where('item.createdAt < :cursor', { cursor: decodedCursor }); } const items = await qb.getMany(); const hasMore = items.length > limit; if (hasMore) { items.pop(); // Remover el extra } return { data: items, nextCursor: hasMore ? this.encodeCursor(items[items.length - 1]) : null, hasMore, }; } ``` ### 2.5 Select Solo Campos Necesarios ```typescript // ❌ INCORRECTO: Traer toda la entidad const users = await this.userRepository.find(); // ✅ CORRECTO: Solo campos necesarios const users = await this.userRepository.find({ select: ['id', 'email', 'firstName'], }); // ✅ CORRECTO: Con QueryBuilder const users = await this.userRepository .createQueryBuilder('user') .select(['user.id', 'user.email', 'user.firstName']) .getMany(); ``` --- ## 3. BACKEND PERFORMANCE ### 3.1 Caching con Redis ```typescript // src/shared/cache/cache.service.ts import { Injectable, Inject } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; @Injectable() export class CacheService { constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} async get(key: string): Promise { return this.cacheManager.get(key); } async set(key: string, value: T, ttlSeconds: number): Promise { await this.cacheManager.set(key, value, ttlSeconds * 1000); } async del(key: string): Promise { await this.cacheManager.del(key); } async delByPattern(pattern: string): Promise { const keys = await this.cacheManager.store.keys(pattern); await Promise.all(keys.map(key => this.cacheManager.del(key))); } } ``` ### 3.2 Cache Decorator ```typescript // src/shared/decorators/cached.decorator.ts import { SetMetadata } from '@nestjs/common'; export const CACHE_KEY = 'cache_key'; export const CACHE_TTL = 'cache_ttl'; export const Cached = (key: string, ttlSeconds: number = 300) => { return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { SetMetadata(CACHE_KEY, key)(target, propertyKey, descriptor); SetMetadata(CACHE_TTL, ttlSeconds)(target, propertyKey, descriptor); }; }; // Interceptor que implementa el caching @Injectable() export class CacheInterceptor implements NestInterceptor { constructor( private readonly cacheService: CacheService, private readonly reflector: Reflector, ) {} async intercept(context: ExecutionContext, next: CallHandler): Promise> { const cacheKey = this.reflector.get(CACHE_KEY, context.getHandler()); if (!cacheKey) { return next.handle(); } const cacheTtl = this.reflector.get(CACHE_TTL, context.getHandler()) || 300; const request = context.switchToHttp().getRequest(); const fullKey = `${cacheKey}:${JSON.stringify(request.query)}`; const cached = await this.cacheService.get(fullKey); if (cached) { return of(cached); } return next.handle().pipe( tap(async (data) => { await this.cacheService.set(fullKey, data, cacheTtl); }), ); } } ``` ### 3.3 Uso de Cache en Service ```typescript // src/modules/product/services/product.service.ts @Injectable() export class ProductService { constructor( private readonly repository: Repository, private readonly cacheService: CacheService, ) {} async findAll(query: ProductQueryDto): Promise { const cacheKey = `products:list:${JSON.stringify(query)}`; // Intentar obtener de cache const cached = await this.cacheService.get(cacheKey); if (cached) { return cached; } // Query a BD const products = await this.repository.find({ where: this.buildWhereClause(query), take: query.limit, }); // Guardar en cache (5 minutos) await this.cacheService.set(cacheKey, products, 300); return products; } async update(id: string, dto: UpdateProductDto): Promise { const product = await this.repository.save({ id, ...dto }); // Invalidar cache relacionado await this.cacheService.delByPattern('products:*'); return product; } } ``` ### 3.4 Compresion de Responses ```typescript // src/main.ts import * as compression from 'compression'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Comprimir responses > 1kb app.use(compression({ threshold: 1024, level: 6, // Balance entre compresion y CPU })); await app.listen(3000); } ``` ### 3.5 Lazy Loading de Modulos ```typescript // Cargar modulo pesado solo cuando se necesita @Module({ imports: [ // Modulo de reportes cargado lazy RouterModule.register([ { path: 'reports', module: ReportsModule, }, ]), ], }) export class AppModule {} ``` --- ## 4. FRONTEND PERFORMANCE ### 4.1 Code Splitting ```typescript // ❌ INCORRECTO: Importar todo import { HeavyComponent } from './HeavyComponent'; // ✅ CORRECTO: Lazy loading const HeavyComponent = lazy(() => import('./HeavyComponent')); // Uso con Suspense }> ``` ### 4.2 Memoizacion ```typescript // React.memo para componentes puros const UserCard = memo(({ user }: { user: User }) => { return (

{user.name}

{user.email}

); }); // useMemo para calculos costosos const ExpensiveList = ({ items, filter }: Props) => { const filteredItems = useMemo( () => items.filter(item => complexFilter(item, filter)), [items, filter], // Solo recalcular si cambian ); return
    {filteredItems.map(/* ... */)}
; }; // useCallback para funciones estables const ParentComponent = () => { const [count, setCount] = useState(0); const handleClick = useCallback(() => { console.log('clicked'); }, []); // Funcion estable return ; }; ``` ### 4.3 Virtualizacion de Listas ```typescript // Para listas largas, usar virtualizacion import { useVirtualizer } from '@tanstack/react-virtual'; const VirtualList = ({ items }: { items: Item[] }) => { const parentRef = useRef(null); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, // Altura estimada de cada item }); return (
{virtualizer.getVirtualItems().map(virtualItem => (
))}
); }; ``` ### 4.4 Optimizacion de Imagenes ```typescript // Componente de imagen optimizada const OptimizedImage = ({ src, alt, width, height, }: ImageProps) => { return ( {alt} ); }; // Con srcset para responsive const ResponsiveImage = ({ src, alt }: Props) => { return ( {alt} ); }; ``` ### 4.5 Debounce y Throttle ```typescript // src/shared/hooks/useDebounce.ts import { useState, useEffect } from 'react'; export function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debouncedValue; } // Uso en busqueda const SearchInput = () => { const [search, setSearch] = useState(''); const debouncedSearch = useDebounce(search, 300); // Query solo se ejecuta cuando debouncedSearch cambia const { data } = useQuery({ queryKey: ['search', debouncedSearch], queryFn: () => api.search(debouncedSearch), enabled: debouncedSearch.length > 2, }); return setSearch(e.target.value)} />; }; ``` ### 4.6 React Query - Cache y Stale Time ```typescript // src/shared/hooks/useProducts.ts export const useProducts = (filters: ProductFilters) => { return useQuery({ queryKey: ['products', filters], queryFn: () => productService.getAll(filters), staleTime: 5 * 60 * 1000, // 5 minutos antes de refetch gcTime: 30 * 60 * 1000, // 30 minutos en cache placeholderData: keepPreviousData, // Mostrar datos anteriores mientras carga }); }; // Prefetch para navegacion anticipada const ProductList = () => { const queryClient = useQueryClient(); const handleMouseEnter = (productId: string) => { // Prefetch detalle del producto queryClient.prefetchQuery({ queryKey: ['product', productId], queryFn: () => productService.getById(productId), }); }; return (/* ... */); }; ``` --- ## 5. API DESIGN PARA PERFORMANCE ### 5.1 Campos Seleccionables ```typescript // Permitir al cliente elegir campos @Get() async findAll( @Query('fields') fields?: string, // ?fields=id,name,price ): Promise[]> { const select = fields?.split(',') || undefined; return this.productService.findAll({ select }); } ``` ### 5.2 Expansion de Relaciones ```typescript // Permitir expansion opcional de relaciones @Get(':id') async findOne( @Param('id') id: string, @Query('expand') expand?: string, // ?expand=category,reviews ): Promise { const relations = expand?.split(',') || []; return this.productService.findOne(id, { relations }); } ``` ### 5.3 Batch Endpoints ```typescript // Endpoint para multiples operaciones @Post('batch') async batchCreate(@Body() dtos: CreateProductDto[]): Promise { // Una transaccion en lugar de N requests return this.productService.createMany(dtos); } // Endpoint para multiples IDs @Get('batch') async batchGet(@Query('ids') ids: string): Promise { const idArray = ids.split(','); return this.productService.findByIds(idArray); } ``` --- ## 6. MONITORING Y PROFILING ### 6.1 Metricas de API ```typescript // src/shared/interceptors/metrics.interceptor.ts @Injectable() export class MetricsInterceptor implements NestInterceptor { constructor(private readonly metricsService: MetricsService) {} intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const { method, url } = request; const startTime = Date.now(); return next.handle().pipe( tap({ next: () => { const duration = Date.now() - startTime; this.metricsService.recordRequest(method, url, 200, duration); }, error: (error) => { const duration = Date.now() - startTime; this.metricsService.recordRequest(method, url, error.status || 500, duration); }, }), ); } } ``` ### 6.2 Query Logging Condicional ```typescript // Solo loguear queries lentas en produccion const typeOrmConfig: TypeOrmModuleOptions = { logging: process.env.NODE_ENV === 'production' ? ['error', 'warn'] : true, maxQueryExecutionTime: 1000, // Loguear queries > 1s }; ``` --- ## 7. CHECKLIST DE PERFORMANCE ``` Database: [ ] Indices en columnas de WHERE frecuentes [ ] Indices compuestos para queries comunes [ ] No N+1 queries (usar JOIN/relations) [ ] Paginacion cursor-based para datasets grandes [ ] SELECT solo campos necesarios [ ] EXPLAIN ANALYZE en queries criticas Backend: [ ] Cache implementado para datos frecuentes [ ] Invalidacion de cache correcta [ ] Compresion habilitada [ ] Connection pooling configurado [ ] Timeouts apropiados Frontend: [ ] Code splitting / lazy loading [ ] Memoizacion donde corresponde [ ] Virtualizacion para listas largas [ ] Imagenes optimizadas y lazy loaded [ ] Debounce en inputs de busqueda [ ] React Query con staleTime apropiado API: [ ] Paginacion en endpoints de listas [ ] Campos seleccionables (opcional) [ ] Batch endpoints para operaciones multiples [ ] Rate limiting para proteger recursos ``` --- **Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Performance