Auditoría Fudo → Toteat e Impresoras
Fecha del informe: 2026-05-05
Repositorio: pedidos_express_server (FastAPI, "Biergarten Klein")
Este documento contiene:
- Auditoría completa del uso de la API de Fudo en el código actual.
- Descripción del sistema de impresoras y su acoplamiento con Fudo.
- Notas operativas para migrar a la API de Toteat usando el nuevo módulo
toteat/toteat.py.
1. Auditoría del uso de la API de Fudo
1.1 Funciones definidas en fudo/fudo.py
| Función |
Tipo |
Propósito |
get_token() |
sync |
OAuth con FUDO_API_KEY / FUDO_API_SECRET, cachea token en Redis. |
get_modifiers() |
sync |
GET /v1alpha1/product-modifiers. Definida pero nunca llamada. |
get_categories() |
sync |
GET /v1alpha1/product-categories. |
get_category_dict() |
sync |
Diccionario {id: nombre} derivado de get_categories. |
get_category(id) |
sync |
GET de una categoría puntual. |
get_product(id) |
sync |
GET de un producto, devuelve models.items.Product. |
get_products(page) |
sync |
GET paginado de /v1alpha1/products. |
get_all_indexed_products() |
async |
Concurrencia con aiohttp (8 páginas en paralelo). Filtra active && enableQrMenu. |
get_all_products() |
async |
Wrapper que devuelve la lista. |
_get_page_bounds(page, token) |
sync |
Helper interno, retorna (first, last, data) de una página de mesas. |
get_table(number) |
sync |
Búsqueda exponencial + binaria sobre /v1alpha1/tables (Fudo no permite filtrar por número). |
get_table_items(number) |
sync |
Encadena get_table → get_active_sale → get_sale → ThreadPoolExecutor (max_workers=10) sobre los items. |
get_sale(id) |
sync |
GET /v1alpha1/sales/{id}. |
create_sale(table_id) |
sync |
POST /v1alpha1/sales con people=1, saleType=EAT-IN, waiter.id=76. |
create_item(product_id, qty, sale_id, comment) |
sync |
POST /v1alpha1/items, prefija comentario con "Pedido desde pedidos express". |
get_active_sale(table) |
sync |
Lee relationships.activeSales.data[0]. |
clear_token() |
sync |
Borra el token cacheado en Redis. |
1.2 Uso por archivo
routes/orders.py
| Línea |
Llamado |
Propósito |
| 180 |
fudo.get_token() |
Validación defensiva de conectividad antes de procesar el pedido. |
| 190 |
add_product_to_fudo(item.id, item.quantity, table) |
Loop por cada item del pedido (wrapper de services/fudo_service.py). |
| 221 |
fudo.get_active_sale(fudo.get_table(table)) |
Recupera el fudo_id para guardar la venta en la BD local. |
routes/products.py
| Línea |
Llamado |
Propósito |
| 250 |
get_products_by_table(table_number) |
Endpoint GET /products/table/{table_number}. |
routes/store.py
| Línea |
Llamado |
Propósito |
| 33 |
get_table(int(q)) |
Endpoint GET /store/tables/exists?q=N. |
services/fudo_service.py
| Línea |
Llamado |
Propósito |
| 8 |
fd.get_table(table_number) |
Resuelve mesa antes de añadir item. |
| 13 |
fd.get_active_sale(table) |
Si no existe, crea venta. |
| 15 |
fd.create_sale(table['id']) |
Crea venta nueva en Fudo. |
| 20 |
fd.create_item(product_id, quantity, sale_id, comment) |
Añade item. |
| 29 |
fd.get_table_items(table_number) |
Lectura de items de mesa. |
services/data_service.py
| Línea |
Llamado |
Propósito |
| 10 |
from fudo.fudo import get_all_products, get_category_dict |
Imports. |
| 563 |
await get_all_products() |
Carga del catálogo en ProductDataService.get_all (con cache local). |
| 569 |
get_category_dict() |
Resuelve nombre de categoría a partir de categoryId. |
load_products.py
| Línea |
Llamado |
Propósito |
| 1 |
from fudo.fudo import get_all_products, get_product |
Imports (script). |
update_prices.py
| Línea |
Llamado |
Propósito |
| 4 |
from fudo.fudo import get_product, get_products, get_category, get_all_products |
Imports (script). |
| 72 |
get_category(int(...)) |
Resuelve nombre de categoría para insertar productos nuevos. |
| 121 |
get_all_products() (sync, sin await — bug del script) |
Carga el catálogo para sincronizar precios. |
1.3 Resumen por función
| Función |
Frecuencia |
Archivos que la usan |
get_token |
1+ por request |
fudo.py, routes/orders.py:180 |
get_table |
3-5 por pedido |
services/fudo_service.py:8, routes/orders.py:221, routes/store.py:33 |
get_active_sale |
2-3 por pedido |
services/fudo_service.py:13, routes/orders.py:221 |
create_sale |
0-1 por mesa nueva |
services/fudo_service.py:15 |
create_item |
N por pedido |
services/fudo_service.py:20 |
get_table_items |
1 por endpoint |
services/fudo_service.py:29, routes/products.py:250 |
get_all_products (async) |
1 al iniciar / refrescar cache |
services/data_service.py:563, load_products.py, update_prices.py |
get_category_dict |
1 al iniciar |
services/data_service.py:569 |
get_category |
N (script) |
update_prices.py:72 |
get_products |
raro (script) |
update_prices.py |
get_product |
raro (script) |
load_products.py, update_prices.py |
get_modifiers |
nunca |
— |
1.4 Transformaciones notables
get_product (fudo.py:138-150): mapea el JSON:API de Fudo a models.items.Product, con conversión active: bool → status: 0/1.
get_all_indexed_products (fudo.py:217-219): filtra active && enableQrMenu antes de indexar.
create_item (fudo.py:452): siempre prefija el comentario con "Pedido desde pedidos express".
get_table_items (fudo.py:388-392): proyecta cada línea a {id: int, quantity: int} y descarta el resto.
update_prices.add_missing_products (update_prices.py:80): construye la URL de imagen como
https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/{id} (acoplamiento al bucket S3 de Fudo).
2. Sistema de impresoras
2.1 Arquitectura
El servicio services/print_service.py no habla con hardware local: es un
cliente HTTP a un servidor remoto que se encarga de la impresión física
(probablemente ESC/POS o CUPS detrás de él).
| Item |
Valor |
| Endpoint base |
http://10.10.12.3:8000 (hardcoded en print_service.py) |
| Auth |
Authorization: Bearer PRINTER123cerveza@ (hardcoded) |
| Timeouts |
10s pedidos/tickets · 1000s facturación (anómalo) |
| Librerías Python |
requests y subprocess (para ping). No usa python-escpos ni pycups. |
2.2 Endpoints del servidor de impresión
| Función Python |
HTTP |
Disparado por |
print_order(Order) |
POST /print |
POST /api/orders/send (routes/orders.py:281) |
print_ticket(table_id) |
GET /ticket/{table} |
GET /api/products/free-beer/{table_id} y routes/users.py:313 |
print_billing(OrderBilling) |
GET /billing/{table}/{payment} |
POST /api/orders/billing |
get_status() |
GET /status |
No invocada en routes (solo manual). |
2.3 Validación de conectividad
En routes/orders.py:126-142, antes de procesar el pedido:
printers = {"ServerPrincipal": "10.10.12.3"}
for name, ip in printers.items():
response = subprocess.run(
["/usr/bin/ping", "-c", "1", "-W", "10", ip],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
Si el ping falla:
- HTTP 424 al cliente con
ErrorResponse.PRINTER_DISCONNECTED.
- Email a
erwinjacimino2003@gmail.com y mompyn@gmail.com usando la
plantilla PRINTER_DISCONNECTED_MAIL (config/mails.py).
2.4 Acoplamiento con Fudo
kitchen_id enviado al servidor de impresión sale de Product.kitchen_id,
y este venía originalmente de fudo.relationships.kitchen.data.id
(fudo.py:146). El módulo toteat/toteat.py mantiene este campo
mapeándolo desde categoryId (Toteat no expone "kitchen" como Fudo).
- Orden de operaciones en
printer_order:
ping → POST a Fudo (loop de items) → guardar venta en BD local con
fudo_id (de Fudo) → POST /print.
- Riesgo: si Fudo cae después del ping, todo el pedido falla con 500.
Si la impresora se desconecta después de los
create_item exitosos en
Fudo, hay venta registrada en Fudo + BD pero sin comanda física.
2.5 Configuración
No hay variables de entorno para impresoras. Toda la configuración (IP, token,
timeouts) está hardcoded en services/print_service.py. Mover a
config/settings.py es trabajo futuro.
3. Plan de migración usando toteat/toteat.py
3.1 Variables de entorno nuevas
TOTEAT_API_TOKEN=...
TOTEAT_RESTAURANT_ID=...
TOTEAT_LOCAL_ID=...
TOTEAT_USER_ID=...
La URL base alterna entre apidev.toteat.com y api.toteat.com según la flag
DEVELOPMENT ya existente.
3.2 Equivalencias
El módulo toteat/toteat.py expone exactamente las mismas funciones
públicas que fudo/fudo.py, devolviendo datos en la forma JSON:API estilo
Fudo. Para activarlo:
- En
services/fudo_service.py: cambiar import fudo.fudo as fd por
import toteat.toteat as fd.
- En
services/data_service.py:10: cambiar
from fudo.fudo import get_all_products, get_category_dict por
from toteat.toteat import get_all_products, get_category_dict.
- En
routes/orders.py:13: cambiar from fudo import fudo por
from toteat import toteat as fudo.
- En
routes/store.py:8: cambiar from fudo.fudo import get_table por
from toteat.toteat import get_table.
- En
load_products.py y update_prices.py: idem.
fudo/ queda intacto durante la transición; cuando la migración esté
validada en producción se puede borrar.
3.3 Diferencias funcionales conocidas
get_modifiers: nunca se usa, solo está por compatibilidad. En Toteat
los modificadores vienen embebidos en /products.
get_products(page): Toteat no pagina. Retorna todo en page=1 y
lista vacía para page > 1 (compatible con loops while data).
create_sale + create_item: el flujo crear venta vacía + N items
de Fudo se preserva. En Toteat la primera llamada a create_item para una
venta hace POST /orders con orderId=0; las siguientes hacen
POST /orders con orderId=<real> para añadir líneas a la orden de mesa
existente. El orderId se cachea en Redis (toteat:table_order:{N},
TTL 8h) para que get_active_sale siga devolviendo el mismo ID después
del loop.
kitchen_id: por defecto se mapea a categoryId. Si en el futuro hace
falta enrutar items a impresoras distintas según otra dimensión, hay que
agregar un mapping configurable en _to_fudo_product.
- Rate limits Toteat:
/products: 3 req/min → cache local de 5 min.
/tables: 3 req/min → cache local de 1 min.
/orders: 1 req/seg → para pedidos largos puede agregar latencia. Si se
vuelve cuello de botella, se puede refactorizar routes/orders.py para
enviar todos los items en una sola llamada (el módulo ya soporta el
payload line=[...]).
3.4 Auditoría rápida post-migración
Antes de borrar fudo/:
- Probar
python -m toteat.toteat (suite de smoke tests embebida).
- Validar un pedido end-to-end en ambiente dev (
apidev.toteat.com).
- Revisar logs por
Toteat GET ... falló o status=4xx.
- Confirmar que
fudo_id en la tabla sales ahora guarda el orderId de
Toteat (es un entero largo, no string corto).