Blog

Construyendo una Plataforma de Atención al Cliente en Redes Sociales

Aprende a construir una plataforma de atención al cliente en redes sociales con soporte omnicanal, webhooks en tiempo real y respuestas impulsadas por IA usando TypeScript.

Por

+8

Publica en todo. Una API.

Try Free

Construyendo una Plataforma de Atención al Cliente en Redes Sociales

Los clientes modernos esperan soporte donde sea que estén. Una plataforma robusta de atención al cliente en redes sociales debe agregar conversaciones de Facebook, Instagram, Twitter, LinkedIn y más en una única interfaz donde los equipos de soporte puedan responder rápida y consistentemente. Esta guía te lleva a través de la construcción de dicha plataforma desde cero, cubriendo decisiones de arquitectura, infraestructura en tiempo real, colaboración de equipos y asistencia impulsada por IA.

Al final de este artículo, tendrás código TypeScript funcional para una plataforma de soporte social al cliente que maneja agregación de mensajes multicanal, enrutamiento inteligente, seguimiento de SLA e integración perfecta con CRM.

La Necesidad de Atención al Cliente en Redes Sociales

Los clientes ya no esperan en espera ni envían correos electrónicos. Tuitean quejas, envían DM a marcas en Instagram y dejan comentarios en publicaciones de Facebook esperando respuestas rápidas. Las investigaciones muestran que el 40% de los consumidores espera una respuesta dentro de una hora en redes sociales, y el 79% espera una respuesta dentro de 24 horas.

Construir un helpdesk de redes sociales efectivo requiere resolver varios desafíos técnicos:

  • Fragmentación de canales: Cada plataforma tiene diferentes APIs, límites de tasa y formatos de datos
  • Capacidad de respuesta en tiempo real: Los clientes esperan reconocimiento inmediato
  • Coordinación de equipos: Múltiples agentes necesitan trabajar sin interferir entre sí
  • Preservación del contexto: Los agentes necesitan el historial del cliente en todos los canales
  • Seguimiento del rendimiento: La gerencia necesita visibilidad de los tiempos de respuesta y tasas de resolución

Una API de soporte omnicanal bien arquitecturada abstrae estas complejidades, dando a tu equipo una interfaz unificada independientemente de dónde se originen las conversaciones.

Visión General de la Arquitectura de la Plataforma

La arquitectura sigue un diseño modular que separa responsabilidades y escala de forma independiente:

Arquitectura de la Plataforma

// types/customer-service.ts
export interface SupportPlatformConfig {
  platforms: SupportedPlatform[];
  webhookEndpoint: string;
  routingStrategy: 'round-robin' | 'skill-based' | 'load-balanced';
  slaDefaults: SLAConfiguration;
  aiAssistEnabled: boolean;
}

export interface SupportedPlatform {
  name: 'facebook' | 'instagram' | 'twitter' | 'linkedin' | 'bluesky' | 'telegram';
  features: ('messages' | 'comments' | 'reviews')[];
  credentials: PlatformCredentials;
  webhookSecret?: string;
}

export interface Conversation {
  id: string;
  platform: string;
  accountId: string;
  customerId: string;
  customerName: string;
  customerHandle: string;
  customerAvatarUrl?: string;
  status: 'open' | 'pending' | 'resolved' | 'closed';
  priority: 'low' | 'medium' | 'high' | 'urgent';
  assignedTo?: string;
  tags: string[];
  messages: Message[];
  metadata: ConversationMetadata;
  createdAt: Date;
  updatedAt: Date;
  firstResponseAt?: Date;
  resolvedAt?: Date;
}

export interface Message {
  id: string;
  conversationId: string;
  direction: 'inbound' | 'outbound';
  content: string;
  contentType: 'text' | 'image' | 'video' | 'attachment';
  attachments?: Attachment[];
  senderId: string;
  senderType: 'customer' | 'agent' | 'bot';
  platform: string;
  platformMessageId: string;
  timestamp: Date;
  deliveryStatus?: 'sent' | 'delivered' | 'read' | 'failed';
}

export interface SLAConfiguration {
  firstResponseMinutes: number;
  resolutionHours: number;
  businessHoursOnly: boolean;
  businessHours: {
    timezone: string;
    schedule: WeeklySchedule;
  };
}

export interface ConversationMetadata {
  source: 'dm' | 'comment' | 'mention' | 'review';
  postId?: string;
  postUrl?: string;
  sentiment?: 'positive' | 'neutral' | 'negative';
  language?: string;
  crmContactId?: string;
  previousConversations?: number;
}

