/** * Tipos centralizados para mensajes de WhatsApp * Basado en la API de Baileys (@whiskeysockets/baileys) */ // Tipos de mensaje soportados por Baileys export type MessageType = | 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'contact' | 'location' | 'reaction' | 'poll' | 'unknown' // Estados de mensaje export type MessageStatus = 'pending' | 'sent' | 'delivered' | 'read' | 'failed' // Estados de presencia export type PresenceStatus = 'available' | 'unavailable' | 'composing' | 'recording' | 'paused' /** * Información de media (imagen, video, audio, documento, sticker) */ export interface MediaInfo { /** URL del media (puede ser endpoint de API o URL directa) */ url?: string /** Tipo MIME del archivo */ mimetype?: string /** Nombre del archivo */ filename?: string /** Tamaño en bytes */ filesize?: number /** Ancho en píxeles (para imagen/video/sticker) */ width?: number /** Alto en píxeles (para imagen/video/sticker) */ height?: number /** Duración en segundos (para audio/video) */ duration?: number /** Thumbnail en base64 */ thumbnail?: string /** Si es mensaje de vista única */ isViewOnce?: boolean /** Si es audio de voz (PTT) */ isPtt?: boolean /** Waveform del audio (array de bytes) */ waveform?: number[] } /** * Información de ubicación */ export interface LocationInfo { /** Latitud */ latitude: number /** Longitud */ longitude: number /** Nombre del lugar */ name?: string /** Dirección */ address?: string /** URL del mapa */ url?: string } /** * Información de contacto compartido */ export interface ContactInfo { /** Nombre para mostrar */ displayName: string /** vCard completo */ vcard: string /** Números de teléfono extraídos */ phones?: string[] } /** * Información de mensaje citado (quoted/reply) */ export interface QuotedMessage { /** ID del mensaje original */ id: string /** Contenido del mensaje original */ content: string | null /** Tipo del mensaje original */ type: MessageType /** Si fue enviado por mí */ fromMe: boolean /** JID del autor original (en grupos) */ participant?: string /** Nombre del autor original */ participantName?: string /** Media info si era un mensaje con media */ media?: MediaInfo } /** * Información de reacción a un mensaje */ export interface ReactionInfo { /** Emoji de la reacción */ emoji: string /** JID de quien reaccionó */ reactorJid: string /** Nombre de quien reaccionó */ reactorName?: string /** Timestamp de la reacción */ timestamp?: Date } /** * Mensaje completo con toda la información */ export interface Message { /** UUID interno de la BD */ id: string /** ID del mensaje de WhatsApp */ messageId: string /** ID del chat */ chatId?: string /** JID del remitente */ fromJid: string /** Si fue enviado por mí */ fromMe: boolean /** Tipo de mensaje */ type: MessageType /** Contenido de texto (para text/caption) */ content: string | null /** Caption para media */ caption?: string /** Información de media */ media?: MediaInfo /** Información de ubicación */ location?: LocationInfo /** Información de contacto */ contact?: ContactInfo /** Mensaje citado */ quoted?: QuotedMessage /** Reacciones al mensaje */ reactions?: ReactionInfo[] /** Timestamp del mensaje */ timestamp: Date /** Estado del mensaje */ status: MessageStatus /** JID del participante en grupos */ participant?: string /** Nombre del remitente (pushName) */ pushName?: string /** Si es un mensaje del sistema (stub) */ isStub?: boolean /** Tipo de stub (si aplica) */ stubType?: string /** Parámetros del stub */ stubParams?: string[] } /** * Información de presencia de un usuario */ export interface PresenceInfo { /** Estado actual */ status: PresenceStatus /** Última vez visto */ lastSeen?: Date } /** * Participante de grupo */ export interface GroupParticipant { /** JID del participante */ jid: string /** Nombre del participante */ name?: string /** Si es administrador */ isAdmin: boolean /** Si es super administrador (creador) */ isSuperAdmin: boolean } /** * Chat/Conversación */ export interface Chat { /** UUID interno */ id: string /** JID del chat */ jid: string /** Nombre del chat */ name: string /** Si es grupo */ isGroup: boolean /** URL de foto de perfil */ profilePicture?: string /** Último mensaje (texto o placeholder) */ lastMessage: string /** Tipo del último mensaje */ lastMessageType?: MessageType /** Timestamp del último mensaje */ lastMessageAt: Date /** Contador de mensajes no leídos */ unreadCount: number /** Presencia actual (si está suscrito) */ presence?: PresenceInfo /** Participantes (solo para grupos) */ participants?: GroupParticipant[] /** Si está archivado */ isArchived?: boolean /** Si está fijado */ isPinned?: boolean /** Si está silenciado */ isMuted?: boolean } /** * Contenido para enviar un mensaje */ export interface SendMessageContent { /** Texto del mensaje */ text?: string /** Archivo de imagen */ image?: File | string /** Archivo de video */ video?: File | string /** Archivo de audio */ audio?: File | string /** Archivo de documento */ document?: File | string /** Caption para media */ caption?: string /** ID del mensaje a citar */ quotedMessageId?: string /** JIDs para mencionar */ mentions?: string[] /** Si es audio PTT (nota de voz) */ isPtt?: boolean } /** * Evento de actualización de presencia */ export interface PresenceUpdateEvent { /** ID de la instancia */ instanceId: string /** JID del chat */ jid: string /** Presencias por participante */ presences: Record } /** * Evento de actualización de estado de mensaje */ export interface MessageStatusEvent { /** ID de la instancia */ instanceId: string /** ID del mensaje */ messageId: string /** Nuevo estado */ status: MessageStatus } /** * Evento de nuevo mensaje */ export interface MessageReceivedEvent { /** ID de la instancia */ instanceId: string /** Mensaje completo */ message: Message } /** * Evento de reacción a mensaje */ export interface MessageReactionEvent { /** ID de la instancia */ instanceId: string /** ID del mensaje */ messageId: string /** Información de la reacción */ reaction: ReactionInfo } // Utilidades /** * Obtiene el placeholder de texto para un tipo de mensaje */ export function getMessageTypePlaceholder(type: MessageType): string { const placeholders: Record = { text: '', image: 'Foto', video: 'Video', audio: 'Audio', document: 'Documento', sticker: 'Sticker', contact: 'Contacto', location: 'Ubicación', reaction: 'Reacción', poll: 'Encuesta', unknown: 'Mensaje' } return placeholders[type] || 'Mensaje' } /** * Obtiene el ícono para un tipo de mensaje */ export function getMessageTypeIcon(type: MessageType): string { const icons: Record = { text: 'i-lucide-message-square', image: 'i-lucide-image', video: 'i-lucide-video', audio: 'i-lucide-music', document: 'i-lucide-file', sticker: 'i-lucide-smile', contact: 'i-lucide-user', location: 'i-lucide-map-pin', reaction: 'i-lucide-heart', poll: 'i-lucide-bar-chart', unknown: 'i-lucide-help-circle' } return icons[type] || 'i-lucide-message-square' } /** * Genera un color consistente basado en un string (para nombres en grupos) */ export function stringToColor(str: string): string { const colors = [ '#e17076', // rojo '#faa774', // naranja '#a695e7', // morado '#7bc862', // verde '#6ec9cb', // cyan '#65aadd', // azul '#ee7aae', // rosa '#e5a36b', // marrón ] let hash = 0 for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash) } return colors[Math.abs(hash) % colors.length] } /** * Formatea bytes a tamaño legible */ export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}` } /** * Formatea duración en segundos a mm:ss */ export function formatDuration(seconds: number): string { const mins = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${mins}:${secs.toString().padStart(2, '0')}` }