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
	Private su As 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 = ""
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

'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

'Trae un string con hora, minutos y segundos - HHmmss
Sub hmsKMT As String 'ignore
'	Log(fecha)
	Dim OrigFormat As String = DateTime.DateFormat  'save orig date format
	DateTime.DateFormat="HHmmss"
	Private nuevaHora As String=DateTime.Date(DateTime.Now)
	DateTime.DateFormat=OrigFormat  'return to orig date format
'	Log(nuevaFecha)
	Return nuevaHora
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.DirInternal, "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
	If B4XPages.MainPage.logger Then 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 Main.Logger Then Log("LatLon="&latlon)
		If Not(kmt.IsInitialized) Then revisaBD
		kmt.ExecNonQuery2("INSERT INTO RUTA_GPS(fecha, lat, lon) VALUES (?,?,?)", Array As Object (latlon(2),latlon(0),latlon(1)))
	Catch
		Log(LastException)
	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 B4XPages.MainPage.logger 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 B4XPages.MainPage.logger 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;")
End Sub

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

'Borramos el archio "gps.txt"
Sub borramosArchivoGPS 'ignore
	Dim out As OutputStream = File.OpenOutput(File.DirInternal, "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 algunas tablas dentro de ella
Sub revisaBD 'ignore
	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
'	kmt.ExecNonQuery("DROP TABLE IF EXISTS CAT_FOLIOS")
	kmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS REPARTO_GEO (CLIENTE TEXT, USUARIO TEXT, FECHA_PUNTEO TEXT, LATITUD TEXT, LONGITUD TEXT, ALMACEN TEXT, RUTA_REPARTO TEXT, RUTA_PREV TEXT, FECHA_PREVENTA TEXT, MOTIVO TEXT, COMENTARIO TEXT, TIPO TEXT, ENVIO TEXT)")
	'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

'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)
'		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.Magenta)
	If kmt.IsInitialized Then 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
'		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
	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 B4XPages.MainPage.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 B4XPages.MainPage.Logger Then Log("++++ Distancia a anterior="&daa&"|"&"Precision="&Tracker.FLP.GetLastKnownLocation.Accuracy)
		End If
		Tracker.UUGCoords = Tracker.FLP.GetLastKnownLocation
	End If
End Sub

'Revisamos que el FLP (FusedLocationProvider) este inicializado y activo
Sub revisaFLP 'ignore
	LogColor($"**** **** Revisamos FLP - ${fechaKMT(DateTime.Now)}**** ****"$, Colors.RGB(78,0,227))
	Private todoBienFLP As Boolean = True
	If Not(Tracker.FLP.IsInitialized) Then
		log2DB("revisaFLP: No esta inicializado ... 'Reinicializando FLP'")
		Tracker.FLP.Initialize("flp")
		todoBienFLP = False
	End If
	If Tracker.FLP.IsInitialized Then
		If Not(Tracker.FLP.IsConnected) Then
			log2DB("revisaFLP: No esta conectado ... 'Reconectando FLP'")
'			Tracker.FLP.Connect
			CallSubDelayed(Tracker,"StartFLP")
			todoBienFLP = False
		End If
		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
	End If
	If todoBienFLP Then LogColor(" +++ +++ Sin errores en FLP", Colors.Green)
	' 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))
'	Try
'		If Not(PushService.wsh.IsInitialized) Then 'Si no esta inicializado ...
'			log2DB("revisaPushService: No esta inicializado ... 'Reinicializando PushService'")
'			CallSubDelayed(PushService, "Connect")
'			todoBienPS = False
'		End If
'	Catch
'		Log(LastException)
'		insertaEnErrores("Subs.revisaPushService: Reiniciando -  "&LastException)
'	End Try
'	Try
'		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
'	Catch
'		Log(LastException)
'		insertaEnErrores("Subs.revisaPushService: Reconectando - "&LastException)
'	End Try
'	Try
'		If masDeXXMinsKMT(Starter.pushServiceActividad, 5) Then  'Si mas de xx minutos de la ultima actividad entonces ...
'			PushService.wsh.Close
'			CallSubDelayed(PushService, "Connect")
'	'		StartService(PushService)
'	'		If Main.Logger Then Log("Ultima act: "&Starter.pushServiceActividad)
'			log2DB("revisaPushService: 'Reconectamos 'PushService' por inactividad")
'			Starter.pushServiceActividad = fechaKMT(DateTime.Now)
'			todoBienPS = False
'		End If
'	Catch
'		Log(LastException)
'		insertaEnErrores("Subs.revisaPushService: Reconectando por inactividad - "&LastException)
'	End Try
'	If todoBienPS Then LogColor(" +++ +++ Sin errores en PushService", Colors.Green)
End Sub

'Borramos renglones extra de la tabla de errores
Sub borraArribaDe100Errores 'ignore
	If Not(errorLog.IsInitialized) Then revisaBD
	LogColor("Recortamos la tabla de Errores, limite de 100", 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
	LogColor("Recortamos la tabla de la Bitacora, limite de 600", Colors.Magenta)
	Private c As Cursor
	c = kmt.ExecQuery("select fecha from bitacora")
	c.Position = 0
	If c.RowCount > 650 Then
		kmt.ExecNonQuery("DELETE FROM bitacora WHERE fecha NOT in (SELECT fecha FROM bitacora ORDER BY fecha desc LIMIT 599 )")
		kmt.ExecNonQuery("vacuum;")
'		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
	If Not(errorLog.IsInitialized) Then 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)
'			Log(" +++ +++ pFecha:"&parteFecha&" | pHora:"&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
	If result Then
		Dim p As String
		If File.ExternalWritable Then
			p = File.DirInternal
'			Log("Externo")
		Else
			p = File.DirInternal
'			Log("Interno")
		End If
		Dim theDir As String
		Try
			File.MakeDir(File.DirInternal,"kmts")
			theDir = "/kmts"
		Catch
			theDir = ""
		End Try
		Try
			File.Copy(File.DirInternal,"kmt.db",File.DirInternal&theDir,"guna_rep_kmt.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.DirInternal="&File.DirInternal)
	Else
		ToastMessageShow("Sin permisos", False)
	End If
End Sub

'Hace visible 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

Sub insertaEnErrores(error As String)  'ignore
	If Not(errorLog.IsInitialized) Then revisaBD
	errorLog.ExecNonQuery2("INSERT INTO errores(fecha, error) VALUES (?,?)", Array As Object (fechaKMT(DateTime.now), error))
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

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

'Centra un panel 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 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 boton dentro de un elemento superior
Sub centraBoton(elemento As Button, anchoElementoSuperior As Int) 'ignore
	elemento.Left = Round(anchoElementoSuperior/2)-(elemento.Width/2)
End Sub

'Trae el nombre del producto con ID dado, desde HIST_VENTAS.
Sub traeNombre(id As String) As String 
	Private nombre As String
	Private idc As Cursor = kmt.ExecQuery($"select HVD_PRONOMBRE from HIST_VENTAS where HVD_PROID = '${id}'"$)
	idc.Position=0
	If idc.RowCount > 0 Then nombre = idc.GetString("HVD_PRONOMBRE")
	idc.Close
'	Log("NOMBRE=" & nombre)
	Return nombre
End Sub

'Trae el precio del ID dado, desde CAT_GUNAPROD o HIST_VENTAS, dependiendo de si es promoción o no. 
Sub traePrecio(id As String, quePromo As String) As String
	Private pu As String = "0"
'	Log(id & ", "& quePromo)
	If quePromo = traeAlmacen Then
		Private idc As Cursor = kmt.ExecQuery($"select CAT_GP_PRECIO from CAT_GUNAPROD where CAT_GP_ID = '${id}'"$)
		If idc.RowCount > 0 Then
			idc.Position=0
			If idc.RowCount > 0 And IsNumber(idc.GetString("CAT_GP_PRECIO")) Then pu = idc.GetString("CAT_GP_PRECIO")
'		Log("id=" & id & "|p=" & x & "|" & idc.GetString("CAT_GP_PRECIO"))
		End If
		idc.Close
	Else 'Si es una promo, entonces sacamos el costo total del producto en la promo de HIST_VENTAS2 y lo dividimos entre la cantidad.
		Private pc As Cursor = Starter.skmt.ExecQuery($"select HVD_CANT, HVD_COSTO_TOT from HIST_VENTAS2 where HVD_CLIENTE in (select cuenta from cuentaa) and HVD_PROID = '${id}' and HVD_CODPROMO = '${quePromo}'"$)
		If pc.RowCount > 0 Then
			pc.Position = 0
'			Log(pc.GetString("HVD_COSTO_TOT")&", "&  pc.GetString("HVD_CANT"))
			If pc.GetString("HVD_COSTO_TOT")<> Null And pc.GetString("HVD_CANT") <> Null Then
				pu = pc.GetString("HVD_COSTO_TOT") / pc.GetString("HVD_CANT")
			End If
		End If
'		Log("x cosa")
		pc.Close
	End If
'	Log(pu)
	Return pu
	
End Sub

'Trae el precio del ID dado, desde CAT_GUNAPROD o HIST_VENTAS, dependiendo de si es promoción o no. 
Sub traePrecio2(id As String, quePromo As String, consecutivo As String) As String
	Private pu As String = "0"
'	Log(id & ", "& quePromo)
	If quePromo = traeAlmacen Then
		Private idc As Cursor = kmt.ExecQuery($"select CAT_GP_PRECIO from CAT_GUNAPROD where CAT_GP_ID = '${id}'"$)
		If idc.RowCount > 0 Then
			idc.Position=0
			If idc.RowCount > 0 And IsNumber(idc.GetString("CAT_GP_PRECIO")) Then pu = idc.GetString("CAT_GP_PRECIO")
'		Log("id=" & id & "|p=" & x & "|" & idc.GetString("CAT_GP_PRECIO"))
		End If
		idc.Close
	Else 'Si es una promo, entonces sacamos el costo total del producto en la promo de HIST_VENTAS2 y lo dividimos entre la cantidad.
		Private pc As Cursor = Starter.skmt.ExecQuery($"select HVD_CANT, HVD_COSTO_TOT from HIST_VENTAS where HVD_CLIENTE in (select cuenta from cuentaa) and HVD_PROID = '${id}' and HVD_CODPROMO = '${quePromo}' AND CONSECUTIVO = '${consecutivo}'"$)
		If pc.RowCount > 0 Then
			pc.Position = 0
'			Log(pc.GetString("HVD_COSTO_TOT")&", "&  pc.GetString("HVD_CANT"))
			If pc.GetString("HVD_COSTO_TOT")<> Null And pc.GetString("HVD_CANT") <> Null Then
				pu = pc.GetString("HVD_COSTO_TOT") / pc.GetString("HVD_CANT")
			End If
		End If
'		Log("x cosa")
		pc.Close
	End If
'	Log(pu)
	Return pu
	
End Sub

'Trae el precio del ID dado, desde CAT_GUNAPROD o HIST_VENTAS, dependiendo de si es promoción o no Y CAJAS. 
Sub traePrecioC(id As String) As String
	Private pu As String = "0"
	Private idc As Cursor = kmt.ExecQuery($"select PRECIOCONVER from CAT_GUNAPROD where CAT_GP_ID = '${id}'"$)
	If idc.RowCount > 0 Then
		idc.Position=0
		If idc.RowCount > 0 And IsNumber(idc.GetString("PRECIOCONVER")) Then pu = idc.GetString("PRECIOCONVER")
'		Log("id=" & id & "|p=" & x & "|" & idc.GetString("CAT_GP_PRECIO"))
	End If
	idc.Close
	Return pu
End Sub

'Trae el precio del ID dado, desde CAT_GUNAPROD o HIST_VENTAS, dependiendo de si es promoción o no Y CAJAS. 
Sub traeConversion(id As String) As String
	Private pu As String = "0"
	
		Private idc As Cursor = kmt.ExecQuery($"select CONVERSION from CAT_GUNAPROD where CAT_GP_ID = '${id}'"$)
		If idc.RowCount > 0 Then
			idc.Position=0
			If idc.RowCount > 0 And IsNumber(idc.GetString("CONVERSION")) Then pu = idc.GetString("CONVERSION")
'		Log("id=" & id & "|p=" & x & "|" & idc.GetString("CAT_GP_PRECIO"))
		End If
		idc.Close

	Return pu
End Sub


'Trae el cliente desde la BD.
Sub traeCliente As String 'ignore
	Private cli As Cursor = Starter.skmt.ExecQuery("Select CUENTA from cuentaa")
	cli.Position = 0
	Private cl As String = cli.GetString("CUENTA")
	cli.Close
	Log("Algo paso..."& cl)
	Return cl
End Sub

Sub traeFechaReparto As String
	Private fe As Cursor = Starter.skmt.ExecQuery("select HVD_FECHA from HIST_VENTAS WHERE HVD_CLIENTE IN (Select CUENTA from cuentaa)")
	Private f As String = "-"
	If fe.RowCount > 0 Then
		fe.Position = 0
		f = fe.GetString("HVD_FECHA")
	End If
	fe.Close
	Return f
End Sub

'Trae de la tabla Reparto,la cantidad rechazada de un producto dado de un cliente dado.
Sub traeCantidadRechazada(cliente As String, prodId As String) As Int
	Private cant As Int = 0
	Private cr As Cursor = Starter.skmt.ExecQuery($"Select rep_cliente, rep_prodid, REP_CANT, REP_RECHAZO from reparto where rep_cliente = '${cliente}' and rep_prodid = '${prodId}' and REP_RECHAZO = 1"$)
	If cr.RowCount > 0 Then
		cr.Position = 0
		cant = cr.GetString("REP_CANT")
	End If
	Log($"${cliente}, ${prodId}, rowcont:${cr.RowCount}, cant: ${cant}"$)
	Return cant
End Sub

'Trae de la tabla Reparto,la cantidad vendida de un producto dado de un cliente dado.
Sub traeCantidadVendida(cliente As String, prodId As String) As Int
	Private cant As Int = 0
	Private cr As Cursor = Starter.skmt.ExecQuery($"Select rep_cliente, rep_prodid, REP_CANT, REP_RECHAZO from reparto where rep_cli_orig = '${cliente}' and rep_prodid = '${prodId}' and REP_RECHAZO = 0 and REP_CANT > 0"$)
'	Log($"${cliente}, ${prodId}, rowcont:${cr.RowCount}"$)
	If cr.RowCount > 0 Then
		cr.Position = 0
		cant = cr.GetString("REP_CANT")
	End If
	Return cant
End Sub

'Trae la cantidad desde la tabla hist_ventas2, del producto dado, del cliente dado.
Sub traeMaxCantidad(cliente As String, prodId As String) As Int
	Private cant As Int = 0
	Private cr As Cursor = Starter.skmt.ExecQuery($"Select hvd_cant from hist_ventas2 where hvd_cliente = '${cliente}' and hvd_proid = '${prodId}'"$)
'	Log($"${cliente}, ${prodId}, rowcont:${cr.RowCount}"$)
	If cr.RowCount > 0 Then
		cr.Position = 0
		cant = cr.GetString("HVD_CANT")
	End If
	Return cant
End Sub

'Ponemos venta de producto en la tabla de Reparto.
Sub prodVenta(clienteOriginal As String, prodId As String)
'	Log("RECHAZO VENTA")
	Private precio As String = traePrecio(prodId, traeAlmacen)
	Starter.skmt.ExecNonQuery($"update reparto set REP_CANT = REP_CANT - 1 where REP_CLIENTE = '${clienteOriginal}' and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 1 and REP_PRODID = '${prodId}'"$)
	Starter.skmt.ExecNonQuery($"update reparto set REP_CANT = REP_CANT + 1 where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 0 and REP_PRODID = '${prodId}'"$)
	Log($"update reparto set REP_CANT = REP_CANT - 1 where REP_CLIENTE = '${clienteOriginal}' and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 1 and REP_PRODID = '${prodId}'"$)
	Log($"update reparto set REP_CANT = REP_CANT + 1 where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 0 and REP_PRODID = '${prodId}'"$)
	Starter.skmt.ExecNonQuery($"update reparto set REP_COSTO_TOT = REP_CANT * ${precio}, REP_PRECIO = '${precio}' where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 0 and REP_PRODID = '${prodId}'"$)

'	Private pu As String = 0
'	Private cant As Int = 0
'	Private pc As Cursor = Starter.skmt.ExecQuery($"select (HVD_COSTO_TOT / HVD_CANT) as PU, HVD_CANT from HIST_VENTAS2 where HVD_CLIENTE = '${clienteOriginal}' and HVD_PROID = '${prodId}'"$)
'	Log($"PU ROWCOUNT = ${pc.RowCount}"$)
'	If pc.RowCount > 0 Then
'		pc.Position = 0
'		pu = pc.GetString("PU")
'	End If
'	pc.Close
'	Private pc As Cursor = Starter.skmt.ExecQuery($"select HVD_CANT from HIST_VENTAS where HVD_CLIENTE = '${clienteOriginal}' and HVD_PROID = '${prodId}'"$)
'	If pc.RowCount > 0 Then
'		pc.Position = 0
'		cant = pc.GetString("HVD_CANT")
'	End If
'	pc.Close
	''	Starter.skmt.ExecNonQuery($"update hist_ventas set HVD_CANT = '${cant - 1}' where HVD_CLIENTE in (Select CUENTA from cuentaa) and HVD_PROID = '${prodId}'"$)
'	Starter.skmt.ExecNonQuery($"update hist_ventas set HVD_COSTO_TOT = '${cant * pu}' where HVD_CLIENTE in (Select CUENTA from cuentaa) and HVD_PROID = '${prodId}'"$)
	''	Log($"update hist_ventas set HVD_CANT = '${cant - 1}' where HVD_CLIENTE in (Select CUENTA from cuentaa) and HVD_PROID = '${prodId}'"$)
'	Log($"update hist_ventas set HVD_COSTO_TOT = '${cant * pu}' where HVD_CLIENTE in (Select CUENTA from cuentaa) and HVD_PROID = '${prodId}'"$)
End Sub

'Ponemos rechazo de producto en la tabla de Reparto. 
Sub prodRechazo(clienteOriginal As String, prodId As String)
	Log("RECHAZO DEVOLUCION")
	Private rr As Cursor = Starter.skmt.ExecQuery($"select count(REP_CLIENTE) as hayRechazo from REPARTO where rep_prodid = '${prodId}' and REP_CLIENTE in (Select CUENTA from cuentaa)"$)
	rr.Position = 0
	If rr.GetString("hayRechazo") = 0 Then
		Log("INSERTAMOS EN REPARTO")
'		Log($"select * from hist_ventas where hvd_cliente in (select cuenta from cuentaa) and hvd_proid = '${prodId}'"$)
		Private chv As Cursor = Starter.skmt.ExecQuery($"select * from hist_ventas where hvd_cliente in (select cuenta from cuentaa) and hvd_proid = '${prodId}'"$)
'		Log(chv.RowCount)
		If chv.RowCount > 0 Then
			chv.Position = 0
'			Log($"CANT=${chv.GetString("HVD_CANT")}"$)
			Private precio As String = traePrecio(prodId, traeAlmacen)
			Starter.skmt.ExecNonQuery2("insert into reparto(REP_CLIENTE, REP_PRONOMBRE, REP_CANT, REP_COSTO_TOT, REP_FECHA, REP_RECHAZO, REP_PRODID, REP_PRECIO, REP_CLI_ORIG)  VALUES (?,?,?,?,?,1,?,?,?) ", Array As String(traeCliente, chv.GetString("HVD_PRONOMBRE"), 1, precio, chv.GetString("HVD_FECHA"), prodId, precio, traeCliente))
			Starter.skmt.ExecNonQuery2("insert into reparto(REP_CLIENTE, REP_PRONOMBRE, REP_CANT, REP_COSTO_TOT, REP_FECHA, REP_RECHAZO, REP_PRODID, REP_PRECIO, REP_CLI_ORIG)  VALUES (?,?,?,?,?,0,?,?,?) ", Array As String(traeCliente, chv.GetString("HVD_PRONOMBRE"), chv.GetString("HVD_CANT") - 1, (chv.GetString("HVD_CANT") - 1) * precio, chv.GetString("HVD_FECHA"), prodId, precio, traeCliente))
			Starter.skmt.ExecNonQuery($"update HIST_VENTAS set HVD_PARCIAL = 1, HVD_CANT = 0, HVD_COSTO_TOT = '0' where HVD_PROID = '${prodId}' and HVD_CLIENTE in (Select CUENTA from cuentaa)"$)
		End If
	Else
		Log($"ACTUALIZAMOS REPARTO"$)
		Private precio As String = traePrecio(prodId, traeAlmacen)
		Starter.skmt.ExecNonQuery2("update HIST_VENTAS set HVD_PARCIAL = 1, HVD_CANT = 0, HVD_COSTO_TOT = '0' WHERE HVD_PROID = ? and HVD_CLIENTE in (Select CUENTA from cuentaa)", Array As String(prodId))
		Starter.skmt.ExecNonQuery($"update reparto set REP_CANT = REP_CANT + 1 where REP_CLIENTE = '${clienteOriginal}' and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 1 and REP_PRODID = '${prodId}'"$)
		Starter.skmt.ExecNonQuery($"update reparto set REP_CANT = REP_CANT - 1 where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 0 and REP_PRODID = '${prodId}'"$)
'		Log($"update reparto set REP_CANT = REP_CANT + 1 where REP_CLIENTE = '${clienteOriginal}' and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 1 and REP_PRODID = '${prodId}'"$)
'		Log($"update reparto set REP_CANT = REP_CANT - 1 where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}' and REP_RECHAZO = 0 and REP_PRODID = '${prodId}'"$)
		Starter.skmt.ExecNonQuery($"update reparto set REP_COSTO_TOT = REP_CANT * ${precio}, REP_PRECIO = '${precio}' where REP_CLIENTE in (Select CUENTA from cuentaa) and REP_CLI_ORIG = '${clienteOriginal}'  and REP_PRODID = '${prodId}'"$)
	End If
End Sub

Sub traemosCantYMonto(clv As CustomListView) As Map
'	Log("TRAEMOS CANT Y MONTO")
	Private cant As Int = 0
	Private monto As Float = 0
	For i = 0 To clv.GetSize - 1
		Private p0 As B4XView = clv.GetPanel(i)
		Private p As B4XView = p0.GetView(0)
		Private cant1 As B4XView = p.GetView(2).GetView(3)
		Private cant2 As B4XView = p.GetView(3).GetView(3)
'		Log("|"&cant1.Text&"|")
		If cant1.text <> "" Then
			If cant1.Text = "" Then cant1.Text = 0
			Private esteTag As List = Regex.Split("\|", cant1.Tag)
'			Log($"CANT:${cant1.text}, esteTag=${esteTag}"$)
			If esteTag.Get(2) <> esteTag.Get(3) Then cant = cant + cant1.Text 'Si el ID es diferente de la promo, lo sumamos, si no, no lo sumamos porque es el encabezado de la promo en 0.
			monto = monto + (esteTag.Get(0) * cant1.Text)
		Else
			If cant2.Text = "" Then cant2.Text = 0
			Private esteTag As List = Regex.Split("\|", cant2.Tag)
'			Log($"CANT:${cant2.text}, esteTag=${esteTag}"$)
			If esteTag.Get(2) <> esteTag.Get(3) Then cant = cant + cant2.Text 'Si el ID es diferente de la promo, lo sumamos, si no, no lo sumamos porque es el encabezado de la promo en 0.
			monto = monto + (esteTag.Get(0) * cant2.Text)
		End If
	Next
'	Log($"CANT=${cant}, MONTO=${monto}"$)
	Return CreateMap("cantidad":cant, "monto":monto)
End Sub

Sub traeCantYMonto2(cliente As String) As Map
'	Log("TRAE CANT Y MONTO2")
	Private cant As Int = 0
	Private monto As Float = 0
	Private ccym As Cursor = Starter.skmt.ExecQuery($"Select hvd_codpromo, hvd_proid, HVD_CANT, (Select CAT_GP_PRECIO from cat_gunaprod where cat_gp_id = hvd_proid) As precio from HIST_VENTAS where HVD_CLIENTE = '${cliente}'"$)
	If ccym.RowCount > 0 Then
		For i=0 To ccym.RowCount -1
			ccym.Position = i
			If ccym.GetString("HVD_CODPROMO") <> ccym.GetString("HVD_PROID") And ccym.GetString("precio") <> Null Then 'Si no es el header de una promo...
				cant = cant + ccym.GetString("HVD_CANT")
'				Log($"${ccym.GetString("HVD_CANT")} * ${ccym.GetString("precio")}"$)
				monto = monto + (ccym.GetString("HVD_CANT") * traePrecio(ccym.GetString("HVD_PROID"), ccym.GetString("HVD_CODPROMO")))
			End If
		Next
'		Log($"CANT=${cant}, MONTO=${monto}"$)
	End If
	
	ccym = Starter.skmt.ExecQuery($"Select rep_prodid, iFNULL(REP_CANT, 0) AS REP_CANT, iFNULL((Select CAT_GP_PRECIO from cat_gunaprod where cat_gp_id = rep_prodid),0) As precio from REPARTO where REP_CLIENTE in (Select cuenta from cuentaa) And REP_RECHAZO = '0' and REP_CANT > 0"$)
	If ccym.RowCount > 0 Then
		For i=0 To ccym.RowCount -1
			ccym.Position = i
			If ccym.GetString("REP_CANT") <> Null Then cant = cant + ccym.GetString("REP_CANT")
			monto = monto + (ccym.GetString("REP_CANT") * ccym.GetString("precio"))
		Next
		Log($"CANT=${cant}, MONTO=${monto}"$)
	End If
	
	ccym.Close
	Return CreateMap("cantidad":cant, "monto":monto)
End Sub

'Regresa la cantidad entregada desde HIST_VENTAS.
Sub traeEntregados As Map
	Private m As Map
	Private cant As Int = 0
	Private monto As String = 0
	Private rc As Cursor = Starter.skmt.ExecQuery($"select sum(HVD_CANT) as CUANTOS, sum(HVD_COSTO_TOT) as CUANTO from HIST_VENTAS where HVD_ESTATUS = '1' and HVD_COSTO_TOT <> '0'"$)
	If rc.RowCount > 0 Then
		rc.Position = 0
		If rc.GetString("CUANTOS") <> Null Then
			cant = cant + rc.GetString("CUANTOS")
			monto = monto + rc.GetString("CUANTO")
		End If
	End If
'	LogColor($"CANTIDAD ENTREGADA = ${cant}"$, Colors.red)
	Return CreateMap("cantidad": cant, "monto": monto)
End Sub

'Regresa la cantidad rechazada desde REPARTO.
Sub traeRechazados As Map
	Private m As Map
	Private cant As Int = 0
	Private monto As String = 0
	Private rc As Cursor = Starter.skmt.ExecQuery($"select sum(REP_CANT) as CUANTOS, sum(REP_CANT * REP_PRECIO) as CUANTO from REPARTO where REP_RECHAZO = '1' and REP_CANT <> '0'"$)
	If rc.RowCount > 0 Then
		rc.Position = 0
		If rc.GetString("CUANTOS") <> Null Then
			cant = cant + rc.GetString("CUANTOS")
			monto = monto + rc.GetString("CUANTO")
		End If
	End If
'	LogColor($"CANTIDAD RECHAZADA = ${cant}"$, Colors.red)
	Return CreateMap("cantidad": cant, "monto": monto)
End Sub

'Regresa la cantidad vendida desde REPARTO.
Sub traeVendidos As Map
	Private m As Map
	Private cant As Int = 0
	Private monto As String = 0
	Private rc As Cursor = Starter.skmt.ExecQuery($"select sum(REP_CANT) as CUANTOS, sum(REP_CANT * REP_PRECIO) as CUANTO from REPARTO where REP_RECHAZO = '0' and REP_CANT <> '0'"$)
	If rc.RowCount > 0 Then
		rc.Position = 0
		If rc.GetString("CUANTOS") <> Null Then
			cant = cant + rc.GetString("CUANTOS")
			monto = monto + rc.GetString("CUANTO")
		End If
	End If
'	LogColor($"CANTIDAD VENDIDA = ${cant}"$, Colors.red)
	Return CreateMap("cantidad": cant, "monto": monto)
End Sub

'Agrega una columna a la tabla especificada.
'Hay que indicar el "tipo"  de la columna (TEXT, INTEGER, ETC)
'Ej. agregaColumna("TABLA", "COLUMNA", "TIPO")
Sub agregaColumna(tabla As String, columna As String, tipo As String)
	Try 'Intentamos usar "pragma_table_info" para revisar si existe la columna en la tabla
		Private c As Cursor = Starter.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
			Starter.skmt.ExecNonQuery($"ALTER TABLE ${tabla} ADD COLUMN ${columna} ${tipo}"$)
			Log($"Columna "${columna} ${tipo}", agregada a "${tabla}"."$)
		End If
	Catch 'Si no funciona "pragma_table_info" lo hacemos con try/catch
		Try
			Starter.skmt.ExecNonQuery($"ALTER TABLE ${tabla} ADD COLUMN ${columna} ${tipo}"$)
			Log($"Columna "${columna} ${tipo}", agregada a "${tabla}".."$)
		Catch
			Log(LastException)
		End Try
	End Try
End Sub

'Regresa la ruta actual de la base de datos.
Sub traeRutaReparto As String 'ignore
	Private c As Cursor
	Private r As String
	c=Starter.skmt.ExecQuery("select RUTAA from RUTAA")
	r = "0"
	If c.RowCount > 0 Then
		c.Position=0
		r = c.GetString("RUTAA")
	End If
	c.Close
	Return r
End Sub

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

Sub traeUsuario As String 'ignore
	Private c As Cursor
	Private a As String
	c = Starter.skmt.ExecQuery("select USUARIO from USUARIOA")
	c.Position = 0
	a = C.GetString("USUARIO")
	c.Close
	Return a
End Sub


Sub traefECHAPREV As String 'ignore
	Private c As Cursor
	Private a As String
	Private clienteasing As Cursor = Starter.skmt.ExecQuery("SELECT CUENTA FROM CUENTAA")
	clienteasing.Position = 0
	If clienteasing.GetString("CUENTA") <> "63403" Then
		c = Starter.skmt.ExecQuery("select HVD_FECHA from HIST_VENTAS where HVD_CLIENTE IN (SELECT CUENTA FROM CUENTAA)")
		c.Position = 0
		Dim fecha() As String = Regex.Split(" ", c.GetString("HVD_FECHA"))
		Dim fecha2() As String = Regex.Split("-", fecha(0))
		Dim fecha3 As String = fecha2(2)&"/"&fecha2(1)&"/"&fecha2(0)
		Log(fecha3)
		Dim hora() As String = Regex.Split("\.", fecha(1))
		a = fecha3 & " " & hora(0)
		c.Close
	Else If clienteasing.GetString("CUENTA") = "63403" Then
		c = Starter.skmt.ExecQuery("select HVD_FECHA from HIST_VENTAS")
		c.Position = 0
		Dim fecha() As String = Regex.Split(" ", c.GetString("HVD_FECHA"))
		Dim fecha2() As String = Regex.Split("-", fecha(0))
		Dim fecha3 As String = fecha2(2)&"/"&fecha2(1)&"/"&fecha2(0)
		Log(fecha3)
		Dim hora() As String = Regex.Split("\.", fecha(1))
		a = fecha3 & " " & hora(0)
		c.Close
	End If
	Return a
End Sub

Sub traerUTAPREV As String 'ignore
	Private c As Cursor
	Private a As String
	c = Starter.skmt.ExecQuery("select CAT_CL_RUTA from kmt_info where CAT_CL_CODIGO IN (SELECT CUENTA FROM CUENTAA)")
	c.Position = 0
	a = C.GetString("CAT_CL_RUTA")
	c.Close
	Return a
End Sub

Sub CreateBitmapWithNumber(OriginalBitmap As Bitmap, Number As Int) As Bitmap 'ignore
	Dim NewBitmap As Bitmap
	NewBitmap.InitializeMutable(OriginalBitmap.Width, OriginalBitmap.Height)
	Dim Canvas As Canvas
	Canvas.Initialize2(NewBitmap)
	' Definir el rectángulo donde se dibujará el Bitmap
	Dim DestRect As Rect
	DestRect.Initialize(0, 0, OriginalBitmap.Width, OriginalBitmap.Height)
	' Dibujar la imagen original
	Canvas.DrawBitmap(OriginalBitmap, Null, DestRect)
	' Dibujar el número en el centro del Bitmap y configurar el estilo del texto
	Canvas.DrawText(Number, (OriginalBitmap.Width / 2), (OriginalBitmap.Height / 2.4), Typeface.DEFAULT, 20, Colors.White, "CENTER")
	Return NewBitmap
End Sub

'Muestra en el Log los campos y valores que regresan en el JobDone.
Sub logJobDoneResultados(resultado As DBResult)
	For Each records() As Object In resultado.Rows
		LogColor($"====== ${resultado.Tag} - REGISTROS = ${resultado.Rows.Size}"$, Colors.RGB(215,37,0))
		For Each k As String In resultado.Columns.Keys
			LogColor(k & " = " & records(resultado.Columns.Get(k)), Colors.RGB(215,37,0))
		Next
	Next
End Sub

Sub deshabilitaValidaciones
	Log("Deshabilitamos VERIFY_DEVICE")
	Starter.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("VERIFY_DEVICE"))
	Starter.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("VERIFY_DEVICE", 0))
	Log("Deshabilitamos VALIDA_VERSION")
'	Starter.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("VALIDA_VERSION"))
'	Starter.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("VALIDA_VERSION", 0))
	Log("Espeificamos LIGADO")
	Starter.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("LIGADO"))
	Starter.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("LIGADO", 1))
'	B4XPages.MainPage.principal.Btn_CheckList.Enabled = True
'	B4XPages.MainPage.principal.trabajar.Enabled = True
'	B4XPages.MainPage.principal.Resumen.Enabled = True
'	B4XPages.MainPage.principal.BUSCA.Enabled = True
	ToastMessageShow("REALIZADO", False)
End Sub