Los componentes principales incluyen:

ComponenteResponsabilidadEstrategia de Escalado
Receptor de WebhookIngerir eventos en tiempo real de las plataformasHorizontal con balanceador de carga
Agregador de MensajesNormalizar y almacenar conversacionesRéplicas de lectura para consultas
Motor de EnrutamientoAsignar conversaciones a agentesSin estado, basado en eventos
Interfaz de AgenteGestión de conversaciones en tiempo realConexiones WebSocket
Asistente de IAGenerar sugerencias de respuestaWorkers habilitados para GPU
Motor de AnalíticasRastrear métricas y generar informesBase de datos de series temporales

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

Estrategia de Integración Multicanal

Cada plataforma social tiene características de API únicas. Una plataforma de soporte social al cliente exitosa normaliza estas diferencias mientras preserva las características específicas de cada plataforma.

// services/platform-adapter.ts
import { INBOX_PLATFORMS, isPlatformSupported } from '@/libs/inbox/platforms';

export interface PlatformAdapter {
  platform: string;
  fetchConversations(accountId: string, since?: Date): Promise<Conversation[]>;
  fetchMessages(conversationId: string, cursor?: string): Promise<Message[]>;
  sendMessage(conversationId: string, content: MessageContent): Promise<Message>;
  markAsRead(conversationId: string): Promise<void>;
}

export class MultiChannelAdapter {
  private adapters: Map<string, PlatformAdapter> = new Map();

  constructor(private config: SupportPlatformConfig) {
    this.initializeAdapters();
  }

  private initializeAdapters(): void {
    for (const platform of this.config.platforms) {
      if (!isPlatformSupported(platform.name, 'messages')) {
        console.warn(`Platform ${platform.name} does not support direct messages`);
        continue;
      }
      
      const adapter = this.createAdapter(platform);
      this.adapters.set(platform.name, adapter);
    }
  }

  private createAdapter(platform: SupportedPlatform): PlatformAdapter {
    // Factory pattern for platform-specific implementations
    switch (platform.name) {
      case 'facebook':
        return new FacebookAdapter(platform.credentials);
      case 'instagram':
        return new InstagramAdapter(platform.credentials);
      case 'twitter':
        return new TwitterAdapter(platform.credentials);
      case 'linkedin':
        return new LinkedInAdapter(platform.credentials);
      case 'bluesky':
        return new BlueskyAdapter(platform.credentials);
      case 'telegram':
        return new TelegramAdapter(platform.credentials);
      default:
        throw new Error(`Unsupported platform: ${platform.name}`);
    }
  }

  async aggregateConversations(
    accountIds: string[],
    options: AggregationOptions
  ): Promise<AggregatedConversations> {
    const results: Conversation[] = [];
    const errors: AggregationError[] = [];

    const fetchPromises = accountIds.map(async (accountId) => {
      const account = await this.getAccount(accountId);
      const adapter = this.adapters.get(account.platform);
      
      if (!adapter) {
        return {
          accountId,
          conversations: [],
          error: { message: `No adapter for ${account.platform}` }
        };
      }

      try {
        const timeoutPromise = new Promise<never>((_, reject) => {
          setTimeout(() => reject(new Error('Request timeout')), 10000);
        });

        const conversations = await Promise.race([
          adapter.fetchConversations(accountId, options.since),
          timeoutPromise
        ]);

        return { accountId, conversations, error: null };
      } catch (error: any) {
        return {
          accountId,
          conversations: [],
          error: {
            message: error.message,
            code: error.code,
            retryAfter: error.retryAfter
          }
        };
      }
    });

    const settledResults = await Promise.all(fetchPromises);

    for (const result of settledResults) {
      if (result.error) {
        errors.push({
          accountId: result.accountId,
          platform: 'unknown',
          error: result.error.message,
          code: result.error.code,
          retryAfter: result.error.retryAfter
        });
      } else {
        results.push(...result.conversations);
      }
    }

    // Deduplicate by conversation ID
    const uniqueConversations = this.deduplicateConversations(results);
    
    // Sort by most recent activity
    uniqueConversations.sort((a, b) => 
      new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
    );

    return {
      conversations: uniqueConversations,
      meta: {
        accountsQueried: accountIds.length,
        accountsFailed: errors.length,
        failedAccounts: errors,
        lastUpdated: new Date().toISOString()
      }
    };
  }

