B4A=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=12.8
@EndOfDesignText@
'Class module: C_Promociones
Sub Class_Globals
	Private sql As SQL
	' Referencia a tu clase de Trade Spending (ajusta el tipo si se llama diferente)
	Private ts As Object ' Lo dejo como Object para que no te marque error si no has copiado esa clase aun
    
	' Variable local para el cliente actual (para no depender de Subs.traeCliente)
	Private mClienteActual As String
	
End Sub

' Inicializa la clase.
' db: La conexión SQL (Starter.skmt)
' tradeSpending: La instancia de tu clase de TradeSpending (B4XPages.MainPage.promos.ts)
Public Sub Initialize(db As SQL, tradeSpendingInstance As Object)
	sql = db
	ts = tradeSpendingInstance
End Sub

' =============================================================================
' CONTROLADOR PRINCIPAL: ORQUESTADOR DE VALIDACIONES
' =============================================================================
Public Sub procesaPromocion(idPromo As String, cliente As String) As Map
	Private thisLog As Boolean = True 'Si es verdadero, muestra los logs de este sub.
	Private inicioContador As String = DateTime.Now
	Private mp As Int = 0
	
	If thisLog Then LogColor($"****************************************************************"$, Colors.RGB(142,0,255))
	If thisLog Then LogColor($"********* Iniciamos revision de Promo ${idPromo} *********"$, Colors.RGB(142,0,255))
	If thisLog Then LogColor($"****************************************************************"$, Colors.RGB(142,0,255))
	
	Try
		Private pm As TPromoLegacy = TraePromo(idPromo, cliente)
		If thisLog Then LogColor(pm, Colors.Blue)
		
		Private maxPromosXprodsFijos As Int = RevisaMaxPromosProdsFijos(pm)
		If maxPromosXprodsFijos < 1 Then pm.Resultado = "0"
		
		If thisLog Then LogColor($"***********************************************************************"$, Colors.Blue)
		LogColor($"*** PROMOS DISPONIBLES X PRODS FIJOS (${idPromo}) = ${maxPromosXprodsFijos}   ***"$, Colors.Blue)
		If thisLog Then LogColor($"***********************************************************************"$, Colors.Blue)
		
		If pm.Resultado = "ok" Then 'Si encontramos la promoción, entonces ...
			mp = TraeMaxPromos(pm)
			If mp < 1 Then
				If thisLog Then LogColor("Ya se vendieron las promos PERMITIDAS para el cliente", Colors.red)
				Return CreateMap("status":"ko", "mp":pm)
			End If
			
			Private inventarioSinFijos As Map = restaFijosPromo(pm)
			If thisLog Then LogColor("inventariosfijos="&inventarioSinFijos, Colors.Magenta)
			
			If inventarioSinFijos.Get("resultado") = "ok" Then
				Private pv As Boolean = alcanzanLosVariablesParaPromo(pm, inventarioSinFijos)
				
				If thisLog Then LogColor($"****************************************************"$, Colors.Blue)
				If thisLog Then LogColor($"***** ¿ALCANZAN LOS VARIABLES? ==> ${IIf(pv, "SI", "NO")}  *****"$, Colors.Blue)
				If thisLog Then LogColor($"****************************************************"$, Colors.Blue)
				
				If pv Then Return CreateMap("status":"ok", "mp":pm) Else Return CreateMap("status":"ko", "mp":pm)
			Else
				If thisLog Then LogColor("NO HAY INVENTARIO SUFICIENTE " & idPromo, Colors.red)
				Return CreateMap("status":"ko", "mp":pm)
			End If
		Else
			Return CreateMap("status":"ko", "mp":pm)
		End If
		
		LogColor("TIEMPO DE PROCESO ESTA PROMO: " & ((DateTime.Now-inicioContador)/1000), Colors.Red)
		
	Catch
		Log($"Promo ${idPromo} mal configurada"$)
		ToastMessageShow($"Promo ${idPromo} mal configurada"$, True)
		Log(LastException)
		Return CreateMap("status":"ko", "mp":Null)
	End Try
End Sub

