# BUG AUDIT (NO FIX APPLIED) — $influencerAcco vacío en AutoCanceledColabCompanyNotification

## A) Causa raíz + evidencia (líneas exactas)

**Causa raíz:** El handle del influencer se obtiene con **"primera red social"** de la colección en lugar de la **red social principal** (`pivot.main = true`), por lo que puede ser null, vacío o corresponder a otra red (no Instagram).

**Evidencia:**

| Archivo | Método | Líneas | Código actual |
|---------|--------|--------|----------------|
| `app/Services/NotificationService.php` | `sendAutoCanceledColabCompanyNotification` | **405** | `$mainSocial = $colab->influencer->socialMedia->first();` |
| `app/Services/NotificationService.php` | `sendAutoCanceledColabCompanyNotification` | **406** | `$rawInstagram = $mainSocial ? $mainSocial->pivot->account_name : null;` |
| `app/Services/NotificationService.php` | `sendAutoCanceledColabCompanyNotification` | **407-408** | Normalización + `$influencerAcco = $rawInstagram !== '' ? $rawInstagram : 'usuarioInstagram';` |
| `app/Services/NotificationService.php` | `sendAutoCanceledColabCompanyNotification` | **418** | Body: `"La solicitud de colaboración con $influencerFullName (@$influencerAcco) se ha cancelado automáticamente por falta de respuesta."` |

**Patrón correcto en el mismo archivo** (ejemplo): líneas 280-281 en `sendNewColabCompanyNotification`:

```php
$mainSocialMedia = $influencerModel->socialMedia()->wherePivot('main', true)->first();
$rawInstagram = $mainSocialMedia ? $mainSocialMedia->pivot->account_name : null;
```

---

## B) Métodos potencialmente afectados (lista archivo:método:línea)

Todos en **`app/Services/NotificationService.php`**. Solo se listan los que **NO** usan `wherePivot('main', true)` y construyen mensajes con @handle (o variable equivalente).

| # | Método | Línea(s) obtención handle | ¿Usa wherePivot('main', true)? | ¿Body con @handle? |
|---|--------|---------------------------|--------------------------------|---------------------|
| 1 | **sendAutoCanceledColabCompanyNotification** | **405-406** (`socialMedia->first()`) | **No** | Sí (418: `@$influencerAcco`) |
| 2 | **send1HourCalendarReminderCompanyNotification** | **1503-1504** (`socialMedia()->first()`) | **No** | Sí (1509: `@{$instagramUsername}`) |
| 3 | **sendNomadeCanceledPendingClientCompanyNotification** | **2645** (`socialMedia()->first()?->pivot?->account_name`) | **No** | Sí (2650: `@$instagramUsername`) |
| 4 | sendAutoCanceledModificationCompanyNotification | 1192 (`socialMedia()->first()` + fallback `instagram_username`) | No | Sí (body con handle en 1197) — tiene fallback en modelo |

**Resumen:** Métodos **1, 2 y 3** pueden mostrar handle incorrecto o vacío por no filtrar por `main`. El método **4** usa el mismo patrón sin `wherePivot` pero tiene fallback `$colab->influencer->instagram_username`; si ese atributo no existe en el modelo Influencer, también podría fallar.

**Lista compacta (archivo:método:línea):**

- `NotificationService.php:sendAutoCanceledColabCompanyNotification:405`
- `NotificationService.php:send1HourCalendarReminderCompanyNotification:1503`
- `NotificationService.php:sendNomadeCanceledPendingClientCompanyNotification:2645`
- `NotificationService.php:sendAutoCanceledModificationCompanyNotification:1192` (tiene fallback alternativo)

---

## C) Diff propuesto (NO aplicado)

**Objetivo:** Sustituir **solo** líneas 405-408 en `sendAutoCanceledColabCompanyNotification`. Usar red principal y fallback: si existe `$colab->influencer->instagram_username` usarlo; si no, no mostrar @handle o usar `'@influencer'`. Evitar `'usuarioInstagram'` si hay alternativa.

**Nota:** En el modelo `Influencer` no aparece un atributo `instagram_username` en la búsqueda; si no existe, el fallback recomendado es el nombre de usuario (`getUser()->name`) o literal `'influencer'` sin @ o `'@influencer'`.

**Sustituir (líneas 405-408):**

```php
        $mainSocial = $colab->influencer->socialMedia->first();
        $rawInstagram = $mainSocial ? $mainSocial->pivot->account_name : null;
        $rawInstagram = ($rawInstagram !== null && $rawInstagram !== '') ? ltrim(trim((string) $rawInstagram), '@') : '';
        $influencerAcco = $rawInstagram !== '' ? $rawInstagram : 'usuarioInstagram';
```

**Por:**

```php
        $mainSocialMedia = $colab->influencer->socialMedia()->wherePivot('main', true)->first();
        $rawInstagram = $mainSocialMedia ? $mainSocialMedia->pivot->account_name : null;
        $rawInstagram = ($rawInstagram !== null && $rawInstagram !== '') ? ltrim(trim((string) $rawInstagram), '@') : '';
        $influencerAcco = $rawInstagram !== ''
            ? $rawInstagram
            : (property_exists($colab->influencer, 'instagram_username') && $colab->influencer->instagram_username !== null && $colab->influencer->instagram_username !== ''
                ? ltrim(trim((string) $colab->influencer->instagram_username), '@')
                : ($colab->influencer->getUser()->name ?? 'influencer'));
```

