🧩 Compatibilidad de modulos con multitienda

Actualizado: 2024-12-01

Un modulo compatible con multitienda guarda sus opciones por tienda cuando es necesario y funciona correctamente sea cual sea el contexto de Shop activo (tienda unica, grupo, todas las tiendas).

#Checklist de compatibilidad

ItemComo verificar
Configuration usa id_shopConfiguration::get/updateValue respeta el contexto automaticamente
Tablas custom tienen id_shopCREATE TABLE incluye id_shop INT + Shop::addSqlRestriction en queries
ObjectModel con multishopEn $definition: 'multishop' => true, campos con 'shop' => true
Hooks respetan el contextogetContext()->shop->id usado en hooks que muestran contenido
Install registra en todas las tiendasparent::install() ya lo hace, pero tablas custom deben replicarse
Formulario muestra contextoEl admin ve para que tienda/grupo esta editando

#Configuracion por tienda en getContent()

Formulario de configuracion multitienda
php
<?php

public function getContent(): string
{
    $output = '';

    if (Tools::isSubmit('submit_' . $this->name)) {
        // Configuration::updateValue guarda automaticamente
        // para el contexto de tienda activo en el BO
        Configuration::updateValue('MYMOD_TITLE', Tools::getValue('MYMOD_TITLE'));
        Configuration::updateValue('MYMOD_ENABLED', (int) Tools::getValue('MYMOD_ENABLED'));
        $output .= $this->displayConfirmation('Guardado correctamente');
    }

    // Mostrar indicador de contexto multitienda
    if (Shop::isFeatureActive()) {
        $shop = Context::getContext()->shop;
        $output .= $this->displayInformation(
            'Configurando para: <b>' . $shop->name . '</b> (ID: ' . $shop->id . ')'
        );
        if (Shop::getContext() === Shop::CONTEXT_ALL) {
            $output .= $this->displayWarning(
                'Estas editando la configuracion GLOBAL. Los valores se aplicaran a todas las tiendas que no tengan valor propio.'
            );
        }
    }

    return $output . $this->renderForm();
}

private function renderForm(): string
{
    $fields = [
        'form' => [
            'legend' => ['title' => 'Configuracion'],
            'input'  => [
                [
                    'type'  => 'text',
                    'label' => 'Titulo',
                    'name'  => 'MYMOD_TITLE',
                    'desc'  => 'Titulo mostrado en el front',
                ],
                [
                    'type'    => 'switch',
                    'label'   => 'Activar',
                    'name'    => 'MYMOD_ENABLED',
                    'values'  => [
                        ['id' => 'on', 'value' => 1, 'label' => 'Si'],
                        ['id' => 'off', 'value' => 0, 'label' => 'No'],
                    ],
                ],
            ],
            'submit' => ['title' => 'Guardar'],
        ],
    ];

    $helper = new HelperForm();
    $helper->submit_action = 'submit_' . $this->name;
    $helper->fields_value  = [
        'MYMOD_TITLE'   => Configuration::get('MYMOD_TITLE'),
        'MYMOD_ENABLED' => Configuration::get('MYMOD_ENABLED'),
    ];

    return $helper->generateForm([$fields]);
}

#Install y uninstall multitienda

Install compatible con multitienda
php
<?php

public function install(): bool
{
    // parent::install() ya registra el modulo en todas las tiendas activas
    if (!parent::install()) {
        return false;
    }

    // Crear tabla con id_shop
    $sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'mymodule_data` (
        `id_mymodule_data` INT UNSIGNED AUTO_INCREMENT,
        `id_shop` INT UNSIGNED NOT NULL DEFAULT 1,
        `value` VARCHAR(255) NOT NULL,
        PRIMARY KEY (`id_mymodule_data`),
        KEY `idx_shop` (`id_shop`)
    ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8mb4';

    if (!Db::getInstance()->execute($sql)) {
        return false;
    }

    // Insertar configuracion por defecto para CADA tienda
    if (Shop::isFeatureActive()) {
        $shops = Shop::getShops(true, null, true); // IDs de tiendas activas
        foreach ($shops as $idShop) {
            Configuration::updateValue('MYMOD_ENABLED', 1, false, null, $idShop);
            Configuration::updateValue('MYMOD_TITLE', 'Default', false, null, $idShop);
        }
    } else {
        Configuration::updateValue('MYMOD_ENABLED', 1);
        Configuration::updateValue('MYMOD_TITLE', 'Default');
    }

    return true;
}

public function uninstall(): bool
{
    // Limpiar configuracion de TODAS las tiendas
    Configuration::deleteByName('MYMOD_ENABLED');
    Configuration::deleteByName('MYMOD_TITLE');

    Db::getInstance()->execute(
        'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'mymodule_data`'
    );

    return parent::uninstall();
}

#Errores comunes

ErrorCausaSolucion
Configuracion se pierde al cambiar de tiendaupdateValue sin contexto correctoVerificar Shop::getContext() antes de guardar
Datos duplicados entre tiendasINSERT sin id_shopAñadir id_shop a la tabla y filtrar en queries
Modulo no aparece en tienda 2No instalado en esa tiendaBO → Modulos → Filtrar por tienda → Instalar
Hook se ejecuta en tienda equivocadaNo filtra por contexto en el hookVerificar Context::getContext()->shop->id
Query devuelve datos de todas las tiendasFalta Shop::addSqlRestrictionAñadir filtro de tienda a todas las queries
Descargar en Markdown Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.