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

# Manejo de errores

> Aprende cómo funciona el manejo de excepciones en Laravel. Cubre de forma exhaustiva el reporte, el renderizado, las páginas de error personalizadas y la generación de respuestas HTTP de error.

## Descripción general

Al crear un proyecto Laravel, el manejo de errores y excepciones ya está preconfigurado.
La personalización se realiza en el método `withExceptions` de `bootstrap/app.php`.

### Flujo de manejo de excepciones

Se muestra el recorrido desde que se produce una excepción hasta que se devuelve la respuesta al cliente.

```mermaid theme={null}
flowchart TD
    A["Se lanza la excepción"] --> B["ExceptionHandler::report"]
    B --> C{"¿Debe loguearse?"}
    C -->|"Sí"| D["Registra en el log"]
    C -->|"No"| E["Se omite"]
    D --> F["ExceptionHandler::render"]
    E --> F
    F --> G{"Tipo de petición"}
    G -->|"Web"| H["Página de error HTML"]
    G -->|"API/JSON"| I["Respuesta de error JSON"]
    H --> J["Se devuelve al cliente"]
    I --> J
```

```php theme={null}
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions): void {
        // Configura aquí el reporte y el renderizado de excepciones
    })->create();
```

El objeto `$exceptions` que se pasa al closure `withExceptions` es una instancia de `Illuminate\Foundation\Configuration\Exceptions` y gestiona el manejo de excepciones a nivel de aplicación.

### Configuración de debug

La opción `debug` de `config/app.php` controla la cantidad de información que se muestra ante un error.
Por defecto se utiliza el valor de la variable de entorno `APP_DEBUG` de `.env`.

```ini theme={null}
# Desarrollo local
APP_DEBUG=true

# Producción
APP_DEBUG=false
```

<Warning>
  En producción, `APP_DEBUG` debe ser siempre `false`. Dejarlo en `true` corre el riesgo de exponer información sensible al usuario final.
</Warning>

## Reporte de excepciones

Reportar una excepción significa registrarla en el log o enviarla a servicios externos como [Laravel Nightwatch](https://nightwatch.laravel.com), [Sentry](https://github.com/getsentry/sentry-laravel) o [Flare](https://flareapp.io).
Por defecto se registra según la configuración de `config/logging.php`.

### Callbacks de reporte personalizados

Si quieres aplicar un procesamiento distinto según el tipo de excepción, pasa un closure al método `report`.
Laravel deduce el tipo de excepción a partir del type hint del closure.

```php theme={null}
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // Notificar a un servicio externo, etc.
    });
})
```

Aunque registres un callback personalizado, el logueo por defecto sigue funcionando.
Si quieres detener la propagación al reporte por defecto, llama a `stop()` o devuelve `false`.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();
})
```

### El helper `report()`

Cuando quieras reportar únicamente la excepción sin mostrar una página de error, usa el helper `report()`.

```php theme={null}
public function isValid(string $value): bool
{
    try {
        // Procesamiento de validación...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}
```

<Tip>
  El helper `report()` te permite registrar errores sin interrumpir la respuesta al usuario. Es útil para el manejo de excepciones en jobs en segundo plano y en procesos no críticos.
</Tip>

### Evitar reportes duplicados

Si la misma instancia de excepción se pasa varias veces a `report()`, pueden crearse entradas duplicadas en el log.
Con `dontReportDuplicates()`, la misma instancia solo se registra la primera vez.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportDuplicates();
})
```

```php theme={null}
$original = new RuntimeException('Whoops!');

report($original); // se registra

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // se ignora (misma instancia)
}
```

### Contexto global de logging

Si quieres añadir información común a todos los logs de excepción, usa el método `context`.
Si está disponible, el ID del usuario actual se añade automáticamente.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->context(fn () => [
        'app_version' => config('app.version'),
    ]);
})
```

### Añadir un método `context()` a la clase de excepción

Si defines un método `context()` en la propia clase de excepción, podrás incluir en el log información contextual específica de esa excepción.

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

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    public function __construct(
        private readonly int $orderId,
        string $message = '',
    ) {
        parent::__construct($message);
    }

    /**
     * Devuelve la información contextual de la excepción
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}
```

