# 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/`.
- `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).
