Skip to main content

node-api

Backend principal de Crimoo. Express 5 + TypeScript + Clean Architecture.

Stack

  • Framework: Express.js 5.x + TypeScript
  • BD Principal: PostgreSQL (TypeORM, 34 entities)
  • Analytics: ClickHouse
  • Mensajería: Google Cloud Pub/Sub
  • Auth: JWT + Google OAuth 2.0
  • Pagos: Stripe
  • IA: Google Gemini

Entidades principales

Endpoints principales

Auth /api/auth

MétodoPathAuth
POST/loginNo
POST/registerNo
GET/googleNo
GET/google/callbackNo
GET/meJWT
POST/logoutNo

GTM /api/v1/gtm

MétodoPathDescripción
POST/Crear GTM → publica a Pub/Sub
GET/Listar GTMs del workspace
GET/:idObtener GTM
DELETE/:idEliminar GTM
PUT/:id/custom-loaderHabilitar custom loader
GET/:id/logsLogs históricos (ClickHouse)
GET/:id/logs/streamStream SSE en vivo
GET/:id/healthHealth del container

CRM /api/contacts + /api/deals

MétodoPath
GET/POST/contacts
GET/PUT/DELETE/contacts/:id
GET/contacts/:id/timeline
POST/contacts/:id/activities
GET/POST/deals
PUT/DELETE/deals/:id

CRM Webhooks /api/crm/webhooks

MétodoPathDescripción
GET/:gtmIdListar webhooks del container
POST/:gtmIdCrear webhook (secret visible solo al crear)
GET/:gtmId/:idObtener webhook (secret enmascarado)
PUT/:gtmId/:idActualizar webhook
DELETE/:gtmId/:idEliminar webhook
POST/:gtmId/:id/testEnviar entrega de prueba
GET/:gtmId/:id/deliveriesHistorial de entregas (TTL 7 días)
POST/:gtmId/:id/reset-circuitResetear circuit breaker

Conversiones /api/conversions

MétodoPath
GET/POST/credentials
GET/PUT/DELETE/credentials/:id
POST/credentials/:id/test
GET/POST/triggers
PATCH/triggers/:id/toggle
GET/events
GET/stats

Dominios /api/v1/gtm/:id/domains

MétodoPathDescripción
GET/domainsListar dominios del GTM (paginado)
POST/domains/addAgregar dominio custom
POST/domains/updateActualizar dominio primario
DELETE/domains/removeEliminar dominio
POST/domains/:domain/verifyVerificar DNS + disparar generación de cert

Integración de Ads /api/integrations/ads

MétodoPathDescripción
GET/Listar integraciones del workspace (status, plataforma, última sync)
POST/:platform/connectIniciar OAuth flow (google_ads, meta_ads, tiktok_ads)
GET/:platform/callbackCallback OAuth → guarda tokens → redirige al dashboard
DELETE/:platformRevocar integración y eliminar tokens
GET/accountsListar ad accounts disponibles de todas las integraciones
POST/syncForzar sync manual (rate-limited: 1 por hora por workspace)
GET/performanceMétricas agregadas — params: startDate, endDate, platform?, campaignId?
GET/performance/comparisonMétricas de plataforma vs. conversiones Crimoo server-side

Integración de Plataformas de Ads (Read-Only)

Módulo planificado que conecta las cuentas de Google Ads, Meta Ads y TikTok Ads de los usuarios de Crimoo para hacer pull de métricas de performance publicitario. Los datos se almacenan en ClickHouse junto con los eventos de GTM, habilitando comparaciones directas entre atribución de plataforma vs. atribución server-side de Crimoo.

Estado: Planificado — no implementado aún.

Motivación

Crimoo ya rastrea conversiones server-side (vía GTM) y las envía a plataformas publicitarias. El paso natural es también leer las métricas que esas plataformas reportan, para poder comparar:

FuenteConversiones reportadas
Google Ads / Meta / TikTokLas que ellas atribuyen (click-based, view-through)
Crimoo GTM (server-side)Las que Crimoo rastreó con datos de primera parte

Esta diferencia es el gap de atribución — uno de los insights más valiosos para un anunciante.

