---
title: Dashboard Widget — KPIs y graficas en el BO
section: examples
slug: dashboard-widget
description: "Crear un widget para el dashboard del Back Office de PrestaShop: KPIs, graficas Chart.js, datos en tiempo real y panel personalizado."
keywords: prestashop dashboard widget kpi grafica chartjs modulo backoffice estadisticas
last_updated: 2025-01-15
source_url: "https://ayudaprestashop.es/examples/dashboard-widget"
---

# Dashboard Widget — KPIs y graficas en el BO

> Crear un widget para el dashboard del Back Office de PrestaShop: KPIs, graficas Chart.js, datos en tiempo real y panel personalizado.

## 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),
        ],
    ],
];
```


---

*Fuente: [https://ayudaprestashop.es/examples/dashboard-widget](https://ayudaprestashop.es/examples/dashboard-widget). Version Markdown generada automaticamente para consumo por LLMs.*
