📈 Dashboard Widget — KPIs y graficas en el BO

Actualizado: 2025-01-15

#Tipos de widgets en el dashboard

El dashboard de PS usa dos interfaces: WidgetInterface para modulos que exponen bloques HTML en el dashboard, y los hooks dashboardZoneOne/dashboardZoneTwo + dashboardData para el sistema legacy (PS 1.7). Aqui cubrimos ambos patrones.

#Widget de KPIs (numeros)

Modulo con KPIs en el dashboard
php
<?php
class Ecom_dashstats extends Module
{
    public function __construct()
    {
        $this->name = 'ecom_dashstats';
        $this->tab = 'dashboard';
        $this->version = '1.0.0';
        $this->bootstrap = true;
        parent::__construct();
        $this->displayName = 'Dashboard Stats Avanzadas';
        $this->description = 'KPIs y graficas personalizadas en el dashboard.';
    }

    public function install()
    {
        return parent::install()
            && $this->registerHook('dashboardZoneOne')    // Columna izquierda
            && $this->registerHook('dashboardZoneTwo')    // Columna derecha
            && $this->registerHook('dashboardData')       // Datos AJAX
            && $this->registerHook('actionAdminControllerSetMedia'); // JS/CSS
    }

    /**
     * dashboardZoneOne — HTML del widget (columna izquierda)
     */
    public function hookDashboardZoneOne($params)
    {
        $this->context->smarty->assign([
            'module_dir' => $this->_path,
        ]);
        return $this->display(__FILE__, 'views/templates/admin/dashboard_zone_one.tpl');
    }

    /**
     * dashboardData — devuelve datos para actualizar KPIs via AJAX
     * PS llama a este hook cuando el admin cambia el rango de fechas
     */
    public function hookDashboardData($params)
    {
        $dateFrom = $params['date_from'];
        $dateTo   = $params['date_to'];

        // Ventas del periodo
        $totalSales = (float) Db::getInstance()->getValue(
            'SELECT SUM(total_paid_tax_incl)
             FROM `' . _DB_PREFIX_ . 'orders`
             WHERE valid = 1
             AND date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\''
        );

        // Pedidos del periodo
        $totalOrders = (int) Db::getInstance()->getValue(
            'SELECT COUNT(*)
             FROM `' . _DB_PREFIX_ . 'orders`
             WHERE valid = 1
             AND date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\''
        );

        // Ticket medio
        $avgTicket = $totalOrders > 0 ? $totalSales / $totalOrders : 0;

        // Nuevos clientes
        $newCustomers = (int) Db::getInstance()->getValue(
            'SELECT COUNT(*)
             FROM `' . _DB_PREFIX_ . 'customer`
             WHERE active = 1 AND deleted = 0
             AND date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\''
        );

        // Tasa de conversion
        $visits = (int) Db::getInstance()->getValue(
            'SELECT COUNT(DISTINCT c.id_connections)
             FROM `' . _DB_PREFIX_ . 'connections` c
             WHERE c.date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\''
        );
        $conversionRate = $visits > 0 ? round(($totalOrders / $visits) * 100, 2) : 0;

        // Datos para graficas (ventas por dia)
        $salesByDay = Db::getInstance()->executeS(
            'SELECT DATE(date_add) as day, SUM(total_paid_tax_incl) as total
             FROM `' . _DB_PREFIX_ . 'orders`
             WHERE valid = 1
             AND date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\'
             GROUP BY DATE(date_add)
             ORDER BY day ASC'
        );

        return [
            'data_value' => [
                'ecom_total_sales'    => Tools::displayPrice($totalSales),
                'ecom_total_orders'   => $totalOrders,
                'ecom_avg_ticket'     => Tools::displayPrice($avgTicket),
                'ecom_new_customers'  => $newCustomers,
                'ecom_conversion'     => $conversionRate . '%',
            ],
            'data_chart' => [
                'ecom_sales_chart' => [
                    'labels' => array_column($salesByDay, 'day'),
                    'values' => array_map('floatval', array_column($salesByDay, 'total')),
                ],
            ],
        ];
    }
}

#Widget con grafica

views/js/dashboard.js — Chart.js en el dashboard
javascript
/**
 * Renderizar grafica cuando PS actualiza los datos del dashboard
 * PS emite el evento 'dashboardRefresh' tras recibir datos AJAX
 */
