Saltar al contenido principal
11 min de lectura

Frameworks personalizados

Integre los análisis de Zenovay con frameworks personalizados, herramientas de compilación y arquitecturas únicas. Esta guía cubre los patrones para cualquier configuración no contemplada por nuestras integraciones oficiales.


Referencia rápida

Tipo de integraciónComplejidadIdeal para
Etiqueta de scriptSimpleSitios estáticos, configuraciones básicas
API de JavaScriptMediaSPAs, enrutamiento dinámico
API HTTPAvanzadoServidor, headless, IoT
Proxy de primera parteAvanzadoOmisión de bloqueadores de anuncios

Métodos de integración principales

Método 1: Etiqueta de script (el más simple)

Añada el script de seguimiento a cualquier página HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Your Site</title>
    <!-- Zenovay Analytics -->
    <script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
</head>
<body>
    <!-- Your content -->
</body>
</html>

Atributos:

AtributoRequeridoDescripción
data-tracking-codeSu código de seguimiento
deferRecomendadoCarga no bloqueante
data-api-urlNoURL del proxy de primera parte
data-autoNoEstablezca false para desactivar las páginas vistas automáticas

Método 2: API de JavaScript

Para control programático:

// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
    (window.zenovay.q = window.zenovay.q || []).push(arguments);
};

// Track page views manually
zenovay('page');

// Track custom events
zenovay('track', 'button_click', {
    button_name: 'signup',
    location: 'header'
});

// Identify users
zenovay('identify', 'user_123', {
    email: '[email protected]',
    plan: 'pro'
});

Método 3: API HTTP

Para entornos de servidor o sin navegador:

// Node.js example
const response = await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Forwarded-For': clientIp // Important for geo
    },
    body: JSON.stringify({
        event: 'pageview',
        url: 'https://yoursite.com/page',
        referrer: 'https://google.com',
        user_agent: clientUserAgent,
        timestamp: new Date().toISOString()
    })
});

Consulte Endpoints de API para la documentación completa de la API.


Aplicaciones de página única (SPAs)

Las SPAs requieren el seguimiento manual de páginas vistas en los cambios de ruta.

Patrón genérico para SPA

// Create a tracking wrapper
class ZenovayTracker {
    constructor(siteId) {
        this.siteId = siteId;
        this.previousPath = null;
    }

    init() {
        // Queue function (works before script loads)
        window.zenovay = window.zenovay || function() {
            (window.zenovay.q = window.zenovay.q || []).push(arguments);
        };
    }

    trackPageView(path) {
        // Prevent duplicate tracking
        if (path === this.previousPath) return;
        this.previousPath = path;

        zenovay('page');
    }

    trackEvent(name, properties = {}) {
        zenovay('track', name, properties);
    }
}

// Usage
const tracker = new ZenovayTracker('YOUR_TRACKING_CODE');
tracker.init();

// On route change
router.on('change', (route) => {
    tracker.trackPageView(route.path);
});

Integración con la History API

Para SPAs que usan la History API:

// Intercept pushState and replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

history.pushState = function(...args) {
    originalPushState.apply(this, args);
    window.dispatchEvent(new Event('locationchange'));
};

history.replaceState = function(...args) {
    originalReplaceState.apply(this, args);
    window.dispatchEvent(new Event('locationchange'));
};

// Also handle popstate (back/forward)
window.addEventListener('popstate', () => {
    window.dispatchEvent(new Event('locationchange'));
});

// Track on location change
window.addEventListener('locationchange', () => {
    zenovay('page');
});

Enrutamiento basado en hash

Para aplicaciones que usan enrutamiento por hash:

window.addEventListener('hashchange', () => {
    zenovay('page');
});

// Initial page view
document.addEventListener('DOMContentLoaded', () => {
    zenovay('page');
});

Generadores de sitios estáticos

Eleventy (11ty)

Cree _includes/analytics.njk:

<!-- Zenovay Analytics -->
<script defer data-tracking-code="{{ site.zenovayId }}" src="https://api.zenovay.com/z.js"></script>

Añada a su plantilla base:

<!DOCTYPE html>
<html>
<head>
    {% include "analytics.njk" %}
</head>
<body>
    {{ content | safe }}
</body>
</html>

En _data/site.js:

module.exports = {
    zenovayId: process.env.ZENOVAY_TRACKING_CODE || 'YOUR_TRACKING_CODE'
};

Hugo

Cree layouts/partials/analytics.html:

{{ if not .Site.IsServer }}
<script defer data-tracking-code="{{ .Site.Params.zenovayId }}" src="https://api.zenovay.com/z.js"></script>
{{ end }}

