Manual Técnico

Configuración, mantenimiento y flujo de trabajo de la plataforma Revo Analytics.

Visión general

Revo Analytics es una plataforma multi-tenant que extrae datos de la API de Revo XEF, los transforma en un modelo analítico estrella (star schema) y los expone a través de:

Stack tecnológico

Componente Tecnología Versión
Runtime Python 3.12+
Base de datos PostgreSQL 16+
API Server FastAPI + Uvicorn 0.100+
ORM SQLAlchemy 2.0+
Frontend HTML + Chart.js 4.4.7 CDN
Auth admin PyJWT (HS256) + bcrypt 2.8+ / 4.0+
Auth API API Keys (SHA-256 hash)
ETL Pandas + Requests 2.0+

Arquitectura

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Revo XEF   │────▶│     ETL      │────▶│  PostgreSQL  │
│   (API REST) │     │  pipeline.py │     │ Star Schema  │
└──────────────┘     └──────────────┘     └──────┬───────┘
                                                 │
                           ┌─────────────────────┼─────────────────────┐
                           │                     │                     │
                    ┌──────▼──────┐     ┌────────▼────────┐  ┌────────▼────────┐
                    │  Dashboard  │     │   OData v4 API  │  │   Admin Panel   │
                    │  index.html │     │ /odata/v1/{ent} │  │    /panel       │
                    │  (Chart.js) │     │  (Power BI)     │  │  (JWT + RBAC)   │
                    └─────────────┘     └─────────────────┘  └─────────────────┘
                         │                      │                     │
                    API Key auth          API Key / Basic        JWT Bearer

Estructura de archivos

revo-analytics/
├── .env # Variables de entorno (no en git)
├── requirements.txt # Dependencias Python
├── config/
│ ├── settings.py # Pydantic Settings (5 grupos)
│ └── database.py # SQLAlchemy engine + SessionLocal
├── models/
│ ├── system.py # Distribuidor, Tenant, User, ApiKey, EtlLog, Informe
│ └── analytics.py # 18 modelos: 9 dimensiones + 11 facts
├── api/
│ ├── main.py # FastAPI app, routers, static mount
│ ├── auth.py # API Key auth + rate limiting
│ ├── admin_auth.py # JWT auth + RBAC + impersonación
│ ├── admin_router.py # 20+ endpoints REST admin
│ └── odata_router.py # 22 entidades OData v4
├── etl/
│ └── pipeline.py # ETL completo (extract/transform/load)
├── extractors/
│ ├── revo_api_client.py # Cliente HTTP para Revo API
│ └── json_parser.py # Parseo de JSON a DataFrames
├── transformers/
│ ├── dimensions.py # Transformar dimensiones
│ ├── fact_ventas_linea.py # Transformar líneas de venta
│ ├── fact_factura.py # Transformar facturas
│ └── discount_calculator.py # Cálculo de descuentos
├── loaders/
│ └── db_loader.py # Bulk upsert a PostgreSQL
├── scripts/
│ ├── setup_db.py # Crear todas las tablas
│ ├── create_first_tenant.py # Seed primer tenant
│ ├── create_superadmin.py # Crear usuario superadmin
│ ├── generate_dim_tiempo.py # Llenar calendario
│ ├── run_etl.py # Ejecutar ETL manual
│ └── etl_scheduler.py # Scheduler nocturno (cron)
├── static/
│ ├── index.html # Dashboard BI
│ ├── admin.html # Panel Admin SPA
│ ├── manual-superadmin.html
│ ├── manual-distribuidor.html
│ ├── manual-usuario.html
│ └── js/
│ ├── dashboard.js # Lógica del dashboard (2500+ líneas)
│ └── api.js # Cliente API (28+ métodos)

Requisitos del sistema

Instalación paso a paso

1. Clonar el repositorio

git clone <repo-url> revo-analytics
cd revo-analytics

2. Crear entorno virtual

# Windows
python -m venv .venv
.venv\Scripts\activate

# Linux / macOS
python3 -m venv .venv
source .venv/bin/activate

3. Instalar dependencias

pip install -r requirements.txt

4. Crear archivo .env

copy .env.example .env     # Windows
# cp .env.example .env     # Linux

Edita el archivo con tus valores (ver sección siguiente).

5. Crear base de datos e inicializar tablas

# Crear la BD en PostgreSQL
psql -U postgres -c "CREATE DATABASE revo_analytics;"

# Crear todas las tablas
python scripts/setup_db.py

# Generar dimensión de tiempo (calendario)
python scripts/generate_dim_tiempo.py

6. Crear superadmin

python scripts/create_superadmin.py --email admin@tudominio.com --password tu_password --nombre "Admin"

7. Iniciar servidor

uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

Configuración .env

El archivo .env configura toda la plataforma. Variables agrupadas por prefijo:

PostgreSQL (destino analítico)

Variable Defecto Descripción
PG_HOST localhost Host del servidor PostgreSQL
PG_PORT 5432 Puerto
PG_DB revo_analytics Nombre de la base de datos
PG_USER analytics Usuario
PG_PASS (vacío) Contraseña

