Programando un plugin para restringir envíos de WooCommerce en provincias o estados de un país

¿Necesitas restringir envíos a ciertas provincias en tu tienda WooCommerce? ¿Te gustaría que tu cliente pudiera gestionar estas restricciones sin tocar código? En este artículo te enseñaré a crear un sistema completo de gestión para bloquear provincias o estados con una interfaz de administración fácil de usar.

Muchas tiendas online en España se encuentra con el siguiente problema. No pueden o no quieren enviar a ciertas zonas como Ceuta, Melilla, Canarias o Baleares debido a costes logísticos, restricciones en aduanas o limitaciones del transporte.

Una solución habitual que hasta ahora he ido utilizando, consiste en añadir un snippet de código en PHP. Siempre me ha funcionado bien pero a la hora de añadir nuevas restricciones, había que modificar su código PHP, lo cual no es práctico ni escalable. Por eso, vamos a crear un sistema que permita gestionar todo desde el panel de WordPress y WooCommerce.

Para que veas lo que vamos a programar, te enseño por aquí el resultado de nuestro código. Te voy a explicar cada una de sus secciones y el código que lleva.

¿Qué vamos a hacer?

  • Crearemos una pagina de administración o página de opciones dentro del menú de WooCommerce. Aquí es donde vamos a integrar los formularios para añadir o modificar información de la base de datos.
  • Añadimos un sistema para consultar los estados o provincias de un país. (No todos los países tienen estados o provincias).
  • La gestión del plugin se hará a través de campos personalizados nativos de WordPress, en este caso un repeater que utilizaremos para añadir los países con sus respectivas restricciones. (Los formularios guardarán la información relevante en la base de datos).
  • ¡Importante! Todas las cadenas de texto del plugin son 100% traducibles. Una decisión acertada para permitir que otros usuarios de otros países puedan usar nuestro plugin.

Ventajas de este sistema

  • Interfaz intuitiva: No requiere tener conocimientos técnicos.
  • Gestión centralizada: Tienes una página de opciones bien organizada.
  • Cambios inmediatos: Modificas los parámetros sin necesidad de usar FTP o editar archivos.
  • Seguridad: Validaciones y nonces incorporados.
  • Escalabilidad: Fácil de ampliar con nuevas funcionalidades.
  • Mantenibilidad: Código organizado y documentado.

¿Necesitas un desarrollo web a medida?

Me especializo en desarrollar sitios webs avanzados, programando soluciones personalizadas para proyectos web.

Entendiendo el filtro woocommerce_states

Antes de entrar en el código, es importante entender cómo funciona WooCommerce con las provincias.

El filtro woocommerce_states de WordPress permite modificar el array de estados/provincias que WooCommerce utiliza en los formularios de checkout, páginas de dirección y otras partes donde se muestran selectores de estados o provincias.

Con este filtro vamos a poder:

  • Eliminar provincias específicas.
  • Añadir nuevas provincias o estados.
  • Renombrar provincias o estados existentes.
  • Añadir estados a un país que no las tiene.
  • Sustituir totalmente los estados de un país.
  • Eliminar todos los estados de un país.

Nos vamos a centrar en eliminar las provincias de España que no queremos que aparezcan y por tanto no será posible establecer una dirección correcta para terminar una compra.

El siguiente snippet de codigo funciona perfectamente y es lo que he estado utilizando siempre que un cliente me ha pedido este tipo de restricciones.

add_filter('woocommerce_states', 'eliminar_provincias_especificas');
function eliminar_provincias_especificas($states) {
    if (isset($states['ES'])) {
        // Eliminar Ceuta, Melilla y Canarias
        unset($states['ES']['CE']); // Ceuta
        unset($states['ES']['ML']); // Melilla
        unset($states['ES']['TF']); // Santa Cruz de Tenerife
        unset($states['ES']['GC']); // Las Palmas
    }
    return $states;
}

Ejemplo de como utilizar el filtro woocommerce_states

Para mostrar los estados o provincias de un país, podemos utilizar el siguiente snippet.

function obtener_estados_pais($codigo_pais) {
    $countries_obj = new WC_Countries();
    return $countries_obj->get_states($codigo_pais);
}

// Uso
$provincias_espana = obtener_estados_pais('ES');
foreach ($provincias_espana as $codigo => $nombre) {
    echo "Código: $codigo - Nombre: $nombre<br>";
}

