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

# Desarrollo de paquetes Laravel

> Cubre el desarrollo de paquetes Laravel centrado en los service providers: publicación de configuración, vistas, migraciones y facades, autodiscovery y carga diferida.

## Qué es un paquete

Un paquete en Laravel es un paquete de Composer que añade funcionalidad a tu aplicación. Existen dos grandes tipos.

* **Paquetes stand-alone** — bibliotecas PHP genéricas que no dependen de Laravel (por ejemplo, Carbon, Pest).
* **Paquetes Laravel** — paquetes con funcionalidad integrada con Laravel: rutas, controladores, vistas, configuración, etc.

Esta guía trata el segundo caso: paquetes específicos para Laravel. El desarrollo de paquetes requiere entender a fondo la estructura interna de Laravel: service providers, facades, publicación de archivos de configuración, etc.

<Info>
  Para escribir tests de tu paquete, usa [Orchestra Testbench](https://github.com/orchestral/testbench). Podrás escribir los tests como si estuvieras dentro de una aplicación Laravel normal.
</Info>

## Autodiscovery de paquetes

Al instalar un paquete, Laravel lee la sección `extra.laravel` de `composer.json` y registra automáticamente el service provider y los facades.

```json theme={null}
"extra": {
    "laravel": {
        "providers": [
            "Acme\\Courier\\CourierServiceProvider"
        ],
        "aliases": {
            "Courier": "Acme\\Courier\\Facades\\Courier"
        }
    }
}
```

Con esta configuración, el paquete se carga automáticamente sin que el usuario tenga que editar `bootstrap/providers.php`.

### Desactivar el autodiscovery

Si el usuario quiere desactivar el autodiscovery de un paquete concreto, lo configura en el `composer.json` de la aplicación.

```json theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

## Rol del service provider

El service provider es el punto de entrada del paquete. En él centralizas el registro en Laravel de vistas, configuración, migraciones, rutas y demás recursos.

Un service provider extiende `Illuminate\Support\ServiceProvider` y tiene dos métodos principales: `register` y `boot`.

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    /**
     * Registra los servicios del paquete
     */
    public function register(): void
    {
        // Los bindings al contenedor van aquí
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Bootea los servicios del paquete
     */
    public function boot(): void
    {
        // Los registros de recursos van aquí
        $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
        $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

        $this->publishesMigrations([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ]);

        $this->publishes([
            __DIR__.'/../config/courier.php' => config_path('courier.php'),
        ], 'courier-config');

        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
        ], 'courier-views');
    }
}
```

<Warning>
  No registres event listeners, rutas o vistas dentro del método `register`. Podrías acabar utilizando por error servicios de otro provider aún no cargado. Todo lo que no sean bindings debe hacerse en `boot`.
</Warning>

## Publicación del archivo de configuración

### publishes() — publicar archivos

Al llamar a `publishes()` desde `boot`, el usuario puede copiar el archivo de configuración a su aplicación con el comando `vendor:publish`.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}
```

Los valores publicados se leen igual que cualquier otra configuración.

```php theme={null}
$value = config('courier.option');
```

### mergeConfigFrom() — mezclar con valores por defecto

Con `mergeConfigFrom()` en `register`, se aplican los valores por defecto del paquete aunque el usuario no haya publicado el archivo.

```php theme={null}
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
```

<Warning>
  `mergeConfigFrom()` no mezcla en profundidad los arrays anidados. En configuraciones con arrays multinivel, si el usuario define solo parte de las opciones, el resto puede no mezclarse.
</Warning>

### Separar grupos de publicación con tags

Con el segundo argumento de `publishes()` puedes indicar una etiqueta para que el usuario publique solo los recursos que necesite.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ], 'courier-config');

    $this->publishesMigrations([
        __DIR__.'/../database/migrations/' => database_path('migrations'),
    ], 'courier-migrations');
}
```

```shell theme={null}
# Publica solo el archivo de configuración
php artisan vendor:publish --tag=courier-config

# Publica todos los archivos que ofrece el provider
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"
```

## Registro de rutas

Con `loadRoutesFrom()` cargas el archivo de rutas. Si la caché de rutas de la aplicación está activa, se salta automáticamente.

```php theme={null}
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
```

En el archivo de rutas, apunta a los controladores del paquete.

```php theme={null}
// routes/web.php
use Acme\Courier\Http\Controllers\TrackingController;
use Illuminate\Support\Facades\Route;

Route::prefix('courier')->group(function () {
    Route::get('/track/{id}', [TrackingController::class, 'show'])
        ->name('courier.track');
});
```

## Publicación de migraciones

