2a37a4ffbf
Equivalentes .command (doble-clic en Finder) de los .bat de Windows: - setup_mac.command: bootstrap con un click (detecta Python 3.10+, crea .venv, instala requirements + Chromium de Playwright, copia .env.example -> .env). - start/stop/restart/start_persistent_profile.command: espejo de los .bat, lanzan el server con nohup usando el python del .venv. - mp_common.sh: helper compartido (raíz, venv, banners). runtime_control.py ahora es cross-platform (IS_WINDOWS): lsof/ps/pgrep/kill en POSIX, netstat/PowerShell/taskkill en Windows. _kill_tree_posix mata el árbol padre+worker de uvicorn con SIGTERM. .venv/ añadido a .gitignore. Docs actualizadas (CLAUDE.md, AGENTS.md, PLAYWRIGHT_SESSION.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
12 KiB
Markdown
114 lines
12 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
> Este repo ya tiene un [AGENTS.md](AGENTS.md) con documentación operativa detallada (comandos, gotchas de GHL, paginación, reglas de negocio Monte Providencia). **Léelo siempre primero.** Este archivo cubre el panorama arquitectónico que no es obvio leyendo un único módulo.
|
|
|
|
## Comandos esenciales
|
|
|
|
- `python -m pip install -r requirements.txt` — instalar dependencias.
|
|
- `python main.py` — levanta FastAPI/Uvicorn en `http://127.0.0.1:8000` con reload.
|
|
- `start.bat` / `stop.bat` / `restart.bat` (Windows) — lanzan/matan/reinician la app y liberan el puerto 8000. `restart.bat` detecta automáticamente si estaba en modo normal o perfil persistente (lee `generated/runtime/last_mode`), mata Chromium zombies y limpia batch files huérfanos en `generated/runtime/batch/`.
|
|
- **macOS / Linux** — equivalentes doble-clickeables en Finder con extensión `.command`:
|
|
- `setup_mac.command` — inicialización con un click (NO tiene equivalente Windows): detecta Python 3.10+, crea el venv `.venv`, instala `requirements.txt` y el Chromium de Playwright, y copia `.env.example` a `.env`. Idempotente.
|
|
- `start.command` / `stop.command` / `restart.command` / `start_persistent_profile.command` — espejo de los `.bat`. Lanzan el server con `nohup` en segundo plano (logs en `generated/logs/server.out`) usando el python del `.venv`. `stop.command` acepta `--force` igual que el `.bat`.
|
|
- `mp_common.sh` — helper compartido (sourced, no se ejecuta solo): resuelve la raíz del proyecto, localiza el python del venv y define los banners/utilidades. La lógica segura de preflight/stop la delegan todos a `runtime_control.py`, que ahora es cross-platform (`lsof`/`ps`/`pgrep`/`kill` en POSIX, `netstat`/PowerShell/`taskkill` en Windows; ver `IS_WINDOWS`).
|
|
- `python -m py_compile main.py db.py ghl_client.py sync_engine.py script_runner.py paths.py` — único "lint" disponible (no hay test/lint/format configurado).
|
|
- Scripts de utilidad se corren directo: `python scripts\<nombre>.py [args]`. La mayoría leen `generated/data/mp_manager.sqlite` (vía `paths.DB_PATH`) y asumen una sync previa.
|
|
- Variables de entorno:
|
|
- `SYNC_ENGINE_MAX_WORKERS` (default 1000, sin tope) — paralelismo de "Sincronizar Todo". La concurrencia efectiva es `min(total_accounts, env_var)`, así que el default sincroniza todas las cuentas a la vez. El rate limit de GHL es por location, así que paralelizar más cuentas no provoca 429; bajar este valor solo tiene sentido para acotar uso de CPU/red local.
|
|
- `SCRIPT_RUNNER_MAX_WORKERS` (default 4, máx 20) — paralelismo del runner del dashboard. Subir esto aumenta riesgo de `429` porque los subprocesos no comparten rate limit.
|
|
|
|
## Arquitectura — el panorama
|
|
|
|
**Patrón general:** el repo es un panel de control FastAPI sobre SQLite que cachea datos de varias cuentas (locations) de Go High Level y orquesta scripts Python que pueden auditar o mutar datos directamente en GHL.
|
|
|
|
### Flujo de datos
|
|
|
|
```
|
|
CSV de tokens ──► sync_engine ──► ghl_client (API GHL) ──► db.py (SQLite)
|
|
│
|
|
┌────────────────────────┘
|
|
▼
|
|
main.py (FastAPI) ◄── templates/index.html + static/js
|
|
│
|
|
▼
|
|
script_runner ──► subprocess scripts/*.py ──► (audita en script_audit, muta en GHL)
|
|
```
|
|
|
|
### Módulos núcleo (raíz del repo)
|
|
|
|
| Módulo | Rol |
|
|
|---|---|
|
|
| `main.py` | Punto de entrada FastAPI. Define endpoints, middleware de request_id, manejadores de excepción que escriben a `error_log`. En startup carga `TOKENS_CACHE` desde el CSV — los tokens nunca tocan SQLite. |
|
|
| `paths.py` | Fuente única de verdad para rutas en disco. Todo lo generado por la app/scripts (DB, reports, exports, logs, screenshots, sesiones Playwright, snapshots de migración, estado de runtime) vive bajo `generated/`. Cualquier módulo nuevo debe importar sus paths desde aquí — nunca hardcodear. |
|
|
| `db.py` | Capa SQLite. Define el esquema (`accounts`, `contacts`, `pipelines`, `opportunities`, `workflows`, `sync_log`, `error_log`) y operaciones de lectura/escritura. Cada `save_*` por location se hace en una transacción que reemplaza los registros previos de esa location. |
|
|
| `ghl_client.py` | Cliente HTTP de GHL. Implementa rate limiting (110ms entre peticiones por token), reintentos con backoff exponencial para 5xx y lineal para 429, sesión TCP persistente por hilo. Es la fuente de verdad del comportamiento GHL. |
|
|
| `sync_engine.py` | Orquesta `sync_account` (pipelines → opportunities → contacts → guardado transaccional) y `sync_all_accounts` (ThreadPoolExecutor con `SYNC_ENGINE_MAX_WORKERS`). Lee el CSV en cada arranque y sincronización. |
|
|
| `script_runner.py` | Lanza scripts como subprocesos, encola sus logs para SSE, maneja ejecución paralela vs secuencial, asocia cada run a un `run_id` para auditoría. Sólo expone los scripts listados en `SCRIPTS_METADATA`. |
|
|
| `script_audit.py` | Sistema de auditoría/rollback paralelo: `script_runs`, `script_change_log`, `script_run_control`. Los scripts mutadores deben registrar cada cambio aquí (estado `planned` → `applied` → opcionalmente `rolled_back`) para que el dashboard pueda revertirlos por `run_id`. |
|
|
| `error_logging.py` | Centraliza logging técnico. Cada error retorna un `error_id` que se devuelve al cliente y se persiste en `error_log`. |
|
|
|
|
### Carpeta `generated/`
|
|
|
|
Todos los archivos que produce la app o los scripts viven bajo `generated/`. La raíz del repo solo contiene código fuente, docs, configs estáticos (CSVs de tokens y verificador) y `script_metadata_overrides.json` (config viva del dashboard).
|
|
|
|
```
|
|
generated/
|
|
├── data/ mp_manager.sqlite (+ -shm, -wal)
|
|
├── reports/ outputs de scripts de auditoría (audit_custom_fields/, duplicados/, drift/, coverage/)
|
|
├── exports/ descargas del dashboard (servido vía /api/exports/{filename})
|
|
├── logs/ errors.jsonl + script_runs/ para runs futuros
|
|
├── migrations/ snapshots pre-destructive de scripts mutadores
|
|
├── browser/ session.json (Playwright shared), profile/, screenshots/
|
|
├── runtime/ server_info.json, last_mode, batch/ (bulk batches en vuelo)
|
|
└── _archive/ basura legacy fechada
|
|
```
|
|
|
|
Está ignorada en `.gitignore` y `.megaignore`. Para añadir un nuevo destino, agregar la constante en `paths.py` y registrar el directorio en `ensure_dirs()`.
|
|
|
|
### Cuenta principal vs sucursales
|
|
|
|
El `location_id` `GbKkBpCmKu2QmloKFHy3` es la **cuenta de Marca principal** (Monte Providencia). Está hardcodeado en `sync_engine.parse_accounts_csv` y `scripts/common.py` (`BRAND_LOCATION_ID`). Todas las demás filas del CSV son sucursales. La sincronización de contactos es bidireccional Marca↔Sucursal; la de oportunidades es unidireccional Sucursal→Marca (la sucursal tiene prioridad). Ver AGENTS.md para las reglas completas.
|
|
|
|
### Custom fields dinámicos
|
|
|
|
Los IDs de custom fields varían por sucursal. **Nunca los hardcodees.** Resuélvelos por nombre via los endpoints `GET /objects/?locationId=…` y `GET /objects/{contact|opportunity}?locationId=…` (header `Version: 2021-04-15`). `scripts/common.py` expone `SchemaResolver` y `FIELD_ALIASES` para esto — úsalo en scripts nuevos en vez de reinventar.
|
|
|
|
### Convenciones de scripts
|
|
|
|
Los scripts viven en `scripts/` y se categorizan por nombre:
|
|
- **Read-only**: `audit_*`, `mp_*_search`, `analyze_*`, `find_*`, `check_*`, `health_check_*`, `*_readonly`, `daily_summary_*`.
|
|
- **Mutadores** (escriben en GHL): `fix_*`, `migrate_*`, `move_*`, `update_*`, `sync_contact_*`, `cleanup_*`, `reconcile_*`, `fill_*`. Estos deben soportar `--dry-run` y registrar cambios en `script_audit` con su `--run-id`.
|
|
- Todos los scripts comparten utilidades en `scripts/common.py` (carga de cuentas, resolver de schemas, normalización de nombres).
|
|
- Para que un script aparezca en el dashboard hay que agregarlo a `SCRIPTS_METADATA` en `script_runner.py` (título, descripción, categoría, opciones, `mutator: True` si aplica).
|
|
|
|
### Frontend
|
|
|
|
`templates/index.html` + `static/js/app.js` + `static/css/style.css` son el SPA. Consume los endpoints `/api/*` y streamea logs de scripts via SSE en `/api/scripts/stream/{task_id}`.
|
|
|
|
### Automatización con navegador
|
|
|
|
`scripts/ghl_browser_*.py` usan Playwright contra la UI web de GHL para cosas que la API no soporta (renombrar/eliminar workflows, p.ej.). Soportan dos modos de sesión: shared `storage_state` en `generated/browser/session.json` (default) o perfil Chrome persistente vía `GHL_BROWSER_PROFILE_DIR` (más estable, default `generated/browser/profile`, arrancar con `start_persistent_profile.bat`). El auto-login con 2FA por correo (IMAP) se configura en `.env` (ver `.env.example`).
|
|
|
|
Antes de tocar estos scripts:
|
|
- [docs/PLAYWRIGHT_SESSION.md](docs/PLAYWRIGHT_SESSION.md) — manejo de sesión, modos, síntomas comunes, cómo reportar errores útilmente.
|
|
- [docs/PLAYWRIGHT_PATTERNS.md](docs/PLAYWRIGHT_PATTERNS.md) — **patrones probados** (SPA detection por URL + DOM, OTP en inputs maxlength=1, selectores defensivos, screenshots de debug, validación contra API tras mutaciones, `SessionExpiredError` para abortar early en bulks). Incluye plantilla para scripts nuevos y checklist de revisión.
|
|
|
|
## Capa agentica MCP
|
|
|
|
El proyecto expone un servidor MCP (stdio) en `mcp_server/` que Claude Code consume automáticamente vía `.mcp.json` en la raíz. Da acceso tipado a cuentas, contactos, opps, syncs y al runner genérico `run_script` que cubre los ~60 scripts no expuestos como tool dedicada. Defaults seguros: toda tool mutadora arranca con `apply=False` y requiere `confirm_token="I-HAVE-USER-CONFIRMATION"` para aplicar — forzando confirmación humana explícita. Los runs con `apply=True` quedan en `script_audit` y son reversibles desde el dashboard.
|
|
|
|
- **Guía user-facing** (cómo aprovecharlo desde Claude Code, recetas y prompts): [docs/GUIA_AGENTICA.md](docs/GUIA_AGENTICA.md).
|
|
- **Catálogo técnico** (contrato, tools, troubleshooting): [docs/AGENT_TOOLS.md](docs/AGENT_TOOLS.md).
|
|
- **Manifest autogenerado**: `generated/agent/tools_manifest.json`. **Auditoría de salud**: `python scripts/audit_agent_readiness.py`.
|
|
|
|
## Reglas críticas que no son obvias
|
|
|
|
1. **El CSV `Bucéfalo - Mesa de control - API Tokens - MP.csv` y los tokens son secretos.** No los imprimas en logs ni los persistas en SQLite.
|
|
2. **Paginación GHL obligatoria**: contactos con `meta.nextPageUrl`/`startAfter`, oportunidades con `POST /opportunities/search`. Nunca uses offset pagination ni `GET /opportunities/`. Detalle completo en AGENTS.md.
|
|
3. **Marca:** Bucéfalo es nuestro CRM. Aunque GHL aparece en el código por necesidad técnica, no lo menciones como producto en interfaces de usuario o documentación destinada a clientes.
|
|
4. **Pipelines sintéticos**: si GHL devuelve `[]` de pipelines pero hay oportunidades con `pipelineId`, `sync_engine` los reconstruye desde las etapas — no es bug, es fallback.
|
|
5. **Antes de modificar GHL**: prefiere correr el script en modo `--dry-run` y revisar el resumen de auditoría antes de aplicar.
|
|
6. **Registro de casos** (`docs/casos/`): bitácora de operaciones/investigaciones reales, optimizada para recall del agente. **Antes** de investigar un síntoma nuevo, `grep` ahí por el error/síntoma (quizá ya está resuelto). **Después** de mutar Bucéfalo o cerrar una investigación con causa raíz, registra el caso (copia `docs/casos/_PLANTILLA.md`, agrega fila a `docs/casos/INDEX.md`). Es complemento de los `PLAYBOOK_*` (teoría) y la memoria (hechos atómicos).
|