En config.toml:

[params]
zenovayId = "YOUR_TRACKING_CODE"

Incluya en layouts/_default/baseof.html:

<head>
    {{ partial "analytics.html" . }}
</head>

Jekyll

Cree _includes/analytics.html:

{% if jekyll.environment == "production" %}
<script defer data-tracking-code="{{ site.zenovay_id }}" src="https://api.zenovay.com/z.js"></script>
{% endif %}

En _config.yml:

zenovay_id: "YOUR_TRACKING_CODE"

Gatsby

Cree gatsby-ssr.js:

import React from "react";

export const onRenderBody = ({ setHeadComponents }) => {
    if (process.env.NODE_ENV === "production") {
        setHeadComponents([
            <script
                key="zenovay"
                defer
                data-tracking-code={process.env.GATSBY_ZENOVAY_ID}
                src="https://api.zenovay.com/z.js"
            />
        ]);
    }
};

Para los cambios de ruta, en gatsby-browser.js:

export const onRouteUpdate = ({ location }) => {
    if (typeof window.zenovay !== 'undefined') {
        window.zenovay('page');
    }
};

Remix

Añada a app/root.tsx:

import { Scripts } from "@remix-run/react";

export default function App() {
    return (
        <html>
            <head>
                {process.env.NODE_ENV === "production" && (
                    <script
                        defer
                        data-tracking-code={process.env.ZENOVAY_ID}
                        src="https://api.zenovay.com/z.js"
                    />
                )}
            </head>
            <body>
                <Outlet />
                <Scripts />
            </body>
        </html>
    );
}

Aplicaciones web progresivas (PWAs)

Seguimiento de instalación

// Track PWA installation
window.addEventListener('beforeinstallprompt', (e) => {
    zenovay('track', 'pwa_install_prompt_shown');
});

window.addEventListener('appinstalled', () => {
    zenovay('track', 'pwa_installed', {
        timestamp: new Date().toISOString()
    });
});

Cola de seguimiento sin conexión

Rastree eventos sin conexión y envíelos cuando haya conexión:

class OfflineQueue {
    constructor() {
        this.storageKey = 'zenovay_offline_queue';
        this.init();
    }

    init() {
        // Send queued events when coming online
        window.addEventListener('online', () => this.flush());

        // Check if we need to flush on load
        if (navigator.onLine) {
            this.flush();
        }
    }

    getQueue() {
        try {
            return JSON.parse(localStorage.getItem(this.storageKey)) || [];
        } catch {
            return [];
        }
    }

    saveQueue(queue) {
        localStorage.setItem(this.storageKey, JSON.stringify(queue));
    }

    add(event, properties) {
        const queue = this.getQueue();
        queue.push({
            event,
            properties,
            timestamp: new Date().toISOString(),
            url: window.location.href
        });
        this.saveQueue(queue);

        if (navigator.onLine) {
            this.flush();
        }
    }

    async flush() {
        const queue = this.getQueue();
        if (queue.length === 0) return;

        const successful = [];

        for (const item of queue) {
            try {
                zenovay('track', item.event, {
                    ...item.properties,
                    _queued_at: item.timestamp,
                    _sent_at: new Date().toISOString()
                });
                successful.push(item);
            } catch (error) {
                console.error('Failed to send queued event:', error);
                break; // Stop on first failure
            }
        }

        // Remove successfully sent items
        const remaining = queue.filter(item => !successful.includes(item));
        this.saveQueue(remaining);
    }
}

// Usage
const offlineQueue = new OfflineQueue();

// Track event (works offline)
offlineQueue.add('page_view', { path: '/home' });

Integración con Service Worker

En su service worker:

// sw.js
self.addEventListener('sync', (event) => {
    if (event.tag === 'zenovay-sync') {
        event.waitUntil(syncAnalytics());
    }
});

async function syncAnalytics() {
    const cache = await caches.open('zenovay-analytics');
    const requests = await cache.keys();

    for (const request of requests) {
        try {
            await fetch(request);
            await cache.delete(request);
        } catch {
            // Will retry on next sync
        }
    }
}

Web Components

Elemento personalizado con seguimiento

class TrackableButton extends HTMLElement {
    static get observedAttributes() {
        return ['track-event', 'track-properties'];
    }

    connectedCallback() {
        this.addEventListener('click', this.handleClick.bind(this));
    }

    handleClick() {
        const eventName = this.getAttribute('track-event') || 'button_click';
        let properties = {};

        try {
            properties = JSON.parse(this.getAttribute('track-properties') || '{}');
        } catch {}

        if (window.zenovay) {
            zenovay('track', eventName, {
                ...properties,
                element: this.tagName.toLowerCase(),
                text: this.textContent
            });
        }
    }
}

