# API Partners y beneficios — integración frontend

Todas las rutas viven bajo el prefijo **`/api`** (Laravel por defecto). La URL base real depende del entorno, por ejemplo:

`https://tu-dominio.com/api`

---

## Autenticación (JWT)

Se usa **tymon/jwt-auth**. En cada request autenticado enviar:

```http
Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json
```

| Flujo | Login | Middleware | Uso |
|--------|--------|------------|-----|
| **Backoffice / admin** | `POST /api/login_admin` | `JwtAdminMiddleware` | ABM partners (`/api/admin/partner/...`). Requiere usuario con `role_id` distinto de `2` (según reglas actuales del middleware). |
| **App móvil** | `POST /api/login` | `JwtMiddleware` | Partners cercanos, registrar uso y listar usos (`/api/users/partners/...`). No debe ser usuario admin (`role_id === 1` está bloqueado en ese middleware). |

Si el token falta o es inválido, la respuesta suele ser **401** con cuerpo tipo `{ "error": "Token not valid" }` (no el envelope `status`/`data` del resto de endpoints).

---

## Formato general de respuesta (endpoints del controlador)

Éxito:

```json
{
  "status": true,
  "data": { ... }
}
```

Error de negocio o validación (HTTP **400** en la mayoría de los casos):

```json
{
  "status": false,
  "message": "Texto del primer error de validación u otra excepción"
}
```

---

## 1. App — Partners cercanos

Obtiene partners ordenados por distancia a un punto (`lat` / `lng`). Si **ningún** partner cae dentro del radio indicado, se devuelven **todos** los partners igualmente ordenados por distancia (el front puede usar `meta.showing_all_fallback` para mostrar un mensaje o UI distinta).

### Request

```http
GET /api/users/partners/nearby?lat={number}&lng={number}&radius_km={number}
Authorization: Bearer <token_app>
```

| Query | Obligatorio | Descripción |
|--------|-------------|-------------|
| `lat` | Sí | Latitud del usuario, entre -90 y 90. |
| `lng` | Sí | Longitud del usuario, entre -180 y 180. |
| `radius_km` | No | Radio en kilómetros. **Default: 50**. Mínimo 0.1, máximo 20000. |

### Respuesta 200 (ejemplo)

```json
{
  "status": true,
  "data": {
    "partners": [
      {
        "id": 1,
        "title": "Café Central",
        "description": "Descripción del partner",
        "image": "partners/1.jpg",
        "url_image": "https://api.ukraine.com.ar/public/storage/partners/1.jpg",
        "order": 1,
        "latitude": -34.603722,
        "longitude": -58.381592,
        "distance_km": 1.234,
        "beneficios": [
          {
            "id": 10,
            "title": "10% de descuento",
            "description": "Mostrando la app",
            "image": "partner_beneficios/10.jpg",
            "url_image": "https://api.ukraine.com.ar/public/storage/partner_beneficios/10.jpg",
            "order": 1
          }
        ]
      }
    ],
    "meta": {
      "radius_km": 50,
      "showing_all_fallback": false
    }
  }
}
```

- **`distance_km`**: distancia en kilómetros (Haversine), redondeada a 3 decimales.
- **`meta.showing_all_fallback`**: `true` cuando no había ningún partner dentro del radio y se listaron **todos** ordenados por distancia.
- **`beneficios`**: solo beneficios activos (no eliminados con soft delete).

### Errores

- **400**: `status: false` y `message` con el error de validación (por ejemplo lat/lng fuera de rango).

---

## 2. App — Registrar uso de un beneficio

Cuando el usuario elige un beneficio concreto de un partner (por ejemplo confirma en la app que lo va a usar), el front envía el **`partner_beneficio_id`** que ya recibió en `partners/nearby` dentro de `beneficios[].id`. Cada llamada crea un **registro de uso** (historial); no hay límite en el backend salvo que ustedes agreguen reglas de negocio después.

### Request

```http
POST /api/users/partners/beneficio-usos
Authorization: Bearer <token_app>
Content-Type: application/json
```

### Body (JSON)

| Campo | Obligatorio | Descripción |
|--------|-------------|-------------|
| `partner_beneficio_id` | Sí | Entero. Id del beneficio (`partner_beneficios.id`), el mismo que viene en `data.partners[].beneficios[].id` del endpoint cercano. |

### Ejemplo

