Blog

Gestión de Comentarios en Redes Sociales a Gran Escala: Guía Multiplataforma

Descubre cómo crear una moderación unificada de comentarios en redes sociales en más de 8 plataformas con ejemplos en TypeScript, análisis de sentimiento con IA

Por

+8

Publica en todo. Una API.

Try Free

Gestionar los comentarios en múltiples plataformas sociales se ha convertido en uno de los desafíos más exigentes tanto para las marcas como para los desarrolladores. Efectivo moderación de comentarios en redes sociales requiere gestionar APIs, formatos de datos y límites de tasa muy diferentes, manteniendo tiempos de respuesta consistentes para tu comunidad. Esta guía te lleva a través de la construcción de un sistema listo para producción que agrega, normaliza y procesa comentarios de Facebook, Instagram, Twitter, YouTube, LinkedIn, Reddit, Bluesky y Threads.

Al final de este artículo, tendrás un código TypeScript funcional para un sistema de gestión de comentarios unificado que escala a millones de interacciones al día.

El reto de los comentarios en múltiples plataformas

Cada plataforma social aborda los comentarios de manera diferente. Facebook anida las respuestas bajo los comentarios principales con tokens de paginación. Instagram vincula los comentarios a objetos multimedia con límites de tasa estrictos. YouTube utiliza una API completamente separada de los otros servicios de Google. Twitter (X) trata las respuestas como publicaciones regulares en un hilo de conversación.

Estas diferencias generan tres problemas fundamentales:

Inconsistencia de datosCada plataforma devuelve diferentes campos, formatea las marcas de tiempo de manera distinta y estructura los datos de usuario de formas únicas. Un "comentario" en YouTube no se parece en nada a una "respuesta" en Twitter en términos de respuesta de la API.

Complejidad del límite de tasaFacebook permite 200 llamadas por hora por token de usuario. La API v2 de Twitter tiene acceso escalonado con diferentes límites. YouTube aplica unidades de cuota en lugar de simples conteos de solicitudes. Gestionar estos límites entre plataformas requiere un control de flujo sofisticado.

Expectativas en tiempo realLos usuarios esperan respuestas casi instantáneas. Cuando alguien comenta en tu publicación de Instagram, no les importa que también estés supervisando otras siete plataformas. Tu sistema necesita agregar comentarios rápidamente sin alcanzar los límites de tasa.

Aquí está la realidad con la que te enfrentas:

PlatformEstilo de APILímites de tasaEstructura de Comentarios
FacebookAPI de Gráficos200 llamadas/hora/usuarioAnidado con paginación
InstagramAPI de Gráficos200 llamadas/hora/usuarioPlano, adjunto a medios
Twitter/XREST + StreamingBasado en niveles (10K-1M/mes)Hilos de conversación
YouTubeAPI de Datos v310,000 unidades de cuota/díaCon hilos de respuestas
LinkedInAPI REST100 llamadas/día (varía)Solo para organizaciones
RedditOAuth REST60 solicitudes/minutoÁrboles profundamente anidados
BlueskyProtocolo AT3.000 puntos/5 minPlano con referencias de respuesta
ThreadsAPI de GráficosCompartido con InstagramSimilar a Instagram

Comparativa de Plataformas: Características y Limitaciones

Antes de escribir cualquier código, es fundamental comprender qué soporta realmente cada plataforma. No todas las plataformas exponen los datos de comentarios a través de su API, y algunas tienen restricciones significativas.

type InboxFeature = 'mensajes' | 'comentarios' | 'reseñas';

const INBOX_PLATFORMS = {
  mensajes: ['facebook', 'instagram', 'twitter', 'bluesky', 'reddit', 'telegram'] as const,
  // TikTok y Pinterest excluidos: sus APIs no soportan la lectura de comentarios
  comentarios: ['facebook', 'instagram', 'twitter', 'bluesky', 'threads', 'youtube', 'linkedin', 'reddit'] as const,
  reseñas: ['facebook', 'googlebusiness'] as const,
} as const;

type CommentsPlatform = (typeof INBOX_PLATFORMS.comentarios)[number];

function isPlatformSupported(platform: string, feature: InboxFeature): boolean {
  return (INBOX_PLATFORMS[feature] as readonly string[]).includes(platform);
}

