> ## 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 para Laravel

> Cubre el desarrollo de paquetes para Laravel centrado en los service providers, desde la publicación de configuración, vistas, migraciones y fachadas hasta la detección automática y la carga diferida.

## Qué es un paquete

En Laravel, un paquete es un paquete de Composer que añade funcionalidad a la aplicación. Hay dos tipos principales.

* **Paquete independiente** — biblioteca PHP de propósito general que no depende de Laravel (por ejemplo: Carbon, Pest).
* **Paquete de Laravel** — paquete que ofrece funciones integradas con Laravel, como rutas, controladores, vistas o configuración.

Esta guía trata el segundo caso, el desarrollo de paquetes específicos para Laravel. El desarrollo de paquetes requiere un conocimiento profundo de la estructura interna de Laravel, incluidos los service providers, las fachadas y la publicación de archivos de configuración.

<Info>
  Si vas a escribir pruebas para el paquete, utiliza [Orchestra Testbench](https://github.com/orchestral/testbench). Podrás escribir las pruebas del paquete igual que en una aplicación Laravel normal.
</Info>

## Detección automática de paquetes

Cuando instalas un paquete, Laravel lee la sección `extra.laravel` de `composer.json` y registra automáticamente los service providers y las fachadas.

```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` a mano.

<Info>
  En [Estructura interna de la detección automática de paquetes](/es/advanced/package-discovery) se explica cómo se implementa esta detección automática y cuándo se reconstruye la caché.
</Info>

### Desactivar la detección automática

Si el usuario quiere desactivar la detección automática de un paquete concreto, puede configurarlo en el `composer.json` de la aplicación.

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

## El papel del service provider

El service provider es el punto de entrada del paquete. Aquí es donde concentras el registro de recursos como vistas, configuración, migraciones o rutas en Laravel.

Un service provider extiende `Illuminate\Support\ServiceProvider` y dispone de dos métodos: `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 service container se realizan aquí
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

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

    /**
     * Inicializa los servicios del paquete
     */
    public function boot(): void
    {
        // El registro de recursos se realiza 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 listeners de eventos, rutas o vistas dentro del método `register`. Podrías utilizar por error servicios de otro service provider que aún no se ha cargado. Todo lo que no sean bindings debe hacerse en el método `boot`.
</Warning>

## Publicación de archivos de configuración

### publishes() — publicar archivos

Si llamas a `publishes()` dentro del método `boot`, el usuario podrá copiar el archivo de configuración a su aplicación mediante el comando `vendor:publish`.

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

Una vez publicado, puedes acceder a los valores de configuración con el mecanismo habitual de `config`.

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

### mergeConfigFrom() — fusionar con los valores por defecto

Si utilizas `mergeConfigFrom()` en el método `register`, los valores por defecto del paquete se aplicarán aunque el usuario no haya publicado el archivo de configuración.

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

<Warning>
  `mergeConfigFrom()` no fusiona los niveles más profundos de los arrays anidados. En configuraciones con arrays multidimensionales, es posible que si el usuario define solo una parte, el resto de las opciones no se fusionen.
</Warning>

### Separar grupos de publicación mediante etiquetas

Si pasas una etiqueta como segundo argumento a `publishes()`, el usuario podrá publicar 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}
# Publicar solo el archivo de configuración
php artisan vendor:publish --tag=courier-config

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

## Registro de rutas

Utiliza `loadRoutesFrom()` para cargar el archivo de rutas. Si la caché de rutas de la aplicación está activa, se omite automáticamente.

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

Dentro del archivo de rutas apuntas 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 publicar, Laravel actualiza automáticamente las marcas de tiempo.

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

## Publicación de vistas

### loadViewsFrom() — registrar vistas

Registra el directorio de vistas con `loadViewsFrom()`. El segundo argumento es el espacio de nombres, que se utiliza para referenciar las vistas con el formato `paquete::vista`.

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

Una vez registradas, las vistas se referencian con el espacio de nombres del paquete.

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

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

### 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 Blade

Si el paquete incluye componentes, regístralos en el método `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 por lotes usando un espacio de nombres de componentes.

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

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

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

{{-- Registro con espacio de nombres --}}
<x-courier::alert />
```

## Publicación de archivos de traducción

Registra los archivos de traducción con `loadTranslationsFrom()`. Las traducciones se referencian con el formato `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 utilizas archivos de traducción en JSON, usa `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()`. Lo habitual es registrarlos solo en el 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 integrarlo con `php artisan optimize` y `php artisan optimize:clear` mediante el método `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`, utiliza `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 fachadas

Las fachadas permiten invocar los bindings del service container como si fueran 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
        {
            // Procesamiento del envío del mensaje
            return true;
        }

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

  <Step title="Crea la clase de fachada">
    Extiende `Illuminate\Support\Facades\Facade` y devuelve la clave del binding del service container 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="Vincúlala 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ístrala en composer.json">
    ```json theme={null}
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    }
    ```
  </Step>
</Steps>

Al añadir anotaciones PHPDoc `@method` a los métodos de la fachada, se habilita el autocompletado del IDE.

```php theme={null}
// Invocar el servicio a través de la fachada
use Acme\Courier\Facades\Courier;

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

## DeferrableProvider — implementar la carga diferida

Los providers que solo realizan bindings al service container pueden implementar la interfaz `DeferrableProvider` para lograr una carga diferida. Como el provider no se carga hasta que un servicio se necesita realmente, 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 la lista de servicios que ofrece este provider
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
```

Laravel compila y almacena la lista de servicios que ofrecen los providers diferidos. El provider solo se carga cuando se resuelve alguno de los servicios enumerados en `provides()`.

<Warning>
  No utilices `DeferrableProvider` en providers que necesiten registrar recursos (vistas, rutas, listeners de eventos, etc.). Si se cargan de forma diferida, esos recursos podrían no quedar registrados.
</Warning>

## Pruebas del paquete

Para probar el paquete por separado, utiliza [Orchestra Testbench](https://github.com/orchestral/testbench). Podrás escribir las pruebas del paquete como si estuvieras dentro de una aplicación Laravel normal.

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

En el caso de prueba, sobreescribe `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 fachada 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 el paquete en [Packagist](https://packagist.org/).

**Configuración básica de `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` puedes incluir como dependencia únicamente los componentes de Laravel que necesites, en lugar de todo `illuminate/framework`. Mantén reducido el árbol de dependencias del paquete.
</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">
    Revisa los métodos `register` y `boot` de los service providers y los detalles de los providers diferidos.
  </Card>

  <Card title="Gestión de compatibilidad de versiones" icon="git-branch" href="/es/advanced/package-versioning">
    Explica la estrategia para adaptarse a los cambios de versión mayor de Laravel y PHP y la configuración de la matriz de pruebas en GitHub Actions.
  </Card>
</Columns>


## Related topics

- [Laravel Package Skeleton — Plantilla oficial para paquetes](/es/blog/package-skeleton-introduction.md)
- [CHANGELOG y gestión de releases de paquetes](/es/advanced/package-changelog.md)
- [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 Boost](/es/boost.md)
