# ANTIPATRONES: Lo que NUNCA Hacer

**Versión:** 1.0.0
**Fecha:** 2025-12-08
**Prioridad:** OBLIGATORIA - Consultar antes de implementar
**Sistema:** SIMCO + CAPVED

---

## PROPÓSITO

Documentar antipatrones comunes para que agentes y subagentes los eviten. Cada antipatrón incluye el problema, por qué es malo, y la solución correcta.

---

## 1. ANTIPATRONES DE DATABASE

### DB-001: Crear tabla sin schema

```sql
-- ❌ INCORRECTO
CREATE TABLE users (
    id UUID PRIMARY KEY
);

-- ✅ CORRECTO
CREATE TABLE auth.users (
    id UUID PRIMARY KEY
);
```

**Por qué es malo:** Sin schema, la tabla va a `public`, mezclando dominios y dificultando permisos.

---

### DB-002: Columna NOT NULL sin DEFAULT en tabla existente

```sql
-- ❌ INCORRECTO (en tabla con datos)
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL;

-- ✅ CORRECTO
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
```

**Por qué es malo:** Falla en INSERT para registros existentes que no tienen valor.

---

### DB-003: Foreign Key sin ON DELETE

```sql
-- ❌ INCORRECTO
CREATE TABLE orders (
    user_id UUID REFERENCES users(id)
);

-- ✅ CORRECTO
CREATE TABLE orders (
    user_id UUID REFERENCES users(id) ON DELETE CASCADE
    -- o ON DELETE SET NULL, ON DELETE RESTRICT según el caso
);
```

**Por qué es malo:** Al eliminar usuario, los orders quedan huérfanos o el DELETE falla sin explicación clara.

---

### DB-004: Usar TEXT cuando debería ser ENUM

```sql
-- ❌ INCORRECTO
CREATE TABLE users (
    status TEXT  -- Permite cualquier valor
);

-- ✅ CORRECTO
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'suspended');
CREATE TABLE users (
    status user_status DEFAULT 'active'
);

-- O con CHECK constraint
CREATE TABLE users (
    status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'suspended'))
);
```

**Por qué es malo:** TEXT permite valores inválidos, causando bugs silenciosos.

---

### DB-005: Índice faltante en columna de búsqueda frecuente

```sql
-- ❌ INCORRECTO
CREATE TABLE products (
    sku VARCHAR(50) UNIQUE  -- Sin índice adicional para búsquedas
);

-- ✅ CORRECTO
CREATE TABLE products (
    sku VARCHAR(50) UNIQUE
);
CREATE INDEX idx_products_sku ON products(sku);
-- UNIQUE ya crea índice, pero para búsquedas parciales:
CREATE INDEX idx_products_sku_pattern ON products(sku varchar_pattern_ops);
```

**Por qué es malo:** Queries lentas en tablas grandes (full table scan).

---

### DB-006: Guardar contraseñas en texto plano

```sql
-- ❌ INCORRECTO
CREATE TABLE users (
    password VARCHAR(100)  -- Almacena "mipassword123"
);

-- ✅ CORRECTO
CREATE TABLE users (
    password_hash VARCHAR(255)  -- Almacena hash bcrypt
);
-- Hashing se hace en backend, no en SQL
```

**Por qué es malo:** Vulnerabilidad de seguridad crítica.

---

## 2. ANTIPATRONES DE BACKEND

### BE-001: Lógica de negocio en Controller

```typescript
// ❌ INCORRECTO
@Controller('orders')
export class OrderController {
    @Post()
    async create(@Body() dto: CreateOrderDto) {
        // Lógica de negocio en controller
        const total = dto.items.reduce((sum, item) => sum + item.price * item.qty, 0);
        const tax = total * 0.16;
        const discount = dto.couponCode ? await this.calculateDiscount() : 0;
        // ... más lógica
    }
}

// ✅ CORRECTO
@Controller('orders')
export class OrderController {
    @Post()
    async create(@Body() dto: CreateOrderDto) {
        return this.orderService.create(dto);  // Delega a service
    }
}

@Injectable()
export class OrderService {
    async create(dto: CreateOrderDto) {
        const total = this.calculateTotal(dto.items);
        const tax = this.calculateTax(total);
        // Lógica de negocio en service
    }
}
```