function validatePlatformSupport(
  platform: string,
  feature: InboxFeature
): { valid: true } | { valid: false; error: string; supportedPlatforms: readonly string[] } {
  if (!isPlatformSupported(platform, feature)) {
    const featureLabel = feature === 'mensajes' ? 'mensajes directos' : feature;
    return {
      valid: false,
      error: `La plataforma '${platform}' no soporta ${featureLabel}`,
      supportedPlatforms: INBOX_PLATFORMS[feature],
    };
  }
  return { valid: true };
}

Nota: TikTok y Pinterest carecen notablemente de soporte para comentarios. Sus APIs no exponen datos de comentarios para lectura, solo para publicación en escenarios limitados. Planifica tu estrategia multiplataforma en consecuencia.

LinkedIn presenta un caso especial. La plataforma solo permite el acceso a comentarios para páginas de organizaciones (empresas), no para perfiles personales. Tu sistema necesita validar esto:

function isLinkedInOrgAccount(
  metadata: Map | Record | null | undefined
): boolean {
  if (!metadata) return false;

  // Manejar Mongoose Map (común en esquemas de MongoDB)
  if (metadata instanceof Map) {
    return metadata.get('accountType') === 'organization' || metadata.has('selectedOrganization');
  }

  // Manejar objeto simple
  return metadata.accountType === 'organization' || 'selectedOrganization' in metadata;
}

function filterAccountsForFeature(
  accounts: SocialAccount[],
  feature: InboxFeature
): SocialAccount[] {
  const supportedPlatforms = INBOX_PLATFORMS[feature] as readonly string[];

  return accounts.filter((account) => {
    if (!supportedPlatforms.includes(account.platform)) {
      return false;
    }

    // LinkedIn requiere tipo de cuenta de organización para funciones de bandeja de entrada
    if (account.platform === 'linkedin' && !isLinkedInOrgAccount(account.metadata)) {
      return false;
    }

    return true;
  });
}

Modelo de Datos de Comentarios Unificado

La base de una comunicación efectiva gestión de comentarios en múltiples plataformas es un modelo de datos normalizado. La estructura de comentarios de cada plataforma debe mapearse a una única interfaz con la que la lógica de tu aplicación pueda trabajar de manera coherente.

interface UnifiedComment {
  // Identificación
  id: string;                          // Único dentro de tu sistema
  platformId: string;                  // ID original de la plataforma
  platform: CommentsPlatform;
  accountId: string;                   // Cuenta conectada
  
  // Contenido
  text: string;
  textHtml?: string;                   // Texto enriquecido si está disponible
  attachments: CommentAttachment[];
  
  // Autor
  author: {
    id: string;
    username: string;
    displayName: string;
    avatarUrl?: string;
    profileUrl?: string;
    isVerified: boolean;
  };
  
  // Contexto
  postId: string;                      // Publicación/video/media padre
  parentCommentId?: string;            // Para respuestas anidadas
  threadId?: string;                   // Hilo de conversación
  
  // Metadatos
  createdAt: Date;
  updatedAt?: Date;
  likeCount: number;
  replyCount: number;
  
  // Moderación
  status: 'pending' | 'approved' | 'hidden' | 'deleted';
  sentiment?: 'positive' | 'neutral' | 'negative';
  sentimentScore?: number;             // -1 a 1
  flags: string[];                     // spam, ofensivo, etc.
  
  // Datos específicos de la plataforma (salida de emergencia)
  rawData?: Record;
}

interface CommentAttachment {
  type: 'image' | 'video' | 'link' | 'sticker' | 'gif';
  url: string;
  thumbnailUrl?: string;
  width?: number;
  height?: number;
}

Este modelo captura los campos esenciales mientras preserva los datos específicos de la plataforma en rawData para casos extremos. El status and sentiment los campos soportan flujos de trabajo de moderación que construiremos más adelante.

Agregando Comentarios en Todas las Plataformas

Con tu modelo de datos definido, necesitas una infraestructura que te permita obtener comentarios de múltiples cuentas de manera simultánea. Los principales desafíos son gestionar los fallos de forma eficiente (que una plataforma esté caída no debería afectar al resto) y manejar los tiempos de espera.

