# 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<User[]> {
  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<User[]> {
  return this.userRepository.find({
    relations: ['orders'],  // TypeORM hace JOIN
  });
}

// ✅ CORRECTO: QueryBuilder con control
async findAllWithOrders(): Promise<User[]> {
  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<T>(key: string): Promise<T | null> {
    return this.cacheManager.get<T>(key);
  }

  async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
    await this.cacheManager.set(key, value, ttlSeconds * 1000);
  }

  async del(key: string): Promise<void> {
    await this.cacheManager.del(key);
  }

  async delByPattern(pattern: string): Promise<void> {
    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<Observable<any>> {
    const cacheKey = this.reflector.get<string>(CACHE_KEY, context.getHandler());
    if (!cacheKey) {
      return next.handle();
    }

    const cacheTtl = this.reflector.get<number>(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<ProductEntity>,
    private readonly cacheService: CacheService,
  ) {}

  async findAll(query: ProductQueryDto): Promise<Product[]> {
    const cacheKey = `products:list:${JSON.stringify(query)}`;

    // Intentar obtener de cache
    const cached = await this.cacheService.get<Product[]>(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<Product> {
    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
<Suspense fallback={<LoadingSpinner />}>
  <HeavyComponent />
</Suspense>
```

### 4.2 Memoizacion

```typescript
// React.memo para componentes puros
const UserCard = memo(({ user }: { user: User }) => {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
});

// 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 <ul>{filteredItems.map(/* ... */)}</ul>;
};

// useCallback para funciones estables
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);  // Funcion estable

  return <ChildComponent onClick={handleClick} />;
};
```

### 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<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,  // Altura estimada de cada item
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: virtualItem.start,
              height: virtualItem.size,
            }}
          >
            <ItemComponent item={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
};
```

### 4.4 Optimizacion de Imagenes

```typescript
// Componente de imagen optimizada
const OptimizedImage = ({
  src,
  alt,
  width,
  height,
}: ImageProps) => {
  return (
    <img
      src={src}
      alt={alt}
      width={width}
      height={height}
      loading="lazy"           // Lazy loading nativo
      decoding="async"         // Decodificacion asincrona
      style={{ aspectRatio: `${width}/${height}` }}  // Prevenir layout shift
    />
  );
};

// Con srcset para responsive
const ResponsiveImage = ({ src, alt }: Props) => {
  return (
    <img
      src={src}
      alt={alt}
      srcSet={`
        ${src}?w=400 400w,
        ${src}?w=800 800w,
        ${src}?w=1200 1200w
      `}
      sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
      loading="lazy"
    />
  );
};
```

### 4.5 Debounce y Throttle

```typescript
// src/shared/hooks/useDebounce.ts
import { useState, useEffect } from 'react';

export function useDebounce<T>(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 <input value={search} onChange={e => 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<Partial<Product>[]> {
  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<Product> {
  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<Product[]> {
  // 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<Product[]> {
  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<any> {
    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