**Alternativa más simple** (sin comprobar `instagram_username` si no existe en modelo): fallback solo a nombre o `'influencer'`:

```php
        $mainSocialMedia = $colab->influencer->socialMedia()->wherePivot('main', true)->first();
        $rawInstagram = $mainSocialMedia ? $mainSocialMedia->pivot->account_name : null;
        $rawInstagram = ($rawInstagram !== null && $rawInstagram !== '') ? ltrim(trim((string) $rawInstagram), '@') : '';
        $influencerAcco = $rawInstagram !== '' ? $rawInstagram : ($colab->influencer->getUser()->name ?? 'influencer');
```

**Diff sugerido (versión simple, solo líneas 405-408):**

```diff
--- a/app/Services/NotificationService.php
+++ b/app/Services/NotificationService.php
@@ -402,10 +402,10 @@ class NotificationService extends ResourceService implements CollabableConstants
     {
         $FCMToken   = $this->getFCMToken($colab->offer->company->getUser()->uid, $throw);

-        $mainSocial = $colab->influencer->socialMedia->first();
-        $rawInstagram = $mainSocial ? $mainSocial->pivot->account_name : null;
+        $mainSocialMedia = $colab->influencer->socialMedia()->wherePivot('main', true)->first();
+        $rawInstagram = $mainSocialMedia ? $mainSocialMedia->pivot->account_name : null;
         $rawInstagram = ($rawInstagram !== null && $rawInstagram !== '') ? ltrim(trim((string) $rawInstagram), '@') : '';
-        $influencerAcco = $rawInstagram !== '' ? $rawInstagram : 'usuarioInstagram';
+        $influencerAcco = $rawInstagram !== '' ? $rawInstagram : ($colab->influencer->getUser()->name ?? 'influencer');
     }
```

**Recomendación:** Aplicar la versión simple. Si más adelante se confirma que `Influencer` tiene `instagram_username`, se puede añadir ese fallback intermedio antes de `getUser()->name`.

---

## D) Checklist de validación manual

### 1) Preparar datos en staging/local

- [ ] Crear o usar un **influencer** con **varias redes** en `influencer_social_media` (pivot):
  - Al menos 2 filas: una con `main = 0` (o false) y otra con `main = 1` (true).
  - La fila con `main = 1` debe tener `account_name` con el handle real (ej. `mi_handle_ig`).
  - Opcional: la fila con `main = 0` primero en orden de inserción o con id menor, para que `->first()` sin filtro devuelva esa y no la principal.
- [ ] Tener una **colab** en estado **PENDING_COMPANY** asociada a ese influencer y a una company con usuario con FCM token válido (para recibir push).

### 2) Disparar cancelación automática

- [ ] Ejecutar el flujo que cancela automáticamente la collab (PENDING_COMPANY → CANCELLED):
  - Opción A: comando tipo `nomade:cancel-expired-collaborations` si aplica para esa collab (por tiempo).
  - Opción B: forzar en código/BD la transición a CANCELLED con `user` null para que caiga en la rama `else` de `handleCancelledState` (líneas 612-618) y se llame `sendAutoCanceledColabCompanyNotification($colab)`.
- [ ] Confirmar que se ejecuta `sendAutoCanceledColabCompanyNotification` (log o breakpoint en línea 401).

### 3) Comprobar payload / body de la push

- [ ] **Antes del fix:** Verificar que el body recibido muestra handle vacío, genérico (`usuarioInstagram`) o incorrecto (ej. handle de otra red).
- [ ] **Después del fix:** Verificar que el body contiene el handle de la red principal, ej. `(@mi_handle_ig)`.
- [ ] Formas de comprobación:
  - **Log:** Añadir temporalmente `Log::info('AutoCanceledColabCompany body', ['body' => $body]);` tras construir `$body` (línea 418) y revisar el log.
  - **MessagingService:** Si el servicio de FCM/push registra el payload enviado, revisar ahí el body.
  - **Cliente:** En dispositivo con la app, recibir la notificación y comprobar el texto en pantalla.

### 4) Casos a probar (resumen)

| Caso | Descripción | Resultado esperado tras fix |
|------|-------------|-----------------------------|
| Influencer con main=true y account_name rellenado | Red principal con handle | Body con `(@handle_real)`. |
| Influencer con main=true pero account_name vacío | Fallback | Body con nombre de usuario o `(influencer)`. |
| Influencer sin redes sociales | `first()` null | Body con fallback, sin @ vacío. |

---

**Resumen:** La causa es el uso de `socialMedia->first()` sin `wherePivot('main', true)` en las líneas 405-406. Los métodos afectados por el mismo patrón son sendAutoCanceledColabCompanyNotification (405), send1HourCalendarReminderCompanyNotification (1503) y sendNomadeCanceledPendingClientCompanyNotification (2645). El diff propuesto reemplaza solo esas 4 líneas por el patrón con `wherePivot('main', true)` y fallback a `getUser()->name` o `'influencer'`. No se ha aplicado ningún cambio en el código.
