Integración API Notion — CULTIVA IA

Pipeline CRM sync · Python patterns · REST API v2022-06-28

Notion Python 3.11 REST API
Resumen de operaciones — Sincronización en vivo
6
Proyectos sincronizados
Pipeline Clientes CULTIVA
4
Páginas creadas
create_page() · nuevas entradas
2
Páginas actualizadas
update_page() · sin duplicados
3/s
Rate limit gestionado
backoff exponencial activo
Flujo de sincronización
🔑
Autenticación
NOTION_TOKEN env
🔍
Query BD
detect duplicados
📥
Lee datos externos
CSV / HubSpot API
Create / Update
upsert inteligente
🔄
Rate limit
429 + backoff
Notion actualizado
6 filas en BD
Base de datos Notion — Pipeline Clientes CULTIVA

Pipeline Clientes CULTIVA

db: a3f8c2d1-9e4b-4a7f-b521-cd8e12345678
Cliente Servicio Estado Valor Inicio Cierre Operación
Clínica DentaPlus ⚙️ Automatización Lead Scoring Activo 2.400 € 2026-05-01 2026-07-31 update
Bodega Ramírez 💬 Chatbot WhatsApp Propuesta 1.800 € 2026-06-01 2026-08-15 create
Grupo Torres Inmob. 🤖 Agente IA Captación Activo 3.600 € 2026-04-15 2026-10-15 update
Academia FitPro 📧 Automatización Email Cerrado ✓ 1.200 € 2026-03-01 2026-04-30 create
Restaurante Casa Nova 🔍 SEO + Contenidos IA Propuesta 950 € 2026-06-10 2026-09-30 create
SaaS Contabilix 📊 Dashboard Analytics Negociación 5.200 € 2026-05-20 2026-12-31 create
Patrones de código aplicados
notion_config.py
Python
# Autenticación — token interno CULTIVA IA
import os
import requests

API = "https://api.notion.com/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['NOTION_TOKEN']}",
    "Notion-Version": "2022-06-28",
    "Content-Type": "application/json",
}

# DB pipeline de proyectos CULTIVA
DB_PIPELINE = "a3f8c2d1-9e4b-4a7f-b521-cd8e12345678"
query_database.py
Python
def query_pipeline(estado=None) -> list:
    """Consulta BD con filtro opcional por estado."""
    body = {}
    if estado:
        body["filter"] = {
            "property": "Estado",
            "select": {"equals": estado}
        }
    pages, cursor = [], None
    while True:
        if cursor: body["start_cursor"] = cursor
        r = requests.post(
            f"{API}/databases/{DB_PIPELINE}/query",
            json=body, headers=HEADERS).json()
        pages.extend(r["results"])
        if not r.get("has_more"): break
        cursor = r["next_cursor"]
    return pages
create_proyecto.py
Python
def crear_proyecto(p: dict) -> dict:
    """Crea fila en Pipeline Clientes CULTIVA."""
    return requests.post(
        f"{API}/pages",
        json={
            "parent": {"database_id": DB_PIPELINE},
            "properties": {
                "Nombre": {"title": [{"text":
                    {"content": p["cliente"]}}]},
                "Servicio": {"select":
                    {"name": p["servicio"]}},
                "Estado": {"select":
                    {"name": p["estado"]}},
                "Valor €": {"number": p["valor"]},
                "Fecha inicio": {"date":
                    {"start": p["inicio"]}},
            }
        },
        headers=HEADERS).json()
safe_request.py
Python
import time

def safe_request(method, url, **kw):
    """Reintento con backoff — 3 req/s Notion API."""
    for attempt in range(5):
        resp = requests.request(
            method, url,
            headers=HEADERS, **kw)
        if resp.status_code == 429:
            wait = int(resp.headers.get(
                "Retry-After", 2 ** attempt))
            time.sleep(wait)
            continue
        resp.raise_for_status()
        return resp.json()
    raise Exception("Rate limit: 5 reintentos fallidos")
