Lección 35 de 45 15 min de lectura

Notificaciones

Laravel proporciona un sistema unificado para enviar notificaciones a través de múltiples canales: email, base de datos, SMS, Slack y más. Una sola clase de notificación puede enviarse por varios canales simultáneamente.

¿Por qué usar notificaciones?

Las notificaciones son diferentes a los emails tradicionales. Mientras que un email es un mensaje puntual, una notificación es un sistema que puede enviarse por múltiples canales según las preferencias del usuario.

Ejemplos de uso:

  • Pedido confirmado: email + notificación en la app
  • Nuevo mensaje: push notification + base de datos
  • Pago recibido: email + SMS para montos altos
  • Alerta del sistema: Slack para el equipo técnico

El trait Notifiable

Para enviar notificaciones a un modelo (normalmente User), debe usar el trait Notifiable. Laravel lo incluye por defecto en el modelo User:

php
<?php

namespace App\Models;

use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    // ...
}

Este trait proporciona el método notify() y las relaciones para acceder a las notificaciones del usuario. Sin él, no podrás enviar notificaciones.

Crear una notificación

Usa Artisan para generar una nueva notificación:

bash
php artisan make:notification OrderConfirmed

Esto crea app/Notifications/OrderConfirmed.php:

php
<?php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class OrderConfirmed extends Notification
{
    use Queueable;

    public function __construct(
        public Order $order
    ) {}

    // Define por qué canales se enviará
    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    // Contenido para el canal email
    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Pedido confirmado')
            ->greeting("Hola {$notifiable->name}")
            ->line("Tu pedido #{$this->order->id} ha sido confirmado.")
            ->line("Total: {$this->order->total}€")
            ->action('Ver pedido', route('orders.show', $this->order))
            ->line('Gracias por tu compra.');
    }
}

Enviar notificaciones

Hay dos formas de enviar notificaciones. La más común es usar el método notify() del modelo User:

php
<?php

use App\Notifications\OrderConfirmed;

// Enviar a un usuario específico
$user->notify(new OrderConfirmed($order));

Para enviar a múltiples usuarios, usa la facade Notification:

php
<?php

use Illuminate\Support\Facades\Notification;

$admins = User::where('is_admin', true)->get();

Notification::send($admins, new OrderConfirmed($order));

Notificaciones por email

El método toMail() devuelve un objeto MailMessage con una API fluida para construir el contenido:

php
<?php

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Bienvenido a nuestra plataforma')
        ->greeting('Hola ' . $notifiable->name)
        ->line('Gracias por registrarte.')
        ->line('Tu cuenta ha sido creada correctamente.')
        ->action('Acceder al dashboard', route('dashboard'))
        ->line('Si tienes preguntas, no dudes en contactarnos.')
        ->salutation('Saludos, el equipo de ' . config('app.name'));
}

Para emails de error o alerta, usa error():

php
<?php

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->error()
        ->subject('Pago fallido')
        ->line('No pudimos procesar tu pago.')
        ->action('Actualizar método de pago', route('billing.edit'));
}

Notificaciones en base de datos

El canal database guarda las notificaciones en una tabla para mostrarlas en la aplicación (como el icono de campana con notificaciones pendientes).

Primero, crea la tabla de notificaciones:

bash
php artisan make:notifications-table
php artisan migrate

Añade el canal database al método via() y define el método toDatabase() o toArray():

php
<?php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\Order;
use Illuminate\Notifications\Notification;

class OrderConfirmed extends Notification
{
    public function __construct(
        public Order $order
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail', 'database'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        // ... configuración del email
    }

    // Datos que se guardan en la base de datos
    public function toArray(object $notifiable): array
    {
        return [
            'order_id' => $this->order->id,
            'total' => $this->order->total,
            'message' => "Tu pedido #{$this->order->id} ha sido confirmado.",
        ];
    }
}

Leer notificaciones del usuario

El modelo User (con el trait Notifiable) tiene relaciones para acceder a sus notificaciones:

php
<?php

$user = auth()->user();

// Todas las notificaciones
$user->notifications;

// Solo las no leídas
$user->unreadNotifications;

// Contar notificaciones no leídas
$user->unreadNotifications()->count();

// Acceder a los datos de una notificación
foreach ($user->unreadNotifications as $notification) {
    echo $notification->data['message'];
}

Marcar notificaciones como leídas

php
<?php

// Marcar una notificación específica como leída
$notification->markAsRead();

