# 🧪 Guía de Testing — Integración MercadoLibre

> **Proyecto:** Carfi CRM  
> **Última actualización:** 2026-02-19  
> **Ubicación del código:** `/var/www/html/carfi-crm-back`

---

## 📑 Índice

1. [Prerrequisitos](#1-prerrequisitos)
2. [Comandos Artisan de MercadoLibre](#2-comandos-artisan-de-mercadolibre)
   - 2.1 [Setup Automático Completo](#21-setup-automático-completo-recomendado)
   - 2.2 [Obtener Token de Desarrollador](#22-obtener-token-de-desarrollador)
   - 2.3 [Crear Usuario de Prueba](#23-crear-usuario-de-prueba-manual)
   - 2.4 [Obtener Token del Usuario de Prueba](#24-obtener-token-del-usuario-de-prueba)
   - 2.5 [Crear Propiedad de Test en ML](#25-crear-propiedad-de-test-en-mercadolibre)
   - 2.6 [Simular Pregunta de ML](#26-simular-pregunta-de-mercadolibre)
   - 2.7 [Sincronizar Preguntas](#27-sincronizar-preguntas-desde-mercadolibre)
   - 2.8 [Sincronizar Propiedades](#28-sincronizar-propiedades-bidireccional)
3. [Endpoints API REST](#3-endpoints-api-rest)
4. [Tests Automatizados (PHPUnit)](#4-tests-automatizados-phpunit)
5. [Flujo Completo de Testing (Paso a Paso)](#5-flujo-completo-de-testing-paso-a-paso)
6. [Troubleshooting](#6-troubleshooting)

---

## 1. Prerrequisitos

### Configuración del `.env`

Asegurate de tener las siguientes variables configuradas en el archivo `.env`:

```env
MERCADOLIBRE_CLIENT_ID=tu_client_id
MERCADOLIBRE_CLIENT_SECRET=tu_client_secret
MERCADOLIBRE_REDIRECT_URI=https://tu-dominio.com/
MERCADOLIBRE_ACCESS_TOKEN=      # Se obtiene con los comandos de abajo
```

> 💡 **¿De dónde saco el Client ID y Secret?**  
> Desde la consola de desarrolladores de MercadoLibre: [https://developers.mercadolibre.com.ar/devcenter](https://developers.mercadolibre.com.ar/devcenter)

### Limpiar Caché de Config

Después de modificar el `.env`, siempre ejecutar:

```bash
php artisan config:clear
```

---

## 2. Comandos Artisan de MercadoLibre

### 2.1 Setup Automático Completo (Recomendado)

> 🏆 **Este es el comando recomendado para empezar.** Automatiza todo el flujo: obtener token de desarrollador, crear usuario de prueba y obtener su token.

```bash
php artisan mercadolibre:auto-setup-test
```

**Flujo interactivo:**

| Paso | Acción | Descripción |
|------|--------|-------------|
| **1** | Token de Desarrollador | El sistema genera una URL OAuth. Autorizá con tu cuenta **REAL** de desarrollador y pegá el código. |
| **2** | Crear Usuario de Prueba | Se crea automáticamente un usuario test en `MLA` (Argentina). **Guardar Nickname y Password.** |
| **3** | Token del Usuario de Prueba | Genera otra URL OAuth. Abrí en **INCÓGNITO**, logueá con el usuario de prueba y pegá el código. |

**Salida exitosa:**

```
✅ Developer Token Obtained Successfully.
✅ Test User Created Successfully!
+----+-----------+----------+---------+
| ID | Nickname  | Password | Site ID |
+----+-----------+----------+---------+
| ...| TETE...   | qatest...| MLA     |
+----+-----------+----------+---------+
⚠️  PLEASE SAVE THESE CREDENTIALS NOW.
🎉 FULL SUCCESS! You are done.
```

---

### 2.2 Obtener Token de Desarrollador

> Obtiene un Access Token para tu cuenta **principal** (real) de MercadoLibre usando PKCE.

```bash
php artisan mercadolibre:get-token
```

**Pasos:**

1. El comando genera una URL de autorización.
2. Abrir la URL en el navegador → Iniciar sesión con tu cuenta real de ML.
3. Serás redirigido a tu `REDIRECT_URI`. Copiar la URL completa o el parámetro `code`.
4. Pegar el código en la terminal.

**Resultado:** Access Token, Refresh Token, User ID.

> ⏳ **El token expira en ~6 horas.** Usar el Refresh Token para renovarlo.

---

### 2.3 Crear Usuario de Prueba (Manual)

> Crea un usuario de prueba (sandbox) en MercadoLibre. Requiere un Access Token válido de tu cuenta de desarrollador.

```bash
php artisan mercadolibre:create-test-user
```

**Pasos:**

1. El comando te pide un Access Token (del desarrollador, obtenido con 2.2).
2. Se crea el usuario test automáticamente en el site `MLA`.

**Salida:**

```
+----------+-----------+-----------+---------+--------------+
| Key      | Value                                          |
+----------+-----------+-----------+---------+--------------+
| ID       | 1234567890                                     |
| Nickname | TETE1234567                                    |
| Password | qatest1234                                     |
| Site ID  | MLA                                            |
+----------+-----------+-----------+---------+--------------+
⚠️ IMPORTANT: Save the password now! It will not be shown again.
```

---

### 2.4 Obtener Token del Usuario de Prueba

> Obtiene un Access Token **para el usuario de prueba** usando el flujo PKCE. Necesario para publicar, consultar ítems, etc. con la cuenta de test.

```bash
php artisan mercadolibre:get-test-user-token
```

**Pasos:**

1. Abrir la URL generada en una ventana de **INCÓGNITO** (para no cerrar sesión de tu cuenta real).
2. Iniciar sesión con las credenciales del usuario de prueba (Nickname + Password del paso 2.3).
3. Autorizar la aplicación.
4. Copiar el código de redirección y pegarlo en la terminal.

**Resultado:** Access Token, Refresh Token, Expires In, User ID, Scope.

---

### 2.5 Crear Propiedad de Test en MercadoLibre

> Crea una propiedad inmobiliaria de prueba directamente en MercadoLibre y la guarda en la base de datos local.

```bash
# Opción 1: Pasar token como argumento
php artisan mercadolibre:create-test-property TOKEN_DEL_USUARIO_TEST

# Opción 2: Interactivo (te pide el token)
php artisan mercadolibre:create-test-property
```

**Qué hace:**

1. Resuelve automáticamente la categoría hoja (leaf) desde `MLA1459` (Inmuebles).
2. Crea un ítem con datos simulados (título aleatorio, precio USD 100.000, ubicación en Palermo).
3. Guarda la propiedad en la tabla `propiedades` con `mercadolibre_id`, `mercadolibre_status` y `mercadolibre_permalink`.

**Salida exitosa:**

```
✅ Property Created Successfully!
+------+---------+-------------------------------------------+----------+
| Key  | Value                                                        |
+------+---------+-------------------------------------------+----------+
| ID   | MLA1234567890                                                |
| Title| Item de Test Integracion Inmobiliaria 5678                   |
| Status| payment_required                                            |
+------+---------+-------------------------------------------+----------+
Note: Status is "payment_required". This is EXPECTED for test users without a paid pack.
✅ Saved to local 'propiedades' table (ID: 42)
```

> ⚠️ **El status `payment_required` es NORMAL** para cuentas de test. El ítem fue creado correctamente y se puede usar para testing.

---

### 2.6 Simular Pregunta de MercadoLibre

> 🎯 **Comando clave para testing.** Simula una pregunta entrante de MercadoLibre sin necesidad de un listing real. Testea toda la lógica del CRM: creación de contacto, consulta y oportunidad.

```bash
php artisan ml:simulate-question {item_id} {text} [--from_id=ID]
```

**Parámetros:**

| Parámetro | Requerido | Descripción | Ejemplo |
|-----------|-----------|-------------|---------|
| `item_id` | ✅ Sí | ID del ítem de ML (debe existir en `propiedades.mercadolibre_id`) | `MLA1234567890` |
| `text` | ✅ Sí | Texto de la pregunta | `"¿Tiene cochera?"` |
| `--from_id` | ❌ No | ID del usuario que pregunta (default: `123456789`) | `--from_id=987654321` |

**Ejemplos:**

```bash
# Pregunta básica
php artisan ml:simulate-question MLA1234567890 "¿Tiene cochera?"

# Pregunta desde un usuario específico
php artisan ml:simulate-question MLA1234567890 "¿Cuántos ambientes tiene?" --from_id=987654321

# Múltiples preguntas del mismo usuario (simula conversación)
php artisan ml:simulate-question MLA1234567890 "¿Aceptan mascotas?"
php artisan ml:simulate-question MLA1234567890 "¿Cuánto es la expensa?"
```

**Qué se genera en la base de datos:**

| Tabla | Registro |
|-------|----------|
| `consultas_mercadolibre` | La pregunta con `ml_id` = `SIM-{timestamp}` |
| `contactos` | Se busca o crea un contacto con `ml_user_id` |
| `oportunidades` | Se crea o actualiza una oportunidad vinculada a la propiedad y el contacto |

**Salida:**

```
Simulating question for item MLA1234567890 from user 123456789
Text: ¿Tiene cochera?
Property with ML ID MLA1234567890 not found. Storing question without link.  # ← Solo si el item_id no existe en DB
Opportunity updated/created for Question SIM-1708300000
Simulation completed. Check database.
```

---

### 2.7 Sincronizar Preguntas desde MercadoLibre

> Descarga las preguntas reales de la API de MercadoLibre y las importa al CRM.

```bash
php artisan ml:sync-questions
```

**Requiere:** `MERCADOLIBRE_ACCESS_TOKEN` configurado en `.env`.

**Qué hace:**

1. Consulta `GET /my/received_questions/search` (últimas 50).
2. Para cada pregunta nueva:
   - Crea registro en `consultas_mercadolibre`.
   - Busca/crea el contacto por `ml_user_id`.
   - Si hay propiedad vinculada, crea/actualiza la oportunidad.

---

### 2.8 Sincronizar Propiedades (Bidireccional)

> Sincroniza propiedades entre MercadoLibre y la base de datos local.

```bash
# Solo sincronizar propiedades existentes (Pull de ML → DB)
php artisan mercadolibre:sync

# Importar nuevas propiedades desde la cuenta de ML + sincronizar existentes
php artisan mercadolibre:sync --import
```

**Requiere:** `MERCADOLIBRE_ACCESS_TOKEN` configurado en `.env`.

**Qué hace:**

- **Sin `--import`:** Actualiza el `status` y `permalink` de propiedades locales que ya tienen `mercadolibre_id`.
- **Con `--import`:** Además, busca ítems activos en la cuenta de ML que no estén en el CRM y los importa (precio, dirección, ubicación, imágenes, barrios).

**Salida:**

```
Starting MercadoLibre Sync...
Found 3 linked properties to sync.
 3/3 [============================] 100%
+----+-----------------+------------------------------+----------+
| ID | ML ID           | Título                       | Status ML|
+----+-----------------+------------------------------+----------+
| 1  | MLA1234567890   | Depto 3 amb Palermo          | active   |
| 2  | MLA0987654321   | Casa en Belgrano             | paused   |
+----+-----------------+------------------------------+----------+
Sync completed.
```

---

## 3. Endpoints API REST

> Todas las rutas de MercadoLibre requieren autenticación con **Sanctum** (Bearer Token del CRM).

### 3.1 Publicar Propiedad en MercadoLibre

```
POST /api/propiedades/{id}/mercadolibre/publish
```

| Campo | Tipo | Requerido | Descripción |
|-------|------|-----------|-------------|
| `access_token` | string | No | Token de ML. Si no se envía, usa el de `.env`. |
| `category_id` | string | ✅ Sí | Categoría de ML (ej: `MLA1459`). Se auto-resuelve a leaf. |
| `listing_type_id` | string | No | Tipo de listing (default: `gold_premium`). Opciones: `gold_premium`, `gold_pro`, `gold`, `silver`, `bronze`, `free`. |
| `attributes` | object | No | Atributos ML en formato `{ML_ID: valor}`. |

**Ejemplo con cURL:**

```bash
curl -X POST http://localhost/api/propiedades/1/mercadolibre/publish \
  -H "Authorization: Bearer TU_TOKEN_CRM" \
  -H "Content-Type: application/json" \
  -d '{
    "access_token": "APP_USR-xxx",
    "category_id": "MLA1459",
    "listing_type_id": "free",
    "attributes": {
      "COVERED_AREA": "60 m2",
      "ROOMS": "3"
    }
  }'
```

**Respuestas posibles:**

| Status | Significado |
|--------|-------------|
| `200` | Publicación exitosa |
| `400` | Error de validación de ML |
| `402` | Publicado pero requiere pago (esperado en cuentas test) |
| `422` | Falta `category_id` |
| `500` | Token no configurado |

---

### 3.2 Obtener Subcategorías

```
GET /api/mercadolibre/categories/{categoryId}
```

**Ejemplo:**

```bash
curl http://localhost/api/mercadolibre/categories/MLA1459 \
  -H "Authorization: Bearer TU_TOKEN_CRM"
```

---

### 3.3 Obtener Atributos Requeridos de Categoría

```
GET /api/mercadolibre/categories/{categoryId}/required-attributes
```

**Ejemplo:**

```bash
curl http://localhost/api/mercadolibre/categories/MLA1459/required-attributes \
  -H "Authorization: Bearer TU_TOKEN_CRM"
```

**Respuesta:**

```json
{
  "category_id": "MLA401684",
  "required_attributes": [
    {
      "ml_id": "COVERED_AREA",
      "ml_name": "Superficie cubierta",
      "value_type": "number_unit",
      "crm_name": "Metros Cubiertos"
    },
    {
      "ml_id": "ROOMS",
      "ml_name": "Ambientes",
      "value_type": "number",
      "crm_name": "Ambientes"
    }
  ]
}
```

---

### 3.4 Obtener Tipos de Listing Disponibles

```
GET /api/mercadolibre/categories/{categoryId}/available-listing-types
```

**Ejemplo:**

```bash
curl http://localhost/api/mercadolibre/categories/MLA1459/available-listing-types \
  -H "Authorization: Bearer TU_TOKEN_CRM"
```

---

### 3.5 Obtener Detalle de Ítem de ML

```
GET /api/mercadolibre/items/{mlItemId}
```

**Ejemplo:**

```bash
curl http://localhost/api/mercadolibre/items/MLA1234567890 \
  -H "Authorization: Bearer TU_TOKEN_CRM"
```

---

## 4. Tests Automatizados (PHPUnit)

### Ejecutar todos los tests de MercadoLibre

```bash
php artisan test --filter=MercadoLibreTest
```

### Ejecutar un test específico

```bash
php artisan test --filter=test_publish_successful_with_provided_token
php artisan test --filter=test_publish_fails_validation_without_category
php artisan test --filter=test_publish_successful_with_config_token
php artisan test --filter=test_publish_fails_without_token_configured
php artisan test --filter=test_publish_handles_api_failure
```

### Detalle de los Tests

| Test | Qué verifica |
|------|-------------|
| `test_publish_fails_validation_without_category` | Que se retorne `422` si no se envía `category_id`. |
| `test_publish_successful_with_provided_token` | Publicación exitosa enviando `access_token` en el request. Verifica que la DB se actualice con `mercadolibre_id`, `status` y `permalink`. |
| `test_publish_successful_with_config_token` | Publicación exitosa usando el token de la config (`.env`). Verifica que el header `Authorization: Bearer CONFIG_TOKEN` se envíe correctamente. |
| `test_publish_fails_without_token_configured` | Que se retorne `400` con mensaje descriptivo si no hay token disponible. |
| `test_publish_handles_api_failure` | Que se retorne `400` cuando la API de ML devuelve error de validación y que **no** se persistan datos en la DB. |

> 💡 Los tests usan `Http::fake()` para simular respuestas de la API de MercadoLibre sin hacer requests reales.

---

## 5. Flujo Completo de Testing (Paso a Paso)

### Escenario 1: Testing completo con sandbox de ML

```bash
# 1. Configurar credenciales en .env
# MERCADOLIBRE_CLIENT_ID=...
# MERCADOLIBRE_CLIENT_SECRET=...
# MERCADOLIBRE_REDIRECT_URI=...

# 2. Limpiar caché
php artisan config:clear

# 3. Setup completo (crea usuario test + obtiene tokens)
php artisan mercadolibre:auto-setup-test
# → Guardar: Nickname, Password, Access Token, User ID

# 4. Crear una propiedad de prueba en ML
php artisan mercadolibre:create-test-property TOKEN_OBTENIDO_PASO_3
# → Guardar: MLA ID del ítem creado

# 5. Simular preguntas sobre esa propiedad
php artisan ml:simulate-question MLA_ID_PASO_4 "¿Está disponible?"
php artisan ml:simulate-question MLA_ID_PASO_4 "¿Acepta permuta?" --from_id=999888777

# 6. Verificar en la base de datos
php artisan tinker
# >>> \App\Models\ConsultaMercadoLibre::latest()->take(5)->get(['ml_id','item_id','text','status']);
# >>> \App\Models\Oportunidad::latest()->take(5)->get(['nombre','contacto_id','propiedad_id']);
# >>> \App\Models\Contacto::where('ml_user_id', '!=', null)->get(['nombre','ml_user_id']);
```

### Escenario 2: Testing sin sandbox (solo simulación local)

```bash
# 1. Crear una propiedad local con mercadolibre_id ficticio
php artisan tinker
# >>> \App\Models\Propiedad::first()->update(['mercadolibre_id' => 'MLA-TEST-001']);

# 2. Simular preguntas
php artisan ml:simulate-question MLA-TEST-001 "¿Cuánto sale?"
php artisan ml:simulate-question MLA-TEST-001 "¿Tiene balcón?" --from_id=111222333

# 3. Verificar resultados
php artisan tinker
# >>> \App\Models\ConsultaMercadoLibre::where('item_id','MLA-TEST-001')->get();
```

### Escenario 3: Tests unitarios automatizados

```bash
# Ejecutar toda la suite de tests
php artisan test

# Solo los tests de MercadoLibre
php artisan test --filter=MercadoLibreTest

# Con output detallado
php artisan test --filter=MercadoLibreTest -v
```

---

## 6. Troubleshooting

### ❌ Error: "Config [services.mercadolibre.client_id] not found"

```bash
php artisan config:clear
php artisan config:cache
```

Verificar que `config/services.php` tenga:

```php
'mercadolibre' => [
    'client_id' => env('MERCADOLIBRE_CLIENT_ID'),
    'client_secret' => env('MERCADOLIBRE_CLIENT_SECRET'),
    'redirect_uri' => env('MERCADOLIBRE_REDIRECT_URI'),
    'access_token' => env('MERCADOLIBRE_ACCESS_TOKEN'),
],
```

### ❌ Error: "No MercadoLibre access token provided or configured."

1. Verificar que `MERCADOLIBRE_ACCESS_TOKEN` esté en el `.env`.
2. Ejecutar `php artisan config:clear`.
3. Si el token expiró (~6 horas), obtener uno nuevo:

   ```bash
   php artisan mercadolibre:get-token
   # O para test user:
   php artisan mercadolibre:get-test-user-token
   ```

### ❌ Error: "Property with ML ID xxx not found" (en simulación)

La propiedad con ese `mercadolibre_id` no existe en la tabla `propiedades`. Opciones:

1. Crear la propiedad con el comando `mercadolibre:create-test-property`.
2. Asignar manualmente un `mercadolibre_id` a una propiedad existente:

   ```bash
   php artisan tinker
   >>> \App\Models\Propiedad::find(1)->update(['mercadolibre_id' => 'MLA-TEST-001']);
   ```

### ❌ Error: Token expirado

Los tokens de MercadoLibre expiran en **~6 horas**. Renovar:

```bash
php artisan mercadolibre:get-token            # Para cuenta real
php artisan mercadolibre:get-test-user-token   # Para cuenta test
```

### ❌ Status "payment_required" al crear propiedad

Esto es **comportamiento esperado** para cuentas de prueba sin pack de publicación pagado. El ítem fue creado correctamente y tiene un ID válido que se puede usar para testing.

---

## 📁 Archivos Relevantes

| Archivo | Descripción |
|---------|-------------|
| `app/Console/Commands/AutoSetupMercadoLibreTest.php` | Setup completo automatizado |
| `app/Console/Commands/GetMercadoLibreToken.php` | Obtener token de cuenta real |
| `app/Console/Commands/GetMercadoLibreTestUserToken.php` | Obtener token de cuenta test |
| `app/Console/Commands/CreateMercadoLibreTestUser.php` | Crear usuario de prueba |
| `app/Console/Commands/CreateMercadoLibreTestProperty.php` | Crear propiedad test en ML |
| `app/Console/Commands/SimulateMercadoLibreQuestion.php` | Simular pregunta de ML |
| `app/Console/Commands/SyncMercadoLibreQuestions.php` | Sincronizar preguntas reales |
| `app/Console/Commands/SyncMercadoLibreProperties.php` | Sincronizar propiedades bidireccional |
| `app/Services/MercadoLibreService.php` | Servicio principal de integración |
| `app/Http/Controllers/Api/V1/MercadoLibreController.php` | Controller de API REST |
| `tests/Feature/Api/V1/MercadoLibreTest.php` | Tests automatizados PHPUnit |
| `app/Models/ConsultaMercadoLibre.php` | Modelo de consultas/preguntas |
| `config/services.php` | Configuración de credenciales |

---

## 📋 Resumen Rápido de Comandos

```bash
# === SETUP ===
php artisan mercadolibre:auto-setup-test          # Setup completo (recomendado)
php artisan mercadolibre:get-token                 # Token cuenta real
php artisan mercadolibre:get-test-user-token        # Token cuenta test
php artisan mercadolibre:create-test-user           # Crear usuario test

# === TESTING ===
php artisan mercadolibre:create-test-property       # Crear propiedad en ML
php artisan ml:simulate-question ITEM TEXT           # Simular pregunta
php artisan ml:sync-questions                        # Sincronizar preguntas reales
php artisan mercadolibre:sync                        # Sync propiedades
php artisan mercadolibre:sync --import               # Sync + importar nuevas

# === TESTS AUTOMATIZADOS ===
php artisan test --filter=MercadoLibreTest           # Tests PHPUnit
php artisan test --filter=MercadoLibreTest -v         # Con detalle

# === UTILIDADES ===
php artisan config:clear                             # Limpiar caché config
```
