import json import anthropic import config client = anthropic.Anthropic(api_key=config.ANTHROPIC_API_KEY) SYSTEM_PROMPT = """ Eres un agente experto en optimización de campañas de generación de leads para centros de formación. Cada campaña corresponde a un curso concreto con un PPL (precio por lead) fijo acordado con los centros compradores. MODELO DE NEGOCIO: - Ingreso = leads_entregados × PPL - Margen = (Ingreso - Gasto Google Ads) / Ingreso - El objetivo es maximizar leads dentro del capping mensual manteniendo margen positivo. - El CPA máximo ya refleja el margen mínimo aceptable. REGLAS DE DECISIÓN: 1. urgencia=PAUSAR → accion=PAUSAR siempre. El capping está lleno, seguir gastando destruye margen. 2. urgencia=SPRINT → accion=AUMENTAR_PRESUPUESTO con parametro entre 1.3 y 1.5. Quedan pocos días y leads por entregar. 3. urgencia=ACELERAR y campaña rentable → accion=AUMENTAR_PRESUPUESTO con parametro entre 1.1 y 1.25. 4. urgencia=ACELERAR y campaña NO rentable → accion=MANTENER o revisar keywords (no gastar más si no convierte). 5. urgencia=FRENAR → accion=REDUCIR_PRESUPUESTO con parametro entre 0.75 y 0.9. 6. urgencia=EN_RITMO y rentable → accion=MANTENER. 7. urgencia=EN_RITMO y NO rentable → accion=REDUCIR_PRESUPUESTO con parametro 0.85. 8. alerta_tracking=true → añadir alerta sobre discrepancia de tracking aunque la acción sea otra. Devuelve ÚNICAMENTE un JSON válido con esta estructura exacta, sin texto adicional ni markdown: { "accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER", "parametro": 1.0, "nuevo_budget_diario": 0.0, "justificacion": "explicación breve en español", "alerta": "texto si hay algo crítico, null si no hay", "confianza": 0.0 } El campo nuevo_budget_diario = budget_diario_actual × parametro (calcula tú el valor final). """ def decide(analysis: dict) -> dict: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=400, system=SYSTEM_PROMPT, messages=[{ "role": "user", "content": ( f"Analiza esta campaña y devuelve la decisión en JSON:\n\n" f"{json.dumps(analysis, ensure_ascii=False, indent=2)}" ) }] ) raw = response.content[0].text.strip() clean = raw.replace("```json", "").replace("```", "").strip() try: return json.loads(clean) except json.JSONDecodeError: # Fallback seguro si el modelo no devuelve JSON limpio return { "accion": "MANTENER", "parametro": 1.0, "nuevo_budget_diario": analysis.get("budget_diario_actual", 0), "justificacion": "Error parseando respuesta del agente. Revisión manual recomendada.", "alerta": f"JSON inválido recibido: {raw[:200]}", "confianza": 0.0, }