""" API endpoints para generación de contenido con IA. """ from typing import Optional, List from fastapi import APIRouter, HTTPException from pydantic import BaseModel from app.services.content_generator import content_generator from app.core.config import settings router = APIRouter() # ============================================================ # Schemas # ============================================================ class GenerateTipRequest(BaseModel): """Solicitud para generar tip tech.""" category: str = "general" platform: str = "x" template: Optional[str] = None class GenerateProductPostRequest(BaseModel): """Solicitud para generar post de producto.""" product: dict # name, description, price, category, specs, highlights platform: str = "x" class GenerateServicePostRequest(BaseModel): """Solicitud para generar post de servicio.""" service: dict # name, description, category, target_sectors, benefits, call_to_action platform: str = "x" class GenerateThreadRequest(BaseModel): """Solicitud para generar hilo educativo.""" topic: str num_posts: int = 5 class GenerateResponseRequest(BaseModel): """Solicitud para generar sugerencias de respuesta.""" interaction_content: str interaction_type: str = "comment" # comment, mention, dm context: Optional[str] = None class AdaptContentRequest(BaseModel): """Solicitud para adaptar contenido a plataforma.""" content: str target_platform: str class ImproveContentRequest(BaseModel): """Solicitud para mejorar contenido existente.""" content: str platform: str = "x" style: str = "engaging" # engaging, professional, casual, educational class GenerateResponse(BaseModel): """Respuesta de generación.""" success: bool content: Optional[str] = None contents: Optional[List[str]] = None error: Optional[str] = None tokens_used: Optional[int] = None # ============================================================ # Helper # ============================================================ def check_api_configured(): """Verificar que la API de DeepSeek esté configurada.""" if not settings.DEEPSEEK_API_KEY: raise HTTPException( status_code=503, detail="DeepSeek API no configurada. Agrega DEEPSEEK_API_KEY en .env" ) # ============================================================ # Endpoints # ============================================================ @router.post("/tip", response_model=GenerateResponse) async def generate_tip(request: GenerateTipRequest): """ Generar un tip de tecnología. Categorías sugeridas: - productividad, seguridad, ia, programacion, hardware, - redes, cloud, automatizacion, impresion3d, general """ check_api_configured() try: content = await content_generator.generate_tip_tech( category=request.category, platform=request.platform, template=request.template ) return GenerateResponse( success=True, content=content ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/product", response_model=GenerateResponse) async def generate_product_post(request: GenerateProductPostRequest): """ Generar post promocional de producto. Campos del producto: - name (requerido): Nombre del producto - description: Descripción - price (requerido): Precio en MXN - category: Categoría (computadoras, impresoras3d, accesorios) - specs: Dict con especificaciones técnicas - highlights: Lista de puntos destacados """ check_api_configured() # Validar campos mínimos if "name" not in request.product or "price" not in request.product: raise HTTPException( status_code=400, detail="El producto debe tener 'name' y 'price'" ) try: content = await content_generator.generate_product_post( product=request.product, platform=request.platform ) return GenerateResponse( success=True, content=content ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/service", response_model=GenerateResponse) async def generate_service_post(request: GenerateServicePostRequest): """ Generar post promocional de servicio. Campos del servicio: - name (requerido): Nombre del servicio - description: Descripción - category: Categoría (consultoria, desarrollo, soporte, capacitacion) - target_sectors: Lista de sectores objetivo - benefits: Lista de beneficios - call_to_action: CTA personalizado """ check_api_configured() if "name" not in request.service: raise HTTPException( status_code=400, detail="El servicio debe tener 'name'" ) try: content = await content_generator.generate_service_post( service=request.service, platform=request.platform ) return GenerateResponse( success=True, content=content ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/thread", response_model=GenerateResponse) async def generate_thread(request: GenerateThreadRequest): """ Generar hilo educativo. - topic: Tema del hilo (ej: "5 tips para proteger tu PC") - num_posts: Cantidad de posts en el hilo (3-10) """ check_api_configured() if request.num_posts < 3 or request.num_posts > 10: raise HTTPException( status_code=400, detail="num_posts debe estar entre 3 y 10" ) try: posts = await content_generator.generate_thread( topic=request.topic, num_posts=request.num_posts ) return GenerateResponse( success=True, contents=posts ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/response", response_model=GenerateResponse) async def generate_response_suggestions(request: GenerateResponseRequest): """ Generar sugerencias de respuesta para una interacción. Devuelve 3 opciones de respuesta diferentes. """ check_api_configured() try: suggestions = await content_generator.generate_response_suggestion( interaction_content=request.interaction_content, interaction_type=request.interaction_type, context=request.context ) return GenerateResponse( success=True, contents=suggestions ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/adapt", response_model=GenerateResponse) async def adapt_content(request: AdaptContentRequest): """ Adaptar contenido existente a una plataforma específica. Útil para tomar contenido de una plataforma y adaptarlo a los límites y estilo de otra. """ check_api_configured() valid_platforms = ["x", "threads", "instagram", "facebook"] if request.target_platform.lower() not in valid_platforms: raise HTTPException( status_code=400, detail=f"Plataforma no válida. Opciones: {valid_platforms}" ) try: content = await content_generator.adapt_content_for_platform( content=request.content, target_platform=request.target_platform.lower() ) return GenerateResponse( success=True, content=content ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.post("/improve", response_model=GenerateResponse) async def improve_content(request: ImproveContentRequest): """ Mejorar contenido existente. Estilos disponibles: - engaging: Más atractivo y con gancho - professional: Más formal y corporativo - casual: Más cercano y amigable - educational: Más informativo y didáctico """ check_api_configured() try: # Usar el generador para mejorar from openai import OpenAI client = OpenAI( api_key=settings.DEEPSEEK_API_KEY, base_url=settings.DEEPSEEK_BASE_URL ) style_prompts = { "engaging": "Hazlo más atractivo, con un gancho inicial que capture atención", "professional": "Hazlo más profesional y corporativo, manteniendo credibilidad", "casual": "Hazlo más cercano y amigable, como si hablaras con un amigo", "educational": "Hazlo más informativo y didáctico, que enseñe algo útil" } char_limits = { "x": 280, "threads": 500, "instagram": 2200, "facebook": 1000 } prompt = f"""Mejora este contenido para {request.platform}: CONTENIDO ORIGINAL: {request.content} ESTILO DESEADO: {style_prompts.get(request.style, style_prompts['engaging'])} LÍMITE DE CARACTERES: {char_limits.get(request.platform, 500)} REQUISITOS: - Mantén el mensaje principal - Mejora la redacción y el impacto - Incluye emojis relevantes - Termina con hashtags apropiados Responde SOLO con el contenido mejorado.""" response = client.chat.completions.create( model="deepseek-chat", messages=[ {"role": "system", "content": content_generator._get_system_prompt()}, {"role": "user", "content": prompt} ], max_tokens=400, temperature=0.7 ) improved = response.choices[0].message.content.strip() return GenerateResponse( success=True, content=improved, tokens_used=response.usage.total_tokens ) except Exception as e: return GenerateResponse( success=False, error=str(e) ) @router.get("/categories") async def get_tip_categories(): """Obtener categorías disponibles para tips.""" return { "categories": [ {"id": "productividad", "name": "Productividad", "description": "Tips para ser más eficiente"}, {"id": "seguridad", "name": "Seguridad", "description": "Ciberseguridad y protección"}, {"id": "ia", "name": "Inteligencia Artificial", "description": "IA y machine learning"}, {"id": "programacion", "name": "Programación", "description": "Desarrollo de software"}, {"id": "hardware", "name": "Hardware", "description": "Equipos y componentes"}, {"id": "redes", "name": "Redes", "description": "Networking y conectividad"}, {"id": "cloud", "name": "Cloud", "description": "Servicios en la nube"}, {"id": "automatizacion", "name": "Automatización", "description": "Automatizar procesos"}, {"id": "impresion3d", "name": "Impresión 3D", "description": "Impresoras y diseño 3D"}, {"id": "general", "name": "General", "description": "Tips variados de tecnología"}, ] } class BatchGenerateRequest(BaseModel): """Solicitud de generación por lotes.""" platforms: List[str] days: int = 7 # 7 para semana, 30 para mes tip_categories: Optional[List[str]] = None @router.post("/batch") async def generate_batch(request: BatchGenerateRequest): """ Generar contenido por lotes para el calendario. - **platforms**: Lista de plataformas ["x", "threads", "instagram"] - **days**: Cantidad de días (7 para semana, 30 para mes) - **tip_categories**: Categorías de tips a usar """ check_api_configured() if request.days < 1 or request.days > 30: raise HTTPException( status_code=400, detail="days debe estar entre 1 y 30" ) from app.services.batch_generator import batch_generator try: result = await batch_generator._generate_batch( platforms=request.platforms, start_date=None, # Usa mañana por defecto days=request.days, tip_categories=request.tip_categories ) return { "success": result.success, "total_requested": result.total_requested, "total_generated": result.total_generated, "posts": [ { "content": p.content, "content_type": p.content_type.value, "platform": p.platform, "scheduled_at": p.scheduled_at.isoformat(), "metadata": p.metadata } for p in result.posts ], "errors": result.errors } except Exception as e: raise HTTPException( status_code=500, detail=str(e) ) @router.get("/batch/estimate") async def estimate_batch_cost( platforms: str, # comma-separated days: int = 7 ): """ Estimar costo de generación por lotes. Calcula tokens y costo estimado sin generar contenido. """ from app.services.batch_generator import batch_generator platform_list = [p.strip() for p in platforms.split(",")] estimate = batch_generator.calculate_costs(platform_list, days) return estimate @router.post("/tips/batch") async def generate_tips_batch( count: int = 10, platform: str = "x", categories: Optional[str] = None # comma-separated ): """ Generar múltiples tips de una vez. - **count**: Cantidad de tips (1-20) - **platform**: Plataforma destino - **categories**: Categorías separadas por coma """ check_api_configured() if count < 1 or count > 20: raise HTTPException( status_code=400, detail="count debe estar entre 1 y 20" ) from app.services.batch_generator import batch_generator category_list = None if categories: category_list = [c.strip() for c in categories.split(",")] try: tips = await batch_generator.generate_tips_batch( count=count, platform=platform, categories=category_list ) return { "success": True, "count": len(tips), "tips": tips } except Exception as e: return { "success": False, "error": str(e), "tips": [] } @router.get("/status") async def get_ai_status(): """Verificar estado de la API de IA.""" configured = bool(settings.DEEPSEEK_API_KEY) result = { "configured": configured, "provider": "DeepSeek", "model": "deepseek-chat", "base_url": settings.DEEPSEEK_BASE_URL } if configured: try: # Test rápido from openai import OpenAI client = OpenAI( api_key=settings.DEEPSEEK_API_KEY, base_url=settings.DEEPSEEK_BASE_URL ) response = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": "test"}], max_tokens=5 ) result["status"] = "connected" result["test"] = "OK" except Exception as e: result["status"] = "error" result["error"] = str(e) else: result["status"] = "not_configured" return result