interface AggregationError {
  accountId: string;
  accountUsername?: string;
  platform: string;
  error: string;
  code?: string;
  retryAfter?: number;
}

interface AggregatedResult {
  items: T[];
  errors: AggregationError[];
}

async function aggregateFromAccounts(
  accounts: SocialAccount[],
  fetcher: (account: SocialAccount) => Promise,
  options?: { timeout?: number }
): Promise> {
  const timeout = options?.timeout || 10000; // 10 segundos por defecto
  const results: T[] = [];
  const errors: AggregationError[] = [];

  const fetchPromises = accounts.map(async (account) => {
    try {
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Tiempo de espera agotado')), timeout);
      });

      const items = await Promise.race([fetcher(account), timeoutPromise]);
      return { account, items, error: null };
    } catch (error: unknown) {
      const err = error as Error & { code?: string; retryAfter?: number };
      return {
        account,
        items: [] as T[],
        error: {
          accountId: account._id?.toString() || account.id,
          accountUsername: account.username,
          platform: account.platform,
          error: err.message || 'Error desconocido',
          code: err.code,
          retryAfter: err.retryAfter,
        },
      };
    }
  });

  const settledResults = await Promise.all(fetchPromises);

  for (const result of settledResults) {
    if (result.error) {
      errors.push(result.error);
    } else {
      results.push(...result.items);
    }
  }

  return { items: results, errors };
}

Este patrón permite un éxito parcial. Si tu token de Facebook expira pero Twitter funciona correctamente, seguirás recibiendo comentarios de Twitter mientras registras el error de Facebook para reintentar.

Estrategias de Normalización de Comentarios

Cada plataforma devuelve comentarios en diferentes formatos. Necesitas funciones transformadoras que conviertan las respuestas específicas de cada plataforma en tu modelo unificado. Así es como debes estructurar estos normalizadores:

type CommentNormalizer = (
  rawComment: unknown,
  account: SocialAccount,
  postId: string
) => UnifiedComment;

const normalizers: Record = {
  facebook: normalizeFacebookComment,
  instagram: normalizeInstagramComment,
  twitter: normalizeTwitterComment,
  youtube: normalizeYouTubeComment,
  linkedin: normalizeLinkedInComment,
  reddit: normalizeRedditComment,
  bluesky: normalizeBlueskyComment,
  threads: normalizeThreadsComment,
};

function normalizeFacebookComment(
  raw: FacebookCommentResponse,
  account: SocialAccount,
  postId: string
): UnifiedComment {
  return {
    id: `fb_${raw.id}`,
    platformId: raw.id,
    platform: 'facebook',
    accountId: account._id.toString(),
    text: raw.message || '',
    attachments: raw.attachment ? [{
      type: mapFacebookAttachmentType(raw.attachment.type),
      url: raw.attachment.url,
      thumbnailUrl: raw.attachment.media?.image?.src,
    }] : [],
    author: {
      id: raw.from.id,
      username: raw.from.id, // Facebook no expone el nombre de usuario
      displayName: raw.from.name,
      avatarUrl: `https://graph.facebook.com/${raw.from.id}/picture`,
      profileUrl: `https://facebook.com/${raw.from.id}`,
      isVerified: false, // No disponible en la respuesta básica
    },
    postId,
    parentCommentId: raw.parent?.id ? `fb_${raw.parent.id}` : undefined,
    createdAt: new Date(raw.created_time),
    likeCount: raw.like_count || 0,
    replyCount: raw.comment_count || 0,
    status: raw.is_hidden ? 'hidden' : 'approved',
    flags: [],
    rawData: raw,
  };
}

