# Controladores

Los controladores son responsables de manejar solicitudes HTTP y WebSocket en su aplicación. Sirven como punto de entrada para solicitudes externas y actúan como puente entre la capa de transporte y su lógica de negocio.

## Propósito

Los controladores SOLO DEBEN realizar estas tareas específicas:

1. **Validar la entrada de la capa de transporte** - Verificar autenticación, autorización y formato de entrada
2. **Llamar a componentes de lógica** - Delegar la lógica de negocio a los componentes de lógica apropiados
3. **Transformar la entrada** - Convertir los datos de la solicitud en parámetros para las llamadas a componentes de lógica
4. **Manejar errores** - Capturar y responder correctamente a los errores de los componentes de lógica

{% hint style="warning" %}
Los controladores NO deben contener lógica de negocio. Todas las reglas de negocio DEBEN implementarse en los componentes de lógica.
{% endhint %}

## Ubicación

Todos los controladores DEBEN colocarse bajo el `/src/controllers` directorio y nombrarse en referencia a la acción que realizan.

```
src/
└── controllers/
    ├── users/
    │   ├── get-user.ts
    │   ├── create-user.ts
    │   └── update-user.ts
    ├── content/
    │   ├── publish-content.ts
    │   └── delete-content.ts
    └── health.ts
```

## Estructura del controlador

### Manejador HTTP básico

```tsx
import { HandlerContextWithPath } from '@dcl/platform-server-commons'

export async function getUserHandler(
  context: HandlerContextWithPath<'userLogic' | 'logs', '/users/:id'>
) {
  const { 
    components: { userLogic, logs },
    params: { id },
    verification 
  } = context

  const logger = logs.getLogger('get-user-handler')

  try {
    // 1. Validar autenticación
    if (!verification?.auth) {
      return {
        status: 401,
        body: {
          error: 'Unauthorized',
          message: 'Authentication required'
        }
      }
    }

    // 2. Validar entrada
    if (!id || typeof id !== 'string') {
      return {
        status: 400,
        body: {
          error: 'Bad Request',
          message: 'Invalid user ID'
        }
      }
    }

    // 3. Llamar al componente de lógica
    const user = await userLogic.getUser(id)

    // 4. Devolver respuesta de éxito
    return {
      status: 200,
      body: {
        ok: true,
        data: user
      }
    }
  } catch (error) {
    // 5. Manejar errores
    logger.error('Error fetching user', error)

    if (error instanceof UserNotFoundError) {
      return {
        status: 404,
        body: {
          error: 'Not Found',
          message: error.message
        }
      }
    }

    return {
      status: 500,
      body: {
        error: 'Internal Server Error',
        message: 'An unexpected error occurred'
      }
    }
  }
}
```

### Manejador de solicitudes POST

```tsx
export async function createUserHandler(
  context: HandlerContextWithPath<'userLogic' | 'logs', '/users'>
) {
  const {
    components: { userLogic, logs },
    request,
    verification
  } = context

  const logger = logs.getLogger('create-user-handler')

  try {
    // 1. Validar autenticación
    if (!verification?.auth) {
      return {
        status: 401,
        body: {
          error: 'Unauthorized',
          message: 'Authentication required'
        }
      }
    }

    // 2. Analizar y validar el cuerpo de la solicitud
    const body = await request.json()
    
    if (!body.username || !body.email) {
      return {
        status: 400,
        body: {
          error: 'Bad Request',
          message: 'Username and email are required'
        }
      }
    }

    // 3. Transformar la entrada para el componente de lógica
    const userData = {
      username: body.username.toLowerCase(),
      email: body.email.toLowerCase(),
      display_name: body.display_name || body.username
    }

    // 4. Llamar al componente de lógica
    const user = await userLogic.createUser(userData)

    // 5. Devolver respuesta de éxito
    return {
      status: 201,
      body: {
        ok: true,
        data: user
      }
    }
  } catch (error) {
    logger.error('Error creating user', error)

    if (error instanceof UserAlreadyExistsError) {
      return {
        status: 409,
        body: {
          error: 'Conflict',
          message: error.message
        }
      }
    }

    if (error instanceof InvalidEmailError) {
      return {
        status: 400,
        body: {
          error: 'Bad Request',
          message: error.message
        }
      }
    }

    return {
      status: 500,
      body: {
        error: 'Internal Server Error',
        message: 'An unexpected error occurred'
      }
    }
  }
}
```

### Manejador de parámetros de consulta

```tsx
export async function searchUsersHandler(
  context: HandlerContextWithPath<'userLogic' | 'logs', '/users/search'>
) {
  const {
    components: { userLogic, logs },
    url,
    verification
  } = context

  const logger = logs.getLogger('search-users-handler')

  try {
    // 1. Validar autenticación
    if (!verification?.auth) {
      return {
        status: 401,
        body: {
          error: 'Unauthorized',
          message: 'Authentication required'
        }
      }
    }

    // 2. Extraer y validar parámetros de consulta (¡en minúsculas!)
    const searchParams = new URLSearchParams(url.search)
    const query = searchParams.get('query')?.toLowerCase()
    const limit = parseInt(searchParams.get('limit') || '10', 10)
    const offset = parseInt(searchParams.get('offset') || '0', 10)

    if (!query) {
      return {
        status: 400,
        body: {
          error: 'Bad Request',
          message: 'Query parameter is required'
        }
      }
    }

    // 3. Llamar al componente de lógica
    const results = await userLogic.searchUsers({
      query,
      limit,
      offset
    })

    // 4. Devolver respuesta de éxito
    return {
      status: 200,
      body: {
        ok: true,
        data: results
      }
    }
  } catch (error) {
    logger.error('Error searching users', error)

    return {
      status: 500,
      body: {
        error: 'Internal Server Error',
        message: 'An unexpected error occurred'
      }
    }
  }
}
```