```json
{
  "partner_beneficio_id": 10
}
```

### Respuesta 200

```json
{
  "status": true,
  "data": {
    "uso": {
      "id": 42,
      "created_at": "2026-05-08T15:30:00+00:00",
      "beneficio": {
        "id": 10,
        "title": "10% de descuento",
        "description": "Mostrando la app"
      },
      "partner": {
        "id": 1,
        "title": "Café Central",
        "latitude": -34.603722,
        "longitude": -58.381592
      }
    }
  }
}
```

### Errores

- **400**: Validación (campo faltante, id inexistente en tabla).
- **400**: `"Beneficio no disponible o el partner ya no está activo."` si el beneficio o el partner están dados de baja (soft delete) o el beneficio no cumple las condiciones del servidor.

---

## 3. App — Listar mis usos de beneficios

Devuelve el historial del usuario autenticado, del más reciente al más antiguo. Incluye datos del beneficio y del partner aunque hayan sido dados de baja después (para que el historial siga teniendo sentido).

### Request

```http
GET /api/users/partners/beneficio-usos
Authorization: Bearer <token_app>
```

### Respuesta 200 (ejemplo)

```json
{
  "status": true,
  "data": {
    "usos": [
      {
        "id": 42,
        "created_at": "2026-05-08T15:30:00+00:00",
        "beneficio": {
          "id": 10,
          "title": "10% de descuento",
          "description": "Mostrando la app"
        },
        "partner": {
          "id": 1,
          "title": "Café Central",
          "latitude": -34.603722,
          "longitude": -58.381592
        }
      }
    ]
  }
}
```

Si no hay usos, `usos` es `[]`.

---

## 4. Admin — Listar usos de beneficios (todos los usuarios)

```http
GET /api/admin/partner/beneficio-usos/all
Authorization: Bearer <token_admin>
```

| Query | Descripción |
|--------|-------------|
| `partner_id` | Opcional. Filtra usos de beneficios de ese partner. |
| `user_id` | Opcional. Filtra por usuario. |

### Respuesta 200 (ejemplo)

```json
{
  "status": true,
  "data": {
    "usos": [
      {
        "id": 42,
        "created_at": "2026-05-12T20:00:00+00:00",
        "user": {
          "id": 5,
          "name": "Juan Pérez",
          "email": "juan@example.com",
          "document_number": "12345678"
        },
        "beneficio": {
          "id": 10,
          "title": "10% de descuento",
          "description": "Mostrando la app",
          "image": "https://..."
        },
        "partner": {
          "id": 1,
          "title": "Café Central",
          "image": "https://..."
        }
      }
    ]
  }
}
```

Ordenado del más reciente al más antiguo. Incluye beneficios/partners dados de baja (para historial completo).

---

## 5. Público — Listar partners (sin autenticación)

Devuelve exactamente la misma información que el endpoint admin de listado, pero **no requiere token**.

```http
GET /api/partners
GET /api/partners?id={id}
```

| Query | Descripción |
|--------|-------------|
| `id` | Opcional. Filtra por id del partner. |

### Respuesta 200 (ejemplo)

```json
{
  "status": true,
  "data": {
    "partners": [
      {
        "id": 1,
        "title": "Café Central",
        "description": "...",
        "image": "partners/1.jpg",
        "url_image": "https://api.ukraine.com.ar/public/storage/partners/1.jpg",
        "order": 1,
        "latitude": -34.603722,
        "longitude": -58.381592,
        "created_at": "...",
        "updated_at": "...",
        "deleted_at": null,
        "beneficios": [
          {
            "id": 10,
            "partner_id": 1,
            "title": "10% de descuento",
            "description": "...",
            "image": "partner_beneficios/10.jpg",
            "url_image": "https://api.ukraine.com.ar/public/storage/partner_beneficios/10.jpg",
            "order": 1,
            "created_at": "...",
            "updated_at": "...",
            "deleted_at": null
          }
        ]
      }
    ]
  }
}
```

Listado ordenado por **`order`** ascendente (y `title` como desempate).

---

## 6. Admin — Listar partners

```http
GET /api/admin/partner
GET /api/admin/partner?id={id}
Authorization: Bearer <token_admin>
```

| Query | Descripción |
|--------|-------------|
| `id` | Opcional. Filtra por id del partner. |

### Respuesta 200 (ejemplo)