MySQL (lectura legacy, opcional)

Variable Defecto Descripción
MYSQL_HOST localhost Host MySQL
MYSQL_PORT 3306 Puerto
MYSQL_DB facturas_db BD de lectura
MYSQL_USER readonly_user Usuario (solo lectura)
MYSQL_PASS (vacío) Contraseña

Revo OAuth

Variable Defecto Descripción
REVO_CLIENT_ID (vacío) Client ID de la aplicación OAuth
REVO_AUTH_URL https://api.integrations.revoxef.works/oauth/authorize URL de autorización
REVO_TOKEN_URL https://api.integrations.revoxef.works/oauth/token URL de token
REVO_API_BASE_URL https://api.integrations.revoxef.works URL base de la API
REVO_SCOPES classic.reports classic.catalog.view classic.payments.methods Scopes OAuth
REVO_ACCESS_TOKEN (vacío) Token de acceso inicial (dev)
REVO_REFRESH_TOKEN (vacío) Token de refresco inicial (dev)
REVO_TOKEN_EXPIRES_AT (vacío) Expiración ISO del token
REVO_TENANT (vacío) Tenant Revo por defecto

Servidor OData

Variable Defecto Descripción
ODATA_HOST 0.0.0.0 Host de escucha
ODATA_PORT 8000 Puerto del servidor
ODATA_PAGE_SIZE 5000 Máximo de registros por petición OData

ETL

Variable Defecto Descripción
ETL_HOUR 3 Hora por defecto para ETL (legacy, ahora por tenant)
ETL_BATCH_DAYS 1 Días hacia atrás en modo incremental
ETL_FULL_SYNC_DAYS 90 Días hacia atrás en modo full
ETL_RATE_LIMIT_MS 200 Pausa entre peticiones a la API de Revo (ms)

Admin Panel

Variable Defecto Descripción
ADMIN_SECRET change_me_in_production Clave secreta para firmar JWT. ¡Cambiar en producción!
🔴 ADMIN_SECRET Esta variable firma todos los tokens JWT del panel admin. Si la cambias, todas las sesiones abiertas se invalidan. Usa un string aleatorio de al menos 32 caracteres en producción.

Ejemplo de .env mínimo

# PostgreSQL
PG_HOST=localhost
PG_PORT=5432
PG_DB=revo_analytics
PG_USER=postgres
PG_PASS=postgres

# Admin
ADMIN_SECRET=mi_clave_secreta_muy_larga_32_chars

# Revo OAuth (si ya tienes tokens)
REVO_CLIENT_ID=tu_client_id
REVO_ACCESS_TOKEN=ey...
REVO_REFRESH_TOKEN=def...

Crear base de datos

# Conectar a PostgreSQL como superusuario
psql -U postgres

-- Crear BD y usuario (ajustar nombre/password)
CREATE DATABASE revo_analytics;
CREATE USER analytics WITH PASSWORD 'tu_password';
GRANT ALL PRIVILEGES ON DATABASE revo_analytics TO analytics;
\q

# Crear tablas SQLAlchemy
python scripts/setup_db.py

# Generar dimensión de tiempo (2020-2030)
python scripts/generate_dim_tiempo.py
ℹ️ setup_db.py Ejecuta Base.metadata.create_all(engine). Es idempotente: si las tablas ya existen, no las modifica. Para cambios de esquema, editar el modelo y re-ejecutar (o usar Alembic).

Crear superadmin

python scripts/create_superadmin.py \
  --email admin@tudominio.com \
  --password una_password_segura \
  --nombre "Administrador"

El script crea un usuario con rol superadmin. La contraseña se hashea con bcrypt. Se puede ejecutar varias veces con diferentes emails.

Primer tenant

python scripts/create_first_tenant.py

O créalo desde el Panel Admin (/panel) una vez iniciada sesión como superadmin: Pestaña Tenants → + Nuevo Tenant.

Después de crear el tenant, configura sus tokens OAuth (editando desde el panel o directamente en la BD) y ejecuta el primer ETL:

python scripts/run_etl.py --tenant-id 1 --mode full --days 90

Iniciar servidor

Desarrollo (con auto-reload)

uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

Producción

# Con múltiples workers
uvicorn api.main:app --host 0.0.0.0 --port 8000 --workers 4

# O con gunicorn (Linux)
gunicorn api.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000

URLs principales

URL Función
/ Dashboard BI (index.html)
/panel Panel Admin (admin.html)
/health Health check (JSON)
/odata/v1/ Service document OData
/odata/v1/$metadata Metadata CSDL
/odata/v1/{Entity} Entidades OData (22)
/manual-superadmin Manual SuperAdmin
/manual-distribuidor Manual Distribuidor
/manual-usuario Manual Usuario
/manual-tecnico Este manual

ETL manual

Comando

python scripts/run_etl.py [opciones]

Opciones

Flag Defecto Descripción
--tenant-id 1 ID del tenant a procesar
--mode incremental incremental, full o catalog
--days 1 Días hacia atrás (incremental/full)

Modos