document.addEventListener('DOMContentLoaded', function() {
    // PS dashboard refresh hook
    if (typeof prestashop !== 'undefined' && prestashop.on) {
        prestashop.on('dashboardRefresh', function(data) {
            if (data.ecom_sales_chart) {
                renderSalesChart(data.ecom_sales_chart);
            }
        });
    }

    let chartInstance = null;

    function renderSalesChart(chartData) {
        const canvas = document.getElementById('ecom-sales-chart');
        if (!canvas) return;

        // Destruir instancia anterior
        if (chartInstance) {
            chartInstance.destroy();
        }

        chartInstance = new Chart(canvas.getContext('2d'), {
            type: 'line',
            data: {
                labels: chartData.labels,
                datasets: [{
                    label: 'Ventas',
                    data: chartData.values,
                    borderColor: '#2fb5d2',
                    backgroundColor: 'rgba(47, 181, 210, 0.1)',
                    borderWidth: 2,
                    fill: true,
                    tension: 0.3,
                    pointRadius: 3,
                    pointHoverRadius: 6,
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            callback: value => value.toLocaleString('es-ES', {
                                style: 'currency', currency: 'EUR'
                            })
                        }
                    }
                },
                plugins: {
                    tooltip: {
                        callbacks: {
                            label: ctx => ctx.parsed.y.toLocaleString('es-ES', {
                                style: 'currency', currency: 'EUR'
                            })
                        }
                    }
                }
            }
        });
    }
});

#Datos AJAX en tiempo real

El dashboard de PS usa AJAX para actualizar datos cuando el admin cambia el rango de fechas. El hook dashboardData devuelve un array con claves data_value (KPIs) y data_chart (datos de graficas). PS mapea automaticamente data_value a elementos HTML con data-value="clave".

#Template Smarty del widget

views/templates/admin/dashboard_zone_one.tpl
smarty
<div class="panel" id="ecom_dashstats">
  <div class="panel-heading">
    <i class="icon-bar-chart"></i> Estadisticas avanzadas
    <span class="badge">{l s='Ecom Experts' mod='ecom_dashstats'}</span>
  </div>
  <div class="panel-body">
    {* KPIs — data-value se actualiza automaticamente via dashboardData *}
    <div class="row">
      <div class="col-sm-4">
        <div class="kpi-card">
          <div class="kpi-value" data-value="ecom_total_sales">--</div>
          <div class="kpi-label">{l s='Ventas' mod='ecom_dashstats'}</div>
        </div>
      </div>
      <div class="col-sm-4">
        <div class="kpi-card">
          <div class="kpi-value" data-value="ecom_total_orders">--</div>
          <div class="kpi-label">{l s='Pedidos' mod='ecom_dashstats'}</div>
        </div>
      </div>
      <div class="col-sm-4">
        <div class="kpi-card">
          <div class="kpi-value" data-value="ecom_avg_ticket">--</div>
          <div class="kpi-label">{l s='Ticket medio' mod='ecom_dashstats'}</div>
        </div>
      </div>
    </div>
    <div class="row" style="margin-top:15px;">
      <div class="col-sm-6">
        <div class="kpi-card">
          <div class="kpi-value" data-value="ecom_new_customers">--</div>
          <div class="kpi-label">{l s='Nuevos clientes' mod='ecom_dashstats'}</div>
        </div>
      </div>
      <div class="col-sm-6">
        <div class="kpi-card">
          <div class="kpi-value" data-value="ecom_conversion">--</div>
          <div class="kpi-label">{l s='Conversion' mod='ecom_dashstats'}</div>
        </div>
      </div>
    </div>

    {* Grafica *}
    <div style="height:300px; margin-top:20px;">
      <canvas id="ecom-sales-chart"></canvas>
    </div>
  </div>
</div>

#Ejemplo: ventas por hora del dia

Query: distribucion de ventas por hora
php
<?php
// Perfecto para saber a que horas compran mas tus clientes
$salesByHour = Db::getInstance()->executeS(
    'SELECT HOUR(date_add) as hour,
            COUNT(*) as orders,
            SUM(total_paid_tax_incl) as revenue
     FROM `' . _DB_PREFIX_ . 'orders`
     WHERE valid = 1
     AND date_add BETWEEN \'' . pSQL($dateFrom) . '\' AND \'' . pSQL($dateTo) . '\'
     GROUP BY HOUR(date_add)
     ORDER BY hour ASC'
);

// Rellenar horas vacias (0-23)
$hourlyData = array_fill(0, 24, 0);
foreach ($salesByHour as $row) {
    $hourlyData[(int) $row['hour']] = (float) $row['revenue'];
}

// Labels: 00:00, 01:00, ..., 23:00
$labels = array_map(fn($h) => str_pad($h, 2, '0', STR_PAD_LEFT) . ':00', range(0, 23));

return [
    'data_chart' => [
        'ecom_hourly_chart' => [
            'labels' => $labels,
            'values' => array_values($hourlyData),
        ],
    ],
];
Descargar en Markdown Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.