// Marcar todas como leídas
$user->unreadNotifications->markAsRead();

// También puedes usar el método directo
auth()->user()->unreadNotifications()->update(['read_at' => now()]);

Un controlador típico para gestionar notificaciones:

php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class NotificationController extends Controller
{
    public function index(): View
    {
        return view('notifications.index', [
            'notifications' => auth()->user()->notifications()->paginate(20),
        ]);
    }

    public function markAsRead(string $id): RedirectResponse
    {
        auth()->user()->notifications()->findOrFail($id)->markAsRead();

        return redirect()->back();
    }

    public function markAllAsRead(): RedirectResponse
    {
        auth()->user()->unreadNotifications->markAsRead();

        return redirect()->back()->with('success', 'Notificaciones marcadas como leídas.');
    }

    // Para APIs: devolver notificaciones como JSON
    public function unread(): JsonResponse
    {
        return response()->json([
            'count' => auth()->user()->unreadNotifications()->count(),
            'notifications' => auth()->user()->unreadNotifications()->take(5)->get(),
        ]);
    }
}

Canales condicionales

Puedes enviar por diferentes canales según condiciones del usuario o la notificación:

php
<?php

public function via(object $notifiable): array
{
    $channels = ['database'];

    // Siempre email si el usuario lo tiene habilitado
    if ($notifiable->email_notifications) {
        $channels[] = 'mail';
    }

    // SMS solo para pedidos grandes
    if ($this->order->total > 1000) {
        $channels[] = 'vonage'; // antes Nexmo
    }

    return $channels;
}

Notificaciones en cola

Para evitar que el envío de emails ralentice la respuesta, implementa ShouldQueue:

php
<?php

declare(strict_types=1);

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class OrderConfirmed extends Notification implements ShouldQueue
{
    use Queueable;

    // La notificación se enviará en segundo plano
}

Recuerda: Para que las notificaciones en cola funcionen, debes tener un worker ejecutándose (php artisan queue:work). Vimos esto en la lección anterior sobre Colas y Jobs.

Notificaciones bajo demanda

A veces necesitas notificar a alguien que no es un usuario de tu aplicación (por ejemplo, enviar un email a una dirección específica):

php
<?php

use Illuminate\Support\Facades\Notification;
use App\Notifications\ContactFormReceived;

Notification::route('mail', 'admin@example.com')
    ->notify(new ContactFormReceived($message));

// Múltiples canales
Notification::route('mail', 'admin@example.com')
    ->route('slack', 'https://hooks.slack.com/...')
    ->notify(new ContactFormReceived($message));

Personalizar el remitente

Por defecto, Laravel usa la configuración de config/mail.php. Puedes personalizarlo por notificación:

php
<?php

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->from('soporte@example.com', 'Equipo de Soporte')
        ->replyTo('no-reply@example.com')
        ->subject('Tu solicitud ha sido recibida')
        ->line('Hemos recibido tu mensaje y te responderemos pronto.');
}

Adjuntar archivos

php
<?php

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Tu factura')
        ->line('Adjuntamos la factura de tu pedido.')
        ->attach(storage_path("app/invoices/{$this->order->id}.pdf"), [
            'as' => "factura-{$this->order->id}.pdf",
            'mime' => 'application/pdf',
        ]);
}

Resumen

  • Las notificaciones permiten enviar mensajes por múltiples canales desde una sola clase
  • Crea notificaciones con php artisan make:notification
  • El método via() define los canales: mail, database, slack, etc.
  • Usa $user->notify() para enviar a un usuario
  • El canal database guarda notificaciones para mostrar en la UI
  • Implementa ShouldQueue para enviar en segundo plano
  • Accede a las notificaciones con $user->notifications y $user->unreadNotifications

Ejercicios

Ejercicio 1: Notificación de bienvenida

Crea una notificación WelcomeNotification que se envíe por email cuando un usuario se registra. Debe incluir un saludo personalizado con el nombre del usuario y un botón para acceder al dashboard.

Ver solución
php artisan make:notification WelcomeNotification
<?php

// app/Notifications/WelcomeNotification.php

declare(strict_types=1);

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class WelcomeNotification extends Notification implements ShouldQueue
{
    use Queueable;

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

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Bienvenido a ' . config('app.name'))
            ->greeting("¡Hola {$notifiable->name}!")
            ->line('Gracias por unirte a nuestra plataforma.')
            ->line('Tu cuenta ha sido creada correctamente.')
            ->action('Ir al dashboard', route('dashboard'))
            ->line('Si tienes alguna pregunta, no dudes en contactarnos.')
            ->salutation('Saludos, el equipo de ' . config('app.name'));
    }
}
<?php