Modo Qué hace Duración típica
incremental Últimos N días. Ideal para ejecución nocturna. 30s – 2min
full Todo el histórico (N días). Corrige inconsistencias. 5 – 30min
catalog Solo catálogos (productos, categorías, camareros, mesas). 5 – 15s

Flujo ETL

1. Leer config del tenant (tokens, empresa_id)
2. Obtener token válido (refresh si expirado)
3. Extraer datos de API Revo XEF:
   - Órdenes con líneas de detalle
   - Pagos y desglose
   - Catálogos (productos, categorías, grupos, camareros, mesas, métodos de pago)
4. Parsear JSON → DataFrames
5. Transformar:
   - DimTiempo (merge)
   - DimProducto, DimCategoria, DimGrupo
   - DimCamarero, DimMesa, DimMetodoPago
   - FactFactura, FactVentasLinea
   - Descuentos
6. Cargar a PostgreSQL (bulk upsert)
7. Post-procesamiento (14 pasos):
   - Clasificación ABC (Pareto)
   - Menu Engineering (BCG)
   - Heatmap de ocupación
   - KPIs resumen
   - Forecast (predicción)
   - Alertas (anomalías)
   - Combinaciones (cross-selling)
   - Clientes RFM
   - Cohortes retención
   - Rentabilidad
   - Anulaciones
   - Velocidad de servicio
   - Desglose pagos/impuestos
8. Registrar resultado en EtlLog

Scheduler automático

El script scripts/etl_scheduler.py ejecuta automáticamente el ETL para cada tenant según su hora programada.

Cómo funciona

  1. Lee todos los tenants con activo=True y etl_habilitado=True.
  2. Filtra los que tienen etl_hora_ejecucion ≤ hora_actual.
  3. Excluye los que ya se ejecutaron hoy (compara ultimo_etl con la fecha actual).
  4. Ejecuta ETL incremental para cada tenant pendiente.
  5. Actualiza ultimo_etl en caso de éxito.

Opciones del scheduler

Flag Descripción
--dry-run Solo muestra qué se ejecutaría, sin ejecutar.
--hour HH:MM Simular una hora diferente (para testing).
--mode Forzar modo (incremental/full/catalog).
--days Forzar número de días.

Configurar en Windows (Task Scheduler)

1

Abre Programador de tareas de Windows (taskschd.msc).

2

Crear tarea básica → Nombre: "Revo ETL Scheduler".

3

Desencadenador: Diariamente, repetir cada 1 hora durante 24 horas.

4

Acción: Iniciar un programa.

  • Programa: C:\xampp\htdocs\python\.venv\Scripts\python.exe
  • Argumentos: scripts/etl_scheduler.py
  • Iniciar en: C:\xampp\htdocs\python
5

En la pestaña General: "Ejecutar tanto si el usuario inició sesión como si no".

Configurar en Linux (cron)

# Editar crontab
crontab -e

# Ejecutar cada hora
0 * * * * cd /opt/revo-analytics && /opt/revo-analytics/.venv/bin/python scripts/etl_scheduler.py >> /var/log/revo-etl.log 2>&1
ℹ️ El scheduler es seguro de ejecutar cada hora Solo procesa tenants cuya hora haya pasado y que no se hayan ejecutado hoy. Si lo ejecutas 24 veces al día, cada tenant solo se procesa una vez (a su hora programada).

Exponer con ngrok y OData remoto

Para hacer accesible tu servidor local (incluyendo la API OData) desde internet, sigue estos pasos exactos:

1. Activar entorno y arrancar servidor

# 1. Abrir terminal en la carpeta del proyecto
cd C:\xampp\htdocs\python

# 2. Activar entorno virtual
.venv\Scripts\activate

# 3. Arrancar el servidor web (puerto 8000)
uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

2. Arrancar ngrok con tu dominio estático

En otra terminal, ejecuta el siguiente comando para vincular tu dominio reservado al puerto 8000 local:

ngrok http --domain=sylvie-snobbish-shantae.ngrok-free.dev 8000

3. Verificar conexión

✅ OData vía ngrok Sí, es totalmente posible usar OData a través de ngrok. Para conectar Power BI:
  1. Usa la URL: https://sylvie-snobbish-shantae.ngrok-free.dev/odata/v1/
  2. Si te pide autenticación, prueba con Anonymous (si la API es pública) o Basic Auth si has configurado usuarios y contraseñas en tu servidor.
  3. Importante: Ngrok debe estar ejecutándose siempre que quieras actualizar los datos en Power BI.

Logs y diagnóstico

Log ETL en base de datos

Cada ejecución se registra en la tabla etl_log:

SELECT id, tenant_id, modo, status, duracion_seg, ordenes,
       error_mensaje, created_at
FROM etl_log
ORDER BY created_at DESC
LIMIT 20;

Log del servidor

Uvicorn escribe a stdout. Para persistir:

uvicorn api.main:app --port 8000 2>&1 | tee server.log     # Linux
uvicorn api.main:app --port 8000 *>&1 | Tee-Object server.log  # PowerShell

