meta-optimizer-formacion/setup_airtable_meta_tables.py
José Manuel Gómez 9239e2f67f Initial scaffold: Meta Optimizer for RoiFormacion campaigns
Ports meta-optimizer's Meta Ads execution/approval/creative-analysis layer
(agent.py, meta_ads_client.py, baserow_client.py, slack_notifier.py,
approval_server.py) and replaces the per-vertical CPL model with the
PPL + monthly-capping-per-course model already used by leads-optimizer,
via a new airtable_client.py that shares Cursos/Familias/CentroCurso/
CursoMes/Leads Lake with that project and adds Meta Ads Campaigns /
MetaCampaignMes alongside its Google Ads Campaigns / GACampaignMes.
2026-07-07 16:53:03 +02:00

95 lines
3.6 KiB
Python

"""
One-time script: adds "Meta Ads Campaigns" and "MetaCampaignMes" tables to the
EXISTING Airtable base shared with leads-optimizer (AIRTABLE_BASE_ID), next to
"Google Ads Campaigns" / "GACampaignMes".
⚠️ This mutates a shared, already-in-production Airtable base used by
leads-optimizer. Run it deliberately, not as part of routine deploys.
Requires an Airtable Personal Access Token with the `schema.bases:write`
scope over that base (AIRTABLE_TOKEN in .env).
Usage:
python setup_airtable_meta_tables.py
"""
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.environ.get("AIRTABLE_TOKEN", "")
BASE_ID = os.environ.get("AIRTABLE_BASE_ID", "")
if not TOKEN or not BASE_ID:
print("Error: AIRTABLE_TOKEN and AIRTABLE_BASE_ID must be set in your .env file.")
sys.exit(1)
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
META_URL = f"https://api.airtable.com/v0/meta/bases/{BASE_ID}/tables"
def create_table(name: str, fields: list) -> dict:
resp = requests.post(META_URL, headers=HEADERS, json={"name": name, "fields": fields}, timeout=15)
if not resp.ok:
print(f" API error {resp.status_code}: {resp.text[:500]}")
resp.raise_for_status()
data = resp.json()
print(f" ✓ Table '{name}' created (id={data['id']})")
return data
existing = requests.get(META_URL, headers=HEADERS, timeout=15)
existing.raise_for_status()
existing_names = {t["name"] for t in existing.json().get("tables", [])}
if "Meta Ads Campaigns" in existing_names or "MetaCampaignMes" in existing_names:
print("Error: 'Meta Ads Campaigns' or 'MetaCampaignMes' already exist in this base. Aborting.")
sys.exit(1)
print(f"Creando tablas en la base {BASE_ID}...")
campaigns_table = create_table("Meta Ads Campaigns", [
{"name": "Campaign Name", "type": "singleLineText"},
{"name": "CampaignID", "type": "singleLineText"},
{"name": "CursoID Text", "type": "singleLineText"},
{
"name": "Status", "type": "singleSelect",
"options": {"choices": [{"name": "Activa"}, {"name": "Pausada"}]},
},
{"name": "PPL", "type": "number", "options": {"precision": 2}},
])
metacampaignmes_table = create_table("MetaCampaignMes", [
{"name": "Mes", "type": "singleLineText"},
{"name": "Año", "type": "singleLineText"},
{
"name": "CampaignID", "type": "multipleRecordLinks",
"options": {"linkedTableId": campaigns_table["id"]},
},
{"name": "PPL", "type": "number", "options": {"precision": 2}},
{"name": "CPAMax", "type": "number", "options": {"precision": 2}},
{"name": "CapTotalMes", "type": "number", "options": {"precision": 0}},
{"name": "CosteMes", "type": "number", "options": {"precision": 2}},
{"name": "ConvMes", "type": "number", "options": {"precision": 2}},
{
"name": "Status", "type": "singleSelect",
"options": {"choices": [{"name": "Activa"}, {"name": "Pausada"}]},
},
{"name": "Consejo", "type": "multilineText"},
{
"name": "Criticidad", "type": "singleSelect",
"options": {"choices": [{"name": "Crítico"}, {"name": "Peligro"}, {"name": "Mantener"}]},
},
{"name": "Log", "type": "multilineText"},
{"name": "ConvLeadsLakeMesFinal", "type": "number", "options": {"precision": 0}},
])
print(f"""
{'='*55}
Listo. En Airtable ya existen 'Meta Ads Campaigns' y 'MetaCampaignMes'.
No hace falta añadir nada a .env: AIRTABLE_TOKEN / AIRTABLE_BASE_ID
ya son los mismos que usa leads-optimizer.
{'='*55}
""")