' =============================================================================
' RUTINA PRINCIPAL: ARMA LA ESTRUCTURA (Clon de Subs.traePromo)
' =============================================================================
Public Sub TraePromo(promo As String, cliente As String) As TPromoLegacy
	mClienteActual = cliente ' Guardamos el cliente para uso interno
    
	Dim t As TPromoLegacy
	t.Initialize
    
	' Inicialización de Listas y Mapas (CRÍTICO para evitar Nulls)
	t.ProdsFijos.Initialize
	t.ProdsFijosPiezas.Initialize
	t.ProdsFijosPrecios.Initialize
	t.ProdsVariables.Initialize
	t.ProdsVariablesPrecios.Initialize
	t.Productos.Initialize
	t.Tipos.Initialize
    
	t.Id = promo
	t.MaxXCliente = 0
	t.MaxRecurrente = 0
	t.MaxPromos = 0
    
	' --- 1. LECTURA GENERAL ---
	Dim c As Cursor = sql.ExecQuery("Select * from promos_comp where cat_pa_id = '"& promo&"'")
	If c.RowCount > 0 Then
		c.Position = 0
		t.MaxXCliente = c.GetString("CAT_PA_MAXPROMCLIE")
		t.MaxRecurrente = c.GetString("CAT_PA_MAXPROMREC")
		t.MaxPromos = c.GetString("CAT_PA_MAXPROM")
	End If
	c.Close
    
	' --- 2. SEGMENTACIÓN ---
	Dim ps As Cursor = sql.ExecQuery($"select * from HIST_CLIENTE_CANT_PROMOS where HCCP_PROMO = '${promo}'"$)
	If ps.RowCount > 0 Then
		' Es segmentada -> Bloqueamos por defecto
		t.MaxXCliente = "0"
		t.MaxRecurrente = "0"
		t.MaxPromos = "0"
        
		Dim ps2 As Cursor = sql.ExecQuery($"Select * from HIST_CLIENTE_CANT_PROMOS where HCCP_PROMO = '${promo}' and HCCP_CLIENTE = '${cliente}' and HCCP_CANT > HCCP_CANT_VENDIDA"$)
		If ps2.RowCount > 0 Then
			ps2.Position = 0
			t.MaxXCliente = (ps2.GetInt("HCCP_CANT") - ps2.GetInt("HCCP_CANT_VENDIDA"))
			t.MaxRecurrente = ps2.GetString("HCCP_CANT")
			t.MaxPromos = ps2.GetString("HCCP_CANT")
		End If
		ps2.Close
	End If
	ps.Close
    
	' --- 3. HISTÓRICO ---
	Dim siHistorico As String = "0"
	c = sql.ExecQuery("Select count(*) as hist from HIST_PROMOS where HP_CLIENTE = '"& cliente & "' and HP_CODIGO_PROMOCION = '" & promo & "'")
	c.Position = 0
	If c.GetInt("hist") > 0 Then siHistorico = "1"
	t.Historico = siHistorico
	c.Close
    
	' --- 4. DETALLES Y PRODUCTOS ---
	c = sql.ExecQuery("Select * from CAT_DETALLES_PAQ where CAT_DP_ID = '"& promo & "'")
	If c.RowCount > 0 Then
		t.Resultado = "ok"
		For i = 0 To c.RowCount -1
			c.Position = i
			Dim idProd As String = c.GetString("CAT_DP_IDPROD")
            
			' Llenamos el mapa de productos (igual que el original para compatibilidad)
			t.Productos.Put(idProd, CreateMap("idProducto":idProd, "precioSimptos":c.GetString("CAT_DP_PRECIO_SIMPTOS"), "precio":c.GetString("CAT_DP_PRECIO"), "tipo":c.GetString("CAT_DP_TIPO"), "piezas":c.GetString("CAT_DP_PZAS"), "regalo":c.GetString("CAT_DP_REGALO"), "clasif":c.GetString("CAT_DP_CLASIF")))
			t.Tipos.Put(idProd, c.GetString("CAT_DP_TIPO"))
            
			If c.GetString("CAT_DP_TIPO") = "0" Then
				t.ProdsFijos.Add(idProd)
				t.ProdsFijosPrecios.Add(c.GetString("CAT_DP_PRECIO"))
				t.ProdsFijosPiezas.Add(c.GetString("CAT_DP_PZAS"))
			End If
			If c.GetString("CAT_DP_TIPO") = "1" Then
				t.ProdsVariables.Add(idProd)
				t.ProdsVariablesPrecios.Add(c.GetString("CAT_DP_PRECIO"))
			End If
		Next
	Else
		t.Resultado = "No hay datos de la promoción."
		t.MensajeError = "Sin detalles"
	End If
	c.Close
    
	' --- 5. VARIABLES REQUERIDAS ---
	c = sql.ExecQuery("Select CAT_GP_STS, CAT_GP_NOMBRE from CAT_GUNAPROD2 where CAT_GP_ID = '"& promo & "'")
	If c.RowCount > 0 Then
		c.Position = 0
		t.ProdsVariablesRequeridos = c.GetInt("CAT_GP_STS")
		t.Descripcion = c.GetString("CAT_GP_NOMBRE")
	Else
		t.ProdsVariablesRequeridos = 0
	End If
	c.Close

	Return t
