---
title: Modulo con Cron — tareas automatizadas
section: examples
slug: cron-module
description: "Ejemplo completo de modulo PrestaShop con tareas cron: limpieza de carritos abandonados, sincronizacion de stock y emails automaticos."
keywords: prestashop cron modulo tarea automatica carrito abandonado sincronizacion stock
last_updated: 2025-01-15
source_url: "https://ayudaprestashop.es/examples/cron-module"
---

# Modulo con Cron — tareas automatizadas

> Ejemplo completo de modulo PrestaShop con tareas cron: limpieza de carritos abandonados, sincronizacion de stock y emails automaticos.

> **[I] Compatibilidad**
>
> Este patron funciona en PS 1.7+ y 8.x. En PS 9.x se recomienda usar Symfony Messenger para tareas async, pero el patron cron sigue siendo valido.

## Concepto: cron en PrestaShop

PrestaShop no tiene un sistema de cron interno. Los modulos exponen URLs que el servidor ejecuta periodicamente via crontab del sistema operativo. El modulo oficial `cronjobs` facilita esto, pero puedes crear tu propio endpoint sin depender de el.

## Estructura del modulo

*Arbol de archivos*

```text
modules/ecom_crontasks/
├── ecom_crontasks.php          # Modulo principal
├── controllers/
│   └── front/
│       └── cron.php            # Endpoint publico para crontab
├── classes/
│   ├── CronTaskRunner.php      # Logica de ejecucion
│   └── CronLog.php             # ObjectModel para logs
├── sql/
│   ├── install.sql
│   └── uninstall.sql
└── views/
    └── templates/admin/
        └── configure.tpl       # Panel de configuracion
```

## Registro del cron task

*ecom_crontasks.php — modulo principal*