## Requisitos de entrada

Todas las claves de entrada recibidas a través de los controladores (cuerpo JSON, parámetros de consulta, parámetros de URL, encabezados, etc.) **DEBEN estar en minúsculas** para evitar problemas de mayúsculas/minúsculas al procesar datos.

### Parámetros con varias palabras

Los parámetros de varias palabras DEBEN definirse usando **snake\_case**.

{% hint style="danger" %}
**Nombres incorrectos:**

* `minPrice`
* `Car`
* `somethingURL`
* `userId`
  {% endhint %}

{% hint style="success" %}
**Nombres correctos:**

* `min_price`
* `car`
* `something_url`
* `user_id`
  {% endhint %}

### Ejemplo

```tsx
// Cuerpo de la solicitud
{
  "user_name": "john_doe",        // ✅ Correcto
  "email_address": "john@example.com",  // ✅ Correcto
  "display_name": "John Doe",     // ✅ Correcto
  "max_items": 100                // ✅ Correcto
}

// Parámetros de consulta
?search_query=test&max_results=50&sort_by=created_at  // ✅ Correcto

// Parámetros de URL
/users/:user_id/posts/:post_id   // ✅ Correcto
```

## Manejo de errores

### Respuestas de error estándar

Los controladores deben devolver formatos de respuesta de error consistentes:

```tsx
// 400 Bad Request
{
  "error": "Bad Request",
  "message": "Detailed error message",
  "details": {} // Detalles adicionales opcionales
}

// 401 Unauthorized
{
  "error": "Unauthorized",
  "message": "Authentication required"
}

// 403 Forbidden
{
  "error": "Forbidden",
  "message": "You don't have permission to access this resource"
}

// 404 Not Found
{
  "error": "Not Found",
  "message": "Resource not found"
}

// 409 Conflict
{
  "error": "Conflict",
  "message": "Resource already exists"
}

// 500 Internal Server Error
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred"
}
```

### Mapeo de errores

Mapear los errores del dominio a los códigos de estado HTTP apropiados:

```tsx
function mapErrorToResponse(error: Error, logger: ILogger) {
  logger.error('Handler error', error)

  // Errores específicos del dominio
  if (error instanceof UserNotFoundError) {
    return { status: 404, body: { error: 'Not Found', message: error.message } }
  }
  
  if (error instanceof UserAlreadyExistsError) {
    return { status: 409, body: { error: 'Conflict', message: error.message } }
  }
  
  if (error instanceof UnauthorizedError) {
    return { status: 403, body: { error: 'Forbidden', message: error.message } }
  }
  
  if (error instanceof InvalidInputError) {
    return { status: 400, body: { error: 'Bad Request', message: error.message } }
  }

  // Error genérico
  return {
    status: 500,
    body: {
      error: 'Internal Server Error',
      message: 'An unexpected error occurred'
    }
  }
}
```

## Autenticación y autorización

### Comprobación de autenticación

```tsx
// Requerir autenticación
if (!verification?.auth) {
  return {
    status: 401,
    body: {
      error: 'Unauthorized',
      message: 'Authentication required'
    }
  }
}

const userAddress = verification.auth.toLowerCase()
```

### Comprobación de autorización

```tsx
// Comprobar permisos mediante componente de lógica
const canAccess = await permissionsLogic.canUserAccessResource(
  userAddress,
  resourceId
)

if (!canAccess) {
  return {
    status: 403,
    body: {
      error: 'Forbidden',
      message: 'You don\'t have permission to access this resource'
    }
  }
}
```

## Buenas prácticas

### 1. Mantener los controladores delgados

Los controladores deben ser envoltorios delgados alrededor de los componentes de lógica:

```tsx
// ✅ Bien: controlador delgado
export async function handler(context) {
  const { userLogic } = context.components
  const user = await userLogic.getUser(context.params.id)
  return { status: 200, body: { data: user } }
}

// ❌ Mal: lógica de negocio en el controlador
export async function handler(context) {
  const { database } = context.components
  const user = await database.query('SELECT * FROM users...')
  const processed = processUser(user) // ¡Lógica de negocio!
  const validated = validateUser(processed) // ¡Lógica de negocio!
  return { status: 200, body: { data: validated } }
}
```

### 2. Validar temprano

Validar y fallar rápido:

```tsx
// Validar autenticación primero
if (!verification?.auth) {
  return { status: 401, body: { error: 'Unauthorized' } }
}

// Luego validar la entrada
if (!params.id) {
  return { status: 400, body: { error: 'Bad Request' } }
}

// Luego proceder con la lógica de negocio
const result = await logic.doSomething(params.id)
```

### 3. Usar seguridad de tipos

Aprovechar TypeScript para seguridad de tipos:

```tsx
interface CreateUserRequest {
  user_name: string
  email_address: string
  display_name?: string
}

const body: CreateUserRequest = await request.json()
```

### 4. Registrar apropiadamente

Registrar eventos y errores importantes:

```tsx
logger.info('User created', { userId: user.id, userAddress })
logger.warn('Rate limit approaching', { userAddress, requests })
logger.error('Failed to create user', { error, userAddress })
```

## Pruebas de controladores

Vea la [Testing Services (WKC)](https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/testing-standards/testing-services-wkc) documentación para orientación sobre pruebas de integración.