End Sub

' =============================================================================
' LÓGICA DE CÁLCULO 1: FIJOS (Clon de Subs.revisaMaxPromosProdsFijosPorInventario)
' =============================================================================
Public Sub RevisaMaxPromosProdsFijos(pm As TPromoLegacy) As Int
	Dim tLista As List
	tLista.Initialize
    
	' 1. Agregamos el máximo configurado
	tLista.Add(TraeMaxPromos(pm))
    
	' 2. Obtenemos inventario real
	Dim invDispParaPromo As Map = TraemosInventarioDisponibleParaPromo(pm.Id)
    
	Dim prodsFijosPiezas As List = pm.ProdsFijosPiezas
	Dim idProdsFijos As List = pm.ProdsFijos
	Dim idProdsFijosPrecios As List = pm.ProdsFijosPrecios
    
	' 3. Iteramos sobre los fijos
	For p = 0 To idProdsFijos.Size -1
		Dim thisInvDisp As Int = 0
		If invDispParaPromo.Get(idProdsFijos.Get(p)) <> Null Then
			thisInvDisp = invDispParaPromo.Get(idProdsFijos.Get(p))
		End If
        
		Dim pzasReq As Int = prodsFijosPiezas.Get(p)
        
		If pzasReq > 0 Then
			Dim division As Double = thisInvDisp / pzasReq
			Dim x() As String = Regex.Split("\.", $"${division}"$)
			tLista.Add(x(0).As(Int))
            
			' --- AQUI ESTA TU CAMBIO (DIRECTO Y SIN DOLOR) ---
			' Nota: Cambié pm.Get("id") por pm.Id porque ahora es un Type
            
			Dim maxTS As Int = B4XPages.MainPage.promos.ts.traeBonificacionesMaximas("bonificaciones", mClienteActual, idProdsFijos.Get(p), pzasReq, idProdsFijosPrecios.Get(p), pm.Id)
            
			tLista.Add(maxTS)
			' -------------------------------------------------
            
		Else
			Log($"Promo ${pm.Id} mal configurada (0 piezas requeridas en producto fijo)"$)
			ToastMessageShow($"Promo ${pm.Id} mal configurada"$, True)
			tLista.Add(0)
		End If
	Next
    
	tLista.Sort(True)
	Return tLista.Get(0)
End Sub