Consultas de diagnóstico útiles

-- Tenants sin tokens
SELECT id, nombre FROM tenants WHERE revo_access_token IS NULL OR revo_access_token = '';

-- Tenants con ETL fallido hoy
SELECT t.nombre, e.status, e.error_mensaje
FROM etl_log e JOIN tenants t ON t.id = e.tenant_id
WHERE e.created_at::date = CURRENT_DATE AND e.status = 'error';

-- API Keys más usadas hoy
SELECT a.nombre, a.requests_hoy, t.nombre as tenant
FROM api_keys a JOIN tenants t ON t.id = a.tenant_id
ORDER BY a.requests_hoy DESC;

-- Volumen de datos por tenant
SELECT empresa_id, COUNT(*) as lineas
FROM fact_ventas_linea GROUP BY empresa_id;

Gestión de tokens OAuth

Flujo de tokens

  1. Se pegan manualmente los tokens en el tenant (Access + Refresh).
  2. El ETL usa el Access Token para llamar a la API de Revo.
  3. Si el Access Token ha expirado, el sistema usa el Refresh Token para obtener uno nuevo.
  4. Los nuevos tokens (access + refresh + expires_at) se guardan automáticamente en la BD.
  5. Si el Refresh Token también ha expirado, el ETL falla y hay que obtener nuevos tokens.

Obtener tokens manualmente

Mientras no haya flujo OAuth automatizado, los tokens se obtienen del proceso de autorización de Revo y se pegan en el Panel Admin (editar tenant → campos de tokens).

Actualizar tokens por SQL (emergencia)

UPDATE tenants SET
  revo_access_token = 'nuevo_access_token_aqui',
  revo_refresh_token = 'nuevo_refresh_token_aqui',
  revo_token_expires_at = '2026-03-15T12:00:00'
WHERE id = 1;

Backups

Backup completo de la BD

# Dump completo
pg_dump -U postgres revo_analytics > backup_$(date +%Y%m%d).sql

# Solo tablas de sistema (config, sin datos analíticos)
pg_dump -U postgres revo_analytics \
  -t distribuidores -t tenants -t users -t api_keys -t informes \
  > backup_system_$(date +%Y%m%d).sql

Restaurar

psql -U postgres revo_analytics < backup_20260210.sql

Script de backup automático (cron)

# Backup diario a las 6 AM, mantener 30 días
0 6 * * * pg_dump -U postgres revo_analytics | gzip > /backups/revo_$(date +\%Y\%m\%d).sql.gz
0 7 * * * find /backups -name "revo_*.sql.gz" -mtime +30 -delete

Actualizar código

# Parar servidor
# (Ctrl+C o matar el proceso)

# Obtener cambios
git pull origin main

# Actualizar dependencias (si cambiaron)
pip install -r requirements.txt

# Aplicar cambios de esquema (si hay)
python scripts/setup_db.py

# Reiniciar servidor
uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

Llamadas a la API de Revo

Este apartado documenta todas las peticiones HTTP que el sistema realiza a la API de Revo XEF durante un ciclo ETL. Úsalo para auditar, optimizar y diagnosticar el consumo de la API.

Resumen de llamadas por ejecución ETL

# Fase Endpoint Peticiones Modo
1 Catálogo /classic/catalog/items 1+N páginas todos
2 Catálogo /classic/catalog/categories 1+N páginas todos
3 Catálogo /classic/catalog/groups 1+N páginas todos
4 Catálogo /classic/payment-methods 1+N páginas todos
5 Enriquecimiento /catalog/v1/items 1+N páginas todos
6 Enriquecimiento /catalog/v1/categories 1+N páginas todos
7 Enriquecimiento /catalog/v1/groups 1+N páginas todos
8 Enriquecimiento /catalog/v1/general-groups 1+N páginas todos
9 Órdenes /classic/reports/v3/orders chunks × N páginas incremental/full
Auth /oauth/token 0 o 1 si token expirado
⚠️ Nota sobre volumen Un ETL incremental (1 día) de un restaurante mediano genera entre 15–40 peticiones HTTP. Un ETL full (90 días) puede superar las 200+ peticiones por la paginación de órdenes.

Fase 1 — Classic Catalog API

Se ejecuta siempre (en los 3 modos: incremental, full, catalog). Obtiene los catálogos básicos del restaurante.

1. Productos

Detalle Valor
Endpoint GET /classic/catalog/items
URL completa https://api.integrations.revoxef.works/classic/catalog/items?page=N
Parámetros page (paginación automática)
Scope requerido classic.catalog.view
Razón Obtener el catálogo completo de productos: id, nombre, precio, categoría, grupo. Se usa para crear/actualizar dim_producto y para resolver nombres de productos en líneas de venta.
Datos extraídos id, name, price, category_id, group_id, active
Peticiones típicas 1–5 páginas (según tamaño de la carta)
Paginación Automática vía next_page_url + last_page
Rate limit 200ms entre páginas (RATE_LIMIT_DELAY)

2. Categorías

