> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Canales de notificación personalizados en paquetes y verificación de la entrega

> Explica cómo ofrecer canales de notificación de Laravel 13 desde un paquete: resolución por nombre de clase, enrutamiento de destinatarios, procesamiento en colas y pruebas de la entrega real.

Cuando quieras añadir nuevas formas de entregar notificaciones desde un paquete, diseña por separado **la selección del canal** y **la determinación del destinatario**. En esta página se usa como ejemplo un pequeño canal que registra eventos operativos en el log para explicar cómo se resuelven las clases que ofrece el paquete y qué verificaciones necesitas al actualizarlo. Si lo sustituyes por un envío a un servicio externo, el reparto de responsabilidades entre el canal y la notificación es el mismo.

Esta página se basa en la implementación de Laravel Framework `v13.38.0`. Para las operaciones básicas con notificaciones, consulta [Notificaciones](/es/notifications.md); para el entorno de pruebas de paquetes, consulta [Pruebas de paquetes](/es/advanced/package-testing.md).

## El nombre de clase es la forma de entrega; la ruta es el destino

El nombre de la clase de canal que devuelves en `via()` indica sobre qué objeto se llama a `send()`. En cambio, el valor que se obtiene de `routeNotificationFor()` es el destino que usa ese canal. No es necesario que ambos tengan el mismo nombre.

| Dónde se indica | Ejemplo en esta página | Función |
| - | - | - |
| `via()` de la notificación | `OperationsLogChannel::class` | Resolución del canal y entrega |
| Ruta del objeto notificable | `operationsLog` → `operations` | Destino que se usa dentro del canal |
| `viaQueues()` de la notificación | `OperationsLogChannel::class` → `operations-log` | Cola de destino del job cuando se encola |

`NotificationSender` pasa el nombre de canal elegido por `via()` a `ChannelManager::driver()`. Si `ChannelManager` no lo encuentra entre los nombres integrados ni entre los registrados, y el nombre corresponde a una clase existente, resuelve esa clase con el contenedor. **Si solo lo usas por nombre de clase, no necesitas `Notification::extend()` ni un service provider en el paquete.** Basta con que el autoload de Composer encuentre la clase y que el contenedor pueda resolver las dependencias del constructor.

```mermaid theme={null}
flowchart LR
    A["Elegir el nombre de clase en via()"] --> B["ChannelManager::driver()"]
    B --> C{"¿Ya resuelto?"}
    C -->|Sí| D["Canal retenido"]
    C -->|No| E["Crear con el contenedor"]
    E --> D
    D --> F["Obtener el destino en send() y entregar"]
```

## Ofrecer el canal desde el paquete

El paquete de ejemplo `Acme\OperationsLog` registra un nombre de evento sin información sensible junto con el nombre del equipo de operaciones destinatario. En una aplicación Laravel, `Psr\Log\LoggerInterface` está vinculado al servicio de log, así que puedes recibirlo en el constructor.

```php theme={null}
<?php

namespace Acme\OperationsLog;

use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;

final class OperationsLogChannel
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }

    public function send(object $notifiable, Notification $notification): void
    {
        $destination = $notifiable->routeNotificationFor('operationsLog', $notification);

        if (! is_string($destination) || $destination === '') {
            throw new InvalidArgumentException('Operations log destination is required.');
        }

        $this->logger->info('Operations notification', [
            'destination' => $destination,
            'event' => $notification->toOperationsLog($notifiable),
        ]);
    }
}
```

El objeto recibido puede ser un modelo que use el trait `Notifiable` o un destinatario bajo demanda creado con `Notification::route()`. En el primer caso se devuelve el valor de `routeNotificationForOperationsLog()`; en el segundo, el valor registrado con `route('operationsLog', ...)`. Para no tratar como entregada una notificación sin destino, este ejemplo lanza una excepción. Si lo sustituyes por una entrega externa real, comprueba también el destino y el resultado del envío, y no ocultes los fallos como si fueran éxitos.

> [!WARNING]
>
> No incluyas tokens de acceso ni datos personales en los nombres de evento ni en los destinos que registras en el log. Este ejemplo es un envío al log para ilustrar la resolución de canales y las pruebas; no es una implementación de entrega a un servicio externo.

## Definir la notificación y el destino en la aplicación

La notificación de la aplicación devuelve la clase del canal y solo define el mensaje que se pasa al canal. Si usas `ShouldQueue` y `Queueable`, las llamadas normales a `notify()` pasan por la cola.

```php theme={null}
<?php

namespace App\Notifications;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class HealthCheckPassed extends Notification implements ShouldQueue
{
    use Queueable;

    public function via(object $notifiable): array
    {
        return [OperationsLogChannel::class];
    }

    public function toOperationsLog(object $notifiable): string
    {
        return 'health-check-passed';
    }

    public function viaQueues(): array
    {
        return [OperationsLogChannel::class => 'operations-log'];
    }
}
```

Si indicas el destino en el momento, la clave de `Notification::route()` **no es el nombre de la clase del canal**, sino la clave que el canal usa al llamar a `routeNotificationFor('operationsLog', ...)`.

```php theme={null}
use App\Notifications\HealthCheckPassed;
use Illuminate\Support\Facades\Notification;

Notification::route('operationsLog', 'operations')
    ->notify(new HealthCheckPassed());
```

Si envías desde un modelo de la aplicación, añade el siguiente método al modelo que usa `Notifiable`.