**Por qué es malo:** Controller difícil de testear, lógica no reutilizable, violación de SRP.

---

### BE-002: Query directo en Service sin Repository

```typescript
// ❌ INCORRECTO
@Injectable()
export class UserService {
    async findActive() {
        // Query directo con QueryBuilder
        return this.connection.query(`
            SELECT * FROM users WHERE status = 'active'
        `);
    }
}

// ✅ CORRECTO
@Injectable()
export class UserService {
    constructor(
        @InjectRepository(UserEntity)
        private readonly repository: Repository<UserEntity>,
    ) {}

    async findActive() {
        return this.repository.find({
            where: { status: UserStatus.ACTIVE },
        });
    }
}
```

**Por qué es malo:** No tipado, vulnerable a SQL injection, difícil de mantener.

---

### BE-003: Validación en Service en lugar de DTO

```typescript
// ❌ INCORRECTO
@Injectable()
export class UserService {
    async create(dto: any) {
        if (!dto.email) throw new BadRequestException('Email required');
        if (!dto.email.includes('@')) throw new BadRequestException('Invalid email');
        if (dto.name.length < 2) throw new BadRequestException('Name too short');
        // ... más validaciones manuales
    }
}

// ✅ CORRECTO
export class CreateUserDto {
    @IsEmail()
    @IsNotEmpty()
    email: string;

    @IsString()
    @MinLength(2)
    name: string;
}

// Service recibe DTO ya validado
```

**Por qué es malo:** Validación duplicada, inconsistente, no documentada en Swagger.

---

### BE-004: Catch vacío o silencioso

```typescript
// ❌ INCORRECTO
async processPayment() {
    try {
        await this.paymentGateway.charge();
    } catch (e) {
        // Silencioso - error perdido
    }
}

// ❌ TAMBIÉN INCORRECTO
async processPayment() {
    try {
        await this.paymentGateway.charge();
    } catch (e) {
        console.log(e);  // Solo log, sin manejar
    }
}

// ✅ CORRECTO
async processPayment() {
    try {
        await this.paymentGateway.charge();
    } catch (error) {
        this.logger.error('Payment failed', { error, context: 'payment' });
        throw new InternalServerErrorException('Error procesando pago');
    }
}
```

**Por qué es malo:** Errores silenciosos causan bugs imposibles de debuggear.

---

### BE-005: Entity no alineada con DDL

```typescript
// DDL tiene:
// status VARCHAR(20) CHECK (status IN ('active', 'inactive'))

// ❌ INCORRECTO
@Column()
status: string;  // No valida valores

// ✅ CORRECTO
enum UserStatus {
    ACTIVE = 'active',
    INACTIVE = 'inactive',
}

@Column({ type: 'varchar', length: 20 })
status: UserStatus;
```

**Por qué es malo:** Entity permite valores que BD rechaza, errores en runtime.

---

### BE-006: Hardcodear configuración

```typescript
// ❌ INCORRECTO
const API_URL = 'https://api.stripe.com/v1';
const DB_HOST = '192.168.1.100';

// ✅ CORRECTO
const API_URL = this.configService.get('STRIPE_API_URL');
const DB_HOST = this.configService.get('DB_HOST');

// O usar decorador
@Injectable()
export class PaymentService {
    constructor(
        @Inject('STRIPE_CONFIG')
        private readonly config: StripeConfig,
    ) {}
}
```

**Por qué es malo:** Difícil cambiar entre ambientes, secretos expuestos en código.

---

### BE-007: N+1 Query Problem

```typescript
// ❌ INCORRECTO
async getOrdersWithItems() {
    const orders = await this.orderRepository.find();
    for (const order of orders) {
        order.items = await this.itemRepository.find({
            where: { orderId: order.id }
        });
    }
    return orders;
}
// Resultado: 1 query para orders + N queries para items

// ✅ CORRECTO
async getOrdersWithItems() {
    return this.orderRepository.find({
        relations: ['items'],  // JOIN en una query
    });
}
// Resultado: 1 query con JOIN
```

**Por qué es malo:** Performance terrible en listas grandes (100 orders = 101 queries).

