# Doc: Symfony Console Commands Best Practices (Symfony 7.x)

> Relevancia Wari: 3 comandos — AlertasRevisarCommand, EventoRecordatorioCommand, CrearAdminCommand

## Estructura moderna con atributo

```php
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'app:evento:recordatorio',
    description: 'Envía recordatorios de eventos próximos.',
)]
class EventoRecordatorioCommand extends Command
{
    public function __construct(
        private EventoService $eventoService,
        private TelegramService $telegram,
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // lógica aquí
        $output->writeln('Recordatorios enviados.');
        return Command::SUCCESS;
    }
}
```

## Códigos de salida — siempre explícitos

```php
return Command::SUCCESS;  // 0 — todo bien
return Command::FAILURE;  // 1 — error de ejecución
return Command::INVALID;  // 2 — uso incorrecto (argumentos inválidos)
```

Nunca devolver `null` ni un entero literal.

## Argumentos y opciones

```php
protected function configure(): void
{
    $this
        ->addArgument('email', InputArgument::REQUIRED, 'Email del usuario')
        ->addOption('admin', null, InputOption::VALUE_NONE, 'Crear como admin');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $email  = $input->getArgument('email');
    $isAdmin = $input->getOption('admin');
    // ...
}
```

## Ciclo de vida del comando

1. `initialize()` — preparar variables, validar entorno.
2. `interact()` — pedir datos faltantes de forma interactiva.
3. `execute()` — lógica principal.

Solo sobreescribir `interact()` si el comando puede ejecutarse de forma interactiva (ej. `CrearAdminCommand`).

## Output estructurado

```php
$output->writeln('<info>Proceso iniciado</info>');
$output->writeln('<comment>Advertencia: sin eventos hoy</comment>');
$output->writeln('<error>Error al conectar con Telegram</error>');

// Progreso
$progressBar = new ProgressBar($output, count($eventos));
$progressBar->start();
foreach ($eventos as $evento) {
    // procesar
    $progressBar->advance();
}
$progressBar->finish();
```

## Testing de comandos

```php
class EventoRecordatorioCommandTest extends KernelTestCase
{
    public function testEnviaRecordatorios(): void
    {
        $kernel = self::bootKernel();
        $application = new Application($kernel);

        $command = $application->find('app:evento:recordatorio');
        $tester = new CommandTester($command);
        $tester->execute([]);

        $tester->assertCommandIsSuccessful();
        $this->assertStringContainsString('Recordatorios enviados', $tester->getDisplay());
    }
}
```

## Errores comunes

| Error | Corrección |
|---|---|
| Devolver `null` o no devolver nada | Siempre `return Command::SUCCESS/FAILURE/INVALID` |
| Lógica de negocio dentro del comando | Delegarla al servicio; el comando solo orquesta y muestra output |
| Sin descripción en `#[AsCommand]` | Definirla siempre — aparece en `php bin/console list` |
| Llamar a `parent::__construct()` en constructor | Obligatorio si el comando extiende `Command` |
| Sin manejo de excepciones | Envolver en try/catch y devolver `Command::FAILURE` |
