---
id: CASE-2026-05-30-verificador-tipo-de-tienda-colapso
fecha: 2026-05-30
categoria: config_location | cascada_n8n | custom_field
location_ids: ["GbKkBpCmKu2QmloKFHy3 (Marca)", "uZnMH5bO6MXTHcgHeyq9 (85935 Pilares = hub digital Toluca/Metepec/Lerma)", "NSDniGzjxotVDNa5YxqW (85937 Metepec, VACÍA)", "jE41bVhhnb5T505BFm4F (85964 Morelia 1)"]
run_ids: []
snapshots: ["generated/migrations/baserow_verificador_pre_tipo_tienda_750_20260530_005359.json", "generated/migrations/baserow_verificador_pre_restore_nodigital_suc_750_20260530_011528.json", "n8n/backup_add_tipo_filter_*_20260530_0115*.json (3 workflows)"]
status: resuelto
memorias: [[verificador_tipo_de_tienda_colapso]], [[tienda_gap_formulario_sitio_web]], [[baserow_api_y_corrector]]
playbooks: []
---

## TRIGGERS
- `TIPO DE TIENDA`, `field 7279`, `NO DIGITAL`, `PARCIAL`, `FULL AUTOS`, `Verificador 750 columna tipo de tienda`
- `colapsar filas digitales`, `1 fila canónica por location`, `SUCURSAL contains más de un resultado`
- `Toluca 3 filas Pilares`, `hub digital Pilares`, `Metepec 85937 vacío`, `Metepec routing Pilares`
- `no-digital 0 contactos`, `premisa formulario no-digital`, `fix_baserow_tipo_de_tienda.py`, `baserow delete_row`

## SÍNTOMA
Leads "Formulario - Sitio Web" de zonas multi-tienda (Toluca/Metepec/Lerma) con TIENDA inconsistente/vacía (ver caso 2026-05-29). El owner agregó columna `TIPO DE TIENDA` a Baserow 750 y propuso que n8n excluya `NO DIGITAL`. Pidió estudiar implicaciones e iterar hasta confirmar funcionamiento en vivo.

## DIAGNÓSTICO (read-only)
1. **3 workflows** leen 750 (no 2): `ddUEORBEtZLzsQF2` [2004] (escribe `contact.tienda` por SC BUCEFALO `equal` limit 1), `4UMRwxJdHFfOGHBp` [1604] Marca→Sucursal (por SUCURSAL `contains` limit 3, toma row[0]), `EuPdIkCORyh0skoB` [SUCURSAL] (2 nodos 750). `x4DqZ5FtSc43tdzB` y `Cfgwp0bOtDW8zuKW` NO tocan 750.
2. **Premisa validada en vivo** (`get_all_contacts`): 85938/85939/85940/85941/85965 = **0 contactos**. Y **85937 Metepec = 0**. Pilares 85935 = 87 contactos, TIENDA consenso `PILARES` (85/87). Morelia 1 = 60, `MORELIA 1`.
3. **Contradicción CSV vs datos:** el CSV editado enrutaba Metepec→85937, pero 85937 está vacío; los leads de la zona caen en Pilares. Decisión del owner: **Metepec→Pilares** (modelo hub).
4. Estado live 750 (52 filas): Toluca = 3 filas →Pilares (GRAND PLAZA/ISIDRO FABELA/INDEPENDENCIA, TIPO vacío) = **ambiguo**. Metepec = 1 digital + 2 NO DIGITAL dup. Morelia/otros = OK. Fila 258 = junk (TIPO literal="TIPO DE TIENDA"). Filas NO DIGITAL de Toluca (Isidro/Grand/Indep) ya existían huérfanas (sin SUCURSAL).

## CAUSA RAÍZ
Ambigüedad estructural en Baserow 750: **varias filas por el mismo texto SUCURSAL** (sin colapsar). El filtro NO DIGITAL solo, NO basta para Toluca (las 3 filas eran todas digitales →Pilares con TIENDA distinta). Hace falta **colapsar a 1 fila canónica por location digital**.

## ACCIÓN (Baserow 750, reversible vía backup)
Script `scripts/fix_baserow_tipo_de_tienda.py` (dry-run→apply, `backup_table` previo). Se agregó `delete_row()` a `scripts/baserow_client.py`.
1. **Routing (piloto):** fila 241 Toluca→`TIENDA=PILARES, TIPO=FULL AUTOS`; **borrar** 242, 243 (dups Toluca). fila 244 Lerma→`PILARES/FULL AUTOS`. **borrar** 254 (dup Metepec) y 258 (junk).
2. **Labels (batch):** `TIPO DE TIENDA=PARCIAL` en 31 filas single-store vacías.
3. **Consistencia:** blanquear `SUCURSAL` de la fila 240 (Metepec NO DIGITAL) → patrón referencia-only.

