# AGENTS.md > **Capa agentica (MCP)**: para uso desde Claude Code u otros clientes LLM, este repo expone un servidor MCP stdio en `mcp_server/` con tools tipadas (defaults dry-run, `confirm_token` para aplicar, rollback vía `script_audit`). Ver [docs/AGENT_TOOLS.md](docs/AGENT_TOOLS.md) para el catálogo y recetas. Manifest navegable en `generated/agent/tools_manifest.json`. ## Commands - Install deps with `python -m pip install -r requirements.txt`. - Run the local app with `python main.py`; it serves FastAPI/Uvicorn at `http://127.0.0.1:8000` with reload enabled. - On Windows, `start.bat` runs `python main.py` in a new window and opens the browser; `stop.bat` kills any process using port `8000`. - There is no test, lint, or formatter config in this repo. For a syntax-only check, run `python -m py_compile main.py db.py ghl_client.py sync_engine.py script_runner.py` and add specific scripts as needed. - Run focused utility scripts directly, for example `python scripts\mp_contact_search.py ` or `python scripts\mp_opportunity_search.py `. - Global dashboard sync parallelism is controlled with `SYNC_ENGINE_MAX_WORKERS`; default is `12`, hard maximum is `20`. It affects the `Sincronizar Todo` button and processes multiple GHL locations in parallel. - Dashboard batch parallelism is controlled globally with `SCRIPT_RUNNER_MAX_WORKERS`; default is `4`, hard maximum is `20`. Higher values can increase GHL `429`/timeout risk because script subprocesses do not share in-memory rate-limit state. ## App Wiring - `main.py` is the app entrypoint; API routes call `db.py`, `sync_engine.py`, and `script_runner.py`. - `templates/index.html` plus `static/js/app.js` and `static/css/style.css` are the single-page dashboard UI. - `db.py` owns the local SQLite schema at `generated/data/mp_manager.sqlite` (path comes from `paths.DB_PATH`); sync writes replace per-location contacts, pipelines, and opportunities inside transactions. - `sync_engine.py` reads `Bucéfalo - Mesa de control - API Tokens - MP.csv` at startup/sync time. The CSV must include `Location_ID`, `Nombre`, and `API_token`; tokens are cached in memory and are not stored in SQLite. - The main brand account is hard-coded as location `GbKkBpCmKu2QmloKFHy3`; all other CSV locations are treated as branches. ## Generated Files All dynamic outputs live under `generated/` and are sourced from `paths.py`. Never hardcode disk paths in new code — import from `paths` (or via `scripts/common.py`, which re-exports them). Layout: `generated/data/` (SQLite), `generated/reports/` (audit/dup/drift/coverage outputs), `generated/exports/` (downloads served by `/api/exports/`), `generated/logs/` (`errors.jsonl` + `script_runs/`), `generated/migrations/` (pre-destructive snapshots), `generated/browser/` (Playwright session, profile, screenshots), `generated/runtime/` (`server_info.json`, `last_mode`, in-flight bulk batches), `generated/_archive/` (legacy). The whole tree is gitignored and megaignored. ## GHL API Gotchas - `ghl_client.py` is the executable source for GHL behavior: base URL `https://services.leadconnectorhq.com`, API version header `2021-07-28`, and bearer-token auth. - Keep the per-token rate limiting in `GHLClient._wait_for_rate_limit`; global sync concurrency must use `SYNC_ENGINE_MAX_WORKERS` instead of hardcoded worker counts. - Contacts paginate with `meta.nextPageUrl` and `startAfter`; do not replace this with offset pagination. - Opportunities must be fetched with `POST /opportunities/search`; `GET /opportunities/` is documented here as returning empty/zero results. - If GHL returns no pipelines but opportunities have pipeline IDs, sync creates synthetic pipelines from opportunity stages instead of failing. ## GHL Pagination & Full Dataset Retrieval - **Contactos:** usar la paginación cursor-based de GHL con `meta.nextPageUrl`, `startAfter` y, cuando aplique, `startAfterId`. No usar offset pagination. Continuar solicitando páginas hasta que no exista siguiente cursor/URL o hasta alcanzar un límite explícito del script (`--limit`, `--max-contacts`, etc.). - **Oportunidades:** usar exclusivamente `POST /opportunities/search` por `locationId`. Si se requiere un dataset completo, paginar/iterar la búsqueda hasta agotar resultados o alcanzar un límite explícito. No usar `GET /opportunities/` para escaneos porque puede devolver vacío/cero resultados. - **Acumulación segura:** cada página debe agregarse a una colección global, deduplicando por `id` para evitar repetidos entre páginas o reintentos. - **Protección anti-loop:** todo paginador debe controlar cursores/URLs ya vistos, páginas vacías repetidas o límites máximos razonables para evitar ciclos infinitos. - **Auditorías profundas:** antes de comparar, sincronizar o corregir datos, el script debe haber consolidado todas las páginas necesarias de contactos/oportunidades para no dejar datos fuera del análisis. - **Resumen final:** los scripts que paginan deben reportar total de páginas consultadas, total recibido, total único procesado, duplicados descartados y si se cortó por límite. ## Dynamic Schema & Custom Fields Mapping Para consultar y mapear campos personalizados cuyos IDs varían entre diferentes sucursales de GHL, se debe utilizar el siguiente flujo de API (ambos usando la cabecera `Version: 2021-04-15`): 1. **Obtener Catálogo de Objetos:** - **Endpoint:** `GET https://services.leadconnectorhq.com/objects/?locationId={locationId}` - **Detalle:** Requerido para inicializar y autorizar la sesión de consulta de esquemas de metadatos en GHL para esa ubicación. 2. **Obtener Esquema por Objeto (Schema by Key):** - **Endpoint:** `GET https://services.leadconnectorhq.com/objects/{objectKey}?locationId={locationId}` - **Parámetro `{objectKey}`:** Usar `contact` o `opportunity`. - **Detalle:** Devuelve el listado completo de campos (estándar y personalizados). Permite mapear de forma dinámica campos por su nombre (ej. `"Canal de Origen"` o `"Fuente de Prospecto"`) a su correspondiente ID dinámico en esa sucursal para lectura o actualización segura. ## Business Logic & Field Correlations (Monte Providencia Rules) Derivado de `DOCUMENTACIÓN de Monte Providencia.md`, estas reglas rigen el comportamiento de los scripts, la integridad de los datos y el mapeo en GHL: ### 1. Flujo de Sincronización y Prioridades - **Sincronización de Contactos:** Bidireccional entre la cuenta de Marca principal (`GbKkBpCmKu2QmloKFHy3`) y las sucursales. Cualquier creación/modificación en un lado se sincroniza al otro. - **Sincronización de Oportunidades:** Unidireccional de Sucursal a Marca (la sucursal tiene prioridad). Las modificaciones de oportunidades en la cuenta de marca no se sincronizan hacia las sucursales. - **Multi-Oportunidades:** Un mismo contacto puede tener más de una oportunidad (múltiples intentos de empeño). ### 2. Campos Requeridos para Business Intelligence (BI) Cada contacto u oportunidad debe poblar consistentemente: - `Sucursal` (y campo `TIENDA`) alineados con el "verificador de sucursales". - `Canal de Origen` (e.g., FORMULARIO, FACEBOOK, WHATSAPP, LLAMADA, INSTAGRAM, SUCURSAL). - `Fuente de Prospecto` (e.g., CLIENTE CONOCIDO, SUCURSAL, PROSPECCIÓN, REFERIDO, ALIANZA, EVENTO ESPECIAL, LEAD DIGITAL). ### 3. Reglas de Validación de Origen/Fuente - `LEAD DIGITAL` siempre proviene de un canal digital (Canal de Origen: Formulario, Facebook, etc.). - Si el `Canal de Origen` es `SUCURSAL`, la `Fuente de Prospecto` **no puede ser** `LEAD DIGITAL`. - El tag `"sucursal"` en contactos implica creación manual en sucursal, a menos que el campo `source` nativo de GHL indique lo contrario (un origen digital). ### 4. Correlación de Datos de Vehículo - Para prospectos con fuente `LEAD DIGITAL`, el campo `Vehículo` de la oportunidad debe construirse concatenando los campos personalizados del contacto: `"Marca del Vehículo" + "Versión del Vehículo" + "Año del Vehículo"`. - En oportunidades creadas manualmente, el campo de vehículo de la oportunidad puede diferir de los datos del contacto. ### 5. Pipelines y Oportunidades Huérfanas - Toda oportunidad debe residir en un pipeline activo válido (comúnmente llamado `"Standar"`). - Oportunidades huérfanas (sin pipeline válido) se deben reubicar según coincidencia de nombre de etapa (stage) o marcar para revisión. ## Scripts And Data Safety - `script_runner.py` exposes only scripts listed in `SCRIPTS_METADATA`; add metadata there when adding a dashboard-runnable script. - Most scripts read `mp_manager.sqlite` from the repo root and expect a prior dashboard sync. Some scripts are live GHL mutators (`fix_*`, `migrate_*`, `move_*`, `update_*`, `sync_contact_*`); prefer read-only audit/search scripts first unless the user explicitly asks to modify GHL data. - Treat `Bucéfalo - Mesa de control - API Tokens - MP.csv` and any printed API tokens as secrets. ## Case log (registro de casos) - `docs/casos/` is the chronological log of real operations/investigations on Bucéfalo, written **for agent recall** (dense, exact commands, literal ids/errors). It complements the `docs/PLAYBOOK_*` files (timeless theory) and the agent memory (atomic facts). - **When to add a case:** after any Bucéfalo mutation (with `run_id`/snapshot) OR after closing a non-trivial investigation that reached a root cause (even with no mutation). - **How:** copy `docs/casos/_PLANTILLA.md` → `docs/casos/YYYY-MM-DD-.md`, fill it, add the row to `docs/casos/INDEX.md`, and link the related memory with `[[slug]]`. - **Before** investigating a new symptom, `grep` `docs/casos/` for the error/symptom — it may already be solved.