Syntage · KYC (CSF, RPC y accionistas)
Una vez al día pregunta a Syntage qué extracciones de CSF y del Registro Público de Comercio terminaron, trae los datos de esas entidades y los deja en tablas RAW de
solvento-data-prod.syntagepara el análisis KYC.
En producción desde el 2026-09-25, sin carga histórica
Corre el incremental diario (5:45 MX). Todavía no se hizo el backfill: las tablas tienen lo que terminó en Syntage desde el 2026-09-24 23:17 UTC (ventana del primer incremental, 24 h). Diego ya tiene modelos dbt sobre estas tablas (int_syntage_*, ver Consumo). Además se cargaron a mano algunos RFCs puntuales con ventanas angostas de backfill, sin mover el watermark.
¿Llegaste hasta acá desde…?
| Si ves… | Eso sale de… | La lógica está en… |
|---|---|---|
| Razón social, domicilio, actividad o régimen de un cliente en el proceso de KYC | syntage.tax_status → int_syntage_tax_status_current |
Ingesta: Cloud Run syntage-kyc-extractor-prod · Modelo: dbt-analytics (Diego) |
| Acta constitutiva (FME, número de escritura, fecha de inscripción) o asambleas | syntage.rpc_entidades · syntage.rpc_actos |
Ingesta: cloud_run/kyc/transform.py |
| Accionistas y porcentaje (acciones / total) | syntage.shareholders (y syntage.rpc_socios) |
Ingesta: cloud_run/kyc/transform.py |
| "¿Cuándo corrió y qué cargó?" | syntage.kyc_sync_control |
Tabla de control; en vivo, dashboard "Data Processes" → sección "Syntage KYC" |
Resumen
| Campo | Valor |
|---|---|
| Origen | API de Syntage: /extractions, /taxpayers/{rfc}/tax-status, /entities/{id}/datasources/mx/rpc/entidades, /entities/{id}/shareholders |
| Patrón | Pull programado: Cloud Run + Cloud Scheduler, MERGE en BigQuery |
| Destino | solvento-data-prod.syntage (5 tablas de datos + control) |
| Frecuencia | Diaria 45 5 * * * MX (Cloud Scheduler syntage-kyc-extractor-daily, Terraform 🔧); backfill resumible bajo demanda |
| Volumen | Primer incremental (24 h, 2026-09-25): 16 extracciones terminadas, 9 entidades, 24 llamadas, 81 s |
| Criticidad | 🟡 Media: si falla, Legal & Compliance vuelve a bajar los datos a mano |
| Dueño | Martin |
| Repos | syntage-kyc-extractor (código) · terraform-gcp-infrastructure (Cloud Run, scheduler, métricas) |
El concepto (por qué está diseñado así)
- Parte de las extracciones, no de un catálogo.
GET /extractionscontax_statusyrpcterminadas desde el watermark dice exactamente qué cambió. No recorre todas las entidades ni depende desyntage.entities. El webhook detax_statusdewebhook_syntage(TAX_STATUS_TOPIC=0) sigue apagado a propósito. - Solo lo que terminó. CSF terminada →
tax-statuspor RFC. RPC terminada →rpc/entidades(trae actos y socios embebidos) yshareholderspor entidad. Como máximo 3 llamadas por entidad. tax_statuses historial; el RPC es estado actual. Las constancias se acumulan (upsert porid). Enrpc_*yshareholders, al refrescar una entidad se borra lo que Syntage ya no devuelve: si no, los accionistas de una asamblea anterior seguirían sumando porcentaje.- La clave incluye
entity_idenrpc_*yshareholders: la misma entidad registral (o relación) puede volver para dos entidades de la misma corrida, por ejemplo la empresa y su socio persona moral. - Idempotente. Staging con vencimiento de 1 día +
MERGE. Repetir una ventana no duplica nada; por eso el watermark se toma con 60 minutos de solapamiento. - No descarga ni parsea PDFs. Todo sale del JSON de Syntage.
Diagrama de linaje
graph LR
SCH[["⏰ Cloud Scheduler<br/>syntage-kyc-extractor-daily<br/><i>45 5 * * * MX</i>"]]
API[["🌐 API Syntage"]]
subgraph ing["Ingesta · Cloud Run syntage-kyc-extractor-prod"]
EXT["1 · GET /extractions<br/><i>tax_status y rpc terminadas</i>"]
PLAN["2 · plan por entidad"]
GET["3 · tax-status · rpc/entidades · shareholders"]
STG[("staging _stg_*<br/><i>vence en 1 día</i>")]
end
subgraph raw["solvento-data-prod.syntage"]
TS[("tax_status")]
RE[("rpc_entidades")]
RA[("rpc_actos")]
RS[("rpc_socios")]
SH[("shareholders")]
CTRL[("kyc_sync_control")]
end
MB["📊 Modelos dbt de Diego<br/><i>stg/int_syntage_*</i><br/>→ proceso KYC → Legal & Compliance"]
SCH -->|"POST /process"| EXT
API --> EXT
EXT --> PLAN --> GET
API --> GET
GET --> STG
STG -->|"MERGE"| TS & RE & RA & RS & SH
EXT -.->|"watermark"| CTRL
TS & RE & RA & RS & SH --> MB
classDef src fill:#fde68a,stroke:#b45309,color:#000;
classDef store fill:#bfdbfe,stroke:#1e40af,color:#000;
class SCH,API src;
class TS,RE,RA,RS,SH,CTRL,STG store;
Paso a paso
1 · Ingesta — Cloud Run syntage-kyc-extractor-prod (repo)
- Ventana. En modo
incremental(diario) va desde el fin de la última corrida exitosa menosOVERLAP_MINUTES(60) hasta ahora; la primera vez miraINITIAL_LOOKBACK_HOURS(48 por defecto; en prod se configuró 24 para no traer historia). En modobackfillusasince/until(defaultBACKFILL_SINCE= 2021-01-01 → ahora; ununtilfuturo se recorta a ahora para que el watermark no quede adelantado) y es resumible por cursor: procesa hastaPROCESS_BUDGET_S(~50 min) y respondehas_morehasta terminar. - Novedades.
GET /extractions?extractor[]=tax_status&extractor[]=rpc&status=finished&finishedAt[after]=…&order[finishedAt]=asc&itemsPerPage=1000. Cada página tarda 25–80 s porque Syntage cuenta el total; por eso páginas de 1000. Agrupa por entidad (link.@id) y RFC (taxpayer.@id). - Datos. Por entidad, solo lo que terminó:
GET /taxpayers/{rfc}/tax-statusy/oGET /entities/{id}/datasources/mx/rpc/entidades+GET /entities/{id}/shareholders. PacingREQS_PER_MINUTE(default 30 de las 60/min de la cuenta, que comparten otros procesos) y backoff ante429/5xx. - Carga. Cada
FLUSH_EVERY(200) entidades: load a una staging_stg_<tabla>_<run_id>_*(vence en 1 día) yMERGEcontra la tabla final. Enrpc_*yshareholdersagregaWHEN NOT MATCHED BY SOURCE AND entity_id IN UNNEST(@entity_ids) THEN DELETE. Crea las tablas si no existen (DDL ensql/ddl.sql). - Control. Una fila por corrida en
kyc_sync_control: ventana, estado, filas por tabla, llamadas, cursor yfailed_keys. - Resumen en logs. Cierra con un log estructurado
event="kyc_sync_run"(el resultado completo) y unoevent="kyc_sync_table"por tabla. De ahí salen las métricas del dashboard (ver Monitoreo).
Estados: success (el watermark avanza) · partial (alguna entidad falló por red, 429 o 5xx, o el backfill quedó pendiente: el watermark no avanza y la próxima corrida repite la ventana) · error (falló el listado, un MERGE o el control). Los 4xx de Syntage se cuentan como omitidos (entities_skipped) y no bloquean.
2 · Consumo
- Modelos dbt de Diego (
dbt-analytics, MR !141) ensolvento-risk-analytics-prod:analytics_engineering_staging.stg_syntage__*(uno por tabla) yanalytics_engineering_intermediate.int_syntage_*(CSF vigente con el id del PDF, entidad RPC vigente, socios con porcentaje y accionistas resueltos). - Proceso de KYC de Diego: combina esos modelos con los documentos del onboarding que deja KYC · Documentos a Drive y arma los campos del contrato con Claude por API para Legal & Compliance.
Tablas que alimenta
| Tabla | Contenido | Clave del MERGE | Al refrescar una entidad |
|---|---|---|---|
syntage.tax_status |
CSF: razón social o nombre, domicilio por componentes, actividades, regímenes, obligaciones, id del PDF | id |
upsert (historial) |
syntage.rpc_entidades |
Entidad registral: FME, régimen jurídico, oficina registral, fecha de inscripción | id, entity_id |
reemplaza |
syntage.rpc_actos |
Actos inscritos (M4, M2, M10…) con número de escritura, fechas y data (JSON) |
id, entity_id |
reemplaza |
syntage.rpc_socios |
Socios según el RPC, con CURP y fecha de nacimiento | id, entity_id |
reemplaza |
syntage.shareholders |
Accionistas consolidados por Syntage: acciones, total y fuente | relation_id, entity_id |
reemplaza |
syntage.kyc_sync_control |
Una fila por corrida | — | — |
Sin partición (son tablas chicas) y clusterizadas por rfc. Tienen datos personales de clientes y socios (CURP, fecha de nacimiento, domicilios): el acceso a la question debe quedar acotado a Legal & Compliance (definir). La conexión Solvento-Dwh-BQ (DB 44) de Metabase lee el dataset syntage completo, así que el acceso real depende de los permisos de grupo en Metabase. Conviene revisarlo con Compliance antes de publicar la question.
Entornos
| Dev | Prod | |
|---|---|---|
| Cloud Run | syntage-kyc-extractor-dev |
syntage-kyc-extractor-prod |
| Proyecto / dataset | solvento-data-dev.syntage |
solvento-data-prod.syntage |
| Syntage | sandbox (api.sandbox.syntage.com, secret syntage-api-key-sandbox) |
api.syntage.com, secret syntage-api-key |
| Scheduler | — (se invoca a mano) | syntage-kyc-extractor-daily · 45 5 * * * MX · OIDC sa-scheduler-data-prod |
| Cuenta de servicio | data-dev-sa |
data-prod-sa |
Los dos corren con concurrency 1, max_instances 1 y timeout 3600. Imagen, variables y scheduler se declaran en terraform-gcp-infrastructure (cloud_run_services_dev/prod, cloud_scheduler_jobs_prod); el deploy es publicar la imagen y cambiar el tag. CI del repo en cada push y MR: ruff y pytest.
Monitoreo
| Qué | Dónde | Para qué |
|---|---|---|
| Sección "Syntage KYC" | Dashboard "Data Processes" (Cloud Monitoring, dashboard_gcp) |
Filas por tabla, corridas por resultado, entidades procesadas / fallidas / omitidas, duración, logs ≥ WARNING |
6 métricas log-based syntage-kyc-* |
terraform-gcp-infrastructure → logging_metrics_prod |
Salen de los logs kyc_sync_run y kyc_sync_table; excluyen los dry-run |
| Proceso "Syntage KYC" en el panel de freshness | report.v_freshness_procesos (consistencia → freshness/) |
fail si no hubo corrida en 30 h o si la última success es de antes de ayer (UTC) |
El panel de freshness mira el watermark de kyc_sync_control, con la misma definición que usa el servicio. Es la alarma para el caso más probable: el servicio corre todos los días, pero una entidad que falla siempre lo deja en partial y el watermark no avanza. Los gráficos no muestran bien la ausencia de una corrida; el panel sí.
Las métricas dependen de los nombres de evento y de campo del log: si cambian en el código, hay que cambiar los filtros de Terraform en el mismo MR.
Si falla 🛠️
Impacto: la question de KYC queda con los datos del último día bueno; mientras tanto, Legal & Compliance puede bajar la CSF y el RPC a mano desde Syntage. Criticidad media.
| Síntoma | Dónde mirar | Acción |
|---|---|---|
No corrió hoy (freshness en fail por atraso; syntage-kyc-runs sin puntos) |
Cloud Scheduler syntage-kyc-extractor-daily; logs del Cloud Run |
Re-invocar POST /process (sin body = incremental). Es idempotente |
Freshness en fail: "el job corre pero el dato no avanza" |
kyc_sync_control: las últimas corridas en partial o error |
Seguir las dos filas de abajo. El watermark avanza con la primera corrida success |
status = partial varios días seguidos |
kyc_sync_control.failed_keys; logs (Entidad … falló) |
Fallo persistente en esas entidades: revisar la respuesta de Syntage. El watermark no avanza hasta resolverlo |
status = error |
kyc_sync_control.error; logs |
Permisos o MERGE en BigQuery, o el listado de /extractions. Corregir y re-invocar |
| Backfill a medias | kyc_sync_control con mode = backfill y has_more = true |
Invocar {"mode":"backfill"} sin ventana: reanuda por cursor |
| Errores 429 | Logs (X-RateLimit-*) |
Ya hace backoff; si persiste, bajar REQS_PER_MINUTE |
| Falta un cliente | ¿Tiene una extracción tax_status o rpc terminada en Syntage? |
Sin extracción no hay dato: pedirla en Syntage y esperar la próxima corrida |
Escalación: ingesta → Martin · question de Metabase → Diego.
Enlaces
- 📦 Ingesta:
syntage-kyc-extractor—cloud_run/app.py,cloud_run/kyc/, DDL ensql/ddl.sql - 📊 Estado en vivo: dashboard "Data Processes" (Cloud Monitoring), sección "Syntage KYC" · freshness en
report.v_freshness_procesos(proceso "Syntage KYC") - 🏗️ Infra:
terraform-gcp-infrastructure—cloud_run_services_dev/prod,cloud_scheduler_jobs_prod,logging_metrics_prod - 🔗 Relacionado: Cloud Schedulers · Dashboard Data Processes
- 🎫 Ticket: SOL-463 (Linear)
Última actualización: 2026-09-30 · Si cambia el flujo, actualizar esta ficha en el mismo MR.