### Cambiar el nivel de log

Para registrar una excepción concreta con un nivel de log determinado, usa el método `level`.

```php theme={null}
use PDOException;
use Psr\Log\LogLevel;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->level(PDOException::class, LogLevel::CRITICAL);
})
```

### Throttling del reporte de excepciones

Cuando se producen muchas excepciones, puedes controlar el número de reportes con el método `throttle`.

```php theme={null}
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    // Reporta al azar solo una vez de cada 1000
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})
```

Si quieres limitar por número de eventos por minuto, usa `Limit`.

```php theme={null}
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300);
        }
    });
})
```

## Renderizado de excepciones

Renderizar es el proceso de convertir la excepción en una respuesta HTTP.
Por defecto Laravel genera automáticamente una respuesta adecuada, pero puedes personalizarla.

### Callback de renderizado personalizado

Pasa un closure al método `render` para convertir excepciones en respuestas.

```php theme={null}
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
```

También puedes sobrescribir el renderizado de excepciones integradas (como `NotFoundHttpException`).
Si el closure no devuelve un valor, se utiliza el renderizado por defecto.

```php theme={null}
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.',
            ], 404);
        }
        // Si devuelves null, se muestra la página 404 por defecto
    });
})
```

### Detección automática de JSON / HTML

Laravel determina automáticamente si responder en HTML o JSON en función de la cabecera `Accept` de la petición.
Si quieres personalizar esta lógica, utiliza `shouldRenderJsonWhen`.

```php theme={null}
use Illuminate\Http\Request;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
        if ($request->is('admin/*')) {
            return true; // El panel de administración siempre responde en JSON
        }

        return $request->expectsJson();
    });
})
```

### Personalizar la respuesta completa

Con el método `respond` puedes seguir manipulando la respuesta generada.

```php theme={null}
use Symfony\Component\HttpFoundation\Response;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->respond(function (Response $response) {
        if ($response->getStatusCode() === 419) {
            return back()->with([
                'message' => 'La página ha caducado. Inténtalo de nuevo.',
            ]);
        }

        return $response;
    });
})
```

## Clases de excepción personalizadas

Puedes crear tus propias clases de excepción en el directorio `app/Exceptions/`.
Si defines los métodos `report()` y `render()`, se invocarán automáticamente sin necesidad de escribir configuración en `bootstrap/app.php`.

### Crear una clase de excepción

<Steps>
  <Step title="Crea la clase de excepción">
    ```shell theme={null}
    php artisan make:exception InvalidOrderException
    ```
  </Step>

  <Step title="Implementa report() y render()">
    ```php theme={null}
    <?php

    namespace App\Exceptions;

    use Exception;
    use Illuminate\Http\Request;
    use Illuminate\Http\Response;

    class InvalidOrderException extends Exception
    {
        public function __construct(
            private readonly int $orderId,
            string $message = 'Invalid order.',
        ) {
            parent::__construct($message);
        }

        /**
         * Reporta la excepción
         */
        public function report(): void
        {
            // Notificar a un servicio externo, etc.
        }

        /**
         * Convierte la excepción en respuesta HTTP
         */
        public function render(Request $request): Response
        {
            return response()->view('errors.invalid-order', [
                'orderId' => $this->orderId,
            ], 422);
        }
    }
    ```
  </Step>
</Steps>

<Info>
  En el método `report()` puedes usar inyección de dependencias con type hints. El service container de Laravel las resolverá automáticamente.
</Info>

### La interfaz `ShouldntReport`

Para las excepciones que no deban reportarse, implementa la interfaz `ShouldntReport`.
Las excepciones que implementen esta interfaz no se reportan nunca.

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

namespace App\Exceptions;