```json
{
  "status": true,
  "data": {
    "partners": [
      {
        "id": 1,
        "title": "Café Central",
        "description": "...",
        "image": "partners/1.jpg",
        "url_image": "https://api.ukraine.com.ar/public/storage/partners/1.jpg",
        "order": 1,
        "latitude": -34.603722,
        "longitude": -58.381592,
        "created_at": "...",
        "updated_at": "...",
        "deleted_at": null,
        "beneficios": [
          {
            "id": 10,
            "partner_id": 1,
            "title": "10% de descuento",
            "description": "...",
            "image": "partner_beneficios/10.jpg",
            "url_image": "https://api.ukraine.com.ar/public/storage/partner_beneficios/10.jpg",
            "order": 1,
            "created_at": "...",
            "updated_at": "...",
            "deleted_at": null
          }
        ]
      }
    ]
  }
}
```

Listado ordenado por **`order`** ascendente (y `title` como desempate). Los `beneficios` de cada partner también vienen ordenados por su `order`.

---

## 7. Admin — Detalle de un partner

```http
GET /api/admin/partner/{id}
Authorization: Bearer <token_admin>
```

### Respuesta 200

```json
{
  "status": true,
  "data": {
    "partner": { ...mismo shape que un ítem de la lista, con beneficios... }
  }
}
```

### Errores

- **400**: partner no encontrado (`message`: `"Partner no encontrado."`).

---

## 8. Admin — Crear partner (y beneficios en un solo paso)

```http
POST /api/admin/partner
Authorization: Bearer <token_admin>
Content-Type: multipart/form-data
```

> **Importante:** como las imágenes se suben como **archivo binario**, este endpoint usa `multipart/form-data` (no JSON). Los beneficios y sus imágenes se envían con notación de array: `beneficios[0][title]`, `beneficios[0][image]`, etc.

### Campos (form-data)

| Campo | Obligatorio | Reglas |
|--------|-------------|--------|
| `title` | Sí | string, máx. 255 |
| `description` | No | string o null |
| `image` | No | **archivo de imagen** (jpg, png, etc.). Se guarda en `storage/app/public/partners`. |
| `order` | No | entero. Default `0`. Define el orden de listado. |
| `latitude` | Sí | numérico, entre -90 y 90 |
| `longitude` | Sí | numérico, entre -180 y 180 |
| `beneficios[n][title]` | Sí (por beneficio) | string, máx. 255 |
| `beneficios[n][description]` | No | string o null |
| `beneficios[n][image]` | No | **archivo de imagen**. Se guarda en `storage/app/public/partner_beneficios`. |
| `beneficios[n][order]` | No | entero. Default `0`. |

### Ejemplo (form-data)

```
title: Café Central
description: Sucursal centro
order: 1
latitude: -34.603722
longitude: -58.381592
image: <archivo binario>
beneficios[0][title]: 10% de descuento
beneficios[0][description]: Válido de lun a vie
beneficios[0][order]: 1
beneficios[0][image]: <archivo binario>
beneficios[1][title]: Café gratis
beneficios[1][order]: 2
```

### Respuesta 200

```json
{
  "status": true,
  "data": {
    "partner": { ...incluye beneficios recién creados con image y order... }
  }
}
```

Sobre las imágenes en las respuestas (aplica a **todos** los GET: nearby, index, show, historial de usos):

- `image`: **path relativo** dentro del disco `public` (ej: `partners/12.jpg`). Útil si querés construir la URL vos mismo.
- `url_image`: **URL pública completa** lista para usar en el `<img src>` (ej: `https://api.ukraine.com.ar/public/storage/partners/12.jpg`). Es `null` si el partner/beneficio no tiene imagen.

---

## 9. Admin — Editar partner y beneficios

```http
POST /api/admin/partner/{id}
Authorization: Bearer <token_admin>
Content-Type: multipart/form-data
```

> Como el envío es `multipart/form-data` con archivos, se usa **method spoofing**: enviar por `POST` agregando el campo `_method: PUT` en el form-data (Laravel lo interpreta como `PUT`).

### Campos del partner

Todos opcionales salvo que envíes el campo: si mandás `title`, debe ser string no vacío; si mandás `latitude` / `longitude`, aplican los mismos rangos que en el alta.