Con `publishesMigrations()` puedes publicar los archivos de migración. Al publicarlos, Laravel actualiza automáticamente el timestamp.

```php theme={null}
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

## Publicación de vistas

### loadViewsFrom() — registrar vistas

Con `loadViewsFrom()` registras un directorio de vistas. El segundo argumento es el namespace con el que referencias las vistas usando `paquete::vista`.

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
```

Tras el registro, referencia las vistas con el namespace del paquete.

```php theme={null}
Route::get('/dashboard', function () {
    return view('courier::dashboard');
});
```

Laravel busca las vistas en dos ubicaciones. Primero mira `resources/views/vendor/courier` de la aplicación y, si no existe, usa el directorio de vistas del paquete. Esto permite al usuario personalizarlas.

### Publicar las vistas

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

    $this->publishes([
        __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
    ], 'courier-views');
}
```

### Registrar componentes de Blade

Si el paquete incluye componentes, regístralos en `boot`.

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Acme\Courier\View\Components\AlertComponent;

public function boot(): void
{
    Blade::component('courier-alert', AlertComponent::class);
}
```

También puedes registrarlos en bloque usando un namespace de componente.

```php theme={null}
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::componentNamespace('Acme\\Courier\\View\\Components', 'courier');
}
```

```blade theme={null}
{{-- Con registro individual --}}
<x-courier-alert />

{{-- Con registro por namespace --}}
<x-courier::alert />
```

## Publicación de traducciones

Con `loadTranslationsFrom()` registras los archivos de traducción. Se referencian como `paquete::archivo.clave`.

```php theme={null}
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

    $this->publishes([
        __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
    ]);
}
```

```php theme={null}
// Uso de la traducción
echo trans('courier::messages.welcome');
```

Si usas archivos de traducción en JSON, utiliza `loadJsonTranslationsFrom()`.

```php theme={null}
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
```

## Registro de comandos

Los comandos Artisan del paquete se registran con el método `commands()`. Es habitual registrarlos solo en entorno de consola.

```php theme={null}
use Acme\Courier\Console\Commands\InstallCommand;
use Acme\Courier\Console\Commands\SyncCommand;

public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            SyncCommand::class,
        ]);
    }
}
```

### Integración con el comando optimize

Si el paquete tiene su propia caché, puedes integrarla con `php artisan optimize` y `php artisan optimize:clear` mediante `optimizes()`.

```php theme={null}
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'courier:cache',
            clear: 'courier:clear-cache',
        );
    }
}
```

### Añadir información al comando `about`

Para añadir información del paquete a la salida de `php artisan about`, usa `AboutCommand::add()`.

```php theme={null}
use Illuminate\Foundation\Console\AboutCommand;

public function boot(): void
{
    AboutCommand::add('Courier Package', fn () => ['Version' => '1.0.0']);
}
```

## Creación de una facade

Los facades permiten invocar bindings del contenedor como métodos estáticos.

<Steps>
  <Step title="Crea la clase de servicio">
    ```php theme={null}
    <?php

    namespace Acme\Courier;

    class CourierManager
    {
        public function __construct(
            protected array $config,
        ) {}

        public function send(string $to, string $message): bool
        {
            // Envío del mensaje
            return true;
        }

        public function track(string $id): array
        {
            // Obtiene la información de seguimiento
            return ['status' => 'delivered'];
        }
    }
    ```
  </Step>

  <Step title="Crea la clase facade">
    Extiende `Illuminate\Support\Facades\Facade` y devuelve la clave del binding del contenedor en `getFacadeAccessor()`.

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

    namespace Acme\Courier\Facades;

    use Illuminate\Support\Facades\Facade;

    /**
     * @method static bool send(string $to, string $message)
     * @method static array track(string $id)
     *
     * @see \Acme\Courier\CourierManager
     */
    class Courier extends Facade
    {
        protected static function getFacadeAccessor(): string
        {
            return \Acme\Courier\CourierManager::class;
        }
    }
    ```
  </Step>

  <Step title="Enlaza en el service provider">
    ```php theme={null}
    public function register(): void
    {
        $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) {
            return new \Acme\Courier\CourierManager($app['config']['courier']);
        });
    }
    ```
  </Step>

  <Step title="Regístralo en composer.json">
    ```json theme={null}
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    }
    ```
  </Step>
</Steps>

Añadiendo anotaciones `@method` en el docblock de la facade activas el autocompletado en el IDE.

```php theme={null}
// Invocación a través de la facade
use Acme\Courier\Facades\Courier;

