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

## 1) Dónde se construye el body para company

| Dato | Valor |
|------|--------|
| **Archivo** | `app/Services/NotificationService.php` |
| **Método** | `sendAutoCanceledColabCompanyNotification` |
| **Líneas** | **417** (title), **418** (body) |

Fragmento exacto (líneas 401-418):

```php
public function sendAutoCanceledColabCompanyNotification(Colab $colab, bool $throw = false): void
{
    $FCMToken   = $this->getFCMToken($colab->offer->company->getUser()->uid, $throw);

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

    // Construir nombre completo del influencer con surnames
    $influencerFullName = trim(($colab->influencer->name ?? '') . ' ' . ($colab->influencer->surnames ?? ''));
    if ($influencerFullName === '') {
        $influencerFullName = $colab->influencer->getUser()->name ?? 'Influencer';
    }

    $offerName      = $colab->offer->company->company;
    $title          = '🔴 Solicitud cancelada';                                 // 417
    $body           = "La solicitud de colaboración con $influencerFullName (@$influencerAcco) se ha cancelado automáticamente por falta de respuesta.";  // 418
    // ...
}
```

---

## 2) Por qué $influencerAcco queda vacío (o con fallback)

**Causa:** En la **línea 405** se usa la red social “cualquiera” (la primera de la colección) en lugar de la **principal** (`main = true`).

- **Línea 405:**  
  `$mainSocial = $colab->influencer->socialMedia->first();`  
  - `socialMedia` como **propiedad** devuelve la colección de la relación (orden no garantizado como “main primero”).  
  - `->first()` toma el **primer** registro de esa colección, **sin** filtrar por `pivot.main = true`.

- **Evidencia en el mismo archivo:** En `sendNewColabCompanyNotification` (líneas 279-281) el handle se obtiene así:
  ```php
  $mainSocialMedia = $influencerModel->socialMedia()->wherePivot('main', true)->first();
  $rawInstagram = $mainSocialMedia ? $mainSocialMedia->pivot->account_name : null;
  ```
  Es decir, se usa la relación como **query** y se filtra por `wherePivot('main', true)`.

- **Consecuencia:**  
  - Si el primer registro no es el “main” o no tiene `account_name`, `$rawInstagram` queda `null` o `''`.  
  - Entonces `$influencerAcco` pasa a ser `'usuarioInstagram'` (línea 408).  
  - En casos donde `$mainSocial` sea null (relación no cargada o sin filas) o `pivot->account_name` sea null, el body muestra el fallback; si en algún flujo la variable no se interpola correctamente, podría verse “vacío” el handle.

**Conclusión:** El fallo no es un typo de nombre de variable (`influencerAcco` está definida y usada correctamente), sino el **origen del dato**: se usa “primera red social” en lugar de “red social principal”, por lo que el handle mostrado suele ser el genérico o vacío en lugar del Instagram real.

---

## 3) Traza de datos en el método

| Variable | Origen | Evidencia |
|----------|--------|-----------|
| **$influencerFullName** | `$colab->influencer->name` + `surnames`; si vacío, `$colab->influencer->getUser()->name` (líneas 411-414). | Correcto. |
| **$influencerAcco** | Depende de **$rawInstagram** (línea 408). | Ver abajo. |
| **$rawInstagram** | Líneas 405-407: `$mainSocial = $colab->influencer->socialMedia->first()`; luego `$mainSocial->pivot->account_name` (si existe). | **Incorrecto:** no se filtra por `main`. |
| **Influencer** | `$colab->influencer`. Relación estándar de Colab. | OK. |

**Modelo Influencer** (`app/Models/Influencer.php`, líneas 117-124):

- Relación `socialMedia()`: `belongsToMany(SocialMedia::class)` con pivot `account_name`, `main`, etc.  
- No existe propiedad `influencerAcco` en el modelo. El nombre usado en el body es solo una variable local del método.  
- El handle “real” está en el **pivot** de esa relación: `influencer_social_media.account_name`, y la fila principal suele ser la que tiene `main = true`.

---

## 4) Flujo que dispara la notificación