```php
<?php
class Ecom_crontasks extends Module
{
    public function __construct()
    {
        $this->name = 'ecom_crontasks';
        $this->tab = 'administration';
        $this->version = '1.0.0';
        $this->author = 'Ecom Experts';
        $this->need_instance = 0;
        $this->bootstrap = true;
        parent::__construct();
        $this->displayName = $this->trans('Cron Tasks Manager', [], 'Modules.Ecomcrontasks.Admin');
        $this->description = $this->trans('Automated tasks: cart cleanup, stock sync, email alerts.', [], 'Modules.Ecomcrontasks.Admin');
    }

    public function install()
    {
        // Crear tabla de logs
        $sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ecom_cron_log` (
            `id_cron_log` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `task_name` VARCHAR(100) NOT NULL,
            `status` ENUM("success","error","running") NOT NULL DEFAULT "running",
            `items_processed` INT(10) UNSIGNED NOT NULL DEFAULT 0,
            `execution_time` FLOAT NOT NULL DEFAULT 0,
            `message` TEXT,
            `date_add` DATETIME NOT NULL,
            PRIMARY KEY (`id_cron_log`),
            KEY `task_date` (`task_name`, `date_add`)
        ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8mb4;';

        return parent::install()
            && Db::getInstance()->execute($sql)
            && Configuration::updateValue('ECOM_CRON_SECRET', Tools::passwdGen(32))
            && Configuration::updateValue('ECOM_CRON_CART_DAYS', 7)
            && Configuration::updateValue('ECOM_CRON_STOCK_ALERT', 5);
    }

    public function getContent()
    {
        $output = '';
        if (Tools::isSubmit('submitCronConfig')) {
            Configuration::updateValue('ECOM_CRON_CART_DAYS', (int) Tools::getValue('ECOM_CRON_CART_DAYS'));
            Configuration::updateValue('ECOM_CRON_STOCK_ALERT', (int) Tools::getValue('ECOM_CRON_STOCK_ALERT'));
            $output .= $this->displayConfirmation('Configuracion guardada.');
        }

        // Mostrar la URL del cron al admin
        $cronUrl = $this->context->link->getModuleLink(
            $this->name, 'cron',
            ['secure_key' => Configuration::get('ECOM_CRON_SECRET')],
            true
        );

        $output .= $this->displayConfirmation(
            'URL para crontab: <code>' . $cronUrl . '</code>'
        );

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

## Controller del cron

*controllers/front/cron.php — endpoint seguro*

```php
<?php
class Ecom_crontasksCronModuleFrontController extends ModuleFrontController
{
    public $ajax = true;
    public $auth = false;

    /** Tiempo maximo de ejecucion (5 min) */
    const MAX_EXECUTION_TIME = 300;

    public function initContent()
    {
        // Validar clave secreta
        $key = Tools::getValue('secure_key');
        if ($key !== Configuration::get('ECOM_CRON_SECRET')) {
            $this->respond(['error' => 'Unauthorized'], 403);
            return;
        }

        // Evitar timeout
        set_time_limit(self::MAX_EXECUTION_TIME);
        ignore_user_abort(true);

        $task = Tools::getValue('task', 'all');
        $results = [];

        switch ($task) {
            case 'clean_carts':
                $results[] = $this->taskCleanCarts();
                break;
            case 'stock_alerts':
                $results[] = $this->taskStockAlerts();
                break;
            case 'all':
                $results[] = $this->taskCleanCarts();
                $results[] = $this->taskStockAlerts();
                break;
            default:
                $this->respond(['error' => 'Task not found: ' . $task], 400);
                return;
        }

        $this->respond(['success' => true, 'tasks' => $results]);
    }

    /**
     * Limpiar carritos abandonados
     */
    protected function taskCleanCarts(): array
    {
        $start = microtime(true);
        $days = (int) Configuration::get('ECOM_CRON_CART_DAYS') ?: 7;
        $dateLimit = date('Y-m-d H:i:s', strtotime("-{$days} days"));

        // Carritos no convertidos en pedido, sin sesion activa
        $sql = 'DELETE c FROM `' . _DB_PREFIX_ . 'cart` c
                LEFT JOIN `' . _DB_PREFIX_ . 'orders` o ON c.id_cart = o.id_cart
                WHERE o.id_order IS NULL
                AND c.date_upd < \'' . pSQL($dateLimit) . '\'
                AND c.id_customer = 0';

        Db::getInstance()->execute($sql);
        $affected = Db::getInstance()->Affected_Rows();

        $this->logTask('clean_carts', 'success', $affected, microtime(true) - $start);

        return [
            'task'      => 'clean_carts',
            'deleted'   => $affected,
            'threshold' => "{$days} days",
        ];
    }

    /**
     * Alertas de stock bajo
     */
    protected function taskStockAlerts(): array
    {
        $start = microtime(true);
        $threshold = (int) Configuration::get('ECOM_CRON_STOCK_ALERT') ?: 5;

        $products = Db::getInstance()->executeS(
            (new DbQuery())
                ->select('p.id_product, pl.name, sa.quantity')
                ->from('product', 'p')
                ->innerJoin('product_lang', 'pl',
                    'p.id_product = pl.id_product AND pl.id_lang = ' . (int) Configuration::get('PS_LANG_DEFAULT'))
                ->innerJoin('stock_available', 'sa',
                    'p.id_product = sa.id_product AND sa.id_product_attribute = 0')
                ->innerJoin('product_shop', 'ps',
                    'p.id_product = ps.id_product AND ps.id_shop = 1')
                ->where('ps.active = 1')
                ->where('sa.quantity <= ' . $threshold)
                ->where('sa.quantity > 0')
                ->orderBy('sa.quantity ASC')
                ->build()
        );

        // Enviar email al admin si hay productos con stock bajo
        if (!empty($products)) {
            $this->sendStockAlertEmail($products, $threshold);
        }

        $this->logTask('stock_alerts', 'success', count($products ?? []), microtime(true) - $start);

        return [
            'task'     => 'stock_alerts',
            'products' => count($products ?? []),
            'threshold'=> $threshold,
        ];
    }

    protected function logTask(string $name, string $status, int $items, float $time): void
    {
        Db::getInstance()->insert('ecom_cron_log', [
            'task_name'       => pSQL($name),
            'status'          => pSQL($status),
            'items_processed' => $items,
            'execution_time'  => round($time, 3),
            'date_add'        => date('Y-m-d H:i:s'),
        ]);
    }

    protected function respond(array $data, int $code = 200): void
    {
        http_response_code($code);
        header('Content-Type: application/json');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
        exit;
    }
}
```

## Ejemplo: limpiar carritos abandonados

La tarea `clean_carts` elimina carritos de invitados (id_customer = 0) que no se convirtieron en pedido y llevan mas de X dias sin actividad. Es seguro porque solo toca carritos sin pedido asociado y sin cliente registrado.

> **[!] Cuidado con carritos de clientes registrados**
>
> No elimines carritos de clientes registrados (id_customer > 0) sin avisarles primero. Podrian tener un carrito guardado intencionalmente. Para esos, envia un email de recordatorio antes de limpiar.

## Configuracion del crontab

*Crontab del servidor*

```bash
# Ejecutar todas las tareas cada 6 horas
0 */6 * * * curl -s "https://mitienda.com/module/ecom_crontasks/cron?secure_key=TU_CLAVE_SECRETA" > /dev/null 2>&1

# O ejecutar tareas individuales:
# Limpiar carritos cada dia a las 3am
0 3 * * * curl -s "https://mitienda.com/module/ecom_crontasks/cron?secure_key=TU_CLAVE&task=clean_carts" > /dev/null

# Alertas de stock cada 2 horas
0 */2 * * * curl -s "https://mitienda.com/module/ecom_crontasks/cron?secure_key=TU_CLAVE&task=stock_alerts" > /dev/null

# Alternativa con wget:
0 */6 * * * wget -q -O /dev/null "https://mitienda.com/module/ecom_crontasks/cron?secure_key=TU_CLAVE"
```

## Logging y monitorizacion

Siempre registra la ejecucion de tareas cron. Sin logs, es imposible saber si las tareas se ejecutan correctamente. La tabla `ecom_cron_log` almacena: nombre de tarea, estado, items procesados, tiempo de ejecucion y timestamp.

*Consultar logs del cron*

```sql
-- Ultimas 20 ejecuciones
SELECT task_name, status, items_processed, execution_time, date_add
FROM ps_ecom_cron_log
ORDER BY date_add DESC
LIMIT 20;

-- Errores de los ultimos 7 dias
SELECT * FROM ps_ecom_cron_log
WHERE status = 'error'
AND date_add > DATE_SUB(NOW(), INTERVAL 7 DAY);

-- Estadisticas por tarea
SELECT task_name,
       COUNT(*) as runs,
       SUM(items_processed) as total_items,
       AVG(execution_time) as avg_time,
       MAX(date_add) as last_run
FROM ps_ecom_cron_log
GROUP BY task_name;
```


---

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