Detalle Valor
Endpoint GET /classic/catalog/categories
URL completa https://api.integrations.revoxef.works/classic/catalog/categories?page=N
Scope requerido classic.catalog.view
Razón Obtener categorías de productos para construir dim_categoria. Se usa para agrupar ventas por categoría en el dashboard (Bebidas, Entrantes, Postres, etc.).
Datos extraídos id, name, group_id, active
Peticiones típicas 1–2 páginas

3. Grupos

Detalle Valor
Endpoint GET /classic/catalog/groups
URL completa https://api.integrations.revoxef.works/classic/catalog/groups?page=N
Scope requerido classic.catalog.view
Razón Obtener la jerarquía de grupos que agrupan categorías. Para construir dim_grupo y poder filtrar/analizar ventas a nivel macro (COMIDA vs BEBIDA, etc.).
Datos extraídos id, name, active
Peticiones típicas 1 página (suelen ser pocos)

4. Métodos de pago

Detalle Valor
Endpoint GET /classic/payment-methods
URL completa https://api.integrations.revoxef.works/classic/payment-methods?page=N
Scope requerido classic.payments.methods
Razón Obtener los métodos de pago configurados (Efectivo, Tarjeta, Bizum, etc.) para construir dim_metodo_pago y resolver nombres en los pagos de las facturas.
Datos extraídos id, name, active
Peticiones típicas 1 página

Fase 2 — Catalog API v1 (enriquecimiento)

Se ejecuta siempre, después de la fase Classic. Obtiene datos adicionales de cada entidad que el Classic API no proporciona. Si falla, se omite el enriquecimiento sin cancelar el ETL.

ℹ️ ¿Por qué dos APIs de catálogo? El Classic Catalog API (fase 1) devuelve los datos básicos pero no incluye precio de coste, código de barras, impuestos ni la jerarquía de general_groups (super-grupos). La Catalog API v1 complementa esos campos. Ambas son necesarias porque la v1 no incluye category_id directamente.

5. Items (Catalog v1)

Detalle Valor
Endpoint GET /catalog/v1/items
URL completa https://api.integrations.revoxef.works/catalog/v1/items?complete=true&with=tax&limit=50&page=N
Parámetros complete=true, with=tax, limit=50, page=N
Scope requerido catalog.view (scope separado del classic)
Razón Obtener campos que el Classic API no devuelve: cost_price (para cálculos de rentabilidad), barcode (trazabilidad), weighted (si se vende a peso), tax (% de impuesto para desglose fiscal).
Datos extraídos id, cost_price, barcode, weighted, external_reference, tax{id, name, percentage}
Peticiones típicas 2–8 páginas (límite 50/página)
Paginación Vía links.next + meta.current_page

6. Categories (Catalog v1)

Detalle Valor
Endpoint GET /catalog/v1/categories
URL completa https://api.integrations.revoxef.works/catalog/v1/categories?with=group,general_group,tax&limit=50&page=N
Parámetros with=group,general_group,tax
Razón Obtener la relación de cada categoría con su general_group (super-grupo). Permite construir una jerarquía de 4 niveles: Super-grupo → Grupo → Categoría → Producto.
Datos extraídos id, name, group{id,name}, general_group{id,name}, tax{percentage}
Peticiones típicas 1–2 páginas

7. Groups (Catalog v1)

Detalle Valor
Endpoint GET /catalog/v1/groups
URL completa https://api.integrations.revoxef.works/catalog/v1/groups?with=general_group&limit=50&page=N
Parámetros with=general_group
Razón Enriquecer cada grupo con su super_group (general_group). Necesario para la dimensión de grupo con ID y nombre del super-grupo.
Datos extraídos id, name, general_group{id,name}
Peticiones típicas 1 página

8. General Groups (Catalog v1)

Detalle Valor
Endpoint GET /catalog/v1/general-groups
URL completa https://api.integrations.revoxef.works/catalog/v1/general-groups?limit=50&page=N
Razón Obtener la tabla maestra de super-grupos. Se usa como fallback para resolver nombres de super-grupos cuando la relación general_group viene como ID en vez de objeto expandido.
Datos extraídos id, name
Peticiones típicas 1 página (suelen ser 3–6 super-grupos)

Fase 3 — Orders API (datos transaccionales)

Se ejecuta solo en modos incremental y full (NO en catalog). Es la llamada más costosa en volumen.

9. Órdenes

