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

16 KiB

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

-- ❌ 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

-- ❌ 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

-- ❌ 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

-- ❌ 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

-- ❌ 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

-- ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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

// ❌ 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