use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}
```

## Lanzar excepciones

### El helper `abort()`

Desde cualquier parte de la aplicación puedes generar una respuesta HTTP de error.

```mermaid theme={null}
flowchart LR
    A["abort(404)"] --> B["NotFoundHttpException"]
    C["abort(403)"] --> D["AccessDeniedHttpException"]
    E["abort(500)"] --> F["HttpException<br>500"]
    G["abort(422)"] --> H["UnprocessableEntityHttpException"]
    B --> I["resources/views/errors/404.blade.php"]
    D --> J["resources/views/errors/403.blade.php"]
    F --> K["resources/views/errors/500.blade.php"]
    H --> L["resources/views/errors/422.blade.php"]
```

```php theme={null}
// 404 Not Found
abort(404);

// Con mensaje
abort(403, 'No tienes permiso para realizar esta operación.');
```

### `abort_if()` / `abort_unless()`

Helpers para lanzar la excepción condicionalmente.

```php theme={null}
// Aborta cuando la condición es true
abort_if(! $user->isAdmin(), 403);

// Aborta cuando la condición es false
abort_unless($user->hasPermission('edit'), 403, 'Permission denied.');
```

<Tip>
  Son útiles al hacer comprobaciones de permisos en controladores o middleware. A menudo se combinan con gates o policies.
</Tip>

## Control global de excepciones

### Ignorar excepciones concretas

Indica con `dontReport` las excepciones que no quieres reportar. La lógica de renderizado personalizada sigue funcionando.

```php theme={null}
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})
```

Si quieres ignorar condicionalmente, pasa un closure a `dontReportWhen`.

```php theme={null}
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportWhen(function (Throwable $e) {
        return $e instanceof PodcastProcessingException &&
               $e->reason() === 'Subscription expired';
    });
})
```

<Info>
  Laravel ignora automáticamente por defecto ciertas excepciones, como el 404, un token CSRF inválido (419) o un origen no coincidente (403).
</Info>

### Volver a reportar excepciones que Laravel ignora

Para volver a reportar excepciones que se ignoran por defecto, usa `stopIgnoring`.

```php theme={null}
use Symfony\Component\HttpKernel\Exception\HttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->stopIgnoring(HttpException::class);
})
```

## Páginas de error HTTP

En Laravel puedes definir vistas de error personalizadas por código de estado HTTP.

### Crear vistas de error personalizadas

Crea en `resources/views/errors/` plantillas Blade cuyo nombre de archivo sea el código de estado.

```
resources/
└── views/
    └── errors/
        ├── 404.blade.php
        ├── 403.blade.php
        └── 500.blade.php
```

Dentro de la vista puedes acceder a la información de error mediante la variable `$exception`.

```blade theme={null}
{{-- resources/views/errors/404.blade.php --}}
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Página no encontrada</title>
</head>
<body>
    <h1>404 - Página no encontrada</h1>
    <p>{{ $exception->getMessage() }}</p>
    <a href="{{ url('/') }}">Volver al inicio</a>
</body>
</html>
```

### Publicar las plantillas de error por defecto

Si quieres usar las páginas de error estándar de Laravel como punto de partida para personalizar, obténlas con `vendor:publish`.

```shell theme={null}
php artisan vendor:publish --tag=laravel-errors
```

### Página de error de reserva

Como fallback cuando no exista una vista específica para el código de estado, puedes crear `4xx.blade.php` y `5xx.blade.php`.

```
resources/
└── views/
    └── errors/
        ├── 4xx.blade.php  # Fallback para 400
        └── 5xx.blade.php  # Fallback para 500