Courier::send('user@example.com', 'Ha llegado tu paquete');
$status = Courier::track('ABC-123');
```

## DeferrableProvider — carga diferida

Los providers que solo hacen bindings al contenedor pueden implementar la interfaz `DeferrableProvider` para cargarse de forma diferida. El provider no se carga hasta que el servicio se necesita realmente, lo que mejora el rendimiento de la aplicación.

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

namespace Acme\Courier;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Devuelve los servicios que provee este provider
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
```

Laravel compila y almacena la lista de servicios que proveen los providers diferidos. El provider solo se carga cuando se resuelve algún servicio de `provides()`.

<Warning>
  No uses `DeferrableProvider` en providers que necesitan registrar recursos (vistas, rutas, event listeners, etc.). Si se cargan de forma diferida, esos recursos quedarán sin registrar.
</Warning>

## Pruebas del paquete

Para testear el paquete de forma aislada, usa [Orchestra Testbench](https://github.com/orchestral/testbench). Podrás escribir los tests como si estuvieras dentro de una app Laravel normal.

```shell theme={null}
composer require --dev orchestra/testbench
```

En tu test case, sobrescribe `getPackageProviders()` para registrar el service provider del paquete.

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

namespace Acme\Courier\Tests;

use Acme\Courier\CourierServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{
    /**
     * Registra los service providers del paquete
     */
    protected function getPackageProviders($app): array
    {
        return [
            CourierServiceProvider::class,
        ];
    }

    /**
     * Registra los alias de facade del paquete
     */
    protected function getPackageAliases($app): array
    {
        return [
            'Courier' => \Acme\Courier\Facades\Courier::class,
        ];
    }

    /**
     * Configuración del entorno de pruebas
     */
    protected function defineEnvironment($app): void
    {
        $app['config']->set('courier.api_key', 'test-key');
    }
}
```

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

namespace Acme\Courier\Tests\Feature;

use Acme\Courier\Facades\Courier;
use Acme\Courier\Tests\TestCase;

class CourierTest extends TestCase
{
    public function test_can_send_message(): void
    {
        $result = Courier::send('user@example.com', 'Mensaje de prueba');

        $this->assertTrue($result);
    }
}
```

## Publicación en Composer

Buenas prácticas para publicar tu paquete en [Packagist](https://packagist.org/).

**Configuración básica del `composer.json`**

```json theme={null}
{
    "name": "acme/courier",
    "description": "A Laravel courier package",
    "type": "library",
    "license": "MIT",
    "require": {
        "php": "^8.2",
        "illuminate/support": "^11.0||^12.0||^13.0"
    },
    "require-dev": {
        "orchestra/testbench": "^9.0||^10.0",
        "phpunit/phpunit": "^11.0"
    },
    "autoload": {
        "psr-4": {
            "Acme\\Courier\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Acme\\Courier\\Tests\\": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}
```

<Tip>
  Al depender de `illuminate/support`, incluyes solo los componentes de Laravel que necesita el paquete en lugar de `illuminate/framework` completo. Mantén pequeño el árbol de dependencias.
</Tip>

**Ejemplo de estructura de directorios**

```
acme/courier/
├── config/
│   └── courier.php
├── database/
│   └── migrations/
│       └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│   └── es/
│       └── messages.php
├── resources/
│   └── views/
│       └── dashboard.blade.php
├── routes/
│   └── web.php
├── src/
│   ├── Console/
│   │   └── Commands/
│   │       └── InstallCommand.php
│   ├── Facades/
│   │   └── Courier.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── TrackingController.php
│   ├── CourierManager.php
│   └── CourierServiceProvider.php
├── tests/
│   ├── Feature/
│   └── TestCase.php
├── composer.json
└── README.md
```

## Páginas relacionadas

<Columns cols={2}>
  <Card title="Service providers" icon="plug" href="/es/service-providers">
    Detalles sobre los métodos `register` y `boot` de los service providers y los providers diferidos.
  </Card>

  <Card title="Gestión de compatibilidad de versiones" icon="git-branch" href="/es/advanced/package-versioning">
    Estrategia para reaccionar ante actualizaciones mayores de Laravel y PHP, y configuración de la matriz de pruebas en GitHub Actions.
  </Card>
</Columns>


## Related topics

- [Laravel Agent Detector — Paquete de detección de agentes de IA](/es/blog/agent-detector-introduction.md)
- [Guía de desarrollo de apps con integración de la Engine API - VOICEVOX for Laravel](/es/packages/laravel-voicevox/app-guide.md)
- [Laravel Maestro — Orquestador de desarrollo de starter kits](/es/blog/maestro-introduction.md)
- [Gestión de la compatibilidad de versiones de paquetes](/es/advanced/package-versioning.md)
- [CHANGELOG y gestión de releases de paquetes](/es/advanced/package-changelog.md)
