workspace/projects/gamilit/docs/95-guias-desarrollo/backend/NAMING-CONVENTIONS-API.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

8.0 KiB

Convenciones de Nombres - APIs y Payloads

Versión: 1.0.0 Fecha: 2025-11-16 Estado: Activo


🎯 Propósito

Este documento establece las convenciones de nombres para payloads de API, DTOs y comunicación Frontend-Backend en el proyecto GAMILIT.

Objetivo: Evitar errores 500 por incompatibilidad de nombres de campos entre Frontend y Backend.


📋 Regla General

Backend (NestJS + PostgreSQL)

Usar snake_case para todos los campos de API

Razón:

  • PostgreSQL usa snake_case por convención
  • NestJS DTOs se mapean directamente a columnas de DB
  • Consistencia entre DB → Backend → Frontend

Frontend (React + TypeScript)

Usar snake_case al comunicarse con Backend ⚠️ Internamente puede usar camelCase, pero SIEMPRE transformar antes de enviar a API


🔑 Casos Específicos

1. Registro de Usuario (Público)

Backend espera (RegisterUserDto):

{
  email: string,         // required
  password: string,      // required
  first_name?: string,   // optional, snake_case
  last_name?: string,    // optional, snake_case
  // role NOT accepted (assigned automatically as 'student')
}

Frontend DEBE enviar:

// ✅ CORRECTO
const payload = {
  email: data.email,
  password: data.password,
  first_name: firstName,
  last_name: lastName
};

// ❌ INCORRECTO
const payload = {
  email: data.email,
  password: data.password,
  firstName: firstName,    // ❌ camelCase
  lastName: lastName,      // ❌ camelCase
  role: 'student'          // ❌ campo NO aceptado
};

2. Creación de Usuario (Admin)

Backend espera (CreateUserDto - admin only):

{
  email: string,
  password: string,
  first_name?: string,
  last_name?: string,
  role: 'student' | 'admin_teacher' | 'super_admin'  // Admin CAN set role
}

Diferencia clave:

  • Registro público: NO acepta role (asignado automáticamente)
  • Creación admin: SÍ acepta role (admin puede asignar roles)

3. Actualización de Perfil

Backend espera:

{
  first_name?: string,
  last_name?: string,
  display_name?: string,
  avatar_url?: string
}

Nunca usar:

  • firstName (debe ser first_name)
  • lastName (debe ser last_name)
  • displayName (debe ser display_name)
  • avatarUrl (debe ser avatar_url)

🚨 Errores Comunes

Error 1: camelCase en lugar de snake_case

// ❌ INCORRECTO - causa 500 error
await apiClient.post('/auth/register', {
  email: "user@example.com",
  password: "pass123",
  firstName: "John",     // ❌ Backend no reconoce este campo
  lastName: "Doe"        // ❌ Backend no reconoce este campo
});

// ✅ CORRECTO
await apiClient.post('/auth/register', {
  email: "user@example.com",
  password: "pass123",
  first_name: "John",    // ✅ Backend reconoce este campo
  last_name: "Doe"       // ✅ Backend reconoce este campo
});

Error 2: Enviar campo role en registro público

// ❌ INCORRECTO - Backend rechaza el campo 'role'
await apiClient.post('/auth/register', {
  email: "user@example.com",
  password: "pass123",
  first_name: "John",
  last_name: "Doe",
  role: "student"        // ❌ Backend NO acepta esto (auto-asignado)
});

// ✅ CORRECTO
await apiClient.post('/auth/register', {
  email: "user@example.com",
  password: "pass123",
  first_name: "John",
  last_name: "Doe"
  // role omitido - Backend lo asigna automáticamente
});

Error 3: Campos opcionales como requeridos

// ❌ INCORRECTO - forzar campos opcionales
const payload = {
  email: data.email,
  password: data.password,
  first_name: firstName || "",   // ❌ No enviar string vacío
  last_name: lastName || ""       // ❌ No enviar string vacío
};