' =============================================================================
' LÓGICA DE CÁLCULO 2: MÁXIMOS (Clon de Subs.traeMaxPromos)
' =============================================================================
Public Sub TraeMaxPromos(pm As TPromoLegacy) As Int
	Dim maxPromos As List
	maxPromos.Initialize
    
	' A. Histórico del Cliente (HCCP)
	Dim hccp As Cursor = sql.ExecQuery($"select HCCP_CANT from HIST_CLIENTE_CANT_PROMOS where HCCP_CLIENTE = '${mClienteActual}' and HCCP_PROMO = '${pm.Id}'"$)
	If hccp.RowCount > 0 Then
		hccp.Position = 0
		maxPromos.Add(hccp.GetInt("HCCP_CANT"))
	End If
	hccp.Close

	' B. Trade Spending Variables
	If ts <> Null Then
		Dim maxPromosXDescPV As String = B4XPages.MainPage.promos.ts.maxPromosPorProdsVariables(pm.ProdsVariables, pm.Id)
		maxPromos.Add(maxPromosXDescPV.As(Int))
	End If

	' C. Límites Generales
	If pm.Historico = "1" And pm.MaxRecurrente <> "null" Then maxPromos.Add(pm.MaxRecurrente.As(Int))
	If pm.MaxPromos <> "null" Then maxPromos.Add(pm.MaxPromos.As(Int))
	If pm.MaxXCliente <> "null" Then maxPromos.Add(pm.MaxXCliente.As(Int))

	maxPromos.Sort(True)

	Dim mp As Int = 0
	If maxPromos.Size > 0 Then
		Dim mp0 As Int = maxPromos.Get(0)
		' Restamos lo ya vendido
		mp = mp0 - TraePromosVendidas(pm.Id, mClienteActual)
	End If

	Return mp
End Sub

' =============================================================================
' LÓGICA DE CÁLCULO 3: RESTA INVENTARIO FIJO (Clon de Subs.restaFijosPromo)
' =============================================================================
Public Sub restaFijosPromo(pm As TPromoLegacy) As Map
	Private thisLog As Boolean = False 'Si es verdadero, muestra los logs de este sub.
	Private inventariosDisponiblesParaEstaPromo As Map = TraemosInventarioDisponibleParaPromo(pm.Id) 'Obtenemos un mapa con el inventario disponible para cada producto de la promocion desde la base de datos.
	
	If thisLog Then LogColor(inventariosDisponiblesParaEstaPromo, Colors.red)
	If thisLog Then LogColor("Inventario inicial antes de FIJOS: "&inventariosDisponiblesParaEstaPromo, Colors.Gray) 'Inventario inicial.
	
	Private i As Int
	Private prodsmap As Map = pm.productos 'Obtenemos un mapa con todos los productos de la promoción.
	Private prodsFijos As List = pm.ProdsFijos 'Obtenemos una lista con los productos fijos de la promoción.
	
	If thisLog Then LogColor("ProdsFijos -> " & prodsFijos, Colors.red)
	inventariosDisponiblesParaEstaPromo.Put("resultado", "No hay suficiente producto para la promocion.") 'Valor por default
	
	If thisLog Then Log($"Prods fijos requeridos ${prodsFijos.Size}"$)
	If prodsFijos.Size = 0 Then inventariosDisponiblesParaEstaPromo.Put("resultado", "ok") 'Si no lleva prods fijos la promo, entonces ponemos FIJOS OK.
	
	For p = 0 To prodsFijos.Size - 1
		Private t As String = prodsFijos.Get(p) 'Obtenemos el Id de este producto desde la lista de productos fijos.
		Private p2 As Map = prodsmap.Get(t) 'Obtenemos un mapa con los datos de este producto (id, precio, almacen, tipo, piezas, etc.)
		If thisLog Then Log($"T: ${t}, prod ${p2.Get("idProducto")}, piezas: ${p2.Get("piezas")}"$) 'Producto y piezas requeridas
		If thisLog Then Log("inventariosDisponiblesParaEstaPromo="&inventariosDisponiblesParaEstaPromo)
		
		If inventariosDisponiblesParaEstaPromo.ContainsKey(t) Then 'Si el mapa del inventario contiene el id del producto entonces ...
			i = inventariosDisponiblesParaEstaPromo.get(t) 'Obtenemos del mapa el inventario de este producto.
			Private nuevoInv As Int = NumberFormat2((i - pm.ProdsFijosPiezas.Get(p)), 1, 0,0,False)
			If thisLog Then Log($"Nuevo inventario de ${t}: ${i}-${pm.ProdsFijosPiezas.Get(p)} = ${nuevoInv}"$) 'El nuevo inventario.
			inventariosDisponiblesParaEstaPromo.Put(t, $"${nuevoInv}"$) 'Restamos del inventario las piezas requeridas para la promoción y guardamos el nuevo inventario en el mapa.
			inventariosDisponiblesParaEstaPromo.Put("resultado", "ok")
		Else 'Si en el mapa no esta el id del producto, entonces no tenemos inventario.
			inventariosDisponiblesParaEstaPromo.Put("resultado", "No hay suficiente producto para la promocion.")
			If thisLog Then LogColor("Sin suficiente inventario fijo: " & t, Colors.Blue)
			Exit
		End If
		
		If i - p2.Get("piezas") < 0 Then
			inventariosDisponiblesParaEstaPromo.Put("resultado", "No hay suficiente producto para la promocion.") 'Si el inventario de este producto sale negativo, quiere decir que no tenemos suficiente inventario para la promoción.
			Exit
		End If
	Next
	
	If thisLog Then LogColor("Inventario final despues de FIJOS: "&inventariosDisponiblesParaEstaPromo, Colors.blue) 'Inventario final.
	Return inventariosDisponiblesParaEstaPromo
