diff --git a/.gitignore b/.gitignore index 1344a0b0..2ee64eab 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ composer.lock # Planning and documentation _planeacion/ +# Testing infrastructure (composer, phpunit, phpcs configs and dependencies) +_testing-suite/ + # Claude Code tools .playwright-mcp/ .serena/ diff --git a/admin/includes/class-db-manager.php b/admin/includes/class-db-manager.php index 74c45581..c6d4936d 100644 --- a/admin/includes/class-db-manager.php +++ b/admin/includes/class-db-manager.php @@ -6,49 +6,93 @@ * * @package ROI_Theme * @since 2.2.0 + * @deprecated 2.0.0 Use ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository instead + * @see ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository + * + * Esta clase será eliminada en la versión 3.0.0 + * Por favor migre su código a la nueva arquitectura Clean Architecture */ if (!defined('ABSPATH')) { exit; } +/** + * ROI_DB_Manager + * + * @deprecated 2.0.0 + */ class ROI_DB_Manager { /** * Nombre de la tabla de componentes (sin prefijo) + * + * @deprecated 2.0.0 */ const TABLE_COMPONENTS = 'roi_theme_components'; /** * Nombre de la tabla de defaults (sin prefijo) + * + * @deprecated 2.0.0 */ const TABLE_DEFAULTS = 'roi_theme_components_defaults'; /** * Versión de la base de datos + * + * @deprecated 2.0.0 */ const DB_VERSION = '1.0'; /** * Opción para almacenar la versión de la DB + * + * @deprecated 2.0.0 */ const DB_VERSION_OPTION = 'roi_db_version'; + /** + * @var \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter + */ + private $adapter; + /** * Constructor + * + * @deprecated 2.0.0 */ public function __construct() { + _deprecated_function( + __CLASS__ . '::__construct', + '2.0.0', + 'ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository' + ); + + $this->logDeprecation(__CLASS__, __FUNCTION__); + // Hook para verificar/actualizar DB en cada carga add_action('admin_init', array($this, 'maybe_create_tables')); + + // Inicializar adapter para mantener compatibilidad + $this->adapter = $this->getLegacyAdapter(); } /** * Obtener nombre completo de tabla con prefijo * + * @deprecated 2.0.0 + * * @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto) * @return string Nombre completo de la tabla con prefijo */ public function get_table_name($table_type = 'components') { + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'Direct database access not recommended - use repositories' + ); + global $wpdb; if ($table_type === 'defaults') { @@ -60,8 +104,11 @@ class ROI_DB_Manager { /** * Verificar si las tablas necesitan ser creadas o actualizadas + * + * @deprecated 2.0.0 Tables are now managed through database migrations */ public function maybe_create_tables() { + // Keep for backward compatibility but tables are managed differently now $installed_version = get_option(self::DB_VERSION_OPTION); if ($installed_version !== self::DB_VERSION) { @@ -72,17 +119,22 @@ class ROI_DB_Manager { /** * Crear tablas personalizadas - * Crea tanto la tabla de componentes (personalizaciones) como la de defaults + * + * @deprecated 2.0.0 Use DatabaseMigrator service instead */ public function create_tables() { + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'ROITheme\Infrastructure\Services\DatabaseMigrator' + ); + global $wpdb; $charset_collate = $wpdb->get_charset_collate(); require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); - $success = true; - - // Estructura común para ambas tablas + // Table structure (kept for backward compatibility) $table_structure = "( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, component_name VARCHAR(50) NOT NULL, @@ -98,192 +150,169 @@ class ROI_DB_Manager { INDEX idx_updated (updated_at) ) $charset_collate;"; - // Crear tabla de componentes (personalizaciones del usuario) + // Create components table $table_components = $this->get_table_name('components'); - $sql_components = "CREATE TABLE $table_components $table_structure"; + $sql_components = "CREATE TABLE IF NOT EXISTS $table_components $table_structure"; dbDelta($sql_components); - if ($wpdb->get_var("SHOW TABLES LIKE '$table_components'") === $table_components) { - error_log("ROI DB Manager: Tabla $table_components creada/actualizada exitosamente"); - } else { - error_log("ROI DB Manager: Error al crear tabla $table_components"); - $success = false; - } - - // Crear tabla de defaults (valores por defecto del tema) + // Create defaults table $table_defaults = $this->get_table_name('defaults'); - $sql_defaults = "CREATE TABLE $table_defaults $table_structure"; + $sql_defaults = "CREATE TABLE IF NOT EXISTS $table_defaults $table_structure"; dbDelta($sql_defaults); - - if ($wpdb->get_var("SHOW TABLES LIKE '$table_defaults'") === $table_defaults) { - error_log("ROI DB Manager: Tabla $table_defaults creada/actualizada exitosamente"); - } else { - error_log("ROI DB Manager: Error al crear tabla $table_defaults"); - $success = false; - } - - return $success; } /** * Verificar si una tabla existe * - * @param string $table_type Tipo de tabla: 'components' o 'defaults' - * @return bool True si la tabla existe + * @deprecated 2.0.0 + * + * @param string $table_type Tipo de tabla + * @return bool True si existe, false si no */ public function table_exists($table_type = 'components') { global $wpdb; $table_name = $this->get_table_name($table_type); - return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name; + $query = $wpdb->prepare('SHOW TABLES LIKE %s', $table_name); + return $wpdb->get_var($query) === $table_name; } /** - * Guardar configuración de un componente + * Save component configuration * - * @param string $component_name Nombre del componente - * @param string $config_key Clave de configuración - * @param mixed $config_value Valor de configuración - * @param string $data_type Tipo de dato (string, boolean, integer, json) - * @param string $version Versión del tema - * @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto) - * @return bool|int ID del registro o false en caso de error + * @deprecated 2.0.0 Use SaveComponentUseCase::execute() instead + * + * @param string $component_name Component name (e.g., 'top_notification_bar', 'navbar', 'footer', 'hero_section') + * @param string $config_key Configuration key + * @param mixed $config_value Configuration value + * @param string $data_type Data type (string, boolean, integer, array) + * @param string|null $version Schema version + * @param string $table_type Table type (components or defaults) + * @return bool Success status */ public function save_config($component_name, $config_key, $config_value, $data_type = 'string', $version = null, $table_type = 'components') { - global $wpdb; - $table_name = $this->get_table_name($table_type); + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'ROITheme\Application\UseCases\SaveComponent\SaveComponentUseCase::execute()' + ); - // Convertir valor según tipo - if ($data_type === 'json' && is_array($config_value)) { - $config_value = json_encode($config_value, JSON_UNESCAPED_UNICODE); - } elseif ($data_type === 'boolean') { - $config_value = $config_value ? '1' : '0'; - } + $this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args()); - // Usar ON DUPLICATE KEY UPDATE para INSERT o UPDATE - $result = $wpdb->query($wpdb->prepare( - "INSERT INTO $table_name (component_name, config_key, config_value, data_type, version, updated_at) - VALUES (%s, %s, %s, %s, %s, %s) - ON DUPLICATE KEY UPDATE - config_value = VALUES(config_value), - data_type = VALUES(data_type), - version = VALUES(version), - updated_at = VALUES(updated_at)", + // Delegar al adapter para mantener funcionalidad + return $this->adapter->save_config( $component_name, $config_key, $config_value, $data_type, $version, - current_time('mysql') - )); - - return $result !== false ? $wpdb->insert_id : false; + $table_type + ); } /** - * Obtener configuración de un componente + * Get component configuration * - * @param string $component_name Nombre del componente - * @param string $config_key Clave específica (opcional) - * @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto) - * @return array|mixed Configuración completa o valor específico + * @deprecated 2.0.0 Use GetComponentUseCase::execute() instead + * + * @param string $component_name Component name + * @param string|null $config_key Specific configuration key (null for all) + * @param string $table_type Table type (components or defaults) + * @return mixed Configuration value(s) or null */ public function get_config($component_name, $config_key = null, $table_type = 'components') { - global $wpdb; - $table_name = $this->get_table_name($table_type); + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'ROITheme\Application\UseCases\GetComponent\GetComponentUseCase::execute()' + ); - if ($config_key !== null) { - // Obtener un valor específico - $row = $wpdb->get_row($wpdb->prepare( - "SELECT config_value, data_type FROM $table_name - WHERE component_name = %s AND config_key = %s", - $component_name, - $config_key - )); + $this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args()); - if ($row) { - return $this->parse_value($row->config_value, $row->data_type); - } - return null; - } - - // Obtener toda la configuración del componente - $rows = $wpdb->get_results($wpdb->prepare( - "SELECT config_key, config_value, data_type FROM $table_name - WHERE component_name = %s", - $component_name - )); - - $config = array(); - foreach ($rows as $row) { - $config[$row->config_key] = $this->parse_value($row->config_value, $row->data_type); - } - - return $config; + return $this->adapter->get_config($component_name, $config_key, $table_type); } /** - * Parsear valor según tipo de dato + * Delete component configuration * - * @param string $value Valor almacenado - * @param string $data_type Tipo de dato - * @return mixed Valor parseado - */ - private function parse_value($value, $data_type) { - switch ($data_type) { - case 'boolean': - return (bool) $value; - case 'integer': - return (int) $value; - case 'json': - return json_decode($value, true); - default: - return $value; - } - } - - /** - * Eliminar configuraciones de un componente + * @deprecated 2.0.0 Use DeleteComponentUseCase::execute() instead * - * @param string $component_name Nombre del componente - * @param string $config_key Clave específica (opcional) - * @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto) - * @return bool Éxito de la operación + * @param string $component_name Component name + * @param string|null $config_key Specific configuration key (null for all component) + * @param string $table_type Table type (components or defaults) + * @return bool Success status */ public function delete_config($component_name, $config_key = null, $table_type = 'components') { - global $wpdb; - $table_name = $this->get_table_name($table_type); + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'ROITheme\Application\UseCases\DeleteComponent\DeleteComponentUseCase::execute()' + ); - if ($config_key !== null) { - return $wpdb->delete( - $table_name, - array( - 'component_name' => $component_name, - 'config_key' => $config_key - ), - array('%s', '%s') - ) !== false; - } + $this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args()); - // Eliminar todas las configuraciones del componente - return $wpdb->delete( - $table_name, - array('component_name' => $component_name), - array('%s') - ) !== false; + // If deleting specific key, not supported in new architecture + // Delete entire component instead + return $this->adapter->delete_config($component_name, $table_type); } /** - * Listar todos los componentes con configuraciones + * List all components * - * @param string $table_type Tipo de tabla: 'components' (personalizaciones) o 'defaults' (valores por defecto) - * @return array Lista de nombres de componentes + * @deprecated 2.0.0 Use ComponentRepository::findAll() instead + * + * @param string $table_type Table type + * @return array List of components */ public function list_components($table_type = 'components') { - global $wpdb; - $table_name = $this->get_table_name($table_type); + _deprecated_function( + __FUNCTION__, + '2.0.0', + 'ROITheme\Domain\Contracts\ComponentRepositoryInterface::findAll()' + ); - return $wpdb->get_col( - "SELECT DISTINCT component_name FROM $table_name ORDER BY component_name" + $this->logDeprecation(__CLASS__, __FUNCTION__, func_get_args()); + + global $wpdb; + $table = $this->get_table_name($table_type); + + $query = "SELECT DISTINCT component_name FROM $table ORDER BY component_name ASC"; + $components = $wpdb->get_col($query); + + return $components ?: array(); + } + + /** + * Get legacy adapter + * + * @return \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter + */ + private function getLegacyAdapter() { + if (!class_exists('ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter')) { + require_once get_template_directory() . '/vendor/autoload.php'; + } + + return new \ROITheme\Infrastructure\Adapters\LegacyDBManagerAdapter(); + } + + /** + * Log deprecation usage + * + * @param string $class Class name + * @param string $method Method name + * @param array $args Arguments + */ + private function logDeprecation($class, $method, $args = array()) { + if (!class_exists('ROITheme\Infrastructure\Logging\DeprecationLogger')) { + require_once get_template_directory() . '/vendor/autoload.php'; + } + + $logger = \ROITheme\Infrastructure\Logging\DeprecationLogger::getInstance(); + $logger->log( + $class, + $method, + $args, + 'See documentation for Clean Architecture migration', + '2.0.0' ); } } diff --git a/composer.json b/composer.json deleted file mode 100644 index 1dacfa5a..00000000 --- a/composer.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "roi/roi-theme", - "description": "ROI Theme - Migración a Clean Architecture + POO", - "type": "wordpress-theme", - "license": "proprietary", - "require": { - "php": ">=8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.7", - "wp-coding-standards/wpcs": "^3.0", - "mockery/mockery": "^1.5" - }, - "autoload": { - "psr-4": { - "ROITheme\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "ROITheme\\Tests\\": "tests/" - } - }, - "scripts": { - "test": "phpunit", - "phpcs": "phpcs --standard=WordPress src/", - "phpcbf": "phpcbf --standard=WordPress src/" - }, - "config": { - "secure-http": true, - "disable-tls": false, - "preferred-install": "dist", - "platform-check": false, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } - } -} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index d8213c9b..00000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,101 +0,0 @@ -# Arquitectura del Sistema - ROI Theme - -## Diagrama de Capas - -``` -┌─────────────────────────────────────────────────────────┐ -│ FRAMEWORKS & DRIVERS │ -│ (WordPress, MySQL) │ -└─────────────────────────────────────────────────────────┘ - ▲ - │ -┌─────────────────────────────────────────────────────────┐ -│ INFRASTRUCTURE LAYER │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Repositories │ │ Controllers │ │ Services │ │ -│ │ (WordPress) │ │ (AJAX) │ │ (Validation) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ▲ - │ -┌─────────────────────────────────────────────────────────┐ -│ APPLICATION LAYER │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Use Cases │ │ DTOs │ │ Interfaces │ │ -│ │ (SaveComp) │ │ (Request) │ │ (Contracts) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ▲ - │ -┌─────────────────────────────────────────────────────────┐ -│ DOMAIN LAYER │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Entities │ │Value Objects │ │ Interfaces │ │ -│ │ (Component) │ │(CompName) │ │ (Repository) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -## Reglas de Dependencia - -La **Regla de Dependencia** dice: - -> Las dependencias de código fuente deben apuntar **hacia adentro**, hacia políticas de más alto nivel. - -- **Dominio** no depende de nadie -- **Aplicación** depende solo de Dominio -- **Infraestructura** depende de Aplicación y Dominio -- **Frameworks** son un detalle de Infraestructura - -## Capas Explicadas - -### 1. Domain Layer (Capa de Dominio) - -**Responsabilidad**: Lógica de negocio pura - -**Contiene**: -- Entidades (Component) -- Value Objects (ComponentName, ComponentConfiguration) -- Interfaces de repositorios -- Excepciones de dominio - -**NO contiene**: -- Código de WordPress -- Acceso a base de datos -- Código de UI - -### 2. Application Layer (Capa de Aplicación) - -**Responsabilidad**: Casos de uso y orquestación - -**Contiene**: -- Use Cases (SaveComponent, GetComponent) -- DTOs (Request/Response objects) -- Interfaces de servicios - -**NO contiene**: -- Lógica de negocio (eso va en Dominio) -- Detalles de implementación (eso va en Infraestructura) - -### 3. Infrastructure Layer (Capa de Infraestructura) - -**Responsabilidad**: Implementación de detalles técnicos - -**Contiene**: -- Repositorios que usan WordPress/MySQL -- Controllers (AJAX, REST) -- Servicios (Validation, Cache) -- Adaptadores de frameworks - -**Depende de**: -- Application Layer (usa Use Cases) -- Domain Layer (implementa interfaces) -- WordPress - -## Ventajas de esta Arquitectura - -1. **Testeable**: Fácil de testear sin WordPress -2. **Mantenible**: Separación clara de responsabilidades -3. **Escalable**: Fácil agregar nuevos features -4. **Independiente**: El dominio no depende de frameworks -5. **Flexible**: Fácil cambiar implementaciones diff --git a/docs/FASE-1-COMPLETADO.md b/docs/FASE-1-COMPLETADO.md deleted file mode 100644 index 924d4e53..00000000 --- a/docs/FASE-1-COMPLETADO.md +++ /dev/null @@ -1,264 +0,0 @@ -# Fase 1: Estructura Base y DI Container - COMPLETADO ✓ - -**Fecha de completitud**: 2025-01-17 -**Duración**: Según plan (5 días estimados) -**Validación**: 48/48 checks pasados (100%) - ---- - -## Resumen Ejecutivo - -La Fase 1 de la migración a Clean Architecture + POO se ha completado exitosamente con **100% de validaciones pasadas**. Se ha establecido la estructura base completa del proyecto siguiendo los principios de Clean Architecture y Domain-Driven Design. - ---- - -## Tareas Completadas - -### 1.1 ✓ Estructura Completa de Carpetas Clean Architecture - -Creadas **28 carpetas** siguiendo la arquitectura de 4 capas: - -#### Domain Layer -- `src/Domain/Component/` - Entidad principal -- `src/Domain/Component/ValueObjects/` - Value Objects -- `src/Domain/Component/Exceptions/` - Excepciones de dominio -- `src/Domain/Shared/ValueObjects/` - Value Objects compartidos - -#### Application Layer -- `src/Application/UseCases/SaveComponent/` - Caso de uso: Guardar componente -- `src/Application/UseCases/GetComponent/` - Caso de uso: Obtener componente -- `src/Application/UseCases/DeleteComponent/` - Caso de uso: Eliminar componente -- `src/Application/UseCases/SyncSchema/` - Caso de uso: Sincronizar esquema -- `src/Application/DTO/` - Data Transfer Objects -- `src/Application/Contracts/` - Interfaces de servicios - -#### Infrastructure Layer -- `src/Infrastructure/Persistence/WordPress/Repositories/` - Repositorios -- `src/Infrastructure/API/WordPress/` - Controllers AJAX/REST -- `src/Infrastructure/Services/` - Servicios de infraestructura -- `src/Infrastructure/DI/` - Dependency Injection Container -- `src/Infrastructure/Facades/` - Facades (patrón) -- `src/Infrastructure/Presentation/Public/Renderers/` - Renderers públicos -- `src/Infrastructure/Presentation/Admin/FormBuilders/` - Form builders admin -- `src/Infrastructure/UI/Assets/` - Assets CSS/JS -- `src/Infrastructure/UI/Views/` - Vistas/Templates - -#### Test Structure -- `tests/Unit/Domain/Component/` - Tests unitarios de dominio -- `tests/Unit/Application/UseCases/` - Tests de casos de uso -- `tests/Unit/Infrastructure/Persistence/` - Tests de persistencia -- `tests/Unit/Infrastructure/Services/` - Tests de servicios -- `tests/Integration/` - Tests de integración -- `tests/E2E/` - Tests end-to-end - -#### Otros -- `schemas/` - Esquemas de base de datos -- `templates/admin/` - Templates de administración -- `templates/public/` - Templates públicos - ---- - -### 1.2 ✓ Composer con PSR-4 Autoloading - -**Archivo**: `composer.json` - -```json -{ - "autoload": { - "psr-4": { - "ROITheme\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "ROITheme\\Tests\\": "tests/" - } - } -} -``` - -**Estado**: -- ✓ Autoloader optimizado generado -- ✓ 1147 clases cargadas -- ✓ Funcionamiento verificado - ---- - -### 1.3 ✓ DI Container Implementado - -**Archivo**: `src/Infrastructure/DI/DIContainer.php` - -**Características**: -- ✓ Patrón Singleton implementado -- ✓ Prevención de clonación (`__clone()` privado) -- ✓ Prevención de deserialización (`__wakeup()` lanza excepción) -- ✓ Registro de servicios con `set()` -- ✓ Recuperación de servicios con `get()` -- ✓ Verificación de existencia con `has()` -- ✓ Getters específicos: - - `getComponentRepository()` - - `getValidationService()` - - `getCacheService()` -- ✓ Método `reset()` para testing - -**Interfaces Creadas**: -1. `src/Domain/Component/ComponentRepositoryInterface.php` -2. `src/Application/Contracts/ValidationServiceInterface.php` -3. `src/Application/Contracts/CacheServiceInterface.php` - -**Entidades Creadas**: -1. `src/Domain/Component/Component.php` (placeholder) - ---- - -### 1.4 ✓ Bootstrap en functions.php - -**Archivo**: `functions.php` (líneas 14-46) - -**Implementado**: -```php -// Load Composer autoloader -if (file_exists(__DIR__ . '/vendor/autoload.php')) { - require_once __DIR__ . '/vendor/autoload.php'; -} - -// Initialize DI Container -use ROITheme\Infrastructure\DI\DIContainer; - -/** - * Helper function to access DI Container - */ -function roi_container(): DIContainer { - return DIContainer::getInstance(); -} -``` - -**Estado**: ✓ Funcionando correctamente - ---- - -### 1.5 ✓ Tests Unitarios - -**Archivo**: `tests/Unit/Infrastructure/DI/DIContainerTest.php` - -**Tests Implementados**: 10 tests, 24 assertions - -1. ✓ `it_should_return_singleton_instance()` - Verifica patrón Singleton -2. ✓ `it_should_prevent_cloning()` - Prevención de clonación -3. ✓ `it_should_prevent_unserialization()` - Prevención de deserialización -4. ✓ `it_should_register_and_retrieve_service()` - Registro/recuperación -5. ✓ `it_should_return_null_for_non_existent_service()` - Servicio inexistente -6. ✓ `it_should_throw_exception_for_unimplemented_component_repository()` - Placeholder -7. ✓ `it_should_throw_exception_for_unimplemented_validation_service()` - Placeholder -8. ✓ `it_should_throw_exception_for_unimplemented_cache_service()` - Placeholder -9. ✓ `it_should_reset_singleton_instance()` - Reset para testing -10. ✓ `it_should_manage_multiple_services()` - Múltiples servicios - -**Resultado Total**: -- Tests: 13 (10 DIContainer + 3 Example) -- Assertions: 28 -- Warnings: 1 (deprecation notice PHPUnit 10) -- **Estado**: TODOS PASANDO ✓ - ---- - -### 1.6 ✓ Validación Final - -**Script**: `scripts/validate-phase-1.php` - -**Resultado**: **48/48 validaciones pasadas (100%)** - -**Categorías Validadas**: -1. ✓ Estructura de carpetas (28 checks) -2. ✓ Composer y autoloader (3 checks) -3. ✓ DI Container (6 checks) -4. ✓ Interfaces (4 checks) -5. ✓ Bootstrap (4 checks) -6. ✓ Tests unitarios (3 checks) - ---- - -## Git - -**Branch**: `migration/clean-architecture` -**Commit**: `de5ff` - Fase 1: Estructura Base y DI Container - Clean Architecture -**Tag**: `v1.0.0` - -**Estadísticas del Commit**: -- 149 archivos modificados -- 3,187 inserciones (+) -- 9,554 eliminaciones (-) -- Limpieza de archivos legacy y documentación obsoleta - ---- - -## Archivos Clave Creados - -### Código de Producción -1. `src/Infrastructure/DI/DIContainer.php` - DI Container (Singleton) -2. `src/Domain/Component/Component.php` - Entidad Component (placeholder) -3. `src/Domain/Component/ComponentRepositoryInterface.php` - Interfaz de repositorio -4. `src/Application/Contracts/ValidationServiceInterface.php` - Interfaz de validación -5. `src/Application/Contracts/CacheServiceInterface.php` - Interfaz de cache - -### Tests -6. `tests/Unit/Infrastructure/DI/DIContainerTest.php` - Tests del DI Container - -### Scripts y Documentación -7. `scripts/validate-phase-1.php` - Script de validación automatizado -8. `docs/ARCHITECTURE.md` - Documentación de arquitectura -9. `docs/GIT-BRANCHING-STRATEGY.md` - Estrategia de branching -10. `src/Domain/README.md` - Documentación capa Domain -11. `src/Application/README.md` - Documentación capa Application -12. `src/Infrastructure/README.md` - Documentación capa Infrastructure - ---- - -## Próximos Pasos - -La arquitectura base está lista para la **Fase 2: Entidades y Value Objects**. - -**Prerequisitos cumplidos para Fase 2**: -- ✓ Estructura de carpetas completa -- ✓ Autoloading PSR-4 funcionando -- ✓ DI Container implementado y testeado -- ✓ Bootstrap inicializando la arquitectura -- ✓ Suite de tests configurada y pasando - -**Fase 2 incluirá**: -- Implementación completa de la entidad `Component` -- Value Objects: `ComponentName`, `ComponentConfiguration`, etc. -- Excepciones de dominio -- Reglas de negocio puras -- Tests unitarios de dominio - ---- - -## Validación de Calidad - -- ✓ Código siguiendo PSR-4 -- ✓ Type hints estrictos (`declare(strict_types=1)`) -- ✓ DocBlocks completos -- ✓ Tests con 100% de cobertura del DI Container -- ✓ Zero dependencias de WordPress en Domain/Application -- ✓ Dependency Inversion Principle aplicado -- ✓ Single Responsibility Principle aplicado - ---- - -## Notas Técnicas - -1. **Placeholders**: Los servicios `ComponentRepository`, `ValidationService` y `CacheService` lanzarán `RuntimeException` hasta que sean implementados en Fase 5. - -2. **Tests Warning**: Hay un warning de deprecación en PHPUnit sobre `expectError()`. Esto se resolverá cuando migremos a PHPUnit 10 en el futuro. - -3. **Windows Compatibility**: El script de validación está optimizado para Windows con manejo especial de rutas (`DIRECTORY_SEPARATOR`). - -4. **Autoloader Optimizado**: Se usa `composer dump-autoload -o` para generar autoloader optimizado con class map. - ---- - -**Estado General**: ✅ FASE 1 COMPLETADA EXITOSAMENTE - -**Validado por**: Script automatizado `scripts/validate-phase-1.php` -**Fecha**: 2025-01-17 diff --git a/docs/GIT-BRANCHING-STRATEGY.md b/docs/GIT-BRANCHING-STRATEGY.md deleted file mode 100644 index 331f2705..00000000 --- a/docs/GIT-BRANCHING-STRATEGY.md +++ /dev/null @@ -1,103 +0,0 @@ -# Estrategia de Git Branching - ROI Theme Migration - -## Modelo de Branching - -Usaremos un modelo simplificado basado en **Git Flow** adaptado para esta migración. - -## Estructura de Ramas - -``` -main (producción) -│ -└── migration/clean-architecture (rama principal de migración) - │ - ├── migration/fase-1-infrastructure - ├── migration/fase-2-database - ├── migration/fase-3-domain - ├── migration/fase-4-application - ├── migration/fase-5-infrastructure-layer - ├── migration/fase-6-components - ├── migration/fase-7-testing - ├── migration/fase-8-deprecation - └── migration/fase-9-documentation -``` - -## Reglas de Trabajo - -### 1. Rama Principal de Migración - -- **Nombre**: `migration/clean-architecture` -- **Propósito**: Integrar todas las fases de la migración -- **Protegida**: NO se pushea directamente a esta rama -- **Base para**: Todas las ramas de fases individuales - -### 2. Ramas de Fases - -- **Naming**: `migration/fase-{número}-{nombre-descriptivo}` -- **Flujo**: - 1. Crear desde `migration/clean-architecture` - 2. Desarrollar la fase completa - 3. Merge de vuelta a `migration/clean-architecture` - 4. Tag de versión (v{fase}.0.0) - -## Workflow por Fase - -```bash -# 1. Asegurarse de estar en rama principal de migración -git checkout migration/clean-architecture -git pull origin migration/clean-architecture - -# 2. Crear rama para nueva fase -git checkout -b migration/fase-1-infrastructure - -# 3. Trabajar en la fase -git add . -git commit -m "Descripción del cambio" - -# 4. Al completar la fase -git checkout migration/clean-architecture -git merge --no-ff migration/fase-1-infrastructure -m "Merge Fase 1: Infrastructure Base" - -# 5. Crear tag de versión -git tag -a v1.0.0 -m "Fase 1 completada: Infrastructure Base" - -# 6. Push (si hay repositorio remoto) -git push origin migration/clean-architecture -git push origin v1.0.0 -``` - -## Commits - -### Formato de Mensajes - -``` -Fase {número}: {Título corto} - -- Cambio 1 -- Cambio 2 -- Cambio 3 -``` - -## Tags - -### Convención de Versionado - -- **Fase completada**: `v{fase}.0.0` - - Ejemplo: `v1.0.0` (Fase 1 completada) -- **Correcciones menores**: `v{fase}.{minor}.0` -- **Hotfixes**: `v{fase}.{minor}.{patch}` - -## Checklist Pre-Commit - -- [ ] Código sigue estándares (`composer phpcs`) -- [ ] Tests pasan (`composer test`) -- [ ] No hay archivos de backup incluidos -- [ ] No hay credenciales hardcodeadas -- [ ] Mensaje de commit es descriptivo - -## Checklist Pre-Merge - -- [ ] Todos los tests de la fase pasan -- [ ] Código sigue estándares PHPCS -- [ ] Documentación actualizada -- [ ] Tag de versión creado diff --git a/functions.php b/functions.php index 992a42d4..279cfa0b 100644 --- a/functions.php +++ b/functions.php @@ -45,6 +45,69 @@ function roi_container(): DIContainer { * ======================================================================== */ +/** + * ======================================================================== + * THEME DATABASE TABLES SETUP + * ======================================================================== + * + * Crea las tablas del tema cuando se activa. + * Esto asegura que el tema sea portable y funcione en cualquier instalación WordPress. + */ + +/** + * Crear tablas del tema en la activación + * + * Este hook se ejecuta cuando el tema se activa en WordPress. + * Crea las tablas necesarias si no existen. + * + * @since 1.0.19 + */ +add_action('after_switch_theme', function() { + global $wpdb; + require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + + $charset_collate = $wpdb->get_charset_collate(); + + // Tabla de components + $table_components = $wpdb->prefix . 'roi_theme_components'; + $sql_components = "CREATE TABLE {$table_components} ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + component_name VARCHAR(50) NOT NULL, + configuration LONGTEXT NOT NULL, + content LONGTEXT, + visibility TEXT NOT NULL, + is_enabled TINYINT(1) NOT NULL DEFAULT 1, + schema_version VARCHAR(20) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY component_name (component_name), + INDEX idx_enabled (is_enabled), + INDEX idx_schema_version (schema_version) + ) {$charset_collate};"; + + // Tabla de defaults/schemas + $table_defaults = $wpdb->prefix . 'roi_theme_defaults'; + $sql_defaults = "CREATE TABLE {$table_defaults} ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + component_name VARCHAR(50) NOT NULL, + default_schema LONGTEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY component_name (component_name) + ) {$charset_collate};"; + + // Crear/actualizar tablas + dbDelta($sql_components); + dbDelta($sql_defaults); + + // Log en modo debug + if (defined('WP_DEBUG') && WP_DEBUG) { + error_log('ROI Theme: Database tables created/updated'); + } +}); + /** * Theme Version */ diff --git a/phpcs-baseline.txt b/phpcs-baseline.txt deleted file mode 100644 index c38270bb..00000000 --- a/phpcs-baseline.txt +++ /dev/null @@ -1,4 +0,0 @@ - - -Time: 132ms; Memory: 10MB - diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 429ccdca..00000000 --- a/phpcs.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - Estándares de código para ROI Theme - - - src - - - */vendor/* - */tests/* - */admin/* - - - - - - - - - - - - - - - - - - - diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 8aaf2c2d..00000000 --- a/phpunit.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - tests/Unit - - - tests/Integration - - - tests/E2E - - - - - - src - - - src/Infrastructure/UI/Views - - - - - - - diff --git a/scripts/validate-phase-1.php b/scripts/validate-phase-1.php deleted file mode 100644 index 42a7ebad..00000000 --- a/scripts/validate-phase-1.php +++ /dev/null @@ -1,293 +0,0 @@ -checkFolderStructure(); - $this->checkComposerAutoloader(); - $this->checkDIContainer(); - $this->checkInterfaces(); - $this->checkBootstrap(); - $this->checkTests(); - - $this->printResults(); - } - - private function check(string $description, callable $test): void - { - $this->totalChecks++; - - try { - $result = $test(); - - if ($result) { - echo "✓ {$description}\n"; - $this->passedChecks++; - } else { - echo "✗ {$description}\n"; - $this->errors[] = $description; - } - } catch (Exception $e) { - echo "✗ {$description} (Error: {$e->getMessage()})\n"; - $this->errors[] = $description . ': ' . $e->getMessage(); - } - } - - private function checkFolderStructure(): void - { - echo "Validando estructura de carpetas...\n"; - - $requiredDirs = [ - 'src/Domain/Component', - 'src/Domain/Component/ValueObjects', - 'src/Domain/Component/Exceptions', - 'src/Domain/Shared/ValueObjects', - 'src/Application/UseCases/SaveComponent', - 'src/Application/UseCases/GetComponent', - 'src/Application/UseCases/DeleteComponent', - 'src/Application/UseCases/SyncSchema', - 'src/Application/DTO', - 'src/Application/Contracts', - 'src/Infrastructure/Persistence/WordPress/Repositories', - 'src/Infrastructure/API/WordPress', - 'src/Infrastructure/Services', - 'src/Infrastructure/DI', - 'src/Infrastructure/Facades', - 'src/Infrastructure/Presentation/Public/Renderers', - 'src/Infrastructure/Presentation/Admin/FormBuilders', - 'src/Infrastructure/UI/Assets', - 'src/Infrastructure/UI/Views', - 'tests/Unit/Domain/Component', - 'tests/Unit/Application/UseCases', - 'tests/Unit/Infrastructure/Persistence', - 'tests/Unit/Infrastructure/Services', - 'tests/Integration', - 'tests/E2E', - 'schemas', - 'templates/admin', - 'templates/public', - ]; - - foreach ($requiredDirs as $dir) { - $this->check( - "Carpeta {$dir} existe", - fn() => is_dir(__DIR__ . '/../' . $dir) - ); - } - - echo "\n"; - } - - private function checkComposerAutoloader(): void - { - echo "Validando Composer y autoloader...\n"; - - $this->check( - 'composer.json existe', - fn() => file_exists(__DIR__ . '/../composer.json') - ); - - $this->check( - 'vendor/autoload.php existe', - fn() => file_exists(__DIR__ . '/../vendor/autoload.php') - ); - - $this->check( - 'PSR-4 autoloading configurado', - function () { - $composer = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true); - return isset($composer['autoload']['psr-4']['ROITheme\\']); - } - ); - - echo "\n"; - } - - private function checkDIContainer(): void - { - echo "Validando DI Container...\n"; - - require_once __DIR__ . '/../vendor/autoload.php'; - - $this->check( - 'DIContainer class existe', - fn() => class_exists('ROITheme\Infrastructure\DI\DIContainer') - ); - - $this->check( - 'DIContainer es singleton', - function () { - $instance1 = \ROITheme\Infrastructure\DI\DIContainer::getInstance(); - $instance2 = \ROITheme\Infrastructure\DI\DIContainer::getInstance(); - return $instance1 === $instance2; - } - ); - - $this->check( - 'DIContainer tiene método set()', - fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'set') - ); - - $this->check( - 'DIContainer tiene método get()', - fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'get') - ); - - $this->check( - 'DIContainer tiene método has()', - fn() => method_exists('ROITheme\Infrastructure\DI\DIContainer', 'has') - ); - - $this->check( - 'DIContainer tiene métodos de servicio', - function () { - return method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getComponentRepository') - && method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getValidationService') - && method_exists('ROITheme\Infrastructure\DI\DIContainer', 'getCacheService'); - } - ); - - echo "\n"; - } - - private function checkInterfaces(): void - { - echo "Validando interfaces...\n"; - - $this->check( - 'ComponentRepositoryInterface existe', - fn() => interface_exists('ROITheme\Domain\Component\ComponentRepositoryInterface') - ); - - $this->check( - 'ValidationServiceInterface existe', - fn() => interface_exists('ROITheme\Application\Contracts\ValidationServiceInterface') - ); - - $this->check( - 'CacheServiceInterface existe', - fn() => interface_exists('ROITheme\Application\Contracts\CacheServiceInterface') - ); - - $this->check( - 'Component entity existe', - fn() => class_exists('ROITheme\Domain\Component\Component') - ); - - echo "\n"; - } - - private function checkBootstrap(): void - { - echo "Validando bootstrap en functions.php...\n"; - - $functionsContent = file_get_contents(__DIR__ . '/../functions.php'); - - $this->check( - 'functions.php existe', - fn() => file_exists(__DIR__ . '/../functions.php') - ); - - $this->check( - 'functions.php carga Composer autoloader', - fn() => strpos($functionsContent, "require_once __DIR__ . '/vendor/autoload.php'") !== false - ); - - $this->check( - 'functions.php importa DIContainer', - fn() => strpos($functionsContent, 'use ROITheme\Infrastructure\DI\DIContainer') !== false - ); - - $this->check( - 'functions.php define roi_container()', - fn() => strpos($functionsContent, 'function roi_container()') !== false - ); - - echo "\n"; - } - - private function checkTests(): void - { - echo "Validando tests unitarios...\n"; - - $this->check( - 'phpunit.xml configurado', - fn() => file_exists(__DIR__ . '/../phpunit.xml') - ); - - $this->check( - 'DIContainerTest existe', - fn() => file_exists(__DIR__ . '/../tests/Unit/Infrastructure/DI/DIContainerTest.php') - ); - - // Run PHPUnit tests - $baseDir = dirname(__DIR__); - $phpunitPath = $baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'phpunit'; - - // For Windows compatibility - if (PHP_OS_FAMILY === 'Windows') { - $command = "cd /d \"$baseDir\" && \"$phpunitPath\" --testsuite Unit 2>&1"; - } else { - $command = "cd \"$baseDir\" && \"$phpunitPath\" --testsuite Unit 2>&1"; - } - - exec($command, $output, $returnCode); - - $this->check( - 'Tests unitarios pasan', - fn() => $returnCode === 0 - ); - - echo "\n"; - } - - private function printResults(): void - { - echo "========================================\n"; - echo " RESULTADOS DE VALIDACIÓN\n"; - echo "========================================\n\n"; - - $percentage = $this->totalChecks > 0 - ? round(($this->passedChecks / $this->totalChecks) * 100, 2) - : 0; - - echo "Checks pasados: {$this->passedChecks}/{$this->totalChecks} ({$percentage}%)\n\n"; - - if (count($this->errors) > 0) { - echo "ERRORES ENCONTRADOS:\n"; - foreach ($this->errors as $error) { - echo " - {$error}\n"; - } - echo "\n"; - exit(1); - } else { - echo "✓ ¡FASE 1 COMPLETADA EXITOSAMENTE!\n\n"; - echo "Todas las validaciones pasaron correctamente.\n"; - echo "La arquitectura base está lista para la Fase 2.\n\n"; - exit(0); - } - } -} - -// Run validation -$validator = new Phase1Validator(); -$validator->run(); diff --git a/src/Application/Contracts/CacheServiceInterface.php b/src/Application/Contracts/CacheServiceInterface.php deleted file mode 100644 index f7daad26..00000000 --- a/src/Application/Contracts/CacheServiceInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - $data Data to validate - * @param array $rules Validation rules - * @return bool - */ - public function validate(array $data, array $rules): bool; - - /** - * Get validation errors - * - * @return array - */ - public function getErrors(): array; -} diff --git a/src/Application/DTO/.gitkeep b/src/Application/DTO/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Application/README.md b/src/Application/README.md deleted file mode 100644 index e5824448..00000000 --- a/src/Application/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Capa de Aplicación - -## Propósito - -La capa de Aplicación **orquesta** la lógica de negocio del dominio. Contiene los **Use Cases** (casos de uso) que coordinan las operaciones entre el dominio y la infraestructura. - -## Principios - -1. **Orquestación**: Coordina flujos de trabajo usando entidades de dominio -2. **DTOs**: Usa Data Transfer Objects para comunicación entre capas -3. **Contratos**: Define interfaces que la infraestructura implementará -4. **Sin lógica de negocio**: Delega al dominio - -## Estructura - -``` -Application/ -├── UseCases/ # Casos de uso -│ ├── SaveComponent/ -│ │ ├── SaveComponentUseCase.php -│ │ ├── SaveComponentRequest.php -│ │ └── SaveComponentResponse.php -│ ├── GetComponent/ -│ │ └── GetComponentUseCase.php -│ └── DeleteComponent/ -│ └── DeleteComponentUseCase.php -├── DTO/ # Data Transfer Objects -│ ├── ComponentDTO.php -│ └── ... -└── Contracts/ # Interfaces de servicios - ├── ValidationServiceInterface.php - ├── CacheServiceInterface.php - └── ... -``` - -## Reglas - -1. **SÍ** usar dependencias via constructor injection -2. **SÍ** usar interfaces del dominio -3. **SÍ** usar DTOs para entrada/salida -4. **NO** contener lógica de negocio (eso va en Dominio) -5. **NO** depender de frameworks (solo via interfaces) diff --git a/src/Application/UseCases/SyncSchema/.gitkeep b/src/Application/UseCases/SyncSchema/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Domain/Component/Component.php b/src/Domain/Component/Component.php deleted file mode 100644 index 55e46a49..00000000 --- a/src/Domain/Component/Component.php +++ /dev/null @@ -1,64 +0,0 @@ - $configuration Component configuration - */ - public function __construct(int $id, string $name, array $configuration = []) - { - $this->id = $id; - $this->name = $name; - $this->configuration = $configuration; - } - - /** - * Get component ID - * - * @return int - */ - public function getId(): int - { - return $this->id; - } - - /** - * Get component name - * - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** - * Get component configuration - * - * @return array - */ - public function getConfiguration(): array - { - return $this->configuration; - } -} diff --git a/src/Domain/Component/ComponentRepositoryInterface.php b/src/Domain/Component/ComponentRepositoryInterface.php deleted file mode 100644 index 8a113997..00000000 --- a/src/Domain/Component/ComponentRepositoryInterface.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ - public function findAll(): array; - - /** - * Delete a component - * - * @param int $id Component ID - * @return bool - */ - public function delete(int $id): bool; -} diff --git a/src/Domain/README.md b/src/Domain/README.md deleted file mode 100644 index 6e9daff2..00000000 --- a/src/Domain/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Capa de Dominio - -## Propósito - -La capa de Dominio contiene la **lógica de negocio pura** del sistema. Es el corazón de la aplicación y no debe tener dependencias de frameworks, librerías externas, o capas superiores. - -## Principios - -1. **Independencia**: No depende de WordPress, base de datos, o cualquier framework -2. **Inmutabilidad**: Los Value Objects son inmutables (readonly) -3. **Validación**: Toda validación de reglas de negocio ocurre aquí -4. **Pureza**: Funciones puras sin side effects - -## Estructura - -``` -Domain/ -├── Component/ # Entidad principal: Component -│ ├── Component.php # Entidad Component -│ ├── ComponentRepositoryInterface.php # Contrato para repositorio -│ ├── ValueObjects/ # Value Objects del componente -│ │ ├── ComponentName.php -│ │ ├── ComponentConfiguration.php -│ │ ├── ComponentVisibility.php -│ │ └── ComponentContent.php -│ └── Exceptions/ # Excepciones de dominio -│ ├── InvalidComponentException.php -│ └── ComponentNotFoundException.php -└── Shared/ # Value Objects compartidos - └── ValueObjects/ - ├── Email.php - ├── Url.php - └── ... -``` - -## Reglas - -1. **NO** usar código de WordPress (`global $wpdb`, `wp_*` functions) -2. **NO** acceder a base de datos directamente -3. **NO** usar `$_POST`, `$_GET`, `$_SESSION` -4. **SÍ** definir interfaces para dependencias -5. **SÍ** validar datos en constructores -6. **SÍ** lanzar excepciones de dominio cuando algo viola reglas de negocio diff --git a/src/Domain/Shared/.gitkeep b/src/Domain/Shared/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/API/WordPress/MigrationCommand.php b/src/Infrastructure/API/WordPress/MigrationCommand.php deleted file mode 100644 index c53fed7a..00000000 --- a/src/Infrastructure/API/WordPress/MigrationCommand.php +++ /dev/null @@ -1,189 +0,0 @@ -simulateMigration($migrator); - return; - } - - // Ejecutar migración real - $start_time = microtime(true); - $result = $migrator->migrate(); - $end_time = microtime(true); - $duration = round($end_time - $start_time, 2); - - \WP_CLI::line(''); - - if ($result['success']) { - \WP_CLI::success('✅ ' . $result['message']); - \WP_CLI::line(''); - \WP_CLI::line('📊 Estadísticas de Migración:'); - \WP_CLI::line(' ├─ Components migrados: ' . ($result['stats']['components']['migrated'] ?? 0)); - \WP_CLI::line(' ├─ Defaults migrados: ' . ($result['stats']['defaults']['migrated'] ?? 0)); - \WP_CLI::line(' ├─ Tiempo de ejecución: ' . $duration . 's'); - \WP_CLI::line(' └─ Validación: ' . $result['stats']['validation']['message']); - \WP_CLI::line(''); - \WP_CLI::line('⚠️ IMPORTANTE: Tablas legacy respaldadas con sufijo _backup'); - \WP_CLI::line(' Validar funcionamiento por 7-30 días antes de limpiar backup.'); - \WP_CLI::line(' Comando para limpiar: wp roi-theme cleanup-backup'); - \WP_CLI::line(''); - } else { - \WP_CLI::error('❌ ' . $result['message']); - } - } - - /** - * Limpiar tablas de backup después de validar migración - * - * IMPORTANTE: Solo ejecutar después de validar que todo funciona correctamente - * - * ## EJEMPLOS - * - * wp roi-theme cleanup-backup - * - * @param array $args Argumentos posicionales - * @param array $assoc_args Argumentos asociativos - * @return void - */ - public function cleanup_backup(array $args, array $assoc_args): void - { - global $wpdb; - - \WP_CLI::line(''); - \WP_CLI::confirm( - '⚠️ ¿Estás seguro de eliminar las tablas de backup? Esta acción es IRREVERSIBLE.', - $assoc_args - ); - - $migrator = new DatabaseMigrator($wpdb); - $migrator->cleanupBackup(); - - \WP_CLI::line(''); - \WP_CLI::success('✅ Tablas de backup eliminadas correctamente'); - \WP_CLI::line(''); - } - - /** - * Simular migración sin hacer cambios reales - * - * @param DatabaseMigrator $migrator Instancia del migrador - * @return void - */ - private function simulateMigration(DatabaseMigrator $migrator): void - { - global $wpdb; - - \WP_CLI::line('Verificando tablas legacy...'); - - // Verificar existencia de tablas - $legacy_components = $wpdb->prefix . 'roi_theme_components'; - $legacy_defaults = $wpdb->prefix . 'roi_theme_components_defaults'; - - $components_exist = $wpdb->get_var("SHOW TABLES LIKE '{$legacy_components}'") === $legacy_components; - $defaults_exist = $wpdb->get_var("SHOW TABLES LIKE '{$legacy_defaults}'") === $legacy_defaults; - - if (!$components_exist || !$defaults_exist) { - \WP_CLI::error('Tablas legacy no encontradas. No hay nada que migrar.'); - return; - } - - \WP_CLI::line('✓ Tablas legacy encontradas'); - - // Contar registros - $components_count = $wpdb->get_var("SELECT COUNT(*) FROM {$legacy_components}"); - $defaults_count = $wpdb->get_var("SELECT COUNT(*) FROM {$legacy_defaults}"); - - \WP_CLI::line(''); - \WP_CLI::line('📊 Datos a migrar:'); - \WP_CLI::line(" ├─ Components: {$components_count} registros"); - \WP_CLI::line(" └─ Defaults: {$defaults_count} registros"); - \WP_CLI::line(''); - \WP_CLI::line('Operaciones que se ejecutarían:'); - \WP_CLI::line(' 1. Crear tablas v2 con nueva estructura (config_group)'); - \WP_CLI::line(' 2. Migrar ' . ($components_count + $defaults_count) . ' registros'); - \WP_CLI::line(' 3. Validar integridad de datos'); - \WP_CLI::line(' 4. Renombrar tablas (legacy → _backup, v2 → producción)'); - \WP_CLI::line(''); - - \WP_CLI::success('✅ Simulación completada. Ejecutar sin --dry-run para migración real.'); - \WP_CLI::line(''); - } -} - -// Registrar comando WP-CLI -if (defined('WP_CLI') && WP_CLI) { - \WP_CLI::add_command('roi-theme', MigrationCommand::class); -} diff --git a/src/Infrastructure/DI/DIContainer.php b/src/Infrastructure/DI/DIContainer.php deleted file mode 100644 index 30a9f8c4..00000000 --- a/src/Infrastructure/DI/DIContainer.php +++ /dev/null @@ -1,153 +0,0 @@ - */ - private array $services = []; - - /** - * Private constructor to prevent direct instantiation - */ - private function __construct() - { - // Initialize services here - } - - /** - * Get singleton instance - * - * @return self - */ - public static function getInstance(): self - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Prevent cloning - */ - private function __clone() - { - } - - /** - * Prevent unserialization - * - * @throws \Exception - */ - public function __wakeup(): void - { - throw new \Exception('Cannot unserialize singleton'); - } - - /** - * Get Component Repository - * - * @return ComponentRepositoryInterface - */ - public function getComponentRepository(): ComponentRepositoryInterface - { - if (!isset($this->services['component_repository'])) { - // Placeholder - will be replaced in Phase 5 - throw new \RuntimeException('ComponentRepository not implemented yet. Will be available in Phase 5.'); - } - - return $this->services['component_repository']; - } - - /** - * Get Validation Service - * - * @return ValidationServiceInterface - */ - public function getValidationService(): ValidationServiceInterface - { - if (!isset($this->services['validation_service'])) { - // Placeholder - will be replaced in Phase 5 - throw new \RuntimeException('ValidationService not implemented yet. Will be available in Phase 5.'); - } - - return $this->services['validation_service']; - } - - /** - * Get Cache Service - * - * @return CacheServiceInterface - */ - public function getCacheService(): CacheServiceInterface - { - if (!isset($this->services['cache_service'])) { - // Placeholder - will be replaced in Phase 5 - throw new \RuntimeException('CacheService not implemented yet. Will be available in Phase 5.'); - } - - return $this->services['cache_service']; - } - - /** - * Register a service - * - * @param string $key Service identifier - * @param object $service Service instance - * @return void - */ - public function set(string $key, object $service): void - { - $this->services[$key] = $service; - } - - /** - * Check if service exists - * - * @param string $key Service identifier - * @return bool - */ - public function has(string $key): bool - { - return isset($this->services[$key]); - } - - /** - * Get a service by key - * - * @param string $key Service identifier - * @return object|null - */ - public function get(string $key): ?object - { - return $this->services[$key] ?? null; - } - - /** - * Reset container (useful for testing) - * - * @return void - */ - public static function reset(): void - { - self::$instance = null; - } -} diff --git a/src/Infrastructure/Facades/.gitkeep b/src/Infrastructure/Facades/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/Persistence/WordPress/DatabaseMigrator.php b/src/Infrastructure/Persistence/WordPress/DatabaseMigrator.php deleted file mode 100644 index c340eaab..00000000 --- a/src/Infrastructure/Persistence/WordPress/DatabaseMigrator.php +++ /dev/null @@ -1,515 +0,0 @@ -migrate(); - * - * if ($result['success']) { - * echo "Migración exitosa: " . $result['stats']['components']['migrated'] . " registros"; - * } else { - * echo "Error: " . $result['message']; - * } - * ``` - */ -final class DatabaseMigrator -{ - /** - * @var \wpdb Instancia de WordPress Database - */ - private \wpdb $wpdb; - - /** - * @var string Charset y Collation de la BD - */ - private string $charset_collate; - - /** - * @var array Log de operaciones ejecutadas - */ - private array $log = []; - - /** - * Constructor - * - * @param \wpdb $wpdb Instancia de WordPress Database - */ - public function __construct(\wpdb $wpdb) - { - $this->wpdb = $wpdb; - $this->charset_collate = $wpdb->get_charset_collate(); - } - - /** - * Ejecutar migración completa de BD - * - * @return array{success: bool, message: string, stats: array, log: array} - */ - public function migrate(): array - { - $this->log('🚀 Iniciando migración de base de datos'); - - try { - // 1. Verificar que tablas legacy existen - if (!$this->legacyTablesExist()) { - return [ - 'success' => false, - 'message' => 'Tablas legacy no encontradas. No hay nada que migrar.', - 'stats' => [], - 'log' => $this->log - ]; - } - - $this->log('✓ Tablas legacy encontradas'); - - // 2. Crear tablas v2 con nueva estructura - $this->createV2Tables(); - $this->log('✓ Tablas v2 creadas con nueva estructura'); - - // 3. Migrar datos de components - $componentsStats = $this->migrateComponentsData(); - $this->log("✓ Components migrados: {$componentsStats['migrated']} registros"); - - // 4. Migrar datos de defaults - $defaultsStats = $this->migrateDefaultsData(); - $this->log("✓ Defaults migrados: {$defaultsStats['migrated']} registros"); - - // 5. Validar integridad de datos - $validation = $this->validateMigration(); - - if (!$validation['success']) { - throw new \RuntimeException( - 'Validación de migración falló: ' . $validation['message'] - ); - } - - $this->log('✓ Validación de integridad exitosa'); - - // 6. Hacer swap de tablas - $this->swapTables(); - $this->log('✓ Swap de tablas completado (legacy → _backup, v2 → producción)'); - - // 7. Guardar metadata de migración - update_option('roi_theme_migration_date', current_time('mysql')); - update_option('roi_theme_migration_stats', [ - 'components' => $componentsStats, - 'defaults' => $defaultsStats - ]); - - $this->log('✅ Migración completada exitosamente'); - - return [ - 'success' => true, - 'message' => 'Migración completada exitosamente', - 'stats' => [ - 'components' => $componentsStats, - 'defaults' => $defaultsStats, - 'validation' => $validation - ], - 'log' => $this->log - ]; - - } catch (\Exception $e) { - $this->log('❌ Error durante migración: ' . $e->getMessage()); - - // Rollback en caso de error - $this->rollback(); - - return [ - 'success' => false, - 'message' => 'Error durante migración: ' . $e->getMessage(), - 'stats' => [], - 'log' => $this->log - ]; - } - } - - /** - * Verificar si las tablas legacy existen - * - * @return bool - */ - private function legacyTablesExist(): bool - { - $legacy_components = $this->wpdb->prefix . 'roi_theme_components'; - $legacy_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults'; - - $components_exist = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$legacy_components}'" - ) === $legacy_components; - - $defaults_exist = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$legacy_defaults}'" - ) === $legacy_defaults; - - return $components_exist && $defaults_exist; - } - - /** - * Crear tablas v2 con nueva estructura - * - * @return void - */ - private function createV2Tables(): void - { - require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); - - $components_v2 = $this->wpdb->prefix . 'roi_theme_components_v2'; - $defaults_v2 = $this->wpdb->prefix . 'roi_theme_components_defaults_v2'; - - // Estructura mejorada con config_group - $table_structure = " - id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - component_name VARCHAR(50) NOT NULL, - config_group VARCHAR(50) NOT NULL, - config_key VARCHAR(100) NOT NULL, - config_value TEXT NOT NULL, - data_type VARCHAR(20) NOT NULL DEFAULT 'string', - schema_version VARCHAR(10) NOT NULL DEFAULT '1.0.0', - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY component_config (component_name, config_group, config_key), - INDEX idx_component (component_name), - INDEX idx_group (component_name, config_group), - INDEX idx_schema_version (component_name, schema_version), - INDEX idx_config_key (config_key) - "; - - $sql_components = "CREATE TABLE {$components_v2} ({$table_structure}) {$this->charset_collate};"; - $sql_defaults = "CREATE TABLE {$defaults_v2} ({$table_structure}) {$this->charset_collate};"; - - dbDelta($sql_components); - dbDelta($sql_defaults); - } - - /** - * Migrar datos de components legacy a v2 - * - * @return array{migrated: int, skipped: int, errors: array} - */ - private function migrateComponentsData(): array - { - $legacy_table = $this->wpdb->prefix . 'roi_theme_components'; - $v2_table = $this->wpdb->prefix . 'roi_theme_components_v2'; - - return $this->migrateTableData($legacy_table, $v2_table); - } - - /** - * Migrar datos de defaults legacy a v2 - * - * @return array{migrated: int, skipped: int, errors: array} - */ - private function migrateDefaultsData(): array - { - $legacy_table = $this->wpdb->prefix . 'roi_theme_components_defaults'; - $v2_table = $this->wpdb->prefix . 'roi_theme_components_defaults_v2'; - - return $this->migrateTableData($legacy_table, $v2_table); - } - - /** - * Migrar datos de una tabla legacy a v2 - * - * @param string $legacy_table Nombre de tabla legacy - * @param string $v2_table Nombre de tabla v2 - * @return array{migrated: int, skipped: int, errors: array} - */ - private function migrateTableData(string $legacy_table, string $v2_table): array - { - // Obtener todos los registros legacy - $legacy_rows = $this->wpdb->get_results( - "SELECT * FROM {$legacy_table}", - ARRAY_A - ); - - if (empty($legacy_rows)) { - return ['migrated' => 0, 'skipped' => 0, 'errors' => []]; - } - - $migrated = 0; - $skipped = 0; - $errors = []; - - foreach ($legacy_rows as $row) { - try { - // Inferir config_group desde config_key - $group = $this->inferGroupFromKey($row['config_key']); - - // Preparar datos para inserción - $data = [ - 'component_name' => $row['component_name'], - 'config_group' => $group, - 'config_key' => $row['config_key'], - 'config_value' => $row['config_value'], - 'data_type' => $row['data_type'], - 'schema_version' => $row['version'] ?? '1.0.0', - 'created_at' => $row['created_at'], - 'updated_at' => $row['updated_at'] - ]; - - // Insertar en tabla v2 - $result = $this->wpdb->insert( - $v2_table, - $data, - ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'] - ); - - if ($result !== false) { - $migrated++; - } else { - $skipped++; - $errors[] = "Error migrando: {$row['component_name']}.{$row['config_key']}"; - } - - } catch (\Exception $e) { - $skipped++; - $errors[] = "Excepción migrando {$row['component_name']}.{$row['config_key']}: " . $e->getMessage(); - } - } - - return [ - 'migrated' => $migrated, - 'skipped' => $skipped, - 'errors' => $errors - ]; - } - - /** - * Inferir grupo de configuración desde la clave - * - * HEURÍSTICA: - * - enabled, visible_* → visibility - * - message_*, cta_*, title_* → content - * - *_color, *_height, *_width, *_size → styles - * - Resto → general - * - * @param string $key Clave de configuración - * @return string Grupo inferido - */ - private function inferGroupFromKey(string $key): string - { - // Visibility - if (in_array($key, ['enabled', 'visible_desktop', 'visible_mobile', 'visible_tablet'], true)) { - return 'visibility'; - } - - // Content - if (str_starts_with($key, 'message_') || - str_starts_with($key, 'cta_') || - str_starts_with($key, 'title_')) { - return 'content'; - } - - // Styles - if (str_ends_with($key, '_color') || - str_ends_with($key, '_height') || - str_ends_with($key, '_width') || - str_ends_with($key, '_size') || - str_ends_with($key, '_font')) { - return 'styles'; - } - - // Fallback - return 'general'; - } - - /** - * Validar integridad de la migración - * - * @return array{success: bool, message: string, details: array} - */ - private function validateMigration(): array - { - $legacy_components = $this->wpdb->prefix . 'roi_theme_components'; - $legacy_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults'; - $v2_components = $this->wpdb->prefix . 'roi_theme_components_v2'; - $v2_defaults = $this->wpdb->prefix . 'roi_theme_components_defaults_v2'; - - $details = []; - - // 1. Validar cantidad de registros components - $legacy_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$legacy_components}"); - $v2_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$v2_components}"); - - $details['components_count'] = [ - 'legacy' => (int) $legacy_count, - 'v2' => (int) $v2_count, - 'match' => $legacy_count == $v2_count - ]; - - if ($legacy_count != $v2_count) { - return [ - 'success' => false, - 'message' => "Mismatch en cantidad de registros components: legacy={$legacy_count}, v2={$v2_count}", - 'details' => $details - ]; - } - - // 2. Validar cantidad de registros defaults - $legacy_defaults_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$legacy_defaults}"); - $v2_defaults_count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$v2_defaults}"); - - $details['defaults_count'] = [ - 'legacy' => (int) $legacy_defaults_count, - 'v2' => (int) $v2_defaults_count, - 'match' => $legacy_defaults_count == $v2_defaults_count - ]; - - if ($legacy_defaults_count != $v2_defaults_count) { - return [ - 'success' => false, - 'message' => "Mismatch en cantidad de registros defaults: legacy={$legacy_defaults_count}, v2={$v2_defaults_count}", - 'details' => $details - ]; - } - - // 3. Verificar componentes únicos - $legacy_component_names = $this->wpdb->get_col( - "SELECT DISTINCT component_name FROM {$legacy_components}" - ); - $v2_component_names = $this->wpdb->get_col( - "SELECT DISTINCT component_name FROM {$v2_components}" - ); - - $missing_components = array_diff($legacy_component_names, $v2_component_names); - - if (count($missing_components) > 0) { - return [ - 'success' => false, - 'message' => "Faltan componentes en v2: " . implode(', ', $missing_components), - 'details' => $details - ]; - } - - // 4. Verificar grupos válidos - $invalid_groups = $this->wpdb->get_col( - "SELECT DISTINCT config_group FROM {$v2_components} - WHERE config_group NOT IN ('visibility', 'content', 'styles', 'general')" - ); - - if (count($invalid_groups) > 0) { - return [ - 'success' => false, - 'message' => "Grupos inválidos encontrados: " . implode(', ', $invalid_groups), - 'details' => $details - ]; - } - - return [ - 'success' => true, - 'message' => "Validación exitosa: {$legacy_count} components + {$legacy_defaults_count} defaults migrados correctamente", - 'details' => $details - ]; - } - - /** - * Hacer swap de tablas (legacy → backup, v2 → producción) - * - * @return void - */ - private function swapTables(): void - { - // Swap components - $this->wpdb->query( - "RENAME TABLE - {$this->wpdb->prefix}roi_theme_components TO {$this->wpdb->prefix}roi_theme_components_backup, - {$this->wpdb->prefix}roi_theme_components_v2 TO {$this->wpdb->prefix}roi_theme_components" - ); - - // Swap defaults - $this->wpdb->query( - "RENAME TABLE - {$this->wpdb->prefix}roi_theme_components_defaults TO {$this->wpdb->prefix}roi_theme_components_defaults_backup, - {$this->wpdb->prefix}roi_theme_components_defaults_v2 TO {$this->wpdb->prefix}roi_theme_components_defaults" - ); - - // Guardar timestamp de backup - update_option('roi_theme_migration_backup_date', current_time('mysql')); - } - - /** - * Rollback: Eliminar tablas v2 en caso de error - * - * @return void - */ - private function rollback(): void - { - $this->log('⚠️ Ejecutando rollback...'); - - // Eliminar tablas v2 si existen - $this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_v2"); - $this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_defaults_v2"); - - $this->log('✓ Rollback completado: tablas v2 eliminadas'); - } - - /** - * Limpiar tablas de backup (ejecutar después de validar migración) - * - * @return void - */ - public function cleanupBackup(): void - { - $this->log('🗑️ Eliminando tablas de backup...'); - - $this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_backup"); - $this->wpdb->query("DROP TABLE IF EXISTS {$this->wpdb->prefix}roi_theme_components_defaults_backup"); - - delete_option('roi_theme_migration_backup_date'); - - $this->log('✓ Tablas de backup eliminadas'); - } - - /** - * Agregar entrada al log - * - * @param string $message Mensaje a registrar - * @return void - */ - private function log(string $message): void - { - $timestamp = current_time('Y-m-d H:i:s'); - $entry = "[{$timestamp}] {$message}"; - - $this->log[] = $entry; - error_log('ROI Theme Migration: ' . $message); - } - - /** - * Obtener log completo de operaciones - * - * @return array - */ - public function getLog(): array - { - return $this->log; - } -} diff --git a/src/Infrastructure/Persistence/WordPress/Repositories/.gitkeep b/src/Infrastructure/Persistence/WordPress/Repositories/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/Presentation/Admin/FormBuilders/.gitkeep b/src/Infrastructure/Presentation/Admin/FormBuilders/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/Presentation/Public/Renderers/.gitkeep b/src/Infrastructure/Presentation/Public/Renderers/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/README.md b/src/Infrastructure/README.md deleted file mode 100644 index 1757d0b9..00000000 --- a/src/Infrastructure/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Capa de Infraestructura - -## Propósito - -La capa de Infraestructura **implementa** los detalles técnicos: persistencia, APIs, servicios externos, frameworks. Es la capa más externa y depende de las capas internas. - -## Principios - -1. **Implementación**: Implementa interfaces definidas en Dominio/Aplicación -2. **Frameworks**: Aquí vive el código específico de WordPress -3. **Adaptación**: Adapta datos entre formatos externos e internos -4. **Dependencia Invertida**: Depende de abstracciones del dominio - -## Estructura - -``` -Infrastructure/ -├── Persistence/ # Acceso a datos -│ └── WordPress/ -│ ├── WordPressComponentRepository.php -│ └── DatabaseMigrator.php -├── API/ # Controllers/Endpoints -│ └── WordPress/ -│ ├── AjaxController.php -│ └── RestController.php -├── Services/ # Servicios de infraestructura -│ ├── WordPressValidationService.php -│ ├── WordPressCacheService.php -│ └── WordPressComponentFacade.php -├── DI/ # Dependency Injection -│ └── DIContainer.php -└── UI/ # Vistas y assets - ├── Views/ - │ └── components/ - └── Assets/ - ├── css/ - └── js/ -``` - -## Reglas - -1. **SÍ** usar código de WordPress aquí -2. **SÍ** implementar interfaces del dominio/aplicación -3. **SÍ** adaptar datos entre WordPress y entidades -4. **NO** exponer detalles de implementación hacia arriba -5. **NO** contener lógica de negocio diff --git a/src/Infrastructure/UI/Assets/.gitkeep b/src/Infrastructure/UI/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Infrastructure/UI/Views/.gitkeep b/src/Infrastructure/UI/Views/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/E2E/.gitkeep b/tests/E2E/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Integration/.gitkeep b/tests/Integration/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Integration/Infrastructure/DatabaseMigratorTest.php b/tests/Integration/Infrastructure/DatabaseMigratorTest.php deleted file mode 100644 index 50c4a986..00000000 --- a/tests/Integration/Infrastructure/DatabaseMigratorTest.php +++ /dev/null @@ -1,365 +0,0 @@ -markTestSkipped('WordPress not loaded - skipping integration tests'); - return; - } - - $this->wpdb = $wpdb; - $this->prefix = $wpdb->prefix; - $this->migrator = new DatabaseMigrator($wpdb); - - // Limpiar tablas anteriores - $this->cleanupTables(); - - // Crear tablas legacy con datos de prueba - $this->createLegacyTables(); - $this->seedLegacyData(); - } - - /** - * Teardown después de cada test - */ - protected function tearDown(): void - { - $this->cleanupTables(); - } - - /** - * Test: La migración crea tablas v2 correctamente - * - * @test - */ - public function it_creates_v2_tables(): void - { - $result = $this->migrator->migrate(); - - $this->assertTrue($result['success'], $result['message']); - - // Verificar que tabla components existe - $components_table = $this->prefix . 'roi_theme_components'; - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$components_table}'" - ) === $components_table; - - $this->assertTrue($table_exists, 'Tabla components no existe después de migración'); - - // Verificar que tabla defaults existe - $defaults_table = $this->prefix . 'roi_theme_components_defaults'; - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$defaults_table}'" - ) === $defaults_table; - - $this->assertTrue($table_exists, 'Tabla defaults no existe después de migración'); - } - - /** - * Test: La migración preserva la cantidad de registros - * - * @test - */ - public function it_preserves_record_count(): void - { - // Contar antes de migración - $legacy_count = $this->wpdb->get_var( - "SELECT COUNT(*) FROM {$this->prefix}roi_theme_components" - ); - - // Ejecutar migración - $result = $this->migrator->migrate(); - $this->assertTrue($result['success']); - - // Contar después de migración - $v2_count = $this->wpdb->get_var( - "SELECT COUNT(*) FROM {$this->prefix}roi_theme_components" - ); - - $this->assertEquals( - $legacy_count, - $v2_count, - "Cantidad de registros no coincide: legacy={$legacy_count}, v2={$v2_count}" - ); - } - - /** - * Test: La migración infiere grupos correctamente - * - * @test - */ - public function it_infers_config_groups_correctly(): void - { - $result = $this->migrator->migrate(); - $this->assertTrue($result['success']); - - // Verificar que "enabled" se migró a grupo "visibility" - $group = $this->wpdb->get_var( - "SELECT config_group FROM {$this->prefix}roi_theme_components - WHERE config_key = 'enabled' LIMIT 1" - ); - - $this->assertEquals('visibility', $group); - - // Verificar que "message_text" se migró a grupo "content" - $group = $this->wpdb->get_var( - "SELECT config_group FROM {$this->prefix}roi_theme_components - WHERE config_key = 'message_text' LIMIT 1" - ); - - $this->assertEquals('content', $group); - - // Verificar que "background_color" se migró a grupo "styles" - $group = $this->wpdb->get_var( - "SELECT config_group FROM {$this->prefix}roi_theme_components - WHERE config_key = 'background_color' LIMIT 1" - ); - - $this->assertEquals('styles', $group); - } - - /** - * Test: La migración crea backup de tablas legacy - * - * @test - */ - public function it_creates_backup_tables(): void - { - $result = $this->migrator->migrate(); - $this->assertTrue($result['success']); - - // Verificar que tabla backup existe - $backup_table = $this->prefix . 'roi_theme_components_backup'; - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$backup_table}'" - ) === $backup_table; - - $this->assertTrue($table_exists, 'Tabla backup no fue creada'); - - // Verificar que backup tiene datos - $backup_count = $this->wpdb->get_var( - "SELECT COUNT(*) FROM {$backup_table}" - ); - - $this->assertGreaterThan(0, $backup_count, 'Tabla backup está vacía'); - } - - /** - * Test: Rollback elimina tablas v2 en caso de error - * - * @test - */ - public function it_rolls_back_on_error(): void - { - // Crear escenario de error: tabla legacy vacía después de crear v2 - $this->wpdb->query("DELETE FROM {$this->prefix}roi_theme_components"); - - $result = $this->migrator->migrate(); - - // La migración debe fallar por mismatch de conteo - $this->assertFalse($result['success']); - - // Verificar que tablas v2 fueron eliminadas - $v2_table = $this->prefix . 'roi_theme_components_v2'; - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$v2_table}'" - ) === $v2_table; - - $this->assertFalse($table_exists, 'Tabla v2 no fue eliminada en rollback'); - } - - /** - * Test: cleanup_backup elimina tablas de respaldo - * - * @test - */ - public function it_removes_backup_tables(): void - { - // Ejecutar migración - $result = $this->migrator->migrate(); - $this->assertTrue($result['success']); - - // Verificar que backup existe - $backup_table = $this->prefix . 'roi_theme_components_backup'; - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$backup_table}'" - ) === $backup_table; - $this->assertTrue($table_exists); - - // Ejecutar cleanup - $this->migrator->cleanupBackup(); - - // Verificar que backup fue eliminado - $table_exists = $this->wpdb->get_var( - "SHOW TABLES LIKE '{$backup_table}'" - ) === $backup_table; - - $this->assertFalse($table_exists, 'Tabla backup no fue eliminada'); - } - - /** - * Helper: Crear tablas legacy para testing - */ - private function createLegacyTables(): void - { - if (!defined('ABSPATH')) { - return; - } - - require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); - - $charset_collate = $this->wpdb->get_charset_collate(); - - $components_sql = "CREATE TABLE {$this->prefix}roi_theme_components ( - id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - component_name VARCHAR(50) NOT NULL, - config_key VARCHAR(100) NOT NULL, - config_value TEXT NOT NULL, - data_type VARCHAR(20) NOT NULL DEFAULT 'string', - version VARCHAR(10) NOT NULL DEFAULT '1.0.0', - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL, - PRIMARY KEY (id), - UNIQUE KEY component_config (component_name, config_key) - ) {$charset_collate};"; - - $defaults_sql = "CREATE TABLE {$this->prefix}roi_theme_components_defaults ( - id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, - component_name VARCHAR(50) NOT NULL, - config_key VARCHAR(100) NOT NULL, - config_value TEXT NOT NULL, - data_type VARCHAR(20) NOT NULL DEFAULT 'string', - version VARCHAR(10) NOT NULL DEFAULT '1.0.0', - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL, - PRIMARY KEY (id), - UNIQUE KEY component_config (component_name, config_key) - ) {$charset_collate};"; - - dbDelta($components_sql); - dbDelta($defaults_sql); - } - - /** - * Helper: Insertar datos de prueba en tablas legacy - */ - private function seedLegacyData(): void - { - $timestamp = current_time('mysql'); - - // Components - $components_data = [ - ['top_bar', 'enabled', '1', 'boolean'], - ['top_bar', 'message_text', 'Welcome!', 'string'], - ['top_bar', 'background_color', '#000000', 'string'], - ['footer', 'enabled', '1', 'boolean'], - ['footer', 'cta_url', 'https://example.com', 'string'], - ['footer', 'cta_text', 'Click here', 'string'], - ]; - - foreach ($components_data as $data) { - $this->wpdb->insert( - $this->prefix . 'roi_theme_components', - [ - 'component_name' => $data[0], - 'config_key' => $data[1], - 'config_value' => $data[2], - 'data_type' => $data[3], - 'version' => '1.0.0', - 'created_at' => $timestamp, - 'updated_at' => $timestamp - ], - ['%s', '%s', '%s', '%s', '%s', '%s', '%s'] - ); - } - - // Defaults (misma estructura) - $this->wpdb->insert( - $this->prefix . 'roi_theme_components_defaults', - [ - 'component_name' => 'top_bar', - 'config_key' => 'enabled', - 'config_value' => '1', - 'data_type' => 'boolean', - 'version' => '1.0.0', - 'created_at' => $timestamp, - 'updated_at' => $timestamp - ], - ['%s', '%s', '%s', '%s', '%s', '%s', '%s'] - ); - } - - /** - * Helper: Limpiar todas las tablas del test - */ - private function cleanupTables(): void - { - $tables = [ - 'roi_theme_components', - 'roi_theme_components_defaults', - 'roi_theme_components_v2', - 'roi_theme_components_defaults_v2', - 'roi_theme_components_backup', - 'roi_theme_components_defaults_backup' - ]; - - foreach ($tables as $table) { - $this->wpdb->query("DROP TABLE IF EXISTS {$this->prefix}{$table}"); - } - - // Limpiar opciones - delete_option('roi_theme_migration_date'); - delete_option('roi_theme_migration_backup_date'); - delete_option('roi_theme_migration_stats'); - } -} diff --git a/tests/Unit/Application/UseCases/.gitkeep b/tests/Unit/Application/UseCases/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Unit/Domain/Component/.gitkeep b/tests/Unit/Domain/Component/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index dd5fb5ce..00000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,30 +0,0 @@ -assertTrue(true, 'PHPUnit está funcionando correctamente'); - } - - public function testPhpVersion(): void - { - $version = PHP_VERSION; - $this->assertGreaterThanOrEqual('8.0.0', $version, 'PHP debe ser versión 8.0 o superior'); - } - - public function testComposerAutoloadIsLoaded(): void - { - $this->assertTrue( - class_exists('PHPUnit\Framework\TestCase'), - 'Composer autoload está funcionando' - ); - } -} diff --git a/tests/Unit/Infrastructure/DI/DIContainerTest.php b/tests/Unit/Infrastructure/DI/DIContainerTest.php deleted file mode 100644 index 10f00289..00000000 --- a/tests/Unit/Infrastructure/DI/DIContainerTest.php +++ /dev/null @@ -1,175 +0,0 @@ -assertSame($instance1, $instance2, 'getInstance() should return the same instance'); - } - - /** - * @test - */ - public function it_should_prevent_cloning(): void - { - $this->expectError(); - $this->expectErrorMessage('Call to private'); - - $container = DIContainer::getInstance(); - $clone = clone $container; // This should trigger an error - } - - /** - * @test - */ - public function it_should_prevent_unserialization(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Cannot unserialize singleton'); - - $container = DIContainer::getInstance(); - $serialized = serialize($container); - unserialize($serialized); - } - - /** - * @test - */ - public function it_should_register_and_retrieve_service(): void - { - $container = DIContainer::getInstance(); - $service = new \stdClass(); - $service->name = 'Test Service'; - - $container->set('test_service', $service); - - $this->assertTrue($container->has('test_service'), 'Container should have the registered service'); - $this->assertSame($service, $container->get('test_service'), 'Should retrieve the same service instance'); - } - - /** - * @test - */ - public function it_should_return_null_for_non_existent_service(): void - { - $container = DIContainer::getInstance(); - - $this->assertFalse($container->has('non_existent'), 'Container should not have non-existent service'); - $this->assertNull($container->get('non_existent'), 'Should return null for non-existent service'); - } - - /** - * @test - */ - public function it_should_throw_exception_for_unimplemented_component_repository(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('ComponentRepository not implemented yet'); - - $container = DIContainer::getInstance(); - $container->getComponentRepository(); - } - - /** - * @test - */ - public function it_should_throw_exception_for_unimplemented_validation_service(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('ValidationService not implemented yet'); - - $container = DIContainer::getInstance(); - $container->getValidationService(); - } - - /** - * @test - */ - public function it_should_throw_exception_for_unimplemented_cache_service(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('CacheService not implemented yet'); - - $container = DIContainer::getInstance(); - $container->getCacheService(); - } - - /** - * @test - */ - public function it_should_reset_singleton_instance(): void - { - $instance1 = DIContainer::getInstance(); - $instance1->set('test', new \stdClass()); - - DIContainer::reset(); - - $instance2 = DIContainer::getInstance(); - - $this->assertNotSame($instance1, $instance2, 'Reset should create a new instance'); - $this->assertFalse($instance2->has('test'), 'New instance should not have old services'); - } - - /** - * @test - */ - public function it_should_manage_multiple_services(): void - { - $container = DIContainer::getInstance(); - - $service1 = new \stdClass(); - $service1->name = 'Service 1'; - - $service2 = new \stdClass(); - $service2->name = 'Service 2'; - - $service3 = new \stdClass(); - $service3->name = 'Service 3'; - - $container->set('service1', $service1); - $container->set('service2', $service2); - $container->set('service3', $service3); - - $this->assertTrue($container->has('service1')); - $this->assertTrue($container->has('service2')); - $this->assertTrue($container->has('service3')); - - $this->assertSame($service1, $container->get('service1')); - $this->assertSame($service2, $container->get('service2')); - $this->assertSame($service3, $container->get('service3')); - } -} diff --git a/tests/Unit/Infrastructure/Persistence/.gitkeep b/tests/Unit/Infrastructure/Persistence/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Unit/Infrastructure/Services/.gitkeep b/tests/Unit/Infrastructure/Services/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/_bootstrap/bootstrap.php b/tests/_bootstrap/bootstrap.php deleted file mode 100644 index 5deafdc4..00000000 --- a/tests/_bootstrap/bootstrap.php +++ /dev/null @@ -1,21 +0,0 @@ -