- **Transición:** Colab pasa de **PENDING_COMPANY** a **CANCELLED** por **cancelación automática** (sin `$user`; no es el cliente ni Nomade quien cancela).
- **Archivo:** `app/Traits/HandlesHistoryTransitions.php`, método `handleCancelledState`.
- **Líneas:** 597-619 (rama `$prevStateId === ColabsStatusConstants::COLAB_PENDING_COMPANY_STATE`) y dentro el `else` (líneas 612-618) cuando no es `$user->isCompany()` ni `$user->isNomade()`.
- **Llamada:** Línea 617: `$this->notificationService()->sendAutoCanceledColabCompanyNotification($colab);`
- **Otra vía:** Comando `nomade:cancel-expired-collaborations` (o equivalente) que marca collabs como canceladas con `user` null; la transición de estado puede acabar en el mismo `handleCancelledState` y en ese mismo `else`.

En ese flujo la `$colab` suele venir del evento de transición; no hay eager load explícito de `influencer.socialMedia` en ese punto, por lo que la relación se carga bajo demanda. El problema no es tanto que no esté cargada, sino que al usar `->socialMedia->first()` se toma el primer elemento de la colección sin filtrar por `main`, por eso el handle mostrado no es el de la cuenta principal.

---

## 5) Fix propuesto (mínimo, NO aplicado)

Objetivo: obtener el handle de la **red social principal** (`main = true`) y mantener un fallback seguro.

**Archivo:** `app/Services/NotificationService.php`  
**Método:** `sendAutoCanceledColabCompanyNotification`  
**Líneas a sustituir:** 405-408.

**Sustituir:**

```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 (alineado con sendNewColabCompanyNotification):**

```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 ?? 'usuarioInstagram');
```

- Opcional: si en el modelo existiera algo como `instagram_username` o getter de handle, se podría usar como segundo fallback en lugar de `getUser()->name` (véase línea 1192 en `sendAutoCanceledModificationCompanyNotification` que usa `$colab->influencer->instagram_username`).

**Diff sugerido (solo cambio de lógica del handle):**

```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 ?? 'usuarioInstagram');
```

No se ha aplicado ningún cambio en el código; solo se propone el fix.

---

## 6) Otros métodos con patrón similar (posible mismo fallo o naming inconsistente)

Métodos en **NotificationService.php** que obtienen el handle de la red social del influencer y que podrían verse afectados o ser inconsistentes:

| Método | Línea aprox. | Cómo obtiene el handle | ¿Filtra por main? |
|--------|--------------|-------------------------|-------------------|
| **sendAutoCanceledColabCompanyNotification** | 405-406 | `socialMedia->first()` + pivot->account_name | No → **bug auditado** |
| sendNewColabCompanyNotification | 280-281 | `socialMedia()->wherePivot('main', true)->first()` | Sí |
| send1HourCalendarReminderCompanyNotification | 1503-1504 | `socialMedia()->first()` (sin wherePivot) | No → mismo riesgo |
| sendAutoCanceledModificationCompanyNotification | 1192 | `socialMedia()->first()?->pivot?->account_name ?? $colab->influencer->instagram_username` | No, pero tiene fallback en modelo |
| sendCanceledColabCompanyNotification | 1629-1630 | `socialMedia()->wherePivot('main', true)->first()` | Sí |
| sendPendingColabReminder1CompanyNotification | 1713-1714 | `wherePivot('main', true)->first()` | Sí |
| sendPendingColabReminder2CompanyNotification | 1825-1826 | `wherePivot('main', true)->first()` | Sí |
| sendDateChangedColabCompanyNotification | 2024-2025 | `wherePivot('main', true)->first()` | Sí |
| sendDataChangedColabCompanyNotification | 2059-2060 | `wherePivot('main', true)->first()` | Sí |
| sendNomadeCanceledPendingClientCompanyNotification | 2645 | `socialMedia()->first()?->pivot?->account_name` | No → mismo riesgo |

Recomendación: revisar también **send1HourCalendarReminderCompanyNotification** (1503) y **sendNomadeCanceledPendingClientCompanyNotification** (2645) por usar “first” sin filtrar por `main`; y, si se desea homogeneizar, usar en todos el mismo patrón: `socialMedia()->wherePivot('main', true)->first()` + fallback seguro.

---

**Resumen:** El body de la push a company se construye en `NotificationService::sendAutoCanceledColabCompanyNotification` (líneas 417-418). El handle queda vacío o con fallback porque en la línea 405 se usa `socialMedia->first()` en lugar de la red social principal (`wherePivot('main', true)->first()`). El fix propuesto es sustituir esas líneas por el patrón usado en `sendNewColabCompanyNotification` y un fallback a nombre de usuario o `'usuarioInstagram'`. No se ha aplicado ningún cambio en el código.