```php theme={null}
use Illuminate\Notifications\Notification;

public function routeNotificationForOperationsLog(Notification $notification): string
{
    return 'operations';
}
```

Con `$user->notify(new HealthCheckPassed())` sobre este modelo también se elige el mismo canal. Configura la conexión de cola e inicia un worker que procese la cola `operations-log`.

```shell theme={null}
php artisan queue:work --queue=operations-log
```

El propio `OperationsLogChannel` queda retenido en el Manager por cada canal, así que **no guardes el destinatario ni el contenido de la notificación en propiedades del canal**: obtenlos en cada llamada a partir de los argumentos de `send()`. Así evitas que un worker de cola de larga duración arrastre información de la notificación anterior.

## Cuando necesitas un alias corto

Usa `Notification::extend()` en un service provider normal del paquete solo si quieres que los usuarios indiquen en `via()` un nombre corto en lugar del nombre de clase.

```php theme={null}
<?php

namespace Acme\OperationsLog;

use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ServiceProvider;

class OperationsLogServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Notification::extend(
            'acme-operations-log',
            fn ($app) => $app->make(OperationsLogChannel::class),
        );
    }
}
```

Haz que el provider se cargue antes de que empiece la entrega, por ejemplo mediante el [descubrimiento automático de paquetes](/es/advanced/package-discovery.md). En las notificaciones que elijan el alias, cambia `via()` a `['acme-operations-log']` y usa el mismo nombre como clave en `viaQueues()`. **`operationsLog`, que sirve para obtener el destino, es una clave distinta**, así que no hace falta cambiarla. Usa un nombre que no colisione con los canales integrados.

El Manager retiene los canales resueltos por nombre. Aunque vuelvas a registrar la lógica de creación del alias en el mismo proceso, la instancia ya creada no se reemplaza. Verifica los cambios en la forma de registro también en aplicaciones y workers recién iniciados.

## Probar la entrega por separado de fake

`Notification::fake()` sirve para verificar la **intención** de enviar una notificación. No confirma que funcionen el `send()` del canal ni la obtención de la ruta. En las pruebas de entrega, usa el Manager de notificaciones real y sustituye `LoggerInterface` por uno de prueba.

El siguiente ejemplo se ejecuta desde el [`TestCase` de Testbench](/es/advanced/package-testing). Con `notifyNow()` puedes ejecutar el procesamiento del canal sin iniciar un worker, incluso en notificaciones `ShouldQueue`.

```php theme={null}
<?php

namespace Acme\OperationsLog\Tests;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification as BaseNotification;
use Illuminate\Support\Facades\Notification;
use Orchestra\Testbench\TestCase;
use Psr\Log\LoggerInterface;

class OperationsLogChannelTest extends TestCase
{
    public function test_channel_writes_the_routed_event(): void
    {
        $notification = new class extends BaseNotification implements ShouldQueue {
            use Queueable;

            public function via(object $notifiable): array
            {
                return [OperationsLogChannel::class];
            }

            public function toOperationsLog(object $notifiable): string
            {
                return 'health-check-passed';
            }
        };

        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info')->with(
            'Operations notification',
            ['destination' => 'operations', 'event' => 'health-check-passed'],
        );

        $this->app->instance(LoggerInterface::class, $logger);

        Notification::route('operationsLog', 'operations')->notifyNow($notification);
    }
}
```

Además, comprueba que se lanza una excepción cuando no hay destino y, si ofreces un alias, que se resuelve desde un arranque nuevo. Si lo usas a través de colas, prueba también, en una aplicación de verificación con un worker de cola en ejecución, que los jobs llegan a la cola `operations-log` y se procesan.

`NotificationSender` divide las notificaciones `ShouldQueue` en un job por canal y elige la cola según la clave de `viaQueues()`. Si el `send()` del canal lanza una excepción, dispara `NotificationFailed` y vuelve a lanzar la excepción; si termina con normalidad, dispara `NotificationSent`. La configuración de reintentos de la cola sigue la configuración de la aplicación. Ten en cuenta que, si ocultas la excepción dentro del canal, la entrega se tratará como exitosa aunque haya fallado.

## Fuentes primarias consultadas

* [Documentación oficial de Laravel 13: canales de notificación personalizados y colas](https://github.com/laravel/docs/blob/13.x/notifications.md#custom-channels)
* [Documentación oficial de Laravel 13: descubrimiento automático de paquetes](https://github.com/laravel/docs/blob/13.x/packages.md#package-discovery)
* [ChannelManager: resolución a partir del nombre de clase](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/ChannelManager.php)
* [NotificationSender: entrega, colas y eventos por canal](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/NotificationSender.php)
* [RoutesNotifications y AnonymousNotifiable: rutas de destino de las notificaciones](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/RoutesNotifications.php)
* [Manager: retención y extensión de drivers](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Support/Manager.php)
* [Application: vinculación de LoggerInterface](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Foundation/Application.php)


## Related topics

- [Laravel Console Starter](/es/packages/laravel-console-starter/index.md)
- [Verificación (Verify)](/es/packages/laravel-bluesky/verify.md)
- [Notificaciones](/es/notifications.md)
- [Registro y caché de listeners de eventos en paquetes](/es/advanced/package-events.md)
- [Diseño del ciclo de vida de los servicios de paquetes](/es/advanced/package-service-lifetimes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.