sync_pipeline_cultiva.py — Script principal de sincronización
Python
def sync_proyectos(proyectos: list[dict]):
    """Upsert inteligente: crea o actualiza proyectos en Notion sin duplicados.

    Args:
        proyectos: Lista de dicts con keys: cliente, servicio, estado, valor, inicio, cierre
    """
    # 1. Obtener páginas existentes y construir índice por nombre de cliente
    existentes = query_pipeline()
    indice = {}
    for page in existentes:
        titulo = page["properties"]["Nombre"]["title"]
        if titulo:
            nombre = titulo[0]["plain_text"]
            indice[nombre] = page["id"]

    # 2. Por cada proyecto: crear si es nuevo, actualizar si existe
    stats = {"created": 0, "updated": 0}
    for p in proyectos:
        props = {
            "Nombre":   {"title":  [{"text": {"content": p["cliente"]}}]},
            "Servicio": {"select": {"name": p["servicio"]}},
            "Estado":   {"select": {"name": p["estado"]}},
            "Valor €":  {"number": p["valor"]},
            "Fecha inicio": {"date": {"start": p["inicio"], "end": p["cierre"]}},
        }
        if p["cliente"] in indice:   # Actualizar existente
            update_page(indice[p["cliente"]], props)
            stats["updated"] += 1
        else:                          # Crear nuevo
            create_page(DB_PIPELINE, props)
            stats["created"] += 1

    print(f"Sync completado: {stats['created']} creados, {stats['updated']} actualizados")
    return stats

# --- Ejecutar ---
if __name__ == "__main__":
    proyectos_cultiva = [
        {"cliente": "Clínica DentaPlus",      "servicio": "Automatización Lead Scoring", "estado": "Activo",    "valor": 2400, "inicio": "2026-05-01", "cierre": "2026-07-31"},
        {"cliente": "Bodega Ramírez",          "servicio": "Chatbot WhatsApp",           "estado": "Propuesta", "valor": 1800, "inicio": "2026-06-01", "cierre": "2026-08-15"},
        {"cliente": "Grupo Torres Inmob.",     "servicio": "Agente IA Captación",        "estado": "Activo",    "valor": 3600, "inicio": "2026-04-15", "cierre": "2026-10-15"},
        {"cliente": "Academia FitPro",         "servicio": "Automatización Email",       "estado": "Cerrado",   "valor": 1200, "inicio": "2026-03-01", "cierre": "2026-04-30"},
        {"cliente": "Restaurante Casa Nova",   "servicio": "SEO + Contenidos IA",        "estado": "Propuesta", "valor":  950, "inicio": "2026-06-10", "cierre": "2026-09-30"},
        {"cliente": "SaaS Contabilix",         "servicio": "Dashboard Analytics",        "estado": "Negociación","valor": 5200, "inicio": "2026-05-20", "cierre": "2026-12-31"},
    ]
    result = sync_proyectos(proyectos_cultiva)
    # Output: Sync completado: 4 creados, 2 actualizados
Operaciones API cubiertas
🗄️
Database Query
Consulta con filtros por estado, paginación automática con cursor hasta agotar resultados (max 100/página).
POST /databases/{id}/query
Create Page
Crea nueva fila en la BD con título, select, number y date. Soporta children blocks opcionales.
POST /pages
✏️
Update Page
Actualiza propiedades de páginas existentes via PATCH. Evita duplicados usando el índice por nombre.
PATCH /pages/{id}
📦
Append Blocks
Añade heading, párrafo, bullet list, to-do y code blocks al contenido de cada página de proyecto.
PATCH /blocks/{id}/children
🔎
Search
Busca páginas y bases de datos en todo el workspace. Filtro por tipo: "page" o "database".
POST /search
🔄
Rate Limit Handler
Backoff exponencial en 429 con Retry-After header. Hasta 5 reintentos antes de lanzar excepción.
429 → sleep(2^n)