El objetivo de este tutorial es construir una interfaz en WooCommerce amigable para alimentar este código de una forma fácil.

Comenzamos…

Creamos la página de administración

Aqui es donde vamos a tener todos los campos personalizados, donde se van a añadir o modificar los estados y provincias, así como otras funcionalidades que incorporemos más adelante. ¿Cómo extendemos nuestro plugin? Te lo cuento mas abajo.

Vamos a trabajar sobre el hook admin_menu junto con la función add_submenu_page para localizar el menú de WooCommerce e integrar una subpagina en el mismo.

// Registrar la página de administración
add_action('admin_menu', 'agregar_pagina_estados_bloqueados');
function agregar_pagina_estados_bloqueados() {
    add_submenu_page(
        'woocommerce',
        esc_html__('Gestión de Estados/Provincias', 'woocommerce-estados'),
        esc_html__('Estados Bloqueados', 'woocommerce-estados'),
        'manage_options',
        'wc-estados-bloqueados',
        'mostrar_pagina_estados_bloqueados'
    );
}

Si quieres saber más sobre como crear páginas de opciones potentes, te animo a seguir leyendo otro articulo que tengo con más detalle.

Interfaz de usuario

La página de administración tendrá dos secciones principales:

  • La primera sección será informativa, Lleva un selector de países el cual a través de AJAX actualizará una sección con los resultados requeridos, mostrando un listado de estados o provincias y sus respectivos prefijos. Usaremos la clase WC_Countries de WooCommerce.
    $countries_obj = new WC_Countries();
    $all_countries = $countries_obj->get_countries();
  • La segunda sección será para integrar el formulario para gestionar los bloqueos (A través de un repeater nativo) junto con otros datos que necesite el plugin. Estos se guardarán en la base de datos para utilizarlos cuando sean necesarios.
<?php
function mostrar_pagina_estados_bloqueados() {
    // Procesar el guardado de datos
    if (isset($_POST['guardar_estados_bloqueados']) && wp_verify_nonce($_POST['wc_estados_nonce'], 'guardar_estados_bloqueados')) {
        $estados_bloqueados = isset($_POST['estados_bloqueados']) ? $_POST['estados_bloqueados'] : array();
        update_option('wc_estados_bloqueados', $estados_bloqueados);
        echo '<div class="notice notice-success"><p>' . esc_html__('Estados bloqueados guardados correctamente.', 'woocommerce-estados') . '</p></div>';
    }
    
    $countries_obj = new WC_Countries();
    $all_countries = $countries_obj->get_countries();
    $estados_bloqueados = get_option('wc_estados_bloqueados', array());
    ?>
    
    <div class="wrap">
        <h1><?php esc_html_e('Gestión de Estados/Provincias Bloqueados', 'woocommerce-estados'); ?></h1>
        
        <div class="wc-estados-container" style="display: flex; gap: 20px;">
            <!-- Panel izquierdo: Explorador -->
            <div class="wc-estados-explorer" style="flex: 1; max-width: 400px;">
                <h2><?php esc_html_e('Explorador de Estados/Provincias', 'woocommerce-estados'); ?></h2>
                <select id="pais-selector" style="width: 100%;">
                    <option value=""><?php esc_html_e('Selecciona un país', 'woocommerce-estados'); ?></option>
                    <?php foreach ($all_countries as $codigo => $nombre) : ?>
                        <option value="<?php echo esc_attr($codigo); ?>">
                            <?php echo esc_html($nombre); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
                
                <div id="estados-lista" style="margin-top: 20px; max-height: 400px; overflow-y: auto;">
                    <!-- Se llenará por AJAX -->
                </div>
            </div>
            
            <!-- Panel derecho: Gestión de bloqueos -->
            <div class="wc-estados-bloqueados" style="flex: 2;">
                <h2><?php esc_html_e('Estados/Provincias Bloqueados', 'woocommerce-estados'); ?></h2>
                <!-- Formulario de gestión aquí -->
            </div>
        </div>
    </div>
    <?php
}

Sistema AJAX para explorar provincias

Para hacer solicitudes AJAX en WordPress con la posibilidad de conectar datos (data) entre PHP y AJAX usaremos el hook wp_ajax_{$actiom} junto con jQuery Ajax. Con el fin de actualizar el resultado en tiempo real a través de la función obtener_estados_admin.

