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
Frescura de cache: sync_logs → todas sincronizaron 11:18–11:19. No es stale.
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').
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.
El +1 de opps = bucket opportunities_in_brand_duplicate_link: 2 opps de Marca con el MISMO link zzBzWC4adBrdTA8WhQph (Morelia 1). extra_opps=1.
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.
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 opps: réplica duplicada en Marca (n8n Cfgwp0bOtDW8zuKW hizo CREATE en vez de UPDATE por carrera de indexado). La sobrante: sAxBY01AQNwSr0OExQofabandoned $0 created==updated 2026-05-30 03:02, colgada de contacto distinto (edgar morales). Mismo patrón que positive_opp_descuadre_double_replica.
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.