Detalle Valor
Endpoint GET /classic/reports/v3/orders
URL completa https://api.integrations.revoxef.works/classic/reports/v3/orders?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&withInvoices=1&withContents=1&withCustomer=1&withPayments=1&withAppliedTaxes=1&page=N
Parámetros start_date — Inicio del rango
end_date — Fin del rango (máx 30 días por chunk)
withInvoices=1 — Incluir datos de facturas
withContents=1 — Incluir líneas de detalle de cada orden
withCustomer=1 — Incluir datos del cliente (si existe)
withPayments=1 — Incluir desglose de pagos
withAppliedTaxes=1 — Incluir impuestos aplicados
Scope requerido classic.reports
Razón Es la petición principal. Obtiene todas las órdenes/tickets del restaurante en el rango de fechas, con toda la información anidada: líneas de productos vendidos, facturas asociadas, pagos recibidos, impuestos y clientes. De aquí se generan las tablas de hechos: fact_ventas_linea, fact_factura, fact_desglose_pago, fact_desglose_impuesto.
Datos extraídos Orden: id, opened, closed, table_id, waiter.id/name, guests, discount
Contents: item_id, item_name, quantity, price, discount, modifiers
Invoices: id, sum, base, tax_amount
Payments: payment_method_id, amount
AppliedTaxes: name, percentage, base, tax_amount
Customer: id, name, email
Chunking Máximo 30 días por petición (MAX_API_RANGE_DAYS = 30). Si el rango es mayor, se divide en chunks automáticos.
Peticiones típicas Incremental (1 día): 1 chunk × 1–3 páginas = ~3 peticiones
Full (90 días): 3 chunks × 5–20 páginas = ~30–60 peticiones
• Restaurante muy activo: puede llegar a 100+ peticiones
🔴 Llamada más costosa El endpoint de órdenes con todos los with* devuelve respuestas grandes (1–5 MB por página). Un ETL full de 90 días puede descargar 50–200 MB de JSON. Esto es por diseño: es preferible hacer una sola llamada con toda la información anidada que hacer llamadas separadas para cada entidad.

Token Refresh

Detalle Valor
Endpoint POST /oauth/token
URL completa https://api.integrations.revoxef.works/oauth/token
Content-Type application/x-www-form-urlencoded
Body grant_type=refresh_token
refresh_token={token}
client_id={REVO_CLIENT_ID}
Razón Renovar el access_token cuando ha expirado o está a punto de expirar (margen de 5 minutos). No usa client_secret (flujo PKCE público).
Peticiones típicas 0 (si el token es válido) o 1 (al inicio del ETL). También puede ocurrir a mitad del ETL si el token expira durante la ejecución.
Respuesta access_token, refresh_token (nuevo), expires_in (segundos, default 30 días)
Callback Los tokens nuevos se persisten automáticamente en la tabla tenants vía on_token_refresh.

Además, si cualquier petición recibe un HTTP 401, se intenta un refresh automático y se reintenta la petición original.

Análisis de optimización

Flujo de llamadas por modo

┌──────────────────────────────────────────────────────────────────────┐
│  MODO CATALOG (solo catálogos)                                      │
│  Llamadas: 8 endpoints × 1–5 páginas = ~12–20 peticiones            │
│                                                                      │
│  1. /classic/catalog/items           → dim_producto                  │
│  2. /classic/catalog/categories      → dim_categoria                 │
│  3. /classic/catalog/groups          → dim_grupo                     │
│  4. /classic/payment-methods         → dim_metodo_pago               │
│  5. /catalog/v1/items                → enrich(cost, barcode, tax)    │
│  6. /catalog/v1/categories           → enrich(general_group)         │
│  7. /catalog/v1/groups               → enrich(general_group)         │
│  8. /catalog/v1/general-groups       → lookup tabla super-grupos     │
│  ────────────────────────────────────────────────────────────────     │
│  FIN                                                                 │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│  MODO INCREMENTAL (1 día, ejecución nocturna)                       │
│  Llamadas: ~12–25 peticiones                                         │
│                                                                      │
│  [Fases 1–2 igual que Catalog]                                       │
│  9. /classic/reports/v3/orders       → 1 chunk × 1–5 páginas        │
│  ────────────────────────────────────────────────────────────────     │
│  + Post-procesamiento (14 pasos, sin API calls, todo en BD)          │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│  MODO FULL (90 días)                                                 │
│  Llamadas: ~40–200+ peticiones                                       │
│                                                                      │
│  [Fases 1–2 igual que Catalog]                                       │
│  9. /classic/reports/v3/orders       → 3 chunks × 5–60 páginas      │
│  ────────────────────────────────────────────────────────────────     │
│  + Post-procesamiento (14 pasos, sin API calls, todo en BD)          │
└──────────────────────────────────────────────────────────────────────┘

Puntos de optimización identificados