  private deduplicateConversations(conversations: Conversation[]): Conversation[] {
    const seen = new Set<string>();
    return conversations.filter(conv => {
      const key = `${conv.platform}_${conv.customerId}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  }
}

Nota: Las APIs de las plataformas tienen diferentes límites de tasa. Facebook permite 200 llamadas por hora por usuario, mientras que los límites de Twitter varían según el endpoint. Siempre implementa retroceso exponencial y respeta los encabezados retryAfter.

Infraestructura de Webhook en Tiempo Real para tu Helpdesk de Redes Sociales

Un helpdesk de redes sociales receptivo requiere entrega de mensajes en tiempo real. Los webhooks eliminan el polling y aseguran que los agentes vean los nuevos mensajes instantáneamente.

// api/webhooks/social/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { EventEmitter } from 'events';

const webhookEmitter = new EventEmitter();

interface WebhookPayload {
  platform: string;
  eventType: string;
  data: any;
  timestamp: string;
  signature?: string;
}

export async function POST(request: NextRequest) {
  const platform = request.nextUrl.searchParams.get('platform');
  
  if (!platform) {
    return NextResponse.json(
      { error: 'Platform parameter required' },
      { status: 400 }
    );
  }

  try {
    const body = await request.text();
    const signature = request.headers.get('x-webhook-signature');

    // Verify webhook signature
    const isValid = await verifyWebhookSignature(platform, body, signature);
    if (!isValid) {
      console.error(`Invalid webhook signature for ${platform}`);
      return NextResponse.json(
        { error: 'Invalid signature' },
        { status: 401 }
      );
    }

    const payload = JSON.parse(body);
    const normalizedEvent = normalizeWebhookEvent(platform, payload);

    // Process asynchronously to respond quickly
    processWebhookEvent(normalizedEvent).catch(error => {
      console.error('Webhook processing error:', error);
    });

    // Emit for real-time listeners
    webhookEmitter.emit('message', normalizedEvent);

    return NextResponse.json({ received: true });
  } catch (error: any) {
    console.error('Webhook error:', error);
    return NextResponse.json(
      { error: 'Processing failed' },
      { status: 500 }
    );
  }
}

async function verifyWebhookSignature(
  platform: string,
  body: string,
  signature: string | null
): Promise<boolean> {
  if (!signature) return false;

  const secret = process.env[`${platform.toUpperCase()}_WEBHOOK_SECRET`];
  if (!secret) {
    console.warn(`No webhook secret configured for ${platform}`);
    return false;
  }

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

function normalizeWebhookEvent(platform: string, payload: any): NormalizedEvent {
  switch (platform) {
    case 'facebook':
    case 'instagram':
      return normalizeFacebookEvent(payload);
    case 'twitter':
      return normalizeTwitterEvent(payload);
    case 'linkedin':
      return normalizeLinkedInEvent(payload);
    default:
      throw new Error(`Unknown platform: ${platform}`);
  }
}

function normalizeFacebookEvent(payload: any): NormalizedEvent {
  const entry = payload.entry?.[0];
  const messaging = entry?.messaging?.[0];

  if (!messaging) {
    return { type: 'unknown', data: payload };
  }

  return {
    type: 'new_message',
    platform: payload.object === 'instagram' ? 'instagram' : 'facebook',
    conversationId: messaging.sender.id,
    message: {
      id: messaging.message.mid,
      content: messaging.message.text,
      senderId: messaging.sender.id,
      timestamp: new Date(messaging.timestamp)
    },
    accountId: entry.id
  };
}

async function processWebhookEvent(event: NormalizedEvent): Promise<void> {
  if (event.type !== 'new_message') return;

  // Find or create conversation
  const conversation = await findOrCreateConversation(event);
  
  // Add message to conversation
  await addMessageToConversation(conversation.id, event.message);
  
  // Trigger routing if new conversation
  if (conversation.isNew) {
    await routeConversation(conversation);
  }
  
  // Update SLA tracking
  await updateSLATracking(conversation.id);
  
  // Generate AI suggestions if enabled
  if (process.env.AI_ASSIST_ENABLED === 'true') {
    await generateResponseSuggestions(conversation.id, event.message);
  }

  // Notify assigned agent via WebSocket
  await notifyAgent(conversation.assignedTo, {
    type: 'new_message',
    conversationId: conversation.id,
    message: event.message
  });
}

export function subscribeToWebhooks(
  callback: (event: NormalizedEvent) => void
): () => void {
  webhookEmitter.on('message', callback);
  return () => webhookEmitter.off('message', callback);
}

Enrutamiento y Asignación de Conversaciones

El enrutamiento inteligente

Una API. 13+ plataformas.

Integra redes sociales en minutos, no semanas.

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