Skip to Content
DocumentaciónConvenciones

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.tsx

Variables 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 object

Branch 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-code

Testing

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.ts

Convenciones 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.

Last updated on