addInfo("Validando sincronización JSON→BD para: {$componentName}"); // 1. Verificar que schema JSON existe $schemaPath = $themePath . '/Schemas/' . $componentName . '.json'; if (!file_exists($schemaPath)) { $result->addError("Schema JSON no encontrado: {$schemaPath}"); $result->addInfo("Ejecutar primero: wp roi-theme sync-component {$componentName}"); return $result; } // 2. Parsear JSON $jsonContent = file_get_contents($schemaPath); $schema = json_decode($jsonContent, true); if (json_last_error() !== JSON_ERROR_NONE) { $result->addError("JSON inválido: " . json_last_error_msg()); return $result; } // 3. Verificar que tabla existe $tableName = $wpdb->prefix . 'roi_theme_component_settings'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $tableExists = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = %s", $tableName )); if ($tableExists == 0) { $result->addError("Tabla '{$tableName}' no existe en la base de datos"); $result->addInfo("La tabla debería crearse automáticamente en functions.php"); return $result; } // 4. Obtener todos los campos del JSON $jsonFields = $this->extractFieldsFromSchema($schema); $totalJsonFields = count($jsonFields); // 5. Obtener todos los registros de BD para este componente // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $dbRecords = $wpdb->get_results($wpdb->prepare( "SELECT component_name, group_name, attribute_name, is_editable FROM {$tableName} WHERE component_name = %s", $componentName ), ARRAY_A); $totalDbRecords = count($dbRecords); // 6. Validar sincronización $this->validateSync($componentName, $jsonFields, $dbRecords, $result); // 7. Validar no hay duplicados $this->validateNoDuplicates($componentName, $tableName, $wpdb, $result); // Estadísticas $result->setStat('Schema JSON', "schemas/{$componentName}.json"); $result->setStat('Campos en JSON', $totalJsonFields); $result->setStat('Registros en BD', $totalDbRecords); $result->setStat('Tabla BD', $tableName); return $result; } /** * Extrae todos los campos del schema JSON * * @param array $schema * @return array Array de arrays con ['group' => '', 'attribute' => '', 'editable' => bool] */ private function extractFieldsFromSchema(array $schema): array { $fields = []; if (!isset($schema['groups'])) { return $fields; } foreach ($schema['groups'] as $groupName => $group) { if (!isset($group['fields'])) { continue; } foreach ($group['fields'] as $attributeName => $field) { $fields[] = [ 'group' => $groupName, 'attribute' => $attributeName, 'editable' => $field['editable'] ?? false, ]; } } return $fields; } private function validateSync(string $componentName, array $jsonFields, array $dbRecords, ValidationResult $result): void { // Crear índice de registros de BD para búsqueda rápida $dbIndex = []; foreach ($dbRecords as $record) { $key = $record['group_name'] . '.' . $record['attribute_name']; $dbIndex[$key] = $record; } // Validar que cada campo del JSON está en BD $missingInDb = []; $editableMismatch = []; foreach ($jsonFields as $field) { $key = $field['group'] . '.' . $field['attribute']; if (!isset($dbIndex[$key])) { $missingInDb[] = $key; } else { // Validar is_editable coincide $dbEditable = (bool) $dbIndex[$key]['is_editable']; $jsonEditable = $field['editable']; if ($dbEditable !== $jsonEditable) { $editableMismatch[] = "{$key} (JSON: " . ($jsonEditable ? 'true' : 'false') . ", BD: " . ($dbEditable ? 'true' : 'false') . ")"; } // Remover de índice para detectar huérfanos unset($dbIndex[$key]); } } // Campos faltantes en BD if (!empty($missingInDb)) { foreach ($missingInDb as $field) { $result->addError("Campo '{$field}' existe en JSON pero NO en BD"); } $result->addInfo("Ejecutar: wp roi-theme sync-component {$componentName}"); } // Campos huérfanos en BD (no están en JSON) if (!empty($dbIndex)) { foreach ($dbIndex as $key => $record) { $result->addWarning("Campo '{$key}' existe en BD pero NO en JSON (campo huérfano)"); } } // is_editable no coincide if (!empty($editableMismatch)) { foreach ($editableMismatch as $mismatch) { $result->addError("Campo {$mismatch} tiene is_editable diferente entre JSON y BD"); } $result->addInfo("Ejecutar: wp roi-theme sync-component {$componentName}"); } // Si todo está sincronizado if (empty($missingInDb) && empty($editableMismatch) && empty($dbIndex)) { $result->addInfo("✓ Todos los campos están sincronizados correctamente"); } } private function validateNoDuplicates(string $componentName, string $tableName, $wpdb, ValidationResult $result): void { // Buscar duplicados // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $duplicates = $wpdb->get_results($wpdb->prepare( "SELECT component_name, group_name, attribute_name, COUNT(*) as count FROM {$tableName} WHERE component_name = %s GROUP BY component_name, group_name, attribute_name HAVING count > 1", $componentName ), ARRAY_A); if (!empty($duplicates)) { foreach ($duplicates as $dup) { $result->addError( "Duplicado en BD: {$dup['group_name']}.{$dup['attribute_name']} " . "({$dup['count']} registros)" ); } $result->addInfo("El constraint UNIQUE debería prevenir duplicados. Revisar integridad de BD."); } } public function getPhaseNumber(): int|string { return 2; } public function getPhaseDescription(): string { return 'JSON→DB Sync'; } }