### ACCIÓN 2 — Filtro en n8n (2026-05-30, a pedido del owner)
Tras el fix de Baserow, el owner pidió **implementar el filtro explícito** y dejar las filas NO DIGITAL CON su SUCURSAL para que el filtro actúe de verdad (load-bearing). Script `n8n/add_tipo_de_tienda_filter.py` (backup por workflow + dry-run + apply + activate + verify, idempotente).
1. **Operador confirmado empíricamente:** query directa Baserow REST → `not_equal` OK (excluye las 6 NO DIGITAL, 0 fugas); `is_not` → `ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST` (la suposición del agente era falsa); `contains_not` también sirve pero `not_equal` es el semántico.
2. Se agregó `{field:7279, operator:"not_equal", value:"NO DIGITAL"}` a `additionalOptions.filters.fields` (AND con el filtro existente) en: `4UMRwxJdHFfOGHBp` "Obtener Info de cuenta objetivo - SUCURSAL" (**load-bearing**), `ddUEORBEtZLzsQF2` "Buscar Sucursal en Verificador de Sucursales" (defensa), `EuPdIkCORyh0skoB` "Buscar Cuenta Sucursal Bucefalo" (defensa). Los 3 quedaron `active=true`, verificado post-PUT.
3. Se **restauró la SUCURSAL** de las 6 filas NO DIGITAL (240/252/253/255/256/257) → ahora el `contains` matchea digital+no-digital y el filtro excluye las no-digital.

## VERIFICACIÓN
- `list_rows(750)` (48 filas tras borrar 4): **las 41 SUCURSAL resuelven a exactamente 1 fila digital** (0 ambiguas). Toluca/Metepec/Lerma `contains=1`→PILARES; Morelia→MORELIA 1.
- SC BUCEFALO `85935 - MP - Pilares` → 3 filas (226/241/244) **todas TIENDA=PILARES** → [2004] limit 1 determinista.
- Pilares live: 64 leads formulario, **0 con TIENDA vacía** (sin rezagados; no requiere backfill nuevo).
- **Filtro (con/sin) por query directa:** Toluca 4→1, Metepec 2→1, Lerma 2→1, Morelia 2→1 → en todas la fila que queda es la digital correcta. Prueba que el filtro es load-bearing y funciona.
- **E2E REAL en vivo:** POST al webhook de [1604] con lead `qa-test` Sucursal=Metepec → ejecución `52776` success, nodo Baserow devolvió **1 fila PILARES** (excluyó la NO DIGITAL de Metepec), réplica creada en Pilares (`DjOIZgekf3Sy2B49AWKp`) con **TIENDA=PILARES** (escrita por [2004]). Contactos de prueba (Marca `h9VXb9jy6ix9v0e5KHaY` + réplica) **borrados y verificados** (GET→error). El nodo "Envio a tienda" mandó 1 correo a la tienda (autorizado por el owner).

## EDGE-CASES / TRAMPAS
- El store **Metepec 85937 está vacío en vivo**; NO es el target digital de Metepec (lo es Pilares). El backfill de Jorge Enrique→Pilares (caso 2026-05-29) era correcto; la edición del CSV→85937 se descartó.
- Las filas NO DIGITAL se dejan **sin SUCURSAL** a propósito (referencia-only) para que el `contains` no las matchee → fix sin depender de un filtro en el workflow.
- **Operador Baserow para "≠" = `not_equal`** (NO `is_not`, que no existe). Validar SIEMPRE el operador con query directa antes de PUTear el nodo.
- El nodo Baserow n8n pasa `operator` tal cual al filter type de Baserow; el E2E confirmó que `not_equal` funciona en el nodo en vivo.
- E2E real del [1604] dispara "Envio a tienda" → **1 correo a la tienda** por cada lead de prueba. Usar `qa-test` y nombre "QA TEST" visible, y borrar Marca+réplica al terminar.
- `baserow_client` no tenía `delete_row` (se agregó). Mutaciones reversibles vía `backup_table`/`backup_workflow`.

## REUTILIZABLE
```python
# Unicidad de resolución por SUCURSAL (lo que ve el workflow)
from collections import defaultdict
from scripts.baserow_client import BaserowClient
rows = BaserowClient.from_credentials().list_rows(750)
by = defaultdict(list)
for r in rows:
    s = str(r.get("SUCURSAL") or "").strip()
    if s and s != "-": by[s].append(r)
amb = {s: rs for s, rs in by.items()
       if len([r for r in rs if str(r.get("TIPO DE TIENDA") or "").upper() != "NO DIGITAL"]) != 1}
# amb vacío => toda SUCURSAL resuelve a 1 fila digital
```

## PENDIENTES
- Ninguno. Filtro desplegado en los 3 workflows + E2E en vivo OK. (Si en el futuro se agregan filas, recordar: NO DIGITAL nunca recibe formulario; el filtro `not_equal` las excluye.)
- Rollback disponible: `n8n/backup_add_tipo_filter_*` (workflows) y `generated/migrations/baserow_verificador_pre_*` (Baserow).

## ENLACES
- Memorias: [[verificador_tipo_de_tienda_colapso]], [[tienda_gap_formulario_sitio_web]], [[baserow_api_y_corrector]]
- Scripts: `scripts/fix_baserow_tipo_de_tienda.py`, `scripts/baserow_client.py` (+`delete_row`)
- Caso previo: [2026-05-29-tienda-vacia-formulario-sitio-web.md](2026-05-29-tienda-vacia-formulario-sitio-web.md)
