v0.1.8
Desarrollo y Extensión
Esta sección proporciona una guía completa para desarrolladores que deseen extender, personalizar o contribuir al desarrollo del Conector NewBytes.
🛠️ Configuración del Entorno de Desarrollo
Requisitos del Entorno
bash
# Versiones recomendadas
WordPress: 6.0+
WooCommerce: 7.0+
PHP: 8.0+
MySQL: 5.7+ / MariaDB: 10.3+
Node.js: 16+ (para herramientas de desarrollo)
Composer: 2.0+
# Versiones recomendadas
WordPress: 6.0+
WooCommerce: 7.0+
PHP: 8.0+
MySQL: 5.7+ / MariaDB: 10.3+
Node.js: 16+ (para herramientas de desarrollo)
Composer: 2.0+
Estructura de Desarrollo
woocommerce-newbytes/
├── .git/ # Control de versiones
├── .gitignore # Archivos ignorados
├── composer.json # Dependencias PHP
├── package.json # Dependencias Node.js
├── phpunit.xml # Configuración de tests
├── webpack.config.js # Configuración de build
├── src/ # Código fuente (si usas build)
│ ├── js/ # JavaScript fuente
│ ├── scss/ # Estilos fuente
│ └── php/ # PHP organizado por módulos
├── assets/ # Assets compilados
│ ├── css/ # CSS compilado
│ ├── js/ # JavaScript compilado
│ └── images/ # Imágenes
├── includes/ # Archivos PHP del plugin
├── tests/ # Tests automatizados
│ ├── unit/ # Tests unitarios
│ ├── integration/ # Tests de integración
│ └── fixtures/ # Datos de prueba
├── docs/ # Documentación adicional
└── logs-sync-nb/ # Logs (creado automáticamente)
woocommerce-newbytes/
├── .git/ # Control de versiones
├── .gitignore # Archivos ignorados
├── composer.json # Dependencias PHP
├── package.json # Dependencias Node.js
├── phpunit.xml # Configuración de tests
├── webpack.config.js # Configuración de build
├── src/ # Código fuente (si usas build)
│ ├── js/ # JavaScript fuente
│ ├── scss/ # Estilos fuente
│ └── php/ # PHP organizado por módulos
├── assets/ # Assets compilados
│ ├── css/ # CSS compilado
│ ├── js/ # JavaScript compilado
│ └── images/ # Imágenes
├── includes/ # Archivos PHP del plugin
├── tests/ # Tests automatizados
│ ├── unit/ # Tests unitarios
│ ├── integration/ # Tests de integración
│ └── fixtures/ # Datos de prueba
├── docs/ # Documentación adicional
└── logs-sync-nb/ # Logs (creado automáticamente)
Configuración de Desarrollo Local
Docker Compose Setup
yaml
# docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DEBUG: 1
volumes:
- ./:/var/www/html/wp-content/plugins/woocommerce-newbytes
- wordpress_data:/var/www/html
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
volumes:
wordpress_data:
db_data:
# docker-compose.yml
version: '3.8'
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DEBUG: 1
volumes:
- ./:/var/www/html/wp-content/plugins/woocommerce-newbytes
- wordpress_data:/var/www/html
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
volumes:
wordpress_data:
db_data:
Configuración de wp-config.php para Desarrollo
php
// wp-config.php - Configuración de desarrollo
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);
// Configuraciones específicas del plugin
define('NB_DEBUG_MODE', true);
define('NB_API_TIMEOUT', 30);
define('NB_LOG_LEVEL', 'debug');
define('NB_CACHE_DISABLED', true);
// wp-config.php - Configuración de desarrollo
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);
// Configuraciones específicas del plugin
define('NB_DEBUG_MODE', true);
define('NB_API_TIMEOUT', 30);
define('NB_LOG_LEVEL', 'debug');
define('NB_CACHE_DISABLED', true);
🏗️ Arquitectura de Desarrollo
Patrón de Diseño Utilizado
El plugin sigue el patrón Modular con separación de responsabilidades:
php
// Estructura modular
class NB_Plugin_Core {
private $modules = array();
public function __construct() {
$this->load_modules();
$this->init_hooks();
}
private function load_modules() {
$this->modules['auth'] = new NB_Auth_Module();
$this->modules['sync'] = new NB_Sync_Module();
$this->modules['logs'] = new NB_Logs_Module();
$this->modules['admin'] = new NB_Admin_Module();
}
private function init_hooks() {
foreach ($this->modules as $module) {
if (method_exists($module, 'init')) {
$module->init();
}
}
}
}
// Estructura modular
class NB_Plugin_Core {
private $modules = array();
public function __construct() {
$this->load_modules();
$this->init_hooks();
}
private function load_modules() {
$this->modules['auth'] = new NB_Auth_Module();
$this->modules['sync'] = new NB_Sync_Module();
$this->modules['logs'] = new NB_Logs_Module();
$this->modules['admin'] = new NB_Admin_Module();
}
private function init_hooks() {
foreach ($this->modules as $module) {
if (method_exists($module, 'init')) {
$module->init();
}
}
}
}
Módulos del Sistema
Módulo de Autenticación
php
// includes/modules/class-nb-auth-module.php
class NB_Auth_Module {
private $token_cache_key = 'nb_auth_token';
private $token_expiry = 3600; // 1 hora
public function init() {
add_action('wp_ajax_nb_test_connection', array($this, 'test_connection'));
add_filter('nb_api_headers', array($this, 'add_auth_headers'));
}
public function get_token($force_refresh = false) {
if (!$force_refresh) {
$cached_token = get_transient($this->token_cache_key);
if ($cached_token) {
return $cached_token;
}
}
$token = $this->request_new_token();
if ($token) {
set_transient($this->token_cache_key, $token, $this->token_expiry);
}
return $token;
}
private function request_new_token() {
$credentials = array(
'user' => get_option('nb_user'),
'password' => get_option('nb_password'),
'mode' => 'wp-extension',
'domain' => home_url()
);
$response = wp_remote_post(NB_API_URL . '/auth/login', array(
'headers' => array('Content-Type' => 'application/json'),
'body' => json_encode($credentials),
'timeout' => 10
));
if (is_wp_error($response)) {
NB_Logger::error('Auth request failed: ' . $response->get_error_message());
return false;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
return $body['token'] ?? false;
}
public function test_connection() {
check_ajax_referer('nb_test_connection', 'nonce');
$token = $this->get_token(true);
if ($token) {
wp_send_json_success(array('message' => 'Conexión exitosa'));
} else {
wp_send_json_error(array('message' => 'Error de conexión'));
}
}
}
// includes/modules/class-nb-auth-module.php
class NB_Auth_Module {
private $token_cache_key = 'nb_auth_token';
private $token_expiry = 3600; // 1 hora
public function init() {
add_action('wp_ajax_nb_test_connection', array($this, 'test_connection'));
add_filter('nb_api_headers', array($this, 'add_auth_headers'));
}
public function get_token($force_refresh = false) {
if (!$force_refresh) {
$cached_token = get_transient($this->token_cache_key);
if ($cached_token) {
return $cached_token;
}
}
$token = $this->request_new_token();
if ($token) {
set_transient($this->token_cache_key, $token, $this->token_expiry);
}
return $token;
}
private function request_new_token() {
$credentials = array(
'user' => get_option('nb_user'),
'password' => get_option('nb_password'),
'mode' => 'wp-extension',
'domain' => home_url()
);
$response = wp_remote_post(NB_API_URL . '/auth/login', array(
'headers' => array('Content-Type' => 'application/json'),
'body' => json_encode($credentials),
'timeout' => 10
));
if (is_wp_error($response)) {
NB_Logger::error('Auth request failed: ' . $response->get_error_message());
return false;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
return $body['token'] ?? false;
}
public function test_connection() {
check_ajax_referer('nb_test_connection', 'nonce');
$token = $this->get_token(true);
if ($token) {
wp_send_json_success(array('message' => 'Conexión exitosa'));
} else {
wp_send_json_error(array('message' => 'Error de conexión'));
}
}
}
Módulo de Sincronización
php
// includes/modules/class-nb-sync-module.php
class NB_Sync_Module {
private $batch_size = 50;
private $max_execution_time = 300;
public function init() {
add_action('nb_cron_sync_event', array($this, 'run_sync'));
add_action('wp_ajax_nb_manual_sync', array($this, 'manual_sync'));
}
public function run_sync($sync_type = 'automatic') {
$start_time = microtime(true);
// Configurar límites de ejecución
$this->setup_execution_limits();
try {
$result = $this->execute_sync($sync_type);
$this->log_sync_result($result, $start_time);
do_action('nb_after_sync', $sync_type, $result['stats'], $result);
} catch (Exception $e) {
NB_Logger::error('Sync failed: ' . $e->getMessage());
$this->log_sync_error($e, $start_time);
}
}
private function execute_sync($sync_type) {
$auth = NB_Auth_Module::get_instance();
$token = $auth->get_token();
if (!$token) {
throw new Exception('No se pudo obtener token de autenticación');
}
$products = $this->fetch_products($token);
$result = $this->process_products($products, $sync_type);
return $result;
}
private function process_products($products, $sync_type) {
$stats = array(
'processed' => 0,
'created' => 0,
'updated' => 0,
'deleted' => 0,
'errors' => array()
);
// Procesar en lotes
$batches = array_chunk($products, $this->batch_size);
foreach ($batches as $batch) {
$batch_result = $this->process_batch($batch, $sync_type);
$stats = $this->merge_stats($stats, $batch_result);
// Verificar límites de tiempo
if ($this->should_stop_execution()) {
break;
}
}
return array('stats' => $stats, 'errors' => $stats['errors']);
}
private function process_batch($products, $sync_type) {
$batch_stats = array(
'processed' => 0,
'created' => 0,
'updated' => 0,
'errors' => array()
);
foreach ($products as $product_data) {
try {
$result = $this->sync_single_product($product_data, $sync_type);
$batch_stats[$result['action']]++;
$batch_stats['processed']++;
} catch (Exception $e) {
$batch_stats['errors'][] = array(
'product_id' => $product_data['id'],
'error' => $e->getMessage()
);
}
}
return $batch_stats;
}
}
// includes/modules/class-nb-sync-module.php
class NB_Sync_Module {
private $batch_size = 50;
private $max_execution_time = 300;
public function init() {
add_action('nb_cron_sync_event', array($this, 'run_sync'));
add_action('wp_ajax_nb_manual_sync', array($this, 'manual_sync'));
}
public function run_sync($sync_type = 'automatic') {
$start_time = microtime(true);
// Configurar límites de ejecución
$this->setup_execution_limits();
try {
$result = $this->execute_sync($sync_type);
$this->log_sync_result($result, $start_time);
do_action('nb_after_sync', $sync_type, $result['stats'], $result);
} catch (Exception $e) {
NB_Logger::error('Sync failed: ' . $e->getMessage());
$this->log_sync_error($e, $start_time);
}
}
private function execute_sync($sync_type) {
$auth = NB_Auth_Module::get_instance();
$token = $auth->get_token();
if (!$token) {
throw new Exception('No se pudo obtener token de autenticación');
}
$products = $this->fetch_products($token);
$result = $this->process_products($products, $sync_type);
return $result;
}
private function process_products($products, $sync_type) {
$stats = array(
'processed' => 0,
'created' => 0,
'updated' => 0,
'deleted' => 0,
'errors' => array()
);
// Procesar en lotes
$batches = array_chunk($products, $this->batch_size);
foreach ($batches as $batch) {
$batch_result = $this->process_batch($batch, $sync_type);
$stats = $this->merge_stats($stats, $batch_result);
// Verificar límites de tiempo
if ($this->should_stop_execution()) {
break;
}
}
return array('stats' => $stats, 'errors' => $stats['errors']);
}
private function process_batch($products, $sync_type) {
$batch_stats = array(
'processed' => 0,
'created' => 0,
'updated' => 0,
'errors' => array()
);
foreach ($products as $product_data) {
try {
$result = $this->sync_single_product($product_data, $sync_type);
$batch_stats[$result['action']]++;
$batch_stats['processed']++;
} catch (Exception $e) {
$batch_stats['errors'][] = array(
'product_id' => $product_data['id'],
'error' => $e->getMessage()
);
}
}
return $batch_stats;
}
}
🧪 Testing y Calidad de Código
Configuración de PHPUnit
xml
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
<testsuites>
<testsuite name="Unit Tests">
<directory>tests/unit/</directory>
</testsuite>
<testsuite name="Integration Tests">
<directory>tests/integration/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">includes/</directory>
</whitelist>
</filter>
</phpunit>
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
<testsuites>
<testsuite name="Unit Tests">
<directory>tests/unit/</directory>
</testsuite>
<testsuite name="Integration Tests">
<directory>tests/integration/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">includes/</directory>
</whitelist>
</filter>
</phpunit>
Tests Unitarios
php
// tests/unit/test-auth-module.php
class Test_NB_Auth_Module extends WP_UnitTestCase {
private $auth_module;
public function setUp(): void {
parent::setUp();
$this->auth_module = new NB_Auth_Module();
}
public function test_token_caching() {
// Mock de respuesta exitosa
$this->mock_successful_auth_response();
// Primera llamada debería hacer request
$token1 = $this->auth_module->get_token();
$this->assertNotEmpty($token1);
// Segunda llamada debería usar caché
$token2 = $this->auth_module->get_token();
$this->assertEquals($token1, $token2);
}
public function test_token_refresh() {
// Mock de respuesta exitosa
$this->mock_successful_auth_response();
$token1 = $this->auth_module->get_token();
$token2 = $this->auth_module->get_token(true); // Force refresh
// Deberían ser diferentes si se fuerza refresh
$this->assertNotEquals($token1, $token2);
}
public function test_invalid_credentials() {
// Mock de respuesta de error
$this->mock_failed_auth_response();
$token = $this->auth_module->get_token();
$this->assertFalse($token);
}
private function mock_successful_auth_response() {
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array('token' => 'mock_token_' . time()))
);
}
return $response;
}, 10, 3);
}
private function mock_failed_auth_response() {
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 401),
'body' => json_encode(array('error' => 'Invalid credentials'))
);
}
return $response;
}, 10, 3);
}
}
// tests/unit/test-auth-module.php
class Test_NB_Auth_Module extends WP_UnitTestCase {
private $auth_module;
public function setUp(): void {
parent::setUp();
$this->auth_module = new NB_Auth_Module();
}
public function test_token_caching() {
// Mock de respuesta exitosa
$this->mock_successful_auth_response();
// Primera llamada debería hacer request
$token1 = $this->auth_module->get_token();
$this->assertNotEmpty($token1);
// Segunda llamada debería usar caché
$token2 = $this->auth_module->get_token();
$this->assertEquals($token1, $token2);
}
public function test_token_refresh() {
// Mock de respuesta exitosa
$this->mock_successful_auth_response();
$token1 = $this->auth_module->get_token();
$token2 = $this->auth_module->get_token(true); // Force refresh
// Deberían ser diferentes si se fuerza refresh
$this->assertNotEquals($token1, $token2);
}
public function test_invalid_credentials() {
// Mock de respuesta de error
$this->mock_failed_auth_response();
$token = $this->auth_module->get_token();
$this->assertFalse($token);
}
private function mock_successful_auth_response() {
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array('token' => 'mock_token_' . time()))
);
}
return $response;
}, 10, 3);
}
private function mock_failed_auth_response() {
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 401),
'body' => json_encode(array('error' => 'Invalid credentials'))
);
}
return $response;
}, 10, 3);
}
}
Tests de Integración
php
// tests/integration/test-product-sync.php
class Test_Product_Sync_Integration extends WP_UnitTestCase {
public function setUp(): void {
parent::setUp();
// Configurar WooCommerce
$this->setup_woocommerce();
// Configurar plugin
update_option('nb_user', 'test_user');
update_option('nb_password', 'test_password');
update_option('nb_prefix', 'TEST-');
}
public function test_complete_sync_flow() {
// Mock de API response
$this->mock_api_responses();
// Ejecutar sincronización
$sync_module = new NB_Sync_Module();
$result = $sync_module->run_sync('manual');
// Verificar resultados
$this->assertArrayHasKey('stats', $result);
$this->assertGreaterThan(0, $result['stats']['processed']);
// Verificar que se crearon productos
$products = wc_get_products(array(
'meta_key' => '_sku',
'meta_value' => 'TEST-',
'meta_compare' => 'LIKE'
));
$this->assertNotEmpty($products);
}
private function setup_woocommerce() {
// Activar WooCommerce para tests
if (!class_exists('WooCommerce')) {
$this->markTestSkipped('WooCommerce not available');
}
}
private function mock_api_responses() {
// Mock auth response
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array('token' => 'test_token'))
);
}
if (strpos($url, '/products') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array(
'products' => $this->get_mock_products()
))
);
}
return $response;
}, 10, 3);
}
private function get_mock_products() {
return array(
array(
'id' => '12345',
'name' => 'Test Product 1',
'price' => 99.99,
'stock' => 10,
'description' => 'Test product description',
'image' => 'https://example.com/image.jpg'
)
);
}
}
// tests/integration/test-product-sync.php
class Test_Product_Sync_Integration extends WP_UnitTestCase {
public function setUp(): void {
parent::setUp();
// Configurar WooCommerce
$this->setup_woocommerce();
// Configurar plugin
update_option('nb_user', 'test_user');
update_option('nb_password', 'test_password');
update_option('nb_prefix', 'TEST-');
}
public function test_complete_sync_flow() {
// Mock de API response
$this->mock_api_responses();
// Ejecutar sincronización
$sync_module = new NB_Sync_Module();
$result = $sync_module->run_sync('manual');
// Verificar resultados
$this->assertArrayHasKey('stats', $result);
$this->assertGreaterThan(0, $result['stats']['processed']);
// Verificar que se crearon productos
$products = wc_get_products(array(
'meta_key' => '_sku',
'meta_value' => 'TEST-',
'meta_compare' => 'LIKE'
));
$this->assertNotEmpty($products);
}
private function setup_woocommerce() {
// Activar WooCommerce para tests
if (!class_exists('WooCommerce')) {
$this->markTestSkipped('WooCommerce not available');
}
}
private function mock_api_responses() {
// Mock auth response
add_filter('pre_http_request', function($response, $args, $url) {
if (strpos($url, '/auth/login') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array('token' => 'test_token'))
);
}
if (strpos($url, '/products') !== false) {
return array(
'response' => array('code' => 200),
'body' => json_encode(array(
'products' => $this->get_mock_products()
))
);
}
return $response;
}, 10, 3);
}
private function get_mock_products() {
return array(
array(
'id' => '12345',
'name' => 'Test Product 1',
'price' => 99.99,
'stock' => 10,
'description' => 'Test product description',
'image' => 'https://example.com/image.jpg'
)
);
}
}
🔧 Herramientas de Desarrollo
Configuración de Composer
json
{
"name": "newbytes/woocommerce-connector",
"description": "NewBytes WooCommerce Connector Plugin",
"type": "wordpress-plugin",
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"wp-coding-standards/wpcs": "^2.3",
"squizlabs/php_codesniffer": "^3.6",
"phpstan/phpstan": "^1.0"
},
"scripts": {
"test": "phpunit",
"cs": "phpcs --standard=WordPress includes/",
"cbf": "phpcbf --standard=WordPress includes/",
"analyze": "phpstan analyse includes/"
},
"autoload": {
"psr-4": {
"NewBytes\\WooCommerce\\": "includes/"
}
}
}
{
"name": "newbytes/woocommerce-connector",
"description": "NewBytes WooCommerce Connector Plugin",
"type": "wordpress-plugin",
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"wp-coding-standards/wpcs": "^2.3",
"squizlabs/php_codesniffer": "^3.6",
"phpstan/phpstan": "^1.0"
},
"scripts": {
"test": "phpunit",
"cs": "phpcs --standard=WordPress includes/",
"cbf": "phpcbf --standard=WordPress includes/",
"analyze": "phpstan analyse includes/"
},
"autoload": {
"psr-4": {
"NewBytes\\WooCommerce\\": "includes/"
}
}
}
Configuración de Webpack
javascript
// webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
admin: './src/js/admin.js',
frontend: './src/js/frontend.js'
},
output: {
path: path.resolve(__dirname, 'assets'),
filename: 'js/[name].min.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].min.css'
})
],
mode: process.env. NODE_ENV === 'production' ? 'production' : 'development'
};
// webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
admin: './src/js/admin.js',
frontend: './src/js/frontend.js'
},
output: {
path: path.resolve(__dirname, 'assets'),
filename: 'js/[name].min.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].min.css'
})
],
mode: process.env. NODE_ENV === 'production' ? 'production' : 'development'
};
Scripts de Desarrollo
json
{
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack --mode=development --watch",
"test": "composer test",
"lint": "composer cs",
"fix": "composer cbf",
"analyze": "composer analyze"
}
}
{
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack --mode=development --watch",
"test": "composer test",
"lint": "composer cs",
"fix": "composer cbf",
"analyze": "composer analyze"
}
}
🚀 Proceso de Deployment
Preparación para Release
bash
#!/bin/bash
# scripts/prepare-release.sh
# Verificar tests
composer test
if [ $? -ne 0 ]; then
echo "Tests failed. Aborting release."
exit 1
fi
# Verificar coding standards
composer cs
if [ $? -ne 0 ]; then
echo "Coding standards check failed. Run 'composer cbf' to fix."
exit 1
fi
# Build assets
npm run build
# Actualizar versión
read -p "Enter new version: " version
sed -i "s/Version: .*/Version: $version/" woocommerce-newbytes.php
sed -i "s/VERSION_NB', '.*'/VERSION_NB', '$version'/" woocommerce-newbytes.php
# Crear tag
git add .
git commit -m "Release version $version"
git tag -a "v$version" -m "Version $version"
echo "Release $version prepared. Push with: git push origin main --tags"
#!/bin/bash
# scripts/prepare-release.sh
# Verificar tests
composer test
if [ $? -ne 0 ]; then
echo "Tests failed. Aborting release."
exit 1
fi
# Verificar coding standards
composer cs
if [ $? -ne 0 ]; then
echo "Coding standards check failed. Run 'composer cbf' to fix."
exit 1
fi
# Build assets
npm run build
# Actualizar versión
read -p "Enter new version: " version
sed -i "s/Version: .*/Version: $version/" woocommerce-newbytes.php
sed -i "s/VERSION_NB', '.*'/VERSION_NB', '$version'/" woocommerce-newbytes.php
# Crear tag
git add .
git commit -m "Release version $version"
git tag -a "v$version" -m "Version $version"
echo "Release $version prepared. Push with: git push origin main --tags"
Estructura de Release
bash
# Crear paquete de distribución
#!/bin/bash
# scripts/create-package.sh
version=$1
if [ -z "$version" ]; then
echo "Usage: $0 <version>"
exit 1
fi
# Crear directorio temporal
temp_dir="/tmp/woocommerce-newbytes-$version"
mkdir -p "$temp_dir"
# Copiar archivos necesarios
cp -r includes/ "$temp_dir/"
cp -r assets/ "$temp_dir/"
cp woocommerce-newbytes.php "$temp_dir/"
cp README.md "$temp_dir/"
# Excluir archivos de desarrollo
rm -rf "$temp_dir/assets/src"
rm -rf "$temp_dir/node_modules"
rm -rf "$temp_dir/tests"
# Crear ZIP
cd /tmp
zip -r "woocommerce-newbytes-$version.zip" "woocommerce-newbytes-$version/"
echo "Package created: /tmp/woocommerce-newbytes-$version.zip"
# Crear paquete de distribución
#!/bin/bash
# scripts/create-package.sh
version=$1
if [ -z "$version" ]; then
echo "Usage: $0 <version>"
exit 1
fi
# Crear directorio temporal
temp_dir="/tmp/woocommerce-newbytes-$version"
mkdir -p "$temp_dir"
# Copiar archivos necesarios
cp -r includes/ "$temp_dir/"
cp -r assets/ "$temp_dir/"
cp woocommerce-newbytes.php "$temp_dir/"
cp README.md "$temp_dir/"
# Excluir archivos de desarrollo
rm -rf "$temp_dir/assets/src"
rm -rf "$temp_dir/node_modules"
rm -rf "$temp_dir/tests"
# Crear ZIP
cd /tmp
zip -r "woocommerce-newbytes-$version.zip" "woocommerce-newbytes-$version/"
echo "Package created: /tmp/woocommerce-newbytes-$version.zip"
📝 Contribución al Proyecto
Guía de Contribución
- Fork del repositorio
- Crear rama feature:
git checkout -b feature/nueva-funcionalidad
- Seguir coding standards: WordPress Coding Standards
- Escribir tests para nueva funcionalidad
- Documentar cambios en código y README
- Crear Pull Request con descripción detallada
Coding Standards
php
// Ejemplo de código que sigue WordPress Coding Standards
/**
* Sincroniza un producto individual desde NewBytes.
*
* @since 0.1.8
*
* @param array $product_data Datos del producto desde la API.
* @param string $sync_type Tipo de sincronización.
* @return array Resultado de la sincronización.
* @throws Exception Si ocurre un error durante la sincronización.
*/
function nb_sync_single_product( $product_data, $sync_type = 'automatic' ) {
// Validar datos de entrada.
if ( empty( $product_data ) || ! is_array( $product_data ) ) {
throw new Exception( 'Datos de producto inválidos' );
}
// Aplicar filtros antes de procesar.
$product_data = apply_filters( 'nb_product_data_before_sync', $product_data, $product_data['id'] ?? '' );
// Buscar producto existente.
$existing_product = nb_find_product_by_nb_id( $product_data['id'] );
if ( $existing_product ) {
$result = nb_update_existing_product( $existing_product, $product_data );
$action = 'updated';
} else {
$result = nb_create_new_product( $product_data );
$action = 'created';
}
// Ejecutar acciones post-sincronización.
do_action( "nb_after_product_{$action}", $result['product_id'], $product_data );
return array(
'action' => $action,
'product_id' => $result['product_id'],
'success' => $result['success'],
);
}
// Ejemplo de código que sigue WordPress Coding Standards
/**
* Sincroniza un producto individual desde NewBytes.
*
* @since 0.1.8
*
* @param array $product_data Datos del producto desde la API.
* @param string $sync_type Tipo de sincronización.
* @return array Resultado de la sincronización.
* @throws Exception Si ocurre un error durante la sincronización.
*/
function nb_sync_single_product( $product_data, $sync_type = 'automatic' ) {
// Validar datos de entrada.
if ( empty( $product_data ) || ! is_array( $product_data ) ) {
throw new Exception( 'Datos de producto inválidos' );
}
// Aplicar filtros antes de procesar.
$product_data = apply_filters( 'nb_product_data_before_sync', $product_data, $product_data['id'] ?? '' );
// Buscar producto existente.
$existing_product = nb_find_product_by_nb_id( $product_data['id'] );
if ( $existing_product ) {
$result = nb_update_existing_product( $existing_product, $product_data );
$action = 'updated';
} else {
$result = nb_create_new_product( $product_data );
$action = 'created';
}
// Ejecutar acciones post-sincronización.
do_action( "nb_after_product_{$action}", $result['product_id'], $product_data );
return array(
'action' => $action,
'product_id' => $result['product_id'],
'success' => $result['success'],
);
}
Template de Pull Request
markdown
## Descripción
Breve descripción de los cambios realizados.
## Tipo de cambio
- [ ] Bug fix (cambio que corrige un problema)
- [ ] Nueva funcionalidad (cambio que añade funcionalidad)
- [ ] Breaking change (cambio que podría romper funcionalidad existente)
- [ ] Documentación (cambios solo en documentación)
## ¿Cómo se ha probado?
Describe las pruebas realizadas para verificar los cambios.
## Checklist:
- [ ] Mi código sigue las guías de estilo del proyecto
- [ ] He realizado una auto-revisión de mi código
- [ ] He comentado mi código, especialmente en áreas difíciles de entender
- [ ] He realizado cambios correspondientes en la documentación
- [ ] Mis cambios no generan nuevas advertencias
- [ ] He añadido tests que prueban que mi fix es efectivo o que mi funcionalidad funciona
- [ ] Tests unitarios nuevos y existentes pasan localmente con mis cambios
## Descripción
Breve descripción de los cambios realizados.
## Tipo de cambio
- [ ] Bug fix (cambio que corrige un problema)
- [ ] Nueva funcionalidad (cambio que añade funcionalidad)
- [ ] Breaking change (cambio que podría romper funcionalidad existente)
- [ ] Documentación (cambios solo en documentación)
## ¿Cómo se ha probado?
Describe las pruebas realizadas para verificar los cambios.
## Checklist:
- [ ] Mi código sigue las guías de estilo del proyecto
- [ ] He realizado una auto-revisión de mi código
- [ ] He comentado mi código, especialmente en áreas difíciles de entender
- [ ] He realizado cambios correspondientes en la documentación
- [ ] Mis cambios no generan nuevas advertencias
- [ ] He añadido tests que prueban que mi fix es efectivo o que mi funcionalidad funciona
- [ ] Tests unitarios nuevos y existentes pasan localmente con mis cambios
Esta guía de desarrollo proporciona todo lo necesario para contribuir efectivamente al proyecto, mantener la calidad del código y seguir las mejores prácticas de desarrollo.
💡 Desarrollo Local
Usa el entorno Docker proporcionado para desarrollo local. Esto garantiza consistencia entre diferentes entornos de desarrollo y facilita la colaboración en equipo.
⚠️ Tests Obligatorios
Todos los cambios deben incluir tests apropiados. Los Pull Requests sin tests adecuados no serán aceptados.