Convenciones
Las convenciones establecen un lenguaje común en el equipo, mejoran la legibilidad del código y facilitan el mantenimiento a largo plazo. Estas reglas son aplicadas automáticamente por nuestras herramientas de desarrollo.
Nomenclatura
Archivos y Carpetas
// Archivos: kebab-case
user-profile.tsx
api-client.ts
error-boundary.tsx
// Carpetas: kebab-case
components/
ui/
user-management/
error-handling/
// Páginas Next.js: kebab-case o descriptivo
app/
user-profile/
api/
auth/
route.ts
// Componentes del servidor: prefijo underscore
_navigation_links.tsx
_theme-toggle.tsx
_user-menu.tsxVariables y Funciones
// Variables: camelCase
const userName = 'john_doe';
const isAuthenticated = true;
const userPreferences = {};
// Funciones: camelCase
function getUserData() {}
function handleSubmit() {}
function validateEmail() {}
// Constantes globales: UPPER_SNAKE_CASE
const NAVBAR_CONTENT = {};
const API_ROUTES = {};
const THEME_STYLES = {};
const USER_ROLES = {};Componentes y Tipos
// Componentes: PascalCase
export function UserProfile() {}
export function NavigationMenu() {}
export function ErrorBoundary() {}
// Interfaces y Types: PascalCase
interface UserData {
id: string;
name: string;
}
type ApiResponse<T> = {
data: T;
status: number;
};
// Enums: PascalCase
enum UserRole {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest',
}Hooks Personalizados
// Hooks: camelCase con prefijo 'use'
export function useAuth() {}
export function useLocalStorage() {}
export function useDebounce() {}
export function useApiQuery() {}Convenciones Específicas de Creative Minds
Constantes y Contenido Estático
Todas las constantes como contenido, internacionalización, objetos estáticos, rutas y estilos deben nombrarse en UPPER_SNAKE_CASE:
// constants/content.ts
export const NAVBAR_CONTENT = {
brand: "Creative Minds",
links: [
{ label: 'Docs', href: '/docs' },
{ label: 'Components', href: '/components' }
]
} as const;
// constants/styles.ts
export const STYLES = {
active: 'px-3 py-2 rounded-lg bg-gray-100',
inactive: 'px-3 py-2 rounded-lg hover:bg-gray-50'
};
// constants/routes.ts
export const API_ROUTES = {
USERS: '/api/users',
AUTH: '/api/auth',
POSTS: '/api/posts'
};Componentes del Cliente
Los componentes del cliente tanto el archivo como el componente deben empezar con underscore (_):
// components/_navigation_links.tsx
'use client';
export function _NavigationLinks() {
// Lógica del componente cliente
}
// components/_theme-toggle.tsx
'use client';
export function _ThemeToggle() {
// Lógica del componente cliente
}Separación de Contenido
No se debe poner texto directamente dentro del JSX. Siempre se lleva a un objeto de contenido que se importa donde se necesita:
// ❌ Incorrecto - texto directo en JSX
export function Header() {
return <h1>Bienvenido a Creative Minds</h1>;
}
// ✅ Correcto - contenido separado
// constants/content.ts
export const HEADER_CONTENT = {
title: "Bienvenido a Creative Minds",
subtitle: "Documentación y recursos"
};
// components/header.tsx
import { HEADER_CONTENT } from '../constants/content';
export function Header() {
return <h1>{HEADER_CONTENT.title}</h1>;
}Estructura de Componentes
Patrón Render Props para Server Components
Los componentes del servidor usan el patrón render props para pasar contexto a componentes hijos:
// components/root.tsx
export async function Root({
children,
className,
}: {
children: (args: { data: { name: string } }) => React.ReactNode;
className?: string;
}) {
const data = {
name: 'server data',
};
return (
<nav className={cn('px-3 py-3 relative z-50', className)}>
{children({ data })}
</nav>
);
}
// Uso del componente
<Root>
{({ data }) => (
<div>{data.name}</div>
)}
</Root>Organización de Imports
Orden de Imports
// 1. React y frameworks
import React from 'react';
import { NextPage, GetServerSideProps } from 'next';
import { useRouter } from 'next/router';
// 2. Librerías externas (alfabético)
import { clsx } from 'clsx';
import { z } from 'zod';
import { useQuery } from '@tanstack/react-query';
// 3. Imports internos - UI components
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card } from '@/components/ui/card';
// 4. Imports internos - Features
import { UserCard } from '@/components/features/user-card';
import { Navigation } from '@/components/features/navigation';
// 5. Imports internos - Hooks y utils
import { useAuth } from '@/hooks/use-auth';
import { cn } from '@/lib/utils';
import { apiClient } from '@/lib/api-client';
// 6. Imports de constantes (SIEMPRE después de utils)
import { NAVBAR_CONTENT } from '../constants/content';
import { STYLES } from '../constants/styles';
import { API_ROUTES } from '../constants/routes';
// 7. Imports internos - Types
import type { User, ApiResponse } from '@/types';
// 8. Imports relativos
import { UserForm } from './user-form';
import './styles.css';Agrupación con Líneas Vacías
import React from 'react';
import { NextPage } from 'next';
import { Button } from '@/components/ui/button';
import { useAuth } from '@/hooks/use-auth';
import type { User } from '@/types';
import './component.css';Convenciones de CSS
Clases Tailwind
// Orden de clases: layout -> spacing -> styling -> responsive
<div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-md md:flex-row md:gap-6">
<Button className="w-full px-4 py-2 text-white bg-blue-600 hover:bg-blue-700 md:w-auto">
Submit
</Button>
</div>
// Usar cn() para clases condicionales
import { cn } from '@/lib/utils';
<button
className={cn(
"px-4 py-2 rounded-md font-medium transition-colors",
variant === 'primary' && "bg-blue-600 text-white hover:bg-blue-700",
variant === 'secondary' && "bg-gray-200 text-gray-900 hover:bg-gray-300",
disabled && "opacity-50 cursor-not-allowed"
)}
>CSS Custom Properties
/* Usar variables CSS para valores reutilizables */
:root {
--color-primary: #3b82f6;
--color-secondary: #6b7280;
--spacing-unit: 0.25rem;
--border-radius: 0.375rem;
}
.button {
background-color: var(--color-primary);
padding: calc(var(--spacing-unit) * 3) calc(var(--spacing-unit) * 6);
border-radius: var(--border-radius);
}Manejo de Errores
Try-Catch Patterns
// Funciones async siempre con manejo de errores
async function fetchUserData(userId: string): Promise<User | null> {
try {
const response = await apiClient.get(`/users/${userId}`);
return response.data;
} catch (error) {
console.error('Error fetching user data:', error);
// Log to monitoring service
logger.error('Failed to fetch user', { userId, error });
return null;
}
}
// Error boundaries para componentes
export function UserProfileWithErrorBoundary({ userId }: Props) {
return (
<ErrorBoundary fallback={<UserProfileError />}>
<UserProfile userId={userId} />
</ErrorBoundary>
);
}Validación de Datos
// Usar Zod para validación
const createUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email format'),
age: z.number().min(18, 'Must be at least 18 years old'),
});
function createUser(data: unknown) {
const result = createUserSchema.safeParse(data);
if (!result.success) {
throw new ValidationError(result.error.issues);
}
return apiClient.post('/users', result.data);
}Comentarios y Documentación
JSDoc para Funciones Públicas
/**
* Fetches user data from the API with caching support
* @param userId - The unique identifier for the user
* @param options - Additional options for the request
* @returns Promise that resolves to user data or null if not found
* @throws {ApiError} When the API request fails
* @example
* ```typescript
* const user = await getUserData('123', { cache: true });
* if (user) {
* console.log(user.name);
* }
* ```
*/
export async function getUserData(
userId: string,
options: { cache?: boolean } = {}
): Promise<User | null> {
// Implementation
}Comentarios en Código
// ✅ Buenos comentarios - explican el "por qué"
// Debounce search to avoid excessive API calls
const debouncedSearch = useDebounce(searchTerm, 300);
// Fallback to guest user when authentication fails
const user = authenticatedUser ?? createGuestUser();
// ❌ Malos comentarios - explican el "qué" (obvio)
// Set loading to true
setLoading(true);
// Create a new user object
const user = { name, email };Git y Commits
Conventional Commits
# Formato: type(scope): description
feat(auth): add OAuth login support
fix(api): resolve user data caching issue
docs(readme): update installation instructions
style(components): fix button padding consistency
refactor(hooks): simplify useAuth implementation
test(utils): add tests for date formatting
chore(deps): update dependencies to latest versions
# Breaking changes
feat(api)!: change user endpoint response format
BREAKING CHANGE: The user endpoint now returns { user: {...} } instead of direct user objectBranch Naming
# Feature branches
feature/user-authentication
feature/payment-integration
feature/admin-dashboard
# Bug fixes
fix/login-redirect-issue
fix/memory-leak-in-chat
# Hotfixes
hotfix/critical-security-patch
hotfix/payment-processing-error
# Chores
chore/update-dependencies
chore/cleanup-unused-codeTesting
Naming de Tests
// Describe blocks: funcionalidad que se está probando
describe('UserProfile component', () => {
// Test cases: comportamiento específico
it('should display user name when data is loaded', () => {});
it('should show loading state while fetching data', () => {});
it('should handle API errors gracefully', () => {});
});
// Para hooks
describe('useAuth hook', () => {
it('should return authenticated user when logged in', () => {});
it('should redirect to login when token expires', () => {});
});Test Data
// Factories para datos de prueba
export const createMockUser = (overrides: Partial<User> = {}): User => ({
id: '1',
name: 'John Doe',
email: 'john@example.com',
role: 'user',
...overrides,
});
// Usar en tests
const mockUser = createMockUser({ name: 'Jane Doe' });Herramientas de Enforcement
ESLint Rules
{
"rules": {
"prefer-const": "error",
"no-var": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"import/order": ["error", {
"groups": ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "always"
}]
}
}Prettier Configuration
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}Estructura de Features
Organización por Funcionalidad
Cada feature se organiza en su propia carpeta con la siguiente estructura:
src/features/navbar/
├── components/ # Componentes de la feature
│ ├── root.tsx # Componente raíz (server)
│ ├── logo.tsx # Componentes regulares
│ ├── links.tsx
│ ├── _navigation_links.tsx # Componentes cliente (prefijo _)
│ └── _theme-toggle.tsx
├── constants/ # Constantes y contenido estático
│ ├── content.ts # NAVBAR_CONTENT
│ ├── styles.ts # STYLES
│ └── routes.ts # API_ROUTES
├── hooks/ # Hooks personalizados
│ ├── useNav.tsx
│ ├── useThemeToggle.tsx
│ └── useRenderContent.tsx
├── utils/ # Utilidades específicas
│ └── active_to_classname.ts
├── fallbacks/ # Componentes de fallback
│ └── navbar_content_fallback.tsx
└── api/ # Lógica de API (opcional)
├── queries.ts
└── mutations.tsConvenciones de Archivos por Tipo
Componentes del servidor:
- Archivo:
root.tsx,logo.tsx,links.tsx - Función:
Root(),Logo(),Links()
Componentes del cliente:
- Archivo:
_navigation_links.tsx,_theme-toggle.tsx - Función:
_NavigationLinks(),_ThemeToggle()
Constantes:
- Archivo:
content.ts,styles.ts,routes.ts - Exportación:
NAVBAR_CONTENT,STYLES,API_ROUTES
Hooks:
- Archivo:
useNav.tsx,useThemeToggle.tsx - Función:
useNav(),useThemeToggle()
Utilidades:
- Archivo:
active_to_classname.ts - Función:
activeToClassname()
Separación Cliente/Servidor
- Server Components: Sin directiva, pueden hacer fetch de datos
- Client Components: Con
'use client'y prefijo_en el nombre - Render Props: Para pasar datos del servidor a componentes cliente
Fallbacks y Suspense
Cada feature debe incluir componentes de fallback para mejorar la UX:
// fallbacks/navbar_content_fallback.tsx
export function NavbarContentFallback() {
return (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-20"></div>
</div>
);
}
// Uso con Suspense
<Suspense fallback={<NavbarContentFallback />}>
<NavLinks content={NAVBAR_CONTENT} />
</Suspense>Estas convenciones son aplicadas automáticamente por nuestras herramientas de desarrollo y validadas en el proceso de CI/CD para garantizar consistencia en todo el código base.