function normalizeTwitterComment(
  raw: TwitterTweetResponse,
  account: SocialAccount,
  postId: string
): UnifiedComment {
  const author = raw.includes?.users?.find(u => u.id === raw.data.author_id);
  
  return {
    id: `tw_${raw.data.id}`,
    platformId: raw.data.id,
    platform: 'twitter',
    accountId: account._id.toString(),
    text: raw.data.text,
    attachments: extractTwitterAttachments(raw.data, raw.includes),
    author: {
      id: raw.data

El patrón de normalización mantiene la lógica específica de cada plataforma aislada. Cuando Twitter cambia su API (lo cual sucede con frecuencia), solo necesitas actualizar una función.

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

Creando Flujos de Trabajo de Moderación

La agregación de comentarios en bruto es solo el comienzo. Efectivo moderación de comentarios en redes sociales requiere flujos de trabajo para revisar, responder y actuar sobre los comentarios. Aquí tienes un enfoque de máquina de estados:

type ModerationAction = 
  | 'aprobar'
  | 'ocultar'
  | 'eliminar'
  | 'responder'
  | 'marcar'
  | 'escalar'
  | 'asignar';

interface ModerationRule {
  id: string;
  name: string;
  conditions: RuleCondition[];
  actions: ModerationAction[];
  priority: number;
  enabled: boolean;
}

interface RuleCondition {
  field: keyof UnifiedComment | 'author.isVerified' | 'sentiment';
  operator: 'igual' | 'contiene' | 'coincide' | 'mayor' | 'menor';
  value: string | number | boolean | RegExp;
}

class ModerationEngine {
  private rules: ModerationRule[] = [];

  addRule(rule: ModerationRule): void {
    this.rules.push(rule);
    this.rules.sort((a, b) => b.priority - a.priority);
  }

  async processComment(comment: UnifiedComment): Promise<{
    actions: ModerationAction[];
    matchedRules: string[];
  }> {
    const matchedRules: string[] = [];
    const actions: Set = new Set();

    for (const rule of this.rules) {
      if (!rule.enabled) continue;

      const matches = this.evaluateConditions(comment, rule.conditions);
      if (matches) {
        matchedRules.push(rule.id);
        rule.actions.forEach(action => actions.add(action));
      }
    }

    return {
      actions: Array.from(actions),
      matchedRules,
    };
  }

  private evaluateConditions(
    comment: UnifiedComment,
    conditions: RuleCondition[]
  ): boolean {
    return conditions.every(condition => {
      const value = this.getFieldValue(comment, condition.field);
      return this.evaluateCondition(value, condition.operator, condition.value);
    });
  }

  private getFieldValue(comment: UnifiedComment, field: string): unknown {
    const parts = field.split('.');
    let value: unknown = comment;
    for (const part of parts) {
      if (value && typeof value === 'object') {
        value = (value as Record)[part];
      } else {
        return undefined;
      }
    }
    return value;
  }

  private evaluateCondition(
    fieldValue: unknown,
    operator: RuleCondition['operator'],
    ruleValue: RuleCondition['value']
  ): boolean {

Ejemplos de reglas que podrías configurar:

const moderationEngine = new ModerationEngine();

// Ocultar automáticamente comentarios con lenguaje ofensivo
moderationEngine.addRule({
  id: 'filtro-ofensivo',
  name: 'Ocultar Ofensivo',
  conditions: [
    { field: 'text', operator: 'matches', value: /\b(badword1|badword2)\b/i }
  ],
  actions: ['hide', 'flag'],
  priority: 100,
  enabled: true,
});

// Escalar sentimientos negativos de usuarios verificados
moderationEngine.addRule({
  id: 'verificado-negativo',
  name: 'Escalar Negativo Verificado',
  conditions: [
    { field: 'author.isVerified', operator: 'equals', value: true },
    { field: 'sentiment', operator: 'equals', value: 'negative' }
  ],
  actions: ['escalate'],
  priority: 90,
  enabled: true,
});

// Aprobar automáticamente comentarios de usuarios frecuentes
moderationEngine.addRule({
  id: 'comentador-confiable',
  name: 'Aprobar Automáticamente Confiable',
  conditions: [
    { field: 'author.id', operator: 'contains', value: 'trusted_list' }
  ],
  actions: ['approve'],
  priority: 80,
  enabled: true,
});

Análisis de Sentimientos Potenciado por IA

Modern API de moderación de comentarios las implementaciones aprovechan la IA para el análisis de sentimientos y la detección de toxicidad. Puedes integrar OpenAI u otros proveedores para clasificar automáticamente los comentarios:

interface SentimentResult {
  sentimiento: 'positivo' | 'neutral' | 'negativo';
  puntuación: number;        // -1 a 1
  confianza: number;        // 0 a 1
  toxicidad?: number;       // 0 a 1
  categorías?: string[];    // spam, odio, acoso, etc.
}

async function analizarSentimientoComentario(
  comentario: UnifiedComment,
  openaiApiKey: string
): Promise {
  const respuesta = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${openaiApiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content: `Analiza el sentimiento de los comentarios en redes sociales. Devuelve JSON con:
- sentimiento: "positivo", "neutral" o "negativo"
- puntuación: número de -1 (muy negativo) a 1 (muy positivo)
- confianza: número de 0 a 1
- toxicidad: número de 0 a 1 (0 = no tóxico, 1 = muy tóxico)
- categorías: array de etiquetas aplicables de [spam, odio, acoso, amenaza, autolesión, sexual, ninguno]`
        },
        {
          role: 'user',
          content: `Analiza este comentario:\n\n"${comentario.text}"\n\nContexto: Este es un comentario de ${comentario.platform} en la publicación de una marca.`
        }
      ],
      response_format: { type: 'json_object' },
      temperature: 0.1,
    }),
  });

  if (!respuesta.ok) {
    throw new Error(`Error en la API de OpenAI: ${respuesta.status}`);
  }

  const datos = await respuesta.json();
  const resultado = JSON.parse(datos.choices[0].message.content);

  return {
    sentimiento: resultado.sentiment,
    puntuación: resultado.score,
    confianza: resultado.confidence,
    toxicidad: resultado.toxicity,
    categorías: resultado.categories?.filter((c: string) => c !== 'none'),
  };
}