customElements.define('trackable-button', TrackableButton);

Uso:

<trackable-button
    track-event="cta_click"
    track-properties='{"location": "hero", "variant": "primary"}'>
    Get Started
</trackable-button>

Consideraciones con Shadow DOM

Cuando se usa Shadow DOM, adjunte los event listeners a la shadow root:

class TrackedComponent extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
    }

    connectedCallback() {
        this.render();
        this.setupTracking();
    }

    render() {
        // Create button element safely
        const button = document.createElement('button');
        button.id = 'cta';
        button.textContent = 'Click Me';
        this.shadowRoot.appendChild(button);
    }

    setupTracking() {
        // Track clicks within shadow DOM
        this.shadowRoot.addEventListener('click', (e) => {
            if (e.target.id === 'cta') {
                zenovay('track', 'shadow_button_click', {
                    component: 'tracked-component'
                });
            }
        });

        // Track visibility with Intersection Observer
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    zenovay('track', 'component_viewed', {
                        component: 'tracked-component'
                    });
                    observer.disconnect();
                }
            });
        });

        observer.observe(this);
    }
}

Integraciones con herramientas de compilación

Plugin de Vite

Cree vite-plugin-zenovay.js:

export default function zenovayPlugin(options = {}) {
    const { siteId, enabled = true } = options;

    return {
        name: 'vite-plugin-zenovay',
        transformIndexHtml(html) {
            if (!enabled || !siteId) return html;

            const script = `<script defer data-tracking-code="${siteId}" src="https://api.zenovay.com/z.js"></script>`;

            return html.replace('</head>', `${script}</head>`);
        }
    };
}

// Usage in vite.config.js
import zenovayPlugin from './vite-plugin-zenovay';

export default {
    plugins: [
        zenovayPlugin({
            siteId: process.env.VITE_ZENOVAY_ID,
            enabled: process.env.NODE_ENV === 'production'
        })
    ]
};

Plugin de Webpack

Cree webpack-zenovay-plugin.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');

class ZenovayWebpackPlugin {
    constructor(options = {}) {
        this.options = options;
    }

    apply(compiler) {
        compiler.hooks.compilation.tap('ZenovayPlugin', (compilation) => {
            HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
                'ZenovayPlugin',
                (data, cb) => {
                    if (this.options.siteId && this.options.enabled !== false) {
                        const script = `<script defer data-tracking-code="${this.options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
                        data.html = data.html.replace('</head>', `${script}</head>`);
                    }
                    cb(null, data);
                }
            );
        });
    }
}

module.exports = ZenovayWebpackPlugin;

// Usage in webpack.config.js
const ZenovayPlugin = require('./webpack-zenovay-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin(),
        new ZenovayPlugin({
            siteId: process.env.ZENOVAY_ID,
            enabled: process.env.NODE_ENV === 'production'
        })
    ]
};

Plugin de Rollup

// rollup-plugin-zenovay.js
export default function zenovayPlugin(options = {}) {
    return {
        name: 'zenovay',
        generateBundle(outputOptions, bundle) {
            for (const fileName in bundle) {
                if (fileName.endsWith('.html')) {
                    const chunk = bundle[fileName];
                    if (chunk.type === 'asset') {
                        const script = `<script defer data-tracking-code="${options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
                        chunk.source = chunk.source.replace('</head>', `${script}</head>`);
                    }
                }
            }
        }
    };
}

Seguimiento en el lado del servidor

Patrón de SDK de Node.js

// zenovay-server.js
class ZenovayServer {
    constructor(siteId, options = {}) {
        this.siteId = siteId;
        this.apiUrl = options.apiUrl || 'https://api.zenovay.com/e/';
        this.queue = [];
        this.flushInterval = options.flushInterval || 5000;
        this.batchSize = options.batchSize || 10;

        this.startFlushTimer();
    }

    startFlushTimer() {
        setInterval(() => this.flush(), this.flushInterval);
    }

    track(event, properties = {}, context = {}) {
        this.queue.push({
            event,
            properties,
            user_agent: context.userAgent,
            ip: context.ip,
            url: context.url,
            referrer: context.referrer,
            timestamp: new Date().toISOString()
        });

        if (this.queue.length >= this.batchSize) {
            this.flush();
        }
    }