Plataformas y fases

FasePlataformaJustificación
1Google AdsGoogle OAuth ya existe en Crimoo — menor fricción
2Meta AdsAlta demanda, API estable
3TikTok AdsAudiencias jóvenes, crecimiento sostenido

Arquitectura

Modelo de datos

PostgreSQL — Configuración de integración

-- Una integración por workspace + plataforma
CREATE TABLE ad_integrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
platform VARCHAR(20) NOT NULL, -- 'google_ads' | 'meta_ads' | 'tiktok_ads'
status VARCHAR(20) NOT NULL DEFAULT 'active', -- 'active' | 'expired' | 'revoked'
access_token TEXT NOT NULL, -- AES-256-GCM encriptado
refresh_token TEXT, -- AES-256-GCM encriptado
token_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (workspace_id, platform)
);

-- Cuentas publicitarias vinculadas (una integración puede tener varias)
CREATE TABLE ad_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
integration_id UUID NOT NULL REFERENCES ad_integrations(id) ON DELETE CASCADE,
platform_account_id VARCHAR(100) NOT NULL,
name VARCHAR(255),
currency CHAR(3),
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now()
);

ClickHouse — Métricas diarias

CREATE TABLE ad_performance_daily (
workspace_id UUID,
integration_id UUID,
platform LowCardinality(String), -- 'google_ads' | 'meta_ads' | 'tiktok_ads'
account_id String,
campaign_id String,
campaign_name String,
date Date,
spend Decimal(12, 4),
impressions UInt64,
clicks UInt64,
conversions Float64, -- reportadas por la plataforma
revenue Decimal(12, 4),
currency FixedString(3),
inserted_at DateTime DEFAULT now()
) ENGINE = ReplacingMergeTree(inserted_at)
PARTITION BY toYYYYMM(date)
ORDER BY (workspace_id, platform, account_id, campaign_id, date);

ReplacingMergeTree permite re-sincronizar el mismo día sin duplicados — el sync idempotente sobrescribe por (workspace_id, platform, account_id, campaign_id, date).

OAuth por plataforma

Google Ads — Reutiliza el Google OAuth existente (/api/auth/google) con scopes adicionales: https://www.googleapis.com/auth/adwords. Acceso vía google-ads-api Node.js client con Customer ID seleccionado.

Meta Ads — OAuth independiente a graph.facebook.com/oauth/authorize. Scopes: ads_read, ads_management. Token de larga duración (60 días), renovado automáticamente por OAuthTokenService.

TikTok Ads — OAuth independiente a business-api.tiktok.com/portal/auth. Scopes: ad.read. Access token de 24h + refresh token de 30 días.

Flujo de sincronización

Rango de sync: Últimos 7 días en cada ciclo (no solo ayer) — corrige datos retroactivos de las plataformas (atribución diferida, view-through tardío).

Vista en el Frontend

Nuevo tab "Ad Performance" en el dashboard de Crimoo.

KPIs superiores: Gasto total · Impresiones · Clicks · CTR · CPC promedio · ROAS

Tabla de campañas:

ColumnaFuente
CampañaPlataforma
PlataformaPlataforma
GastoPlataforma
ClicksPlataforma
Conv. (Plataforma)Plataforma
Conv. (Crimoo)ClickHouse — eventos GTM server-side
Gap de atribución(Conv. Plataforma - Conv. Crimoo) / Conv. Plataforma
ROASRevenue / Spend

Si no hay integraciones → wizard con botones por plataforma para iniciar OAuth.

Seguridad

  • Tokens OAuth almacenados con AES-256-GCM (mismo mecanismo que PlatformCredential en conversiones offline)
  • Los tokens nunca se exponen al frontend — solo status y token_expires_at
  • Al revocar, se eliminan tokens de PostgreSQL y se llama al endpoint de revocación de la plataforma si existe
  • Scopes solicitados son mínimos (solo lectura)

Variables de entorno nuevas

