B4A=true
Group=Default Group
ModulesStructureVersion=1
Type=StaticCode
Version=11
@EndOfDesignText@
'Code module
'Subs in this code module will be accessible from all modules.
Sub Process_Globals
	'These global variables will be declared once when the application starts.
	'These variables can be accessed from all modules.
	Public GZip As GZipStrings 		'Usa la libreria CompressStrings
	Private su As StringUtils 		'Usa la libreria StringUtils
	Dim phn As Phone
	Dim devModel As String
	Dim kmt, errorLog As SQL 'Requiere la libreria "SQL"
'	Dim wifi As MLwifi
	Dim ssid As String  'ignore
	Dim rutaMaxPoints As Int = 3000
	Dim rutaHrsAtras As Int = 48
'	Dim rutaInicioHoy As String = ""
	Private subsLogs As Boolean = False
	Dim skmt As SQL
	Dim in As Intent
	Dim intentUsado As Boolean = False
End Sub

'Pone el valor de phn.Model en la variable global "devModel"
Sub getPhnId As String 'ignore
	'Requiere la libreria "Phone"
	devModel = phn.Model
	If devModel.Length <= 3 Then 'Si phn.Model esta en blanco ...
		Dim t As String = phn.GetSettings("android_id") 'Intentamos con "android_id"
		devModel = t
	End If
	If devModel.Length >= 3 Then 'Si tenemos valor para phn.Model
		File.WriteString(File.DirInternal, "phnId.txt", devModel) 'Sobreescribimos archivo phnId.txt with deviceId
'		Log("Tenemos phnId: "&devModel&" "&File.DirInternal&"/phn.txt sobreescrito")
	Else If devModel.Length < 3 Then ' Si no tenemos valor, lo leemos de phnId.txt
		Dim s As String = File.ReadString(File.DirInternal, "phnId.txt")
		devModel = s
'		Log("Leemos id de "&File.DirInternal&"/phnId.txt")
'		Log(devModel)
	End If
	Return devModel
End Sub

Sub SetDivider(lv As ListView, Color As Int, Height As Int) 'ignore
	Dim r As Reflector
	r.Target = lv
	Dim CD As ColorDrawable
	CD.Initialize(Color, 0)
	r.RunMethod4("setDivider", Array As Object(CD), Array As String("android.graphics.drawable.Drawable"))
	r.RunMethod2("setDividerHeight", Height, "java.lang.int")
End Sub

