Skip to content

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.syntage para 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 /extractions con tax_status y rpc terminadas desde el watermark dice exactamente qué cambió. No recorre todas las entidades ni depende de syntage.entities. El webhook de tax_status de webhook_syntage (TAX_STATUS_TOPIC=0) sigue apagado a propósito.
  • Solo lo que terminó. CSF terminada → tax-status por RFC. RPC terminada → rpc/entidades (trae actos y socios embebidos) y shareholders por entidad. Como máximo 3 llamadas por entidad.
  • tax_status es historial; el RPC es estado actual. Las constancias se acumulan (upsert por id). En rpc_* y shareholders, 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_id en rpc_* y shareholders: 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)

  1. Ventana. En modo incremental (diario) va desde el fin de la última corrida exitosa menos OVERLAP_MINUTES (60) hasta ahora; la primera vez mira INITIAL_LOOKBACK_HOURS (48 por defecto; en prod se configuró 24 para no traer historia). En modo backfill usa since/until (default BACKFILL_SINCE = 2021-01-01 → ahora; un until futuro se recorta a ahora para que el watermark no quede adelantado) y es resumible por cursor: procesa hasta PROCESS_BUDGET_S (~50 min) y responde has_more hasta terminar.
  2. 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).
  3. Datos. Por entidad, solo lo que terminó: GET /taxpayers/{rfc}/tax-status y/o GET /entities/{id}/datasources/mx/rpc/entidades + GET /entities/{id}/shareholders. Pacing REQS_PER_MINUTE (default 30 de las 60/min de la cuenta, que comparten otros procesos) y backoff ante 429/5xx.
  4. Carga. Cada FLUSH_EVERY (200) entidades: load a una staging _stg_<tabla>_<run_id>_* (vence en 1 día) y MERGE contra la tabla final. En rpc_* y shareholders agrega WHEN NOT MATCHED BY SOURCE AND entity_id IN UNNEST(@entity_ids) THEN DELETE. Crea las tablas si no existen (DDL en sql/ddl.sql).
  5. Control. Una fila por corrida en kyc_sync_control: ventana, estado, filas por tabla, llamadas, cursor y failed_keys.
  6. Resumen en logs. Cierra con un log estructurado event="kyc_sync_run" (el resultado completo) y uno event="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) en solvento-risk-analytics-prod: analytics_engineering_staging.stg_syntage__* (uno por tabla) y analytics_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 en sql/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.