### GET `/perfil-profesional`

Lista el catálogo de perfiles profesionales disponibles. Permite filtrar por estado (`1` activos, `0` inactivos) y realizar búsquedas por nombre usando `?buscar=`.

**Autenticación:** ✅ Bearer Token  
**Parámetros opcionales:**

| Param | Tipo | Descripción |
|-------|------|-------------|
| `estado` | integer | `1` activos, `0` inactivos |
| `buscar` | string | Texto para filtrar por nombre |

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Perfiles profesionales obtenidos correctamente",
  "data": [
    {
      "ppf_id": 1,
      "ppf_nombre": "Desarrollador Frontend",
      "ppf_descripcion": "Especialista en interfaces",
      "ppf_estado": 1
    },
    {
      "ppf_id": 2,
      "ppf_nombre": "Desarrollador Backend",
      "ppf_descripcion": "Especialista en APIs",
      "ppf_estado": 1
    }
  ]
}
```

#### Response sin resultados (200)

```json
{
  "success": true,
  "message": "Perfiles profesionales obtenidos correctamente",
  "data": []
}
```

---

# Nexus API — Documentación Completa

> **Base URL:** `http://localhost/enxu_back/public`  
> **Versión:** v1  
> **Autenticación:** JWT Bearer Token  
> **Formato de respuesta:** JSON

---

## Tabla Maestra de Endpoints

| Método | Endpoint | Descripción | Auth | Body / Params |
|--------|----------|-------------|------|---------------|
| GET | `/` | Health check de la API | ❌ | — |
| **AUTH** | | | | |
| POST | `/auth/login` | Login con email y contraseña | ❌ | `email`, `password`, `device_uuid` |
| POST | `/auth/registrar` | Registro de nuevo usuario | ❌ | `email`, `password`, `device_uuid` |
| POST | `/auth/login-google` | Login con Google OAuth | ❌ | `id_token`, `email`, `device_uuid` |
| POST | `/auth/login-facebook` | Login con Facebook OAuth | ❌ | `access_token`, `email`, `device_uuid` |
| GET | `/auth/verificar-sesion` | Verifica que la sesión sea válida | ✅ | — |
| POST | `/auth/logout` | Cierra la sesión actual | ✅ | — |
| **PASSWORD** | | | | |
| POST | `/password/solicitar` | Solicita enlace de recuperación | ❌ | `email` |
| GET | `/password/validar` | Valida token de recuperación | ❌ | `?token=` |
| POST | `/password/restablecer` | Restablece la contraseña | ❌ | `token`, `password`, `confirmacion` |
| **PERFIL** | | | | |
| POST | `/perfil` | Crea o actualiza perfil personal | ✅ | campos opcionales del perfil |
| GET | `/perfil/resumen` | Resumen completo del perfil | ✅ | — |
| GET | `/perfil/habilidades` | Lista habilidades del perfil | ✅ | — |
| PUT | `/perfil/habilidades` | Actualiza habilidades del perfil | ✅ | `habilidades[]` |
| **PERFIL PROFESIONAL** | | | | |
| GET | `/perfil-profesional` | Lista perfiles profesionales | ✅ | `?estado=`, `?buscar=` |
| POST | `/perfil-profesional` | Crea perfil profesional | ✅ | `ppf_nombre`, `ppf_descripcion` |
| PUT | `/perfil-profesional/{ppf_id}` | Actualiza perfil profesional | ✅ | `ppf_nombre`, `ppf_descripcion` |
| **UBICACIONES** | | | | |
| GET | `/ubicaciones/paises` | Lista todos los países | ❌ | — |
| GET | `/ubicaciones/paises/{paisId}/departamentos` | Lista departamentos de un país | ❌ | path: `paisId` |
| GET | `/ubicaciones/departamentos/{depId}/ciudades` | Lista ciudades de un departamento | ❌ | path: `depId` |
| **PROYECTOS** | | | | |
| POST | `/proyectos` | Crea un proyecto | ✅ | `titulo`, `descripcion`, `perfiles[]` |
| GET | `/proyectos` | Lista proyectos del usuario | ✅ | — |
| GET | `/proyectos/obtener` | Obtiene proyecto por ID | ✅ | `?id=` |
| PUT | `/proyectos/actualizar` | Actualiza un proyecto | ✅ | `?id=`, campos opcionales |
| DELETE | `/proyectos/eliminar` | Anula un proyecto | ✅ | `?id=` |
| POST | `/proyectos/postular` | Postularse a un proyecto | ✅ | `proyecto_id`, `hab_id` |
| GET | `/proyectos/postulaciones` | Lista postulaciones recibidas | ✅ | `?proyecto_id=`, `?estado=` |
| POST | `/proyectos/postulaciones/gestionar` | Acepta o rechaza postulación | ✅ | `postulacion_id`, `accion` |
| GET | `/proyectos/compatibles` | Proyectos compatibles (matching) | ✅ | `?limite=`, `?offset=`, `?pagina=` |
| **CHAT** | | | | |
| GET | `/chat/conversaciones` | Lista conversaciones del usuario (con total y mensaje contextual) | ✅ | — |
| GET | `/chat/mensajes` | Obtiene mensajes de conversación | ✅ | `?conversacion_id=`, `?limite=`, `?offset=` |
| POST | `/chat/mensajes/texto` | Envía mensaje de texto | ✅ | `conversacion_id`, `contenido` |
| POST | `/chat/mensajes/imagen` | Envía mensaje con imagen | ✅ | `conversacion_id`, `archivo` (multipart) |