// Procesamiento por lotes para mayor eficiencia
async function

Nota: Para escenarios de alto volumen, considera utilizar APIs de moderación dedicadas como Perspective API (gratuita para un uso moderado) o AWS Comprehend, que están optimizadas para este caso de uso y son más rentables que GPT-4 para clasificaciones simples.

Arquitectura de Procesamiento Basada en Colas

To automatiza los comentarios en redes sociales A gran escala, necesitas procesamiento asíncrono. Una arquitectura basada en colas desacopla la ingesta de comentarios del procesamiento, lo que te permite manejar picos de tráfico de manera eficiente.

interface CommentJob {
  type: 'fetch' | 'analyze' | 'moderate' | 'respond';
  commentId?: string;
  accountId: string;
  platform: CommentsPlatform;
  payload: Record;
  attempts: number;
  maxAttempts: number;
  createdAt: Date;
  scheduledFor: Date;
}

class CommentProcessingQueue {
  private queue: CommentJob[] = [];
  private processing: boolean = false;
  private concurrency: number = 5;

  async enqueue(job: Omit): Promise {
    this.queue.push({
      ...job,
      attempts: 0,
      createdAt: new Date(),
    });
    
    this.queue.sort((a, b) => 
      a.scheduledFor.getTime() - b.scheduledFor.getTime()
    );

    if (!this.processing) {
      this.processQueue();
    }
  }

  private async processQueue(): Promise {
    this.processing = true;

    while (this.queue.length > 0) {
      const now = new Date();
      const readyJobs = this.queue.filter(j => j.scheduledFor <= now);
      
      if (readyJobs.length === 0) {
        // Esperar al siguiente trabajo programado
        const nextJob = this.queue[0];
        const waitTime = nextJob.scheduledFor.getTime() - now.getTime();
        await new Promise(resolve => setTimeout(resolve, Math.min(waitTime, 1000)));
        continue;
      }

      // Procesar hasta el límite de concurrencia
      const batch = readyJobs.slice(0, this.concurrency);
      
      await Promise.all(batch.map(async (job) => {
        this.queue = this.queue.filter(j => j !== job);
        
        try {
          await this.processJob(job);
        } catch (error) {
          await this.handleJobError(job, error as Error);
        }
      }));
    }

    this.processing = false;
  }