# Punto Estado actual Optimización posible Impacto
1 Catálogo se re-descarga siempre Se llama a los 8 endpoints de catálogo en cada ejecución (incremental, full y catalog) Cachear catálogo en BD y solo re-descargar si la última descarga fue hace >24h, o si se solicita explícitamente con --mode catalog 🟡 Medio. Ahorra ~12 peticiones/ejecución. Pero el catálogo es pequeño y rápido.
2 Classic + Catalog v1 duplican Dos APIs distintas para productos: Classic (name, price, category_id) y v1 (cost_price, barcode, tax). Se llaman secuencialmente. Si la Catalog v1 proporcionara category_id, se podría eliminar la Classic. Actualmente NO es posible: la v1 no devuelve ese campo. 🔴 No viable. Ambas APIs son necesarias porque devuelven campos complementarios.
3 Paginación de la v1 en lote de 50 La Catalog API v1 usa limit=50 por defecto Probar con limit=100 o limit=200 si la API lo permite, reduciendo el número de páginas 🟡 Medio. Podría reducir a la mitad las peticiones de la fase 2.
4 Chunks de órdenes de 30 días El MAX_API_RANGE_DAYS = 30. Un full de 90 días hace 3 chunks. Si la API acepta rangos mayores, subir a 60 o 90. Probar empíricamente con rangos más largos. 🟢 Alto si funciona. Reduciría chunks a la mitad o tercio.
5 Parámetros with* de órdenes Siempre se pide withInvoices=1&withContents=1&withCustomer=1&withPayments=1&withAppliedTaxes=1 Evaluar si todos son necesarios. withCustomer solo se usa para RFM/segmentación de clientes; podría omitirse si no se necesita ese análisis. 🟡 Medio. Reduce tamaño de respuesta, pero probablemente no número de peticiones.
6 Rate limit 200ms Pausa de 200ms entre cada petición paginada Si Revo no impone rate limit estricto, podría bajarse a 100ms. Si sí hay rate limit, respetar el Retry-After (ya implementado). 🟢 Alto en full syncs. 100 páginas × 100ms menos = 10s ahorro.
7 No hay paralelismo Todas las peticiones son secuenciales (requests síncronas) Usar asyncio + httpx para hacer peticiones de catálogo en paralelo (los 4 endpoints Classic, los 4 v1) 🟢 Alto. Fase de catálogo pasaría de ~8–20s a ~3–5s.
8 General Groups como fallback Se descarga la tabla completa de general-groups solo como fallback para resolver nombres Eliminar esta llamada si /catalog/v1/groups?with=general_group siempre devuelve el objeto expandido 🟢 Bajo (1 petición, pocos datos). Pero simplifica el código.

Métricas de controladores de eficiencia

Config Variable Valor actual Efecto
Rate limit entre páginas RATE_LIMIT_DELAY 0.2s Pausa entre cada petición paginada
Timeout por petición REQUEST_TIMEOUT 120s Timeout HTTP por request
Reintentos máximos MAX_RETRIES 3 Reintentos ante 429/500/502/503/504 o timeout
Factor backoff RETRY_BACKOFF 2 Exponencial: 1s, 2s, 4s
Max días por chunk MAX_API_RANGE_DAYS 30 Divide rangos largos en trozos de 30 días
Límite Catalog v1 limit (hardcoded) 50 Registros por página en la Catalog API v1
Rate limit env ETL_RATE_LIMIT_MS 200 Variable .env (no se usa actualmente, pendiente de conectar)
ℹ️ ETL_RATE_LIMIT_MS vs RATE_LIMIT_DELAY Actualmente ETL_RATE_LIMIT_MS (del .env) no está conectado al RATE_LIMIT_DELAY del cliente API (hardcoded a 0.2). Para unificarlos, hay que pasar el valor desde settings al constructor de RevoApiClient.

Estimación de peticiones por tenant y modo

Modo Restaurante pequeño Restaurante mediano Restaurante grande
catalog ~12 ~15 ~20
incremental (1 día) ~14 ~20 ~30
full (30 días) ~20 ~35 ~70
full (90 días) ~30 ~60 ~200+

Modelos de datos

Modelos de sistema (system.py)

Modelo Tabla Campos clave
Distribuidor distribuidores id, nombre, slug, email, telefono, activo, max_tenants
Tenant tenants id, nombre, slug, empresa_id, distribuidor_id, activo, etl_habilitado, etl_hora_ejecucion, revo_access_token, revo_refresh_token, revo_token_expires_at, ultimo_etl
User users id, nombre, email, password_hash, rol (superadmin/distribuidor/tenant_admin), distribuidor_id, tenant_id, activo
ApiKey api_keys id, clave_hash (SHA-256), clave_prefijo (8 chars), nombre, tenant_id, activo, requests_hoy, max_requests_dia, ultimo_acceso
EtlLog etl_log id, tenant_id, modo, status, duracion_seg, ordenes, facturas, lineas, error_mensaje
Informe informes id, empresa_id, tipo, fecha_desde, fecha_hasta, contenido_json, uuid

Modelos analíticos (analytics.py)

Dimensiones (9): DimTiempo, DimEmpresa, DimProducto, DimCategoria, DimGrupo, DimMetodoPago, DimCamarero, DimMesa, DimCliente

Hechos (11): FactVentasLinea, FactFactura, FactHeatmap, FactKpiResumen, FactForecast, FactAlerta, FactCombinacion, FactRentabilidad, FactAnulacion, FactMenuEngineering, FactVelocidadServicio, FactCohorteRetencion, FactDesglosePago, FactDesgloseImpuesto

Todos los facts llevan empresa_id para aislamiento multi-tenant.

API Admin — Endpoints

Autenticación

Método Ruta Descripción
POST /admin/login Login → JWT (24h). Body: {email, password}
GET /admin/me Info del usuario autenticado

Dashboard

Método Ruta Descripción
GET /admin/stats KPIs del dashboard (tenants, distribuidores, usuarios, informes)

CRUD Distribuidores