// Uso en el controlador de registro
use App\Notifications\WelcomeNotification;

public function store(Request $request): RedirectResponse
{
    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    $user->notify(new WelcomeNotification());

    return redirect()->route('dashboard');
}

Ejercicio 2: Notificación multicanal

Crea una notificación NewCommentNotification que se envíe por email y base de datos cuando alguien comenta en un artículo del usuario. Guarda en la base de datos el ID del comentario, el nombre del autor y un mensaje descriptivo.

Ver solución
php artisan make:notification NewCommentNotification
<?php

// app/Notifications/NewCommentNotification.php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\Comment;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class NewCommentNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Comment $comment
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail', 'database'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Nuevo comentario en tu artículo')
            ->greeting("Hola {$notifiable->name}")
            ->line("{$this->comment->author->name} ha comentado en tu artículo:")
            ->line("\"{$this->comment->article->title}\"")
            ->action('Ver comentario', route('articles.show', $this->comment->article))
            ->line('Gracias por ser parte de nuestra comunidad.');
    }

    public function toArray(object $notifiable): array
    {
        return [
            'comment_id' => $this->comment->id,
            'author_name' => $this->comment->author->name,
            'article_id' => $this->comment->article_id,
            'message' => "{$this->comment->author->name} comentó en \"{$this->comment->article->title}\"",
        ];
    }
}
<?php

// Uso en el controlador de comentarios
use App\Notifications\NewCommentNotification;

public function store(Request $request, Article $article): RedirectResponse
{
    $comment = $article->comments()->create([
        'user_id' => auth()->id(),
        'body' => $request->body,
    ]);

    // Notificar al autor del artículo (si no es el mismo que comenta)
    if ($article->user_id !== auth()->id()) {
        $article->author->notify(new NewCommentNotification($comment));
    }

    return redirect()->back()->with('success', 'Comentario publicado.');
}

Ejercicio 3: Listar notificaciones no leídas

Crea un controlador y una vista para mostrar las notificaciones no leídas del usuario autenticado. Incluye un botón para marcar todas como leídas.

Ver solución
php artisan make:controller NotificationController
<?php

// app/Http/Controllers/NotificationController.php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class NotificationController extends Controller
{
    public function index(): View
    {
        return view('notifications.index', [
            'notifications' => auth()->user()->unreadNotifications,
        ]);
    }

    public function markAsRead(string $id): RedirectResponse
    {
        $notification = auth()->user()
            ->notifications()
            ->findOrFail($id);

        $notification->markAsRead();

        return redirect()->back()
            ->with('success', 'Notificación marcada como leída.');
    }

    public function markAllAsRead(): RedirectResponse
    {
        auth()->user()->unreadNotifications->markAsRead();

        return redirect()->back()
            ->with('success', 'Todas las notificaciones marcadas como leídas.');
    }
}
<?php

// routes/web.php

use App\Http\Controllers\NotificationController;

Route::middleware('auth')->group(function () {
    Route::get('/notifications', [NotificationController::class, 'index'])
        ->name('notifications.index');

    Route::post('/notifications/{id}/read', [NotificationController::class, 'markAsRead'])
        ->name('notifications.read');

    Route::post('/notifications/read-all', [NotificationController::class, 'markAllAsRead'])
        ->name('notifications.read-all');
});
<!-- resources/views/notifications/index.blade.php -->

<x-app-layout>
    <h1>Notificaciones</h1>

    @if($notifications->count() > 0)
        <form action="{{ route('notifications.read-all') }}" method="POST">
            @csrf
            <button type="submit">Marcar todas como leídas</button>
        </form>

        <ul>
            @foreach($notifications as $notification)
                <li>
                    <p>{{ $notification->data['message'] }}</p>
                    <small>{{ $notification->created_at->diffForHumans() }}</small>

                    <form action="{{ route('notifications.read', $notification->id) }}" method="POST">
                        @csrf
                        <button type="submit">Marcar como leída</button>
                    </form>
                </li>
            @endforeach
        </ul>
    @else
        <p>No tienes notificaciones pendientes.</p>
    @endif
</x-app-layout>

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.

Descubrir cursos premium