End Sub

' =============================================================================
' LÓGICA DE CÁLCULO 4: VARIABLES (Clon de Subs.alcanzanLosVariablesParaPromo)
' =============================================================================
Public Sub alcanzanLosVariablesParaPromo(pm As TPromoLegacy, inventarioSinFijos As Map) As Boolean
	Private thisLog As Boolean = False 'Si es verdadero, muestra los logs de este sub.
	If thisLog Then LogColor("Inventario inicial: "&inventarioSinFijos, Colors.Gray) 'Inventario inicial.
	
	Private totalProdsVariables As Int = 0
	Private prodsVariables As List = pm.ProdsVariables 'Obtenemos la lista con los productos variables de la promoción.
	
	For p = 0 To prodsVariables.Size - 1
		Private t As String = prodsVariables.Get(p) 'Obtenemos el Id de este producto desde la lista de productos fijos.
		If inventarioSinFijos.ContainsKey(t) Then 'Si existe el producto en la lista del inventario, entonces ...
			Private p2 As Int = inventarioSinFijos.Get(t) 'Obtenemos el inventario disponible este producto.
			If thisLog Then Log($"prod ${t}, hay: ${p2}"$) 'Producto y piezas requeridas
			totalProdsVariables = totalProdsVariables + p2
		End If
	Next
	
	If thisLog Then Log("Total prods variables=" & totalProdsVariables & ", requeridos=" & pm.ProdsVariablesRequeridos)
	
	Private res As Boolean = False
	If totalProdsVariables >= pm.ProdsVariablesRequeridos Then res = True 'Si el total de inventario de productos variables (totalProdsVariables) es mayor o igual a los productos requeridos entonces regresamos TRUE
	
	Return res
End Sub

' =============================================================================
' HELPERS PRIVADOS (Para hacer la clase portable sin depender de Subs.bas)
' =============================================================================

Private Sub TraePromosVendidas(promo As String, cliente As String) As Int
	Dim c As Cursor
	Dim pv As Int = 0
	c = sql.ExecQuery($"select sum(PE_CANT) as cuantas from PEDIDO where PE_PROID = '${promo}' and PE_CLIENTE = '${cliente}'"$)
	If c.RowCount > 0 Then
		c.Position = 0
		If c.GetString("cuantas") <> Null Then pv = c.GetInt("cuantas")
	End If
	c.Close
	Return pv
End Sub

Private Sub TraemosInventarioDisponibleParaPromo(promo As String) As Map
	Dim c As Cursor
	c = sql.ExecQuery2("SELECT CAT_GP_ID, CAT_GP_ALMACEN FROM CAT_GUNAPROD2 WHERE CAT_GP_ID IN (select CAT_DP_IDPROD FROM CAT_DETALLES_PAQ WHERE CAT_DP_ID = ?)", Array As String(promo))
	Dim prods As Map
	prods.Initialize
	If c.RowCount > 0 Then
		For i=0 To c.RowCount -1
			c.Position=i
			prods.Put(c.GetString("CAT_GP_ID"), c.GetString("CAT_GP_ALMACEN"))
		Next
	End If
	c.Close
	Return prods
End Sub