Implementación inicial del sistema de automatización de redes sociales

- Estructura completa del proyecto con FastAPI
- Modelos de base de datos (productos, servicios, posts, calendario, interacciones)
- Publishers para X, Threads, Instagram, Facebook
- Generador de contenido con DeepSeek API
- Worker de Celery con tareas programadas
- Dashboard básico con templates HTML
- Docker Compose para despliegue
- Documentación completa

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 01:11:44 +00:00
commit 049d2133f9
53 changed files with 5876 additions and 0 deletions

1
app/core/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Core module

60
app/core/config.py Normal file
View File

@@ -0,0 +1,60 @@
"""
Configuración central del sistema.
Carga variables de entorno y define settings globales.
"""
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
"""Configuración de la aplicación."""
# Aplicación
APP_NAME: str = "social-media-automation"
APP_ENV: str = "development"
DEBUG: bool = True
SECRET_KEY: str = "change-this-in-production"
# Base de datos
DATABASE_URL: str = "postgresql://social_user:social_pass@localhost:5432/social_automation"
# Redis
REDIS_URL: str = "redis://localhost:6379/0"
# DeepSeek API
DEEPSEEK_API_KEY: Optional[str] = None
DEEPSEEK_BASE_URL: str = "https://api.deepseek.com/v1"
# X (Twitter) API
X_API_KEY: Optional[str] = None
X_API_SECRET: Optional[str] = None
X_ACCESS_TOKEN: Optional[str] = None
X_ACCESS_TOKEN_SECRET: Optional[str] = None
X_BEARER_TOKEN: Optional[str] = None
# Meta API (Facebook, Instagram, Threads)
META_APP_ID: Optional[str] = None
META_APP_SECRET: Optional[str] = None
META_ACCESS_TOKEN: Optional[str] = None
FACEBOOK_PAGE_ID: Optional[str] = None
INSTAGRAM_ACCOUNT_ID: Optional[str] = None
THREADS_USER_ID: Optional[str] = None
# Información del negocio
BUSINESS_NAME: str = "Consultoría AS"
BUSINESS_LOCATION: str = "Tijuana, México"
BUSINESS_WEBSITE: str = "https://consultoria-as.com"
CONTENT_TONE: str = "profesional pero cercano, educativo, orientado a soluciones"
# Notificaciones
TELEGRAM_BOT_TOKEN: Optional[str] = None
TELEGRAM_CHAT_ID: Optional[str] = None
class Config:
env_file = ".env"
case_sensitive = True
# Instancia global de configuración
settings = Settings()

31
app/core/database.py Normal file
View File

@@ -0,0 +1,31 @@
"""
Configuración de la base de datos PostgreSQL.
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from app.core.config import settings
# Crear engine de SQLAlchemy
engine = create_engine(
settings.DATABASE_URL,
pool_pre_ping=True,
pool_size=10,
max_overflow=20
)
# Crear sesión
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Base para modelos
Base = declarative_base()
def get_db():
"""Dependency para obtener sesión de base de datos."""
db = SessionLocal()
try:
yield db
finally:
db.close()

78
app/core/security.py Normal file
View File

@@ -0,0 +1,78 @@
"""
Configuración de seguridad y autenticación.
"""
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from app.core.config import settings
# Configuración de hashing de contraseñas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configuración de JWT
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 horas
# Security scheme
security = HTTPBearer()
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verificar contraseña contra hash."""
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
"""Generar hash de contraseña."""
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""Crear token JWT."""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> dict:
"""Decodificar y validar token JWT."""
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido o expirado",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
"""Obtener usuario actual desde el token."""
token = credentials.credentials
payload = decode_token(token)
username = payload.get("sub")
if username is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido"
)
return {"username": username}