```

<Warning>
  Para `404`, `500` y `503`, Laravel ya proporciona páginas de error por defecto. Para personalizar estos códigos, crea archivos específicos (por ejemplo, `404.blade.php`) en lugar de usar el fallback.
</Warning>

## Ejemplo práctico: manejador de excepciones para una API

En aplicaciones que exponen una API, hay que devolver siempre las excepciones en JSON.
A continuación se muestra un ejemplo que centraliza los errores de la API en `bootstrap/app.php`.

```php theme={null}
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions): void {
    // Las peticiones de la API siempre en JSON
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Recurso no encontrado.',
            ], 404);
        }
    });

    $exceptions->render(function (AuthenticationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Se requiere autenticación.',
            ], 401);
        }
    });

    $exceptions->render(function (ValidationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Error de validación.',
                'errors'  => $e->errors(),
            ], 422);
        }
    });
})
```

### Implementar una clase de excepción base para la API

Al crear una clase base de excepción específica para la API, puedes devolver respuestas de error uniformes desde cada endpoint.

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

namespace App\Exceptions;

use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class ApiException extends Exception
{
    public function __construct(
        string $message = 'An error occurred.',
        private readonly int $statusCode = 500,
        private readonly array $errors = [],
    ) {
        parent::__construct($message);
    }

    public function render(Request $request): JsonResponse
    {
        $data = ['message' => $this->getMessage()];

        if (! empty($this->errors)) {
            $data['errors'] = $this->errors;
        }

        return response()->json($data, $this->statusCode);
    }
}
```

Ejemplo de uso en un controlador.

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

namespace App\Http\Controllers\Api;

use App\Exceptions\ApiException;
use App\Models\Order;

class OrderController extends Controller
{
    public function show(int $id): JsonResponse
    {
        $order = Order::find($id);

        if (! $order) {
            throw new ApiException('Pedido no encontrado.', 404);
        }

        if ($order->isCancelled()) {
            throw new ApiException('Este pedido ya ha sido cancelado.', 422);
        }

        return response()->json($order);
    }
}
```

## Resumen

<AccordionGroup>
  <Accordion title="Resumen del reporte de excepciones">
    | Método                    | Uso                                                             |
    | ------------------------- | --------------------------------------------------------------- |
    | `$exceptions->report()`   | Registrar lógica personalizada de reporte por tipo de excepción |
    | `$exceptions->context()`  | Añadir información común a todos los logs de excepción          |
    | Método `context()`        | Añadir contexto propio en la clase de excepción                 |
    | Helper `report()`         | Reportar solo la excepción sin interrumpir la respuesta         |
    | `dontReportDuplicates()`  | Evitar reportes duplicados de la misma instancia                |
    | Interfaz `ShouldntReport` | Crear clases de excepción que nunca se reportan                 |
  </Accordion>

  <Accordion title="Resumen del renderizado de excepciones">
    | Método                   | Uso                                                            |
    | ------------------------ | -------------------------------------------------------------- |
    | `$exceptions->render()`  | Devolver respuestas personalizadas por tipo de excepción       |
    | Método `render()`        | Poner la lógica de renderizado en la propia clase de excepción |
    | `shouldRenderJsonWhen()` | Personalizar la lógica de decisión JSON/HTML                   |
    | `respond()`              | Manipular aún más la respuesta generada                        |
  </Accordion>

  <Accordion title="Resumen de las páginas de error HTTP">
    * Basta con crear archivos como `resources/views/errors/404.blade.php` para que se usen automáticamente.
    * Puedes acceder a los detalles del error con la variable `$exception`.
    * Puedes obtener las plantillas por defecto con `php artisan vendor:publish --tag=laravel-errors`.
    * Puedes definir páginas de reserva con `4xx.blade.php` / `5xx.blade.php`.
  </Accordion>

  <Accordion title="Buenas prácticas en producción">
    * Configura siempre `APP_DEBUG=false` para no mostrar los stack traces al usuario.
    * Integra servicios externos de tracking de errores como Sentry o Flare para gestionarlos de forma centralizada.
    * Usa `throttle()` para evitar desbordamientos del log cuando se producen muchas excepciones.
    * Mantén un formato uniforme de respuestas de error JSON en los endpoints de la API.
  </Accordion>
</AccordionGroup>


## Related topics

- [Autenticación OAuth 2.0 - Google Sheets API for Laravel](/es/packages/laravel-google-sheets/oauth.md)
- [Laravel Fetch Metadata](/es/packages/laravel-fetch-metadata.md)
- [Laravel Pulse](/es/pulse.md)
- [Hooks de sesión](/es/packages/laravel-copilot-sdk/hooks.md)
- [Construir una SPA con Inertia.js](/es/blog/inertia-introduction.md)
