Files
MP-Manager/docs/casos/2026-05-30-comparativa-auditoria-completa-buckets.md
T
2026-05-30 14:31:19 -06:00

9.6 KiB
Raw Blame History

id, fecha, categoria, location_ids, run_ids, snapshots, status, memorias, playbooks
id fecha categoria location_ids run_ids snapshots status memorias playbooks
CASE-2026-05-30-comparativa-auditoria-completa-buckets 2026-05-30 descuadre | cascada_n8n | config_location
GbKkBpCmKu2QmloKFHy3 (Monte Providencia / Marca)
jE41bVhhnb5T505BFm4F (85964 - MP - Morelia 1)
uZnMH5bO6MXTHcgHeyq9 (85935 - MP - Pilares, hub)
NSDniGzjxotVDNa5YxqW (85937 - MP - METEPEC, shell)
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
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
resuelto
positive_opp_descuadre_double_replica
verificador_tipo_de_tienda_colapso
positive_descuadre_stale_cache
audit_hub_map_metepec_pilares
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:1811:19).

DIAGNÓSTICO

  1. Frescura de cache: sync_logs → todas sincronizaron 11:1811: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