Skip to content

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

  1. Fork del repositorio
  2. Crear rama feature: git checkout -b feature/nueva-funcionalidad
  3. Seguir coding standards: WordPress Coding Standards
  4. Escribir tests para nueva funcionalidad
  5. Documentar cambios en código y README
  6. 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.