| Campo | Descripción |
|--------|-------------|
| `_method` | `PUT` (obligatorio para el method spoofing) |
| `title` | `sometimes` + required si viene |
| `description` | nullable |
| `image` | nullable. Si se envía un **archivo nuevo**, reemplaza el anterior (borra el viejo del disco). Si se omite, se conserva. |
| `order` | nullable, entero |
| `latitude` | `sometimes` + required si viene |
| `longitude` | `sometimes` + required si viene |

### Beneficios (`beneficios[n][...]`)

Array opcional. Cada ítem:

| Campo en ítem | Descripción |
|----------------|-------------|
| `beneficios[n][id]` | Si **no** se envía (o es null): se **crea** un beneficio nuevo. Si se envía: se **actualiza** (debe pertenecer al partner; si no, error). |
| `beneficios[n][title]` | Requerido cuando se envía el array `beneficios`. |
| `beneficios[n][description]` | Opcional / null |
| `beneficios[n][image]` | Opcional. **Archivo nuevo** reemplaza el anterior; si se omite, se conserva la imagen existente. |
| `beneficios[n][order]` | Opcional, entero. En update se conserva el valor previo si se omite. |

### Eliminar beneficios

| Campo | Descripción |
|--------|-------------|
| `beneficios_eliminar_ids[]` | Array opcional de IDs de filas en `partner_beneficios`. Solo tiene efecto si el ID existe y pertenece al partner; se aplican **soft deletes**. |

### Ejemplo (form-data): editar partner, actualizar un beneficio, agregar otro y borrar uno

```
_method: PUT
title: Café Central — Av. Corrientes
order: 2
image: <archivo binario nuevo>
beneficios_eliminar_ids[0]: 12
beneficios[0][id]: 10
beneficios[0][title]: 15% de descuento
beneficios[0][description]: Actualizado
beneficios[0][order]: 1
beneficios[0][image]: <archivo binario nuevo>
beneficios[1][title]: Nuevo beneficio
beneficios[1][description]: Sin id = creación
beneficios[1][order]: 2
```

Orden en el servidor: primero `update` del partner (e imagen), luego las bajas en `beneficios_eliminar_ids`, luego el procesamiento de `beneficios`.

### Respuesta 200

```json
{
  "status": true,
  "data": {
    "partner": { ...con beneficios actualizados... }
  }
}
```

### Errores frecuentes

- **400**: `"Beneficio no pertenece a este partner."` si un `id` en `beneficios` no corresponde a ese partner.
- Validación `exists` en IDs inexistentes en la tabla.

---

## 10. Admin — Eliminar partner (baja lógica)

```http
DELETE /api/admin/partner/{id}
Authorization: Bearer <token_admin>
```

Elimina en forma lógica el partner y también sus beneficios (soft delete en cascada a nivel aplicación).

### Respuesta 200

```json
{
  "status": true
}
```

---

## Notas para el front

1. **Coordenadas**: usar el mismo sistema que el mapa (WGS84). `latitude` / `longitude` en admin son las del local del partner; en la app se envían `lat` / `lng` del usuario para `nearby`.
2. **Radio y fallback**: si `showing_all_fallback === true`, conviene un copy tipo “No hay partners muy cerca; mostramos todos ordenados por distancia”.
3. **Tipos**: los decimales pueden llegar como número JSON; no asumir enteros.
4. **Listas vacías**: si no hay partners en base, `data.partners` será `[]` y `showing_all_fallback` será `false`.
5. **Flujo beneficio**: con `GET .../nearby` el usuario ve partners y `beneficios[].id`; al confirmar, `POST .../beneficio-usos` con ese id; `GET .../beneficio-usos` alimenta pantalla de historial o “mis beneficios usados”.

---

## Resumen de rutas

| Método | Ruta | Auth |
|--------|------|------|
| GET | `/api/partners` | — Público — |
| GET | `/api/users/partners/nearby` | App JWT |
| POST | `/api/users/partners/beneficio-usos` | App JWT |
| GET | `/api/users/partners/beneficio-usos` | App JWT |
| GET | `/api/admin/partner/beneficio-usos/all` | Admin JWT |
| GET | `/api/admin/partner` | Admin JWT |
| GET | `/api/admin/partner/{id}` | Admin JWT |
| POST | `/api/admin/partner` | Admin JWT |
| PUT | `/api/admin/partner/{id}` | Admin JWT |
| DELETE | `/api/admin/partner/{id}` | Admin JWT |