Método Ruta Rol mínimo
GET /admin/distribuidores superadmin
POST /admin/distribuidores superadmin
PUT /admin/distribuidores/{id} superadmin
DELETE /admin/distribuidores/{id} superadmin

CRUD Tenants

Método Ruta Rol mínimo
GET /admin/tenants distribuidor
POST /admin/tenants distribuidor
PUT /admin/tenants/{id} distribuidor
DELETE /admin/tenants/{id} superadmin
POST /admin/tenants/{id}/etl distribuidor

CRUD Usuarios

Método Ruta Rol mínimo
GET /admin/users distribuidor
POST /admin/users distribuidor
PUT /admin/users/{id} distribuidor
DELETE /admin/users/{id} distribuidor

CRUD API Keys

Método Ruta Rol mínimo
GET /admin/api-keys distribuidor
POST /admin/api-keys distribuidor
DELETE /admin/api-keys/{id} distribuidor

Otros

Método Ruta Descripción
GET /admin/etl-log Log ETL (filtrado por rol)
POST /admin/impersonate/distribuidor/{id} Impersonar distribuidor
POST /admin/impersonate/tenant/{id} Impersonar tenant

Headers de impersonación

Header Valor Efecto
X-Impersonate-Distribuidor ID numérico Filtra todo por ese distribuidor
X-Impersonate-Tenant ID numérico Filtra todo por ese tenant

OData — Referencia

URL base

GET /odata/v1/{EntitySet}?$filter=...&$select=...&$orderby=...&$top=N&$skip=N&$count=true

Autenticación

22 Entidades

VentasLinea, Facturas, Productos, Categorias, Grupos,
MetodosPago, Camareros, Mesas, Calendario, Clientes,
KpisResumen, Heatmap, Alertas, Forecast, Combinaciones,
DesglosePagos, DesgloseImpuestos, Rentabilidad,
Anulaciones, MenuEngineering, VelocidadServicio, Cohortes

Ejemplo

GET /odata/v1/VentasLinea?$filter=fecha ge 2026-01-01&$select=producto_nombre,cantidad,total&$orderby=total desc&$top=100

Seguridad — Referencia

Mecanismo Implementación Archivo
Contraseñas admin bcrypt (hash irreversible, salt automático) admin_auth.py
Sesiones admin JWT HS256, 24h, firmado con ADMIN_SECRET admin_auth.py
API Keys SHA-256 hash. Se genera un token ra_ + 32 bytes random. admin_router.py
Rate limiting máx requests/día por API Key, reset a medianoche auth.py
Multi-tenant empresa_id en todas las queries, never exposed odata_router.py
RBAC 3 roles: superadmin > distribuidor > tenant_admin admin_auth.py
Impersonación Headers X-Impersonate-*, validado por RBAC admin_auth.py

Troubleshooting

El servidor no arranca

Error Solución
Address already in use Otro proceso usa el puerto 8000. Mátalo: netstat -ano | findstr :8000taskkill /PID xxx /F
ModuleNotFoundError Activa el venv: .venv\Scripts\activate y reinstala: pip install -r requirements.txt
Error de conexión PostgreSQL Verifica que PostgreSQL está corriendo y los datos de .env son correctos

ETL falla

Error Causa Solución
401 Unauthorized Token expirado Pegar nuevos tokens en el tenant
Token refresh failed Refresh token también expirado Obtener nuevos tokens del flujo OAuth de Revo
ConnectionError API Revo no accesible Verificar conectividad. Reintentar más tarde.
empresa_id no found Empresa ID incorrecto Verificar el ID en Revo XEF y corregir en el tenant
Duplicate key Conflicto de datos Ejecutar ETL full para ese tenant: --mode full

Dashboard no muestra datos

Síntoma Solución
API Key inválida Verificar que la clave está activa y no excedió el límite diario
Sin datos en el rango Ampliar el filtro de fechas. Verificar que el ETL se ejecutó.
Error 500 Revisar logs del servidor. Probablemente error de BD.
Gráficos vacíos pero KPIs con datos Limpiar caché del navegador (Ctrl+Shift+R)

Panel admin no carga

Síntoma Solución
Login falla Verificar email y contraseña. Resetear con create_superadmin.py si necesario.
"Token expired" Sesión caducada (24h). Haz login de nuevo.
No ves distribuidores Solo superadmin los ve. Verifica tu rol.

Comandos de emergencia

# Resetear contraseña de superadmin
python scripts/create_superadmin.py --email admin@tu.com --password nueva_password

# Ver últimos errores ETL
psql -U postgres revo_analytics -c "SELECT tenant_id, error_mensaje FROM etl_log WHERE status='error' ORDER BY id DESC LIMIT 5;"

# Matar servidor y reiniciar
# Windows:
Get-Process -Name python | Stop-Process -Force
uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

# Recarga completa de un tenant
python scripts/run_etl.py --tenant-id 1 --mode full --days 90

# Regenerar calendario
python scripts/generate_dim_tiempo.py


Revo Analytics — Manual Técnico v1.0 — Febrero 2026