Estándares de Codificación y Mejores Prácticas
Convenciones de codificación base aplicables en todos los proyectos.
Esta habilidad es el piso compartido, no el manual detallado del framework.
- Usa
patrones-frontendpara React, estado, formularios, renderizado y arquitectura de UI. - Usa
patrones-backendodiseño-apipara capas de repositorio/servicio, diseño de endpoints, validación y aspectos específicos del servidor. - Usa
rules/common/coding-style.mdcuando necesites la capa de reglas reutilizables más corta en lugar de un recorrido completo de habilidades.
Cuándo Activar
- Iniciar un nuevo proyecto o módulo
- Revisar código para calidad y mantenibilidad
- Refactorizar código existente para seguir convenciones
- Hacer cumplir la consistencia en nombres, formato o estructura
- Configurar reglas de linting, formateo o verificación de tipos
- Incorporar nuevos contribuyentes a las convenciones de codificación
Límites de Alcance
Activa esta habilidad para:
- nombres descriptivos
- inmutabilidad por defecto
- legibilidad, aplicación de KISS, DRY y YAGNI
- expectativas de manejo de errores y revisión de code smells
No uses esta habilidad como fuente principal para:
- composición de React, hooks o patrones de renderizado
- arquitectura backend, diseño de API o capas de base de datos
- guía de framework específica de dominio cuando ya exista una habilidad ECC más específica
Principios de Calidad de Código
1. Legibilidad Primero
- El código se lee más de lo que se escribe
- Nombres claros de variables y funciones
- Código autodocumentado preferido sobre comentarios
- Formateo consistente
2. KISS (Mantenlo Simple, Estúpido)
- La solución más simple que funcione
- Evita la ingeniería excesiva
- Sin optimización prematura
- Fácil de entender > código inteligente
3. DRY (No Te Repitas)
- Extraer lógica común en funciones
- Crear componentes reutilizables
- Compartir utilidades entre módulos
- Evita la programación copiar-pegar
4. YAGNI (No Lo Vas a Necesitar)
- No construyas características antes de que se necesiten
- Evita la generalidad especulativa
- Agrega complejidad solo cuando sea requerido
- Comienza simple, refactoriza cuando sea necesario
Estándares TypeScript/JavaScript
Nombrado de Variables
// PASA: BUENO: Nombres descriptivos
const consultaBusquedaMercado = 'elección'
const esUsuarioAutenticado = true
const ingresosTotales = 1000
// FALLA: MALO: Nombres poco claros
const q = 'elección'
const bandera = true
const x = 1000
Nombrado de Funciones
// PASA: BUENO: Patrón verbo-sustantivo
async function obtenerDatosMercado(idMercado: string) { }
function calcularSimilitud(a: number[], b: number[]) { }
function esEmailValido(email: string): boolean { }
// FALLA: MALO: Poco claro o solo sustantivo
async function mercado(id: string) { }
function similitud(a, b) { }
function email(e) { }
Patrón de Inmutabilidad (CRÍTICO)
// PASA: SIEMPRE usa el operador spread
const usuarioActualizado = {
...usuario,
nombre: 'Nuevo Nombre'
}
const arregloActualizado = [...items, nuevoItem]
// FALLA: NUNCA mutar directamente
usuario.nombre = 'Nuevo Nombre' // MALO
items.push(nuevoItem) // MALO
Manejo de Errores
// PASA: BUENO: Manejo completo de errores
async function obtenerDatos(url: string) {
try {
const respuesta = await fetch(url)
if (!respuesta.ok) {
throw new Error(`HTTP ${respuesta.status}: ${respuesta.statusText}`)
}
return await respuesta.json()
} catch (error) {
console.error('Falló la obtención:', error)
throw new Error('Falló al obtener datos')
}
}
// FALLA: MALO: Sin manejo de errores
async function obtenerDatos(url) {
const respuesta = await fetch(url)
return respuesta.json()
}
Mejores Prácticas de Async/Await
// PASA: BUENO: Ejecución paralela cuando sea posible
const [usuarios, mercados, estadisticas] = await Promise.all([
obtenerUsuarios(),
obtenerMercados(),
obtenerEstadisticas()
])
// FALLA: MALO: Secuencial cuando no es necesario
const usuarios = await obtenerUsuarios()
const mercados = await obtenerMercados()
const estadisticas = await obtenerEstadisticas()
Seguridad de Tipos
// PASA: BUENO: Tipos adecuados
interface Mercado {
id: string
nombre: string
estado: 'activo' | 'resuelto' | 'cerrado'
creado_en: Date
}
function obtenerMercado(id: string): Promise<Mercado> {
// Implementación
}
// FALLA: MALO: Usar 'any'
function obtenerMercado(id: any): Promise<any> {
// Implementación
}
Mejores Prácticas de React
Estructura de Componentes
// PASA: BUENO: Componente funcional con tipos
interface BotonProps {
children: React.ReactNode
onClick: () => void
deshabilitado?: boolean
variante?: 'primario' | 'secundario'
}
export function Boton({
children,
onClick,
deshabilitado = false,
variante = 'primario'
}: BotonProps) {
return (
<button
onClick={onClick}
disabled={deshabilitado}
className={`btn btn-${variante}`}
>
{children}
</button>
)
}
// FALLA: MALO: Sin tipos, estructura poco clara
export function Boton(props) {
return <button onClick={props.onClick}>{props.children}</button>
}
Hooks Personalizados
// PASA: BUENO: Hook personalizado reutilizable
export function useDebounce<T>(valor: T, retraso: number): T {
const [valorDebounced, setValorDebounced] = useState<T>(valor)
useEffect(() => {
const manejador = setTimeout(() => {
setValorDebounced(valor)
}, retraso)
return () => clearTimeout(manejador)
}, [valor, retraso])
return valorDebounced
}
// Uso
const consultaDebounced = useDebounce(consultaBusqueda, 500)
Gestión de Estado
// PASA: BUENO: Actualizaciones de estado adecuadas
const [contador, setContador] = useState(0)
// Actualización funcional para estado basado en estado anterior
setContador(prev => prev + 1)
// FALLA: MALO: Referencia directa al estado
setContador(contador + 1) // Puede estar desactualizado en escenarios asíncronos
Renderizado Condicional
// PASA: BUENO: Renderizado condicional claro
{cargando && <Spinner />}
{error && <MensajeError error={error} />}
{datos && <MostrarDatos datos={datos} />}
// FALLA: MALO: Infierno ternario
{cargando ? <Spinner /> : error ? <MensajeError error={error} /> : datos ? <MostrarDatos datos={datos} /> : null}
Estándares de Diseño de API
Convenciones REST API
GET /api/mercados # Listar todos los mercados
GET /api/mercados/:id # Obtener mercado específico
POST /api/mercados # Crear nuevo mercado
PUT /api/mercados/:id # Actualizar mercado (completo)
PATCH /api/mercados/:id # Actualizar mercado (parcial)
DELETE /api/mercados/:id # Eliminar mercado
# Parámetros de consulta para filtrado
GET /api/mercados?estado=activo&limite=10&offset=0
Formato de Respuesta
// PASA: BUENO: Estructura de respuesta consistente
interface ApiResponse<T> {
exito: boolean
datos?: T
error?: string
meta?: {
total: number
pagina: number
limite: number
}
}
// Respuesta exitosa
return NextResponse.json({
exito: true,
datos: mercados,
meta: { total: 100, pagina: 1, limite: 10 }
})
// Respuesta de error
return NextResponse.json({
exito: false,
error: 'Solicitud inválida'
}, { status: 400 })
Validación de Entrada
import { z } from 'zod'
// PASA: BUENO: Validación de esquema
const EsquemaCrearMercado = z.object({
nombre: z.string().min(1).max(200),
descripcion: z.string().min(1).max(2000),
fechaFin: z.string().datetime(),
categorias: z.array(z.string()).min(1)
})
export async function POST(request: Request) {
const cuerpo = await request.json()
try {
const validado = EsquemaCrearMercado.parse(cuerpo)
// Proceder con datos validados
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({
exito: false,
error: 'Falló la validación',
detalles: error.issues
}, { status: 400 })
}
}
}
Organización de Archivos
Estructura del Proyecto
src/
├── app/ # Next.js App Router
│ ├── api/ # Rutas de API
│ ├── mercados/ # Páginas de mercado
│ └── (auth)/ # Páginas de autenticación (grupos de rutas)
├── components/ # Componentes React
│ ├── ui/ # Componentes de UI genéricos
│ ├── forms/ # Componentes de formulario
│ └── layouts/ # Componentes de diseño
├── hooks/ # Hooks React personalizados
├── lib/ # Utilidades y configuraciones
│ ├── api/ # Clientes API
│ ├── utils/ # Funciones auxiliares
│ └── constants/ # Constantes
├── types/ # Tipos TypeScript
└── styles/ # Estilos globales
Nombrado de Archivos
components/Boton.tsx # PascalCase para componentes
hooks/useAuth.ts # camelCase con prefijo 'use'
lib/formatearFecha.ts # camelCase para utilidades
types/mercado.tipos.ts # camelCase con sufijo .types
Comentarios y Documentación
Cuándo Comentar
// PASA: BUENO: Explica POR QUÉ, no QUÉ
// Usar retroceso exponencial para evitar saturar la API durante interrupciones
const retraso = Math.min(1000 * Math.pow(2, contadorReintentos), 30000)
// Deliberadamente usando mutación aquí por rendimiento con arreglos grandes
items.push(nuevoItem)
// FALLA: MALO: Decir lo obvio
// Incrementar contador en 1
contador++
// Establecer nombre al nombre del usuario
nombre = usuario.nombre
JSDoc para APIs Públicas
/**
* Busca mercados usando similitud semántica.
*
* @param consulta - Consulta de búsqueda en lenguaje natural
* @param limite - Número máximo de resultados (por defecto: 10)
* @returns Arreglo de mercados ordenados por puntuación de similitud
* @throws {Error} Si la API de OpenAI falla o Redis no está disponible
*
* @example
* ```typescript
* const resultados = await buscarMercados('elección', 5)
* console.log(resultados[0].nombre) // "Trump vs Biden"
* ```
*/
export async function buscarMercados(
consulta: string,
limite: number = 10
): Promise<Mercado[]> {
// Implementación
}
Mejores Prácticas de Rendimiento
Memoización
import { useMemo, useCallback } from 'react'
// PASA: BUENO: Memoizar cálculos costosos
// Copiar antes de ordenar - Array.prototype.sort muta en el lugar
const mercadosOrdenados = useMemo(() => {
return [...mercados].sort((a, b) => b.volumen - a.volumen)
}, [mercados])
// PASA: BUENO: Memoizar callbacks
const manejarBusqueda = useCallback((consulta: string) => {
setConsultaBusqueda(consulta)
}, [])
Carga Perezosa
import { lazy, Suspense } from 'react'
// PASA: BUENO: Cargar componentes pesados de forma perezosa
const GraficoPesado = lazy(() => import('./GraficoPesado'))
export function Tablero() {
return (
<Suspense fallback={<Spinner />}>
<GraficoPesado />
</Suspense>
)
}
Consultas a Base de Datos
// PASA: BUENO: Seleccionar solo las columnas necesarias
const { data } = await supabase
.from('mercados')
.select('id, nombre, estado')
.limit(10)
// FALLA: MALO: Seleccionar todo
const { data } = await supabase
.from('mercados')
.select('*')
Estándares de Pruebas
Estructura de Prueba (Patrón AAA)
test('calcula la similitud correctamente', () => {
// Organizar
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
// Actuar
const similitud = calcularSimilitudCoseno(vector1, vector2)
// Afirmar
expect(similitud).toBe(0)
})
Nombrado de Pruebas
// PASA: BUENO: Nombres de prueba descriptivos
test('devuelve arreglo vacío cuando ningún mercado coincide con la consulta', () => { })
test('lanza error cuando falta la clave de API de OpenAI', () => { })
test('recurre a búsqueda de subcadena cuando Redis no está disponible', () => { })
// FALLA: MALO: Nombres de prueba vagos
test('funciona', () => { })
test('probar búsqueda', () => { })
Detección de Code Smells
Vigila estos antipatrones:
1. Funciones Largas
// FALLA: MALO: Función > 50 líneas
function procesarDatosMercado() {
// 100 líneas de código
}
// PASA: BUENO: Dividir en funciones más pequeñas
function procesarDatosMercado() {
const validado = validarDatos()
const transformado = transformarDatos(validado)
return guardarDatos(transformado)
}
2. Anidamiento Profundo
// FALLA: MALO: 5+ niveles de anidamiento
if (usuario) {
if (usuario.esAdmin) {
if (mercado) {
if (mercado.estaActivo) {
if (tienePermiso) {
// Hacer algo
}
}
}
}
}
// PASA: BUENO: Retornos tempranos
if (!usuario) return
if (!usuario.esAdmin) return
if (!mercado) return
if (!mercado.estaActivo) return
if (!tienePermiso) return
// Hacer algo
3. Números Mágicos
// FALLA: MALO: Números sin explicación
if (contadorReintentos > 3) { }
setTimeout(callback, 500)
// PASA: BUENO: Constantes con nombre
const MAX_REINTENTOS = 3
const RETRASO_DEBOUNCE_MS = 500
if (contadorReintentos > MAX_REINTENTOS) { }
setTimeout(callback, RETRASO_DEBOUNCE_MS)
Recuerda: La calidad del código no es negociable. El código claro y mantenible permite un desarrollo rápido y una refactorización segura.