VariableDescripción
GOOGLE_ADS_DEVELOPER_TOKENToken de desarrollador requerido por Google Ads API
META_APP_IDApp ID de Meta for Developers
META_APP_SECRETApp Secret de Meta
TIKTOK_APP_IDApp ID de TikTok for Business
TIKTOK_APP_SECRETApp Secret de TikTok
ADS_OAUTH_REDIRECT_BASE_URLBase URL para callbacks OAuth (ej: https://api.crimoo.com)

Copilot Sessions /api/sessions

MétodoPathAuthDescripción
POST/JWTCrear sesión → retorna initToken (120s)
POST/claimNoCanjear initToken → activationToken
POST/:id/heartbeatactivationTokenPing para mantener sesión viva
POST/:id/disconnectactivationTokenDesconexión explícita
GET/:id/streamNoSSE — estado en tiempo real
GET/gtm.jsNoScript loader que inyecta el iframe del copilot

Ver flujo completo en Flujo de Sesión — Crimoo Copilot.

Endpoints internos /internal/fabric/*

Solo accesibles desde gtm-fabric (sin autenticación, red interna vía Tailscale):

MétodoPathDescripción
GET/fabric/gtms?vmId=XConfig de GTMs para una VM
PATCH/fabric/containers/:id/portsReportar puertos tras provisioning
POST/fabric/contacts/batchBatch flush de contactos CRM
POST/fabric/usageReportar usage por GTM
POST/fabric/eventsEventos de ciclo de vida
PUT/fabric/certificatesReportar cert generado/renovado
POST/fabric/acme-accountsGuardar cuenta ACME de la VM
GET/fabric/domains?vmId=X&status=YDominios filtrados por VM y status
PATCH/fabric/domains/:domain/statusActualizar status de dominio (ACTIVE/FAILED)

Scheduler: Renovación de certificados

CertificateRenewalScheduler corre diario a las 2:00 AM UTC:

  1. Busca certs en PostgreSQL con expires_at < 30 días y auto_renew = true
  2. Agrupa por GTM → VM → ACME account (batch, evita queries repetidas)
  3. Llama a fabric POST /certificates/renew en paralelo por cada cert
  4. Fabric ejecuta ACME, escribe PEM, notifica proxy, devuelve cert nuevo
  5. Actualiza PostgreSQL con los PEM y fechas nuevas

Flujo de Billing (Stripe)

Deployment

node-api corre en producción en la Hostinger VPS (Tailscale: 100.97.60.119) dentro de Docker, junto a PostgreSQL y ClickHouse en la red crimoo_net.

Dockerfile

Se usa Dockerfile.prod (no el multi-stage Dockerfile). El dist/ se compila localmente y se sube por scp — compilar TypeScript dentro del container supera la RAM disponible en la VPS.

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
EXPOSE 3000
CMD ["node", "dist/app.js"]

Docker Compose

Definido en deployment/docker-compose.prod.yml. El servicio node-api depende de que postgres y clickhouse estén healthy antes de arrancar.

node-api:
build:
context: ..
dockerfile: Dockerfile.prod
container_name: crimoo_node_api
depends_on:
postgres: {condition: service_healthy}
clickhouse: {condition: service_healthy}
ports:
- "3000:3000"
volumes:
- /opt/crimoo/gcp-credentials.json:/app/gcp-credentials.json:ro
env_file:
- .env.prod
networks:
- crimoo_net

Acceso público

node-api no está expuesto directamente a internet. El flujo es:

ui-angular → Cloudflare (api.crimoo.com) → gtm-proxy :443 → node-api :3000

Variables de entorno clave (prod)

VariableValor
DB_HOSTpostgres (nombre del servicio Docker)
CLICKHOUSE_HOSTclickhouse (nombre del servicio Docker)
GTM_FABRIC_URLhttp://100.97.60.119:8000 (Tailscale)
GOOGLE_APPLICATION_CREDENTIALS/app/gcp-credentials.json

Actualizar en producción

cd /opt/node-api
git pull origin main
npm run build # local, luego scp dist/ a la VPS
docker compose -f deployment/docker-compose.prod.yml up -d --build node-api

Ver node-api/deployment/DEPLOY.md en el workspace para el proceso completo.

Flujo de Copilot IA