Los siguientes snippets de código son muy interesantes, muy útiles sobretodo si estás desarrollando plugins o temas de WordPress a medida. Es la forma que hemos utilizado siempre para hacer AJAX en WordPress y que se seguirá utilizando aunque ahora disponemos de una nueva API de Interactividad que viene muy fuerte y que podríamos utilizar en estos casos.

// Handler AJAX para obtener estados
add_action('wp_ajax_obtener_estados_admin', 'ajax_obtener_estados_admin');
function ajax_obtener_estados_admin() {
    check_ajax_referer('obtener_estados_admin', 'nonce');
    
    $pais = isset($_POST['pais']) ? $_POST['pais'] : '';
    
    if (empty($pais)) {
        wp_send_json_error(
            esc_html__('País no especificado', 'woocommerce-estados')
        );
    }
    
    $countries_obj = new WC_Countries();
    $estados = $countries_obj->get_states($pais);
    
    if ($estados) {
        wp_send_json_success($estados);
    } else {
        wp_send_json_error(
            esc_html__('No se encontraron estados para este país', 'woocommerce-estados')
        );
    }
}

JavaScript para el explorador de estados o provincias:

jQuery(document).ready(function($) {
    $('#pais-selector').on('change', function() {
        const pais = $(this).val();
        if (!pais) {
            $('#estados-lista').html('');
            return;
        }
        
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'obtener_estados_admin',
                pais: pais,
                nonce: '<?php echo wp_create_nonce('obtener_estados_admin'); ?>'
            },
            beforeSend: function() {
                $('#estados-lista').html('<p><?php echo esc_js(__('Cargando...', 'woocommerce-estados')); ?></p>');
            },
            success: function(response) {
                if (response.success) {
                    let html = '<table style="width: 100%;">';
                    html += '<thead><tr><th><?php echo esc_js(__('Código', 'woocommerce-estados')); ?></th><th><?php echo esc_js(__('Nombre', 'woocommerce-estados')); ?></th></tr></thead>';
                    html += '<tbody>';
                    
                    for (let codigo in response.data) {
                        html += '<tr class="estado-item">';
                        html += '<td><span class="estado-codigo">' + codigo + '</span></td>';
                        html += '<td>' + response.data[codigo] + '</td>';
                        html += '</tr>';
                    }
                    
                    html += '</tbody></table>';
                    $('#estados-lista').html(html);
                } else {
                    $('#estados-lista').html('<p><?php echo esc_js(__('No se encontraron estados para este país.', 'woocommerce-estados')); ?></p>');
                }
            },
            error: function() {
                $('#estados-lista').html('<p><?php echo esc_js(__('Error al cargar los estados. Por favor, inténtalo de nuevo.', 'woocommerce-estados')); ?></p>');
            }
        });
    });
});

Sistema de repeater para gestionar bloqueos

Esa segunda sección contiene el formulario con el repeater y puede contener otros campos personalizados que necesitemos en el futuro para extender la funcionalidad del plugin.

Así es como se ve la estructura de un repeater.

<!-- Parte del formulario en la función mostrar_pagina_estados_bloqueados() -->
<form method="post" action="">
    <?php wp_nonce_field('guardar_estados_bloqueados', 'wc_estados_nonce'); ?>
    
    <div id="estados-bloqueados-container">
        <?php
        $index = 0;
        if (!empty($estados_bloqueados)) {
            foreach ($estados_bloqueados as $item) {
                ?>
                <div class="estado-bloqueado-item" style="margin-bottom: 15px; padding: 10px; border: 1px solid #ddd;">
                    <select name="estados_bloqueados[<?php echo $index; ?>][pais]" class="pais-bloqueado">
                        <?php foreach ($all_countries as $codigo => $nombre) : ?>
                            <option value="<?php echo esc_attr($codigo); ?>" <?php selected($codigo, $item['pais']); ?>>
                                <?php echo esc_html($nombre); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                    
                    <input type="text" 
                           name="estados_bloqueados[<?php echo $index; ?>][estados]" 
                           value="<?php echo esc_attr($item['estados']); ?>" 
                           placeholder="<?php esc_attr_e('Códigos de estados (ej: CE,ML,TF,GC)', 'woocommerce-estados'); ?>">
                    
                    <button type="button" class="button eliminar-estado">
                        <?php esc_html_e('Eliminar', 'woocommerce-estados'); ?>
                    </button>
                    <span class="estado-preview"></span>
                </div>
                <?php
                $index++;
            }
        }
        ?>
    </div>
    
    <p>
        <button type="button" id="agregar-estado" class="button">
            <?php esc_html_e('Agregar Estado Bloqueado', 'woocommerce-estados'); ?>
        </button>
        <button type="submit" name="guardar_estados_bloqueados" class="button button-primary">
            <?php esc_html_e('Guardar Cambios', 'woocommerce-estados'); ?>
        </button>
    </p>
