Testing Alpha

This commit is contained in:
2025-05-11 14:14:50 -06:00
parent 988b86a33d
commit a7002701f5
1903 changed files with 77534 additions and 36485 deletions

View File

@ -0,0 +1,289 @@
<?php
declare(strict_types=1);
namespace Koneko\VuexyAdmin\Application\Seeding;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Koneko\VuexyAdmin\Application\Seeding\SeederReportBuilder;
use Koneko\VuexyAdmin\Application\Traits\Seeders\Main\HasSeederLogger;
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarImageService;
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarInitialsService;
use Koneko\VuexyAdmin\Support\Seeders\AbstractDataSeeder;
class SeederOrchestrator
{
use HasSeederLogger;
protected ?Command $command = null;
protected SeederReportBuilder $reporter;
public function __construct(?Command $command = null)
{
$this->command = $command;
$this->reporter = new SeederReportBuilder();
}
public function run(array|string|null $only = null, array $options = []): void
{
// Configuración del entorno
if (isset($options['env'])) {
config(['seeder.env' => $options['env']]);
}
// Determinar si se debe generar reporte
$shouldGenerateReport = $options['report'] ?? config('seeder.defaults.report', false);
// Ejecutar limpieza si está activada
$this->clearAssetsIfNeeded($options);
// Filtrar módulos y resolver dependencias
$modules = $this->filterModules(config('seeder.modules', []), $only);
$modules = $this->resolveDependencies($modules);
foreach ($modules as $key => $config) {
$this->log("Iniciando módulo: <info>{$key}</info>");
$mergedConfig = array_merge(config('seeder.defaults', []), $config, $options);
$this->processModule($key, $mergedConfig);
}
// Guardar reporte solo si está habilitado
if ($shouldGenerateReport) {
$this->reporter->saveReports();
}
}
protected function processModule(string $key, array $config): void
{
try {
$seederClass = $config['seeder'];
$seeder = app($seederClass);
// Inyectar comando si es posible
if (method_exists($seeder, 'setCommand')) {
$seeder->setCommand($this->command);
} elseif (property_exists($seeder, 'command')) {
$seeder->command = $this->command;
}
// Configuración general para seeders avanzados
if ($seeder instanceof AbstractDataSeeder) {
if ($this->command) {
$seeder->setCommand($this->command);
}
if (isset($config['file'])) {
$seeder->setTargetFile($config['file']);
}
if ($config['truncate'] ?? false) {
$this->log("🗑️ Truncando tabla para {$key}...");
$seeder->truncate();
}
}
// Modo simulación
if ($config['dry-run'] ?? false) {
$this->logDryRun($key, $config);
$this->reporter->addModule([
'name' => $key,
'status' => 'skipped',
'file' => $config['file'] ?? null,
'records' => null,
'time' => null,
'truncated' => $config['truncate'] ?? false,
'faker' => false,
'details' => 'Dry run',
]);
return;
}
// Ejecución real
$this->executeSeeder($seeder, $key, $config);
} catch (\Throwable $e) {
$this->log("❌ Error en módulo {$key}: {$e->getMessage()}");
$this->reporter->addModule([
'name' => $key,
'status' => 'failed',
'file' => $config['file'] ?? null,
'records' => null,
'time' => null,
'truncated' => $config['truncate'] ?? false,
'faker' => false,
'details' => $e->getMessage(),
]);
}
}
protected function executeSeeder($seeder, string $key, array $config): void
{
$records = null;
if ($config['transactional'] ?? false) {
$connection = $seeder->getModel()->getConnection();
$connection->transaction(function () use ($seeder, $key, $config, &$records) {
$records = $this->runSeederCore($seeder, $key, $config);
});
} else {
$records = $this->runSeederCore($seeder, $key, $config);
}
$this->reporter->addModule([
'name' => $key,
'status' => 'completed',
'records' => $records,
'time' => round(microtime(true) - LARAVEL_START, 2) . 's',
'truncated' => $config['truncate'] ?? false,
'faker' => isset($config['fake']),
'file' => $config['file'] ?? 'N/A',
'note' => 'OK',
]);
$this->log("✅ Módulo completado: <info>{$key}</info>\n");
}
protected function runSeederCore($seeder, string $key, array $config): int
{
$records = 0;
$runFake = $config['fake'] ?? false;
$runNormal = !$runFake || !($config['faker_only'] ?? false); // si no está en modo faker_only, corre run()
// Ejecutar inserción desde archivo solo si se permite
if ($runNormal && method_exists($seeder, 'run')) {
$seeder->run($config);
$records = method_exists($seeder, 'getProcessedCount') ? $seeder->getProcessedCount() : 0;
}
// Ejecutar faker si se permite
if ($runFake && method_exists($seeder, 'runFake')) {
$min = (int) ($config['fake-count'] ?? $config['fake']['min'] ?? 1);
$max = (int) ($config['fake-count'] ?? $config['fake']['max'] ?? $min);
$total = $min === $max ? $min : rand($min, $max);
$seeder->runFake($total, $config['fake']);
$records += method_exists($seeder, 'getProcessedCount') ? $seeder->getProcessedCount() : $total;
}
return $records;
}
protected function resolveDependencies(array $modules): array
{
$resolved = [];
$unresolved = $modules;
while (!empty($unresolved)) {
$resolvedCount = count($resolved);
foreach ($unresolved as $key => $module) {
$dependencies = $module['depends_on'] ?? [];
if (collect($dependencies)->every(fn($dep) => isset($resolved[$dep]))) {
$resolved[$key] = $module;
unset($unresolved[$key]);
}
}
if ($resolvedCount === count($resolved)) {
$keys = implode(', ', array_keys($unresolved));
throw new \RuntimeException("Dependencias circulares o no resueltas detectadas en: {$keys}");
}
}
return $resolved;
}
protected function filterModules(array $modules, $only): array
{
$filtered = [];
foreach ($modules as $key => $module) {
// 1. Si está deshabilitado, lo ignoramos completamente
if (!($module['enabled'] ?? true)) {
continue;
}
// 2. Si hay filtro por "only", respetarlo
if ($only !== null) {
$onlyList = is_array($only) ? $only : [$only];
if (!in_array($key, $onlyList)) {
continue;
}
}
$filtered[$key] = $module;
}
return $filtered;
}
protected function clearAssetsIfNeeded(array $options = []): void
{
if ($options['dry-run'] ?? false) {
$this->log("🔹 [DRY RUN] Simularía limpieza de assets");
return;
}
$config = config('seeder.clear_assets', []);
foreach ($config as $assetType => $enabled) {
if (!$enabled) continue;
match ($assetType) {
'avatars' => app(AvatarImageService::class)->clearAllProfilePhotos(),
'initials' => app(AvatarInitialsService::class)->clearAllInitials(),
'attachments' => Storage::disk('attachments')->deleteDirectory('all'),
default => null,
};
}
}
protected function logDryRun(string $key, array $config): void
{
$this->log("🔹 [DRY RUN] Simulación para módulo: <info>{$key}</info>");
$this->log("├─ Seeder: <comment>{$config['seeder']}</comment>");
if (isset($config['file'])) {
$this->log("├─ Archivo: <comment>{$config['file']}</comment>");
}
if (isset($config['fake'])) {
$count = $config['fake-count'] ?? ($config['fake']['max'] ?? 'aleatorio');
$this->log("├─ Datos falsos: <comment>{$count} registros</comment>");
}
if ($config['truncate'] ?? false) {
$this->log("└─ <fg=red>TRUNCATE activado</>");
}
}
public function getAvailableModules(): array
{
return config('seeder.modules', []);
}
public function getSeederClass(string $module): ?string
{
return config("seeder.modules.$module.seeder");
}
public function getReportPath(): string
{
return $this->reporter->getReportPathMarkdown();
}
public function getReport(): array
{
return $this->reporter->getReportData();
}
public function setCommand(Command $command): static
{
$this->command = $command;
return $this;
}
}

