diff --git a/.opencode/agents/tryton-rpc-probe.md b/.opencode/agents/tryton-rpc-probe.md new file mode 100644 index 0000000..1db48b2 --- /dev/null +++ b/.opencode/agents/tryton-rpc-probe.md @@ -0,0 +1,23 @@ +--- +description: Ejecuta requests RPC de prueba contra Tryton usando sabatron-tryton-rpc-client, siguiendo el skill tryton-rpc-probe. Úsalo para probes aislados (model.product.*, model.product.category.*, fields_get, credenciales staging). +mode: subagent +permission: + bash: + "docker compose *": allow + "*": ask +--- + +Eres un agente especializado en ejecutar requests RPC de prueba (probes) +contra Tryton dentro de este proyecto. + +Antes de ejecutar cualquier probe, **carga el skill `tryton-rpc-probe`** y sigue +exactamente sus instrucciones: +- Usa el paquete `sabatron-tryton-rpc-client` (nunca curl). +- Lee las credenciales `TRYTON_*` desde `.env` y pásalas con `-e` al contenedor. +- Usa `docker compose -f docker-compose.dev.yml run --rm --no-deps ...` para no + levantar dependencias. +- Ante `HTTP 401` (credenciales), `403` (denegado) o `429` (rate-limit) usa la + técnica de revelar el estado HTTP real descripta en el skill y reporta el + código al usuario en lugar de asumir el motivo. +- Reporta los resultados crudos (campos, agrupaciones `field.`, ids) tal cual + los devuelve Tryton, y su implicación para la implementación. \ No newline at end of file diff --git a/.opencode/skills/tryton-rpc-probe/SKILL.md b/.opencode/skills/tryton-rpc-probe/SKILL.md new file mode 100644 index 0000000..3cd3661 --- /dev/null +++ b/.opencode/skills/tryton-rpc-probe/SKILL.md @@ -0,0 +1,154 @@ +--- +name: tryton-rpc-probe +description: Use cuando se necesite hacer requests RPC de prueba contra Tryton (probes) con el paquete sabatron-tryton-rpc-client. Dispara ante tryton, sabatron, probe rpc, model.product.search/read, model.product.category.read, fields_get, credenciales Tryton staging o verificaciones empíricas de respuestas RPC. NO usar curl: solo el paquete de python. +--- + +# Probes RPC a Tryton con sabatron-tryton-rpc-client + +Guía para ejecutar requests RPC de prueba (probes) contra Tryton dentro de este +proyecto, usando el paquete `sabatron-tryton-rpc-client` y el contenedor de +desarrollo. Sirve para verificar campos, modelos, credenciales y respuestas +antes de planear o implementar cambios de integración (TDD: confirmar empíricamente +antes de asumir). + +## Reglas obligatorias + +- **Nunca usar curl.** Solo el paquete python `sabatron-tryton-rpc-client` + (el RPC de Tryton enmascara/muestra estados HTTP distintos que confunden a curl). +- **No hardcodear credenciales**: leer los valores `TRYTON_HOST`, + `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD` de `.env` (raíz del + proyecto) y pasarlos al contenedor con `-e`. No imprimir la contraseña (a lo + sumo imprimir el nombre de la base de datos). +- **Respetar rate-limit**: ante `HTTP 429` esperar (p. ej. ~120s) antes de + reintentar; no hacer ráfagas de logins fallidos. +- Verificar primero el login/credenciales con una sonda mínima antes de asumir + que falla por campos o datos. + +## Comando base (plantilla) + +Todos los probes se ejecutan con `docker compose` dev sin levantar dependencias +(`--no-deps`): + +```bash +docker compose -f docker-compose.dev.yml run --rm --no-deps \ + -e TRYTON_HOST= \ + -e TRYTON_DATABASE= \ + -e TRYTON_USERNAME= \ + -e TRYTON_PASSWORD= \ + django python -c ' +import os +from sabatron_tryton_rpc_client.client import Client +c = Client(hostname=os.environ["TRYTON_HOST"], database=os.environ["TRYTON_DATABASE"], username=os.environ["TRYTON_USERNAME"], password=os.environ["TRYTON_PASSWORD"]) +c.connect() +print("CONNECTED OK", os.environ["TRYTON_DATABASE"]) +ctx = {"company": 1} +print(c.call("model.product.product.search", [[["salable", "=", True]], 0, 5, [["rec_name", "ASC"], ["id", None]], ctx])) +' +``` + +Patrón de la librería: + +```python +from sabatron_tryton_rpc_client.client import Client +c = Client(hostname=..., database=..., username=..., password=...) +c.connect() # login +resultado = c.call("modelo.metodo", (args..., contexto)) # contexto {"company": 1} +``` + +## Ver estados HTTP reales (401/403/429 enmascarados) + +La librería **enmascara** los errores HTTP del login como +`UnboundLocalError: cannot access local variable 'response'...` (bug en +`jsonrpc.py:__request`). Para ver el código real, monkeypatchear el transporte +XML-RPC y capturar `ProtocolError`: + +```python +import xmlrpc.client +from sabatron_tryton_rpc_client import jsonrpc +orig = xmlrpc.client.Transport.request +def wrapped(self, host, handler, body, verbose=False): + try: + return orig(self, host, handler, body, verbose=verbose) + except xmlrpc.client.ProtocolError as e: + print("HTTP_STATUS", e.errcode, repr(e.errmsg)) + raise +jsonrpc.Transport.request = wrapped +# luego Client(...).connect() / ServerProxy(...) +``` + +Guía rápida de códigos vistos en el proyecto: + +- `HTTP 200` + fault JSON (p. ej. `FATAL: database "..." does not exist`): la DB + no existe o el endpoint responde con error de aplicacion. +- `HTTP 401`: credenciales rechazadas (usuario/password) en esa base de datos. +- `HTTP 403`: ruta/método denegado (p. ej. `common.db.list`). +- `HTTP 429`: rate-limit, esperar antes de reintentar. + +## Convención de campos RPC de Tryton (agrupación con punto) + +Los campos con punto (ej. `template.categories.name`) NO llegan como una clave +plana: Tryton los agrupa bajo la clave del modelo padre con puntos. + +En `model.product.product.read`, con los fields +`["id", "name", "template.categories", "template.categories.name"]`, la +respuesta llega así: + +```json +{ + "id": 488, + "name": "Cloruro de magnesio", + "template.": { + "id": 1248, + "categories": [6], + "categories.": [{"id": 6, "name": "Alimentos Transformados Y Granos"}] + } +} +``` + +Combinaciones verificadas en staging (Tryton 8): + +| fields solicitados | claves resultantes | +|---|---| +| `template.categories` | `categories: [ids]` (sin `categories.`) | +| `template.categories.id` | `categories.: [{"id": ...}]` | +| `template.categories.name` | `categories.: [{"id", "name"}]` (id viene incluido) | +| ambos `.id` + `.name` | igual que solo `.name` | + +Conclusión operativa: pedir `template.categories.name` basta para traer ids y +nombres; `template.categories.id` es redundante. No hay `active` en +`product.category` (fields_get lo confirma) → activar/desactivar categorías es +solo local. + +## Ejemplos de sondas útiles + +Login + datos de productos con categorias anidadas: + +```python +ctx = {"company": 1} +pids = c.call("model.product.product.search", [[["salable", "=", True]], 0, 1000, [["rec_name", "ASC"], ["id", None]], ctx]) +detalles = c.call("model.product.product.read", (pids, ["id", "name", "default_uom.id", "default_uom.rec_name", "list_price", "template.categories", "template.categories.name"], ctx)) +``` + +Listar los campos reales de un modelo (evita asumir campos): + +```python +campos = c.call("model.product.category.fields_get", ([], ctx)) +print(sorted(campos)) +``` + +Listar bases de datos (suele estar bloqueado con 403): + +```python +from sabatron_tryton_rpc_client.jsonrpc import ServerProxy +p = ServerProxy(hostname, 8000, "") +print(p.common.db.list()) +``` + +## Integración con tests + +- Los tests de integración NO llaman Tryton real: mockean + `sabatron_tryton_rpc_client.client.Client.call` y `.connect` + (ver `don_confiao/tests/test_products_from_tryton.py`, + `test_categories_from_tryton.py`). +- Los probes de esta skill son solo para verificación empírica/planeación + contra staging, no para la suite de tests. \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 47146f3..cf68152 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -468,3 +468,21 @@ Antes de ir a producción, verificar: ## Integraciones - **Tryton ERP**: Integración mediante sabatron-tryton-rpc-client para sincronización de clientes, productos y ventas + +## Probes RPC a Tryton + +Cuando se necesiten requests RPC de prueba contra Tryton (verificación empírica +de campos/modelos/credenciales, planeación de integraciones, etc.) **cargar el +skill `tryton-rpc-probe`** (`.opencode/skills/tryton-rpc-probe/SKILL.md`) y +seguir sus instrucciones: + +- Usar `sabatron-tryton-rpc-client` en el contenedor dev (`run --rm --no-deps`), + nunca curl. +- Credenciales desde `.env` (TRYTON_HOST/DATABASE/USERNAME/PASSWORD), sin + hardcodear ni imprimir secretos. +- Los errores HTTP (401/403/429) llegan enmascarados como `UnboundLocalError`: + revelar el código real monkeypatcheando `xmlrpc.client.Transport.request`. +- Los campos con punto de Tryton se agrupan en `field.` (ej. `template.` → + `categories.`); pedir `template.categories.name` ya trae id+nombre. +- Para trabajo de integración/código se usan mocks de `Client.call` (no Tryton + real) en `don_confiao/tests/test_*_from_tryton.py`.