// ✅ CORRECTO - solo enviar si tienen valor
const payload = {
  email: data.email,
  password: data.password,
  ...(firstName && { first_name: firstName }),
  ...(lastName && { last_name: lastName })
};

📊 Tabla de Referencia Rápida

Campo Frontend Campo Backend Uso
email email Igual
password password Igual
firstName first_name ⚠️ Transformar a snake_case
lastName last_name ⚠️ Transformar a snake_case
displayName display_name ⚠️ Transformar a snake_case
avatarUrl avatar_url ⚠️ Transformar a snake_case
phoneNumber phone_number ⚠️ Transformar a snake_case
dateOfBirth date_of_birth ⚠️ Transformar a snake_case
role role ⚠️ Solo en endpoints admin

🛠️ Implementación en Frontend

Función Helper para Transformar

/**
 * Transforma objeto de camelCase a snake_case para enviar a Backend
 */
export function toSnakeCase<T extends Record<string, any>>(obj: T): any {
  const result: any = {};
  
  for (const [key, value] of Object.entries(obj)) {
    // Convertir camelCase a snake_case
    const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
    result[snakeKey] = value;
  }
  
  return result;
}

// Uso
const frontendData = {
  firstName: "John",
  lastName: "Doe",
  email: "john@example.com"
};

const backendPayload = toSnakeCase(frontendData);
// { first_name: "John", last_name: "Doe", email: "john@example.com" }

Mapping Manual (Recomendado)

// ✅ MEJOR PRÁCTICA: Mapping explícito
const backendPayload = {
  email: frontendData.email,
  password: frontendData.password,
  first_name: frontendData.firstName,
  last_name: frontendData.lastName
};

// Beneficios:
// - Type-safe
// - Explícito y fácil de leer
// - Control total de qué campos enviar

📝 Validación en Desarrollo

Backend (NestJS)

// class-validator rechaza campos no definidos
export class RegisterUserDto {
  @IsEmail()
  email!: string;

  @IsString()
  @MinLength(8)
  password!: string;

  @IsString()
  @IsOptional()
  first_name?: string;  // ✅ snake_case definido

  @IsString()
  @IsOptional()
  last_name?: string;   // ✅ snake_case definido
  
  // firstName NO definido → Backend rechazará con 400
  // role NO definido en registro público → Backend rechazará con 400
}

Frontend (TypeScript)

// Tipos para Backend API
export interface RegisterPayload {
  email: string;
  password: string;
  first_name?: string;  // ✅ snake_case
  last_name?: string;   // ✅ snake_case
}

// Tipos internos del Frontend (pueden usar camelCase)
export interface RegisterFormData {
  email: string;
  password: string;
  firstName?: string;   // camelCase interno
  lastName?: string;    // camelCase interno
}

// Función de transformación
function toBackendPayload(data: RegisterFormData): RegisterPayload {
  return {
    email: data.email,
    password: data.password,
    first_name: data.firstName,
    last_name: data.lastName
  };
}

🔍 Testing

Test de Convención

describe('API Payload Conventions', () => {
  it('should use snake_case for backend API calls', async () => {
    const payload = {
      email: 'test@example.com',
      password: 'Test123!',
      first_name: 'John',
      last_name: 'Doe'
    };
    
    // Verificar que NO hay camelCase
    expect(payload).not.toHaveProperty('firstName');
    expect(payload).not.toHaveProperty('lastName');
    
    // Verificar snake_case
    expect(payload).toHaveProperty('first_name');
    expect(payload).toHaveProperty('last_name');
  });
});

📚 Referencias

Documentos relacionados:

Correcciones aplicadas:

  • FE-053: Fix Register 500 Error (2025-11-16)
  • Docs: API-CHEATSHEET.md actualizado (2025-11-16)
  • Docs: US-FUND-001 actualizado (2025-11-16)

Creado: 2025-11-16 Última actualización: 2025-11-16 Responsable: Tech Lead Estado: Activo