---

## 3. ANTIPATRONES DE FRONTEND

### FE-001: Fetch directo en componente

```typescript
// ❌ INCORRECTO
const UserProfile = () => {
    const [user, setUser] = useState(null);

    useEffect(() => {
        fetch('/api/users/me')
            .then(res => res.json())
            .then(setUser);
    }, []);

    return <div>{user?.name}</div>;
};

// ✅ CORRECTO
// hooks/useUser.ts
const useUser = () => {
    return useQuery({
        queryKey: ['user', 'me'],
        queryFn: () => userService.getMe(),
    });
};

// components/UserProfile.tsx
const UserProfile = () => {
    const { data: user, isLoading, error } = useUser();

    if (isLoading) return <Loading />;
    if (error) return <Error error={error} />;

    return <div>{user.name}</div>;
};
```

**Por qué es malo:** No cachea, no maneja loading/error, lógica no reutilizable.

---

### FE-002: Hardcodear URLs de API

```typescript
// ❌ INCORRECTO
const response = await fetch('http://localhost:3000/api/users');

// ✅ CORRECTO
// config.ts
export const API_URL = import.meta.env.VITE_API_URL;

// services/api.ts
const api = axios.create({
    baseURL: API_URL,
});
```

**Por qué es malo:** Rompe en producción, difícil cambiar backend.

---

### FE-003: Estado global para todo

```typescript
// ❌ INCORRECTO - Redux/Zustand para estado de formulario
const formStore = create((set) => ({
    name: '',
    email: '',
    setName: (name) => set({ name }),
    setEmail: (email) => set({ email }),
}));

// ✅ CORRECTO - Estado local para formularios
const UserForm = () => {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    // O usar react-hook-form
};

// Store global SOLO para:
// - Auth state (usuario logueado)
// - UI state (tema, sidebar abierto)
// - Cache de server state (mejor usar React Query)
```

**Por qué es malo:** Complejidad innecesaria, renders innecesarios, difícil de seguir.

---

### FE-004: Props drilling excesivo

```typescript
// ❌ INCORRECTO
<App user={user} theme={theme}>
    <Layout user={user} theme={theme}>
        <Sidebar user={user} theme={theme}>
            <UserMenu user={user} theme={theme}>
                <Avatar user={user} />  // Finalmente se usa
            </UserMenu>
        </Sidebar>
    </Layout>
</App>

// ✅ CORRECTO - Context para datos compartidos
const UserContext = createContext<User | null>(null);
const useUser = () => useContext(UserContext);

<UserProvider value={user}>
    <App>
        <Layout>
            <Sidebar>
                <UserMenu>
                    <Avatar />  // Usa useUser() internamente
                </UserMenu>
            </Sidebar>
        </Layout>
    </App>
</UserProvider>
```

**Por qué es malo:** Componentes intermedios reciben props que no usan, refactoring doloroso.

---

### FE-005: useEffect para todo

```typescript
// ❌ INCORRECTO
const ProductList = ({ categoryId }) => {
    const [products, setProducts] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        setLoading(true);
        fetch(`/api/products?category=${categoryId}`)
            .then(res => res.json())
            .then(data => {
                setProducts(data);
                setLoading(false);
            });
    }, [categoryId]);
    // Race conditions, no cleanup, no error handling
};

// ✅ CORRECTO - React Query
const ProductList = ({ categoryId }) => {
    const { data: products, isLoading } = useQuery({
        queryKey: ['products', categoryId],
        queryFn: () => productService.getByCategory(categoryId),
    });
    // Maneja cache, loading, error, race conditions automáticamente
};
```

**Por qué es malo:** Race conditions, memory leaks, re-inventar la rueda.

---

### FE-006: Tipos no sincronizados con backend

```typescript
// Backend DTO (actualizado)
class UserDto {
    id: string;
    email: string;
    name: string;
    phone: string;  // ← NUEVO CAMPO
}

// ❌ INCORRECTO - Frontend desactualizado
interface User {
    id: string;
    email: string;
    name: string;
    // Falta phone → undefined en runtime
}

// ✅ CORRECTO - Mantener sincronizado
interface User {
    id: string;
    email: string;
    name: string;
    phone?: string;  // Sincronizado con backend
}
```