    async flush() {
        if (this.queue.length === 0) return;

        const events = this.queue.splice(0, this.batchSize);

        try {
            await fetch(this.apiUrl + this.siteId, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ events })
            });
        } catch (error) {
            // Re-queue on failure
            this.queue.unshift(...events);
            console.error('Zenovay: Failed to send events', error);
        }
    }

    // Express middleware
    middleware() {
        return (req, res, next) => {
            req.trackEvent = (event, properties) => {
                this.track(event, properties, {
                    userAgent: req.headers['user-agent'],
                    ip: req.ip,
                    url: req.originalUrl,
                    referrer: req.headers.referer
                });
            };
            next();
        };
    }
}

module.exports = ZenovayServer;

// Usage
const ZenovayServer = require('./zenovay-server');
const zenovay = new ZenovayServer('YOUR_TRACKING_CODE');

app.use(zenovay.middleware());

app.get('/checkout', (req, res) => {
    req.trackEvent('checkout_started', {
        cart_value: 99.99
    });
    res.render('checkout');
});

Ejemplo en Python

import requests
from datetime import datetime
from threading import Thread
from queue import Queue

class ZenovayTracker:
    def __init__(self, tracking_code, api_url='https://api.zenovay.com/e/'):
        self.tracking_code = tracking_code
        self.api_url = api_url
        self.queue = Queue()
        self._start_worker()

    def _start_worker(self):
        def worker():
            while True:
                event = self.queue.get()
                try:
                    requests.post(self.api_url + self.tracking_code, json=event, timeout=5)
                except Exception as e:
                    print(f"Zenovay error: {e}")
                self.queue.task_done()

        thread = Thread(target=worker, daemon=True)
        thread.start()

    def track(self, event, properties=None, context=None):
        context = context or {}
        self.queue.put({
            'event': event,
            'properties': properties or {},
            'user_agent': context.get('user_agent'),
            'ip': context.get('ip'),
            'url': context.get('url'),
            'timestamp': datetime.utcnow().isoformat()
        })

# Flask example
from flask import Flask, request

app = Flask(__name__)
tracker = ZenovayTracker('YOUR_TRACKING_CODE')

@app.route('/purchase', methods=['POST'])
def purchase():
    tracker.track('purchase', {
        'revenue': request.json.get('total'),
        'currency': 'USD'
    }, {
        'user_agent': request.headers.get('User-Agent'),
        'ip': request.remote_addr,
        'url': request.url
    })
    return {'status': 'ok'}

Pruebas y depuración

Modo de depuración

// Enable debug mode
zenovay('debug');

// All events will be logged to console
zenovay('track', 'test_event', { test: true });
// Console: [Zenovay] Event tracked: test_event { test: true }

Rastreador simulado para pruebas

// __mocks__/zenovay.js
const mockZenovay = {
    calls: [],

    handler(...args) {
        mockZenovay.calls.push(args);
    },

    reset() {
        mockZenovay.calls = [];
    },

    getEvents() {
        return mockZenovay.calls.filter(c => c[0] === 'track');
    }
};

window.zenovay = (...args) => mockZenovay.handler(...args);
window.zenovay.mock = mockZenovay;

// In your test
test('tracks button click', () => {
    window.zenovay.mock.reset();

    fireEvent.click(getByRole('button', { name: 'Sign Up' }));

    expect(window.zenovay.mock.getEvents()).toContainEqual(
        ['track', 'signup_click', { location: 'header' }]
    );
});

Inspección de red

Compruebe que el seguimiento funciona:

  1. Abra las DevTools → pestaña Network
  2. Filtre por zenovay o z.js
  3. Navegue por su sitio
  4. Verifique las solicitudes a api.zenovay.com

Política de seguridad de contenido

Configure las cabeceras CSP para permitir Zenovay:

Content-Security-Policy:
    default-src 'self';
    script-src 'self' https://api.zenovay.com;
    connect-src 'self' https://api.zenovay.com;

Para el seguimiento de primera parte (recomendado):

Content-Security-Policy:
    default-src 'self';
    script-src 'self';
    connect-src 'self';

Buenas prácticas

  1. Cargue de forma asíncrona: Use siempre defer o async para evitar bloqueos
  2. Gestione los errores: Envuelva las llamadas de seguimiento en try-catch para mayor resiliencia
  3. Respete la privacidad: Compruebe el consentimiento antes de rastrear, respete DNT
  4. Agrupe los eventos: Combine eventos cuando sea posible para reducir solicitudes
  5. Pruebe exhaustivamente: Verifique el seguimiento en múltiples navegadores y dispositivos
  6. Use la primera parte: Configure el seguimiento de primera parte para mayor precisión
  7. Monitorice el rendimiento: Asegúrese de que el seguimiento no afecte a los Core Web Vitals

Recursos relacionados


¿Necesita ayuda con su integración? Contacte con [email protected].

¿Fue útil esta página?