  private async processJob(job: CommentJob): Promise {
    job.attempts++;

    switch (job.type) {
      case 'fetch':
        await this.handleFetchJob(job);
        break;
      case 'analyze':
        await this.handleAnalyzeJob(job);
        break;
      case 'moderate':
        await this.handleModerateJob(job);
        break;
      case '

Para sistemas de producción, reemplaza esta cola en memoria por Redis (utilizando BullMQ) o un servicio gestionado como AWS SQS.

Rendimiento a Gran Escala

Al procesar miles de comentarios por minuto, pequeñas ineficiencias se acumulan. Aquí tienes optimizaciones clave:

DeduplicationLos comentarios pueden aparecer en múltiples solicitudes, especialmente cuando se realiza un sondeo con frecuencia.

function deduplicateItems(
  items: T[],
  keyFn: (item: T) => string
): T[] {
  const seen = new Set();
  return items.filter((item) => {
    const key = keyFn(item);
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

// Uso
const uniqueComments = deduplicateItems(
  allComments,
  (comment) => `${comment.platform}_${comment.platformId}`
);

Paginación basada en cursorLa paginación por desplazamiento se vuelve lenta con grandes conjuntos de datos. Utiliza cursores que codifiquen la posición:

interface PaginationInfo {
  tieneMás: boolean;
  siguienteCursor: string | null;
  totalCount?: number;
}

function paginarConCursor<T extends { id?: string; _id?: unknown }>(
  elementos: T[],
  cursor: string | null,
  limite: number,
  obtenerTimestamp: (elemento: T) => string,
  obtenerIdCuenta: (elemento: T) => string
): { elementos: T[]; paginación: PaginationInfo } {
  let elementosFiltrados = elementos;

  // Formato del cursor: {timestamp}_{accountId}_{itemId}
  if (cursor) {
    const [timestampCursor, idCuentaCursor, idElementoCursor] = cursor.split('_');
    const tiempoCursor = new Date(timestampCursor).getTime();

    elementosFiltrados = elementos.filter((elemento) => {
      const tiempoElemento = new Date(obtenerTimestamp(elemento)).getTime();
      const idCuentaElemento = obtenerIdCuenta(elemento);
      const idElemento = elemento.id || String(elemento._id);

      if (tiempoElemento < tiempoCursor) return true;
      if (tiempoElemento === tiempoCursor) {
        if (idCuentaElemento > idCuentaCursor) return true;
        if (idCuentaElemento === idCuentaCursor && idElemento > idElementoCursor) return true;
      }
      return false;
    });
  }

  const elementosPaginados = elementosFiltrados.slice(0, limite);
  const tieneMás = elementosFiltrados.length > limite;

  let siguienteCursor: string | null = null;
  if (tieneMás && elementosPaginados.length > 0) {
    const últimoElemento = elementosPaginados[elementosPaginados.length - 1];
    siguienteCursor = `${obtenerTimestamp(últimoElemento)}_${obtenerIdCuenta(últimoElemento)}_${últimoElemento.id || últimoElemento._id}`;
  }

  return {
    elementos: elementosPaginados,
    paginación: { tieneMás, siguienteCursor },
  };
}

Clasificación eficientePreordenar por campos comunes y mantener índices:

function sortItems(
  items: T[],
  sortBy: string,
  sortOrder: 'asc' | 'desc',
  fieldMap: Record unknown>
): T[] {
  const getter = fieldMap[sortBy];
  if (!getter) return items;

  return [...items].sort((a, b) => {
    const aVal = getter(a);
    const bVal = getter(b);

    // Manejar fechas
    if (aVal instanceof Date && bVal instanceof Date) {
      return sortOrder === 'asc'
        ? aVal.getTime() - bVal.getTime()
        : bVal.getTime() - aVal.getTime();
    }

    // Manejar números
    if (typeof aVal === 'number' && typeof bVal === 'number') {
      return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
    }

    // Manejar cadenas
    if (typeof aVal === 'string' && typeof bVal === 'string') {
      return sortOrder === 'asc'
        ? aVal.localeCompare(bVal)
        : bVal.localeCompare(aVal);
    }

    return 0;
  });
}

Funciones de colaboración en equipo

La gestión de comentarios en empresas requiere flujos de trabajo en equipo. Varios moderadores deben colaborar sin pisarse los unos a los otros:

interface CommentAssignment {
  commentId: string;
  assignedTo: string;        // ID de usuario
  assignedBy: string;
  assignedAt: Date;
  status: 'pendiente' | 'en_progreso' | 'completado';
  notes?: string;
}

interface ModerationAuditLog {
  id: string;
  commentId: string;
  action: ModerationAction;
  performedBy: string;
  performedAt: Date;
  previousState: Partial;
  newState: Partial;
  reason?: string;
}

class TeamModerationService {
  async assignComment(
    commentId: string,
    assignTo: string,
    assignedBy: string
  ): Promise {
    // Comprobar si ya está asignado
    const existing = await this.getAssignment(commentId);
    if (existing && existing.status === 'en_progreso') {
      throw new Error(`Comentario ya asignado a ${existing.assignedTo}`);
    }

    const assignment: CommentAssignment = {
      commentId,
      assignedTo: assignTo,
      assignedBy,
      assignedAt: new Date(),
      status: 'pendiente',
    };

    await this.saveAssignment(assignment);
    return assignment;
  }

  async logAction(
    commentId: string,
    action: ModerationAction,
    userId: string,
    previousState: Partial,
    newState: Partial,
    reason?: string
  ): Promise {
    const log: ModerationAuditLog = {
      id: generateId(),
      commentId,
      action,
      performedBy: userId,
      performedAt: new Date(),
      previousState,
      newState,
      reason,
    };

    await this.saveAuditLog(log);
  }

  async getCommentHistory(commentId: string): Promise {
    return this.findAuditLogs({ commentId });
  }

  // Métodos abstractos para la implementación del almacenamiento
  protected async getAssignment(commentId: string): Promise {
    throw new Error('No implementado');
  }
  protected async saveAssignment(assignment: CommentAssignment): Promise {
    throw new Error('No implementado');
  }
  protected async saveAuditLog(log: ModerationAuditLog): Promise {
    throw new Error('No implementado');
  }

Uso de Late para la Gestión de Comentarios

Construir y mantener una infraestructura de comentarios multicanal es complicado. Debes gestionar ocho APIs diferentes, cada una con sus flujos de autenticación únicos, límites de tasa, formatos de datos y sistemas de webhook. Cuando las plataformas actualizan sus APIs (lo cual sucede constantemente), tu código se rompe.

Late ofrece una API unificada para la moderación de comentarios en redes sociales que gestiona toda esta complejidad. En lugar de crear integraciones separadas para Facebook, Instagram, Twitter, YouTube, LinkedIn, Reddit, Bluesky y Threads, utilizas un único endpoint.

Las funciones de gestión de comentarios de Late incluyen:

  • Bandeja de entrada unificadaTodos los comentarios de todas las plataformas en un formato normalizado.
  • Agregación en tiempo realComentarios obtenidos automáticamente con sondeo inteligente
  • Manejo de errores integradoLas caídas de la plataforma no afectan a tu sistema.
  • Colaboración en equipoAsignación, registros de auditoría y acceso basado en roles
  • Soporte de WebhooksRecibe notificaciones de nuevos comentarios al instante.

Así de sencillo es obtener comentarios con Late:

// En lugar de 8 integraciones de API diferentes...
const response = await fetch('https://api.getlate.dev/v1/inbox/comments', {
  headers: {
    'Authorization': `Bearer ${LATE_API_KEY}`,
  },
});

const { comments, pagination, meta } = await response.json();

// comments: UnifiedComment[] - ya normalizados
// pagination: { hasMore, nextCursor }
// meta: { accountsQueried, accountsFailed, failedAccounts }

Late se encarga de la complejidad específica de cada plataforma: actualización de tokens OAuth, gestión de límites de tasa, verificación de webhooks y normalización de datos. Tú te concentras en construir tus flujos de trabajo de moderación y la experiencia del usuario.

Echa un vistazo Documentación de Late para ver la referencia completa de la API de bandeja de entrada, incluyendo filtrado por plataforma, integración de análisis de sentimientos y acciones de moderación masiva.


La gestión de comentarios en múltiples plataformas es un problema resuelto si utilizas las herramientas adecuadas. Ya sea que construyas la infraestructura tú mismo siguiendo los patrones de esta guía o aproveches la API unificada de Late, la clave es diseñar para escalar desde el principio: modelos de datos normalizados, procesamiento asíncrono, manejo de errores elegante y flujos de trabajo amigables para el equipo.

Una API. 13+ plataformas.

Integra redes sociales en minutos, no semanas.

Diseñada para desarrolladores. Usada por agencias. Más de 6,325 usuarios.