Migración completa a Clean Architecture con componentes funcionales

- Reorganización de estructura: Admin/, Public/, Shared/, Schemas/
- 12 componentes migrados: TopNotificationBar, Navbar, CtaLetsTalk, Hero,
  FeaturedImage, TableOfContents, CtaBoxSidebar, SocialShare, CtaPost,
  RelatedPost, ContactForm, Footer
- Panel de administración con tabs Bootstrap 5 funcionales
- Schemas JSON para configuración de componentes
- Renderers dinámicos con CSSGeneratorService (cero CSS hardcodeado)
- FormBuilders para UI admin con Design System consistente
- Fix: Bootstrap JS cargado en header para tabs funcionales
- Fix: buildTextInput maneja valores mixed (bool/string)
- Eliminación de estructura legacy (src/, admin/, assets/css/componente-*)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-11-25 21:20:06 -06:00
parent 90de6df77c
commit 0846a3bf03
224 changed files with 21670 additions and 17816 deletions

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Application\UseCases\SyncSchema;
/**
* SyncSchemaRequest - DTO de entrada para sincronizar schemas
*
* RESPONSABILIDAD: Encapsular ruta del archivo JSON de schemas
*
* USO:
* ```php
* $request = new SyncSchemaRequest('/path/to/schemas.json');
* ```
*
* @package ROITheme\Shared\Application\UseCases\SyncSchema
*/
final readonly class SyncSchemaRequest
{
/**
* @param string $schemaFilePath Ruta al archivo JSON de schemas
*/
public function __construct(
private string $schemaFilePath
) {}
/**
* Obtener ruta del archivo de schemas
*
* @return string
*/
public function getSchemaFilePath(): string
{
return $this->schemaFilePath;
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Application\UseCases\SyncSchema;
/**
* SyncSchemaResponse - DTO de salida para sincronizar schemas
*
* RESPONSABILIDAD: Encapsular resultado de sincronización
*
* DATOS INCLUIDOS:
* - success: bool
* - componentsAdded: array de nombres agregados
* - componentsUpdated: array de nombres actualizados
* - componentsDeleted: array de nombres eliminados
* - errors: array de errores
*
* @package ROITheme\Shared\Application\UseCases\SyncSchema
*/
final readonly class SyncSchemaResponse
{
/**
* @param bool $success Indica si la sincronización fue exitosa
* @param array $componentsAdded Componentes agregados
* @param array $componentsUpdated Componentes actualizados
* @param array $componentsDeleted Componentes eliminados
* @param array $errors Errores ocurridos
*/
private function __construct(
private bool $success,
private array $componentsAdded,
private array $componentsUpdated,
private array $componentsDeleted,
private array $errors
) {}
public function isSuccess(): bool
{
return $this->success;
}
public function getComponentsAdded(): array
{
return $this->componentsAdded;
}
public function getComponentsUpdated(): array
{
return $this->componentsUpdated;
}
public function getComponentsDeleted(): array
{
return $this->componentsDeleted;
}
public function getErrors(): array
{
return $this->errors;
}
/**
* Factory method: Sincronización exitosa
*/
public static function success(
array $added,
array $updated,
array $deleted
): self {
return new self(true, $added, $updated, $deleted, []);
}
/**
* Factory method: Sincronización con errores
*/
public static function failure(array $errors): self
{
return new self(false, [], [], [], $errors);
}
/**
* Convertir a array
*/
public function toArray(): array
{
return [
'success' => $this->success,
'components_added' => $this->componentsAdded,
'components_updated' => $this->componentsUpdated,
'components_deleted' => $this->componentsDeleted,
'errors' => $this->errors
];
}
/**
* Obtener resumen de cambios
*/
public function getSummary(): string
{
$added = count($this->componentsAdded);
$updated = count($this->componentsUpdated);
$deleted = count($this->componentsDeleted);
return sprintf(
'Added: %d, Updated: %d, Deleted: %d',
$added,
$updated,
$deleted
);
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Application\UseCases\SyncSchema;
use ROITheme\Shared\Domain\Contracts\ComponentRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\DefaultRepositoryInterface;
use ROITheme\Shared\Domain\ValueObjects\ComponentName;
use ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration;
/**
* SyncSchemaUseCase - Sincronizar schemas JSON → BD
*
* RESPONSABILIDAD: Orquestar sincronización de componentes desde archivo JSON
*
* FLUJO:
* 1. Leer archivo JSON
* 2. Validar estructura
* 3. Comparar con BD (agregar/actualizar/eliminar)
* 4. Persistir cambios
* 5. Retornar resumen
*
* @package ROITheme\Shared\Application\UseCases\SyncSchema
*/
final class SyncSchemaUseCase
{
public function __construct(
private ComponentRepositoryInterface $componentRepository,
private DefaultRepositoryInterface $defaultsRepository
) {}
/**
* Ejecutar sincronización
*/
public function execute(SyncSchemaRequest $request): SyncSchemaResponse
{
try {
// 1. Leer y parsear JSON
$schemas = $this->readSchemas($request->getSchemaFilePath());
if (empty($schemas)) {
return SyncSchemaResponse::failure(['No schemas found in file']);
}
// 2. Obtener componentes actuales de BD
$currentComponents = $this->componentRepository->findAll();
$currentNames = array_map(
fn($c) => $c->name()->value(),
$currentComponents
);
$schemaNames = array_keys($schemas);
// 3. Determinar cambios
$toAdd = array_diff($schemaNames, $currentNames);
$toUpdate = array_intersect($schemaNames, $currentNames);
$toDelete = array_diff($currentNames, $schemaNames);
// 4. Aplicar cambios
$added = $this->addComponents($toAdd, $schemas);
$updated = $this->updateComponents($toUpdate, $schemas);
$deleted = $this->deleteComponents($toDelete);
// 5. Retornar resumen
return SyncSchemaResponse::success($added, $updated, $deleted);
} catch (\Exception $e) {
return SyncSchemaResponse::failure([$e->getMessage()]);
}
}
/**
* Leer schemas desde archivo JSON
*/
private function readSchemas(string $filePath): array
{
if (!file_exists($filePath)) {
throw new \RuntimeException("Schema file not found: {$filePath}");
}
$content = file_get_contents($filePath);
$schemas = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('Invalid JSON: ' . json_last_error_msg());
}
return $schemas;
}
/**
* Agregar nuevos componentes
*/
private function addComponents(array $names, array $schemas): array
{
$added = [];
foreach ($names as $name) {
$schema = $schemas[$name];
$componentName = new ComponentName($name);
$configuration = ComponentConfiguration::fromArray($schema);
$this->defaultsRepository->save($componentName, $configuration);
$added[] = $name;
}
return $added;
}
/**
* Actualizar componentes existentes
*/
private function updateComponents(array $names, array $schemas): array
{
$updated = [];
foreach ($names as $name) {
$schema = $schemas[$name];
$componentName = new ComponentName($name);
$configuration = ComponentConfiguration::fromArray($schema);
$this->defaultsRepository->save($componentName, $configuration);
$updated[] = $name;
}
return $updated;
}
/**
* Eliminar componentes obsoletos
*/
private function deleteComponents(array $names): array
{
$deleted = [];
foreach ($names as $name) {
$this->defaultsRepository->deleteDefaults($name);
$deleted[] = $name;
}
return $deleted;
}
}