**Por qué es malo:** Bugs silenciosos en runtime cuando backend cambia.

---

## 4. ANTIPATRONES DE ARQUITECTURA

### ARCH-001: Importar de capa incorrecta

```typescript
// ❌ INCORRECTO - Controller importa de otro módulo directamente
import { ProductEntity } from '../products/entities/product.entity';

// ✅ CORRECTO - Usar exports del módulo
import { ProductService } from '../products/product.module';

// O mejor: dependency injection
@Module({
    imports: [ProductModule],
})
export class OrderModule {}
```

**Por qué es malo:** Acopla módulos, rompe encapsulamiento, dependencias circulares.

---

### ARCH-002: Duplicar código entre módulos

```typescript
// ❌ INCORRECTO - Misma función en dos módulos
// users/utils.ts
function formatDate(date: Date) { ... }

// orders/utils.ts
function formatDate(date: Date) { ... }  // Duplicado

// ✅ CORRECTO - Shared utils
// shared/utils/date.utils.ts
export function formatDate(date: Date) { ... }
```

**Por qué es malo:** Cambios deben hacerse en múltiples lugares, inconsistencias.

---

### ARCH-003: Circular dependencies

```typescript
// ❌ INCORRECTO
// user.service.ts
import { OrderService } from '../orders/order.service';

// order.service.ts
import { UserService } from '../users/user.service';

// ✅ CORRECTO - Usar forwardRef o reestructurar
@Injectable()
export class UserService {
    constructor(
        @Inject(forwardRef(() => OrderService))
        private orderService: OrderService,
    ) {}
}

// O mejor: Extraer lógica compartida a un tercer servicio
```

**Por qué es malo:** Errores en runtime, código difícil de entender.

---

## 5. ANTIPATRONES DE TESTING

### TEST-001: Tests que dependen de orden

```typescript
// ❌ INCORRECTO
describe('UserService', () => {
    it('should create user', () => {
        const user = service.create({ email: 'test@test.com' });
        // Asume que este test corre primero
    });

    it('should find user', () => {
        const user = service.findByEmail('test@test.com');
        // Depende del test anterior
    });
});

// ✅ CORRECTO - Tests independientes
describe('UserService', () => {
    beforeEach(async () => {
        // Setup limpio para cada test
        await repository.clear();
    });

    it('should create user', () => { ... });

    it('should find user', async () => {
        // Crear datos necesarios en el test
        await repository.save({ email: 'test@test.com' });
        const user = await service.findByEmail('test@test.com');
    });
});
```

---

### TEST-002: Mock incorrecto

```typescript
// ❌ INCORRECTO - Mock parcial/inconsistente
jest.mock('./user.service', () => ({
    findOne: jest.fn().mockResolvedValue({ id: '1' }),
    // Otros métodos no mockeados → undefined
}));

// ✅ CORRECTO - Mock completo
const mockUserService = {
    findOne: jest.fn(),
    findAll: jest.fn(),
    create: jest.fn(),
    update: jest.fn(),
    delete: jest.fn(),
};

beforeEach(() => {
    jest.clearAllMocks();
});
```

---

## 6. CHECKLIST DE REVISIÓN

Antes de hacer commit, verificar que NO estás haciendo:

```
Database:
[ ] Tabla sin schema
[ ] NOT NULL sin DEFAULT en tabla existente
[ ] FK sin ON DELETE
[ ] TEXT donde debería ser ENUM
[ ] Índices faltantes

Backend:
[ ] Lógica en Controller
[ ] Queries directas sin Repository
[ ] Validación en Service
[ ] Catch vacío
[ ] Entity desalineada con DDL
[ ] Config hardcodeada
[ ] N+1 queries

Frontend:
[ ] Fetch en componente
[ ] URLs hardcodeadas
[ ] Store global para estado local
[ ] Props drilling excesivo
[ ] useEffect innecesario
[ ] Tipos desactualizados

Arquitectura:
[ ] Import de capa incorrecta
[ ] Código duplicado
[ ] Dependencias circulares

Testing:
[ ] Tests dependientes
[ ] Mocks incompletos
```

---

**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guía de Antipatrones
