Contactos Cobranza Adela (Google Sheet → BigQuery)
Lleva el directorio de contactos de cobranza (un Google Sheet que mantiene CuSu a mano) a BigQuery, ya validado y normalizado, para que el agente de voz de cobranza de Adela (ElevenLabs) lo consuma vía una question de Metabase a través del backend de Lean.
Puente temporal — el destino era HubSpot
Este proceso existe porque hoy los contactos viven en un Sheet editado a mano. La fuente de verdad a futuro es HubSpot (ya replicado a solvento-data-prod.hubspot.* vía Airbyte). Cuando la migración esté, se reescribe solo el SELECT de la question de Metabase apuntando a HubSpot y este servicio se deprecia.
Actualización 2026-10: el CRM pasa de HubSpot a Lightfield, así que el destino hay que redefinirlo. Lightfield ya replica contacts a solvento-data-prod.lightfield.contacts (sin modelo dbt todavía) — ver Airbyte · Lightfield.
¿Llegaste hasta acá desde…?
| Si ves… | Eso sale de… | La lógica está en… |
|---|---|---|
| Contactos que llama el agente de cobranza de Adela | solvento-data-prod.elevenlabs.contactos_cobranza |
Cloud Run contactos-adela-sheets-etl (transform.py) |
Un teléfono normalizado (+52…) para cobranza |
Columnas *_telefono_e164 de esa tabla |
transform.py (normalización E.164) |
Por qué un contacto no se llama (is_contactable = false) |
Columnas is_contactable / estado_raw / data_warnings |
transform.py (flag de estado + hay teléfono de cobranza válido) |
| Un contacto que "desapareció" o no entró | …_rejected (con reject_reason) |
transform.py (sin RFC / RFC duplicado) |
| Campo | Valor |
|---|---|
| Origen | Google Sheet "Contactos Cartera" (Drive, editado por CuSu) |
| Patrón | Cloud Run agendado (pull del Sheet vía Sheets API → validación → carga a BigQuery) |
| Destino | solvento-data-prod.elevenlabs.contactos_cobranza (+ _history, _rejected) |
| Frecuencia | 3x por dia (Cloud Scheduler contactos-adela-sheets-etl-daily, 0 8,13,18 hora Mexico, POST /extract) |
| Criticidad | 🟡 Media (alimenta un agente que llama clientes; volumen bajo, puente temporal) |
| Dueño | Martin (data eng) · agente/negocio: María Sánchez · backend: Leandro Gutiérrez |
| Repos | contactos-adela-sheets-etl · terraform-gcp-infrastructure |
Diagrama de linaje
graph LR
SCH[["⏰ Cloud Scheduler<br/>contactos-adela-sheets-etl-daily<br/>0 8,13,18 (MX) · POST /extract"]]
SH[["📄 Google Sheet<br/>Contactos Cartera (CuSu)"]]
CR["Cloud Run<br/>contactos-adela-sheets-etl<br/>(Flask · valida + normaliza)"]
BQ[("solvento-data-prod.elevenlabs<br/>contactos_cobranza<br/>(+_history, +_rejected)")]
MB["Question Metabase 6321<br/>(directorio + is_contactable)"]
API["backend Lean<br/>(capa auth)"]
EL["🤖 Agente ElevenLabs<br/>(cobranza)"]
SCH -->|dispara| CR
SH -->|Sheets API · read-only| CR --> BQ --> MB --> API --> EL
classDef src fill:#fde68a,stroke:#b45309,color:#000;
classDef store fill:#bfdbfe,stroke:#1e40af,color:#000;
class SCH,SH src;
class BQ store;
Paso a paso
- Google Sheet — CuSu mantiene a mano el directorio (altas/bajas de contactos y una columna de estado/flag "no contactar"). Compartido como Lector con la SA del servicio (
data-prod-sa@solvento-data-prod.iam.gserviceaccount.com). - Cloud Scheduler (
contactos-adela-sheets-etl-daily,0 8,13,18hora Mexico) hacePOST /extract. - Cloud Run
contactos-adela-sheets-etl(repo) lee el Sheet vía Sheets API (pestaña porgid, detección de header por contenido), y entransform.py:- normaliza teléfonos a E.164 (MX
+52/ US+1); lo no normalizable quedanullcon warning. - valida email y RFC; interpreta el flag de estado (
CERRADA,NO CONTACTAR, … → no contactable). - calcula
is_contactable: estado activo y hay teléfono del contacto de cobranza. Al contacto principal no se lo llama (suele ser CEO o directivo), así que no hay fallback; cuando falta el de cobranza la fila queda no contactable con el warningsin_telefono_cobranza_validoendata_warnings, para que CuSu lo cargue. - descarta a
_rejectedlas filas sin RFC o con RFC duplicado. - emite
schema_warningssi cambian las columnas del Sheet (drift).
- normaliza teléfonos a E.164 (MX
- Carga a BigQuery:
contactos_cobranza(truncate, foto viva) +_history(snapshot diario) +_rejected(con motivo). -
Consumo: la question de Metabase 6321 · Contactos Cobranza (colección CuSu, filtro opcional por
customer_rfc) devuelve el directorio conis_contactableydata_warnings. El backend de Lean la consulta detrás de una capa de autenticación (patrón Consumo desde la app) e invoca al agente de ElevenLabs. ElevenLabs no pega directo a la data.La question no filtra
is_contactable— lo tiene que respetar el backendHoy 6321 devuelve todas las filas (
WHERE 1=1), con el contacto principal y el de cobranza por separado. Quien decide a quién llamar es el backend, así que la marcais_contactable = falsesolo tiene efecto si el backend la respeta. Quitar el fallback al principal del lado del consumo es SOL-386. El archivosql/metabase_contactos_agente.sqldel repo del ETL no refleja la question desplegada.
Tablas que alimenta
| Tabla | Contenido | Job que la carga |
|---|---|---|
elevenlabs.contactos_cobranza |
Directorio actual, 1 fila por RFC, datos limpios | Cloud Run (truncate) |
elevenlabs.contactos_cobranza_history |
Snapshot diario (auditoría) | Cloud Run (append, particionada) |
elevenlabs.contactos_cobranza_rejected |
Filas descartadas + reject_reason |
Cloud Run (append, particionada) |
Consumo aguas abajo
- Agente de cobranza Adela (ElevenLabs) vía backend de Lean → question de Metabase sobre
contactos_cobranza.
Si falla 🛠️
Impacto: el agente de cobranza trabaja con contactos desactualizados (última corrida válida). No bloquea otros procesos.
| Síntoma | Dónde mirar | Acción |
|---|---|---|
| Tabla desactualizada | Job contactos-adela-sheets-etl-daily (Cloud Scheduler) + logs del Cloud Run |
Ver si corrió; revisar error en el response de /extract |
403 SERVICE_DISABLED al leer el Sheet |
Logs Cloud Run | Habilitar sheets.googleapis.com en el proyecto |
403 de permiso al leer el Sheet |
— | Confirmar que el Sheet esté compartido con la SA del servicio |
schema_warnings.missing no vacío |
Response de /extract o log SCHEMA: |
Alguien renombró/borró una columna del Sheet → corregir el Sheet o el mapeo en transform.py |
Muchas filas en _rejected |
Tabla _rejected (reject_reason) |
Datos mal cargados en el Sheet (sin RFC / duplicados) |
Escalación: proceso/código → Martin · contenido del Sheet → CuSu / María Sánchez · backend y consumo del agente → Leandro Gutiérrez.
Enlaces
- 📊 Estado en vivo: dashboard "Data Processes" (Cloud Monitoring)
- 📦 Repos:
contactos-adela-sheets-etl· deploy enterraform-gcp-infrastructure - 🔮 Destino futuro: por redefinir con el CRM nuevo — Airbyte · Lightfield (antes Airbyte · Hubspot) · consumo: Consumo desde la app (Metabase)
Última actualización: 2026-08-25 (regla: solo se llama al contacto de cobranza) · Puente temporal hasta la migración de contactos al CRM (2026-10: HubSpot → Lightfield, destino por redefinir). Si cambia el flujo, actualizar esta ficha en el mismo MR.