</form>
// Actualizar vista previa cuando cambia el país o los estados
$(document).on('change', '.pais-bloqueado, input[name*="estados_bloqueados"]', function() {
    actualizarPreviews();
});

function actualizarPreviews() {
    $('.estado-bloqueado-item').each(function() {
        const $item = $(this);
        const pais = $item.find('.pais-bloqueado').val();
        const estados = $item.find('input[name*="estados"]').val();
        
        if (pais && estados) {
            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'preview_estados_bloqueados',
                    pais: pais,
                    estados: estados,
                    nonce: '<?php echo wp_create_nonce('preview_estados_bloqueados'); ?>'
                },
                success: function(response) {
                    if (response.success) {
                        $item.find('.estado-preview').html('<?php echo esc_js(__('Bloqueados:', 'woocommerce-estados')); ?> ' + response.data);
                    }
                }
            });
        }
    });
}

Aplicar las restricciones en el frontend

Hemos llegado a fin del propósito, donde extraemos el contenido guardado en el campo personalizado wc_estados_bloqueados (es un array) y lanzamos la función para eliminar las provincias o estados a través del filtro woocommerce_states que ya hemos visto antes. Aquí es donde se produce la magia.

// Filtro principal para eliminar estados bloqueados
add_filter('woocommerce_states', 'eliminar_estados_bloqueados_frontend');
function eliminar_estados_bloqueados_frontend($states) {
    $estados_bloqueados = get_option('wc_estados_bloqueados', array());
    
    if (empty($estados_bloqueados)) {
        return $states;
    }
    
    foreach ($estados_bloqueados as $item) {
        if (isset($states[$item['pais']])) {
            $codigos = array_map('trim', explode(',', $item['estados']));
            foreach ($codigos as $codigo) {
                if (isset($states[$item['pais']][$codigo])) {
                    unset($states[$item['pais']][$codigo]);
                }
            }
        }
    }
    
    return $states;
}

Casos de uso prácticos

  • Tienda B2B que solo envía a ciertas provincias
  • Productos especiales con restricciones geográficas
  • Temporadas donde no se puede enviar a ciertas zonas

¿Qué más puedes hacer? ¿Cómo extender el plugin?

  • Añade notificaciones automáticas a clientes de zonas bloqueadas.
  • Haz restricciones por métodos de envío específicos.
  • Haz restricciones según el producto añadido al carrito.
  • Integración con plugins de transportes.
  • Añade una página para el registro de cambios (log) de modificaciones.

Aquí tienes todo el código completo

Copia y pega este código, modifica lo que necesites, extiende la funcionalidad y utilízalo con tus clientes.

<?php
/**
 * Sistema de gestión de estados/provincias bloqueados para WooCommerce
 * Permite gestionar desde el admin qué provincias están restringidas para envíos
 *
 * Text Domain: wc-estados-bloqueados
 * Domain Path: /languages
 */

// Cargar textdomain
add_action('plugins_loaded', 'wceb_cargar_textdomain');
function wceb_cargar_textdomain() {
    load_plugin_textdomain('wc-estados-bloqueados', false, dirname(plugin_basename(__FILE__)) . '/languages');
}

// Registrar la página de administración
add_action('admin_menu', 'agregar_pagina_estados_bloqueados');
function agregar_pagina_estados_bloqueados() {
    add_submenu_page(
        'woocommerce',
        esc_html__('Gestión de Estados/Provincias', 'wc-estados-bloqueados'),
        esc_html__('Estados Bloqueados', 'wc-estados-bloqueados'),
        'manage_options',
        'wc-estados-bloqueados',
        'mostrar_pagina_estados_bloqueados'
    );
}

