workspace-v1/orchestration/patrones/ANTIPATRONES.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

715 lines
16 KiB
Markdown

# 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