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

# Estructura de aplicación en Laravel 11 y posteriores

> Presenta el Slim Application Skeleton introducido en Laravel 11 en detalle y explica la implementación interna, desde Application::configure() hasta ApplicationBuilder.

## Comparación con Laravel 10 y anteriores

En Laravel 11 la estructura de la aplicación se ha rediseñado profundamente con el llamado «Slim Application Skeleton». El cambio más importante es la eliminación de la dispersión de configuración: todo se concentra en `bootstrap/app.php`.

| Elemento               | Laravel 10 y anteriores                   | Laravel 11 y posteriores                     |
| ---------------------- | ----------------------------------------- | -------------------------------------------- |
| Kernel HTTP            | `app/Http/Kernel.php`                     | Eliminado (integrado en el framework)        |
| Kernel de consola      | `app/Console/Kernel.php`                  | Eliminado (migrado a `routes/console.php`)   |
| Handler de excepciones | `app/Exceptions/Handler.php`              | Eliminado (unificado en `bootstrap/app.php`) |
| Service providers      | 5 archivos                                | Un único archivo `AppServiceProvider.php`    |
| Archivos de rutas      | `web.php` y `api.php` por defecto         | Solo `web.php` por defecto, `api.php` opt-in |
| Bootstrap              | Configuración dispersa en varios archivos | Concentrada en `bootstrap/app.php`           |
| BD por defecto         | MySQL/PostgreSQL                          | SQLite                                       |

<Info>
  Este cambio es **para proyectos nuevos**. Si actualizas una aplicación existente de Laravel 10, la estructura antigua seguirá funcionando sin problemas.
</Info>

## Nueva estructura de directorios y archivos

### Estructura de directorios del skeleton

```
laravel-app/
├── app/
│   ├── Http/
│   │   └── Controllers/
│   ├── Models/
│   │   └── User.php
│   └── Providers/
│       └── AppServiceProvider.php
├── bootstrap/
│   ├── app.php          ← centro de la configuración de la aplicación
│   ├── cache/
│   └── providers.php    ← lista de service providers
├── config/
├── database/
├── public/
│   └── index.php        ← punto de entrada
├── resources/
├── routes/
│   ├── web.php          ← rutas web (por defecto)
│   └── console.php      ← comandos de Artisan y schedule
├── storage/
└── tests/
```

### `bootstrap/app.php` — el centro de la configuración de la aplicación

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

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
```

Con este único archivo puedes configurar el enrutamiento, los middlewares y el manejo de excepciones. La configuración que en Laravel 10 y anteriores estaba repartida entre `app/Http/Kernel.php`, `app/Console/Kernel.php` y `app/Exceptions/Handler.php` se concentra aquí.

### `bootstrap/providers.php` — lista de service providers

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

use App\Providers\AppServiceProvider;

return [
    AppServiceProvider::class,
];
```

Este archivo es el lugar donde se registran los service providers. En Laravel 10 se declaraban en el array `providers` de `config/app.php`; ahora esa lista se ha separado en `bootstrap/providers.php`. Por defecto en Laravel 11 solo aparece `AppServiceProvider`.

<Tip>
  Cuando instalas un paquete con `composer require`, el paquete puede actualizar automáticamente `bootstrap/providers.php`. `config/app.php` no ha dejado de leerse, pero ahora el lugar recomendado para registrar providers nuevos es `bootstrap/providers.php`.
</Tip>

### Cambios en el directorio `routes/`

```
routes/
├── web.php      ← rutas web (se cargan por defecto)
└── console.php  ← se definen aquí los comandos Artisan y el schedule
```

Por defecto no existen `api.php` ni `channels.php`. Si los necesitas, los generas con comandos Artisan.

```shell theme={null}
# Añadir rutas de API (instala api.php + Sanctum)
php artisan install:api

# Añadir broadcasting (instala channels.php + Reverb, etc.)
php artisan install:broadcasting
```

También puedes definir el schedule en `routes/console.php`.

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

use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

