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

# Consola Artisan

> Explica cómo crear comandos CLI personalizados usando la consola Artisan de Laravel.

## Qué es Artisan

Artisan es la herramienta de interfaz de línea de comandos (CLI) que se incluye con Laravel.
Existe como script `artisan` en la raíz del proyecto y ofrece numerosos comandos que agilizan el desarrollo y la operación.

Para consultar la lista de comandos disponibles utiliza el comando `list`.

```shell theme={null}
php artisan list
```

Para ver cómo se usa un comando, antepón `help`.

```shell theme={null}
php artisan help migrate
```

<Info>
  Si utilizas Laravel Sail, usa `sail artisan` en lugar de `php artisan`.
  Los comandos se ejecutarán dentro del contenedor Docker.

  ```shell theme={null}
  ./vendor/bin/sail artisan list
  ```
</Info>

## Tinker

[Laravel Tinker](https://github.com/laravel/tinker) es un entorno REPL que permite manipular de forma interactiva desde la línea de comandos toda la aplicación: modelos Eloquent, jobs, eventos, etc.

```shell theme={null}
php artisan tinker
```

Una vez iniciado, puedes ejecutar código PHP directamente.

```php theme={null}
// Obtener y verificar un usuario
>>> App\Models\User::find(1)
// Crear un registro con una factory
>>> App\Models\User::factory()->create()
// Invocar directamente una clase de servicio
>>> app(App\Services\OrderService::class)->process(1)
```

<Tip>
  Tinker modifica los datos de verdad. Ten mucho cuidado al ejecutarlo en producción.
  Es adecuado para verificaciones y depuración en entornos local y staging.
</Tip>

## Creación de comandos personalizados

### Generar un comando

Con `make:command` generas la plantilla de una clase de comando.

```shell theme={null}
php artisan make:command ImportProducts
```

Se genera `app/Console/Commands/ImportProducts.php`.

### Estructura básica de un comando

La clase generada tiene tres elementos principales: `$signature`, `$description` y `handle()`.

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

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ImportProducts extends Command
{
    /**
     * Definición del nombre del comando, argumentos y opciones
     */
    protected $signature = 'import:products';

    /**
     * Descripción del comando (se muestra en php artisan list)
     */
    protected $description = 'Importa datos de productos desde CSV';

    /**
     * Procesamiento del comando
     */
    public function handle(): void
    {
        $this->info('Iniciando la importación...');
        // Describe aquí el procesamiento
        $this->info('Completado.');
    }
}
```

<Info>
  En Laravel 13 también se puede utilizar la forma con atributos PHP.

  ```php theme={null}
  use Illuminate\Console\Attributes\Description;
  use Illuminate\Console\Attributes\Signature;

  #[Signature('import:products')]
  #[Description('Importa datos de productos desde CSV')]
  class ImportProducts extends Command
  {
      public function handle(): void
      {
          // ...
      }
  }
  ```
</Info>

### Definir argumentos y opciones

En `$signature` se define, todo junto, el nombre del comando, los argumentos y las opciones.

```php theme={null}
protected $signature = 'import:products
                        {file : Ruta del CSV que se va a importar}
                        {--limit= : Número máximo de registros a importar}
                        {--dry-run : No guardar realmente en la BD (para comprobar)}';
```

| Sintaxis              | Tipo                            | Descripción                               |
| --------------------- | ------------------------------- | ----------------------------------------- |
| `{file}`              | Argumento obligatorio           | Si se omite, se produce un error          |
| `{file?}`             | Argumento opcional              | Si se omite, `null`                       |
| `{file=products.csv}` | Argumento con valor por defecto | Si se omite, usa el valor por defecto     |
| `{--queue}`           | Opción de tipo flag             | Con la opción es `true`, sin ella `false` |
| `{--limit=}`          | Opción con valor                | Se pasa como `--limit=100`                |
| `{--limit=50}`        | Opción con valor por defecto    | Si se omite, `50`                         |
| `{--Q\|queue}`        | Con atajo                       | También se puede especificar con `-Q`     |

### Obtener argumentos y opciones

Dentro del método `handle()` se obtienen los valores con `argument()` y `option()`.

```php theme={null}
public function handle(): void
{
    $file    = $this->argument('file');       // Obtener argumento
    $limit   = $this->option('limit');        // Obtener opción
    $dryRun  = $this->option('dry-run');      // Opción tipo flag (bool)

    $this->info("Archivo: {$file}");

    if ($dryRun) {
        $this->warn('Modo dry-run: se omite el guardado en la BD');
    }
}
```

### `input()` — accesores tipados

Con el método `input()` puedes obtener los argumentos y opciones del comando con los mismos accesores tipados que en una petición HTTP.

```php theme={null}
use App\Enums\ReportType;

public function handle(): void
{
    // Obtener argumentos/opciones como instancia de CommandInput
    $from = $this->input()->date('from');
    $type = $this->input()->enum('type', ReportType::class);
    $limit = $this->input()->integer('limit');
}
```

Si solo quieres obtener un nombre concreto, pásalo directamente (se busca tanto entre los argumentos como entre las opciones).

```php theme={null}
$queue = $this->input('queue', 'default');
```

<Tip>
  Mientras que `argument()` y `option()` devuelven cadenas, los accesores tipados de `input()` convierten al tipo adecuado. Es útil cuando quieres manejar fechas, números o enums de forma segura por tipo.
</Tip>

## Interacción con el usuario

### Salida de mensajes

Hay métodos para imprimir mensajes en color en la consola.

```php theme={null}
$this->info('El procesamiento se completó correctamente');    // verde
$this->warn('Esta operación no se puede deshacer');            // amarillo
$this->error('Se ha producido un error');                      // rojo
$this->line('Texto normal');                                   // sin color
$this->comment('Información complementaria');                  // gris claro
```

### Preguntar al usuario

Se puede recibir entrada del usuario de forma interactiva.

```php theme={null}
// Entrada de texto
$name = $this->ask('Introduce el nombre del responsable');

// Con valor por defecto
$env = $this->ask('Selecciona el entorno de ejecución', 'production');

// Información confidencial como contraseñas (la entrada no se muestra en pantalla)
$password = $this->secret('Introduce la clave de API');

// Confirmación sí/no
if (! $this->confirm('¿Importar en la BD de producción?')) {
    $this->info('Cancelado.');
    return;
}

// Elegir entre opciones
$format = $this->choice('Elige el formato de salida', ['csv', 'json', 'xml'], 0);
```

### Barra de progreso

Puedes mostrar el progreso de forma clara al procesar grandes cantidades de datos.

```php theme={null}
use App\Models\Product;

// Basta con pasar la colección; la barra de progreso se muestra automáticamente
$this->withProgressBar(Product::cursor(), function (Product $product) {
    $this->processProduct($product);
});
```

Si quieres controlarla en detalle, utiliza el siguiente método.

```php theme={null}
$total = Product::count();
$bar = $this->output->createProgressBar($total);
$bar->start();

Product::cursor()->each(function (Product $product) use ($bar) {
    $this->processProduct($product);
    $bar->advance();
});

$bar->finish();
$this->newLine();
```

## Casos de uso prácticos

### Comando de importación de datos

Ejemplo práctico de un comando que importa datos de productos desde un archivo CSV.

<Steps>
  <Step title="Generar el comando">
    ```shell theme={null}
    php artisan make:command ImportProducts
    ```
  </Step>

  <Step title="Implementar el comando">
    ```php theme={null}
    <?php

    namespace App\Console\Commands;

    use App\Models\Product;
    use Illuminate\Console\Command;

    class ImportProducts extends Command
    {
        protected $signature = 'import:products
                                {file : Ruta del archivo CSV}
                                {--limit= : Número máximo de registros a importar}
                                {--dry-run : Solo comprobar (no guardar en la BD)}';

        protected $description = 'Importa datos de productos desde un archivo CSV';

        public function handle(): void
        {
            $filePath = $this->argument('file');
            $limit    = $this->option('limit') ? (int) $this->option('limit') : null;
            $dryRun   = $this->option('dry-run');

            if (! file_exists($filePath)) {
                $this->error("No se encontró el archivo: {$filePath}");
                return;
            }

            // Lee el CSV con funciones nativas de PHP
            $handle = fopen($filePath, 'r');
            $headers = fgetcsv($handle); // Se toma la primera fila como cabecera
            $rows = [];
            while (($row = fgetcsv($handle)) !== false) {
                $rows[] = array_combine($headers, $row);
                if ($limit !== null && count($rows) >= $limit) {
                    break;
                }
            }
            fclose($handle);

            $count = 0;

            $this->withProgressBar($rows, function (array $row) use ($dryRun, &$count) {
                if (! $dryRun) {
                    Product::updateOrCreate(
                        ['sku' => $row['sku']],
                        [
                            'name'  => $row['name'],
                            'price' => $row['price'],
                        ]
                    );
                }
                $count++;
            });

            $this->newLine();

            if ($dryRun) {
                $this->warn("Hay {$count} registros candidatos (dry-run: guardado omitido)");
            } else {
                $this->info("Se han importado {$count} registros.");
            }
        }
    }
    ```
  </Step>

  <Step title="Ejecutar el comando">
    ```shell theme={null}
    # Importación normal
    php artisan import:products storage/products.csv

    # Limitar el número de registros
    php artisan import:products storage/products.csv --limit=100

    # Comprobar con dry-run
    php artisan import:products storage/products.csv --dry-run
    ```
  </Step>
</Steps>

### Comando de mantenimiento periódico

Ejemplo de un comando de mantenimiento periódico, como eliminar datos antiguos.

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

namespace App\Console\Commands;

use App\Models\Order;
use Illuminate\Console\Command;

class PruneOldOrders extends Command
{
    protected $signature = 'orders:prune {--days=90 : Antigüedad, en días, a partir de la cual se eliminan los datos}';

    protected $description = 'Elimina los pedidos completados anteriores al número de días indicado';

    public function handle(): void
    {
        $days = (int) $this->option('days');

        if (! $this->confirm("¿Eliminar los pedidos completados de hace más de {$days} días?")) {
            $this->info('Cancelado.');
            return;
        }

        $deleted = Order::where('status', 'completed')
            ->where('created_at', '<', now()->subDays($days))
            ->delete();

        $this->info("Se han eliminado {$deleted} pedidos.");
    }
}
```

### Combinación con la ejecución programada

Los comandos que crees se pueden ejecutar de forma programada en `routes/console.php`.
Para más detalles, consulta [Programación de tareas](/es/scheduling).

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

// Ejecutar cada día a las 2:00 de la madrugada
Schedule::command('orders:prune --days=90')->dailyAt('2:00');

// Ejecutar cada lunes a las 9:00
Schedule::command('import:products storage/weekly.csv')->weeklyOn(1, '9:00');
```

## Pruebas de comandos

Con las funcionalidades de testing de Laravel puedes verificar el comportamiento de los comandos Artisan.

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

namespace Tests\Feature\Commands;

use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PruneOldOrdersTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_deletes_old_completed_orders(): void
    {
        // Crear un pedido completado de hace más de 90 días
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(100),
        ]);

        // Pedido completado reciente (que no debería eliminarse)
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(10),
        ]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('¿Eliminar los pedidos completados de hace más de 90 días?', 'yes')
            ->expectsOutput('Se han eliminado 1 pedidos.')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }

    public function test_it_cancels_when_user_declines(): void
    {
        Order::factory()->create(['status' => 'completed', 'created_at' => now()->subDays(100)]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('¿Eliminar los pedidos completados de hace más de 90 días?', 'no')
            ->expectsOutput('Cancelado.')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }
}
```

<Info>
  Ejemplos de métodos de aserción disponibles con `artisan()`:

  | Método                              | Descripción                               |
  | ----------------------------------- | ----------------------------------------- |
  | `expectsOutput('...')`              | Verifica que se imprime el texto indicado |
  | `expectsQuestion('?', 'respuesta')` | Simula la entrada de `ask()`              |
  | `expectsConfirmation('?', 'yes')`   | Simula la respuesta de `confirm()`        |
  | `expectsChoice('?', 'opción')`      | Simula la selección de `choice()`         |
  | `assertExitCode(0)`                 | Verifica el código de salida (0 = éxito)  |
  | `assertFailed()`                    | Verifica que el código de salida no es 0  |
</Info>

## Comando `dev`

El comando Artisan `dev` arranca varios procesos necesarios para el desarrollo local en una única ventana de terminal. Por defecto se ejecutan en paralelo: el servidor de desarrollo de PHP, el worker de la cola, la monitorización de logs con [Pail](/es/logging) y la compilación de assets de Vite.

```shell theme={null}
php artisan dev
```

Internamente utiliza el paquete de npm `concurrently` para gestionar los procesos. A cada proceso se le asigna una etiqueta y un color, para que puedas distinguirlos fácilmente en la salida del terminal. Si alguno de los procesos falla, todos los demás también se detienen automáticamente.

Los procesos que se ejecutan por defecto son los siguientes.

| Nombre   | Comando                                          |
| -------- | ------------------------------------------------ |
| `server` | `php artisan serve --host=localhost`             |
| `queue`  | `php artisan queue:listen --tries=1 --timeout=0` |
| `logs`   | `php artisan pail --timeout=0`                   |
| `vite`   | `npm run dev`                                    |

<Info>
  El proceso `vite` detecta automáticamente el gestor de paquetes de Node que estés usando (npm, pnpm, Yarn, Bun) y utiliza el comando de ejecución adecuado.
</Info>

### Personalizar los procesos de `dev`

Puedes personalizar los procesos que ejecuta el comando `dev` mediante la clase `DevCommands`. Normalmente se registran dentro del método `boot` del `AppServiceProvider` de la aplicación. El método `register` recibe la cadena de comando y, opcionalmente, un nombre de proceso.

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

/**
 * Bootstrap de los servicios de la aplicación
 */
public function boot(): void
{
    DevCommands::register('some-command --flag', 'my-process');
}
```

Para registrar comandos Artisan puedes usar el método `artisan`, que antepone automáticamente `php artisan`.

```php theme={null}
DevCommands::artisan('horizon', 'horizon');
```

De la misma forma, el método `node` antepone el comando de ejecución del gestor de paquetes detectado (por ejemplo, `npm run`), y `nodeExec` antepone el comando exec del gestor de paquetes (por ejemplo, `npx`).

```php theme={null}
DevCommands::node('storybook', 'storybook');

DevCommands::nodeExec('tailwindcss -i resources/css/app.css -o public/css/app.css --watch', 'tailwind');
```

Si registras un proceso con el mismo nombre que uno de los procesos por defecto, lo reemplazarás. Por ejemplo, puedes personalizar el proceso `server` para que use otro puerto.

```php theme={null}
DevCommands::artisan('serve --host=localhost --port=9000', 'server');
```

También puedes personalizar el color de la etiqueta del proceso en el terminal. Los métodos de color disponibles son `blue`, `purple`, `pink`, `orange`, `green` y `yellow`. También puedes pasarle un color hex personalizado al método `color`.

```php theme={null}
DevCommands::register('my-command', 'my-process')->green();

DevCommands::register('my-command', 'my-process')->color('#ff6347');
```

Si quieres listar los procesos `dev` registrados sin arrancarlos, usa el comando `dev:list`.

```shell theme={null}
php artisan dev:list
```

### Filtrar los procesos de `dev`

Con el método `only` puedes indicar que al ejecutar el comando `dev` solo arranquen determinados procesos. De la misma manera, con `except` puedes excluir procesos concretos.

```php theme={null}
// Arrancar solo los procesos server y vite...
DevCommands::only('server', 'vite');

// Arrancar todos los procesos excepto el worker de la cola...
DevCommands::except('queue');
```

<Tip>
  Si en el desarrollo local también usas Horizon o Reverb, regístralos con `DevCommands::artisan()` y podrás arrancarlo todo con un único `php artisan dev`.
</Tip>

## Resumen de comandos habituales

```shell theme={null}
# Crear un comando nuevo
php artisan make:command CommandName

# Listar los comandos disponibles
php artisan list

# Ver cómo se usa un comando
php artisan help command:name

# Iniciar Tinker
php artisan tinker
```


## Related topics

- [Laravel Prompts](/es/prompts.md)
- [Procesos](/es/processes.md)
- [Pruebas de consola](/es/console-tests.md)
- [Ciclo de vida de la solicitud](/es/lifecycle.md)
- [Pruebas de navegador (Dusk)](/es/dusk.md)
