LiveCommerce Blog

Tecnología, estrategia y resultados en ecommerce

Cómo descargar el catálogo CSV de Matacanaria automáticamente (ShopinCloud)

Si trabajas con Matacanaria y necesitas bajar su fichero de tarifas/catálogo en CSV de forma fiable, aquí tienes un enfoque práctico: automatizar el login, mantener la sesión y descargar el archivo para servirlo directamente al navegador desde tu infraestructura.

El problema típico

Muchos proveedores ofrecen el CSV detrás de un área privada. En el navegador funciona, pero en automatización suele fallar por:

  • Sesiones que dependen de cookies (si no las guardas, te devuelven la pantalla de login).
  • Redirecciones (si no sigues Location , te quedas a medias).
  • Descargas que “parecen” un CSV pero realmente devuelven HTML.
  • Concurrencia: dos procesos descargando a la vez se pisan (cookies o ficheros temporales).

Qué hace este código en ShopinCloud

En ShopinCloud monté una acción que hace la descarga de forma robusta usando cURL y una carpeta temporal. La idea es: login → validar sesión → descargar CSV con la misma sesión → servir el CSV al usuario .

1) Login con cURL y persistencia de cookies

El script hace un POST al formulario de login y guarda la sesión en un cookie jar (fichero de cookies). Así, cuando se llama al endpoint real del CSV, Matacanaria ya reconoce la sesión.

  • Se usa CURLOPT_FOLLOWLOCATION para seguir redirecciones.
  • Se configura CURLOPT_COOKIEJAR para persistir cookies en fichero.
  • Se valida que existan cookies (en memoria o en fichero) tras el login.

2) Carpeta temporal + lock para evitar concurrencia

Para que no haya dos descargas pisándose (especialmente con cookies), se crea un lock por email y se bloquea con flock(). Así garantizas que cada credencial mantiene su sesión limpia durante la descarga.

  • Directorio temporal tipo: cache/tmp/matacanaria.
  • Cookie file estable por usuario (hash del email).
  • Lock file asociado a ese cookie file.

3) Descarga del CSV usando la misma sesión

Con el mismo handle de cURL ($ch), se cambia la URL al endpoint de descarga y se vuelca directamente a un fichero temporal con CURLOPT_FILE. Esto evita tener que cargar todo el CSV en memoria.

4) Validaciones “anti-login”

El fallo más común es que el endpoint devuelva HTML (login) en vez de CSV. Para detectarlo:

  • Se leen los primeros bytes del fichero descargado y se busca <html, “login” o “iniciar sesión”.
  • Se revisa el HTTP status (si no es 2xx/3xx, se elimina el fichero).
  • Se calcula el tamaño real antes de servirlo (Content-Length).

5) Servir el CSV al navegador

Cuando el fichero ya está bien descargado, el script envía cabeceras de descarga (Content-Disposition) y hace readfile(). Además, limpia buffers con ob_end_clean() para evitar que se “ensucie” el CSV.

Recomendaciones rápidas

  • No hardcodees credenciales en el código: usa variables de entorno o configuración cifrada en tu plataforma.
  • Guarda logs solo si hace falta (y rota el fichero de verbose).
  • Si el proveedor cambia el formulario o endpoint, centraliza esas URLs en config.
  • Si hay varios usuarios/tiendas, genera cookie/lock por “cuenta” real del proveedor.

Código para compartir (sin credenciales)

Aquí tienes el código tal cual para que quien lo necesite lo adapte. Ojo: he eliminado email y password (y cualquier dato sensible). Configura tú las credenciales en tu sistema de configuración/entorno.

<?php

if (!defined('BASEPATH')) exit('No direct script access allowed');

class MY_matacanaria_action extends ED_Front_Actions
{
    // ✅ NO poner credenciales aquí. Cárgalas desde config/ENV.
    private $_email = '';
    private $_password = '';

    public function __construct()
    {
        parent::__construct();
    }

