import { UserInfo } from "../Users/UserTrii"; import { ILabel } from "./Label"; import { GroupInfo } from "./Groups/Group"; import { Channels } from "../Common/index"; import { IContactAddress, IContactInfo } from "../Contacts/contacts"; import { IEnding } from "./Ending"; import { ChatType } from "../Chats/Chat"; import { TransferConversationCommand, TransferConversationResult } from "./Transfer"; import { AcceptConversationAssignmentCommand, AcceptConversationAssignmentResult, DeclineConversationAssignmentCommand, DeclineConversationAssignmentResult, CancelConversationAssignmentCommand, CancelConversationAssignmentResult } from "./Assignment"; export interface IConversation { id: string; spaceId: string; version: number; /** @deprecated Use assignment.owner. */ type: ChatType; /**only for Front-End */ chatName: string; chatImage: string; chatMembersCount: number; /** * Descripción del grupo interno (B16, acción `updateDescription`). Común a todas las copias * del chat. Para grupos de WhatsApp la descripción sigue viviendo en * `whatsappGroupInfo.description`. */ description?: string | null; /** * Código de invitación vigente del grupo interno (B16, acciones `createInviteLink` / * `revokeInviteLink`, `POST /Chats/join/{inviteCode}`). Común a todas las copias. * null = sin enlace activo. */ inviteCode?: string | null; whatsappGroupInfo?: IWhatsAppGroupInfo; contactInfo?: IContactInfo; direction: ConversationDirection; remoteAddress: string; remoteAddressId: string; channelInfo: Channels.IChannelInfo; status: ConversationStatus; priority: 'low' | 'normal' | 'high' | 'urgent'; assignment: ConversationAssignmentSnapshot; /** @deprecated Use owner and assignment.*/ assignedTo?: ConversationAssigned; sla: { status: 'none' | 'running' | 'warning' | 'breached' | 'met'; deadlineAt?: Date; policyId?: string; version: number; }; lastMessage: string; lastMessageId?: string; newMessagesCount: number; newMessagesUnread: boolean; lastActivityAt: Date; lastCustomerActivityAt?: Date; lastAgentActivityAt?: Date; labels: ILabel[]; /** * Fijado/favorito. En chats internos (GROUP/DIRECT) el documento está duplicado por miembro * (`shardKey = "User|{userId}"`), por lo que este flag es por copia/usuario (B1), no del grupo. */ pinned: boolean; /** * Silenciado hasta (B3). Por copia/usuario, igual que `pinned`. null/undefined = no silenciado. * El front compara contra la hora actual; una fecha lejana representa "siempre". */ mutedUntil?: Date | null; /** @deprecated A transfer is represented by an event and the resulting current state. */ transferBy?: string; /** @deprecated A transfer is represented by an event and the resulting current state. */ transferTo?: ConversationAssigned; expired: boolean; /** @deprecated Expiration does not own a separate assignment snapshot.*/ expiredAssignedTo: ConversationAssigned; participants: UserInfo[]; archived: boolean; spam: boolean; externalMessagingWindowEndAt?: Date; workflowId?: string; botStatus: ConversationBotStatus; botVars: ConversationBotVar[]; metadata: IConversationMetadata; /** Domain objects discussed or served by this conversation. */ finalizedOnWebChat: Date; ending?: IEnding; qualityEvaluation?: IInteractionQualityEvaluation; reOpenAt?: Date; reOpenByUserId?: string; reOpenPolicyId?: string; createdAt: Date; updatedAt: Date; resolvedAt?: Date; resolvedByUserId?: string; resolutionReasonId?: string; finalizedAt: Date; finalizedBy?: string; } export interface IWhatsAppGroupInfo { whatsappGroupId: string; groupInviteCode: string; members: IWhatsAppGroupMember[]; invitationTemplateId: string; subject: string; description: string; } export interface IWhatsAppGroupMember { contactAddress: IContactAddress; contactId: string; name: string; contactInfo: IContactInfo; joinedAt?: Date; } export interface IInteractionQualityEvaluation { courtesy: EvaluationCriterion; clarity: EvaluationCriterion; empathy: EvaluationCriterion; proactivity: EvaluationCriterion; resolution: EvaluationCriterion; responseTime: EvaluationCriterion; overallScore: EvaluationCriterion; } export interface EvaluationCriterion { value: number; comment: string; } export interface IConversationMetadata { nickname?: string; email?: string; ip?: string; country?: string; region?: string; city?: string; timezone?: string; latitude?: number; longitude?: number; isp?: string; userAgent?: string; browser?: string; os?: string; deviceType?: 'Desktop' | 'Mobile' | 'Tablet'; language?: string; referer?: string; utmSource?: string; utmMedium?: string; utmCampaign?: string; sessionId?: string; screenResolution?: string; } export declare enum ConversationBotStatus { NONE = 0, WAITING = 1, PROCESSING = 2, FINALIZED = 4, CANCELED = 5 } export interface ConversationBotVar { title: string; value: string; } /** * @deprecated Legacy channel assignment configuration. Use QueueRoutingConfig. */ export interface ConversationAssigned { groupInfo?: GroupInfo; userInfo?: UserInfo; userIds?: string[]; } export declare enum ConversationDirection { INBOUND = 1, OUTBOUND = 2 } /** * @deprecated Use QueueDistributionMode and QueueRoutingStrategy. */ export declare enum AssignMethod { ALL = 0, ROUND_ROBIN = 1, RANDOM = 2, LEAST_BUSY = 3 } export declare enum ConversationStatus { NEW = 1, OPEN = 2, ACTIVE = 2,// Alias for OPEN RESOLVED = 3, FINALIZED = 3,// Alias for RESOLVED WAITING = 10 } export type InboxViewId = 'pending' | 'following' | 'sla_warning' | 'sla_breached' | 'team_sla_breached' | 'resolved' | 'label'; export type OwnerType = 'user' | 'bot' | 'flow' | 'none'; export interface NoConversationOwner { type: 'none'; id?: never; displayName?: never; avatarUrl?: never; } export interface UserConversationOwner { type: 'user'; id: ID; displayName?: string; avatarUrl?: string; } export interface BotConversationOwner { type: 'bot'; id: ID; displayName?: string; avatarUrl?: string; } export interface FlowConversationOwner { type: 'flow'; id: ID; displayName?: string; avatarUrl?: string; } export type ConversationOwner = NoConversationOwner | UserConversationOwner | BotConversationOwner | FlowConversationOwner; export type ConversationAssignmentStatus = 'unrouted' | 'queued' | 'routing' | 'offered' | 'assigned' | 'accepted'; export type ConversationAssignmentSource = 'routing' | 'manual' | 'transfer' | 'requeue'; interface QueuedConversationAssignmentBase { assignmentId: ID; queueId: ID; source: ConversationAssignmentSource; queuedAt: Date; lastRoutingDecisionId?: ID; } export type ConversationAssignmentSnapshot = { status: 'unrouted'; assignmentId?: never; queueId?: never; owner: NoConversationOwner | BotConversationOwner | FlowConversationOwner; source?: never; queuedAt?: never; lastRoutingDecisionId?: never; offeredAt?: never; offerExpiresAt?: never; assignedAt?: never; acceptedAt?: never; } | QueuedConversationAssignmentBase & { status: 'queued' | 'routing'; owner: NoConversationOwner; offeredAt?: never; offerExpiresAt?: never; assignedAt?: never; acceptedAt?: never; } | QueuedConversationAssignmentBase & { status: 'offered'; owner: UserConversationOwner; offeredAt: Date; offerExpiresAt?: Date; assignedAt?: never; acceptedAt?: never; } | QueuedConversationAssignmentBase & { status: 'assigned'; owner: UserConversationOwner; offeredAt?: Date; offerExpiresAt?: Date; assignedAt: Date; acceptedAt?: never; } | QueuedConversationAssignmentBase & { status: 'accepted'; owner: UserConversationOwner; offeredAt?: Date; offerExpiresAt?: Date; assignedAt: Date; acceptedAt: Date; }; export type ActorType = 'user' | 'contact' | 'bot' | 'flow' | 'system'; export type ParticipantRole = 'owner' | 'collaborator' | 'follower' | 'mentioned' | 'supervisor' | 'bot' | 'flow' | 'historical'; export type ConversationEventType = 'conversation_created' | 'queued' | 'assigned' | 'accepted' | 'transferred' | 'consult_requested' | 'collaborator_added' | 'participant_added' | 'participant_removed' | 'participation_abandoned' | 'follow_started' | 'follow_stopped' | 'message_sent' | 'message_received' | 'internal_note_added' | 'bot_started' | 'bot_finished' | 'flow_started' | 'flow_finished' | 'status_changed' | 'resolved' | 'reopened' | 'sla_warning' | 'sla_breached' | 'sla_started' | 'sla_paused' | 'sla_resumed' | 'sla_met' | 'sla_overridden'; export type ID = string; export type ConversationSubscriptionSource = 'manual' | 'assigned' | 'participated' | 'mentioned' | 'supervisor' | 'rule'; export type ConversationSubscriptionNotificationEvent = 'customer_message' | 'internal_mention' | 'assignment_change' | 'acceptance' | 'resolution' | 'reopen'; export type ConversationSubscriptionNotifications = { mode: 'all' | 'muted'; events?: never; } | { mode: 'custom'; events: [ ConversationSubscriptionNotificationEvent, ...ConversationSubscriptionNotificationEvent[] ]; }; interface ConversationSubscriptionBase { id: ID; spaceId: string; conversationId: ID; userId: ID; source: ConversationSubscriptionSource; notifications: ConversationSubscriptionNotifications; createdAt: Date; updatedAt: Date; version: number; } /** * Current following state for one user/conversation pair. * * A subscription is persistent product intent. Assignment or participation * visibility must not create it unless an explicit auto-follow policy applies. */ export type ConversationSubscription = ConversationSubscriptionBase & { status: 'following'; followedAt: Date; unfollowedAt?: never; } | ConversationSubscriptionBase & { status: 'unfollowed'; followedAt: Date; unfollowedAt: Date; }; export interface PutConversationSubscriptionRequest { expectedVersion?: number; notifications: ConversationSubscriptionNotifications; } export interface UserConversationState { id: ID; spaceId: ID; conversationId: ID; userId: ID; pinned: boolean; pinnedAt?: Date; lastReadMessageId?: ID; lastReadAt?: Date; updatedAt: Date; version: number; } export type ConversationReadStateMode = 'read' | 'unread'; export interface PutConversationReadStateRequest { mode: ConversationReadStateMode; lastReadMessageId?: ID; lastReadAt?: Date; } export interface PutConversationPinRequest { pinned: boolean; } export type InboxVisibilityReason = 'manual_follow' | 'assigned_to_user' | 'shared_queue_member' | 'queue_claimable' | 'active_participant' | 'sla_warning_owner' | 'sla_breached_owner' | 'supervised_queue_sla' | 'supervisor_escalation' | 'resolved_visible' | 'labeled'; export interface ConversationInboxSnapshot { interactionKind: 'private' | 'wall_post' | 'comment' | 'mention'; channelType: string; channelId?: ID; contact: { id?: ID; displayName: string; initials?: string; isBusiness: boolean; }; status: ConversationStatus; owner: ConversationOwner; queueId?: ID; preview?: string; lastMessageId?: ID; unreadCount: number; priority: 'low' | 'normal' | 'high' | 'urgent'; sla: { status: 'none' | 'running' | 'warning' | 'breached' | 'met'; deadlineAt?: Date; }; lastActivityAt: Date; labels: { id: ID; name: string; color?: string; }[]; /** Solo poblado para interactionKind='comment'|'mention' - el WallMessage.id del comentario mas * reciente, para abrir el hilo posicionado ahi (igual que lastMessageId para conversaciones). */ commentId?: ID; participants: { id: ID; displayName?: string; avatarUrl?: string; }[]; } /** * Membership of one materialized conversation in one logical inbox view. * * visibilityReasons has set semantics: values must be unique and non-empty. * Removing one reason preserves the view while at least one reason remains. * * labelId is only populated when viewId is 'label' - identifies which of the * space's labels this particular view entry represents (InboxViewId is a * fixed enum, but labels are dynamic per space). */ export interface UserInboxItemView { viewId: InboxViewId; visibilityReasons: [ InboxVisibilityReason, ...InboxVisibilityReason[] ]; requiresAction: boolean; labelId?: ID; } /** * Materialized read model. There is exactly one document for each * spaceId + userId + conversationId tuple. * * views must contain unique viewId values, EXCEPT for viewId='label': a * conversation can carry multiple labels, so multiple entries with * viewId='label' are allowed as long as their labelId differs (unique * (viewId, labelId) pairs for that case). Delete the document when the last * view is removed. */ export interface UserInboxItemDocument { id: ID; spaceId: ID; userId: ID; conversationId: ID; views: UserInboxItemView[]; snapshot: ConversationInboxSnapshot; pinned: boolean; priorityScore: number; sortAt: Date; conversationVersion: number; subscriptionVersion?: number; projectionVersion: number; lastSourceEventId: ID; projectedAt: Date; } /** * Materialized counters for exactly one user/view pair (user/view/label when * viewId is 'label' - labelId becomes part of the logical key in that case). * * total and requiresAction count conversations, never visibility reasons. * unread counts conversations whose snapshot.unreadCount is greater than zero, * not the number of unread messages. */ export interface UserInboxCounterDocument { id: ID; spaceId: ID; userId: ID; viewId: InboxViewId; labelId?: ID; total: number; requiresAction: number; unread: number; projectionVersion: number; updatedAt: Date; } export interface CursorPage { items: T[]; nextCursor?: string; hasMore: boolean; generatedAt: Date; } export interface ConversationCapabilitiesDto { canView: boolean; canReply: boolean; canFollow: boolean; canAbandon: boolean; canClaim: boolean; canAccept: boolean; canRelease: boolean; canTransfer: boolean; canResolve: boolean; canReopen: boolean; canChangePriority: boolean; canManageLabels: boolean; canManageSla: boolean; canViewAssignmentHistory: boolean; canViewRoutingDecisions: boolean; } export interface InboxViewSummaryDto { id: InboxViewId | 'supervised_target' | `label:${string}`; label: string; scope: 'personal' | 'supervision' | 'history' | 'labels'; order: number; total: number; requiresAction: number; unread: number; enabled: boolean; targetRequired: boolean; capabilities: string[]; } export interface InboxViewsResponse { views: InboxViewSummaryDto[]; countersGeneratedAt: Date; projectionLagMs?: number; } export interface InboxFilterDto { text?: string; queueIds?: ID[]; ownerUserIds?: ID[]; channelTypes?: string[]; statuses?: ConversationStatus[]; priorities?: Array<'low' | 'normal' | 'high' | 'urgent'>; slaStatuses?: Array<'none' | 'running' | 'warning' | 'breached' | 'met'>; labelIds?: ID[]; unread?: boolean; requiresAction?: boolean; pinned?: boolean; lastActivity?: { from?: Date; to?: Date; }; sort: { type: 'activity'; } | { type: 'priority'; }; } export type InboxConversationItemDto = Pick & { capabilities: Pick; }; export interface InboxConversationPage extends CursorPage { appliedFilter: InboxFilterDto; facets?: unknown; } /** * DTO API - GET /inbox/views/{viewId}/filter-options. */ export interface InboxFacetOptionDto { id: string; label: string; count: number; } export interface InboxFacetsDto { queues: InboxFacetOptionDto[]; owners: InboxFacetOptionDto[]; channels: InboxFacetOptionDto[]; statuses: InboxFacetOptionDto[]; priorities: InboxFacetOptionDto[]; slaStatuses: InboxFacetOptionDto[]; labels: InboxFacetOptionDto[]; } export interface InboxFilterOptionsResponse { facets: InboxFacetsDto; generatedAt: Date; } export type ConversationTimelineItemType = 'assignment' | 'sla' | 'system'; export interface ConversationTimelineItem { type: ConversationTimelineItemType; time: Date; text: string; conversationId: string; } export interface ConversationUserRelationDto { isOwner: boolean; isParticipant: boolean; isFollowing: boolean; isPinned: boolean; unreadCount: number; } export interface ConversationDetailResponse { conversation: IConversation; relation: ConversationUserRelationDto; capabilities: ConversationCapabilitiesDto; availableActions: string[]; } export interface AddConversationParticipantRequest { userId: string; } export interface TransferQueueOptionDto { id: ID; name: string; color?: string; canTransfer: boolean; supportsCurrentChannel: boolean; } export interface TransferUserOptionDto { id: ID; displayName: string; available: boolean; capacityUsed: number; capacityTotal: number; } export interface TransferOptionsResponse { queues: TransferQueueOptionDto[]; usersByQueue?: Record; } export type StartTransferRequest = Pick; export type StartTransferResponse = TransferConversationResult; export type AcceptAssignmentRequest = Pick; export type AcceptAssignmentResponse = AcceptConversationAssignmentResult; export type DeclineAssignmentRequest = Pick; export type DeclineAssignmentResponse = DeclineConversationAssignmentResult; export type CancelAssignmentRequest = Pick; export type CancelAssignmentResponse = CancelConversationAssignmentResult; export {};