> ## 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 el mecanismo de manejo de excepciones de Laravel. Cubrimos de forma completa el reporte, la renderización, las páginas de error personalizadas y la generación de respuestas HTTP de error.

## Visión general

Al crear un proyecto Laravel, el manejo de errores y excepciones ya viene configurado.
Personalízalo con el método `withExceptions` en `bootstrap/app.php`.

### Flujo de manejo de excepciones

Cómo transcurre el flujo desde que se lanza una excepción hasta que se devuelve una respuesta al cliente.

```mermaid theme={null}
flowchart TD
    A["Se lanza excepción"] --> B["ExceptionHandler::report"]
    B --> C{"¿Debe loguearse?"}
    C -->|"Sí"| D["Registro en log"]
    C -->|"No"| E["Se omite"]
    D --> F["ExceptionHandler::render"]
    E --> F
    F --> G{"Tipo de solicitud"}
    G -->|"Web"| H["Página HTML de error"]
    G -->|"API/JSON"| I["Respuesta JSON de error"]
    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 renderizado de excepciones
    })->create();
```

El objeto `$exceptions` que recibe la closure de `withExceptions` es una instancia de `Illuminate\Foundation\Configuration\Exceptions` que gestiona el manejo de excepciones de toda la aplicación.

### Configuración de debug

La opción `debug` de `config/app.php` controla el nivel de detalle mostrado.
Por defecto usa el valor de `APP_DEBUG` en `.env`.

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

# Producción
APP_DEBUG=false
```

<Warning>
  En producción, deja siempre `APP_DEBUG=false`. Si se queda en `true`, hay riesgo de exponer información sensible al usuario final.
</Warning>

## Reporte de excepciones

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

### Callback personalizado de reporte

Para reportar según el tipo de excepción, pasa una closure a `report`.
Laravel detecta el tipo por el type-hint de la closure.

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

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

Aun registrando un callback personalizado, el logging por defecto continúa.
Para cortar la propagación, llama a `stop()` o devuelve `false`.

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

### Helper `report()`

Para reportar sin mostrar una página de error, usa el helper `report()`.

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

        return false;
    }
}
```

<Tip>
  El helper `report()` te permite dejar constancia del error sin interrumpir la respuesta al usuario. Útil para excepciones no críticas o de jobs en segundo plano.
</Tip>

### Evitar reportes duplicados

Si la misma instancia de excepción se pasa varias veces a `report()`, pueden generarse entradas duplicadas.
Con `dontReportDuplicates()`, la misma instancia se registra solo 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 del log

Para añadir información común a todos los logs de excepción, usa `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'),
    ]);
})
```

### Método `context()` en la clase de excepción

Definir un método `context()` en la propia clase de excepción permite añadir información específica de esa excepción al log.

```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 el contexto de la excepción
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}
```

### Cambiar el nivel de log

Para registrar ciertas excepciones con un nivel específico, usa `level`.

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

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

### Limitación de reportes

Si se generan muchas excepciones, `throttle` te permite controlar cuántas se reportan.

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

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

Para limitar 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 consiste en convertir la excepción en una respuesta HTTP.
Por defecto Laravel genera la respuesta adecuada; también puedes personalizar el resultado.

### Callback personalizado de renderizado

Pasa una closure a `render` para convertir la excepción en una respuesta.

```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);
    });
})
```

Puedes sobrescribir el renderizado de excepciones integradas (`NotFoundHttpException`, etc.).
Si la closure no devuelve un valor, se usa 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);
        }
        // Devolver null muestra la página 404 por defecto
    });
})
```

### Selección automática JSON / HTML

Laravel decide si devolver HTML o JSON según la cabecera `Accept`.
Para personalizarlo, usa `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 devuelve JSON
        }

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

### Personalizar toda la respuesta

Con `respond` puedes procesar 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 excepciones en `app/Exceptions/`.
Si defines `report()` y `render()`, se invocan automáticamente sin necesidad de configurarlas en `bootstrap/app.php`.

### Crear la clase

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

  <Step title="Implementar 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>
  `report()` admite type-hints para inyección de dependencias. El contenedor de servicios de Laravel las resuelve automáticamente.
</Info>

### Interfaz `ShouldntReport`

Implementa `ShouldntReport` en excepciones que no deban reportarse.
Las excepciones con 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
{
    //
}
```

## Lanzamiento de excepciones

### Helper `abort()`

Puedes generar una respuesta HTTP de error desde cualquier punto de la aplicación.

```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 excepciones condicionalmente.

```php theme={null}
// Aborta si $condition es true
abort_if(! $user->isAdmin(), 403);

// Aborta si $condition es false
abort_unless($user->hasPermission('edit'), 403, 'Permission denied.');
```

<Tip>
  Muy útiles al comprobar permisos en controladores o middleware. Se combinan a menudo con gates y policies.
</Tip>

## Control global de excepciones

### Ignorar ciertas excepciones

Indica las excepciones a no reportar con `dontReport`. La lógica de renderizado personalizado sigue funcionando.

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

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

Para ignorar según condición, pasa una 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>
  Por defecto, Laravel ignora automáticamente ciertas excepciones: errores 404, token CSRF inválido (419), origen no coincidente (403), etc.
</Info>

### Reactivar excepciones ignoradas por Laravel

Para volver a reportar excepciones ignoradas 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 HTTP.

### Crear vistas de error personalizadas

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

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

En la vista puedes usar la variable `$exception` para acceder a la información del error.

```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

Para partir de las plantillas estándar de Laravel, publícalas con `vendor:publish`.

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

### Página de error de reserva

Como reserva cuando no hay una vista para un código concreto, puedes crear `4xx.blade.php` y `5xx.blade.php`.

```
resources/
└── views/
    └── errors/
        ├── 4xx.blade.php  # Fallback para códigos 4xx
        └── 5xx.blade.php  # Fallback para códigos 5xx
```

<Warning>
  Para `404`, `500` y `503`, Laravel ya trae páginas por defecto. Para personalizarlas, crea archivos individuales (`404.blade.php`, etc.), no la de reserva.
</Warning>

## Ejemplo práctico: handler de excepciones para API

En aplicaciones API, las excepciones deben devolverse siempre en JSON.
Ejemplo de gestión centralizada de errores de 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 {
    // Devuelve JSON para las solicitudes API
    $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);
        }
    });
})
```

### Clase base para excepciones de API

Con una clase base para la API, puedes devolver respuestas uniformes en todos los endpoints.

```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);
    }
}
```

Uso en el 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 está cancelado.', 422);
        }

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

## Resumen

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

  <Accordion title="Resumen del renderizado de excepciones">
    | Método                   | Uso                                                    |
    | ------------------------ | ------------------------------------------------------ |
    | `$exceptions->render()`  | Devuelve una respuesta personalizada por tipo          |
    | Método `render()`        | Aporta la lógica de renderizado en la propia excepción |
    | `shouldRenderJsonWhen()` | Personaliza la detección JSON/HTML                     |
    | `respond()`              | Procesa 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
    * Se accede al detalle del error con la variable `$exception`
    * `php artisan vendor:publish --tag=laravel-errors` publica las plantillas por defecto
    * Con `4xx.blade.php` / `5xx.blade.php` defines páginas de reserva
  </Accordion>

  <Accordion title="Buenas prácticas en producción">
    * Configura siempre `APP_DEBUG=false` para no exponer la traza al usuario
    * Integra servicios externos de seguimiento de errores como Sentry o Flare para centralizar
    * Usa `throttle()` para evitar la saturación del log ante grandes volúmenes de excepciones
    * En los endpoints de API, mantén un formato JSON de error consistente
  </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)
