Primer commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
---
|
||||
id: CASE-2026-05-30-comparativa-auditoria-completa-buckets
|
||||
fecha: 2026-05-30
|
||||
categoria: descuadre | cascada_n8n | config_location
|
||||
location_ids: ["GbKkBpCmKu2QmloKFHy3 (Monte Providencia / Marca)", "jE41bVhhnb5T505BFm4F (85964 - MP - Morelia 1)", "uZnMH5bO6MXTHcgHeyq9 (85935 - MP - Pilares, hub)", "NSDniGzjxotVDNa5YxqW (85937 - MP - METEPEC, shell)"]
|
||||
run_ids: ["45554292-1b2d-491c-8107-b0ebf81c0b86 (cleanup Patricia)", "fill-temixco-20260530", "bf-cristhian-20260530", "bf-hugo-20260530", "isai-tienda-20260530", "isai-opp-20260530", "fix-identity-collisions-20260530 (sarahi name + miguel TIENDA)", "fix-sarahi-tienda-20260530", "cleanup link 9i1rDQa (isai dup rpDH)", "create-miguel-brand-opp-20260530"]
|
||||
snapshots: ["generated/migrations/cleanup_brand_duplicate_replica_opps_20260530_122326.json", "generated/migrations/fix_identity_collisions_20260530_130329.json", "generated/migrations/create_miguel_brand_opp_20260530_132718.json"]
|
||||
status: resuelto
|
||||
memorias: ["[[positive_opp_descuadre_double_replica]]", "[[verificador_tipo_de_tienda_colapso]]", "[[positive_descuadre_stale_cache]]", "[[audit_hub_map_metepec_pilares]]"]
|
||||
playbooks: ["docs/PLAYBOOK_DESCUADRE.md"]
|
||||
---
|
||||
|
||||
## TRIGGERS
|
||||
- `Comparativa Marca vs Sucursales sigo viendo discrepancia`
|
||||
- `auditar por completo los buckets`, `optimizar toda la sección Comparativa`
|
||||
- `opps +1 descuadre positivo`, `opportunities_in_brand_duplicate_link 2`
|
||||
- `contacts_in_brand_present_in_other_branch_not_assigned 84 falsos positivos`
|
||||
- `TIENDA=METEPEC vive en Pilares 85935`, `Metepec 85937 vacío hub digital`
|
||||
- `audit lee Verificador CSV no Baserow`, `falta fila METEPEC→85935 digital`
|
||||
- `DIGITAL_HUB_BY_SHELL`, `hub-map en código audit_brand_vs_branches_totals`
|
||||
- `PATRICIA PARRA NAVARRO zzBzWC4adBrdTA8WhQph`, `réplica abandoned $0 updatedAt==createdAt`
|
||||
|
||||
## SÍNTOMA
|
||||
Comparativa del dashboard: contactos cuadraban (diff 0) pero **opps +1** (Marca 1341 vs sucursales 1340). El usuario reportó discrepancia persistente y pidió auditar TODOS los buckets + optimizar la sección. Cache fresco (sync 2026-05-30 11:18–11:19).
|
||||
|
||||
## DIAGNÓSTICO
|
||||
1. Frescura de cache: `sync_logs` → todas sincronizaron 11:18–11:19. No es stale.
|
||||
2. Audit completo a JSON (read-only):
|
||||
```
|
||||
python scripts/audit_brand_vs_branches_totals.py --json > generated/agent/runs/descuadre_audit_20260530.json
|
||||
# OJO: el redirect en Windows escribe cp1252, no utf-8. Leer con .decode('cp1252').
|
||||
```
|
||||
3. Buckets con items: `opportunities_in_brand_duplicate_link=2`, `present_in_other_branch_not_assigned=84`, `contacts_missing_id_field=4`, `without_tienda=2`, `in_branch_not_in_brand=1`, `not_in_any_branch=1`, `opportunities_missing_id_field=1`. Resto 0.
|
||||
4. **El +1 de opps** = bucket `opportunities_in_brand_duplicate_link`: 2 opps de Marca con el MISMO link `zzBzWC4adBrdTA8WhQph` (Morelia 1). `extra_opps=1`.
|
||||
5. **El bucket 84** descompuesto por (expected→actual): **82/84 = TIENDA=METEPEC esperado 85937, actual Pilares 85935.** Verificador CSV mapea METEPEC→85937 (vacío, físico), pero los leads digitales viven en el hub Pilares. Las otras tiendas del cluster (GRAND PLAZA/ISIDRO/SENDERO) SÍ tienen fila digital →85935; METEPEC no. → falsos positivos.
|
||||
6. Los contactos cuadraban a 0 **enmascarando** 2 problemas que se cancelan: `in_branch_not_in_brand=1` (sarahi sarabia, real) + `not_in_any_branch=1` (test21 harness, prueba).
|
||||
|
||||
## CAUSA RAÍZ
|
||||
1. **+1 opps**: réplica duplicada en Marca (n8n `Cfgwp0bOtDW8zuKW` hizo CREATE en vez de UPDATE por carrera de indexado). La sobrante: `sAxBY01AQNwSr0OExQof` *abandoned* $0 created==updated 2026-05-30 03:02, colgada de contacto distinto (edgar morales). Mismo patrón que [[positive_opp_descuadre_double_replica]].
|
||||
2. **84 ruido**: el audit lee el Verificador CSV, que NO codifica la consolidación de hub (Toluca/Metepec/Lerma→Pilares 85935) que Baserow 750 sí tiene ([[verificador_tipo_de_tienda_colapso]]). Falta la fila digital METEPEC→85935.
|
||||
|
||||
## ACCIÓN
|
||||
**A) Optimización del audit (código, sin tocar Bucéfalo)** — autorizada "hub-map en código":
|
||||
- En `scripts/audit_brand_vs_branches_totals.py`: constante `DIGITAL_HUB_BY_SHELL` (shell loc → hub Pilares `uZnMH5bO6MXTHcgHeyq9`) + check en el bucket `present_in_other_branch`: si la sucursal asignada es shell de un hub y el contacto está en el hub, se considera bien asignado.
|
||||
- Resultado: bucket 84 → **2** (los 2 reales: luis fernando tienda=PILARES quirk; miguel angel EUGENIA→Temixco).
|
||||
|
||||
**B) Corrección +1 opps** — autorizada "borrar opp duplicada":
|
||||
```
|
||||
# dry-run (verifica en vivo, snapshot):
|
||||
python scripts/cleanup_brand_duplicate_replica_opps.py --only-link zzBzWC4adBrdTA8WhQph
|
||||
# apply:
|
||||
python scripts/cleanup_brand_duplicate_replica_opps.py --apply --only-link zzBzWC4adBrdTA8WhQph
|
||||
# run_id=45554292-1b2d-491c-8107-b0ebf81c0b86 borró sAxBY01AQNwSr0OExQof (conservó OGQtfmjFk31M5eXKDBpO open $70k)
|
||||
# re-sync Marca para refrescar cache:
|
||||
python -c "import sync_engine as se; a=next(x for x in se.parse_accounts_csv() if x['location_id']=='GbKkBpCmKu2QmloKFHy3'); se.sync_account(a['location_id'], a['token'])"
|
||||
```
|
||||
|
||||
**C) Pendientes (2ª tanda, misma sesión)** — todos aplicados con dry-run+snapshot+audit:
|
||||
- `fill_contact_id_sucursal.py --location Temixco --apply`: cristhian/hugo lado sucursal.
|
||||
- `backfill_brand_contact_id_sucursal.py --only-contact <U9DWipe|bUNqMZaL> --apply`: cristhian/hugo lado Marca.
|
||||
- `fix_brand_tienda_from_sucursal.py --only-contact JV9g9tWO --apply`: isai TIENDA→ECATEPEC (reporta "Errores:1" cosmético al persistir local; el PUT a GHL sí aplica, verificado en vivo).
|
||||
- `backfill_opp_sucursal_link.py --only-opp 0eYLJ6 --apply`: enlazó la opp huérfana de isai → **destapó** que isai tenía 2 réplicas en Marca (rpDH+0eYLJ) de 1 opp en Ecatepec.
|
||||
- `cleanup_brand_duplicate_replica_opps.py --only-link 9i1rDQa --apply`: borró rpDH (réplica nueva), conservó 0eYLJ (original, más antigua). → destapó el faltante −1.
|
||||
- **sarahi** (colisión de teléfono con "luis enrique suchil"): el contacto Marca m6QK tenía email+opp de sarahi pero nombre de luis → UPDATE firstName/lastName→"Sarahí Sarabia" (NO sync, evita 3er duplicado) + TIENDA ATLACOMULCO→ATIZAPAN. jBaK (luis real) intacto.
|
||||
- **miguel** (TIENDA EUGENIA→TEMIXCO) + el faltante −1 era su empeño físico Temixco OQBrOQN9 ($56,671) sin réplica. NO es multi-empeño: son 2 opps DISTINTAS (empeño físico ene-08 $56,671 vs lead digital Marca abr-24 $80,200 sin link, CF vacíos). Se **creó** réplica en Marca (8HITkGkOn3gN23Tl8LBr, con link a OQBrOQN9) SIN tocar la digital — script ad-hoc reusando `resolve_brand_pipeline_and_stage`+`create_opportunity`; el link se setea con PUT separado (el POST no guarda customFields). Gotcha: tras el PUT el GET inmediato da link=None (latencia indexado GHL); re-sync confirma válido.
|
||||
- **luis fernando** (TIENDA=PILARES, vive en Pilares): se agregó 85940 Isidro Fabela (0 contactos) a `DIGITAL_HUB_BY_SHELL` → resuelto.
|
||||
|
||||
## VERIFICACIÓN
|
||||
| | Inicial | Tras 1ª tanda | Final |
|
||||
|---|---|---|---|
|
||||
| Contactos diff | 0 | 0 | **0** |
|
||||
| Opps diff | **+1** | 0 | **0** (1340=1340) |
|
||||
| `opportunities_in_brand_duplicate_link` | 2 | 0 | 0 |
|
||||
| `present_in_other_branch_not_assigned` | 84 | 2 | **0** |
|
||||
| `contacts_missing_id_field` | 4 | 4 | **0** |
|
||||
| `opportunities_missing_id_field` | 1 | 1 | **0** |
|
||||
| `contacts_in_branch_not_in_brand` | 1 | 1 | **0** |
|
||||
| Items accionables totales | ~95 | — | **2 (solo test21, fantasma)** |
|
||||
|
||||
> Lección clave: el "diff 0" inicial de opps era engañoso — enmascaraba el duplicado de isai (+1) contra el faltante de miguel (−1). El faltante NO aparecía en `opportunities_in_branch_not_in_brand` (=0) porque el bucket matchea por contacto y miguel ya tenía una opp en Marca (gap multi-opp). Se cazó con un diff 1:1 de links sucursal↔Marca (parsear `fieldValueString`, no `value`).
|
||||
|
||||
## EDGE-CASES / TRAMPAS
|
||||
- El redirect `> file.json` en Windows NO escribe utf-8; el JSON sale cp1252. Decodificar con `cp1252`.
|
||||
- `sync_account(location_id, token)` — son DOS args posicionales, no el dict de cuenta.
|
||||
- El audit lee SQLite (cache). Tras un DELETE en GHL hay que **re-sync de la location** antes de re-auditar o el número no cambia.
|
||||
- El hub-map mapea varios shells→Pilares pero solo METEPEC generaba falsos positivos (los demás ya resolvían al hub vía CSV o tienen 0 contactos). Mapearlos todos es inocuo y a prueba de cambios de orden en el CSV.
|
||||
|
||||
## REUTILIZABLE
|
||||
- Descomponer cualquier bucket grande por (expected_branch → actual_branch) con Counter antes de concluir "error de datos": muchas veces es mapeo del Verificador, no datos.
|
||||
- `cleanup_brand_duplicate_replica_opps.py --only-link <link>` es el camino seguro para el descuadre positivo de opps por doble réplica (verifica en vivo + snapshot + script_audit).
|
||||
|
||||
## PENDIENTES
|
||||
- **test21** (fantasma de índice): GET 400 / search 200. No accionable; se auto-corrige cuando GHL reconcilie el índice. Sigue en `without_tienda` + `not_in_any_branch`.
|
||||
- **Lead digital de miguel** (opp Marca 1A3P5b $80,200, sin link): es un lead digital creado directo en Marca que nunca bajó a su sucursal (Temixco). Idealmente lo baja la cascada n8n Marca→Sucursal. Queda solo-Marca; el conteo cuadra igual. Revisar si ese lead digital es válido y debe cascar.
|
||||
|
||||
## ENLACES
|
||||
- Memorias: [[positive_opp_descuadre_double_replica]], [[verificador_tipo_de_tienda_colapso]], [[positive_descuadre_stale_cache]], [[name_account_with_location_id]], [[audit_hub_map_metepec_pilares]]
|
||||
- Playbook: docs/PLAYBOOK_DESCUADRE.md
|
||||
- Scripts: scripts/audit_brand_vs_branches_totals.py, scripts/cleanup_brand_duplicate_replica_opps.py
|
||||
- Caso previo relacionado (réplica duplicada, quedó parcial): docs/casos/2026-05-30-descuadre-opp-replica-duplicada-marca.md
|
||||
Reference in New Issue
Block a user