// Página principal de administración
function mostrar_pagina_estados_bloqueados() {
    // Guardar cambios si se han enviado
    if (isset($_POST['guardar_estados_bloqueados']) && wp_verify_nonce($_POST['wc_estados_nonce'], 'guardar_estados_bloqueados')) {
        $estados_bloqueados = isset($_POST['estados_bloqueados']) ? $_POST['estados_bloqueados'] : array();
        update_option('wc_estados_bloqueados', $estados_bloqueados);
        echo '<div class="notice notice-success"><p>' . esc_html__('Estados bloqueados guardados correctamente.', 'wc-estados-bloqueados') . '</p></div>';
    }

    $countries_obj = new WC_Countries();
    $all_countries = $countries_obj->get_countries();
    $estados_bloqueados = get_option('wc_estados_bloqueados', array());

    ?>
    <div class="wrap">
        <h1><?php esc_html_e('Gestión de Estados/Provincias Bloqueados', 'wc-estados-bloqueados'); ?></h1>

        <div class="wc-estados-container" style="display: flex; gap: 20px;">
            <!-- Panel izquierdo: Explorador de países -->
            <div class="wc-estados-explorer" style="flex: 1; max-width: 400px;">
                <h2><?php esc_html_e('Explorador de Estados/Provincias', 'wc-estados-bloqueados'); ?></h2>
                <select id="pais-selector" style="width: 100%;">
                    <option value=""><?php esc_html_e('Selecciona un país', 'wc-estados-bloqueados'); ?></option>
                    <?php foreach ($all_countries as $codigo => $nombre) : ?>
                        <option value="<?php echo esc_attr($codigo); ?>">
                            <?php echo esc_html($nombre); ?>
                        </option>
                    <?php endforeach; ?>
                </select>

                <div id="estados-lista" style="margin-top: 20px; max-height: 400px; overflow-y: scroll; scrollbar-width: thin; scrollbar-color: #888 #F5F5F5;">
                    <!-- Se llenará por AJAX -->
                </div>
            </div>

            <!-- Panel derecho: Estados bloqueados -->
            <div class="wc-estados-bloqueados" style="flex: 2;">
                <h2><?php esc_html_e('Estados/Provincias Bloqueados', 'wc-estados-bloqueados'); ?></h2>

                <form method="post" action="">
                    <?php wp_nonce_field('guardar_estados_bloqueados', 'wc_estados_nonce'); ?>

                    <div id="estados-bloqueados-container">
                        <?php
                        $index = 0;
                        if (!empty($estados_bloqueados)) {
                            foreach ($estados_bloqueados as $item) {
                                ?>
                                <div class="estado-bloqueado-item" style="margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">
                                    <select name="estados_bloqueados[<?php echo $index; ?>][pais]" class="pais-bloqueado" style="width: 200px;">
                                        <?php foreach ($all_countries as $codigo => $nombre) : ?>
                                            <option value="<?php echo esc_attr($codigo); ?>" <?php selected($codigo, $item['pais']); ?>>
                                                <?php echo esc_html($nombre); ?>
                                            </option>
                                        <?php endforeach; ?>
                                    </select>

                                    <input type="text"
                                           name="estados_bloqueados[<?php echo $index; ?>][estados]"
                                           value="<?php echo esc_attr($item['estados']); ?>"
                                           placeholder="<?php esc_attr_e('Códigos de estados (separados por comas)', 'wc-estados-bloqueados'); ?>"
                                           style="width: 300px;">

                                    <button type="button" class="button eliminar-estado"><?php esc_html_e('Eliminar', 'wc-estados-bloqueados'); ?></button>
                                    <span class="estado-preview" style="display: block; margin-top: 5px; font-size: 12px; color: #666;"></span>
                                </div>
                                <?php
                                $index++;
                            }
                        }
                        ?>
                    </div>

                    <p>
                        <button type="button" id="agregar-estado" class="button"><?php esc_html_e('Agregar Estado Bloqueado', 'wc-estados-bloqueados'); ?></button>
                        <button type="submit" name="guardar_estados_bloqueados" class="button button-primary"><?php esc_html_e('Guardar Cambios', 'wc-estados-bloqueados'); ?></button>
                    </p>
                </form>

                <div style="margin-top: 20px; padding: 15px; background: #f0f0f0;">
                    <h3><?php esc_html_e('Instrucciones:', 'wc-estados-bloqueados'); ?></h3>
                    <ol>
                        <li><?php esc_html_e('Usa el explorador de la izquierda para ver los códigos de estados/provincias de cada país', 'wc-estados-bloqueados'); ?></li>
                        <li><?php esc_html_e('En el campo de estados, ingresa los códigos separados por comas (ej: CE,ML,TF,GC)', 'wc-estados-bloqueados'); ?></li>
                        <li><?php esc_html_e('Haz clic en "Guardar Cambios" para aplicar las restricciones', 'wc-estados-bloqueados'); ?></li>
                    </ol>
                </div>
            </div>
        </div>
    </div>

    <style>
        .wc-estados-container {
            margin-top: 20px;
        }
        .estado-item {
            padding: 5px 10px;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .estado-item:hover {
            background: #f5f5f5;
        }
        .estado-codigo {
            font-weight: bold;
            color: #0073aa;
        }
        /* Estilos para la barra de desplazamiento */
        #estados-lista::-webkit-scrollbar {
            width: 8px;
            background-color: #F5F5F5;
        }
        #estados-lista::-webkit-scrollbar-thumb {
            background-color: #888;
            border-radius: 4px;
        }
        #estados-lista::-webkit-scrollbar-track {
            background-color: #F5F5F5;
        }
        /* Estilos responsive */
        @media screen and (max-width: 900px) {
            .wc-estados-container {
                flex-direction: column;
            }
            .wc-estados-explorer {
                flex: none !important;
                max-width: 100% !important;
                width: 100%;
                margin-bottom: 20px;
            }
            .wc-estados-bloqueados {
                flex: none !important;
                width: 100%;
            }
            .estado-bloqueado-item {
                display: flex;
                flex-direction: column;
            }
            .estado-bloqueado-item select,
            .estado-bloqueado-item input {
                width: 100% !important;
                margin-bottom: 10px;
            }
            .eliminar-estado {
                align-self: flex-start;
            }
        }
    </style>

    <script>
    jQuery(document).ready(function($) {
        let estadoIndex = <?php echo $index; ?>;

        // Explorador AJAX
        $('#pais-selector').on('change', function() {
            const pais = $(this).val();
            if (!pais) {
                $('#estados-lista').html('');
                return;
            }

            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'obtener_estados_admin',
                    pais: pais,
                    nonce: '<?php echo wp_create_nonce('obtener_estados_admin'); ?>'
                },
                beforeSend: function() {
                    $('#estados-lista').html('<p><?php echo esc_js(__('Cargando...', 'wc-estados-bloqueados')); ?></p>');
                },
                success: function(response) {
                    if (response.success) {
                        let html = '<table style="width: 100%;">';
                        html += '<thead><tr><th><?php echo esc_js(__('Código', 'wc-estados-bloqueados')); ?></th><th><?php echo esc_js(__('Nombre', 'wc-estados-bloqueados')); ?></th></tr></thead>';
                        html += '<tbody>';

                        for (let codigo in response.data) {
                            html += '<tr class="estado-item">';
                            html += '<td><span class="estado-codigo">' + codigo + '</span></td>';
                            html += '<td>' + response.data[codigo] + '</td>';
                            html += '</tr>';
                        }

                        html += '</tbody></table>';
                        $('#estados-lista').html(html);
                    } else {
                        $('#estados-lista').html('<p><?php echo esc_js(__('No se encontraron estados para este país.', 'wc-estados-bloqueados')); ?></p>');
                    }
                }
            });
        });

        // Agregar nuevo estado bloqueado
        $('#agregar-estado').on('click', function() {
            const template = `
                <div class="estado-bloqueado-item" style="margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">
                    <select name="estados_bloqueados[${estadoIndex}][pais]" class="pais-bloqueado" style="width: 200px;">
                        <?php foreach ($all_countries as $codigo => $nombre) : ?>
                            <option value="<?php echo esc_attr($codigo); ?>">
                                <?php echo esc_html($nombre); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>

                    <input type="text"
                           name="estados_bloqueados[${estadoIndex}][estados]"
                           value=""
                           placeholder="<?php esc_attr_e('Códigos de estados (separados por comas)', 'wc-estados-bloqueados'); ?>"
                           style="width: 300px;">

                    <button type="button" class="button eliminar-estado"><?php esc_html_e('Eliminar', 'wc-estados-bloqueados'); ?></button>
                    <span class="estado-preview" style="display: block; margin-top: 5px; font-size: 12px; color: #666;"></span>
                </div>
            `;

            $('#estados-bloqueados-container').append(template);
            estadoIndex++;
            actualizarPreviews();
        });

        // Eliminar estado bloqueado
        $(document).on('click', '.eliminar-estado', function() {
            $(this).closest('.estado-bloqueado-item').remove();
        });

        // Actualizar vista previa cuando cambia el país o los estados
        $(document).on('change', '.pais-bloqueado, input[name*="estados_bloqueados"]', function() {
            actualizarPreviews();
        });

        function actualizarPreviews() {
            $('.estado-bloqueado-item').each(function() {
                const $item = $(this);
                const pais = $item.find('.pais-bloqueado').val();
                const estados = $item.find('input[name*="estados"]').val();

                if (pais && estados) {
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'preview_estados_bloqueados',
                            pais: pais,
                            estados: estados,
                            nonce: '<?php echo wp_create_nonce('preview_estados_bloqueados'); ?>'
                        },
                        success: function(response) {
                            if (response.success) {
                                $item.find('.estado-preview').html('<?php echo esc_js(__('Estados bloqueados:', 'wc-estados-bloqueados')); ?> ' + response.data);
                            }
                        }
                    });
                }
            });
        }

        // Actualizar previews al cargar
        actualizarPreviews();
    });
    </script>
    <?php
}

