ingeniuscliq-offers
v0.1.4
Published
Offers system module for IngeniusCliq
Downloads
453
Readme
ingeniuscliq-offers
Sistema de ofertas y descuentos para IngeniusCliq basado en el paquete ingenius/discounts del backend.
Instalación
npm install ingeniuscliq-offersCaracterísticas
- ✅ Gestión de campañas de descuento
- ✅ Descuentos apilables y no apilables
- ✅ Condiciones complejas de aplicación
- ✅ Objetivos específicos (productos, categorías, envío, carrito)
- ✅ Cupones de descuento
- ✅ Cálculo automático de descuentos
- ✅ Persistencia con Zustand
- ✅ TypeScript completo
Tipos de Descuentos
DiscountType
PERCENTAGE: Descuento porcentual (ej: 20%)FIXED_AMOUNT: Monto fijo (ej: $10)BUY_ONE_GET_ONE: Compra uno y lleva otro
DiscountScope
PRODUCTS: Solo productosSHIPPING: Solo envíoCART: Solo carritoALL: Todos (default)
Uso Básico
1. Crear el servicio
Primero, implementa el servicio extendiendo CoreOffersBaseService:
import { CoreOffersBaseService } from 'ingeniuscliq-offers'
import type {
CoreDiscountCampaign,
CoreDiscountResult,
CoreOffersFetchParams,
CoreDiscountContext
} from 'ingeniuscliq-offers'
export class MyOffersService extends CoreOffersBaseService {
async fetchCampaigns(params?: CoreOffersFetchParams): Promise<CoreDiscountCampaign[]> {
const response = await fetch(`${this.baseURL}/discounts/campaigns`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
})
return await response.json()
}
async fetchActiveCampaigns(params?: CoreOffersFetchParams): Promise<CoreDiscountCampaign[]> {
const response = await fetch(`${this.baseURL}/discounts/campaigns/active`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...params, is_active: true })
})
return await response.json()
}
async fetchCampaignById(id: number | string): Promise<CoreDiscountCampaign> {
const response = await fetch(`${this.baseURL}/discounts/campaigns/${id}`)
return await response.json()
}
async calculateDiscounts(
context: CoreDiscountContext,
scope?: DiscountScope
): Promise<CoreDiscountResult[]> {
const response = await fetch(`${this.baseURL}/discounts/calculate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ context, scope })
})
return await response.json()
}
async applyCouponCode(
code: string,
context: CoreDiscountContext
): Promise<CoreDiscountResult> {
const response = await fetch(`${this.baseURL}/discounts/apply-coupon`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, context })
})
return await response.json()
}
async validateCouponCode(code: string): Promise<boolean> {
const response = await fetch(`${this.baseURL}/discounts/validate-coupon/${code}`)
const data = await response.json()
return data.valid
}
}2. Crear el store
import { CoreOffersBuilder } from 'ingeniuscliq-offers'
import { MyOffersService } from './services/MyOffersService'
const offersService = new MyOffersService('https://api.example.com')
const builder = new CoreOffersBuilder(offersService)
export const useOffersStore = builder.build()3. Usar en componentes
import { useOffersStore } from './stores/offersStore'
import { DiscountScope } from 'ingeniuscliq-offers'
function MyComponent() {
const {
campaigns,
activeCampaigns,
appliedDiscounts,
fetchActiveCampaigns,
calculateDiscounts,
applyCouponCode
} = useOffersStore()
useEffect(() => {
// Obtener campañas activas
fetchActiveCampaigns({
page: 1,
per_page: 10
})
}, [])
const handleCalculateDiscounts = async () => {
const context = {
cart_total: 10000, // $100 en centavos
items: [
{
productible_id: 1,
productible_type: 'Product',
quantity: 2,
base_price_per_unit_in_cents: 5000,
base_total_in_cents: 10000
}
],
customer_id: user?.id
}
const discounts = await calculateDiscounts(context, DiscountScope.ALL)
console.log('Discounts applied:', discounts)
}
const handleApplyCoupon = async (code: string) => {
const context = {
cart_total: 10000,
items: [...]
}
const discount = await applyCouponCode(code, context)
if (discount) {
console.log('Coupon applied!', discount)
}
}
return (
<div>
<h2>Active Campaigns</h2>
{activeCampaigns?.map(campaign => (
<div key={campaign.id}>
<h3>{campaign.name}</h3>
<p>{campaign.description}</p>
<p>Discount: {campaign.discount_value}%</p>
</div>
))}
</div>
)
}Estructura de Datos
CoreDiscountCampaign
interface CoreDiscountCampaign {
id: number | string
code?: string | null // Código de cupón
name: string
description?: string | null
discount_type: DiscountType // 'percentage' | 'fixed_amount' | 'buy_one_get_one'
discount_value: number // Valor (% o centavos)
start_date: string | Date
end_date: string | Date
is_active: boolean
priority: number // Mayor = se ejecuta primero
is_stackable: boolean // ¿Se puede apilar?
max_uses_total?: number | null
max_uses_per_customer?: number | null
current_uses: number
metadata?: Record<string, any>
conditions?: CoreDiscountCondition[]
targets?: CoreDiscountTarget[]
}CoreDiscountContext
interface CoreDiscountContext {
cart_total: number // Total del carrito en centavos
items: Array<{
productible_id: number | string
productible_type: string
quantity: number
base_price_per_unit_in_cents: number
base_total_in_cents: number
}>
customer_id?: number | string | null
customer_type?: string | null
calculated_shipping_cost?: number | null
metadata?: Record<string, any>
}CoreDiscountResult
interface CoreDiscountResult {
campaign_id: number | string
campaign_name: string
discount_type: string
amount_saved: number // Monto ahorrado en centavos
amount_saved_converted?: string // Formateado (ej: "$20.00")
affected_items?: Array<{...}>
metadata?: Record<string, any>
}Enums
// Tipos de descuento
enum DiscountType {
PERCENTAGE = 'percentage',
FIXED_AMOUNT = 'fixed_amount',
BUY_ONE_GET_ONE = 'buy_one_get_one'
}
// Alcance del descuento
enum DiscountScope {
PRODUCTS = 'products',
SHIPPING = 'shipping',
CART = 'cart',
ALL = 'all'
}
// Tipos de condición
enum ConditionType {
MIN_CART_VALUE = 'min_cart_value',
MIN_QUANTITY = 'min_quantity',
CUSTOMER_SEGMENT = 'customer_segment',
HAS_PRODUCT = 'has_product',
FIRST_ORDER = 'first_order',
DATE_RANGE = 'date_range'
}
// Operadores de condición
enum ConditionOperator {
GREATER_THAN_OR_EQUAL = '>=',
GREATER_THAN = '>',
LESS_THAN_OR_EQUAL = '<=',
LESS_THAN = '<',
EQUALS = '==',
NOT_EQUALS = '!=',
IN = 'in',
NOT_IN = 'not_in'
}
// Tipos de target
enum TargetType {
PRODUCTS = 'products',
CATEGORIES = 'categories',
SHIPMENT = 'shipment',
SHOPCART = 'shopcart'
}
// Acciones de target
enum TargetAction {
APPLY_TO = 'apply_to',
REQUIRES = 'requires',
EXCLUDES = 'excludes'
}Lógica de Apilamiento
El sistema soporta descuentos apilables (stackable):
- Se aplica el mejor descuento NO apilable
- Luego se aplican TODOS los descuentos apilables sobre el precio restante
- Los descuentos de carrito se aplican después de los descuentos de producto
Ejemplo:
Precio Base: $100
1. Mejor NO apilable: 20% = $20 OFF
Precio restante: $80
2. Apilable #1: 10% = $8 OFF (sobre $80)
Precio restante: $72
3. Apilable #2: $5 fijo = $5 OFF
Precio final: $67
Total ahorrado: $33License
MIT
