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:
- Dashboard web (13 páginas de analítica, informes compartibles).
- OData v4 (22 entidades, compatible con Power BI/Excel).
- Panel Admin (gestión de distribuidores, tenants, usuarios, API keys).
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
├── .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
- Python 3.12+
- PostgreSQL 16+ (con usuario y base de datos creados)
- Git (para clonar y actualizar)
- ngrok (opcional, para exponer a internet)
- Sistema operativo: Windows (probado), Linux o macOS
- RAM: Mínimo 2 GB. Recomendado 4 GB.
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! |
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
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
- Lee todos los tenants con
activo=Trueyetl_habilitado=True. - Filtra los que tienen
etl_hora_ejecucion ≤ hora_actual. - Excluye los que ya se ejecutaron hoy (compara
ultimo_etlcon la fecha actual). - Ejecuta ETL incremental para cada tenant pendiente.
- Actualiza
ultimo_etlen 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)
Abre Programador de tareas de Windows (taskschd.msc).
Crear tarea básica → Nombre: "Revo ETL Scheduler".
Desencadenador: Diariamente, repetir cada 1 hora durante 24 horas.
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
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
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
- Web: https://sylvie-snobbish-shantae.ngrok-free.dev/static/index.html
- OData (Power BI): https://sylvie-snobbish-shantae.ngrok-free.dev/odata/v1/
- Usa la URL:
https://sylvie-snobbish-shantae.ngrok-free.dev/odata/v1/ - 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.
- 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
- Se pegan manualmente los tokens en el tenant (Access + Refresh).
- El ETL usa el Access Token para llamar a la API de Revo.
- Si el Access Token ha expirado, el sistema usa el Refresh Token para obtener uno nuevo.
- Los nuevos tokens (access + refresh + expires_at) se guardan automáticamente en la BD.
- 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 |
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.
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 rangoend_date — Fin del rango (máx 30 días por chunk)withInvoices=1 — Incluir datos de facturaswithContents=1 — Incluir líneas de detalle de cada ordenwithCustomer=1 — Incluir datos del cliente (si existe)withPayments=1 — Incluir desglose de pagoswithAppliedTaxes=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 |
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_tokenrefresh_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 (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
- Header:
X-API-Key: ra_xxxx... - Basic Auth:
Authorization: Basic base64(usuario:api_key) - Query param:
?api_key=ra_xxxx...
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 :8000 →
taskkill /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