'Centra un listview dentro de un elemento superior
Sub centraListView(elemento As ListView, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub


'Comprime y regresa un texto (str) en base64
Sub compress(str As String) As String 'ignore
	'Requiere la libreria "CompressStrings"
	Dim compressed() As Byte = GZip.compress(str)
'	Log($"UncompressedBytesLength: ${str.Length}"$)
'	Log($"CompressedBytesLength: ${compressed.Length}"$)
	Dim base64 As String = su.EncodeBase64(compressed)
	Log($"Comprimido: ${base64.Length}"$)
'	Log($"CompressedBytes converted to base64: ${base64}"$)
	Return base64
End Sub

'Descomprime y regresa un texto en base64
Sub decompress(base64 As String) As String 'ignore
	Dim decompressedbytes() As Byte = su.DecodeBase64(base64)
'	Log($"decompressedbytesLength: ${decompressedbytes.Length}"$)
	Dim bc As ByteConverter
	Dim uncompressed As String = bc.StringFromBytes(decompressedbytes,"UTF8")
	Log($"Descomprimido: ${uncompressed.Length}"$)
'	Log($"Decompressed String = ${uncompressed}"$)
	Return uncompressed
End Sub

'Convierte una fecha al formato yyMMddHHmmss
Sub fechaKMT(fecha As String) As String 'ignore
'	Log(fecha)
	Dim OrigFormat As String = DateTime.DateFormat  'save orig date format
	DateTime.DateFormat="yyMMddHHmmss"
	Dim nuevaFecha As String=DateTime.Date(fecha)
	DateTime.DateFormat=OrigFormat  'return to orig date format
'	Log(nuevaFecha)
	Return nuevaFecha
End Sub

'Genera una notificacion con importancia alta
Sub notiHigh(title As String, body As String, activity As Object) 'ignore
	Private notif As Notification
	notif.Initialize2(notif.IMPORTANCE_HIGH)
	notif.Icon = "icon"
	notif.Vibrate = False
	notif.Sound = False
	notif.AutoCancel = True
	Log("notiHigh: "&title)
	notif.SetInfo(title, body, activity)
'	Log("notiHigh SetInfo")
	notif.Notify(777)
End Sub

'Regresa el objeto de una notificacion con importancia baja
Sub notiLowReturn(title As String, Body As String, id As Int) As Notification 'ignore
	Private notification As Notification
	notification.Initialize2(notification.IMPORTANCE_LOW)
	Log("notiLowReturn: "&title)
	notification.Icon = "icon"
	notification.Sound = False
	notification.Vibrate = False
	notification.SetInfo(title, Body, Main)
	notification.Notify(id)
'	Log("notiLowReturn SetInfo")
	Return notification
End Sub

'Escribimos las coordenadas y fecha a un archivo de texto 
Sub guardaInfoEnArchivo(coords As String) 'ignore
	'	Cambiamos el formato de la hora
	Dim OrigFormat As String=DateTime.DateFormat  'save orig date format
	DateTime.DateFormat="MMM-dd HH:mm:ss"
	Dim lastUpdate As String=DateTime.Date(DateTime.Now)
	DateTime.DateFormat=OrigFormat  'return to orig date format

	Dim ubic As String = coords&","&lastUpdate
	Dim out As OutputStream = File.OpenOutput(File.DirRootExternal, "gps.txt", True)
	Dim s As String = ubic & CRLF
	Dim t() As Byte = s.GetBytes("UTF-8")
	out.WriteBytes(t, 0, t.Length)
	out.Close
End Sub

'Escribimos las coordenadas (latitud, longitud, fecha) y fecha a una BD
Sub guardaInfoEnBD(coords As String) 'ignore
	Log("Guardamos ubicacion en BD - "&coords)
	Try
		Dim latlon() As String = Regex.Split("\|", coords)
		If latlon.Length < 2 Then latlon = Regex.Split(",", coords) 'Si son menos de 2, entonces estan separadas por comas y no por "|"
		If subsLogs Then Log("LatLon="&latlon)
		kmt.ExecNonQuery2("INSERT INTO RUTA_GPS(FECHA, LAT, LON) VALUES (?,?,?)", Array As Object (latlon(2),latlon(0),latlon(1)))
	Catch
		LogColor(LastException, Colors.red)
	End Try
End Sub

'Regresa la ruta solicitada comprimida y en base64
Sub dameRuta(inicioRuta As String, origenRuta As String) As String 'ignore
	'Requiere la libreria "SQL"
	Dim fechaInicio As String
	Try 'incioRuta es numero
		inicioRuta = inicioRuta * 1
'		Log("fechaInicio numerica="&fechaInicio)
		fechaInicio = fechaKMT(DateTime.Now - (DateTime.TicksPerHour * inicioRuta))
	Catch 'inicioRuta es string
		fechaInicio = fechaInicioHoy
'		Log("fechaInicio string="&fechaInicio)
	End Try
	If subsLogs Then Log("fechaInicio: "&fechaInicio&" | rutaHrsAtras="&rutaHrsAtras)   'fechaKMT(DateTime.Now)
	Dim c As Cursor
	If kmt.IsInitialized = False Then kmt.Initialize(Starter.ruta, "kmt.db", True)
	If subsLogs Then Log("select FECHA, LAT, LON from "& origenRuta &" where FECHA > " & fechaInicio & " order by FECHA desc limit " & rutaMaxPoints)
	c = kmt.ExecQuery("select FECHA, LAT, LON from "& origenRuta &" where FECHA > " & fechaInicio & " order by FECHA desc limit " & rutaMaxPoints)
	c.Position = 0
	Dim ruta2 As String = ""
	If c.RowCount>0 Then
		For i=0 To c.RowCount -1
			c.Position=i
			ruta2=ruta2&CRLF&c.GetString("LAT")&","&c.GetString("LON")&","&c.GetString("FECHA")
			B4XPages.MainPage.fechaRuta = c.GetString("FECHA")
		Next
	End If
	c.Close
	Return compress(ruta2)
End Sub

'Limpiamos la tabla RUTA_GPS de la BD
Sub deleteGPS_DB 'ignore
	kmt.ExecNonQuery("delete from RUTA_GPS")
	kmt.ExecNonQuery("vacuum;")
	ToastMessageShow("Borramos BD Coords GPS", False)
End Sub

'Limpiamos la tabla errorLog de la BD
Sub deleteErrorLog_DB 'ignore
	errorLog.ExecNonQuery("delete from errores")
	errorLog.ExecNonQuery("vacuum;")
	ToastMessageShow("BD Errores Borrada", False)
End Sub

'Borramos el archio "gps.txt"
Sub borramosArchivoGPS 'ignore
	Dim out As OutputStream = File.OpenOutput(File.DirRootExternal, "gps.txt", False)
	Dim s As String = ""
	Dim t() As Byte = s.GetBytes("UTF-8")
	out.WriteBytes(t, 0, t.Length)
	out.Close
End Sub

'Revisa que exista la BD y si es necesario crea algunans tablas dentro de ella
Sub revisaBD 'ignore
'	Main.ruta = File.DirInternal
	If Not(File.Exists(Starter.ruta, "kmt.db")) Then File.Copy(File.DirAssets, "kmt.db", Starter.ruta, "kmt.db")
	If Not(kmt.IsInitialized) Then kmt.Initialize(Starter.ruta, "kmt.db", True)
	kmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS RUTA_GPS(FECHA INTEGER, LAT TEXT, LON TEXT)")
'	kmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS UUC(fecha INTEGER, lat TEXT, lon TEXT)") 'LastKnownLocation
	kmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS bitacora(fecha INTEGER, texto TEXT)") 'Bitacora
	'Tabla para la bitacora de errores
	If Not(errorLog.IsInitialized) Then errorLog.Initialize(Starter.ruta, "errorLog.db", True)
	errorLog.ExecNonQuery("CREATE TABLE IF NOT EXISTS errores(fecha INTEGER, error TEXT)")
End Sub

Sub fechanormal(fecha As String) As String 'ignore
'	Log(fecha)
	Dim OrigFormat As String = DateTime.DateFormat  'save orig date format
	DateTime.DateFormat = "YYYY/MM/dd HH:mm:ss"
	Dim nuevaFecha As String=DateTime.Date(fecha)
	DateTime.DateFormat = OrigFormat  'return to orig date format
'	Log(nuevaFecha)
	Return nuevaFecha
End Sub

'Obtiene el ssid al que esta conectado el telefono
Sub getSSID  'ignore
'	'Requiere la libreria "MLWifi400"
'	If wifi.isWifiConnected Then
'		ssid = wifi.WifiSSID
'	End If
End Sub

'Convierte un texto en formato JSON a un objeto "Map"
Sub JSON2Map(theJson As String) As Map 'ignore
	'Requiere la libreria "JSON"
	Try
		Private json As JSONParser
		json.Initialize(theJson)
		Return json.NextObject
	Catch
		Log(LastException)
		log2DB("JSON2Map: "&LastException)
		Private m As Map = CreateMap("title":"Error generating JSON", "t":"Error", "Message":LastException, "text" : LastException)
		Return m
	End Try
End Sub

'Convierte un mapa a formato JSON
Sub map2JSON(m As Map) As String 'ignore
	'Requiere la libreria "JSON"
	'Convierte un objecto "Map" a JSON
	Dim jg As JSONGenerator
	jg.Initialize(m)
	Dim t As String = jg.ToString
	Return t
End Sub

'Mandamos "coords" en un mensaje a "Sprvsr"
'Sub mandamosLoc(coords As String) 'ignore
''	Log("Iniciamos mandamosLoc "&coords)
''	Log("locRequest="&Tracker.locRequest)
'	guardaInfoEnBD(coords)'Escribimos coordenadas y fecha a una bd
'	Dim t As String
'	If Tracker.locRequest="Activa" Then
'		If PushService.au = 1 Then
'			t = "au" ' es una actualizacion
'		Else
'			t = "u" ' es una peticion
'		End If
'		Dim params As Map = CreateMap("topic":"Sprvsr", "coords":coords, "t":t, "b":PushService.battery, "mt":Main.montoActual)
'		CallSub2(PushService, "mandaMensaje",params)
'		Tracker.locRequest="Enviada"
'		CallSubDelayed(Tracker,"CreateLocationRequest")
'	End If
'End Sub

'Regresa la fecha y hora de hoy a las 00:00 en el formato "yyMMddHHMMSS"
Sub fechaInicioHoy As String 'ignore
		Dim OrigFormat As String = DateTime.DateFormat  'save orig date format
		DateTime.DateFormat="yyMMdd"
		Private h As String = DateTime.Date(DateTime.Now)&"000000"
		DateTime.DateFormat=OrigFormat  'return to orig date format
	Log("Hoy="&h)
	Return h
End Sub

'Guardamos "texto" a la bitacora
Sub log2DB(texto As String) 'ignore
	LogColor(fechaKMT(DateTime.Now)&" - log2BD: '"&texto&"'", Colors.LightGray)
	kmt.ExecNonQuery2("INSERT INTO bitacora(fecha, texto) VALUES (?,?)", Array As Object (fechaKMT(DateTime.now), texto))
End Sub

'Regresa verdadero si ya pasaron XX minutos de la fecha dada
Sub masDeXXMins(hora As Int, mins As Int) As Boolean 'ignore
	If (hora + mins * DateTime.TicksPerMinute) < DateTime.Now Then
		Return True
	Else
		Return False
	End If
End Sub

'Regresa verdadero si ya pasaron XX minutos de la fechaKMT dada
Sub masDeXXMinsKMT(hora As String, mins As Int) As Boolean 'ignore
	Try
	'	LogColor($"Hora=${fechaKMT(fechaKMT2Ticks(hora) + mins * DateTime.TicksPerMinute)}, Mins=${mins}, Actual=${fechaKMT(DateTime.Now)}"$,Colors.red)
		If fechaKMT2Ticks(hora) + mins * DateTime.TicksPerMinute < DateTime.Now Then
	'		Log("+++ +++ "&fechaKMT(fechaKMT2Ticks(hora) + mins * DateTime.TicksPerMinute) & " < " & fechaKMT(DateTime.Now))
			Return True
		Else
	'		Log("+++ +++ "&fechaKMT(fechaKMT2Ticks(hora) + mins * DateTime.TicksPerMinute) & " > " & fechaKMT(DateTime.Now))
			Return False
		End If
	Catch
		Log(LastException)
	End Try
End Sub

'Limpiamos la tabla "bitacora" de la BD
Sub borraLogDB 'ignore
	LogColor("Borramos BD de log", Colors.Magenta)
	kmt.ExecNonQuery("delete from bitacora")
	kmt.ExecNonQuery("vacuum;")
End Sub

'Monitoreamos los servicios para ver si estan activos (No pausados), y si no, los reniciamos
'Sub Monitor 'ignore
'	Private monitorStatus As Boolean = True
'	LogColor("Corriendo Subs.Monitor", Colors.RGB(161,150,0))
'	If IsPaused(Tracker) Then
'		log2DB("Reiniciando 'Tracker Pausado' desde Subs.Monitor")
'		StartService(Tracker)
'		monitorStatus = False
'	Else
'		CallSubDelayed(Tracker, "revisaFLP")
'	End If
'	If IsPaused(PushService) Then
'		log2DB("Reiniciando 'PushService Pausado' desde Subs.Monitor")
'		StartService(PushService)
'		monitorStatus = False
'	Else
'		revisaPushService
'	End If
'	If monitorStatus Then LogColor(" +++ +++ Servicios Activos", Colors.Green)
'End Sub

'Compara la UUG (Ultima Ubicacion Guardada) con FLP.LastKnowLocation y si
'cumple con los requisitos de distancia y precision la guardamos en la BD.
Sub revisaUUG 'ignore
	Try
'		revisaFLP
		If Tracker.FLP.GetLastKnownLocation.IsInitialized Then
			Dim daa As Int = Tracker.UUGCoords.DistanceTo(Tracker.FLP.GetLastKnownLocation) 'Distancia de la UUG a la actual de Tracker.FLP.GetLastKnownLocation
			If Starter.Logger Then LogColor($"**** UUC "${fechaKMT(Tracker.FLP.GetLastKnownLocation.Time)}|$0.2{Tracker.FLP.GetLastKnownLocation.Accuracy}|$0.8{Tracker.FLP.GetLastKnownLocation.Latitude}|$0.8{Tracker.FLP.GetLastKnownLocation.Longitude}|$0.2{Tracker.FLP.GetLastKnownLocation.Speed}|"$, Colors.RGB(255,112,35))
			If daa > 40 And Tracker.FLP.GetLastKnownLocation.Accuracy < 35 Then 'Si la distancia de la ubicacion anterior es mayor de XX y la precision es menor de XX, la guardamos ...
				kmt.ExecNonQuery2("INSERT INTO RUTA_GPS(fecha, lat, lon) VALUES (?,?,?)", Array As Object (fechaKMT(Tracker.FLP.GetLastKnownLocation.Time),Tracker.FLP.GetLastKnownLocation.Latitude,Tracker.FLP.GetLastKnownLocation.Longitude))
				If Starter.Logger Then Log("++++ Distancia a anterior="&daa&"|"&"Precision="&Tracker.FLP.GetLastKnownLocation.Accuracy)
			End If
			Tracker.UUGCoords = Tracker.FLP.GetLastKnownLocation
		End If
	Catch
		LogColor("If Tracker.FLP.GetLastKnownLocation.IsInitialized --> "&LastException, Colors.Red)
	End Try
End Sub

'Revisamos que el FLP (FusedLocationProvider) este inicializado y activo
Sub revisaFLP 'ignore
	LogColor("**** **** Revisamos FLP **** ****", Colors.RGB(78,0,227))
	Private todoBienFLP As Boolean = True
	Try
		If Not(Tracker.FLP.IsInitialized) Then
			log2DB("revisaFLP: No esta inicializado ... 'Reinicializando FLP'")
			Tracker.FLP.Initialize("flp")
			todoBienFLP = False
		End If
	Catch
		LogColor("If Not(Tracker.FLP.IsInitialized) --- "&LastException, Colors.Red)
	End Try
	Try
		If Tracker.FLP.IsInitialized Then
			Try
				If Not(Tracker.FLP.IsConnected) Then
					log2DB("revisaFLP: No esta conectado ... 'Reconectando FLP'")
		'			Tracker.FLP.Connect
					CallSubDelayed(Tracker,"StartFLP")
					todoBienFLP = False
				End If
			Catch
				LogColor("If Not(Tracker.FLP.IsConnected) --> "&LastException, Colors.Red)
			End Try
			Try
				If Tracker.FLP.IsConnected And _
					Tracker.FLP.GetLastKnownLocation.IsInitialized And _
					Tracker.FLP.GetLastKnownLocation.DistanceTo(Tracker.UUGCoords) > 500 Then
					log2DB("revisaFLP: 'No se esta actualizando, lo reiniciamos ...'")
					StartService(Tracker)
					todoBienFLP = False
				End If
			Catch
				LogColor("If FLP.IsConnectctd and FLP.getLKL.IsInitialized --> "&LastException, Colors.Red)
			End Try
		End If
		If todoBienFLP Then LogColor(" +++ +++ Sin errores en FLP", Colors.Green)
	Catch
		LogColor("If Tracker.FLP.IsInitialized --> "&LastException, Colors.Red)
	End Try
	' revisar hora de lastKnownlocation y si es mayor de 10 minutos llamar StartFLP
End Sub

'Revisamos que el servicio "PushService" este inicializado y activo
'Sub revisaPushService 'ignore
'	Private todoBienPS As Boolean = True
'	LogColor("**** **** Revisamos PushService **** ****", Colors.RGB(78,0,227))
'	If Not(PushService.wsh.IsInitialized) Then 'Si no esta inicializado ...
'		log2DB("revisaPushService: No esta inicializado ... 'Reinicializando PushService'")
'		CallSubDelayed(PushService, "Connect")
'		todoBienPS = False
'	End If
'	If Not(PushService.wsh.ws.Connected) Then 'Si no esta conectado ...
'		log2DB("revisaPushService: No esta conectado ... 'Reconectando PushService'")
'		CallSubDelayed(PushService, "Connect")
'		todoBienPS = False
'	End If
'	If masDeXXMinsKMT(Starter.pushServiceActividad, 5) Then  'Si mas de xx minutos de la ultima actividad entonces ...
'		PushService.wsh.Close
'		CallSubDelayed(PushService, "Connect")
''		StartService(PushService)
'		log2DB("revisaPushService: 'Reconectamos 'PushService' por inactividad")
'		Starter.pushServiceActividad = fechaKMT(DateTime.Now)
'		todoBienPS = False
'	End If
'	If todoBienPS Then LogColor(" +++ +++ Sin errores en PushService", Colors.Green)
'End Sub

'Borramos renglones extra de la tabla de errores
Sub borraArribaDe100Errores 'ignore
	revisaBD
	LogColor("Borramos BD de log", Colors.Magenta)
	errorLog.ExecNonQuery("DELETE FROM errores WHERE fecha NOT in (SELECT fecha FROM errores ORDER BY fecha desc LIMIT 99 )")
	errorLog.ExecNonQuery("vacuum;")
	Log("Borramos mas de 100 de errorLog")
End Sub

'Borramos renglones extra de la tabla de bitacora
Sub borraArribaDe600RenglonesBitacora 'ignore
	revisaBD
	If Starter.logger Then LogColor("Recortamos la tabla de la Bitacora, limite de 600", Colors.Magenta)
	Private c As Cursor
	c = B4XPages.MainPage.skmt.ExecQuery("select fecha from bitacora")
	c.Position = 0
	If c.RowCount > 650 Then
		B4XPages.MainPage.skmt.ExecNonQuery("DELETE FROM bitacora WHERE fecha NOT in (SELECT fecha FROM bitacora ORDER BY fecha desc LIMIT 599 )")
		B4XPages.MainPage.skmt.ExecNonQuery("vacuum;")
'		if starter.logger then Log("Borramos mas de 600 de bitacora")
	End If
	c.Close
End Sub

'Inserta 50 renglones de prueba a la tabla "errores"
Sub insertaRenglonesPruebaEnErrorLog 'ignore
	revisaBD
	Log("insertamos 50 renglones a errorLog")
	For x = 1 To 50
		errorLog.ExecNonQuery2("INSERT INTO errores(fecha, error) VALUES (?,?)", Array As Object (fechaKMT(DateTime.now), "abc"))
		Log(x)
	Next
End Sub

'Regresa la tabla "errores" en una lista de mapas convertida a JSON
Sub dameErroresJSON(SQL As SQL, maxErrores As Int, comprimido As Boolean) As String 'ignore
	Log("dameErroresJSON")
	Private j As JSONGenerator
	Private lim As String
	Private cur As ResultSet
	Private l As List
	Private i As Int = 0
	l.Initialize
	Dim m, m2 As Map
	m2.Initialize
	If maxErrores = 0 Then lim = "" Else lim = "limit "&maxErrores
	cur = SQL.ExecQuery("select * from errores order by fecha desc "&lim)
	Do While cur.NextRow
		m.Initialize
		m.Put("fecha", cur.GetString("fecha"))
		m.Put("error", cur.GetString("error"))
		m2.Put(i,m)
		i = i + 1
	Loop
	cur.Close
	j.Initialize(m2)
	Log(j.ToString)
	If comprimido Then 
		Return compress(j.ToString)
	Else 
		Return j.ToString
	End If
End Sub

'Convierte una fecha en formato YYMMDDHHMMSS a Ticks
Sub fechaKMT2Ticks(fKMT As String) As Long 'ignore
	Try
		If fKMT.Length = 12 Then
			Private parteFecha As String = fKMT.SubString2(0,6)
			Private parteHora As String = fKMT.SubString(6)
			Private OrigFormat As String = DateTime.DateFormat  'save original date format
			DateTime.DateFormat="yymmdd"
			DateTime.TimeFormat="HHmmss"
			Private ticks As Long = DateTime.DateTimeParse(parteFecha,parteHora)
			DateTime.DateFormat=OrigFormat  'return to original date format
			Return ticks
		Else
			Log("Formato de fecha incorrecto, debe de ser 'YYMMDDHHMMSS', no '"&fKMT&"' largo="&fKMT.Length)
			Return 0
		End If
	Catch
		Log(LastException)
		LogColor($"Fecha dada: ${fKMT}, Parte Fecha: ${parteFecha}, Parte Hora: ${parteHora}"$, Colors.Red)
		Return 0
	End Try
End Sub

Sub InstallAPK(dir As String, apk As String) 'ignore
	If File.Exists(dir, apk) Then
		Dim i As Intent
		i.Initialize(i.ACTION_VIEW, "file://" & File.Combine(dir, apk))
		i.SetType("application/vnd.android.package-archive")
		StartActivity(i)
	End If
End Sub

'Copia la base de datos del almacenamiento interno al externo en el directorio kmts
Sub copiaDB(result As Boolean) 'ignore
	ToastMessageShow("copiaDB", False)
	If result Then
		Dim p As String
		If File.ExternalWritable Then
			p = File.DirRootExternal
'			Log("Externo")
		Else
			p = File.DirInternal
'			Log("Interno")
		End If
		Dim theDir As String
		Try
			File.MakeDir(File.DirRootExternal,"kmts")
			theDir = "/kmts"
		Catch
			theDir = ""
		End Try
		Try
			File.Copy(File.DirInternal,"kmt.db",File.DirRootExternal&theDir,"cedex_kmt.db")
			File.Copy(File.DirInternal,"errorLog.db",File.DirRootExternal&theDir,"cedex_errorLog.db")
			ToastMessageShow("BD copiada!", False)
		Catch
			ToastMessageShow("No se pudo hacer la copia: "&LastException, True)
		End Try
		Log("rootExternal="&p)
		Log("File.DirInternal="&File.DirInternal)
		Log("File.DirRootExternal="&File.DirRootExternal)
	Else
		ToastMessageShow("Sin permisos", False)
	End If
End Sub

'Hace visible y trae al frente el panel con los parametros "Top" y "Left" dados
Sub panelVisible(panel As Panel, top As Int, left As Int) 'ignore
	panel.BringToFront
	panel.Visible = True
	panel.Top = top
	panel.Left = left
End Sub

'Centra una etiqueta dentro de un elemento superior
Sub centraEtiqueta(elemento As Label, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub

'Centra un panel horizontalmente dentro de un elemento superior
Sub centraPanel(elemento As Panel, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub

'Centra un panel verticalmente dentro de un elemento superior
Sub centraPanelV(elemento As Panel, altoElementoSuperior As Int) 'ignore
	elemento.Top = Round(altoElementoSuperior/2)-(elemento.Height/2)
End Sub

'Centra una barra de progreso dentro de un elemento superior
Sub centraProgressBar(elemento As ProgressBar, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub

Sub centraEditText(elemento As EditText, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub

'Regresa el usuario de la tabla USUARIOA si es que existe, si no existe, regresa "SinUsuario".
Sub buscaDBUsuario As String 'ignore
	Private c As Cursor
	Private usuario As String = "SinUsuario"
	c=kmt.ExecQuery("select USUARIO from usuarioa")
	c.Position=0
	If c.RowCount > 0 Then usuario = c.GetString("USUARIO")
	Return usuario
End Sub

'Saca el usuario de la tabla USUARIOA
Sub dameUsuarioDeDB As String 'ignore
	Private c As Cursor
	Private u As String = "SinUsuario"
	If Not(kmt.IsInitialized) Then revisaBD
	c=kmt.ExecQuery("select USUARIO from usuarioa")
	c.Position=0
	If c.RowCount > 0 Then u = c.GetString("USUARIO")
	c.Close
	Return u
End Sub

'Inserta un producto en la tabla "PEDIDO"
Sub guardaProductoX(cedis As String, costoTot As String, costoU As String, cant As String, nombre As String, prodId As String, clienteId As String, fecha As String, usuario As String, tipoV As String, precio2 As String, query As String) 'ignore
'	LogColor("guardaProducto", Colors.Magenta)
'	Log($"Guardamos producto ${prodId}"$)
'	B4XPages.MainPage.skmt.ExecNonQuery2("INSERT INTO PEDIDO (PE_CEDIS, PE_COSTO_TOT, PE_COSTOU, PE_CANT, PE_PRONOMBRE, PE_PROID, PE_CLIENTE, PE_FECHA, PE_USUARIO, PE_TIPO, PE_PRECIO2, PE_RUTA) VALUES(?,?,?,?,?,?,?,?,?,?,?,?) ", Array As Object (cedis, costoTot, costoU, cant, nombre, prodId, clienteId, fecha, usuario, tipoV, precio2, Starter.rutaV))
'	B4XPages.MainPage.skmt.ExecNonQuery2("update " & query & " set cat_gp_almacen = cat_gp_almacen - ? where cat_gp_id = ? ", Array As Object(cant, prodId))
'	ToastMessageShow("guardaProd", False)
End Sub

Sub guardaProductoSin(cedis As String, costoTot As String, costoU As String, cant As String, nombre As String, prodId As String, clienteId As String, fecha As String, usuario As String, rutaV As String, precioSin As String, tipoV As String, precio2 As String, query As String) 'ignore
'	LogColor("guardaProductoSin", Colors.Magenta)
'	B4XPages.MainPage.skmt.ExecNonQuery2("INSERT INTO PEDIDO (PE_CEDIS, PE_COSTO_TOT, PE_COSTOU, PE_CANT, PE_PRONOMBRE, PE_PROID, PE_CLIENTE, PE_FECHA, PE_USUARIO, PE_RUTA, PE_COSTO_SIN, PE_TIPO, PE_PRECIO2) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) ", Array As Object (cedis, costoTot,costoU, cant, nombre, prodId, clienteId, fecha, usuario, rutaV, precioSin, tipoV, precio2))
'	B4XPages.MainPage.skmt.ExecNonQuery2("update " & query & " set cat_gp_almacen = cat_gp_almacen - ? where cat_gp_id = ? ", Array As Object(cant, prodId))
'	DateTime.DateFormat = "MM/dd/yyyy"
'	Private sDate As String =DateTime.Date(DateTime.Now)
'	Private sTime As String =DateTime.Time(DateTime.Now)
'	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("select sum(pe_costo_tot) as TOTAL_CLIE, SUM(PE_CANT) AS CANT_CLIE, SUM(PE_COSTO_SIN) AS TOTAL_CLIE_SIN FROM PEDIDO WHERE PE_CLIENTE IN (Select CUENTA from cuentaa)")
'	c.Position=0
'	B4XPages.MainPage.skmt.ExecNonQuery("delete from pedido_cliente where PC_CLIENTE In (select cuenta from cuentaa)")
'	B4XPages.MainPage.skmt.ExecNonQuery2("insert into pedido_cliente(PC_CLIENTE, PC_FECHA, PC_USER, PC_NOART, PC_MONTO,PC_LON, PC_LAT,PC_ALMACEN,PC_RUTA,PC_COSTO_SIN) VALUES (?,?,?,?,?,?,?,?,?,?)", Array As Object(clienteId, sDate & sTime, usuario, c.GetString("CANT_CLIE"),c.GetString("TOTAL_CLIE"), B4XPages.MainPage.lon_gps, B4XPages.MainPage.lat_gps, cedis, rutaV, c.GetString("TOTAL_CLIE_SIN")))
'	B4XPages.MainPage.skmt.ExecNonQuery("UPDATE kmt_info3 set gestion = 2  where CAT_CL_CODIGO In (select cuenta from cuentaa)")
'	c.Close
	ToastMessageShow("guardaProdSin", False)
End Sub

'Regresa el almacen actual de la base de datos.
Sub traeAlmacen As String 'ignore
	Private c As Cursor
	Private a As String = "NA"
	c=B4XPages.MainPage.skmt.ExecQuery("select  ID_ALMACEN from CAT_ALMACEN")
	If c.RowCount > 0 Then
		c.Position = 0
		a = C.GetString("ID_ALMACEN")
	End If
	c.Close
	Return a
End Sub

'Regresa el nombre del producto desde CAT_GUNAPROD
Sub traeProdNombre(id As String) As String
	Private h As Cursor
	Private n As String
	h=B4XPages.MainPage.skmt.ExecQuery2($"select CAT_GP_NOMBRE from ${traeTablaProds(traeTipoVentaDeBD)} where CAT_GP_ID = ? "$, Array As String(id.Trim))
	If h.RowCount > 0 Then
		h.Position = 0
		n = h.GetString("CAT_GP_NOMBRE")
'		Log(h.RowCount&"|"&id&"|"&n&"|")
	End If
	h.Close
	If n = Null Or n="" Then n = "N/A"
'	Log(h.RowCount&"|"&id&"|"&n&"|")
	Return n
End Sub

''Regresa la ruta actual de la base de datos, de acuerdo al cliente actual en CUENTAA.
'Si hay cliente definido (en CUENTAA), trae la ruta de ese cliente, si no, trae la primera ruta de kmt_info3. (esto es por las suplencias)
Sub traeRuta As String 'ignore
	Private c As Cursor
	Private r As String = "0"
	Private idCliente As String = ""
	Try
		Private elCc As Cursor = Starter.skmt.ExecQuery($"select CUENTA from CUENTAA"$)
		If elCc.RowCount > 0 Then
			elCc.Position = 0
			idCliente = elCc.GetString("CUENTA")
			Log("|"&elCc.GetString("CUENTA")&"|")
		End If
'		Log($"#########################${CRLF}HAY USUARIO EN CUENTAA: ${elCc.RowCount}${CRLF}#########################"$)
		If idCliente <> "" Then
'			Log(1)
			c = B4XPages.MainPage.skmt.ExecQuery($"select distinct CAT_CL_RUTA from kmt_info3 where CAT_CL_CODIGO = '${idCliente}'"$)
		Else
'			Log(2)
			c = B4XPages.MainPage.skmt.ExecQuery("select distinct CAT_CL_RUTA from kmt_info3 where CAT_CL_RUTA not in (select * from ruta_suplencia)")
		End If
'		Log($"#########################${CRLF}ENCONTRAMOS RUTA EN kmt_info3: ${c.RowCount}${CRLF}#########################"$)
		If c.RowCount > 0 Then
'			Log(3)
			c.Position=0
			r = c.GetString("CAT_CL_RUTA")
		End If
		c.Close
	Catch
		Log(LastException)
	End Try
'	Log($"#########################${CRLF}RUTA ENCONTRADA: |${r}|${CRLF}#########################"$)
	Return r
End Sub

'Regresa verdadero si la columna gestion en la tabla "kmt_info" tene valor 2.
'si no, entonces regresa falso.
Sub pedidoGuardado As Boolean
	Private guardado As Boolean = False
	Private g As Cursor = B4XPages.MainPage.skmt.ExecQuery("select gestion from kmt_info3 where CAT_CL_CODIGO in (Select CUENTA from cuentaa)")
	If g.RowCount > 0 Then
		g.Position=0
		If g.GetString("gestion") = "2" Or g.GetString("gestion") = "3" Then guardado = True
	End If
'	Log($"Guardado=${guardado}"$)
	Return guardado
End Sub

'Borra el pedido del cliente actual.
'Borra los registros de la tabla "pedido" y "pedido_cliente"
'Actualiza las tablas "cat_gunaprod" y "kmt_info".
Sub borraPedidoClienteActual As String
'	Private thisC As Cursor
'	thisC=B4XPages.MainPage.skmt.ExecQuery("select PE_PROID,PE_CANT FROM PEDIDO where pe_cliente in (Select CUENTA from cuentaa) ")
'	If thisC.RowCount>0 Then
'		For i=0 To thisC.RowCount -1
'			thisC.Position=i
'			B4XPages.MainPage.skmt.ExecNonQuery2($"update ${Starter.tabla} set cat_gp_almacen = cat_gp_almacen + ? where cat_gp_id = ?"$, Array As Object(thisC.GetString("PE_CANT"),thisC.GetString("PE_PROID")))
'			B4XPages.MainPage.skmt.ExecNonQuery2("INSERT INTO INVENT_X_ENVIAR (ALMACEN , PROID , CANTIDAD ) VALUES(?,?,?) ", Array As Object (traeAlmacen, thisC.GetString("PE_PROID"),thisC.GetString("PE_CANT")* -1))
'		Next
'	End If
'	B4XPages.MainPage.skmt.ExecNonQuery("delete from pedido_cliente where pc_cliente in (Select CUENTA from cuentaa)")
'	B4XPages.MainPage.skmt.ExecNonQuery("delete from pedido where pe_cliente in (Select CUENTA from cuentaa)")
'	B4XPages.MainPage.skmt.ExecNonQuery("UPDATE kmt_info set gestion = 0  where CAT_CL_CODIGO In (select cuenta from cuentaa)")
'	Return 1
	Private thisC As Cursor
'	Private tablaProds As String = "cat_gunaprod2"
	thisC=Starter.skmt.ExecQuery("select PE_PROID, PE_CANT, PE_FOLIO FROM PEDIDO where pe_cliente in (Select CUENTA from cuentaa) AND PE_CEDIS <> 'DUR'")
	If thisC.RowCount>0 Then
		For i=0 To thisC.RowCount -1
			thisC.Position = i
'			Log(thisC.GetString("PE_TIPO") & "|" & traeTablaProds(thisC.GetString("PE_TIPO")))
			Starter.skmt.ExecNonQuery($"update ${traeTablaProds(thisC.GetString("PE_FOLIO"))} set cat_gp_almacen = cat_gp_almacen + ${thisC.GetString("PE_CANT")} where cat_gp_id = '${thisC.GetString("PE_PROID")}'"$)
			LogColor($"update ${traeTablaProds(thisC.GetString("PE_FOLIO"))} set cat_gp_almacen = cat_gp_almacen + ${thisC.GetString("PE_CANT")} where cat_gp_id = '${thisC.GetString("PE_PROID")}'"$, Colors.red)
'			Starter.skmt.ExecNonQuery2("INSERT INTO INVENT_X_ENVIAR (ALMACEN , PROID , CANTIDAD ) VALUES(?,?,?) ", Array As Object (traeAlmacen, thisC.GetString("PE_PROID"),thisC.GetString("PE_CANT")* -1))
		Next
	End If
	Starter.skmt.ExecNonQuery("delete from pedido_cliente where pc_cliente in (Select CUENTA from cuentaa)")
	Starter.skmt.ExecNonQuery("delete from pedido where pe_cliente in (Select CUENTA from cuentaa)")
	Starter.skmt.ExecNonQuery("UPDATE kmt_info set gestion = 0  where CAT_CL_CODIGO In (select cuenta from cuentaa)")
	Return 1
End Sub

'Regresa el total del pedido en la tabla "PEDIDO" del cliente actual.
Sub totalPedido As String
	Private cT As Cursor = Starter.skmt.ExecQuery($"select sum(PE_COSTO_TOT) as total from PEDIDO where PE_CLIENTE = '${traeCliente}' AND PE_CEDIS <> 'DUR'"$)
	Private pTotal As String = "0"
	If cT.RowCount > 0 Then
		cT.Position = 0
'		Log("|"&cT.GetLong("total")&"|"&pTotal)
		Private tempT As String = cT.GetLong("total")
		If tempT <> "null" And tempT <> Null Then
'			Log("|"&cT.GetLong("total")&"|")
			pTotal = tempT
		End If
'		Log($"Cliente actual=${traeCliente}, hayPedido=${hay}"$)
	End If
	cT.Close
	Return pTotal
End Sub

'Regresa la ruta actual de la base de datos.
Sub traeRuta2 (cliente As String) As String 'ignore
	Private c As Cursor
	Private r As String
	c=B4XPages.MainPage.skmt.ExecQuery($"select CAT_CL_RUTA from kmt_info where CAT_CL_CODIGO = '${cliente}' UNION ALL select CAT_CL_RUTA from kmt_info2 where CAT_CL_CODIGO = '${cliente}'"$)
	r = "0"
	If c.RowCount > 0 Then
		c.Position=0
		r = c.GetString("CAT_CL_RUTA")
	End If
	c.Close
	Return r
End Sub

Sub traeRutaBitacora As String 'ignore
	Private c As Cursor
	Private r As String
	c=B4XPages.MainPage.skmt.ExecQuery("select CAT_CL_RUTA from kmt_info3 WHERE CAT_CL_CODIGO IN (SELECT CUENTA FROM CUENTAA)")
	r = "0"
	If c.RowCount > 0 Then
		c.Position=0
		r = c.GetString("CAT_CL_RUTA")
	End If
	c.Close
	Return r
End Sub

'Regresa la ruta actual de la base de datos.
Sub traeRutasSup As String
	Dim c As Cursor
	Dim rutas As String
	rutas = ""
    
	c = B4XPages.MainPage.skmt.ExecQuery("SELECT CAT_CL_RUTA FROM kmt_info3")
    
	If c.RowCount > 0 Then
		For i = 0 To c.RowCount - 1
			c.Position = i
			rutas = rutas & c.GetString("CAT_CL_RUTA") & ", "
		Next
		If rutas.Length > 0 Then rutas = rutas.SubString2(0, rutas.Length - 2)
	End If
    
	c.Close
	Return rutas
End Sub

Sub traeCliente As String 'ignore
	Private c As Cursor
	Private cl As String
	c=B4XPages.MainPage.skmt.ExecQuery("Select CUENTA from cuentaa")
	If c.RowCount > 0 Then 
		c.Position=0
		cl = c.GetString("CUENTA")
	End If
	c.Close
	Log($"Cliente=${cl}"$)
	Return cl
End Sub

Sub traeFecha As String 'ignore
	DateTime.DateFormat = "MM/dd/yyyy"
	Private sDate As String =DateTime.Date(DateTime.Now)
	Private sTime As String =DateTime.Time(DateTime.Now)
	Return sDate & sTime
End Sub

'Regresa el usuario de la tabla USUARIOA
Sub traeUsuarioDeBD As String 'ignore
	Private c As Cursor
	Private u As String = "SinUsuario"
	If Not(kmt.IsInitialized) Then revisaBD
	c=kmt.ExecQuery("select USUARIO from usuarioa")
	c.Position=0
	If c.RowCount > 0 Then u = c.GetString("USUARIO")
	c.Close
	Return u
End Sub

'Regresa verdadero si hay pedido en la tabla "PEDIDO" del cliente actual.
Sub hayPedido As Boolean
	Private thisC As Cursor=B4XPages.MainPage.skmt.ExecQuery($"select count(PE_CLIENTE) as hayPedido from PEDIDO where PE_CLIENTE = '${traeCliente}'"$)
	thisC.Position=0
	Private hay As Boolean = False
	If thisC.GetInt("hayPedido") > 0 Then hay = True
'	Log($"Cliente actual=${traeCliente}, hayPedido=${hay}"$)
	Return hay
End Sub

Sub guardaProducto(cedis As String, costoU As String, cant As String, nombre As String, prodId As String, clienteId As String, fecha As String, usuario As String, rutaV As String, precioSin As String, tipoVenta As String)
	LogColor("guardaProducto: "&prodId&", cant="&cant&" - TV:"&tipoVenta, Colors.Magenta)
	Private c As Cursor
	B4XPages.MainPage.skmt.ExecNonQuery2("INSERT INTO PEDIDO (PE_CEDIS,PE_COSTO_TOT,PE_COSTOU,PE_CANT,PE_PRONOMBRE,PE_PROID,PE_CLIENTE,PE_FECHA,PE_USUARIO,PE_RUTA,PE_COSTO_SIN,PE_FOLIO) VALUES(?,?,?,?,?,?,?,?,?,?,?,?) ", Array As Object (cedis, (cant * costoU), costoU, cant, nombre, prodId, clienteId, fecha, usuario, traeRutaBitacora, precioSin, tipoVenta))
	B4XPages.MainPage.skmt.ExecNonQuery2($"update ${traeTablaProds(traeTipoVentaDeBD)} set cat_gp_almacen = cat_gp_almacen - ? where cat_gp_id = ? "$, Array As Object(cant, prodId))
	c=B4XPages.MainPage.skmt.ExecQuery("select sum(pe_costo_tot) as TOTAL_CLIE, SUM(PE_CANT) AS CANT_CLIE, SUM(PE_COSTO_SIN) AS TOTAL_CLIE_SIN FROM PEDIDO WHERE PE_CLIENTE IN (Select CUENTA from cuentaa)")
	c.Position=0
	B4XPages.MainPage.skmt.ExecNonQuery("delete from pedido_cliente where PC_CLIENTE In (select cuenta from cuentaa)")
	B4XPages.MainPage.skmt.ExecNonQuery2("insert into pedido_cliente(PC_CLIENTE, PC_FECHA, PC_USER, PC_NOART, PC_MONTO,PC_LON, PC_LAT,PC_ALMACEN,PC_RUTA,PC_COSTO_SIN) VALUES (?,?,?,?,?,?,?,?,?,?)", Array As Object(clienteId, fecha, usuario, c.GetString("CANT_CLIE"), c.GetString("TOTAL_CLIE"), B4XPages.MainPage.lon_gps, B4XPages.MainPage.lat_gps, cedis, c.GetString("TOTAL_CLIE_SIN")))
	B4XPages.MainPage.skmt.ExecNonQuery("UPDATE kmt_info3 set gestion = 2  where CAT_CL_CODIGO In (select cuenta from cuentaa)")
End Sub


''Regresa un mapa con la información de la promo.
''Regresa: {id, maxXcliente, maxRecurrente, maxPromos, historico,
''	productos={idProducto={idProducto, preciosimptos, precio, almacen, tipo, piezas, usuario, fecha, regalo, clasif}} 'Mapa con los productos de la promo y los datos de cada uno.
''	tipos={idProducto=tipo} 'Mapa con id y tipo del producto, 0 si es fijo y 1 si es variable.
''	prodsFijos={idProducto,idProducto} 'Lista con los ids de los productos fijos.
''	prodsVariables={idProducto,idProducto} 'Lista con los ids de los productos variables.
''	resultado="OK" 'Ok si existe la promocion.
''	prodsVariablesRequeridos=5} 'Cantidad de productos variables requeridos para la promoción.
'Sub traePromo(promo As String, cliente As String) As Map
'	LogColor($"TRAE PROMO = ${promo}"$, Colors.Red)
'	Private inicioContador As String = DateTime.Now
'	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("Select * from promos_comp where cat_pa_id = '"& promo&"'") 'Obtenemos las el maximo de promocioones a otorgar.
'	Private siHistorico As String = 0
'	Private promoMap As Map
'	Private prodsFijos, prodsFijosPrecios, prodsFijosPiezas, prodsVariables As List
'	promoMap.Initialize
'	prodsFijos.Initialize
'	prodsFijosPrecios.Initialize
'	prodsFijosPiezas.Initialize
'	prodsVariables.Initialize
'	c.Position = 0
'	If c.RowCount > 0 Then promoMap = CreateMap("id":promo, "maxXcliente":c.GetString("CAT_PA_MAXPROMCLIE"), "maxRecurrente":c.GetString("CAT_PA_MAXPROMREC"), "maxPromos":c.GetString("CAT_PA_MAXPROM"))
'	c = B4XPages.MainPage.skmt.ExecQuery("Select count(*) as hist from HIST_PROMOS where HP_CLIENTE = '"& cliente & "' and HP_CODIGO_PROMOCION = '" & promo & "'") 'Revisamos si hay historico de la promoción.
'	c.Position = 0
'	If c.GetString("hist") > 0 Then siHistorico = 1
'	promoMap.Put("historico", siHistorico)
''	c = B4XPages.MainPage.skmt.ExecQuery("Select * from CAT_DETALLES_PAQ where CAT_DP_ID = '"& promo & "'") 'Obtenemos los detalles de la promoción.
'	c = B4XPages.MainPage.skmt.ExecQuery("Select * from CAT_DETALLES_PAQ where CAT_DP_ID = '"& promo & "' order by cat_dp_tipo asc")
'	c.Position = 0
'	If c.RowCount > 0 Then
'		Private prods, tipos As Map
'		prods.Initialize
'		tipos.Initialize
'		For i=0 To c.RowCount -1
'			c.Position=i
'			prods.Put(c.GetString("CAT_DP_IDPROD"), CreateMap("idProducto":c.GetString("CAT_DP_IDPROD"), "precioSimptos":c.GetString("CAT_DP_PRECIO_SIMPTOS"), "precio":c.GetString("CAT_DP_PRECIO"), "almacen":c.GetString("CAT_DP_ALMACEN"), "tipo":c.GetString("CAT_DP_TIPO"), "piezas":c.GetString("CAT_DP_PZAS"), "usuario":c.GetString("CAT_DP_USUARIO"), "regalo":c.GetString("CAT_DP_REGALO"), "clasif":c.GetString("CAT_DP_CLASIF")))
'			tipos.Put(c.GetString("CAT_DP_IDPROD"), c.GetString("CAT_DP_TIPO"))
'			If c.GetString("CAT_DP_TIPO") = "0" Then
'				prodsFijos.Add(c.GetString("CAT_DP_IDPROD"))
'				prodsFijosPrecios.Add(c.GetString("CAT_DP_PRECIO"))
'				prodsFijosPiezas.Add(c.GetString("CAT_DP_PZAS"))
'			End If
'			If c.GetString("CAT_DP_TIPO") = "1" Then prodsVariables.Add(c.GetString("CAT_DP_IDPROD"))
''			Log($"id:${c.GetString("CAT_DP_IDPROD")}, tipo:${c.GetString("CAT_DP_TIPO")}"$)
'		Next
'		promoMap.Put("productos", prods) 'Mapa con los productos de la promocion (id, precio, almacen, tipo, piezas, etc.)
'		promoMap.Put("tipos", tipos) 'Mapa con los productos de la promoción y su tipo (fijo o variable).
'		promoMap.Put("prodsFijos", prodsFijos) 'Lista de los productos fijos de la promoción.
'		promoMap.Put("prodsVariables", prodsVariables) 'Lista de los productos variables de la promoción.
'		promoMap.Put("prodsFijosCant", prodsFijos.Size)
'		promoMap.Put("prodsFijosPrecios", prodsFijosPrecios)
'		promoMap.Put("prodsFijosPiezas", prodsFijosPiezas)
'		promoMap.Put("prodsVariablesCant", prodsVariables.Size)
'		promoMap.Put("resultado", "ok")
'	Else
'		promoMap.Put("resultado", "No hay datos de la promoción.")
'	End If
'	
'
'	c = B4XPages.MainPage.skmt.ExecQuery($"Select CAT_GP_STS, CAT_GP_NOMBRE from ${traeTablaProds(traeTipoVentaDeBD)} where CAT_GP_ID = '${promo}' "$) 'Obtenemos las piezas requeridas de productos variables para la promoción.
'
'	c.Position = 0
'	Private pvr As String = 0
'	If c.RowCount > 0 Then
'		c.Position = 0
'		pvr = c.GetString("CAT_GP_STS")
'		promoMap.Put("prodsVariablesRequeridos", pvr) 'Cantidad de productos variables requeridos para la promoción.
'		promoMap.put("descripcion", c.GetString("CAT_GP_NOMBRE"))
'	End If
'	c.Close
''	Log($"Inv variables: ${cuantosVariablesTengoBD(promo)}"$)
''	Log($"Inv dispo: ${traemosInventarioDisponibleParaPromo(promo)}"$)
''	LogColor($"Promo ${promo}: ${promoMap}"$, Colors.Green)
''	LogColor("TIEMPO para traePromo -=" & promo & "=- : " & ((DateTime.Now-inicioContador)/1000), Colors.Red)
'	Return promoMap
'End Sub

'Regresa un mapa con el inventario disponible por producto para la promoción (desde la base de datos).
Sub traemosInventarioDisponibleParaPromo(promo As String) As Map 'ignore
	Private c As Cursor
	c = B4XPages.MainPage.skmt.ExecQuery2($"SELECT CAT_GP_ID, CAT_GP_ALMACEN FROM ${traeTablaProds(traeTipoVentaDeBD)} WHERE CAT_GP_ID IN (select CAT_DP_IDPROD FROM CAT_DETALLES_PAQ WHERE CAT_DP_ID = ?)"$, Array As String(promo))
'	Private prodInv As Map
'	prodInv.Initialize
	Private 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"))
'			Log($"prod:${c.GetString("CAT_GP_ID")}, inventario:${c.GetString("CAT_GP_ALMACEN")}"$)
		Next
'		prodInv.Put("inventarios", prods)
	End If
	Return prods
End Sub

'Resta los productos fijos del inventario de la promoción (mapa) y regresa un mapa con el nuevo inventario.
'Hay que darle como parametro un mapa (traePromo(promo)) con toda la informacion de la promocion.
'Regresa en el mapa la llave "resultado" que nos da "ok" o "No hay suficiente producto para la promocion".
Sub restaFijosPromo(promoMap As Map) As Map 'ignore
	Private thisLog As Boolean = False 'Si es verdadero, muestra los logs de este sub.
	Private inventariosDisponiblesParaEstaPromo As Map = traemosInventarioDisponibleParaPromo(promoMap.Get("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 = promoMap.Get("productos") 'Obtenemos un mapa con todos los productos de la promoción.
	Private prodsFijos As List = promoMap.get("prodsFijos") 'Obtenemos una lista con los productos fijos de la promoción.
	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.
			If thisLog Then Log($"Nuevo inventario de ${t}: ${i}-${promoMap.Get("prodsFijosPiezas").As(List).get(p)} = $1.0{i - promoMap.Get("prodsFijosPiezas").As(List).get(p)}"$) 'El nuevo inventario.
			inventariosDisponiblesParaEstaPromo.Put(t, $"${i - promoMap.Get("prodsFijosPiezas").As(List).get(p)}"$) '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.")
			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 prodsFijos.Size = 0 Then inventariosDisponiblesParaEstaPromo.Put("resultado", "ok") 'No hay productos fijos.
	If thisLog Then LogColor("Inventario final depues de FIJOS: "&inventariosDisponiblesParaEstaPromo, Colors.blue) 'Inventario final.
	Return inventariosDisponiblesParaEstaPromo
End Sub

''Revisa si tenemos los productos variables requeridos para la promoción (mapa).
''Hay que darle como parametro un mapa (traePromo(promo)) con toda la informacion de la promocion.
'Sub alcanzanLosVariablesParaPromo(promoMap As Map, inventarioSinFijos As Map) As Boolean 'ignore
'	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 prodsmap As Map = promoMap.Get("productos") 'Obtenemos un mapa con todos los productos de la promoción.
'	Private prodsVariables As List = promoMap.get("prodsVariables") 'Obtenemos un a 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 String = inventarioSinFijos.Get(t) 'Obtenemos el inventario disponible este producto.
'			totalProdsVariables = totalProdsVariables + p2
'			If thisLog Then Log($"prod ${t}, hay: ${p2}"$) 'Producto y piezas requeridas
'		End If
'	Next
'	If thisLog Then Log("Total prods variables=" & totalProdsVariables & ", requeridos=" & promoMap.Get("prodsVariablesRequeridos"))
'	Private res As Boolean = False
'	If totalProdsVariables >= promoMap.Get("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

'Regresa el numero máximo de promociones permitidas, tomando en cuenta recurrentes, clientes y maxPromos.
Sub traeMaxPromos(pm As Map) As Int
	Private thisLog As Boolean = True 'Si es verdadero, muestra los logs de este sub.
	Private maxPromos As List
	Private vendidas As Int = 0
	maxPromos.Initialize
'	If Starter.promosLog Then Log("==== HISTORICO:"&pm.Get("historico"))
	If thisLog Then Log(pm)
	If pm.Get("historico") = "1" Then maxPromos.Add(pm.Get("maxRecurrente")) 'Si hay historico, agregamos maxRecurrente
	maxPromos.Add(pm.Get("maxPromos")) 'Agregamos maxPromos
	maxPromos.Add(pm.Get("maxXcliente")) 'Agregamos maxXcliente
	If thisLog Then Log(maxPromos)
	maxPromos.Sort(True)
'	Log($"|${pm.Get("id").As(String).trim}|${traeCliente.Trim}|"$)
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery2("select sum(PE_CANT) as vendidas from PEDIDO where PE_PROID = ? and PE_CLIENTE = ? ", Array As String(pm.Get("id").As(String).trim, traeCliente.Trim))
	If c.RowCount > 0 Then
		c.Position = 0
		vendidas = c.GetInt("vendidas")
'		Log(vendidas)
	End If
'	If Starter.promosLog Then Log(maxPromos)
'	If Starter.promosLog Then Log("Max Promos="&maxPromos.Get(0))
'	LogColor($"maxPromos=${maxPromos.Get(0)} - vendidas=${vendidas}"$, Colors.red)
	Return maxPromos.Get(0) - vendidas 'Regresamos el numero mas pequeño de las opciones.
End Sub

'Regresa la cantidad de promos que se le han vendido al cliente.
Sub traePromosVendidas(promo As String, cliente As String) As Int
	Private c As Cursor
	Private pv As Int = 0
	c=B4XPages.MainPage.skmt.ExecQuery($"select PE_CANT from PEDIDO where PE_PROID = '${promo}' and PE_CLIENTE = '${cliente}'"$)
	If c.RowCount > 0 Then
		c.Position = 0
		pv = c.GetInt("PE_CANT")
	End If
	Return pv
End Sub

Sub procesaPromocion(idPromo As String, cliente As String) As Map 'ignore
	Private thisLog As Boolean = True  'Si es verdadero, muestra los logs de este sub.
	Private inicioContador As String = DateTime.Now
	If thisLog Then LogColor($"********* Iniciamos revision de Promo ${idPromo} *********"$, Colors.Magenta)
	'Obtenemos el mapa con toda la info de la promoción.
	Private pm As Map = traePromo(idPromo, cliente)
	If thisLog Then LogColor(pm, Colors.Blue)
	If pm.Get("resultado") = "ok" Then 'Si encontramos la promoción, entonces ...
		'Buscamos el máximo de promociones permitidas.
		If thisLog Then LogColor($"Promociones permitidas=${traeMaxPromos(pm)}"$, Colors.Blue)
		If thisLog Then Log("Promos vendidas: " & traePromosVendidas(idPromo, cliente))
		If traePromosVendidas(idPromo, cliente) >= traeMaxPromos(pm) Then
			If thisLog Then LogColor("Ya se vendieron las promos PERMITIDAS para el cliente", Colors.red)
			Return CreateMap("status":"ko", "mp":pm)
		End If
		'Restamos del inventario (mapa) las piezas necesarias para los productos fijos.
		Private inventarioSinFijos As Map = restaFijosPromo(pm)
		If thisLog Then LogColor("inventariosfijos="&inventarioSinFijos, Colors.Green)
		
		If inventarioSinFijos.Get("resultado") = "ok" Then
			'Revisamos que los productos variables requeridos sean menos que el inventario total (mapa).
			Private pv As Boolean =  alcanzanLosVariablesParaPromo(pm, inventarioSinFijos)
			If thisLog Then Log("Alcanzan los variables? --> " & pv)
			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
	End If
	' Si tenemos suficiente inventario para los variables mostramos la promocion, si no ...
	' 		break	'NO HAY INVENTARIO SUFICIENTE PARA LA PROMOCION.
	
	LogColor("TIEMPO DE PROCESO ESTA PROMO: " & ((DateTime.Now-inicioContador)/1000), Colors.Red)
End Sub

'Regresa cuantas promos alcanzan con los productos FIJOS que hay en inventario.
Sub revisaMaxPromosProdsFijosPorInventario2(pm As Map) As Int
	Private thisLog As Boolean = False
	If thisLog Then Log($"pm=${pm}"$)
'	Private prodsFijos As List = pm.get("prodsFijos")
	Private invDispParaPromo As Map = traemosInventarioDisponibleParaPromo(pm.Get("id"))
	If thisLog Then Log($"invDispParaPromo=${invDispParaPromo}"$)
	Private maxPromos As String = traeMaxPromos(pm)
	Private maxPromosFijosXinv As Int = 1
	Private fpf2, pdp2 As Int
	Private salir As Boolean = False
	Private pf As List = pm.Get("prodsFijos")
	Private pfp As List = pm.Get("prodsFijosPiezas")
	If thisLog Then Log($"maxPromos=${maxPromos}, prodsFijos=${pf}, piezas=${pfp}"$)
	If thisLog Then LogColor($"InvFijo disponible=${invDispParaPromo}"$, Colors.Blue)
	Private invFijoXpromo As Map
	invFijoXpromo.Initialize
	For p = 0 To pf.Size -1 'Generamos mapa con los productos fijo y piezas requeridos por promo.
		invFijoXpromo.Put(pf.Get(p), pfp.Get(p))
	Next
	If thisLog Then LogColor("Inv req. de prods fijos x promo" & invFijoXpromo, Colors.Green)
	For i = 1 To maxPromos 'Revisamos cuantas promociones alcanzan, hasta llegar al máximo de promos permitadas.
		If thisLog Then LogColor("Prods para promo " & (i+1), Colors.Magenta)
		For q = 0 To pf.Size - 1
			Private q2 As String = pf.Get(q)
			If thisLog Then Log("q="&q2)
'			fpf2 = invFijoXpromo.Get(q2) * i 'Multiplicamos las piezas requeridas por la cantidad de promos.
			fpf2 = pfp.Get(q) * i 'Multiplicamos las piezas requeridas por la cantidad de promos.
			pdp2 = invDispParaPromo.Get(q2)
			If thisLog Then Log($"pf=${q2}, Actual=${(i)}, max promos: ${pdp2}-${fpf2}=${pdp2 - fpf2}"$)
			If pdp2 - fpf2 < 0 Then 'Si el inventario es negativo, entonces ya no alcanza para este producto.
				salir=True
				Exit
			End If
		Next
		If salir Then Exit
		maxPromosFijosXinv = i 
	Next
	If thisLog Then LogColor("InvFijo requerido x promo="&invFijoXpromo, Colors.blue)
	LogColor("Maximo de promociones de prodsFijos POR inventario = " & maxPromosFijosXinv, Colors.Red)
	Return maxPromosFijosXinv
End Sub

'Regresa cuantas promos alcanzan con los productos FIJOS que hay en inventario.
Sub revisaMaxPromosProdsFijosPorInventario(pm As Map) As Int
	Private thisLog As Boolean = False
	Private invFijoXpromo As Map
	Private t As List
	t.Initialize
	t.Add(traeMaxPromos(pm)) ' Agregamos a la lista las promos maximas permitidas (recurrente, cliente y promo).
	invFijoXpromo.Initialize
	If thisLog Then LogColor($"pm=${pm}"$, Colors.Blue)
	Private invDispParaPromo As Map = traemosInventarioDisponibleParaPromo(pm.Get("id"))
	If thisLog Then Log($"invDispParaPromo=${invDispParaPromo}"$)
	Private prodsFijosPiezas As List = pm.Get("prodsFijosPiezas")
	Private idProdsFijos As List = pm.Get("prodsFijos")
	For p = 0 To idProdsFijos.Size -1 'Generamos una lista con las promos disponibles por producto (dividimos el inventario total entre las piezas requeridas).
		If thisLog Then Log($"id=${idProdsFijos.Get(p)}, inv=${invDispParaPromo.Get(idProdsFijos.Get(p))}, pzas=${prodsFijosPiezas.Get(p)}"$)
		If thisLog Then Log($"${(invDispParaPromo.Get(idProdsFijos.Get(p)) / prodsFijosPiezas.Get(p))}"$)
		Private x() As String = Regex.Split("\.", $"${(invDispParaPromo.Get(idProdsFijos.Get(p)) / prodsFijosPiezas.Get(p))}"$) 'Separamos el resultado de la division por el punto decimal.
		If thisLog Then Log(x(0))
		t.Add(x(0).As(Int)) 'Solo guardamos la parte del entero de la division.
	Next
	t.Sort(True) 'Ordenamos la lista para que en el lugar 0 este el resultao mas pequeño.
	If thisLog Then LogColor($"prodsFijos=${idProdsFijos}"$, Colors.Blue)
	If thisLog Then LogColor($"prodsFijosPiezasReq=${prodsFijosPiezas}"$, Colors.Blue)
	If thisLog Then LogColor($"invFijoXpromo=${invFijoXpromo}"$, Colors.Blue)
	LogColor("Max promos de prodsFijos POR inventario = " & t.Get(0), Colors.red)
	Return t.Get(0) 'Regresamos el resultado mas pequeño.
End Sub

''Regresa cuantas promos alcanzan con los productos VARIABLES que hay en inventario.
''La cantidad de promos disponibles se calcula DESPUES de descontar los productos fijos, y si las
''promos por productos fijos llega al maximo, aunque se puedan mas de producos variables, solo se
''regresa el maximo por productos fijos. Ej. si las promos por variables es 10, pero el maximo por
''fijos es 5, entonces regresamos 5.
'Sub revisaMaxPromosProdsVariablesPorInventario(pm As Map) As Int 'ignore
'	Private thisLog As Boolean = False
'	If thisLog Then Log("======================================================")
'	If thisLog Then Log("======================================================")
'	Private invFijoXpromo As Map
'	invFijoXpromo.Initialize
'	Private totalProdsVariablesDisponibles As Int = 0
'	If thisLog Then LogColor($"pm=${pm}"$, Colors.Blue)
'	Private invDispParaPromo As Map = traemosInventarioDisponibleParaPromo(pm.Get("id"))
'	If thisLog Then Log($"invDispParaPromo=${invDispParaPromo}"$)
'	Private maxPromos As String = traeMaxPromos(pm)
'	Private maxPromosXFijos As Int = revisaMaxPromosProdsFijosPorInventario(pm)
'	Private idProdsVariables As List = pm.Get("prodsVariables")
'	Private prodsVariablesRequeridos As Int = pm.Get("prodsVariablesRequeridos")
'	Private prodsFijosPiezas As List = pm.Get("prodsFijosPiezas")
'	Private idProdsFijos As List = pm.Get("prodsFijos")
'	For p = 0 To idProdsFijos.Size -1 'Generamos mapa con los productos fijos y piezas requeridas por promo.
'		invFijoXpromo.Put(idProdsFijos.Get(p), prodsFijosPiezas.Get(p))
'		Private idEsteProd As String = idProdsFijos.Get(p)
'		Private invEsteProd As Int = invDispParaPromo.Get(idEsteProd)
'		Private pzasReqEsteProd As Int = prodsFijosPiezas.Get(p)
'		If thisLog Then Log($"id=${idEsteProd}, inv=${invEsteProd}, pzas=${pzasReqEsteProd}"$)
''		invDispParaPromo.Put(   idEsteProd, (invEsteProd - (1))   )
'	Next
'	If thisLog Then LogColor($"MaxPromos=${maxPromos}, promosXFijos=${maxPromosXFijos}"$, Colors.Blue)
'	If thisLog Then LogColor($"prodsFijos=${idProdsFijos}"$, Colors.Blue)
'	If thisLog Then LogColor($"prodsFijosPiezasReq=${prodsFijosPiezas}"$, Colors.Blue)
'	If thisLog Then LogColor($"prodsVariables=${idProdsVariables}${CRLF}Variables Req=${prodsVariablesRequeridos} "$, Colors.Blue)
'	If thisLog Then LogColor($"invFijoXpromo=${invFijoXpromo}"$, Colors.Blue)
'	If thisLog Then Log($"Prods variables disponibles = ${totalProdsVariablesDisponibles}"$)
'	Private maxPromosXVariables As Int = 0
'	For x = 1 To maxPromosXFijos
'		If thisLog Then Log("=====================================================")
'		If thisLog Then Log("=====================================================")
'		For i = 0 To idProdsFijos.Size - 1
'			If thisLog Then Log($"FIJO - ${idProdsFijos.Get(i)}, ${invDispParaPromo.Get(idProdsFijos.Get(i))} - ${prodsFijosPiezas.Get(i).As(Int)*(i+1)}"$)
'			invDispParaPromo.Put(idProdsFijos.Get(i), invDispParaPromo.Get(idProdsFijos.Get(i)).As(Int) - prodsFijosPiezas.Get(i).As(Int)*(i+1)) 'Restamos las piezas de los productos fijos del inventario disponible.
'		Next
'		If thisLog Then LogColor("Inv disponible despues de restar fijos = " & invDispParaPromo, Colors.Blue)
'		
'		totalProdsVariablesDisponibles = 0
'		For i = 0 To idProdsVariables.Size - 1 'Obtenemos total de productos variables disponibes.
'			If invDispParaPromo.ContainsKey(idProdsVariables.Get(i)) Then
'				totalProdsVariablesDisponibles = totalProdsVariablesDisponibles + invDispParaPromo.Get(idProdsVariables.Get(i))
'			End If
'		Next
'		'Revisamos variables.
'		If thisLog Then Log($"Var disponibles -  var requeridos : ${totalProdsVariablesDisponibles} - ${prodsVariablesRequeridos*x}"$)
'		totalProdsVariablesDisponibles = totalProdsVariablesDisponibles - (prodsVariablesRequeridos*x)
'		If thisLog Then Log("prodsVariables disponibles despues de promo = " & totalProdsVariablesDisponibles)
'		If totalProdsVariablesDisponibles < 0 Then Exit 'Ya no hay inventario disponible.
'		maxPromosXVariables = x
'	Next
'	'Restamos fijos.
'	LogColor("Max promos de prodsVariables POR inventario = " & maxPromosXVariables, Colors.red)
'	Return maxPromosXVariables
'End Sub

'Regresa la suma del inventario de los productos variables de la promoción dada desde la base de datos.
Sub cuantosVariablesTengoBD(promo As String) As String 'ignore
'	Private x As String = "0"
'	If promo <> "" Then
'		Private c As Cursor
'		c = Starter.skmt.ExecQuery2("Select SUM(CAT_GP_ALMACEN) as variables FROM CAT_GUNAPROD2 WHERE CAT_GP_ID IN (Select CAT_DP_IDPROD FROM CAT_DETALLES_PAQ WHERE CAT_DP_ID = ? and cat_dp_tipo = 1 GROUP BY CAT_DP_IDPROD)", Array As String (promo))
'		If c.RowCount > -1 Then
'			c.Position = 0
'			If c.GetString("variables") <> Null Then x = c.GetString("variables")
'		End If
'	End If
'	Return x
End Sub

'Regresa un mapa con los datos del producto desde la base de datos.
'el mapa incluye: Id, nombre, tipo y subtipo del producto.
Sub traeProdIdDeBD As Map 'ignore
	Private c As Cursor
	Private m As Map
	c=B4XPages.MainPage.skmt.ExecQuery($"select CAT_GP_ID, CAT_GP_NOMBRE, CAT_GP_TIPO, CAT_GP_SUBTIPO from ${traeTablaProds(traeTipoVentaDeBD)} where CAT_GP_NOMBRE In (Select PDESC from PROID)"$)
	If c.RowCount > 0 Then
		c.Position = 0
		m = CreateMap("id":c.GetString("CAT_GP_ID"), "nombre":c.GetString("CAT_GP_NOMBRE"), "tipo":c.GetString("CAT_GP_TIPO"), "subtipo":c.GetString("CAT_GP_SUBTIPO"))
	Else
		m = CreateMap("id":"N/A", "nombre":"N/A", "tipo":"N/A", "subtipo":"N/A")
	End If
	c.Close
	Return m
End Sub


'Regresa un mapa con los datos del producto desde la base de datos.
'el mapa incluye: Id, nombre, tipo y subtipo del producto.
Sub traePromoIdDeBD As Map 'ignore
	Private c As Cursor
	Private m As Map
	
	Log("ENTRE")
	c=B4XPages.MainPage.skmt.ExecQuery($"select CAT_GP_ID, CAT_GP_NOMBRE, CAT_GP_TIPO, CAT_GP_SUBTIPO from ${traeTablaProds(traeTipoVentaDeBD)} where CAT_GP_NOMBRE In (Select PDESC from PROID) and CAT_GP_TIPO = 'PROMOS'"$)
	If c.RowCount > 0 Then
		c.Position = 0
		m = CreateMap("id":c.GetString("CAT_GP_ID"), "nombre":c.GetString("CAT_GP_NOMBRE"), "tipo":c.GetString("CAT_GP_TIPO"), "subtipo":c.GetString("CAT_GP_SUBTIPO"))
	Else
		m = CreateMap("id":"N/A", "nombre":"N/A", "tipo":"N/A", "subtipo":"N/A")
	End If
	c.Close
	
	Return m
End Sub

Sub agregaColumna(tabla As String, columna As String, tipo As String) 'ignore
	Try 'Intentamos usar "pragma_table_info" para revisar si existe la columna en la tabla
		Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery($"SELECT COUNT(*) AS fCol FROM pragma_table_info('${tabla}') WHERE name='${columna}'"$)
		c.Position = 0
		If c.GetString("fCol") = 0 Then 'Si no esta la columna la agregamos
			B4XPages.MainPage.skmt.ExecNonQuery($"ALTER TABLE ${tabla} ADD COLUMN ${columna} ${tipo}"$)
			Log($"Columna "${columna} ${tipo}", agregada a "${tabla}"."$)
		End If
'		Log(1)
	Catch 'Si no funciona "pragma_table_info" lo hacemos con try/catch
		Try
			B4XPages.MainPage.skmt.ExecNonQuery($"ALTER TABLE ${tabla} ADD COLUMN ${columna} ${tipo}"$)
			Log($"Columna "${columna} ${tipo}", agregada a "${tabla}".."$)
		Catch
			Log(LastException)
		End Try
		Log(2)
	End Try
End Sub

'Guarda el nombre y version de la app en CAT_VARIABLES.
Sub guardaAppInfo 'ignore
	B4XPages.MainPage.skmt.ExecNonQuery("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'EMPRESA' or CAT_VA_DESCRIPCION = 'APP_NAME' or CAT_VA_DESCRIPCION = 'APP_VERSION'")
	B4XPages.MainPage.skmt.ExecNonQuery($"insert into CAT_VARIABLES (CAT_VA_DESCRIPCION, CAT_VA_VALOR) values ('APP_NAME', '${Application.LabelName}')"$)
	B4XPages.MainPage.skmt.ExecNonQuery($"insert into CAT_VARIABLES (CAT_VA_DESCRIPCION, CAT_VA_VALOR) values ('APP_VERSION', '${Application.VersionName}')"$)
End Sub

' Promo $150
Sub TraeMontoProd As Boolean
	Private x As Boolean = False
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("SELECT ifnull( SUM (PE_COSTO_TOT),0) As suma FROM PEDIDO JOIN CAT_PROMO_ESP ON CAT_PE_ID = PE_PROID WHERE PE_CLIENTE IN (Select CUENTA FROM CUENTAA)")
	If c.RowCount > 0 Then
		c.Position = 0
		Private c2 As Cursor = B4XPages.MainPage.skmt.ExecQuery("SELECT DISTINCT CAT_PE_MONTO FROM CAT_PROMO_ESP")
		If c2.RowCount > 0 Then
			c2.Position = 0
				If c.GetString("suma") >= c2.GetString("CAT_PE_MONTO") Then
					x = True
'					Log("verdadero")
				Else
					x = False
'					Log("Falso")
				End If
		End If
	End If
	Return x
End Sub



Sub TraeMontoProd2 As Boolean
	'This subrutine checks if the total cost of a product is between 350 and 499.99
	Private x As Boolean = False
	'Declares a boolean variable
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("SELECT ifnull( SUM (PE_COSTO_TOT),0) As suma FROM PEDIDO JOIN CAT_PROMO_ESP ON CAT_PE_ID = PE_PROID WHERE PE_CLIENTE IN (Select CUENTA FROM CUENTAA)")
	'Executes a query to get the sum of PE_COSTO_TOT from the PEDIDO table, joined with CAT_PROMO_ESP table on CAT_PE_ID = PE_PROID where PE_CLIENTE is in the CUENTA column from CUENTAA table
	If c.RowCount > 0 Then
		'Checks if the query returned any rows
		c.Position = 0
		'Sets the cursor position to the first row
		Private c2 As Cursor = B4XPages.MainPage.skmt.ExecQuery("SELECT DISTINCT CAT_PE_MONTO FROM CAT_PROMO_ESP")
		'Executes a query to get distinct values of CAT_PE_MONTO from CAT_PROMO_ESP table
		If c2.RowCount > 0 Then
			'Checks if the query returned any rows
			c2.Position = 0
			'Sets the cursor position to the first row
			If c.GetString("suma") >= 350 And c.GetString("suma") <= 499.99 Then
				'Checks if the sum is between 350 and 499.99
				x = True
				'Sets x to true if the condition is met
			Else
				x = False
				'Sets x to false if the condition is not met
'					Log("Falso")
			End If
		End If
	End If
	Return x
	'Returns the value of x
End Sub

Sub InvSuficientePromoEsp As Boolean
	Private y As Boolean = False
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery($"select CAT_DP_IDPROD, CAT_DP_PZAS, ifnull(CAT_GP_ALMACEN, 0) As CAT_GP_ALMACEN from CAT_DETALLES_PAQ left JOIN ${traeTablaProds(traeTipoVentaDeBD)} ON CAT_DP_IDPROD = CAT_GP_ID WHERE CAT_DP_ID IN (SELECT DISTINCT CAT_PE_IDPROMO FROM CAT_PROMO_ESP)"$)
	If c.RowCount > 0 Then
		For i = 0 To c.RowCount - 1
		c.Position = i
			If c.GetString("CAT_GP_ALMACEN") >= c.GetString("CAT_DP_PZAS") Then
				y = True
'				Log("verdadero    "& c.GetString("CAT_DP_IDPROD"))
'				Log(c.GetString("CAT_GP_ALMACEN") &"           "& c.GetString("CAT_DP_PZAS"))
			Else
				y = False
'				Log("falso    "& c.GetString("CAT_DP_IDPROD"))
'				Log(c.GetString("CAT_GP_ALMACEN") &"           "& c.GetString("CAT_DP_PZAS"))
				Exit
			End If
		Next
	End If
	c.Close
	Return y
End Sub

Sub vendidoPromoEsp As Boolean
	Private w As Boolean = False
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("select HP_CLIENTE, HP_CODIGO_PROMOCION from HIST_PROMOS WHERE HP_CODIGO_PROMOCION IN (SELECT DISTINCT CAT_PE_IDPROMO FROM CAT_PROMO_ESP) AND HP_CLIENTE IN (SELECT CUENTA FROM CUENTAA)")
	If c.RowCount > 0 Then
		w = True
	End If
	Log(w)
	c.Close
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("select PE_PROID from PEDIDO WHERE PE_PROID IN (SELECT DISTINCT CAT_PE_IDPROMO FROM CAT_PROMO_ESP) AND PE_CLIENTE IN (SELECT CUENTA FROM CUENTAA)")
	If c.RowCount > 0 Then
		w = True
	End If
	c.Close
	Log(w)
	Return w
End Sub

'En geocerca si mete la contraseña poner 0 en precision gps y si esta dentro de los 50 mts poner 1 y 2 para eventos que no lo ocupen
'Mandar fecha de sync(sysdate)
Sub bitacora(fechab As String, usuariob As String, almacenb As String, rutab As String, eventob As String, clienteb As String, iniciob As String, finb As String, latitudb As String, longitudb As String, precision As String, motivonoventa As String, motivonovisita As String, BAN_GEOB As String)
'	Log(motivonovisita)
''	Log("bitacora")
'	Private cmd As DBCommand
'	cmd.Initialize
'	cmd.Name = "mandaBitacora3_GUNA"
''	Log("BITACORA3")
'	Private nombreCliente As String = traeNombreCliente(clienteb)
'	If eventob = "Llega a almacen" Then
'		nombreCliente = "BOLETA"
'		clienteb = ""
'		finb = iniciob
'	End If
'	If eventob = "Salida almacen" Then nombreCliente = "CHECKLIST"
'	If eventob = "Fin Día" Then nombreCliente = "FIN DIA"
'	If eventob = "Carga día" Then nombreCliente = "CARGA DIA"
'	If eventob = "Carga día suplencia" Then nombreCliente = traeRutasSup
'	If eventob <> "Termina Venta" And eventob <> "No Venta" Then
'		B4XPages.MainPage.skmt.ExecNonQuery($"INSERT INTO BITACORAGPS (fechab, usuariob , almacenb , rutab , eventob , clienteb , iniciob , finb , latitudb, longitudb , precision , motivonoventa , motivonovisita, BAN_GEOB) VALUES ('${fechab}' ,'${usuariob}' , '${almacenb}' , '${rutab}' , '${eventob}' , '${clienteb}' , '${iniciob}' , '${finb}' , '${latitudb}' , '${longitudb}' , '${precision}' , '${motivonoventa}' , '${motivonovisita}', '${BAN_GEOB}')"$)
'	Else
'		Private e As Cursor = B4XPages.MainPage.skmt.ExecQuery($"select fechab from BITACORAGPS where usuariob = '${usuariob}' and almacenb = '${almacenb}' and rutab = '${rutab}' and clienteb = '${clienteb}' and eventob = 'Inicia Venta' order by fechab desc"$)
'		If e.RowCount > 0 Then
'			e.Position = 0
'			Log("ACTUALIZA BITACORA")
'			If eventob = "Termina Venta" Then
'				B4XPages.MainPage.skmt.ExecNonQuery($"update BITACORAGPS set finb = '${finb}' where rutab = '${rutab}' and almacenb = '${almacenb}' and usuariob = '${usuariob}' and clienteb = '${clienteb}' and fechab = '${e.GetString("fechab")}' "$)
'				cmd.Parameters = Array As Object(finb, rutab, almacenb, usuariob, clienteb, "Inicia Venta", e.GetString("fechab"))
'			else if  eventob = "No Venta" Then
'				B4XPages.MainPage.skmt.ExecNonQuery($"update BITACORAGPS set finb = '${finb}', motivonoventa = '${motivonoventa}', motivonovisita = '${motivonovisita}' where rutab = '${rutab}' and almacenb = '${almacenb}' and usuariob = '${usuariob}' and clienteb = '${clienteb}' and fechab = '${e.GetString("fechab")}' "$)
'				cmd.Parameters = Array As Object(finb, motivonoventa, motivonovisita, rutab, almacenb, usuariob, clienteb, "Inicia Venta", e.GetString("fechab"))
'			End If
'		End If
'	End If
'	If eventob <> "Inicia Venta" Then
'		Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery($"select * from BITACORAGPS where usuariob = '${usuariob}' and almacenb = '${almacenb}' and rutab = '${rutab}' and clienteb = '${clienteb}' order by fechab desc"$)
'		If c.RowCount > 0 Then
'			c.Position = 0
'			cmd.Parameters = Array As Object(c.GetString("almacenb"), c.GetString("usuariob"), c.GetString("rutab"), c.GetString("eventob"), c.GetString("clienteb"), nombreCliente, c.GetString("iniciob"), c.GetString("finb"), c.GetString("latitudb"), c.GetString("longitudb"), c.GetString("precision"), c.GetString("motivonoventa"), c.GetString("motivonovisita"), c.GetString("fechab"), c.GetString("BAN_GEOB"))
'			Starter.reqManager.ExecuteCommand(cmd , "mandaBitacora")
'		End If
'	End If
'	Log("Mandamos bitacora")
End Sub

'En geocerca si mete la contraseña poner 0 en precision gps y si esta dentro de los 50 mts poner 1 y 2 para eventos que no lo ocupen
''Mandar fecha de sync(sysdate)
'Sub bitacoraX(fechab As String, usuariob As String, almacenb As String, rutab As String, eventob As String, clienteb As String, iniciob As String, finb As String, latitudb As String, longitudb As String, precision As String, motivonoventa As String, motivonovisita As String )
'	Log("bitacora")
'	B4XPages.MainPage.skmt.ExecNonQuery($"INSERT INTO BITACORAGPS (fechab, usuariob , almacenb , rutab , eventob , clienteb , iniciob , finb , latitudb, longitudb , precision , motivonoventa , motivonovisita) VALUES ('${fechab}' ,'${usuariob}' , '${almacenb}' , '${rutab}' , '${eventob}' , '${clienteb}' , '${iniciob}' , '${finb}' , '${latitudb}' , '${longitudb}' , '${precision}' , '${motivonoventa}' , '${motivonovisita}')"$)
'	Private cmd As DBCommand
'	cmd.Initialize
'	If eventob <> "Termina Venta" And eventob <> "No Venta" Then
'		cmd.Name = "mandaBitacora3_GUNA"
'		Log("BITACORA3")
'		Private nombreCliente As String = traeNombreCliente(clienteb)
'		If eventob = "Llega a almacen" Then
'			nombreCliente = "BOLETA"
'			clienteb = ""
'			finb = iniciob
'		End If
'		If eventob = "Salida almacen" Then nombreCliente = "CHECKLIST"
'		If eventob = "Fin Día" Then nombreCliente = "FIN DIA"
'		If eventob = "Carga día" Then nombreCliente = "CARGA DIA"
'		Log($"'${almacenb}', '${usuariob}', '${rutab}', '${eventob}', '${clienteb}', '${nombreCliente}','${ iniciob}', '${finb}','${ latitudb}','${ longitudb}', '${precision}', '${motivonoventa}', '${motivonovisita}', '${fechab}'"$)
''		TMP_ALMACEN, TMP_USUARIO, TMP_RUTA, TMP_EVENTO, TMP_ID_CLIENTE, TMP_NOMBRE_CLIENTE, TMP_INICIO, TMP_FINAL, TMP_LATITUD, TMP_LONGITUD, TMP_PRESICION, TMP_MOTIVO_NO_VENTA, TMP_MOTIVO_NO_VISITA, TMP_FECHA_SINC, TMP_FECHA_MOVIL
'		cmd.Parameters = Array As Object(almacenb, usuariob, rutab, eventob, clienteb, nombreCliente, iniciob, finb, latitudb, longitudb, precision, motivonoventa, motivonovisita, fechab)
'		Starter.reqManager.ExecuteCommand(cmd , "mandaBitacora")
'	Else
'		Private e As Cursor = B4XPages.MainPage.skmt.ExecQuery($"select fechab from BITACORAGPS where usuariob = '${usuariob}' and almacenb = '${almacenb}' and rutab = '${rutab}' and clienteb = '${clienteb}' and eventob = 'Inicia Venta' order by fechab desc"$)
'		If e.RowCount > 0 Then
'			e.Position = 0
'			Log("ACTUALIZA BITACORA")
'			If eventob = "Termina Venta" Then
'				cmd.Name = "actualizaSalidaBitacora"
''				TMP_FINAL = to_date((?),'YYYY/MM/DD HH24:MI:ss') where TMP_RUTA = (?) and tmp_almacen = (?) and tmp_usuario = (?) and tmp_id_cliente = (?) and tmp_evento = (?) and tmp_fecha_movil = to_date((?),'YYYY/MM/DD HH24:MI:ss');
'				cmd.Parameters = Array As Object(finb, rutab, almacenb, usuariob, clienteb, "Inicia Venta", e.GetString("fechab"))
'				Log($"${finb}, ${rutab}, ${almacenb}, ${usuariob}, ${clienteb}, 'Inicia Venta', ${e.GetString("fechab")}, "$)
'				Starter.reqManager.ExecuteCommand(cmd , "mandaBitacora")
'			else if  eventob = "No Venta" Then
'				cmd.Name = "actualizaNoVentaBitacora"
''				TMP_FINAL = to_date((?),'YYYY/MM/DD HH24:MI:ss') where TMP_RUTA = (?) and tmp_almacen = (?) and tmp_usuario = (?) and tmp_id_cliente = (?) and tmp_evento = (?) and tmp_fecha_movil = to_date((?),'YYYY/MM/DD HH24:MI:ss');
'				cmd.Parameters = Array As Object(finb, motivonoventa, motivonovisita, rutab, almacenb, usuariob, clienteb, "Inicia Venta", e.GetString("fechab"))
'				Log($"${finb}, ${rutab}, ${almacenb}, ${usuariob}, ${clienteb}, 'Inicia Venta', ${e.GetString("fechab")}, "$)
'				Starter.reqManager.ExecuteCommand(cmd , "mandaBitacora")
'			End If
'		End If
'	End If
'	Log("Mandamos bitacora")
'End Sub

'Regresa el nombre del cliente del id dado.
Sub traeNombreCliente(id As String) As String
	Private c As ResultSet = B4XPages.MainPage.skmt.ExecQuery($"select CAT_CL_NOMBRE from kmt_info3 where CAT_CL_CODIGO = '${id}'"$)
	Private n As String = "N/A"
	Do While c.NextRow
		n = c.GetString("CAT_CL_NOMBRE")
	Loop
	Return n
End Sub

'Guarda el nombre de la pagina en base de datos la muestra.
Sub iniciaActividad(ia As String)
	If ia <> "" And ia <> Null Then
'		If B4XPages.MainPage.logger Then LogColor($"Guardamos en BD '${ia}'"$, Colors.Yellow)
		B4XPages.MainPage.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("ULTIMOMODULO"))
		B4XPages.MainPage.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("ULTIMOMODULO", ia))
		B4XPages.ShowPage(ia)
'		If B4XPages.MainPage.logger Then LogColor("Iniciamos --> " & ia, Colors.Blue)
	End If
End Sub

'Revisa si en el pedido hay $150 o mas de los 2 productos especificados, y si si, entonces agrega el producto de regalo a la tabla pedido.
Sub revisaPromoProcterPRO3009(prodsMap1 As Map)
	Log("entramos a promo Procter")
	Dim almacenes As List
	almacenes.Initialize2(Array As String("3", "17", "37", "31", "68", "71", "33", "80"))
	Log("Posicion de almacen: " & almacenes.IndexOf(traeAlmacen))
'	("3", "17", "37", "31", "68", "71", "33", "80")
	If almacenes.IndexOf(traeAlmacen) >= 0 Then ' Si el almacen actual esta dentro de la lista de almacenes autorizados para la promocion, entonces continuamos ...
		Private idRegalo As String = "PYG1016"
		Private newProdsMap As Map = prodsMap1
		Private monto As String = 0
		Private invRegalo As Int = 0
		Private totalRegalos As Int = 0
		Private r As ResultSet = Starter.skmt.ExecQuery($"select ifnull(cat_gp_almacen, 0) as CAT_GP_ALMACEN from ${traeTablaProds(traeTipoVentaDeBD)} where cat_gp_id = '${idRegalo}'"$) ' Traemos el inventario del regalo.
		Do While r.NextRow
			Log(r.GetString("CAT_GP_ALMACEN"))
			If r.GetString("CAT_GP_ALMACEN") > 0 Then invRegalo = r.GetString("CAT_GP_ALMACEN")
		Loop
		Log("INV REGALO: " & invRegalo)
		r = Starter.skmt.ExecQuery($"select pe_proid from pedido where pe_proid = '${idRegalo}' and pe_cedis = 'PRO3009' and pe_cant = 1 and pe_cliente = '${traeCliente}' "$) 'Revisamos si el regalo ya existe en el pedido.
		If r.RowCount > 0 Then totalRegalos = r.RowCount
		Log($"TOTAL REGALOS: ${totalRegalos}"$)
		If prodsMap1.ContainsKey("PYG1014") Then ' Calculamos el monto del producto 1
			Private m1 As Map = prodsMap1.Get("PYG1014")
			monto = monto + m1.Get("cant") * m1.Get("precio")
		End If
		If prodsMap1.ContainsKey("PYG1025") Then  ' Calculamos el monto del producto 2
			Private m2 As Map = prodsMap1.Get("PYG1025")
			monto = monto + m2.Get("cant") * m2.Get("precio")
		End If
		Private r2 As ResultSet = Starter.skmt.ExecQuery($"select ifnull(sum(pe_costo_tot),0) as monto from pedido where pe_cliente = '${traeCliente}' and PE_PROID in ("PYG1014", "PYG1025")"$)
		Do While r2.NextRow
			monto = monto + r2.GetString("monto")
		Loop
		Log($"Monto: ${monto}"$)
'		Log("PM: " & prodsMap)
		Log("prodsMap.Size: " & prodsMap1.Size)
		If prodsMap1.Size > 0 Then
			If monto > 149 Then
				Log("Mas de 149")
				If invRegalo > 0 Then
					Log("Hay inventario para regalo")
					If totalRegalos = 0 Then
						guardaProducto("PRO3009", 1, 1, traeProdNombre(idRegalo), idRegalo, traeCliente, traeFecha, traeUsuarioDeBD, traeRuta, 0, B4XPages.MainPage.tipo_venta)
'				ToastMessageShow("Agregamos regalo", False)
						Private cs As CSBuilder
						cs.Initialize
						Msgbox(cs.Color(Colors.red).append("PRO3009:").Pop.append(" En la compra de $150 de la linea Oral B Procter & Gamble se agrega 1 Salvo Multiusuos 500 ml.").Color(Colors.red).Append(" con precio de $1.00").popall, "Producto de regalo agregado") 'ignore
					End If
				Else
					LogColor("NO hay inventario para regalo PRO3009 Procter & Gamble.", Colors.red)
				End If
			Else
				Log("Menos de 149")
				If totalRegalos > 0 Then
					Log("Quitamos regalo")
					Starter.skmt.ExecNonQuery($"delete from pedido where pe_cedis = 'PRO3009' and pe_proid = '${idRegalo}' and pe_cliente = '${traeCliente}' and pe_cant = 1 "$)
					Log($"Borramos pe_cedis='PRO3009' and pe_proid='${idRegalo}' and pe_cliente='${traeCliente}'"$)
					Private pe As Cursor = Starter.skmt.ExecQuery("select count(pe_cliente) as cuantosPedidos from pedido where pe_cliente In (select cuenta from cuentaa)")
					pe.Position=0
					If pe.GetString("cuantosPedidos") = 0 Then
						Log("######    delete from pedido_cliente - " & traeCliente & "|" & traeCliente)
						Log($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
						Starter.skmt.ExecNonQuery($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
					End If
					ToastMessageShow("Quitamos regalo PRO3009", False)
				End If
			End If
		End If
	Else
		LogColor("El almacen actual NO esta en la lista para la promo de Procter & Gamble.", Colors.Magenta)
	End If
'	Return newProdsMap
End Sub

'Valida que si hay un regalo de la promo, tambien existan productos en el pedido para respaldarla.
Sub validaPromoProcterPRO3009
	Private idRegalo As String = "PYG1016"
	Private r As ResultSet = Starter.skmt.ExecQuery($"select pe_cliente from pedido where pe_proid = '${idRegalo}' and pe_cedis = 'PRO3009'"$)
'	Private cont As Int = 0
	Do While r.NextRow
		Private monto As String = 0
		Private r2 As ResultSet = Starter.skmt.ExecQuery($"select ifnull(sum(pe_costo_tot),0) as monto from pedido where pe_cliente = '${r.GetString("PE_CLIENTE")}' and PE_PROID in ("PYG1014", "PYG1025")"$)
		Do While r2.NextRow
			Log($"Cliente: ${r.GetString("PE_CLIENTE")}, monto: ${r2.GetString("monto")}"$)
'			cont = cont + 1
'			Log("CONT = " & cont & " , MONTO: " & r2.GetString("monto"))
			If r2.GetString("monto") < 150 Then
				Starter.skmt.ExecNonQuery($"delete from pedido where pe_cedis = 'PRO3009' and pe_proid = '${idRegalo}' and pe_cliente = '${r.GetString("PE_CLIENTE")}' and pe_cant = 1 "$)
'				If cont = 1 Then 
				Msgbox($"Regalo de la promoción PRO3009 borrado por no cumplir con los requisitos mínimos."$, "Regalo Borrado") 'ignore
'				End If
			End If
		Loop
		r2 = Starter.skmt.ExecQuery($"select count(pe_proid) as cuantos from pedido where pe_cliente = '${r.GetString("PE_CLIENTE")}'"$) 'Revisamos si es que hay mas pedido del cliente, y si no, borramos el header.
		Do While r2.NextRow
'			Log(">>>>    CUANTOS:  " & r2.GetString("cuantos"))
			If r2.GetString("cuantos") = 0 Then
				Starter.skmt.ExecNonQuery($"delete from pedido_cliente where pc_cliente = '${r.GetString("PE_CLIENTE")}'"$)
				Starter.skmt.ExecNonQuery($"update kmt_info3 set gestion = 0 where cat_cl_codigo = '${r.GetString("PE_CLIENTE")}'"$)
			End If
		Loop
	Loop
End Sub

'Regresa TRUE si el cliente especificado existe en el historico de compra de la promo.
Sub revisaPRO2265(idCliente As String) As Boolean
	Private r As ResultSet = Starter.skmt.ExecQuery($"select * from HIST_PRO2265 where H_IDCLIENTE = '${idCliente}'"$)
	Private x As Boolean = False
	Do While r.NextRow
		x = True
	Loop
	Log($">>>>  revisaPRO2265 ${idCliente} = ${x}"$)
	Return x
End Sub

'Regresa TRUE si el cliente especificado existe en el historico de compra de la promo.
Sub revisaPRO3054(idCliente As String) As Boolean
	Private r As ResultSet = Starter.skmt.ExecQuery($"select * from HIST_PRO3054 where H_IDCLIENTE = '${idCliente}'"$)
	Private x As Boolean = False
	Do While r.NextRow
		x = True
	Loop
	Log($">>>>  revisaPRO3054 ${idCliente} = ${x}"$)
	Return x
End Sub

'Regresa TRUE si el cliente especificado existe en el historico de compra de la promo.
Sub revisaPRO3055(idCliente As String) As Boolean
	Private r As ResultSet = Starter.skmt.ExecQuery($"select * from HIST_PRO3055 where H_IDCLIENTE = '${idCliente}'"$)
	Private x As Boolean = False
	Do While r.NextRow
		x = True
	Loop
	Log($">>>>  revisaPRO3055 ${idCliente} = ${x}"$)
	Return x
End Sub

'Revisa si hay actualizacion, si es necesario relaizarla y cual es la liga del APK de la actualización.
' Regresa 0, 1 o 2 dependiendo de CAT_VARIABLES (Sin actualizacion, Forzar actualizacion, Sin adtos, forzar descarga de datos)
Sub revisaForzarActualizacion As Map
	Private ra As ResultSet = Starter.skmt.ExecQuery($"select * from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'VERSION_NUEVA' or CAT_VA_DESCRIPCION = 'FORZAR_ACTUALIZACION'"$)
	Private nuevaVersion As String = ""
	Private forzarActualizacion As Int = 2
	Private m As Map
	m.Initialize
	Do While ra.NextRow
		If ra.GetString("CAT_VA_DESCRIPCION") = "VERSION_NUEVA" Then nuevaVersion = ra.GetString("CAT_VA_VALOR")
		If ra.GetString("CAT_VA_DESCRIPCION") = "FORZAR_ACTUALIZACION" Then forzarActualizacion = ra.GetInt("CAT_VA_VALOR")
	Loop
'	Log(nuevaVersion)
'	Log(forzarActualizacion)
	If nuevaVersion <> Application.VersionName And forzarActualizacion = 1 Then
		m.Put("forzar", 1)
		m.Put("nuevaVersion", nuevaVersion)
	else if forzarActualizacion = 2 Then 'Si no existen los datos en CAT_VARIABLES, entonces los recargamos
		m.Put("forzar", 2)
		m.Put("nuevaVersion", "Recargar")
	Else
		m.Put("forzar", 0)
		m.Put("nuevaVersion", nuevaVersion)
	End If
	If nuevaVersion = "" Or nuevaVersion = Null Or nuevaVersion = "null" Then
		m.Put("forzar", 0)
		m.Put("nuevaVersion", "")
	End If
	Return m
End Sub

Sub parseHTTPError(error As String) As String
	Private nuevoError As String = error
	Private inicio As Int = error.IndexOf("<title>")
	Private final As Int = error.IndexOf("</title>")
	Log("|" & inicio & "|" & final & "|")
	If inicio > 0 And final > 0 Then
		nuevoError = error.SubString2(inicio + 17, final)
'		LogColor(error.SubString2(inicio + 17, final), Colors.Blue)
	End If
	Return nuevoError
End Sub

' Se revisa si hay una intención (intent) de abrir una base de datos y si es así, entonces se importa esa base de datos.
Sub importaBDDesdeWhatsApp
'	Private tmpBDWA As Boolean = traeUsarIntentBDWA
	Log("Revisamos intent de importar desde whatsapp")
	Log(B4XPages.MainPage.intentUsado)
	Log(in)
	If Not(in.IsInitialized) Then in = B4XPages.GetNativeParent(B4XPages.MainPage).GetStartingIntent ' Si se usa esta funcion en Mainpage, se pone "Me" en lugar de B4XPages.MainPage.
	If Not(B4XPages.MainPage.intentUsado) And in <> Null Then
'		Log(in)
		LogColor("Importamos base de datos desde Whatsapp.", Colors.blue)
		B4XPages.MainPage.intentUsado = True
'			Log(in.As(String))
		If in.GetData <> Null Then
			Dim XmlData As String
			XmlData = in.GetData
			Try
				Dim OutStr As OutputStream = File.OpenOutput(File.DirInternal,"kmt.db",False)
				Dim InStr As InputStream = File.OpenInput("ContentDir",XmlData)
				File.Copy2(InStr,OutStr)
				LogColor("BD copiada a interna.", Colors.Blue)
				OutStr.Close
				If in.As(String).Contains("whatsapp") Then ToastMessageShow("BD cargada desde Whatsapp", False)
			Catch
				Log(LastException)
			End Try
'			ExitApplication
'			Starter.skmt.ExecNonQuery("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'IMPORTAR_BD_WA'")
'			Starter.skmt.ExecNonQuery($"insert into CAT_VARIABLES (CAT_VA_DESCRIPCION, CAT_VA_VALOR) values ('IMPORTAR_BD_WA', '${tmpBDWA}')"$)
			Private a As Cursor = Starter.skmt.ExecQuery($"select CAT_VA_VALOR from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'APP_NAME'"$)
			If a.RowCount > 0 Then
				a.Position = 0
				ToastMessageShow($"BD de "${a.GetString("CAT_VA_VALOR")}" cargada."$, True)
			End If
			a = Starter.skmt.ExecQuery($"select * from usuarioa"$)
			If a.RowCount > 0 Then
				a.Position = 0
				B4XPages.MainPage.user.Text = a.GetString("USUARIO")
				B4XPages.MainPage.pass.Text = a.GetString("PASS")
			End If
		End If
	End If
End Sub

'Regresa si se debe de usar el intent de importar la base d datos desde Whatsapp. 
Sub traeUsarIntentBDWA As Boolean 'ignore
	Private BDWA As Boolean = False
	Private x As Cursor = Starter.skmt.ExecQuery($"select CAT_VA_VALOR from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'IMPORTAR_BD_WA'"$)
	If x.RowCount > 0 Then
		x.Position = 0
		If x.GetString("CAT_VA_VALOR") = "true" Then BDWA = True
	End If
'	Log($"cb_importarBDWA = ${BDWA}"$)
	Return BDWA
End Sub

Sub actualizaProducto(cedis As String, costoU As String, cant As String, nombre As String, prodId As String, clienteId As String, fecha As String, usuario As String, rutaV As String, precioSin As String, tipoVenta As String,dev As String)
	If nombre.Length < 6 Then ToastMessageShow("(actualizaProducto) El nombre del producto no es valido " & nombre, True)
	precioSin = NumberFormat2(precioSin, 1, 2, 2, False)
	costoU = NumberFormat2(costoU, 1, 2, 2, False)
	LogColor($"actualizaProducto, c=${clienteId}, p=${prodId}, nombre=${nombre}, cant=${cant}, cedis=${cedis}, tipo=${tipoVenta}"$, Colors.Magenta)
'	Private tablaProds As String = "cat_gunaprod2"
'	If tipoVenta = "ABORDO" Then tablaProds = "cat_gunaprod"
	If dev = 0 Then
		Private c As Cursor=Starter.skmt.ExecQuery($"select * from pedido where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' and PE_CEDIS <> 'DUR'"$)
		If c.RowCount > 0 Then
'		LogColor("ACTUALIZAMOS PROD - " & traeTablaProds(tipoVenta), Colors.Blue)
			c.Position = 0
			Private antCant As Int = 0
			If IsNumber(c.GetInt("PE_CANT")) Then antCant=c.GetInt("PE_CANT")
			Private difCant As Int = cant - antCant
			Starter.skmt.ExecNonQuery($"update pedido set pe_cant = ${cant}, pe_costo_tot = ${NumberFormat2((cant*c.GetString("PE_COSTOU")), 1, 2, 2, False)}, pe_folio = '${tipoVenta}' where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' "$)
			If cedis <> "DUR" Then Starter.skmt.ExecNonQuery($"update ${traeTablaProds(tipoVenta)} set cat_gp_almacen = cat_gp_almacen - (${difCant}) where cat_gp_id = '${prodId}' "$)
			If cant = 0 Then
				LogColor($"BORRAMOS PRODUCTO - ${prodId}"$, Colors.Red)
				Starter.skmt.ExecNonQuery($"delete from pedido where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' "$)
				Log($"Borramos pe_cedis='${cedis}' and pe_proid='${prodId}' and pe_cliente='${clienteId}'"$)
				Private pe As Cursor = Starter.skmt.ExecQuery("select count(pe_cliente) as cuantosPedidos from pedido where pe_cliente In (select cuenta from cuentaa)")
				pe.Position=0
				If pe.GetString("cuantosPedidos") = 0 Then
					Log("######    delete from pedido_cliente - " & traeCliente & "|" & clienteId)
					Log($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
					Starter.skmt.ExecNonQuery($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
				End If
			End If
		Else
			'INSERTAMOS
'		LogColor("INSERTAMOS PROD", Colors.red)
			If cant > 0 Then guardaProductoSinGestion(cedis, costoU, cant, nombre, prodId, clienteId, fecha, usuario, rutaV, precioSin, tipoVenta)
		End If
		c.Close
	Else If dev = 1 Then
		Private c As Cursor=Starter.skmt.ExecQuery($"select * from pedido where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' and PE_CEDIS = 'DUR'"$)
		If c.RowCount > 0 Then
'		LogColor("ACTUALIZAMOS PROD - " & traeTablaProds(tipoVenta), Colors.Blue)
			c.Position = 0
			Private antCant As Int = 0
			If IsNumber(c.GetInt("PE_CANT")) Then antCant=c.GetInt("PE_CANT")
			Private difCant As Int = cant - antCant
			Starter.skmt.ExecNonQuery($"update pedido set pe_cant = ${cant}, pe_costo_tot = ${NumberFormat2((cant*c.GetString("PE_COSTOU")), 1, 2, 2, False)}, pe_folio = '${tipoVenta}' where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' "$)
			If cedis <> "DUR" Then Starter.skmt.ExecNonQuery($"update ${traeTablaProds(tipoVenta)} set cat_gp_almacen = cat_gp_almacen - (${difCant}) where cat_gp_id = '${prodId}' "$)
			If cant = 0 Then
				LogColor($"BORRAMOS PRODUCTO - ${prodId}"$, Colors.Red)
				Starter.skmt.ExecNonQuery($"delete from pedido where pe_cedis = '${cedis}' and pe_proid = '${prodId}' and pe_cliente = '${clienteId}' "$)
				Log($"Borramos pe_cedis='${cedis}' and pe_proid='${prodId}' and pe_cliente='${clienteId}'"$)
				Private pe As Cursor = Starter.skmt.ExecQuery("select count(pe_cliente) as cuantosPedidos from pedido where pe_cliente In (select cuenta from cuentaa)")
				pe.Position=0
				If pe.GetString("cuantosPedidos") = 0 Then
					Log("######    delete from pedido_cliente - " & traeCliente & "|" & clienteId)
					Log($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
					Starter.skmt.ExecNonQuery($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
				End If
			End If
		Else
			'INSERTAMOS
'		LogColor("INSERTAMOS PROD", Colors.red)
			If cant > 0 Then guardaProductoSinGestion(cedis, costoU, cant, nombre, prodId, clienteId, fecha, usuario, rutaV, precioSin, tipoVenta)
		End If
		c.Close
	End If
	
End Sub


'Inserta un producto en la tabla "pedido" y "pedido_cliente" y actualiza "cat_gunaprod".
'NO ACTUALIZA LA BANDERA DE GESTION EN LA TABLA "kmt_info".
'Si "gestion=2" entonces el sistema considera que el pedido ya se guardó y ya no se debe modificar.
Sub guardaProductoSinGestion(cedis As String, costoU As String, cant As String, nombre As String, prodId As String, clienteId As String, fecha As String, usuario As String, rutaV As String, precioSin As String, tipoVenta As String)
	If nombre.Length < 6 Then ToastMessageShow("(guardaProductoSinGestion) El nombre del producto no es valido " & nombre, True)
	precioSin = NumberFormat2(precioSin, 1, 2, 2, False)
	costoU = NumberFormat2(costoU, 1, 2, 2, False)
'	LogColor("guardaProductoSinGestion: "&prodId&", cant="&cant&", tipo="&tipoVenta, Colors.Magenta)
	Private c As Cursor
'	Private tablaProds As String = "cat_gunaprod2"
'	If tipoVenta = "ABORDO" Then tablaProds = "cat_gunaprod"
	Starter.skmt.ExecNonQuery2("INSERT INTO PEDIDO (PE_CEDIS, PE_COSTO_TOT, PE_COSTOU, PE_CANT, PE_PRONOMBRE, PE_PROID, PE_CLIENTE, PE_FECHA, PE_USUARIO, PE_RUTA, PE_COSTO_SIN, PE_FOLIO) VALUES(?,?,?,?,?,?,?,?,?,?,?,?) ", Array As Object (cedis, (cant * costoU), costoU, cant, nombre, prodId, clienteId, fecha, usuario, rutaV, precioSin, tipoVenta))
	'Actualizamos el inventario en cat_gunaprod solo si no es RMI
	If cedis <> "DUR" Then Starter.skmt.ExecNonQuery2($"update ${traeTablaProds(tipoVenta)} set cat_gp_almacen = cat_gp_almacen - ? where cat_gp_id = ? "$, Array As Object(cant, prodId))
	c=Starter.skmt.ExecQuery("select sum(pe_costo_tot) as TOTAL_CLIE, SUM(PE_CANT) AS CANT_CLIE, SUM(PE_COSTO_SIN) AS TOTAL_CLIE_SIN FROM PEDIDO WHERE PE_CLIENTE IN (Select CUENTA from cuentaa)")
	c.Position=0
'	Log("######    delete from pedido_cliente - " & traeCliente & "|" & clienteId)
'	Log($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
	Starter.skmt.ExecNonQuery($"delete from pedido_cliente where PC_CLIENTE = '${traeCliente}'"$)
	Starter.skmt.ExecNonQuery2("insert into pedido_cliente(PC_CLIENTE, PC_FECHA, PC_USER, PC_NOART, PC_MONTO,PC_LON, PC_LAT,PC_ALMACEN,PC_RUTA,PC_COSTO_SIN) VALUES (?,?,?,?,?,?,?,?,?,?)", Array As Object(clienteId, fecha, usuario, c.GetString("CANT_CLIE"), c.GetString("TOTAL_CLIE"), B4XPages.MainPage.lon_gps, B4XPages.MainPage.lat_gps, cedis, rutaV, c.GetString("TOTAL_CLIE_SIN")))
End Sub

'Regresa la tabla de productos (cat_gunaprod o cat_gunaprod2) dependiendo del tipo de venta.
Sub traeTablaProds(tipoventa As String) As String
	Log(tipoventa)
	Private tablaProds As String = "cat_gunaprod2"
	If tipoventa = "ABORDO" Or tipoventa = "PREVENTA" Then tablaProds = "cat_gunaprod"
'	LogColor($"Tipo= ${tipoventa}, tabla=${tablaProds}"$, Colors.RGB(200,136,0))
	Return tablaProds
End Sub

'Revisa si el producto dado se encuentra en el pedido sin formar parte de una promoción.
'Regresa verdadero o falso.
Sub revisaProdFueraDePromo(prodId As String) As Boolean
	Private fueraDePromo As Boolean = False
	Private p As Cursor = Starter.skmt.ExecQuery($"select PE_CEDIS from PEDIDO where PE_PROID = '${prodId}' and PE_CEDIS in (select id_almacen from CAT_ALMACEN) and PE_CLIENTE = '${B4XPages.MainPage.cliente.la_cuenta.text}'"$)
	Log("rowcount: " & p.RowCount)
	If p.RowCount > 0 Then
		p.Position = 0
		Log($"|${p.GetString("PE_CEDIS")}|${prodId}|"$)
		fueraDePromo = True
	End If
	Return fueraDePromo
End Sub
 
'Trae el tipo de venta (VENTA o PREVENTA) desde CAT_VARIABLES, o "PREVENTA" por default.
Sub traeTipoVentaDeBD As String
	Private tipoVenta As String = "PREVENTA"
	Private r As ResultSet = Starter.skmt.ExecQuery("select * from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'TIPO_VENTA'  ")
	Do While r.NextRow
		tipoVenta = r.GetString("CAT_VA_VALOR").As(String).ToUpperCase
	Loop
	If Starter.idCliente = "0" Then
		tipoVenta = "ABORDO"
	End If
'	Log(">>>>>>>    " & tipoVenta)
	Return tipoVenta
End Sub

'Guarda una variable en CAT_VARIABLES
Sub guardaVariable(nombre As String, valor As String)
	B4XPages.MainPage.skmt.ExecNonQuery($"delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = '${nombre}'"$)
	B4XPages.MainPage.skmt.ExecNonQuery($"insert into CAT_VARIABLES (CAT_VA_DESCRIPCION, CAT_VA_VALOR) values ('${nombre}', '${valor}')"$)
End Sub

'Regresa el valor de la variable especificada desde CAT_VARIABLES
Sub traeVariable(nombre As String) As String
	Private valor As String = ""
	Private laVc As Cursor = Starter.skmt.ExecQuery($"select CAT_VA_VALOR from CAT_VARIABLES where CAT_VA_DESCRIPCION = '${nombre}'"$)
	If laVc.RowCount > 0 Then 
		laVc.Position = 0
		valor = laVc.GetString("CAT_VA_VALOR")
	End If
	Return valor
End Sub

Sub revisaImpreso As Boolean 'ignore
	Private impreso As Boolean = False
	Private c As Cursor = Starter.skmt.ExecQuery("select idCliente from ticket_impreso where idCliente in (select cuenta from cuentaa)")
	If c.RowCount > 0 Then impreso = True
	Return impreso
End Sub

Sub deleteFolder(folder As String)
	Log("Borrando " & folder)
	For Each f As String In File.ListFiles(folder)
		Log(f)
		If File.IsDirectory(folder, f) Then
			deleteFolder(File.Combine(folder, f))
			Log($"Borramos ${File.Combine(folder, f)}"$)
		End If
		File.Delete(folder, f)
	Next
End Sub

Sub enviaWhatsappMsg()
'	DateTime.DateFormat="MM/dd/yyyy HH:mm:ss"
'	Private Fecha As String=DateTime.Date(DateTime.now)
'	Private telefono As String = "5215554192439"
'	Private monto As String = "1500"
'	Private idCliente As String = "10748"
'
''	CON POST
'	Dim link As String = "http://keymon.lat:9000/v1/messages"
'	Dim elJSON As String = $"{
'		"number":"${telefono}",
'		"message":"Tu compra de Guna va ser a crédito por el monto de $${NumberFormat2(monto, 1, 2, 2, True)}${retorno}${retorno}Manda *1* para confirmar${retorno}Manda *0* para rechazar${retorno}${retorno}(Solo el *número* por favor),
'		"monto":${monto},
'		"idcliente":${idCliente},
'		"almacen":"1",
'		"ruta": "333",
'		"fecha":${Fecha}
'		}"$
'	Dim job As HttpJob
'	job.Initialize("", Me)
'	job.PostString(link, elJSON)
'	job.GetRequest.SetContentType("application/json")
'	Wait For (job) JobDone(job As HttpJob)
'
''	CON GET
'	Dim retorno As String = "%0D%0A"
'	Dim link As String = $"http://keymon.lat:9000/v1/messages?number=${telefono}&message=Tu compra de Guna va ser a crédito por el monto de $${NumberFormat2(monto, 1, 2, 2, True)}${retorno}${retorno}Manda *1* para confirmar${retorno}Manda *0* para rechazar${retorno}${retorno}(Solo el *número* por favor)&monto=${monto}&idcliente=${idCliente}&almacen=1&ruta=333&fecha=${Fecha}"$
'	Dim job As HttpJob
'	job.Initialize("", Me)
''	job.PostString(link, elJSON)
'	job.Download(link)
''	job.GetRequest.SetContentType("application/json")
'	Wait For (job) JobDone(job As HttpJob)
'
'	Log("Whatsapp enviado con exito: " & job.Success)
End Sub


'Regresa el tipo de cliente:
' - 1 = platinum
' - 2 = oro
' - 3 = bronze
Sub traeTipoCliente As Int
	Private x As Cursor
	Private tc As Int = 3
	x = Starter.skmt.ExecQuery($"select IFNULL(cat_cl_tipocliente,3) AS CAT_CL_TIPOCLIENTE from kmt_info3 where cat_cl_codigo in (select cuenta from cuentaa)"$)
	If x.RowCount > 0 Then
		x.Position = 0
'		Log("ESTO LO HIZO GUERRA ------>  "&x.GetInt("CAT_CL_TIPOCLIENTE"))
		tc = x.GetInt("CAT_CL_TIPOCLIENTE")
	End If
'	Log("ESTO LO HIZO GUERRA X2------>  "&tc)
	Return tc
End Sub

Sub traeinventario(id As String) As String
	Dim c As Cursor
	Dim inventario As String = "0"
	c=B4XPages.MainPage.skmt.ExecQuery($"select CAT_GP_ALMACEN from ${traeTablaProds(B4XPages.MainPage.tipo_venta)} where CAT_GP_ID = '${id}'"$)
'	Log($"select CAT_GP_ALMACEN from ${Starter.tabla} where CAT_GP_NOMBRE = '${id}'"$)
	If c.RowCount > 0 Then
		c.Position = 0
		inventario = c.GetString("CAT_GP_ALMACEN")
	End If
	c.Close
	Return inventario
End Sub

'Regresa el total de productos y monto del pedido del cliente actual.
Sub traeTotalesClienteActual As Map
	Private m As Map
	m.Initialize
	Private c_prodsX As Cursor=B4XPages.MainPage.skmt.ExecQuery("select ifnull(sum(PE_CANT), 0) as cantProds, ifnull(sum(PE_COSTO_TOT), 0) as costoTotal FROM PEDIDO WHERE PE_CLIENTE IN (Select CUENTA from cuentaa) order by PE_PRONOMBRE asc")
	c_prodsX.Position=0
'	LogColor($"Productos de la orden: ${c_prodsX.GetString("cantProds")}, Total: ${c_prodsX.GetString("costoTotal")}"$, Colors.red)
	m = CreateMap("productos": c_prodsX.GetString("cantProds"), "monto" : c_prodsX.GetString("costoTotal"))
	Return m
End Sub


'Regresa un mapa con la información de la promo.
'Regresa: {id, maxXcliente, maxRecurrente, maxPromos, historico,
'	productos={idProducto={idProducto, preciosimptos, precio, almacen, tipo, piezas, usuario, fecha, regalo, clasif}} 'Mapa con los productos de la promo y los datos de cada uno.
'	tipos={idProducto=tipo} 'Mapa con id y tipo del producto, 0 si es fijo y 1 si es variable.
'	prodsFijos={idProducto,idProducto} 'Lista con los ids de los productos fijos.
'	prodsVariables={idProducto,idProducto} 'Lista con los ids de los productos variables.
'	resultado="OK" 'Ok si existe la promocion.
'	prodsVariablesRequeridos=5} 'Cantidad de productos variables requeridos para la promoción.
Sub traePromo(promo As String, cliente As String) As Map
	Private inicioContador As String = DateTime.Now
	Private c As Cursor = B4XPages.MainPage.skmt.ExecQuery("Select * from promos_comp where cat_pa_id = '"& promo&"'") 'Obtenemos las el maximo de promocioones a otorgar.
	Private siHistorico As String = 0
	Private promoMap As Map
	Private prodsFijos, prodsFijosPrecios, prodsFijosPiezas, prodsVariables, prodsVariables2, prodsVariablesPrecios, prodsVariablesPrecios2 As List
	promoMap.Initialize
	prodsFijos.Initialize
	prodsFijosPrecios.Initialize
	prodsFijosPiezas.Initialize
	prodsVariables.Initialize
	prodsVariables2.Initialize
	prodsVariablesPrecios.Initialize
	prodsVariablesPrecios2.Initialize
	c.Position = 0
	If c.RowCount > 0 Then promoMap = CreateMap("id":promo, "maxXcliente":c.GetString("CAT_PA_MAXPROMCLIE"), "maxRecurrente":c.GetString("CAT_PA_MAXPROMREC"), "maxPromos":c.GetString("CAT_PA_MAXPROM"))
	c = B4XPages.MainPage.skmt.ExecQuery("Select count(*) as hist from HIST_PROMOS where HP_CLIENTE = '"& cliente & "' and HP_CODIGO_PROMOCION = '" & promo & "'") 'Revisamos si hay historico de la promoción.
	c.Position = 0
	If c.GetString("hist") > 0 Then siHistorico = 1
	promoMap.Put("historico", siHistorico)
	c = B4XPages.MainPage.skmt.ExecQuery("Select * from CAT_DETALLES_PAQ where CAT_DP_ID = '"& promo & "'") 'Obtenemos los detalles de la promoción.
	c.Position = 0
	If c.RowCount > 0 Then
		Private prods, tipos As Map
		prods.Initialize
		tipos.Initialize
		For i=0 To c.RowCount -1
			c.Position=i
			prods.Put(c.GetString("CAT_DP_IDPROD"), CreateMap("idProducto":c.GetString("CAT_DP_IDPROD"), "precioSimptos":c.GetString("CAT_DP_PRECIO_SIMPTOS"), "precio":c.GetString("CAT_DP_PRECIO"), "almacen":c.GetString("CAT_DP_ALMACEN"), "tipo":c.GetString("CAT_DP_TIPO"), "piezas":c.GetString("CAT_DP_PZAS"), "usuario":c.GetString("CAT_DP_USUARIO"), "regalo":c.GetString("CAT_DP_REGALO"), "clasif":c.GetString("CAT_DP_CLASIF")))
			tipos.Put(c.GetString("CAT_DP_IDPROD"), c.GetString("CAT_DP_TIPO"))
			If c.GetString("CAT_DP_TIPO") = "0" Then
				prodsFijos.Add(c.GetString("CAT_DP_IDPROD"))
				prodsFijosPrecios.Add(c.GetString("CAT_DP_PRECIO"))
				prodsFijosPiezas.Add(c.GetString("CAT_DP_PZAS"))
			End If
			If c.GetString("CAT_DP_TIPO") = "1" Then
				prodsVariables.Add(c.GetString("CAT_DP_IDPROD"))
				prodsVariablesPrecios.Add(c.GetString("CAT_DP_PRECIO"))
			End If
			If c.GetString("CAT_DP_TIPO") = "2" Then
'				LogColor(c.GetString("CAT_DP_IDPROD") & "|" & c.GetString("CAT_DP_TIPO"), Colors.Blue)
				prodsVariables2.Add(c.GetString("CAT_DP_IDPROD"))
				prodsVariablesPrecios2.Add(c.GetString("CAT_DP_PRECIO"))
			End If
'			Log($"id:${c.GetString("CAT_DP_IDPROD")}, tipo:${c.GetString("CAT_DP_TIPO")}"$)
'			If prodsVariables2.Size > 0 Then LogColor(c.GetString("CAT_DP_ID") & "|" & prodsVariables2, Colors.red)
		Next
		promoMap.Put("productos", prods) 'Mapa con los productos de la promocion (id, precio, almacen, tipo, piezas, etc.)
		promoMap.Put("tipos", tipos) 'Mapa con los productos de la promoción y su tipo (fijo o variable).
		promoMap.Put("prodsFijos", prodsFijos) 'Lista de los productos fijos de la promoción.
		promoMap.Put("prodsVariables", prodsVariables) 'Lista de los productos variables de la promoción.
		promoMap.Put("prodsVariables2", prodsVariables2)
		promoMap.Put("prodsFijosCant", prodsFijos.Size)
		promoMap.Put("prodsFijosPrecios", prodsFijosPrecios)
		promoMap.Put("prodsFijosPiezas", prodsFijosPiezas)
		promoMap.Put("prodsVariablesCant", prodsVariables.Size)
		promoMap.Put("prodsVariables2Cant", prodsVariables2.Size)
		promoMap.Put("prodsVariablesPrecios", prodsVariablesPrecios)
		promoMap.Put("prodsVariablesPrecios2", prodsVariablesPrecios2)
		promoMap.Put("resultado", "ok")
	Else
		promoMap.Put("resultado", "No hay datos de la promoción.")
	End If
	c = B4XPages.MainPage.skmt.ExecQuery2($"Select CAT_GP_STS, CAT_GP_IMP1, CAT_GP_NOMBRE from ${Starter.tabla} where CAT_GP_ID = ?"$,Array As String(promo)) 'Obtenemos las piezas requeridas de productos variables para la promoción.
	c.Position = 0
	Private pvr As String = 0
	Private pvr2 As String = 0
	If c.RowCount > 0 Then
		c.Position = 0
		pvr = c.GetString("CAT_GP_STS")
		pvr2 = c.GetString("CAT_GP_IMP1")
		If pvr = Null Or pvr = "null" Then pvr = 0
		If pvr2 = Null Or pvr2 = "null" Then pvr2 = 0
		promoMap.Put("prodsVariablesRequeridos", pvr) 'Cantidad de productos variables requeridos para la promoción.
		promoMap.Put("prodsVariables2Requeridos", pvr2)
		promoMap.put("descripcion", c.GetString("CAT_GP_NOMBRE"))
	End If
	c.Close
'	Log($"Inv variables: ${cuantosVariablesTengoBD(promo)}"$)
'	Log($"Inv dispo: ${traemosInventarioDisponibleParaPromo(promo)}"$)
'	LogColor($"Promo ${promo}: ${promoMap}"$, Colors.Green)
'	LogColor("TIEMPO para traePromo -=" & promo & "=- : " & ((DateTime.Now-inicioContador)/1000), Colors.Red)
	Return promoMap
End Sub

'Regresa cuantas promos alcanzan con los productos VARIABLES que hay en inventario.
'La cantidad de promos disponibles se calcula DESPUES de descontar los productos fijos, y si las
'promos por productos fijos llega al maximo, aunque se puedan mas de producos variables, solo se
'regresa el maximo por productos fijos. Ej. si las promos por variables es 10, pero el maximo por
'fijos es 5, entonces regresamos 5.
Sub revisaMaxPromosProdsVariablesPorInventario(pm As Map) As Int 'ignore
	Private thisLog As Boolean = False
	If thisLog Then Log("======================================================")
	If thisLog Then Log("======================================================")
	Private invFijoXpromo As Map
	invFijoXpromo.Initialize
	Private totalProdsVariablesDisponibles As Int = 0
	Private totalProdsVariables2Disponibles As Int = 0
	If thisLog Then LogColor($"pm=${pm}"$, Colors.Blue)
	Private invDispParaPromo As Map = traemosInventarioDisponibleParaPromo(pm.Get("id"))
	If thisLog Then Log($"invDispParaPromo=${invDispParaPromo}"$)
	Private maxPromos As String = traeMaxPromos(pm)
	Private maxPromosXFijos As Int = revisaMaxPromosProdsFijosPorInventario(pm)
	Private idProdsVariables As List = pm.Get("prodsVariables")
	Private idProdsVariables2 As List = pm.Get("prodsVariables2")
	Private prodsVariablesRequeridos As Int = pm.Get("prodsVariablesRequeridos")
	Private prodsVariables2Requeridos As Int = pm.Get("prodsVariables2Requeridos")
	Private prodsFijosPiezas As List = pm.Get("prodsFijosPiezas")
	Private idProdsFijos As List = pm.Get("prodsFijos")
	For p = 0 To idProdsFijos.Size -1 'Generamos mapa con los productos fijos y piezas requeridas por promo.
		invFijoXpromo.Put(idProdsFijos.Get(p), prodsFijosPiezas.Get(p))
		Private idEsteProd As String = idProdsFijos.Get(p)
		Private invEsteProd As Int = invDispParaPromo.Get(idEsteProd)
		Private pzasReqEsteProd As Int = prodsFijosPiezas.Get(p)
		If thisLog Then Log($"id=${idEsteProd}, inv=${invEsteProd}, pzas=${pzasReqEsteProd}"$)
'		invDispParaPromo.Put(   idEsteProd, (invEsteProd - (1))   )
	Next
	If thisLog Then LogColor($"MaxPromos=${maxPromos}, promosXFijos=${maxPromosXFijos}"$, Colors.Blue)
	If thisLog Then LogColor($"prodsFijos=${idProdsFijos}"$, Colors.Blue)
	If thisLog Then LogColor($"prodsFijosPiezasReq=${prodsFijosPiezas}"$, Colors.Blue)
	If thisLog Then LogColor($"prodsVariables=${idProdsVariables}${CRLF}Variables Req=${prodsVariablesRequeridos} "$, Colors.Blue)
	If thisLog Then LogColor($"prodsVariables2=${idProdsVariables2}${CRLF}Variables2 Req=${prodsVariables2Requeridos} "$, Colors.Blue)
	If thisLog Then LogColor($"invFijoXpromo=${invFijoXpromo}"$, Colors.Blue)
	If thisLog Then Log($"Prods variables disponibles = ${totalProdsVariablesDisponibles}"$)
	If thisLog Then Log($"Prods variables2 disponibles = ${totalProdsVariables2Disponibles}"$)
	Private maxPromosXVariables As Int = 0
	Private maxPromosXVariables2 As Int = 0
	For x = 1 To maxPromosXFijos
		If thisLog Then Log("=====================================================")
		If thisLog Then Log("=====================================================")
		For i = 0 To idProdsFijos.Size - 1
			If thisLog Then Log($"FIJO - ${idProdsFijos.Get(i)}, ${invDispParaPromo.Get(idProdsFijos.Get(i))} - ${prodsFijosPiezas.Get(i).As(Int)*(i+1)}"$)
			invDispParaPromo.Put(idProdsFijos.Get(i), invDispParaPromo.Get(idProdsFijos.Get(i)).As(Int) - prodsFijosPiezas.Get(i).As(Int)*(i+1)) 'Restamos las piezas de los productos fijos del inventario disponible.
		Next
		If thisLog Then LogColor("Inv disponible despues de restar fijos = " & invDispParaPromo, Colors.Blue)
		
		totalProdsVariablesDisponibles = 0
		totalProdsVariables2Disponibles = 0
		For i = 0 To idProdsVariables.Size - 1 'Obtenemos total de productos variables disponibes.
			If invDispParaPromo.ContainsKey(idProdsVariables.Get(i)) Then
				totalProdsVariablesDisponibles = totalProdsVariablesDisponibles + invDispParaPromo.Get(idProdsVariables.Get(i))
			End If
		Next
		For i = 0 To idProdsVariables2.Size - 1 'Obtenemos total de productos variables disponibes.
			If invDispParaPromo.ContainsKey(idProdsVariables2.Get(i)) Then
				totalProdsVariables2Disponibles = totalProdsVariables2Disponibles + invDispParaPromo.Get(idProdsVariables2.Get(i))
			End If
		Next
		'Revisamos variables.
		If thisLog Then Log($"Var disponibles -  var requeridos : ${totalProdsVariablesDisponibles} - ${prodsVariablesRequeridos*x}"$)
		totalProdsVariablesDisponibles = totalProdsVariablesDisponibles - (prodsVariablesRequeridos*x)
		totalProdsVariables2Disponibles = totalProdsVariables2Disponibles - (prodsVariables2Requeridos*x)
		If thisLog Then Log("prodsVariables disponibles despues de promo = " & totalProdsVariablesDisponibles)
		If thisLog Then Log("prodsVariables2 disponibles despues de promo = " & totalProdsVariables2Disponibles)
		If totalProdsVariablesDisponibles < 0 Then Exit 'Ya no hay inventario disponible.
		If totalProdsVariables2Disponibles < 0 Then Exit 'Ya no hay inventario disponible.
		maxPromosXVariables = x
	Next
	'Restamos fijos.
	If thisLog Then LogColor("Max promos de prodsVariables POR inventario = " & maxPromosXVariables, Colors.red)
	Return maxPromosXVariables
End Sub

'Revisa si tenemos los productos variables requeridos para la promoción (mapa).
'Hay que darle como parametro un mapa (traePromo(promo)) con toda la informacion de la promocion.
Sub alcanzanLosVariablesParaPromo(promoMap As Map, inventarioSinFijos As Map) As Boolean 'ignore
	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 totalProdsVariables2 As Int = 0
'	Private prodsmap As Map = promoMap.Get("productos") 'Obtenemos un mapa con todos los productos de la promoción.
	Private prodsVariables As List = promoMap.get("prodsVariables") 'Obtenemos una lista con los productos variables de la promoción.
	Private prodsVariables2 As List = promoMap.get("prodsVariables2")
	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.
'		Log("inventarioSinFijos: " & inventarioSinFijos)
		If inventarioSinFijos.ContainsKey(t) Then 'Si existe el producto en la lista del inventario, entonces ...
			Private p2 As String = inventarioSinFijos.Get(t) 'Obtenemos el inventario disponible este producto.
'			Log(p2)
			totalProdsVariables = totalProdsVariables + p2
'			Log(totalProdsVariables)
			If thisLog Then Log($"prod ${t}, hay: ${p2}"$) 'Producto y piezas requeridas
		End If
	Next
	For p = 0 To prodsVariables2.Size - 1
		Private t As String = prodsVariables2.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 String = inventarioSinFijos.Get(t) 'Obtenemos el inventario disponible este producto.
			totalProdsVariables2 = totalProdsVariables2 + p2
			If thisLog Then Log($"prod ${t}, hay: ${p2}"$) 'Producto y piezas requeridas
		End If
	Next
	If thisLog Then Log("Total prods variables=" & totalProdsVariables & ", requeridos=" & promoMap.Get("prodsVariablesRequeridos"))
	If thisLog Then Log("Total prods variables2=" & totalProdsVariables2 & ", requeridos2=" & promoMap.Get("prodsVariables2Requeridos"))
	Private res As Boolean = False
	Private res1 As Boolean = False
	Private res2 As Boolean = False
	
'	Log($"${totalProdsVariables} >= ${promoMap.Get("prodsVariablesRequeridos")}"$)
'	Log($"${totalProdsVariables2} >= ${promoMap.Get("prodsVariables2Requeridos")}"$)
	
	If totalProdsVariables > 0 And totalProdsVariables >= promoMap.Get("prodsVariablesRequeridos") Then
		res1 = True 'Si el total de inventario de productos variables (totalProdsVariables) es mayor o igual a los productos requeridos entonces regresamos TRUE
	End If
	If promoMap.Get("prodsVariablesRequeridos").As (Int) = 0 Then res1 = True
	If totalProdsVariables2 > 0 And totalProdsVariables2 >= promoMap.Get("prodsVariables2Requeridos") Then
		res2 = True 'Si el total de inventario de productos variables (totalProdsVariables) es mayor o igual a los productos requeridos entonces regresamos TRUE
	End If
	If promoMap.Get("prodsVariables2Requeridos").As(Int) = 0 Then res2 = True
	If res1 And res2 Then 'Solo si son verdadero los dos el resultado es verdadero.
		res = True
	Else
		res = False
	End If
'	Log(res)
	Return res
End Sub

'Calcula el total de pedido tomando en cuenta si hay productos con promos por rango.
Sub calculaTotalConPromoXRango(idCliente As String) As String
'	caso 1 pe_recant = Null
'	monto del pedido = pe_costo_tot
'	caso 2 pe_recant = pe_cant (solo prods dentro de rango)
'	monto del pedido = pe_recalculotot
'	caso 3 pe_recant <> pe_cant (hay prod dentro de rango y producto extra
'	monto del pedido = ((pe_cant - pe_reccant) * pe_costou) + pe_recalculotot
'	Log("888888888888")
	Private totalParcial As String = 0
	Private total As String = 0
	Private cx As Cursor = Starter.skmt.ExecQuery($"select *, ifnull(pe_reccant, 'N') as RECANT from pedido where pe_cliente = '${idCliente}'"$)
	If cx.RowCount > 0 Then
		For i = 0 To cx.RowCount - 1
'			Log("999999999999999999999999999999999999")
			cx.Position = i
			Log("***************************************")
			If cx.getstring("RECANT") = "N" Then ' Sin productos dentro de rango.
				totalParcial = cx.getstring("PE_COSTO_TOT")
			else if cx.getstring("RECANT") = cx.getstring("PE_CANT") Then ' Solo productos dentro de rango.
				totalParcial = cx.getstring("PE_RECALCULOTOT")
			Else ' hay productos dentro de rango y productos arriba del rango.
				totalParcial = ((cx.getstring("PE_CANT") - cx.getstring("PE_RECCANT")) * cx.getstring("PE_COSTOU")) + (cx.getstring("PE_RECALCULO") * cx.getstring("PE_RECCANT"))
				Log($"${((cx.getstring("PE_CANT") - cx.getstring("PE_RECCANT")) * cx.getstring("PE_COSTOU"))} + ${(cx.getstring("PE_RECALCULO") * cx.getstring("PE_RECCANT"))} "$)
			End If
			Log($"Total:${total} + Total Parcial: ${totalParcial}"$)
			total = total + totalParcial
		Next
	End If
	cx.Close
	total = NumberFormat2(total, 1, 2, 2, False)
	Log($"Total con promocXrango = ${total}"$)
	Return total
End Sub