Schedule::command('emails:send')->daily();
```

### Archivos eliminados

<AccordionGroup>
  <Accordion title="Eliminación de app/Http/Kernel.php">
    El kernel HTTP se ha integrado en la clase `Illuminate\Foundation\Http\Kernel` del framework. La personalización de middlewares se hace con `withMiddleware()` en `bootstrap/app.php`.

    ```php theme={null}
    // Laravel 10 y anteriores: app/Http/Kernel.php
    protected $middleware = [
        \Illuminate\Http\Middleware\TrustProxies::class,
        // ...
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            // ...
        ],
    ];
    ```

    ```php theme={null}
    // Laravel 11 y posteriores: bootstrap/app.php
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            EnsureUserIsSubscribed::class,
        ]);

        $middleware->validateCsrfTokens(except: ['stripe/*']);
    })
    ```
  </Accordion>

  <Accordion title="Eliminación de app/Console/Kernel.php">
    Las dos responsabilidades del kernel de consola se han separado. Los comandos de Artisan se colocan en `app/Console/Commands/` y se autodetectan; el schedule se declara en `routes/console.php`.

    ```php theme={null}
    // Laravel 10 y anteriores: app/Console/Kernel.php
    protected function schedule(Schedule $schedule): void
    {
        $schedule->command('emails:send')->daily();
    }
    ```

    ```php theme={null}
    // Laravel 11 y posteriores: routes/console.php
    use Illuminate\Support\Facades\Schedule;

    Schedule::command('emails:send')->daily();
    ```
  </Accordion>

  <Accordion title="Eliminación de app/Exceptions/Handler.php">
    El handler de excepciones se ha integrado en la clase `Illuminate\Foundation\Exceptions\Handler` del framework. La personalización se hace con `withExceptions()` en `bootstrap/app.php`.

    ```php theme={null}
    // Laravel 10 y anteriores: app/Exceptions/Handler.php
    public function register(): void
    {
        $this->reportable(function (InvalidOrderException $e) {
            // ...
        });
    }
    ```

    ```php theme={null}
    // Laravel 11 y posteriores: bootstrap/app.php
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->report(function (InvalidOrderException $e) {
            // ...
        });
    })
    ```
  </Accordion>
</AccordionGroup>

## Cómo funciona `Application::configure()`

### Implementación interna en el framework

`Application::configure()` es un método estático de `Illuminate\Foundation\Application`.

```php theme={null}
// De Illuminate\Foundation\Application
public static function configure(?string $basePath = null)
{
    $basePath = match (true) {
        is_string($basePath) => $basePath,
        default => static::inferBasePath(),
    };

    return (new Configuration\ApplicationBuilder(new static($basePath)))
        ->withKernels()
        ->withEvents()
        ->withCommands()
        ->withProviders();
}
```

Este método realiza lo siguiente:

1. Determina el directorio raíz de la aplicación a partir de `basePath`.
2. Crea una instancia de `Application`.
3. La envuelve con `ApplicationBuilder` y aplica la configuración por defecto.
4. Devuelve la instancia de `ApplicationBuilder`.

Un detalle importante: dentro de `configure()` **ya se han llamado `withKernels()` / `withEvents()` / `withCommands()` / `withProviders()`**. No necesitas volver a invocarlas en `bootstrap/app.php`.

### Flujo de configuración con method chaining

```php theme={null}
Application::configure(basePath: dirname(__DIR__))  // devuelve un ApplicationBuilder
    ->withRouting(...)       // configura las rutas y devuelve $this
    ->withMiddleware(...)    // configura los middlewares y devuelve $this
    ->withExceptions(...)    // configura el manejo de excepciones y devuelve $this
    ->create();              // devuelve la instancia de Application
```

Al llamar a `create()`, se extrae la instancia de `Application` del `ApplicationBuilder`, y esa `Application` es lo que retorna `bootstrap/app.php`.

## Flujo desde la petición hasta el arranque de la aplicación

```mermaid theme={null}
flowchart TD
    A["public/index.php<br>punto de entrada"] --> B["Se hace require de bootstrap/app.php<br>y se obtiene el Application"]
    B --> C["Application::configure()<br>genera un ApplicationBuilder"]
    C --> D["withKernels()<br>registra los kernels HTTP/Console"]
    D --> E["withEvents()<br>configura la event discovery"]
    E --> F["withCommands()<br>configura la ruta de los comandos Artisan"]
    F --> G["withProviders()<br>carga bootstrap/providers.php"]
    G --> H["withRouting()<br>configura el enrutamiento"]
    H --> I["withMiddleware()<br>configura los middlewares"]
    I --> J["withExceptions()<br>configura el manejo de excepciones"]
    J --> K["create()<br>devuelve la instancia de Application"]
    K --> L["El HTTP Kernel procesa la petición<br>middlewares → router → controlador"]
    L --> M["Devuelve la respuesta"]
