Testing Alpha
This commit is contained in:
113
src/Console/Commands/ApisReportCommand.php
Normal file
113
src/Console/Commands/ApisReportCommand.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyApisAndIntegrations\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Koneko\VuexyApisAndIntegrations\Application\Services\ExternalApiRegistryService;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Comando que muestra un resumen detallado de las APIs externas registradas.
|
||||
*
|
||||
* Permite filtrar, agrupar y exportar en diferentes formatos (tabla, JSON, CSV).
|
||||
*/
|
||||
class ApisReportCommand extends Command
|
||||
{
|
||||
protected $signature = 'apis:report
|
||||
{--format=table : Formato de salida (table, json, csv)}
|
||||
{--only-errors : Mostrar solo APIs con errores}
|
||||
{--group-by= : Agrupar por (module, provider, auth_type)}
|
||||
{--provider= : Filtrar por proveedor (ej. google)}';
|
||||
|
||||
protected $description = '📡 Genera un informe detallado de las APIs externas registradas en el ERP';
|
||||
|
||||
public function __construct(protected ExternalApiRegistryService $apis)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$entries = $this->apis->all();
|
||||
|
||||
if ($provider = $this->option('provider')) {
|
||||
$entries = $entries->where('provider', $provider);
|
||||
}
|
||||
|
||||
if ($this->option('only-errors')) {
|
||||
$entries = $entries->filter(fn($api) =>
|
||||
empty($api->auth_type) || empty($api->scopes)
|
||||
);
|
||||
}
|
||||
|
||||
if ($groupBy = $this->option('group-by')) {
|
||||
$entries = $entries->groupBy($groupBy)->map->values();
|
||||
}
|
||||
|
||||
if ($entries->isEmpty()) {
|
||||
$this->warn("⚠️ No se encontraron APIs registradas con los filtros aplicados.");
|
||||
return;
|
||||
}
|
||||
|
||||
match ($this->option('format')) {
|
||||
'json' => $this->outputJson($entries),
|
||||
'csv' => $this->outputCsv($entries),
|
||||
default => $this->outputTable($entries)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Muestra las APIs en formato tabla.
|
||||
*/
|
||||
protected function outputTable(Collection $apis): void
|
||||
{
|
||||
$table = new Table($this->output);
|
||||
$table->setHeaders(['Módulo', 'Proveedor', 'API', 'Auth', 'Scopes', 'Entorno']);
|
||||
|
||||
foreach ($apis as $entry) {
|
||||
$table->addRow([
|
||||
$entry->module,
|
||||
$entry->provider->value ?? '—',
|
||||
$entry->name,
|
||||
$entry->auth_type->value ?? '❌',
|
||||
$entry->scopes ? implode(', ', $entry->scopes) : '❌',
|
||||
$entry->environment->value ?? 'default',
|
||||
]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Muestra las APIs en formato JSON.
|
||||
*/
|
||||
protected function outputJson(Collection $apis): void
|
||||
{
|
||||
$this->line(
|
||||
json_encode($apis->map(fn($api) => $api->toArray())->values(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Muestra las APIs en formato CSV.
|
||||
*/
|
||||
protected function outputCsv(Collection $apis): void
|
||||
{
|
||||
$headers = ['module', 'provider', 'name', 'auth_type', 'scopes', 'environment'];
|
||||
$this->line(implode(',', $headers));
|
||||
|
||||
foreach ($apis as $api) {
|
||||
$this->line(implode(',', [
|
||||
$api->module,
|
||||
$api->provider->value ?? '-',
|
||||
$api->name,
|
||||
$api->auth_type->value ?? '-',
|
||||
is_array($api->scopes) ? implode('|', $api->scopes) : '-',
|
||||
$api->environment->value ?? '-',
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
66
src/Console/Commands/DownloadGeoIpDatabase.php
Normal file
66
src/Console/Commands/DownloadGeoIpDatabase.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class DownloadGeoIpDatabase extends Command
|
||||
{
|
||||
protected $signature = 'geoip:download
|
||||
{--license= : Tu licencia de MaxMind (opcional si ya está en .env)}
|
||||
{--path= : Ruta personalizada de descarga (por defecto: public/vendor/geoip)}';
|
||||
|
||||
protected $description = 'Descarga la base de datos GeoLite2-City.mmdb desde MaxMind.';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$licenseKey = $this->option('license') ?? env('MAXMIND_LICENSE_KEY');
|
||||
$downloadPath = base_path($this->option('path') ?? 'public/vendor/geoip');
|
||||
$fileName = 'GeoLite2-City.mmdb';
|
||||
|
||||
if (!$licenseKey) {
|
||||
$this->error('⚠️ No se proporcionó ninguna licencia de MaxMind. Usa --license= o configura MAXMIND_LICENSE_KEY en .env');
|
||||
return;
|
||||
}
|
||||
|
||||
$url = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key={$licenseKey}&suffix=tar.gz";
|
||||
|
||||
$this->info("⏬ Descargando GeoLite2-City.mmdb desde MaxMind...");
|
||||
$tmpPath = storage_path('app/geoip.tar.gz');
|
||||
|
||||
try {
|
||||
$response = Http::withOptions(['sink' => $tmpPath])->get($url);
|
||||
|
||||
if (! $response->ok()) {
|
||||
$this->error('❌ Falló la descarga. Verifica tu licencia o conexión.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info("✅ Descarga completada. Extrayendo archivo...");
|
||||
|
||||
$tar = new \PharData($tmpPath);
|
||||
$tar->decompress(); // crea .tar
|
||||
$untar = str_replace('.gz', '', $tmpPath);
|
||||
$archive = new \PharData($untar);
|
||||
$archive->extractTo(storage_path('app/geoip_extracted'), null, true);
|
||||
|
||||
$files = File::allFiles(storage_path('app/geoip_extracted'));
|
||||
$mmdb = collect($files)->first(fn($f) => str_ends_with($f->getFilename(), '.mmdb'));
|
||||
|
||||
if (! $mmdb) {
|
||||
$this->error('⚠️ No se encontró el archivo .mmdb en el paquete descargado.');
|
||||
return;
|
||||
}
|
||||
|
||||
File::ensureDirectoryExists($downloadPath);
|
||||
File::copy($mmdb->getRealPath(), "{$downloadPath}/{$fileName}");
|
||||
|
||||
$this->info("🎉 Base de datos copiada a {$downloadPath}/{$fileName}");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->error("🚨 Error inesperado: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
80
src/Console/Commands/KonekoCacheHelperCommand.php
Normal file
80
src/Console/Commands/KonekoCacheHelperCommand.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\Cache\KonekoCacheManager;
|
||||
|
||||
class KonekoCacheHelperCommand extends Command
|
||||
{
|
||||
protected $signature = 'koneko:cache
|
||||
{--component= : Componente (ej: admin, website)}
|
||||
{--group= : Grupo (ej: menu, html, avatar)}
|
||||
{--flush : Limpia el cache del grupo indicado}
|
||||
{--show : Muestra la información del cache manager}
|
||||
{--ttl : Muestra el TTL efectivo}
|
||||
{--driver : Muestra el driver actual}
|
||||
{--enabled : Muestra si el cache está habilitado}
|
||||
{--tags : Muestra las etiquetas asociadas (si el driver lo permite)}
|
||||
';
|
||||
|
||||
protected $description = 'Gestor de Cache Ecosistema Koneko: TTL, driver, flush, debug, enabled, etiquetas';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$component = $this->option('component') ?? 'admin';
|
||||
$group = $this->option('group') ?? 'cache';
|
||||
|
||||
$manager = new KonekoCacheManager($component, $group);
|
||||
|
||||
$title = "\n🧠 Koneko Cache Manager - [{$manager->path()}]";
|
||||
$this->info(str_repeat('-', strlen($title)));
|
||||
$this->info($title);
|
||||
$this->info(str_repeat('-', strlen($title)));
|
||||
|
||||
if ($this->option('flush')) {
|
||||
$manager->flush();
|
||||
$this->warn("\n🧹 Caché limpiada para '{$manager->path()}'");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($this->option('show')) {
|
||||
$info = $manager->info();
|
||||
$this->line("\n🔧 Componente : <info>{$info['component']}</info>");
|
||||
$this->line("🔸 Grupo : <info>{$info['group']}</info>");
|
||||
$this->line("📦 Driver : <info>{$info['driver']}</info>");
|
||||
$this->line("🕒 TTL : <info>{$info['ttl']} seg</info>");
|
||||
$this->line("🚦 Enabled : <info>" . ($info['enabled'] ? 'true' : 'false') . "</info>");
|
||||
$this->line("🐞 Debug : <info>" . ($info['debug'] ? 'true' : 'false') . "</info>");
|
||||
}
|
||||
|
||||
if ($this->option('enabled')) {
|
||||
$this->line("✅ Habilitado: <info>" . ($manager->enabled() ? 'true' : 'false') . "</info>");
|
||||
}
|
||||
|
||||
if ($this->option('ttl')) {
|
||||
$this->line("🕒 TTL efectivo: <info>{$manager->ttl()} seg</info>");
|
||||
}
|
||||
|
||||
if ($this->option('driver')) {
|
||||
$this->line("⚙️ Driver actual: <info>{$manager->driver()}</info>");
|
||||
}
|
||||
|
||||
if ($this->option('tags')) {
|
||||
$tags = $manager->tags();
|
||||
$this->line("🏷 Etiquetas: <info>" . implode(', ', $tags) . "</info>");
|
||||
}
|
||||
|
||||
if (! $this->option('show') &&
|
||||
! $this->option('ttl') &&
|
||||
! $this->option('enabled') &&
|
||||
! $this->option('driver') &&
|
||||
! $this->option('tags') &&
|
||||
! $this->option('flush')) {
|
||||
|
||||
$this->warn("⚠️ No se especificó ninguna acción. Usa --help para ver las opciones disponibles.");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
81
src/Console/Commands/Security/KonekoVaultKeyCommand.php
Normal file
81
src/Console/Commands/Security/KonekoVaultKeyCommand.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands\Security;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\Security\VaultKeyService;
|
||||
|
||||
class VaultKeyCommand extends Command
|
||||
{
|
||||
protected $signature = 'vault:key
|
||||
{action : generate|rotate|deactivate|list}
|
||||
{alias? : Alias de la clave}
|
||||
{--owner=default_project : Nombre del proyecto propietario}
|
||||
{--algorithm=AES-256-CBC : Algoritmo de encriptación}
|
||||
{--sensitive=1 : Marcar clave como sensible (1 o 0)}';
|
||||
|
||||
protected $description = 'Gestión de claves seguras en el Key Vault';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$service = app(VaultKeyService::class);
|
||||
$action = strtolower($this->argument('action'));
|
||||
$alias = $this->argument('alias');
|
||||
$owner = $this->option('owner');
|
||||
$algorithm = $this->option('algorithm');
|
||||
$isSensitive = (bool) $this->option('sensitive');
|
||||
|
||||
match ($action) {
|
||||
'generate' => $this->generateKey($service, $alias, $owner, $algorithm, $isSensitive),
|
||||
'rotate' => $this->rotateKey($service, $alias),
|
||||
'deactivate' => $this->deactivateKey($service, $alias),
|
||||
'list' => $this->listKeys($service),
|
||||
default => $this->error("Acción '{$action}' no válida. Usa: generate, rotate, deactivate, list."),
|
||||
};
|
||||
}
|
||||
|
||||
protected function generateKey(VaultKeyService $service, ?string $alias, string $owner, string $algorithm, bool $isSensitive): void
|
||||
{
|
||||
if (!$alias) {
|
||||
$this->error('El alias es obligatorio para generar una clave.');
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $service->generateKey($alias, $owner, $algorithm, $isSensitive);
|
||||
$this->info("✅ Clave '{$alias}' generada correctamente.");
|
||||
}
|
||||
|
||||
protected function rotateKey(VaultKeyService $service, ?string $alias): void
|
||||
{
|
||||
if (!$alias) {
|
||||
$this->error('El alias es obligatorio para rotar una clave.');
|
||||
return;
|
||||
}
|
||||
|
||||
$service->rotateKey($alias);
|
||||
$this->info("🔄 Clave '{$alias}' rotada correctamente.");
|
||||
}
|
||||
|
||||
protected function deactivateKey(VaultKeyService $service, ?string $alias): void
|
||||
{
|
||||
if (!$alias) {
|
||||
$this->error('El alias es obligatorio para desactivar una clave.');
|
||||
return;
|
||||
}
|
||||
|
||||
$service->deactivateKey($alias);
|
||||
$this->info("🚫 Clave '{$alias}' desactivada.");
|
||||
}
|
||||
|
||||
protected function listKeys(VaultKeyService $service): void
|
||||
{
|
||||
$keys = \Koneko\VuexyAdmin\Models\VaultKey::all(['alias', 'owner_project', 'algorithm', 'is_active', 'rotated_at']);
|
||||
|
||||
if ($keys->isEmpty()) {
|
||||
$this->info('📭 No hay claves registradas.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->table(['Alias', 'Proyecto', 'Algoritmo', 'Activa', 'Rotada en'], $keys->toArray());
|
||||
}
|
||||
}
|
36
src/Console/Commands/VuexyAvatarInitialsCommand.php
Normal file
36
src/Console/Commands/VuexyAvatarInitialsCommand.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarInitialsService;
|
||||
|
||||
#[AsCommand(name: 'avatars:clean-initial')]
|
||||
class VuexyAvatarInitialsCommand extends Command
|
||||
{
|
||||
protected $signature = 'avatars:clean-initial
|
||||
{--days=30 : Número de días antes de eliminar}
|
||||
{--dry : Simular la eliminación sin borrar nada}';
|
||||
|
||||
protected $description = 'Elimina avatares generados automáticamente en el directorio initial-avatars';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$days = (int) $this->option('days');
|
||||
$dry = $this->option('dry') ?? false;
|
||||
|
||||
$this->info("🔍 Escaneando avatares anteriores a $days días...");
|
||||
$deleted = app(AvatarInitialsService::class)->cleanupOldAvatars($days, $dry);
|
||||
|
||||
if ($dry) {
|
||||
$this->line("🧪 DRY RUN: Se encontraron {$deleted} archivos que serían eliminados.");
|
||||
} else {
|
||||
$this->info("🗑️ {$deleted} avatares eliminados.");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
26
src/Console/Commands/VuexyDeviceTokenPruneCommand.php
Normal file
26
src/Console/Commands/VuexyDeviceTokenPruneCommand.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Models\DeviceToken;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'vuexy:tokens:prune')]
|
||||
class VuexyDeviceTokenPruneCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:tokens:prune {--days=30}';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$threshold = now()->subDays((int) $this->option('days'));
|
||||
|
||||
$count = DeviceToken::where('last_used_at', '<', $threshold)
|
||||
->orWhere('is_active', false)
|
||||
->delete();
|
||||
|
||||
$this->info("🧹 $count tokens eliminados por inactividad.");
|
||||
}
|
||||
}
|
72
src/Console/Commands/VuexyListCatalogsCommand.php
Normal file
72
src/Console/Commands/VuexyListCatalogsCommand.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\Bootstrap\Extenders\Catalog\CatalogModuleRegistry;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
||||
class VuexyListCatalogsCommand extends Command
|
||||
{
|
||||
/** @var string */
|
||||
protected $signature = 'vuexy:catalogs:list {--details : Muestra metadatos de cada catálogo} {--component= : Filtra por un componente en particular}';
|
||||
|
||||
/** @var string */
|
||||
protected $description = 'Lista todos los catálogos registrados por componente en el sistema';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$filterComponent = $this->option('component');
|
||||
$components = CatalogModuleRegistry::all();
|
||||
|
||||
if (empty($components)) {
|
||||
$this->warn('No hay componentes registrados en el CatalogModuleRegistry.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
if ($filterComponent && $filterComponent !== $component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$service = CatalogModuleRegistry::get($component);
|
||||
|
||||
$this->info("\n📦 Componente: <fg=cyan>{$component}</>");
|
||||
|
||||
$catalogs = $service->catalogs();
|
||||
|
||||
if (empty($catalogs)) {
|
||||
$this->line(" └ No tiene catálogos registrados.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->option('details')) {
|
||||
$table = new Table($this->output);
|
||||
$table->setHeaders(['Catálogo', 'Tipo', 'Tabla/Enum', 'Llave', 'Label', 'Buscable', 'Filtros']);
|
||||
|
||||
foreach ($catalogs as $cat) {
|
||||
$meta = $service->getCatalogMeta($cat);
|
||||
$table->addRow([
|
||||
$cat,
|
||||
$meta['enum'] ? 'Enum' : 'DB',
|
||||
$meta['enum'] ?? ($meta['table'] ?? '-'),
|
||||
$meta['key'] ?? '-',
|
||||
$meta['label'] ?? '-',
|
||||
implode(', ', $meta['searchable'] ?? []),
|
||||
implode(', ', $meta['filters'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
} else {
|
||||
foreach ($catalogs as $cat) {
|
||||
$this->line(" └ 📚 $cat");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
209
src/Console/Commands/VuexyMenuBuildCommand.php
Normal file
209
src/Console/Commands/VuexyMenuBuildCommand.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuBuilderService;
|
||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuRegistry;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
/**
|
||||
* Comando Artisan para construir, cachear, analizar y validar el menú Vuexy.
|
||||
*/
|
||||
#[AsCommand(name: 'vuexy:menu:build')]
|
||||
class VuexyMenuBuildCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:menu:build
|
||||
{--fresh : Limpia el caché del menú}
|
||||
{--json : Mostrar menú como JSON}
|
||||
{--dump : Hacer dump de la estructura final}
|
||||
{--raw : Mostrar menú crudo desde módulos}
|
||||
{--validate : Valida el menú combinado}
|
||||
{--user= : ID o email del usuario}
|
||||
{--role= : ID o nombre del rol}
|
||||
{--visitor : Menú de visitante}
|
||||
{--summary : Mostrar resumen de claves}
|
||||
{--id-node= : Mostrar nodo por auto_id}';
|
||||
|
||||
protected $description = 'Construye, cachea, analiza y valida el menú principal de Vuexy';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$fresh = $this->option('fresh');
|
||||
$showJson = $this->option('json');
|
||||
$showDump = $this->option('dump');
|
||||
$showRaw = $this->option('raw');
|
||||
$validate = $this->option('validate');
|
||||
$summary = $this->option('summary');
|
||||
$userInput = $this->option('user');
|
||||
$roleInput = $this->option('role');
|
||||
$visitor = $this->option('visitor');
|
||||
$targetId = $this->option('id-node');
|
||||
|
||||
$user = null;
|
||||
|
||||
if (is_string($targetId) && is_numeric($targetId)) {
|
||||
$targetId = (int) $targetId;
|
||||
}
|
||||
|
||||
if ($visitor) {
|
||||
$this->info('Menú para visitante:');
|
||||
$menu = app(VuexyMenuBuilderService::class)->getForUser(null);
|
||||
$this->renderMenu($menu, $showJson, $showDump, $summary, $targetId);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($userInput) {
|
||||
$user = $this->resolveUser($userInput);
|
||||
if (!$user) {
|
||||
$this->error("Usuario no encontrado: $userInput");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($roleInput) {
|
||||
$user = $this->simulateUserWithRole($roleInput);
|
||||
if (!$user) {
|
||||
$this->error("Rol no encontrado: $roleInput");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fresh) {
|
||||
$user
|
||||
? VuexyMenuBuilderService::clearCacheForUser($user)
|
||||
: VuexyMenuBuilderService::clearAllCache();
|
||||
$this->info('Caché de menú limpiado.');
|
||||
}
|
||||
|
||||
if ($showRaw || $validate) {
|
||||
$raw = (new VuexyMenuRegistry())->getMerged();
|
||||
|
||||
if ($validate) {
|
||||
$this->info("Validando estructura de menú...");
|
||||
$this->validateMenu($raw);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->line("Menú crudo desde módulos:");
|
||||
$summary ? $this->summarizeMenu($raw) : $this->dumpOrJson($raw, $showJson, $showDump);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$user = Auth::user();
|
||||
if (!$user && !$fresh) {
|
||||
$this->error('No hay sesión activa. Usa --user o --role.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$menu = app(VuexyMenuBuilderService::class)->getForUser($user);
|
||||
$this->renderMenu($menu, $showJson, $showDump, $summary, $targetId);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function validateMenu(array $menu, string $prefix = ''): void
|
||||
{
|
||||
foreach ($menu as $key => $item) {
|
||||
if (!isset($item['_meta'])) {
|
||||
$this->error("[!] El item '$prefix$key' no contiene '_meta'");
|
||||
continue;
|
||||
}
|
||||
|
||||
$required = ['icon', 'description'];
|
||||
foreach ($required as $field) {
|
||||
if (!isset($item['_meta'][$field])) {
|
||||
$this->warn("[!] '$prefix$key' está incompleto: falta '$field'");
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($item['route']) && !\Illuminate\Support\Facades\Route::has($item['route'])) {
|
||||
$this->warn("[!] Ruta inexistente en '$prefix$key': {$item['route']}");
|
||||
}
|
||||
|
||||
if (isset($item['submenu'])) {
|
||||
$this->validateMenu($item['submenu'], $prefix . "$key/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function renderMenu(array $menu, bool $json, bool $dump, bool $summary, ?int $id = null): void
|
||||
{
|
||||
if ($id !== null) {
|
||||
$target = $this->findById($menu, $id);
|
||||
if (!$target) {
|
||||
$this->error("Nodo ID #$id no encontrado");
|
||||
return;
|
||||
}
|
||||
$this->line("Nodo con ID #$id:");
|
||||
$this->dumpOrJson($target, $json, $dump);
|
||||
return;
|
||||
}
|
||||
|
||||
$summary
|
||||
? $this->summarizeMenu($menu)
|
||||
: $this->dumpOrJson($menu, $json, $dump);
|
||||
}
|
||||
|
||||
protected function dumpOrJson(array $menu, bool $asJson, bool $asDump): void
|
||||
{
|
||||
if ($asJson) {
|
||||
$this->line(json_encode($menu, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
} elseif ($asDump) {
|
||||
dump($menu);
|
||||
} else {
|
||||
$this->warn('Agrega --json, --dump o --summary para mostrar el menú.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function summarizeMenu(array $menu, string $prefix = ''): void
|
||||
{
|
||||
foreach ($menu as $key => $item) {
|
||||
$id = $item['_meta']['auto_id'] ?? '---';
|
||||
$this->line("[#" . str_pad((string) $id, 3, ' ', STR_PAD_LEFT) . "] $prefix$key");
|
||||
if (isset($item['submenu'])) {
|
||||
$this->summarizeMenu($item['submenu'], $prefix . ' └ ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function findById(array $menu, int $id): ?array
|
||||
{
|
||||
foreach ($menu as $item) {
|
||||
if (($item['_meta']['auto_id'] ?? null) === $id) {
|
||||
return $item;
|
||||
}
|
||||
if (isset($item['submenu'])) {
|
||||
$found = $this->findById($item['submenu'], $id);
|
||||
if ($found) return $found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function resolveUser(int|string $user): ?User
|
||||
{
|
||||
return is_numeric($user)
|
||||
? User::find((int) $user)
|
||||
: User::where('email', (string) $user)->first();
|
||||
}
|
||||
|
||||
protected function simulateUserWithRole(string $roleName): ?User
|
||||
{
|
||||
$role = \Spatie\Permission\Models\Role::where('name', $roleName)->first();
|
||||
if (!$role) return null;
|
||||
|
||||
$user = new User();
|
||||
$user->id = 0;
|
||||
$user->email = 'simulado@vuexy.test';
|
||||
$user->name = '[Simulado: ' . $roleName . ']';
|
||||
$user->setRelation('roles', collect([$role]));
|
||||
$user->syncRoles($role);
|
||||
return $user;
|
||||
}
|
||||
}
|
94
src/Console/Commands/VuexyMenuListModulesCommand.php
Normal file
94
src/Console/Commands/VuexyMenuListModulesCommand.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\UX\Menu\VuexyMenuRegistry;
|
||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleRegistry;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'vuexy:menu:list-modules')]
|
||||
class VuexyMenuListModulesCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:menu:list-modules
|
||||
{--flat : Mostrar claves en formato plano}
|
||||
{--json : Mostrar salida como JSON}';
|
||||
|
||||
protected $description = 'Lista los archivos de menú registrados por cada módulo de Vuexy';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$registry = new VuexyMenuRegistry();
|
||||
$modules = KonekoModuleRegistry::enabled();
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($modules as $module) {
|
||||
$menuPath = $module->extensions['menu']['path'] ?? null;
|
||||
$basePath = $module->basePath;
|
||||
$name = $module->composerName;
|
||||
|
||||
if ($menuPath && file_exists($basePath . DIRECTORY_SEPARATOR . $menuPath)) {
|
||||
$fullPath = realpath($basePath . DIRECTORY_SEPARATOR . $menuPath);
|
||||
$menuData = require $fullPath;
|
||||
$keys = $this->extractKeys($menuData);
|
||||
|
||||
$results[$name] = [
|
||||
'file' => $fullPath,
|
||||
'count' => count($keys),
|
||||
'keys' => $keys,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->option('json')) {
|
||||
$this->line(json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
foreach ($results as $module => $data) {
|
||||
$this->info("📦 $module");
|
||||
$this->line(" 📁 Archivo: {$data['file']}");
|
||||
$this->line(" 🔑 Claves: {$data['count']}");
|
||||
|
||||
if ($this->option('flat')) {
|
||||
foreach ($data['keys'] as $key) {
|
||||
$this->line(" └ $key");
|
||||
}
|
||||
} else {
|
||||
$this->displayTree($data['keys']);
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function extractKeys(array $menu, string $prefix = ''): array
|
||||
{
|
||||
$keys = [];
|
||||
foreach ($menu as $key => $item) {
|
||||
$full = $prefix ? "$prefix / $key" : $key;
|
||||
$keys[] = $full;
|
||||
if (isset($item['submenus']) && is_array($item['submenus'])) {
|
||||
$keys = array_merge($keys, $this->extractKeys($item['submenus'], $full));
|
||||
}
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
protected function displayTree(array $keys): void
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
$depth = substr_count($key, '/');
|
||||
$indent = str_repeat(' ', $depth);
|
||||
$last = strrchr($key, '/');
|
||||
$label = $last !== false ? trim($last, '/') : $key;
|
||||
|
||||
$this->line(" {$indent}└ {$label}");
|
||||
}
|
||||
}
|
||||
}
|
37
src/Console/Commands/VuexyModuleInstallCommand.php
Normal file
37
src/Console/Commands/VuexyModuleInstallCommand.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
|
||||
class KonekoModuleInstallCommand extends Command
|
||||
{
|
||||
protected $description = 'Instala módulos desde Composer';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$package = $this->argument('package');
|
||||
|
||||
$this->info("Instalando paquete: {$package}...");
|
||||
|
||||
$process = new Process(['composer', 'require', $package]);
|
||||
$process->setTimeout(300);
|
||||
$process->run(function ($type, $buffer) {
|
||||
echo $buffer;
|
||||
});
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
return $this->error('La instalación falló.');
|
||||
}
|
||||
|
||||
$this->call('migrate');
|
||||
$this->call('vuexy:menu:build');
|
||||
|
||||
$this->info("Módulo instalado con éxito: {$package}");
|
||||
}
|
||||
}
|
89
src/Console/Commands/VuexyRbacCommand copy.php
Normal file
89
src/Console/Commands/VuexyRbacCommand copy.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleRegistry;
|
||||
use Koneko\VuexyAdmin\Application\System\RbacManagerService;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'vuexy:rbac')]
|
||||
class __VuexyRbacCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:rbac
|
||||
{--sync : Importa permisos y roles desde archivos}
|
||||
{--export : Exporta permisos y roles a archivos}
|
||||
{--publish : Publica los archivos de configuración de roles y permisos}
|
||||
{--module= : Especifica un módulo a procesar}
|
||||
{--roles-only : Limita la operación solo a roles}
|
||||
{--permissions-only : Limita la operación solo a permisos}
|
||||
{--overwrite : Sobrescribe roles existentes (solo en sync)}
|
||||
{--force : Fuerza publicación aunque ya existan los archivos}';
|
||||
|
||||
protected $description = '🔐 Sincroniza, exporta o publica los roles y permisos RBAC de Vuexy por módulo';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$moduleName = $this->option('module');
|
||||
$sync = $this->option('sync');
|
||||
$export = $this->option('export');
|
||||
$publish = $this->option('publish');
|
||||
$rolesOnly = $this->option('roles-only');
|
||||
$permissionsOnly = $this->option('permissions-only');
|
||||
$overwrite = $this->option('overwrite');
|
||||
$force = $this->option('force');
|
||||
|
||||
if (!($sync || $export || $publish)) {
|
||||
$this->error('Debes especificar al menos una opción: --sync, --export o --publish');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$modules = $moduleName
|
||||
? [KonekoModuleRegistry::get($moduleName)]
|
||||
: KonekoModuleRegistry::enabled();
|
||||
|
||||
foreach ($modules as $module) {
|
||||
if (!$module) {
|
||||
$this->warn("⚠️ Módulo no encontrado: {$moduleName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info("🎯 Procesando módulo: {$module->name}");
|
||||
|
||||
if ($sync) {
|
||||
if (!$rolesOnly) {
|
||||
$this->line(" 📥 Importando permisos...");
|
||||
RbacManagerService::importPermissions($module);
|
||||
}
|
||||
|
||||
if (!$permissionsOnly) {
|
||||
$this->line(" 🔐 Importando roles...");
|
||||
RbacManagerService::importRoles($module, $overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
if ($export) {
|
||||
if (!$rolesOnly) {
|
||||
$this->line(" 📤 Exportando permisos...");
|
||||
RbacManagerService::exportPermissions($module);
|
||||
}
|
||||
|
||||
if (!$permissionsOnly) {
|
||||
$this->line(" 🔐 Exportando roles...");
|
||||
RbacManagerService::exportRoles($module);
|
||||
}
|
||||
}
|
||||
|
||||
if ($publish) {
|
||||
// Puedes definir aquí lógica futura si decides generar archivos
|
||||
// en `base_path('database/data/vuexy-x/rbac.json')` en lugar de sobrescribir los originales
|
||||
$this->comment("🚧 Publish: aún no implementado. Usa export + config para lograrlo.");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('✅ Comando RBAC finalizado.');
|
||||
return 0;
|
||||
}
|
||||
}
|
78
src/Console/Commands/VuexyRbacCommand.php
Normal file
78
src/Console/Commands/VuexyRbacCommand.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleBootManager;
|
||||
use Koneko\VuexyAdmin\Application\Bootstrap\KonekoModuleRegistry;
|
||||
use Koneko\VuexyAdmin\Application\RBAC\KonekoRbacSyncManager;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'vuexy:rbac')]
|
||||
class VuexyRbacCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:rbac
|
||||
{--sync : Importa permisos y roles desde archivos}
|
||||
{--export : Exporta permisos y roles a archivos}
|
||||
{--publish : Publica los archivos de configuración de roles y permisos}
|
||||
{--module= : Especifica un módulo a procesar}
|
||||
{--roles-only : Limita la operación solo a roles}
|
||||
{--permissions-only : Limita la operación solo a permisos}
|
||||
{--overwrite : Sobrescribe roles existentes (solo en sync)}
|
||||
{--force : Fuerza publicación aunque ya existan los archivos}';
|
||||
|
||||
protected $description = 'Sincroniza, exporta o publica configuración de RBAC desde archivos JSON por módulo';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$module = $this->option('module');
|
||||
$sync = $this->option('sync');
|
||||
$export = $this->option('export');
|
||||
$publish = $this->option('publish');
|
||||
|
||||
if (!$sync && !$export && !$publish) {
|
||||
$this->error('Debes especificar al menos una acción (--sync, --export o --publish).');
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = $module
|
||||
? [KonekoModuleRegistry::get($module)]
|
||||
: KonekoModuleRegistry::enabled();
|
||||
|
||||
foreach ($modules as $mod) {
|
||||
if (!$mod) continue;
|
||||
|
||||
$this->info("\n [🎯 Procesando módulo: {$mod->name}]");
|
||||
|
||||
$basePath = KonekoModuleBootManager::resolvePath($mod, "database/data/rbac");
|
||||
File::ensureDirectoryExists($basePath);
|
||||
|
||||
$syncService = new KonekoRbacSyncManager($mod->name, $basePath);
|
||||
|
||||
if ($publish) {
|
||||
$syncService->publish($this->option('force'));
|
||||
$this->line("📂 Archivos publicados: {$basePath}");
|
||||
}
|
||||
|
||||
if ($sync) {
|
||||
$syncService->import(
|
||||
onlyPermissions: $this->option('permissions-only'),
|
||||
onlyRoles: $this->option('roles-only'),
|
||||
overwrite: $this->option('overwrite'),
|
||||
);
|
||||
$this->line(" ✅ Permisos y roles importados desde JSON");
|
||||
}
|
||||
|
||||
if ($export) {
|
||||
$syncService->export(
|
||||
onlyPermissions: $this->option('permissions-only'),
|
||||
onlyRoles: $this->option('roles-only')
|
||||
);
|
||||
$this->line("📤 Exportación completada a {$basePath}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("\n 🎉 Operación completada.");
|
||||
}
|
||||
}
|
196
src/Console/Commands/VuexySeedCommand.php
Normal file
196
src/Console/Commands/VuexySeedCommand.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Koneko\VuexyAdmin\Application\Seeding\SeederOrchestrator;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'vuexy:seed')]
|
||||
class VuexySeedCommand extends Command
|
||||
{
|
||||
protected $signature = 'vuexy:seed
|
||||
{--list : Mostrar lista de módulos disponibles}
|
||||
{--module=* : Módulos específicos a ejecutar (separados por comas)}
|
||||
{--class= : Ejecutar un seeder específico por su clase}
|
||||
{--file= : Sobrescribir archivo de datos}
|
||||
{--fake : Ejecutar en modo faker}
|
||||
{--fake-count= : Cantidad específica de registros falsos a generar}
|
||||
{--truncate : Vaciar tablas antes de sembrar}
|
||||
{--env= : Entorno de ejecución (local, demo, production)}
|
||||
{--report : Generar reporte detallado}
|
||||
{--dry-run : Simular sin ejecutar cambios}
|
||||
{--chunk-size= : Tamaño de chunk para procesamiento}';
|
||||
|
||||
protected $description = 'Orquestador avanzado de seeders para Koneko Vuexy ERP';
|
||||
|
||||
public function handle(SeederOrchestrator $orchestrator): int
|
||||
{
|
||||
$this->displayWelcomeMessage();
|
||||
|
||||
$orchestrator->setCommand($this);
|
||||
|
||||
if ($this->option('list')) {
|
||||
return $this->listAvailableModules();
|
||||
}
|
||||
|
||||
if ($this->option('class')) {
|
||||
return $this->runSpecificSeeder($this->option('class'));
|
||||
}
|
||||
|
||||
return $this->runFromModules($orchestrator);
|
||||
}
|
||||
|
||||
protected function displayWelcomeMessage(): void
|
||||
{
|
||||
$this->newLine();
|
||||
$this->line('🌱 <fg=magenta;options=bold>Koneko Vuexy ERP Seeder Orchestrator</>');
|
||||
$this->line('📦 Versión: <info>1.0</info> | Entorno: <info>'.($this->option('env') ?: 'auto').'</info>');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
protected function listAvailableModules(): int
|
||||
{
|
||||
$modules = config('seeder.modules', []);
|
||||
|
||||
$this->table(
|
||||
['Módulo', 'Seeder', 'Archivo', 'Faker', 'Descripción'],
|
||||
collect($modules)->map(function ($config, $key) {
|
||||
return [
|
||||
'<info>'.$key.'</info>',
|
||||
$config['seeder'] ?? '-',
|
||||
$config['file'] ?? 'Por defecto',
|
||||
isset($config['fake']) ? '✅' : '❌',
|
||||
$this->getModuleDescription($key)
|
||||
];
|
||||
})->toArray()
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function runSpecificSeeder(string $fqcn): int
|
||||
{
|
||||
$seederClass = $this->qualifySeederClass($fqcn);
|
||||
|
||||
if (!class_exists($seederClass)) {
|
||||
$this->error("❌ La clase especificada no existe: {$seederClass}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$seeder = app($seederClass);
|
||||
|
||||
if (method_exists($seeder, 'setCommand')) {
|
||||
$seeder->setCommand($this);
|
||||
}
|
||||
|
||||
if ($this->option('truncate') && method_exists($seeder, 'truncate')) {
|
||||
$this->info('🗑️ Truncando tabla...');
|
||||
$seeder->truncate();
|
||||
}
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->info("🔹 [DRY RUN] Simulación para: <comment>{$seederClass}</comment>");
|
||||
if ($this->option('file')) {
|
||||
$this->line("📄 Archivo: {$this->option('file')}");
|
||||
}
|
||||
if ($this->option('fake') || $this->option('fake-count')) {
|
||||
$count = $this->option('fake-count') ?: 'aleatorio';
|
||||
$this->line("👤 Faker: {$count} registros");
|
||||
}
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$config = [
|
||||
'file' => $this->option('file'),
|
||||
'fake' => $this->option('fake') || $this->option('fake-count'),
|
||||
'fake-count' => (int) $this->option('fake-count'),
|
||||
'truncate' => $this->option('truncate'),
|
||||
'dry-run' => $this->option('dry-run'),
|
||||
];
|
||||
|
||||
if ($this->option('file') && method_exists($seeder, 'setTargetFile')) {
|
||||
$seeder->setTargetFile($this->option('file'));
|
||||
}
|
||||
|
||||
$this->info("📦 Ejecutando seeder: <comment>{$seederClass}</comment>");
|
||||
|
||||
if ($config['fake'] && method_exists($seeder, 'runFake')) {
|
||||
$count = $config['fake-count'] ?: rand(1, 10);
|
||||
$seeder->runFake($count);
|
||||
$this->info("✅ Faker: {$count} registros generados");
|
||||
} else {
|
||||
$seeder->run($config);
|
||||
$this->info("✅ Seeder ejecutado correctamente");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function runFromModules(SeederOrchestrator $orchestrator): int
|
||||
{
|
||||
$modules = $this->option('module')
|
||||
? explode(',', implode(',', $this->option('module')))
|
||||
: null;
|
||||
|
||||
$options = [
|
||||
'env' => $this->option('env'),
|
||||
'file' => $this->option('file'),
|
||||
'fake' => $this->option('fake') || $this->option('fake-count'),
|
||||
'fake-count' => (int) $this->option('fake-count'),
|
||||
'truncate' => $this->option('truncate'),
|
||||
'dry-run' => $this->option('dry-run'),
|
||||
];
|
||||
|
||||
try {
|
||||
$orchestrator->run($modules, $options);
|
||||
|
||||
if ($this->option('report')) {
|
||||
$this->newLine(2);
|
||||
$this->line('📊 <options=underscore>Reporte generado en:</> ' . $orchestrator->getReportPath());
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
$this->error("❌ Error durante ejecución: {$e->getMessage()}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
protected function qualifySeederClass(string $class): string
|
||||
{
|
||||
if (str_starts_with($class, '\\')) {
|
||||
return ltrim($class, '\\');
|
||||
}
|
||||
|
||||
if (str_contains($class, '\\')) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
$namespaces = [
|
||||
'Koneko\\VuexyAdmin\\Database\\Seeders\\',
|
||||
'Koneko\\VuexyPos\\Database\\Seeders\\',
|
||||
'Koneko\\VuexySatCatalogs\\Database\\Seeders\\',
|
||||
'Database\\Seeders\\',
|
||||
];
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$fqcn = $namespace . $class;
|
||||
if (class_exists($fqcn)) return $fqcn;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
protected function getModuleDescription(string $module): string
|
||||
{
|
||||
return match ($module) {
|
||||
'settings' => 'Configuración global del sistema',
|
||||
'users' => 'Usuarios y permisos',
|
||||
'sat_catalogs' => 'Catálogos del SAT (México)',
|
||||
default => '--'
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user