---

## Formato de respuesta universal

Todos los endpoints retornan el mismo envelope JSON:

```json
{
  "success": true,
  "message": "Descripción del resultado",
  "data": {}
}
```

En caso de error:

```json
{
  "success": false,
  "message": "Descripción del error"
}
```

---

## Módulo: Auth

### POST `/auth/login`

Autentica un usuario existente con email y contraseña.

**Autenticación:** No requerida  
**Content-Type:** `application/json`

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `email` | string | ✅ | Email del usuario |
| `password` | string | ✅ | Contraseña del usuario |
| `device_uuid` | string (UUID) | ✅ | Identificador único del dispositivo |
| `device_sistema` | string | ❌ | Sistema operativo (ej: "Android", "iOS") |
| `device_modelo` | string | ❌ | Modelo del dispositivo (ej: "iPhone 13") |

#### Request de ejemplo

```json
{
  "email": "usuario@ejemplo.com",
  "password": "MiPassword123!",
  "device_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "device_sistema": "Android",
  "device_modelo": "Samsung Galaxy S21"
}
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Login exitoso",
  "data": {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "usuario": {
      "usu_id": 1,
      "usu_email": "usuario@ejemplo.com",
      "usu_estado": 1,
      "usu_verificado": 1
    }
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Login exitoso |
| 400 | Campos requeridos faltantes |
| 401 | Credenciales inválidas |

---

### POST `/auth/registrar`

Registra un nuevo usuario en el sistema.

**Autenticación:** No requerida  
**Content-Type:** `application/json`

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `email` | string | ✅ | Email del usuario (debe ser único) |
| `password` | string | ✅ | Contraseña |
| `device_uuid` | string (UUID) | ✅ | Identificador único del dispositivo |
| `device_sistema` | string | ❌ | Sistema operativo |
| `device_modelo` | string | ❌ | Modelo del dispositivo |

#### Request de ejemplo

```json
{
  "email": "nuevo@ejemplo.com",
  "password": "MiPassword123!",
  "device_uuid": "550e8400-e29b-41d4-a716-446655440001",
  "device_sistema": "iOS",
  "device_modelo": "iPhone 13"
}
```

#### Response exitoso (201)

```json
{
  "success": true,
  "message": "Usuario registrado exitosamente",
  "data": {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "usuario": {
      "usu_id": 42,
      "usu_email": "nuevo@ejemplo.com",
      "usu_estado": 1,
      "usu_verificado": 0
    }
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 201 | Usuario registrado |
| 400 | Email ya registrado o campos faltantes |

---

### POST `/auth/login-google`

Autentica o registra al usuario con Google Sign-In.

**Autenticación:** No requerida  
**Content-Type:** `application/json`

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `id_token` | string | ✅ | Token JWT proporcionado por Google |
| `email` | string | ✅ | Email del usuario (verificado por Google) |
| `device_uuid` | string (UUID) | ✅ | Identificador único del dispositivo |
| `device_sistema` | string | ❌ | Sistema operativo |
| `device_modelo` | string | ❌ | Modelo del dispositivo |

#### Request de ejemplo

```json
{
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "email": "usuario@gmail.com",
  "device_uuid": "550e8400-e29b-41d4-a716-446655440002",
  "device_sistema": "Android",
  "device_modelo": "Pixel 6"
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Login con Google exitoso |
| 400 | Token, email o device_uuid faltantes |

---

### POST `/auth/login-facebook`

Autentica o registra al usuario con Facebook.

**Autenticación:** No requerida  
**Content-Type:** `application/json`

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `access_token` | string | ✅ | Access token de Facebook |
| `email` | string | ✅ | Email del usuario |
| `device_uuid` | string (UUID) | ✅ | Identificador único del dispositivo |
| `device_sistema` | string | ❌ | Sistema operativo |
| `device_modelo` | string | ❌ | Modelo del dispositivo |

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Login con Facebook exitoso |
| 400 | Campos requeridos faltantes |

---

### GET `/auth/verificar-sesion`

Verifica que el token JWT sea vigente y la sesión esté activa.

**Autenticación:** ✅ Bearer Token  

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Sesión válida",
  "data": {
    "usu_id": 1,
    "usu_email": "usuario@ejemplo.com",
    "sesion_activa": true
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Sesión válida |
| 401 | Token inválido, expirado o sesión cerrada |

---

### POST `/auth/logout`

Cierra la sesión invalidando el token actual en base de datos.

**Autenticación:** ✅ Bearer Token  

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Logout exitoso",
  "data": null
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Logout exitoso |
| 400 | Token no proporcionado |
| 401 | Token inválido |

---

## Módulo: Password

### POST `/password/solicitar`

Envía un correo con enlace de recuperación de contraseña. Por seguridad, siempre responde `202` aunque el email no exista.

**Autenticación:** No requerida  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `email` | string | ✅ | Email del usuario |

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 202 | Solicitud procesada (respuesta siempre igual) |
| 400 | Email no proporcionado |
| 500 | Error interno |

---

### GET `/password/validar`

Valida un token de recuperación antes de mostrar el formulario de nueva contraseña.

**Autenticación:** No requerida  

#### Parámetros de query

| Param | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `token` | string | ✅ | Token de recuperación recibido por email |

#### Request de ejemplo

```
GET /password/validar?token=abc123tokenreset
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Token válido",
  "data": {
    "token": "abc123tokenreset",
    "email": "usuario@ejemplo.com",
    "expira": "2025-03-13T23:59:59"
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Token válido |
| 400 | Token no proporcionado, inválido o expirado |

---

### POST `/password/restablecer`

Establece la nueva contraseña usando el token de recuperación.

**Autenticación:** No requerida  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `token` | string | ✅ | Token de recuperación |
| `password` | string | ✅ | Nueva contraseña |
| `confirmacion` | string | ✅ | Confirmación de la nueva contraseña (debe coincidir) |

#### Request de ejemplo

```json
{
  "token": "abc123tokenreset",
  "password": "NuevaPassword123!",
  "confirmacion": "NuevaPassword123!"
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Contraseña restablecida correctamente |
| 400 | Campos faltantes, token inválido o contraseñas no coinciden |

---

## Módulo: Perfil

### POST `/perfil`

Crea el perfil personal si no existe, o lo actualiza si ya existe. Opera como upsert.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body (todos opcionales)

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `per_nombres` | string | Nombres del usuario |
| `per_ape_paterno` | string | Apellido paterno |
| `per_ape_materno` | string | Apellido materno |
| `per_genero` | string | Género (ej: "M", "F") |
| `per_empresa` | string | Empresa actual |
| `ppf_id` | integer | ID del perfil profesional asignado |
| `per_idioma` | string | Idioma preferido (default: "es") |
| `per_tema` | string | Tema de la UI: "claro" o "oscuro" (default: "claro") |
| `per_titulo` | string | Título profesional descriptivo |
| `per_descripcion` | string | Bio o descripción del usuario |
| `per_remoto` | integer | Disponibilidad remota: 1=sí, 0=no |
| `per_disponibilidad` | integer | Disponibilidad general |
| `ciu_id` | integer | ID de la ciudad de residencia |

#### Request de ejemplo

```json
{
  "per_nombres": "Juan Carlos",
  "per_ape_paterno": "Rodríguez",
  "per_ape_materno": "López",
  "per_genero": "M",
  "ppf_id": 2,
  "per_titulo": "Desarrollador Full Stack",
  "per_descripcion": "Desarrollador con 5 años de experiencia en tecnologías web.",
  "per_remoto": 1,
  "ciu_id": 5
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Perfil actualizado |
| 201 | Perfil creado por primera vez |
| 400 | Error de validación (ej: ppf_id inválido) |
| 401 | No autenticado |

---

### GET `/perfil/resumen`

Retorna un resumen completo: datos del usuario, perfil, perfil profesional, métricas y habilidades.

**Autenticación:** ✅ Bearer Token  

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Resumen de perfil obtenido correctamente",
  "data": {
    "usuario": {
      "usu_id": 1,
      "email": "usuario@ejemplo.com",
      "verificado": true
    },
    "perfil": {
      "nombres_completos": "Juan Carlos Rodríguez López",
      "titulo_profesional": "Desarrollador Backend",
      "descripcion": "Especialista en PHP y APIs REST",
      "ciudad_id": 5
    },
    "perfil_profesional": {
      "ppf_id": 2,
      "ppf_nombre": "Desarrollador Backend",
      "ppf_estado": 1
    },
    "metricas": {
      "proyectos_creados": 3,
      "proyectos_participa": 5,
      "total_conexiones": 12
    },
    "conexiones": [],
    "habilidades": [
      { "hab_id": 1, "hab_nombre": "PHP", "hab_categoria": "Backend" }
    ]
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Resumen obtenido |
| 401 | No autenticado |
| 404 | Usuario no encontrado |
| 500 | Error interno |

---

### GET `/perfil/habilidades`

Lista las habilidades vinculadas al perfil profesional del usuario.

**Autenticación:** ✅ Bearer Token  

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Habilidades obtenidas correctamente",
  "data": [
    { "hab_id": 1, "hab_nombre": "PHP", "hab_categoria": "Backend" },
    { "hab_id": 5, "hab_nombre": "JavaScript", "hab_categoria": "Frontend" }
  ]
}
```

---

### PUT `/perfil/habilidades`

Reemplaza completamente el conjunto de habilidades del perfil.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `habilidades` | integer[] | ✅ | Array de IDs de habilidades |

#### Request de ejemplo

```json
{
  "habilidades": [1, 3, 5, 8]
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Habilidades actualizadas |
| 401 | No autenticado |

---

## Módulo: Perfil Profesional

Catálogo de roles/tipos de usuario disponibles en la plataforma (ej: "Desarrollador Backend", "Diseñador UX").

### GET `/perfil-profesional`

Lista los perfiles profesionales con filtros opcionales.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query (opcionales)

| Param | Tipo | Descripción |
|-------|------|-------------|
| `estado` | integer | Filtrar: 1=activo, 0=inactivo |
| `buscar` | string | Búsqueda por nombre |

#### Request de ejemplo

```
GET /perfil-profesional?estado=1&buscar=desarrollador
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Perfiles profesionales obtenidos correctamente",
  "data": [
    { "ppf_id": 1, "ppf_nombre": "Desarrollador Frontend", "ppf_estado": 1 },
    { "ppf_id": 2, "ppf_nombre": "Desarrollador Backend", "ppf_estado": 1 }
  ]
}
```

---

### POST `/perfil-profesional`

Crea un nuevo perfil profesional en el catálogo.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `ppf_nombre` | string | ✅ | Nombre del perfil profesional |
| `ppf_descripcion` | string | ❌ | Descripción del perfil |

#### Request de ejemplo

```json
{
  "ppf_nombre": "Desarrollador Mobile",
  "ppf_descripcion": "Especialista en aplicaciones móviles iOS y Android"
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 201 | Perfil profesional creado |
| 400 | Nombre requerido u otro error |
| 401 | No autenticado |

---

### PUT `/perfil-profesional/{ppf_id}`

Actualiza un perfil profesional existente.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de path

| Param | Tipo | Descripción |
|-------|------|-------------|
| `ppf_id` | integer | ID del perfil profesional |

#### Parámetros del body (todos opcionales)

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `ppf_nombre` | string | Nuevo nombre |
| `ppf_descripcion` | string | Nueva descripción |

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Actualizado correctamente |
| 400 | ID inválido o error de validación |
| 401 | No autenticado |

---

## Módulo: Ubicaciones

Catálogos geográficos. **No requieren autenticación.**

### GET `/ubicaciones/paises`

Lista todos los países disponibles.

#### Response de ejemplo

```json
{
  "success": true,
  "message": "Listado de países",
  "data": [
    { "pai_id": 1, "pai_nombre": "Colombia", "pai_codigo": "CO" },
    { "pai_id": 2, "pai_nombre": "México", "pai_codigo": "MX" }
  ]
}
```

---

### GET `/ubicaciones/paises/{paisId}/departamentos`

Lista los departamentos/estados de un país.

#### Parámetros de path

| Param | Tipo | Descripción |
|-------|------|-------------|
| `paisId` | integer | ID del país |

#### Response de ejemplo

```json
{
  "success": true,
  "message": "Listado de departamentos",
  "data": [
    { "dep_id": 1, "dep_nombre": "Antioquia", "pai_id": 1 },
    { "dep_id": 2, "dep_nombre": "Cundinamarca", "pai_id": 1 }
  ]
}
```

---

### GET `/ubicaciones/departamentos/{depId}/ciudades`

Lista las ciudades de un departamento.

#### Parámetros de path

| Param | Tipo | Descripción |
|-------|------|-------------|
| `depId` | integer | ID del departamento |

#### Response de ejemplo

```json
{
  "success": true,
  "message": "Listado de ciudades",
  "data": [
    { "ciu_id": 1, "ciu_nombre": "Medellín", "dep_id": 1 },
    { "ciu_id": 2, "ciu_nombre": "Bello", "dep_id": 1 }
  ]
}
```

---

## Módulo: Proyectos

### POST `/proyectos`

Crea un proyecto. El usuario autenticado queda como propietario. Se deben definir perfiles profesionales requeridos con habilidades y cupos.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `titulo` | string (max 150) | ✅ | Título del proyecto |
| `descripcion` | string | ✅ | Descripción detallada |
| `ciudad_id` | integer | ❌ | ID de la ciudad del proyecto |
| `remoto` | boolean | ❌ | Si acepta trabajo remoto (default: false) |
| `perfiles` | array | ✅ | Lista de perfiles requeridos (mín. 1) |
| `perfiles[].ppf_id` | integer | ✅ | ID del perfil profesional requerido |
| `perfiles[].cupos` | integer (≥1) | ✅ | Cantidad de cupos para ese perfil |
| `perfiles[].habilidades_requeridas` | integer[] | ✅ | IDs de habilidades requeridas (mín. 1) |
| `socios` | array | ❌ | Socios iniciales a agregar |
| `socios[].usu_id` | integer | ✅ | ID del usuario socio |
| `socios[].hab_id` | integer | ✅ | Habilidad con la que participa |

#### Request de ejemplo

```json
{
  "titulo": "App de Delivery con React Native",
  "descripcion": "Desarrollo de una aplicación móvil de delivery con seguimiento en tiempo real.",
  "ciudad_id": 1,
  "remoto": true,
  "perfiles": [
    {
      "ppf_id": 1,
      "cupos": 2,
      "habilidades_requeridas": [5, 9]
    },
    {
      "ppf_id": 2,
      "cupos": 1,
      "habilidades_requeridas": [1, 3]
    }
  ],
  "socios": [
    { "usu_id": 7, "hab_id": 5 }
  ]
}
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Proyecto creado exitosamente",
  "data": {
    "proyecto_id": 10,
    "proyecto": {
      "pro_id": 10,
      "pro_titulo": "App de Delivery con React Native",
      "pro_remoto": true,
      "pro_estado": 1
    },
    "perfiles": [
      { "ppf_id": 1, "prp_cupos": 2, "prp_cupos_ocupados": 1 }
    ],
    "socios": [
      { "pm_id": 1, "usu_id": 7, "hab_id": 5, "ppf_id": 1 }
    ]
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Proyecto creado |
| 400 | Datos inválidos, título duplicado, ciudad inexistente, etc. |
| 401 | No autenticado |

---

### GET `/proyectos`

Lista todos los proyectos creados por el usuario autenticado.

**Autenticación:** ✅ Bearer Token  

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Lista de proyectos |
| 401 | No autenticado |

---

### GET `/proyectos/obtener`

Obtiene el detalle de un proyecto por ID.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query

| Param | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `id` | integer | ✅ | ID del proyecto |

#### Request de ejemplo

```
GET /proyectos/obtener?id=10
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Proyecto obtenido |
| 400 | ID inválido |
| 401 | No autenticado |
| 403 | No es propietario del proyecto |
| 404 | Proyecto no encontrado |

---

### PUT `/proyectos/actualizar`

Actualiza un proyecto existente. Solo el propietario puede actualizar y solo si está activo.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query

| Param | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `id` | integer | ✅ | ID del proyecto |

#### Parámetros del body (todos opcionales)

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `titulo` | string | Nuevo título |
| `descripcion` | string | Nueva descripción |
| `ciudad_id` | integer | Nueva ciudad |
| `remoto` | boolean | Cambiar modalidad remota |
| `fecha_inicio` | string (date) | Nueva fecha de inicio |

#### Request de ejemplo

```
PUT /proyectos/actualizar?id=10
```

```json
{
  "titulo": "App de Delivery v2 - React Native",
  "remoto": false
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Proyecto actualizado |
| 400 | Datos inválidos o proyecto inactivo |
| 401 | No autenticado |
| 403 | No es propietario |
| 404 | Proyecto no encontrado |

---

### DELETE `/proyectos/eliminar`

Anula (soft-delete) un proyecto cambiando su estado a 0. El propietario puede reactivarlo manualmente en BD.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query

| Param | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `id` | integer | ✅ | ID del proyecto |

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Proyecto anulado |
| 400 | ID inválido o proyecto ya anulado |
| 401 | No autenticado |
| 403 | No es propietario |

---

### POST `/proyectos/postular`

Registra la postulación del usuario autenticado a un proyecto. Requiere que el usuario tenga perfil profesional y habilidades compatibles con el proyecto.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `proyecto_id` | integer | ✅ | ID del proyecto al que se postula |
| `hab_id` | integer | ✅ | ID de la habilidad principal que ofrece |

#### Request de ejemplo

```json
{
  "proyecto_id": 10,
  "hab_id": 5
}
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Postulación registrada correctamente",
  "data": {
    "postulacion_id": 15,
    "proyecto_id": 10,
    "usuario_id": 3,
    "hab_id": 5,
    "cupos_disponibles": 1
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Postulación registrada |
| 400 | Ya postulado, ya miembro, perfil incompleto, perfil incompatible, sin cupos |
| 401 | No autenticado |

---

### GET `/proyectos/postulaciones`

Lista las postulaciones recibidas en los proyectos del usuario autenticado (como dueño). Incluye datos del postulante.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query (opcionales)

| Param | Tipo | Descripción |
|-------|------|-------------|
| `proyecto_id` | integer | Filtrar por proyecto específico |
| `estado` | integer | 1=pendiente, 2=aceptada, 3=rechazada |

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Postulaciones obtenidas correctamente",
  "data": [
    {
      "postulacion": {
        "ppo_id": 15,
        "proyecto_id": 10,
        "hab_id": 5,
        "estado": 1,
        "fecha": "2025-03-12 20:00:00",
        "proyecto": { "pro_id": 10, "titulo": "App de Delivery", "estado": 1 }
      },
      "postulante": {
        "usuario": { "usu_id": 3, "usu_email": "postulante@ejemplo.com" },
        "perfil": { "per_nombres": "María", "per_ape_paterno": "García" },
        "perfil_profesional": { "ppf_nombre": "Desarrollador Frontend" },
        "habilidades": [{ "hab_id": 5, "hab_nombre": "JavaScript" }]
      }
    }
  ]
}
```

---

### POST `/proyectos/postulaciones/gestionar`

El propietario acepta o rechaza una postulación pendiente.

- **Al aceptar:** se crea el miembro en el proyecto, se reserva el cupo y se genera automáticamente la conversación de chat.
- **Al rechazar:** solo cambia el estado de la postulación.

**Autenticación:** ✅ Bearer Token  

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `postulacion_id` | integer | ✅ | ID de la postulación |
| `accion` | string | ✅ | `"aceptar"` o `"rechazar"` |

#### Request de ejemplo

```json
{
  "postulacion_id": 15,
  "accion": "aceptar"
}
```

#### Response exitoso - aceptar (200)

```json
{
  "success": true,
  "message": "Postulación gestionada correctamente",
  "data": {
    "postulacion_id": 15,
    "estado": 2,
    "miembro": { "pro_id": 10, "usu_id": 3, "hab_id": 5 },
    "conversacion_id": 8,
    "cupos_disponibles": 0
  }
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Postulación gestionada |
| 400 | Acción inválida, postulación ya gestionada, sin cupos |
| 401 | No autenticado |
| 403 | No es propietario del proyecto |

---

### GET `/proyectos/compatibles`

Sistema de matching: retorna proyectos donde el perfil y habilidades del usuario son compatibles.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query (todos opcionales)

| Param | Tipo | Descripción |
|-------|------|-------------|
| `limite` | integer | Resultados por página |
| `offset` | integer | Desplazamiento |
| `pagina` | integer | Número de página |

#### Request de ejemplo

```
GET /proyectos/compatibles?limite=10&pagina=1
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Proyectos compatibles obtenidos |
| 401 | No autenticado |

---

## Módulo: Chat

### GET `/chat/conversaciones`

Lista todas las conversaciones del usuario autenticado (como creador de proyecto o como socio).

**Autenticación:** ✅ Bearer Token  

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Conversaciones obtenidas correctamente",
  "data": {
    "total": 1,
    "conversaciones": [
      {
        "pc_id": 8,
        "pro_id": 10,
        "creador_id": 1,
        "socio_id": 3,
        "pc_estado": 1,
        "proyecto": { "pro_titulo": "App de Delivery" }
      }
    ]
  }
}
```

#### Response sin conversaciones (200)

```json
{
  "success": true,
  "message": "Aún no tienes conversaciones disponibles",
  "data": {
    "total": 0,
    "conversaciones": []
  }
}
```

---

### GET `/chat/mensajes`

Obtiene los mensajes de una conversación con paginación. El usuario debe ser participante.

**Autenticación:** ✅ Bearer Token  

#### Parámetros de query

| Param | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `conversacion_id` | integer | ✅ | ID de la conversación |
| `limite` | integer | ❌ | Cantidad de mensajes (default: 50) |
| `offset` | integer | ❌ | Desplazamiento (default: 0) |

#### Request de ejemplo

```
GET /chat/mensajes?conversacion_id=8&limite=50&offset=0
```

#### Response exitoso (200)

```json
{
  "success": true,
  "message": "Mensajes obtenidos correctamente",
  "data": {
    "conversacion_id": 8,
    "mensajes": [
      {
        "pm_id": 1,
        "pc_id": 8,
        "usu_id": 1,
        "pm_tipo": "texto",
        "pm_contenido": "Hola, bienvenido al proyecto!",
        "pm_fecha": "2025-03-12 22:30:00"
      }
    ]
  }
}
```

---

### POST `/chat/mensajes/texto`

Envía un mensaje de texto en una conversación.

**Autenticación:** ✅ Bearer Token  
**Content-Type:** `application/json`

#### Parámetros del body

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `conversacion_id` | integer | ✅ | ID de la conversación |
| `contenido` | string | ✅ | Texto del mensaje (no puede estar vacío) |

#### Request de ejemplo

```json
{
  "conversacion_id": 8,
  "contenido": "¡Hola! ¿Cómo avanza el proyecto?"
}
```

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Mensaje enviado |
| 400 | conversacion_id o contenido faltantes |
| 401 | No autenticado |

---

### POST `/chat/mensajes/imagen`

Envía una imagen en una conversación. Usar `multipart/form-data`.

**Autenticación:** ✅ Bearer Token  
**Content-Type:** `multipart/form-data`

#### Parámetros del form-data

| Campo | Tipo | Obligatorio | Descripción |
|-------|------|-------------|-------------|
| `conversacion_id` | text | ✅ | ID de la conversación |
| `archivo` | file | ✅ | Archivo de imagen (JPG, PNG, GIF, etc.) |

#### Códigos HTTP posibles

| Código | Descripción |
|--------|-------------|
| 200 | Imagen enviada |
| 400 | conversacion_id o archivo faltantes |
| 401 | No autenticado |

---

## Modelos de datos

### Usuario

```json
{
  "usu_id": 1,
  "usu_email": "usuario@ejemplo.com",
  "usu_estado": 1,
  "usu_verificado": 1,
  "usu_metodo_verificacion": "email",
  "usu_ultimo_login": "2025-03-12T22:00:00",
  "creado_fecha": "2025-01-01T10:00:00",
  "actualizado_fecha": "2025-03-12T22:00:00"
}
```

### Perfil

```json
{
  "per_id": 1,
  "usu_id": 1,
  "per_nombres": "Juan Carlos",
  "per_ape_paterno": "Rodríguez",
  "per_ape_materno": "López",
  "per_genero": "M",
  "per_empresa": "Tech Solutions S.A.",
  "ppf_id": 2,
  "per_idioma": "es",
  "per_tema": "claro",
  "per_titulo": "Desarrollador Full Stack",
  "per_descripcion": "Descripción del perfil...",
  "per_remoto": 1,
  "per_disponibilidad": 1,
  "ciu_id": 5
}
```

### PerfilProfesional

```json
{
  "ppf_id": 2,
  "ppf_nombre": "Desarrollador Backend",
  "ppf_descripcion": "Especialista en servidor y APIs",
  "ppf_estado": 1
}
```

### Proyecto

```json
{
  "pro_id": 10,
  "usu_id": 1,
  "pro_titulo": "App de Delivery",
  "pro_descripcion": "Descripción del proyecto...",
  "pro_ciudad_id": 1,
  "pro_remoto": true,
  "pro_estado": 1,
  "pro_fecha_inicio": "2025-03-12T22:00:00",
  "pro_fecha_cierre": null
}
```

### ProyectoPostulacion (estados)

| Valor | Estado |
|-------|--------|
| `1` | Pendiente |
| `2` | Aceptada |
| `3` | Rechazada |

---

## Guía para el equipo frontend

### Cómo usar la colección Postman

1. **Importar:** Abre Postman → Import → selecciona `docs/postman_collection.json`
2. **Crear environment:** Ve a Environments → New → crea uno con:
   - `base_url` = `http://localhost/enxu_back/public`
   - `token` = (dejar vacío, se llena luego del login)
3. **Seleccionar el environment** en el dropdown de Postman antes de hacer requests.

### Cómo configurar el token

1. Ejecuta `POST /auth/login` con tus credenciales de prueba.
2. Copia el valor de `data.token` del response.
3. En tu environment, pega el token en la variable `token`.
4. Todos los endpoints con `Authorization: Bearer {{token}}` lo usarán automáticamente.

> **Tip:** Puedes agregar un script "Tests" en el request de login para que el token se guarde automáticamente:
> ```javascript
> const data = pm.response.json();
> if (data.success && data.data?.token) {
>     pm.environment.set("token", data.data.token);
> }
> ```

### Flujo de autenticación

```
1. Registro (si usuario nuevo):
   POST /auth/registrar → obtienes token

2. Login (usuario existente):
   POST /auth/login → obtienes token

3. Guardar token en variable {{token}}

4. Usar token en header de todas las peticiones protegidas:
   Authorization: Bearer {{token}}

5. Verificar sesión activa (opcional, al abrir la app):
   GET /auth/verificar-sesion

6. Al cerrar sesión:
   POST /auth/logout
```

### Flujo de recuperación de contraseña

```
1. POST /password/solicitar → envía email con token
2. GET /password/validar?token={token} → confirmar que el token es válido
3. POST /password/restablecer → {token, password, confirmacion}
```

### Flujo de creación y postulación a proyectos

```
1. Completar perfil (obligatorio antes de postularse):
   POST /perfil → datos personales
   PUT /perfil/habilidades → agregar habilidades

2. Crear proyecto (si el usuario es creador):
   POST /proyectos → con perfiles y habilidades requeridas

3. Descubrir proyectos compatibles:
   GET /proyectos/compatibles

4. Postularse a un proyecto:
   POST /proyectos/postular → {proyecto_id, hab_id}

5. El propietario gestiona postulaciones:
   GET /proyectos/postulaciones
   POST /proyectos/postulaciones/gestionar → {postulacion_id, accion: "aceptar"|"rechazar"}

6. Al aceptar → se crea conversación de chat automáticamente
```

### Flujo del chat

```
1. Listar conversaciones activas:
   GET /chat/conversaciones

2. Ver mensajes de una conversación:
   GET /chat/mensajes?conversacion_id={id}&limite=50

3. Enviar texto:
   POST /chat/mensajes/texto → {conversacion_id, contenido}

4. Enviar imagen:
   POST /chat/mensajes/imagen → multipart: conversacion_id + archivo
```

### Ejemplos de llamadas comunes con fetch (JavaScript)

#### Login

```javascript
const response = await fetch('http://localhost/enxu_back/public/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: 'usuario@ejemplo.com',
    password: 'MiPassword123!',
    device_uuid: '550e8400-e29b-41d4-a716-446655440000'
  })
});
const data = await response.json();
const token = data.data.token;
```

#### Petición autenticada

```javascript
const response = await fetch('http://localhost/enxu_back/public/perfil/resumen', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});
const data = await response.json();
```

#### Enviar imagen al chat

```javascript
const formData = new FormData();
formData.append('conversacion_id', '8');
formData.append('archivo', fileInput.files[0]);

const response = await fetch('http://localhost/enxu_back/public/chat/mensajes/imagen', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: formData
  // ⚠️ No incluir Content-Type al usar FormData, el browser lo pone automáticamente
});
```

### Notas importantes para el equipo frontend

> **Soft-delete en proyectos:** `DELETE /proyectos/eliminar` no borra el registro, cambia `pro_estado` a `0`. Los proyectos con estado `0` no aparecen en listados ni en compatibles.

> **Conversaciones automáticas:** Cuando se acepta una postulación, la conversación de chat se crea automáticamente. El frontend puede obtener el `conversacion_id` desde el response de `gestionar`.

> **Imagen en chat:** El campo del archivo en multipart **debe llamarse exactamente** `archivo`. No incluyas el header `Content-Type` manualmente al usar `FormData`.

> **device_uuid obligatorio:** En todos los endpoints de autenticación se requiere un `device_uuid`. Genera un UUID v4 único por instalación de la app y guárdalo en el almacenamiento local del dispositivo.

> **Token expiración:** Los tokens JWT tienen expiración configurada en `JWT_EXPIRE` del servidor (default: 3600 segundos). Al recibir 401, redirige al usuario al login.

---

*Documentación generada automáticamente desde el código fuente de `enxu_back`.*  
*Última actualización: Marzo 2025*