```

`public/index.php` es el punto de entrada; carga `bootstrap/app.php` para obtener `Application`. A continuación, el HTTP Kernel pasa la petición por el pipeline de middlewares y el router la envía al controlador correspondiente.

## Métodos principales de `ApplicationBuilder` en profundidad

### `withRouting()` — el proceso interno del registro de rutas

```php theme={null}
public function withRouting(
    ?Closure $using = null,
    array|string|null $web = null,
    array|string|null $api = null,
    ?string $commands = null,
    ?string $channels = null,
    ?string $pages = null,
    ?string $health = null,
    string $apiPrefix = 'api',
    ?callable $then = null
)
```

Internamente registra una callback en `AppRouteServiceProvider::loadRoutesUsing()` y registra el propio `AppRouteServiceProvider` durante el booting de la aplicación.

```php theme={null}
// Procesamiento interno de withRouting() (versión simplificada)
protected function buildRoutingCallback(...)
{
    return function () use ($web, $api, $pages, $health, $apiPrefix, $then) {
        if (is_string($api) || is_array($api)) {
            Route::middleware('api')->prefix($apiPrefix)->group($api);
        }

        if (is_string($health)) {
            Route::get($health, function () {
                Event::dispatch(new DiagnosingHealth);
                return response(View::file(...), status: 200);
            });
        }

        if (is_string($web) || is_array($web)) {
            Route::middleware('web')->group($web);
        }

        if (is_callable($then)) {
            $then($this->app);
        }
    };
}
```

**Puntos clave:**

* A las rutas `api` se les aplican automáticamente el grupo de middleware `api` y el prefijo `/api`.
* Si pasas una cadena a `health`, se registra automáticamente un endpoint de health check (por defecto `/up`).
* La ruta de `health` queda excluida incluso en modo mantenimiento (se configura con `PreventRequestsDuringMaintenance::except()`).
* Ten en cuenta que `api` se registra antes que `web`. Si defines rutas web y de API en la misma ruta, prevalecerá la de API.
* Si pasas una cadena a `pages`, se habilita el enrutamiento de [Laravel Folio](https://github.com/laravel/folio).

### `withMiddleware()` — personalización de middlewares

```php theme={null}
public function withMiddleware(?callable $callback = null)
{
    $this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) {
        $middleware = (new Middleware)
            ->redirectGuestsTo(fn () => route('login'));

        if (! is_null($callback)) {
            $callback($middleware);
        }

        $kernel->setGlobalMiddleware($middleware->getGlobalMiddleware());
        $kernel->setMiddlewareGroups($middleware->getMiddlewareGroups());
        $kernel->setMiddlewareAliases($middleware->getMiddlewareAliases());
        // ...
    });

    return $this;
}
```

`withMiddleware()` ejecuta la callback **después** de que se haya resuelto `HttpKernel`, gracias al hook `afterResolving()`. El objeto `Middleware` que se pasa a la callback ofrece numerosos métodos de personalización.

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    // Añadir un middleware global
    $middleware->append(MyGlobalMiddleware::class);

    // Añadir middleware al grupo web
    $middleware->web(append: [EnsureUserIsSubscribed::class]);

    // Reemplazar un middleware del grupo api
    $middleware->api(replace: [
        OldMiddleware::class => NewMiddleware::class,
    ]);

    // Configurar rutas excluidas de CSRF
    $middleware->validateCsrfTokens(except: ['stripe/*', 'webhook/*']);

    // Cambiar el destino de redirección para invitados
    $middleware->redirectGuestsTo('/custom-login');

    // Establecer la prioridad de los middlewares
    $middleware->priority([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    ]);
})
```

### `withExceptions()` — configuración del manejo de excepciones

```php theme={null}
public function withExceptions(?callable $using = null)
{
    $this->app->singleton(
        \Illuminate\Contracts\Debug\ExceptionHandler::class,
        \Illuminate\Foundation\Exceptions\Handler::class
    );

    if ($using !== null) {
        $this->app->afterResolving(
            \Illuminate\Foundation\Exceptions\Handler::class,
            fn ($handler) => $using(new Exceptions($handler)),
        );
    }

    return $this;
}
```

`withExceptions()` registra la clase `Handler` del framework como singleton y luego programa la callback con `afterResolving()`. A la callback se le pasa un wrapper `Exceptions`.