View File

@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Koneko\VuexyAdmin\Application\Seeding;
use Illuminate\Support\Facades\File;
class SeederReportBuilder
{
protected array $modules = [];
protected array $metadata = [];
/**
* Setea los metadatos globales del reporte.
*/
public function setMetadata(array $data): static
{
$this->metadata = $data;
return $this;
}
/**
* Agrega información de un módulo procesado.
*/
public function addModule(array $data): static
{
$this->modules[] = $data;
return $this;
}
/**
* Finaliza la metadata antes de guardar.
*/
public function finalize(): void
{
$this->metadata['executed_at'] = now()->toDateTimeString();
$this->metadata['duration'] = round(microtime(true) - LARAVEL_START, 2) . 's';
$this->metadata['status'] = collect($this->modules)->contains(fn($mod) => ($mod['status'] ?? '') === 'failed')
? '❌ With errors'
: '✅ Success';
}
/**
* Guarda el reporte Markdown.
*/
public function saveReports(): void
{
$this->finalize();
$this->saveMarkdown();
$this->saveJson();
}
/**
* Genera y guarda el reporte en Markdown.
*/
public function saveMarkdown(?string $path = null): string
{
$env = $this->metadata['env'] ?? 'local';
$timestamp = now()->format('Y-m-d-H-i-s');
$dir = database_path("seeders/reports/{$env}");
File::ensureDirectoryExists($dir);
$filePath = $path ?? "{$dir}/seed-report-{$env}-{$timestamp}.md";
File::put($filePath, $this->generateMarkdown());
return $filePath;
}
/**
* Genera y guarda el reporte en JSON.
*/
public function saveJson(?string $path = null): string
{
$env = $this->metadata['env'] ?? 'local';
$timestamp = now()->format('Y-m-d-H-i-s');
$dir = database_path("seeders/reports/{$env}");
File::ensureDirectoryExists($dir);
$filePath = $path ?? "{$dir}/seed-report-{$env}-{$timestamp}.json";
File::put($filePath, json_encode([
'metadata' => $this->metadata,
'modules' => $this->modules,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
return $filePath;
}
/**
* Genera la tabla Markdown completa.
*/
public function generateMarkdown(): string
{
$lines = [];
$lines[] = '# 📊 Koneko ERP - Seeders Report';
$lines[] = '';
$lines[] = '## 🛠️ Metadata';
$lines[] = '| Metadata | Value |';
$lines[] = '|-----------------|---------------------------|';
$lines[] = '| **Project** | ' . ($this->metadata['project'] ?? 'N/A') . ' |';
$lines[] = '| **Version** | ' . ($this->metadata['version'] ?? 'N/A') . ' |';
$lines[] = '| **Environment** | `' . ($this->metadata['env'] ?? 'N/A') . '` |';
$lines[] = '| **Executed at** | ' . ($this->metadata['executed_at'] ?? now()) . ' |';
$lines[] = '| **Duration** | ' . ($this->metadata['duration'] ?? 'N/A') . ' |';
$lines[] = '| **Status** | ' . ($this->metadata['status'] ?? 'N/A') . ' |';
$lines[] = '';
$lines[] = '## 📦 Modules Summary';
$lines[] = '';
$lines[] = '| Module | Status | Records | Time | Truncated | Faker | File | Note |';
$lines[] = '|-----------------|--------|---------|--------|-----------|-------|-------------|------|';
foreach ($this->modules as $mod) {
$lines[] = sprintf(
'| %-15s | %-6s | %-7s | %-6s | %-9s | %-5s | %-11s | %-4s |',
$mod['name'] ?? '-',
$mod['status'] ?? '-',
(string) ($mod['records'] ?? '-'),
(string) ($mod['time'] ?? '-'),
($mod['truncated'] ?? false) ? '✔️' : '✖️',
($mod['faker'] ?? false) ? '✔️' : '✖️',
$mod['file'] ?? 'N/A',
$mod['note'] ?? '-'
);
}
$lines[] = '';
$lines[] = '## 📂 Archivos utilizados';
$lines[] = '';
$files = collect($this->modules)->pluck('file')->filter()->unique();
foreach ($files as $file) {
$lines[] = '- `' . $file . '`';
}
$lines[] = '';
return implode("\n", $lines);
}
/**
* Ruta del reporte Markdown generado (opcional en runtime).
*/
public function getReportPathMarkdown(): string
{
$env = $this->metadata['env'] ?? 'local';
return database_path("seeders/reports/{$env}/seed-report-{$env}.md");
}
/**
* Obtiene los datos del reporte como array.
*/
public function getReportData(): array
{
return [
'metadata' => $this->metadata,
'modules' => $this->modules,
];
}
}