feat(custom-css-manager): implementar TIPO 3 - CSS Crítico Personalizado

Nuevo sistema de gestión de CSS personalizado con panel admin:
- Admin/CustomCSSManager: CRUD de snippets CSS (crítico/diferido)
- Public/CustomCSSManager: Inyección dinámica en frontend
- Schema JSON para configuración del componente

Migración de CSS estático a BD:
- Tablas APU (~14KB) → snippet diferido en BD
- Tablas Genéricas (~10KB) → snippet diferido en BD
- Comentadas funciones legacy en enqueue-scripts.php

Limpieza de archivos obsoletos:
- Eliminado build-bootstrap-subset.js
- Eliminado migrate-legacy-options.php
- Eliminado minify-css.php
- Eliminado purgecss.config.js

Beneficios:
- CSS editable desde admin sin tocar código
- Soporte crítico (head) y diferido (footer)
- Filtrado por scope (all/home/single/archive)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-12-01 15:43:25 -06:00
parent 423aae062c
commit 9cb0dd1491
24 changed files with 1553 additions and 784 deletions

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Domain\Contracts;
/**
* Contrato para repositorio de snippets CSS
*
* Usado por Admin (CRUD) y Public (lectura)
*/
interface CSSSnippetRepositoryInterface
{
/**
* Obtiene todos los snippets almacenados
* @return array<array> Array de snippets deserializados
*/
public function getAll(): array;
/**
* Guarda un snippet (crear o actualizar)
* @param array $snippet Datos del snippet
*/
public function save(array $snippet): void;
/**
* Elimina un snippet por ID
* @param string $snippetId ID del snippet
*/
public function delete(string $snippetId): void;
/**
* Obtiene snippets por tipo de carga
* @param string $loadType 'critical' o 'deferred'
* @return array<array>
*/
public function getByLoadType(string $loadType): array;
/**
* Obtiene snippets aplicables a una página específica
* @param string $loadType 'critical' o 'deferred'
* @param string $pageType 'all', 'home', 'posts', 'pages', 'archives'
* @return array<array>
*/
public function getForPage(string $loadType, string $pageType): array;
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace ROITheme\Shared\Domain\Exceptions;
/**
* Excepción de dominio para errores de validación
*
* Usada cuando las reglas de negocio no se cumplen.
* Ubicada en Shared porque es usada por múltiples bounded contexts.
*/
final class ValidationException extends \DomainException
{
/**
* @param string $message Mensaje descriptivo del error de validación
* @param int $code Código de error (default 0)
* @param \Throwable|null $previous Excepción anterior para encadenamiento
*/
public function __construct(
string $message,
int $code = 0,
?\Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}
/**
* Factory: Campo requerido faltante
*/
public static function requiredField(string $fieldName): self
{
return new self("El campo '{$fieldName}' es requerido");
}
/**
* Factory: Valor inválido
*/
public static function invalidValue(string $fieldName, string $reason): self
{
return new self("Valor inválido para '{$fieldName}': {$reason}");
}
/**
* Factory: Longitud excedida
*/
public static function maxLengthExceeded(string $fieldName, int $maxLength): self
{
return new self("El campo '{$fieldName}' excede el máximo de {$maxLength} caracteres");
}
}