```php theme={null}
->withExceptions(function (Exceptions $exceptions) {
    // No reportar una excepción concreta
    $exceptions->dontReport(MissedFlightException::class);

    // Reportar una excepción concreta de forma personalizada
    $exceptions->report(function (InvalidOrderException $e) {
        // Notificar a Slack, etc.
    });

    // Personalizar la respuesta HTTP para una excepción concreta
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Not Found'], 404);
        }
    });

    // Throttling (no reportar repetidamente la misma excepción)
    $exceptions->throttle(function (Throwable $e) {
        return Limit::perMinute(20);
    });
})
```

### `withProviders()` — registro de service providers

```php theme={null}
public function withProviders(array $providers = [], bool $withBootstrapProviders = true)
{
    RegisterProviders::merge(
        $providers,
        $withBootstrapProviders
            ? $this->app->getBootstrapProvidersPath()
            : null
    );

    return $this;
}
```

Como `Application::configure()` llama por defecto a `withProviders()`, `bootstrap/providers.php` se carga automáticamente. Si quieres añadir providers adicionales, tienes que llamarlo explícitamente en `bootstrap/app.php`.

```php theme={null}
Application::configure(basePath: dirname(__DIR__))
    ->withProviders([
        // Añadir providers además de los de bootstrap/providers.php
        App\Providers\CustomServiceProvider::class,
    ])
    ->withRouting(...)
    ->create();
```

<Warning>
  Si pasas `withBootstrapProviders: false`, dejará de cargarse `bootstrap/providers.php`. Omite ese parámetro salvo que tengas una razón concreta.
</Warning>

### Otros métodos principales

| Método                               | Descripción                                                                                 |
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
| `withKernels()`                      | Registra como singleton los kernels HTTP / Console. `configure()` lo llama automáticamente. |
| `withEvents()`                       | Activa la event discovery. `configure()` lo llama automáticamente.                          |
| `withCommands(array $commands)`      | Registra clases o directorios adicionales de comandos Artisan.                              |
| `withSchedule(callable $callback)`   | Define el schedule en `bootstrap/app.php`.                                                  |
| `withBroadcasting(string $channels)` | Registra el archivo de canales de broadcasting.                                             |
| `withBindings(array $bindings)`      | Registra bindings del contenedor.                                                           |
| `withSingletons(array $singletons)`  | Registra bindings singleton.                                                                |
| `registered(callable $callback)`     | Añade una callback que se ejecuta tras registrar los service providers.                     |
| `booting(callable $callback)`        | Añade una callback que se ejecuta durante el booting.                                       |
| `booted(callable $callback)`         | Añade una callback que se ejecuta tras el booted.                                           |
| `create()`                           | Devuelve la instancia de `Application`.                                                     |

## Intención del diseño: por qué es así

### Configuración «code first»

El `app/Http/Kernel.php` de Laravel 10 y anteriores utilizaba un estilo en el que los middlewares se enumeraban en arrays. Era un estilo cercano a un archivo de configuración, con la desventaja de que no aprovechaba bien el sistema de tipos de PHP ni el soporte del IDE.

En Laravel 11 se pasa al estilo callback `withMiddleware(function (Middleware $middleware) { ... })`. Así se dispone de autocompletado por tipos y se pueden escribir configuraciones dinámicas de forma natural, con condicionales, bucles y otras estructuras.

### De «convención sobre configuración» a «configuración explícita»

Hacer que `api.php` sea opt-in resuelve el hecho de que el grupo de middleware `api` se cargaba siempre incluso en aplicaciones que no usaban rutas de API. La idea es que las funcionalidades no utilizadas simplemente no existen por defecto.

### El uso del hook `afterResolving()`

En `withMiddleware()` y `withExceptions()` se recurre a `afterResolving()` para evitar problemas de orden en la configuración. Los métodos de `ApplicationBuilder` se llaman antes de que la aplicación se arranque por completo, pero la aplicación real de la configuración (aplicarla al kernel) queda diferida hasta que el kernel se resuelve por primera vez.

## Ejemplos prácticos de personalización

### Convivencia de API y Web

```php theme={null}
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
        apiPrefix: 'api/v1',  // Cambio respecto al /api por defecto
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
```