    public function downloadFile()
    {
        $loginUrl = 'https://www.matacanaria.com/iniciar-sesion?back=my-account';

        $email    = $this->_email;
        $password = $this->_password;

        if (empty($email) || empty($password)) {
            $this->output
                ->set_status_header(500)
                ->set_content_type('application/json')
                ->set_output(json_encode(['ok' => false, 'error' => 'Faltan credenciales Matacanaria en config']));
            return;
        }

        $catalogUrl = 'https://www.matacanaria.com/module/terranetpricelist/download'
            . '?getCsv=1&email=' . rawurlencode($email);

        $tmpDir = frontROOTPATH . 'cache/tmp/matacanaria';
        if (!is_dir($tmpDir)) {
            if (!mkdir($tmpDir, 0755, true)) {
                $this->output
                    ->set_status_header(500)
                    ->set_content_type('application/json')
                    ->set_output(json_encode(['ok' => false, 'error' => 'No se pudo crear el directorio temporal']));
                return;
            }
        }

        // Cookie estable + lock para evitar concurrencia pisándose
        $cookieFile = $tmpDir . DIRECTORY_SEPARATOR . 'matacanaria_cookie_' . md5($email) . '.txt';
        $lockFile   = $cookieFile . '.lock';

        $outFile = $tmpDir . DIRECTORY_SEPARATOR . 'matacanaria_catalog_' . date('Ymd_His') . '.csv';

        $lockFp = null;
        $fpOut  = null;
        $ch     = null;
        $verboseFp = null;

        try {
            $lockFp = @fopen($lockFile, 'c');
            if (!$lockFp) throw new Exception("No se pudo abrir lock: {$lockFile}");
            if (!flock($lockFp, LOCK_EX)) throw new Exception("No se pudo bloquear lock: {$lockFile}");

            // Cookie file limpio (sin helpers)
            @unlink($cookieFile);
            @touch($cookieFile);
            @chmod($cookieFile, 0666);
            clearstatcache(true, $cookieFile);

            // 1) LOGIN
            $postFields = http_build_query([
                'email'       => $email,
                'password'    => $password,
                'submitLogin' => '1',
            ]);

            $ch = curl_init();

            // Log opcional por si vuelve a fallar
            $verboseFp = @fopen($tmpDir . DIRECTORY_SEPARATOR . 'matacanaria_curl_verbose.log', 'ab');

            curl_setopt_array($ch, [
                CURLOPT_URL            => $loginUrl,
                CURLOPT_POST           => true,
                CURLOPT_POSTFIELDS     => $postFields,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS      => 10,

                // cookies en memoria + persistencia en fichero (debug)
                CURLOPT_COOKIEFILE     => '',          // activa engine cookies en memoria
                CURLOPT_COOKIEJAR      => $cookieFile,

                CURLOPT_SSL_VERIFYPEER => true,
                CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_USERAGENT      => 'Mozilla/5.0 (CI cURL Matacanaria)',
                CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
                CURLOPT_TIMEOUT        => 60,

                CURLOPT_VERBOSE        => $verboseFp ? true : false,
                CURLOPT_STDERR         => $verboseFp ?: null,
            ]);

            $loginBody = curl_exec($ch);
            if ($loginBody === false) {
                throw new Exception("Login falló (cURL): " . curl_error($ch));
            }

            $loginHttp = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($loginHttp < 200 || $loginHttp >= 400) {
                throw new Exception("Login falló (HTTP {$loginHttp})");
            }

            // ✅ Validación robusta: cookies en memoria (no filesize)
            $cookieList = curl_getinfo($ch, CURLINFO_COOKIELIST);
            $hasCookies = is_array($cookieList) && count($cookieList) > 0;

            // Fallback: contenido del cookieFile
            clearstatcache(true, $cookieFile);
            $cookieTxt = @file_get_contents($cookieFile);
            $cookieLen = strlen((string)$cookieTxt);

            if (!$hasCookies && $cookieLen === 0) {
                throw new Exception("Login sin cookies (no se generó sesión).");
            }

            // 2) DESCARGAR CSV (mismo $ch => misma sesión)
            $fpOut = fopen($outFile, 'wb');
            if ($fpOut === false) {
                throw new Exception("No se pudo crear el fichero de salida: {$outFile}");
            }

            curl_setopt_array($ch, [
                CURLOPT_URL            => $catalogUrl,
                CURLOPT_HTTPGET        => true,
                CURLOPT_POST           => false,
                CURLOPT_POSTFIELDS     => null,
                CURLOPT_RETURNTRANSFER => false,
                CURLOPT_FILE           => $fpOut,
                CURLOPT_TIMEOUT        => 120,
                // Por si el endpoint mira la cabecera Accept:
                CURLOPT_HTTPHEADER     => ['Accept: text/csv,*/*;q=0.8'],
            ]);

            $ok = curl_exec($ch);
            if ($ok === false) {
                throw new Exception("Descarga CSV falló (cURL): " . curl_error($ch));
            }

            $http        = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

            fflush($fpOut);
            fclose($fpOut);
            $fpOut = null;

            if ($http < 200 || $http >= 400) {
                @unlink($outFile);
                throw new Exception("Descarga CSV falló (HTTP {$http})");
            }

            // Check anti-HTML (si te devuelve login)
            clearstatcache(true, $outFile);
            $head = $this->readFirstBytes($outFile, 1024);
            if (stripos($head, '<html') !== false || stripos($head, 'iniciar sesión') !== false || stripos($head, 'login') !== false) {
                @unlink($outFile);
                throw new Exception("Devolvió HTML (probable no autenticado). Content-Type: {$contentType}");
            }

            // Tamaño robusto
            clearstatcache(true, $outFile);
            $outSize = @filesize($outFile);
            if (!$outSize || $outSize <= 0) {
                $outSize = strlen((string)@file_get_contents($outFile));
            }

            // 3) SERVIR CSV AL NAVEGADOR
            $filename = 'catalog_matacanaria_' . date('Ymd_His') . '.csv';

            header('Content-Description: File Transfer');
            header('Content-Type: text/csv; charset=UTF-8');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Content-Transfer-Encoding: binary');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            header('Expires: 0');
            if ($outSize > 0) {
                header('Content-Length: ' . $outSize);
            }

            while (ob_get_level()) {
                @ob_end_clean();
            }

            readfile($outFile);
            exit;

        } catch (Exception $e) {
            while (ob_get_level()) {
                @ob_end_clean();
            }
            http_response_code(500);
            header('Content-Type: application/json');
            echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
            exit;

        } finally {
            if (is_resource($fpOut)) @fclose($fpOut);
            if (is_resource($ch)) @curl_close($ch);
            if (is_resource($verboseFp)) @fclose($verboseFp);
            if (is_resource($lockFp)) {
                @flock($lockFp, LOCK_UN);
                @fclose($lockFp);
            }

            // Si quieres conservar cookie para inspección, comenta esto:
            // @unlink($cookieFile);

            if (isset($outFile) && is_file($outFile)) {
                register_shutdown_function(function() use ($outFile) {
                    @unlink($outFile);
                });
            }
        }
    }

    private function readFirstBytes($file, $bytes = 512)
    {
        $fh = @fopen($file, 'rb');
        if (!$fh) return '';
        $data = fread($fh, $bytes);
        fclose($fh);
        return $data !== false ? $data : '';
    }
}
Compártelo:

¿Tienes alguna consulta?

Si tienes alguna pregunta o sabes la respuesta sobre algún comentario, no dudes en contribuir.
Responderemos rápidamente.
Puedes utilizar etiquetas BBCode para escribir negrita, enlaces, imágenes, etc...
Más información en la página oficial de BBCOde http://www.bbcode.org/ Ejemplo:
[url=http://google.com]links[/url], [color=red]colores[/color] [b]negrita[/b]...

¿Has visto los videos en nuestro canal de Youtube?

En nuestro canal de Youtube publicamos periódicamente mejoras y funcionalidades del software de ecommerce .