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ón | Complejidad | Ideal para |
|---|---|---|
| Etiqueta de script | Simple | Sitios estáticos, configuraciones básicas |
| API de JavaScript | Media | SPAs, enrutamiento dinámico |
| API HTTP | Avanzado | Servidor, headless, IoT |
| Proxy de primera parte | Avanzado | Omisió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:
| Atributo | Requerido | Descripción |
|---|---|---|
data-tracking-code | Sí | Su código de seguimiento |
defer | Recomendado | Carga no bloqueante |
data-api-url | No | URL del proxy de primera parte |
data-auto | No | Establezca 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:
- Abra las DevTools → pestaña Network
- Filtre por
zenovayoz.js - Navegue por su sitio
- 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
- Cargue de forma asíncrona: Use siempre
deferoasyncpara evitar bloqueos - Gestione los errores: Envuelva las llamadas de seguimiento en try-catch para mayor resiliencia
- Respete la privacidad: Compruebe el consentimiento antes de rastrear, respete DNT
- Agrupe los eventos: Combine eventos cuando sea posible para reducir solicitudes
- Pruebe exhaustivamente: Verifique el seguimiento en múltiples navegadores y dispositivos
- Use la primera parte: Configure el seguimiento de primera parte para mayor precisión
- Monitorice el rendimiento: Asegúrese de que el seguimiento no afecte a los Core Web Vitals
Recursos relacionados
- Script de seguimiento
- Eventos personalizados
- Seguimiento de primera parte
- Endpoints de API
- Ejemplo con JavaScript puro
¿Necesita ayuda con su integración? Contacte con [email protected].