// AJAX handler para obtener estados
add_action('wp_ajax_obtener_estados_admin', 'ajax_obtener_estados_admin');
function ajax_obtener_estados_admin() {
    check_ajax_referer('obtener_estados_admin', 'nonce');

    $pais = isset($_POST['pais']) ? $_POST['pais'] : '';

    if (empty($pais)) {
        wp_send_json_error(__('País no especificado', 'wc-estados-bloqueados'));
    }

    $countries_obj = new WC_Countries();
    $estados = $countries_obj->get_states($pais);

    if ($estados) {
        wp_send_json_success($estados);
    } else {
        wp_send_json_error(__('No se encontraron estados para este país', 'wc-estados-bloqueados'));
    }
}

// AJAX handler para preview de estados bloqueados
add_action('wp_ajax_preview_estados_bloqueados', 'ajax_preview_estados_bloqueados');
function ajax_preview_estados_bloqueados() {
    check_ajax_referer('preview_estados_bloqueados', 'nonce');

    $pais = isset($_POST['pais']) ? $_POST['pais'] : '';
    $estados = isset($_POST['estados']) ? $_POST['estados'] : '';

    if (empty($pais) || empty($estados)) {
        wp_send_json_error(__('Datos incompletos', 'wc-estados-bloqueados'));
    }

    $countries_obj = new WC_Countries();
    $todos_estados = $countries_obj->get_states($pais);

    $codigos = array_map('trim', explode(',', $estados));
    $nombres = array();

    foreach ($codigos as $codigo) {
        if (isset($todos_estados[$codigo])) {
            $nombres[] = $todos_estados[$codigo];
        }
    }

    wp_send_json_success(implode(', ', $nombres));
}

// Filtro principal para eliminar estados bloqueados
add_filter('woocommerce_states', 'eliminar_estados_bloqueados_frontend');
function eliminar_estados_bloqueados_frontend($states) {
    $estados_bloqueados = get_option('wc_estados_bloqueados', array());

    if (empty($estados_bloqueados)) {
        return $states;
    }

    foreach ($estados_bloqueados as $item) {
        if (isset($states[$item['pais']])) {
            $codigos = array_map('trim', explode(',', $item['estados']));
            foreach ($codigos as $codigo) {
                if (isset($states[$item['pais']][$codigo])) {
                    unset($states[$item['pais']][$codigo]);
                }
            }
        }
    }

    return $states;
}

Conclusión

Con este sistema de gestión de provincias, hemos transformado una tarea técnica en una funcionalidad amigable. Nuestros cliente podrá gestionar las restricciones de envío de forma autónoma, ahorrando tiempo y evitando errores.

El código es modular y fácil de mantener, lo que facilita futuras actualizaciones o personalizaciones. Además, al usar las APIs nativas de WordPress y WooCommerce, garantizamos la compatibilidad y seguridad del sistema.

Espero que puedas aprovecharlo y que te sirva para aprender nuevas funciones. ¡Seguimos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.