Regalos — Referencia técnica
Dónde vive esto
Backend
apps/backend/graphql/types/gift.type.js—GiftCatalogItem,Gift,GiftTargetStats,SendGiftInput,SendGiftResponse; queriesgiftCatalog/giftStatsForTarget/giftsForTarget; mutationsendGift.apps/backend/graphql/resolvers/gift.resolver.js— conecta lo anterior con el manager.apps/backend/managers/coin-managers/gift.manager.js— catálogo, verificación de saldo, transferencia de monedas, estadísticas.apps/backend/database/models/Gift.js+ migración20260721000000-create-gift.js— la tablagift.apps/backend/data-access-services/gift/gift.access-service.js— persistencia de filasGift+ la agregaciónGROUP BY gift_typedetrás degetStatsForTarget.apps/backend/graphql/types/post.type.js/post.resolver.js—Post.giftStats: GiftTargetStats!se resuelve por publicación mediantegift.manager#getStatsForTarget, de modo que la consulta del feed obtiene los totales de regalos incluidos en cada publicación en lugar de ungiftStatsForTargetaparte por cada tarjeta.
Los regalos en vivo son un sistema separado (live-stream-gift.type.js / live-stream-gift.resolver.js / LiveStreamGift.js, mutation sendLiveGift); no confundas los dos.
Frontend
apps/frontend-nextjs/src/components/gifts/GiftPicker.tsx— el selector de stickers (cargagiftCatalog, llama asendGift).apps/frontend-nextjs/src/components/gifts/GiftsModal.tsx— vista de regalos recibidos por destino (giftStatsForTarget/giftsForTarget).- Puntos de entrada:
components/chat/ChatView.tsx(botón de regalo en el encabezado del chat 1:1),components/PostCard.tsx,components/stories/StoryViewer.tsx, ypage-components/LiveRoomPage.tsx(variante en vivo).
GraphQL
type GiftCatalogItem { giftType: String! label: String! emoji: String! coinAmount: Int! }
input SendGiftInput {
"One of: post, story, clip, user."
targetType: String!
targetId: ID!
giftType: String!
message: String
}
type SendGiftResponse {
success: Boolean!
message: String # error detail when success is false (e.g. insufficient balance)
giftType: String
emoji: String
coinAmount: Int
newBalance: Int # sender's coin balance after the gift
}
extend type Query {
giftCatalog: [GiftCatalogItem!]!
giftStatsForTarget(targetType: String!, targetId: ID!): GiftTargetStats!
giftsForTarget(targetType: String!, targetId: ID!, limit: Int, offset: Int): [Gift!]!
}
extend type Mutation {
sendGift(input: SendGiftInput!): SendGiftResponse!
}
GiftTargetStats desglosa los regalos acumulados en un destino como byType: [GiftTargetStatItem!] (por sticker: count, totalCoins) más totalCoins / totalGifts.
Notas
- El precio es el
coinAmountdel catálogo; el manager verifica el saldo del remitente antes de transferir, de modo quesendGiftdevuelvesuccess:falsecon unmessageen lugar de lanzar un error cuando los fondos son insuficientes. sendGifttambién crea una notificación de tipogiftpara el destinatario y devuelve el saldo del remitente después del regalo (newBalance, víacoinTransactionManager#getUserBalance); ambas cosas son best-effort y nunca hacen fallar el regalo en sí.myGiftablePostsno forma parte de este sistema pese al nombre — vive entop-fans.type.js/top-fans.resolver.js/top-fans.manager.jsy lista las publicaciones de pago de un creador ofrecidas como recompensarewardTopFan(premium_post), usada solo porRewardFanModal.tsx. Consulta la referencia de Top Fans.