### Personalización de middlewares

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    // Añadir un middleware de verificación a las rutas web
    $middleware->web(append: [
        \App\Http\Middleware\EnsureEmailIsVerified::class,
    ]);

    // Excluir un middleware concreto en las rutas de API
    $middleware->api(remove: [
        \Illuminate\Session\Middleware\StartSession::class,
    ]);

    // Excluir los endpoints de webhook del CSRF
    $middleware->validateCsrfTokens(except: [
        'webhook/*',
        'stripe/webhook',
    ]);

    // Asignar un alias a un middleware específico
    $middleware->alias([
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ]);
})
```

### Centralizar el schedule en `bootstrap/app.php`

El schedule también puede escribirse en `routes/console.php`, pero con `withSchedule()` puedes concentrarlo en `bootstrap/app.php`.

```php theme={null}
use Illuminate\Console\Scheduling\Schedule;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withSchedule(function (Schedule $schedule) {
        $schedule->command('emails:send')->daily();
        $schedule->command('reports:generate')->weeklyOn(1, '8:00');
        $schedule->job(new PruneOldRecords)->daily();
    })
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
```

### Personalización del manejo de excepciones

```php theme={null}
->withExceptions(function (Exceptions $exceptions) {
    // Devolver siempre JSON para las peticiones de API
    $exceptions->render(function (Throwable $e, Request $request) {
        if ($request->is('api/*') || $request->wantsJson()) {
            $status = match (true) {
                $e instanceof NotFoundHttpException => 404,
                $e instanceof AuthenticationException => 401,
                $e instanceof AuthorizationException => 403,
                $e instanceof ValidationException => 422,
                default => 500,
            };

            return response()->json([
                'message' => $e->getMessage(),
            ], $status);
        }
    });

    // Notificar a Slack solo en producción
    if (app()->isProduction()) {
        $exceptions->report(function (Throwable $e) {
            app(SlackNotifier::class)->notify($e);
        })->stop();
    }
})
```

### Gestionar bindings del contenedor en `bootstrap/app.php`

En aplicaciones pequeñas puedes escribir bindings sencillos directamente en `bootstrap/app.php` en lugar de en `AppServiceProvider`.

```php theme={null}
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(...)
    ->withSingletons([
        \App\Contracts\PaymentGateway::class => \App\Services\StripeGateway::class,
        \App\Contracts\MailService::class => \App\Services\SendgridMailService::class,
    ])
    ->withMiddleware(...)
    ->withExceptions(...)
    ->create();
```

## Orden del proceso de arranque de Laravel

Al arrancar una aplicación de Laravel, los service providers y los hooks de Application se ejecutan en este orden:

1. `register()` de todos los ServiceProvider.
2. `registered()` de Application.
3. `booting()` de Application.
4. `boot()` de todos los ServiceProvider.
5. `booted()` de Application.

Puedes comprobar el orden añadiendo el siguiente código a `AppServiceProvider`.

```php theme={null}
class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        info('1. AppServiceProvider register');

        $this->booting(function () {
            info('4. AppServiceProvider booting');
        });

        $this->booted(function () {
            info('5. AppServiceProvider booted');
        });

        $this->app->registered(function () {
            info('2. app registered');
        });

        $this->app->booting(function () {
            info('3. app booting');
        });

        $this->app->booted(function () {
            info('6. AppServiceProvider@register app booted');
        });
    }

    public function boot(): void
    {
        $this->app->booted(function () {
            info('7. AppServiceProvider@boot app booted');
        });
    }
}
```

Los métodos `registered()`, `booting()` y `booted()` de `ApplicationBuilder` simplemente registran callbacks en Application. Normalmente no necesitas usarlos, pero permiten operaciones especiales como modificar el Kernel en `booted()` cuando ya está listo.

```php theme={null}
// bootstrap/app.php

use Illuminate\Contracts\Http\Kernel;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(...)
    ->withMiddleware(...)
    ->withExceptions(...)
    ->booted(function (Application $app) {
        $kernel = $app->make(Kernel::class);

        //

        $app->instance(Kernel::class, $kernel);
    })
    ->create();
```

## Próximos pasos

<Card title="Contenedor de servicios" icon="box" href="/es/service-container">
  Entiende cómo funciona el contenedor de servicios que utiliza internamente `ApplicationBuilder`.
</Card>

<Card title="FAQ de la nueva estructura de aplicación" icon="circle-question" href="/es/advanced/app-structure-faq">
  Reúne preguntas frecuentes y respuestas sobre la nueva estructura de aplicación.
</Card>


## Related topics

- [FAQ de la nueva estructura de aplicación de Laravel 11 y posteriores](/es/advanced/app-structure-faq.md)
- [Actualización de Laravel 10 a 11](/es/blog/upgrade-10-to-11.md)
- [Guía de migración de la estructura antigua a la nueva](/es/advanced/app-structure-migration.md)
- [Estructura de directorios](/es/directory-structure.md)
- [Tutorial - Laravel Console Starter](/es/packages/laravel-console-starter/tutorial.md)
