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 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
'		if starter.logger then 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
'		if starter.logger then Log("Leemos id de "&File.DirInternal&"/phnId.txt")
'		if starter.logger then 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)
'	Dim base64 As String = su.EncodeBase64(compressed)
'	if starter.logger then Log($"Comprimido: ${base64.Length}"$)
'	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)
'	if starter.logger then Log($"decompressedbytesLength: ${decompressedbytes.Length}"$)
	Dim bc As ByteConverter
	Dim uncompressed As String = bc.StringFromBytes(decompressedbytes,"UTF8")
	Log($"Descomprimido: ${uncompressed.Length}"$)
'	if starter.logger then Log($"Decompressed String = ${uncompressed}"$)
	Return uncompressed
End Sub

'Convierte una fecha al formato yyMMddHHmmss
Sub fechaKMT(fecha As String) As String 'ignore
'	if starter.logger then 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
'	if starter.logger then 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)
'	if starter.logger then 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)
	If Starter.logger Then Log("notiLowReturn: "&title)
	notification.Icon = "icon"
	notification.Sound = False
	notification.Vibrate = False
	notification.SetInfo(title, Body, Main)
	notification.Notify(id)
'	if starter.logger then 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
	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(Starter.skmt.IsInitialized) Then revisaBD
		Starter.skmt.ExecNonQuery2("INSERT INTO RUTA_GPS(fecha, lat, lon) VALUES (?,?,?)", Array As Object (latlon(2),latlon(0),latlon(1)))
	Catch
		If Starter.logger Then Log(LastException)
	End Try
End Sub

'Regresa la ruta gps solicitada comprimida y en base64
Sub dameRutaGPS(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 Starter.logger Then Log("fechaInicio: "&fechaInicio&" | rutaHrsAtras="&rutaHrsAtras)   'fechaKMT(DateTime.Now)
	Dim c As Cursor
	If Starter.skmt.IsInitialized = False Then Starter.skmt.Initialize(Starter.ruta, "kmt.db", True)
	If Starter.logger Then Log("select FECHA, LAT, LON from "& origenRuta &" where FECHA > " & fechaInicio & " order by FECHA desc limit " & rutaMaxPoints)
	c = Starter.skmt.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")
			Starter.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
	Starter.skmt.ExecNonQuery("delete from RUTA_GPS")
	Starter.skmt.ExecNonQuery("vacuum;")
End Sub

'Limpiamos la tabla errorLog de la BD
Sub deleteErrorLog_DB 'ignore
	If Not(Starter.errorLog.IsInitialized) Then revisaBD
	Starter.errorLog.ExecNonQuery("delete from errores")
	Starter.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 starter.logger then Log("subs.revisaBD")
	Starter.ruta = File.DirInternal
	If Not(File.Exists(Starter.ruta, "kmt.db")) Then File.Copy(File.DirAssets, "kmt.db", Starter.ruta, "kmt.db")
	If Not(Starter.skmt.IsInitialized) Then Starter.skmt.Initialize(Starter.ruta, "kmt.db", True)
	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS CAT_RMI(CAT_ID TEXT, CAT_DESCRIPCION TEXT, CAT_MONTO TEXT)")
	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS RUTA_GPS(fecha INTEGER, lat TEXT, lon TEXT)")
	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS HIST_ENCUESTA2(HE_CLIENTE TEXT)")
'	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS UUC(fecha INTEGER, lat TEXT, lon TEXT)") 'LastKnownLocation
	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS bitacora(fecha INTEGER, texto TEXT)") 'Bitacora
	Starter.skmt.ExecNonQuery("CREATE TABLE IF NOT EXISTS CAT_DEV(CAT_GP_ID TEXT,CAT_GP_NOMBRE TEXT, CAT_GP_PRECIO TEXT)")
	Try 'Si no existe la columna CAT_CL_CATEGORIA la agregamos.
		Starter.skmt.ExecQuery("select count(CAT_CL_CATEGORIA) from kmt_info")
	Catch
		Try
			Starter.skmt.ExecNonQuery("ALTER TABLE kmt_info ADD COLUMN CAT_CL_CATEGORIA TEXT")
		Catch
			If Starter.logger Then LogColor("No pudimos agregar la columna CAT_CL_CATEGORIA.", Colors.Red)
			If Starter.logger Then LogColor(LastException, Colors.Red)
		End Try
	End Try
	Try 'Si no existe la columna CAT_CL_SEGMENTO la agregamos.
		Starter.skmt.ExecQuery("select count(CAT_CL_SEGMENTO) from kmt_info")
	Catch
		Try
			Starter.skmt.ExecNonQuery("ALTER TABLE kmt_info ADD COLUMN CAT_CL_SEGMENTO TEXT")
		Catch
			If Starter.logger Then LogColor("No pudimos agregar la columna CAT_CL_SEGMENTO.", Colors.Red)
			If Starter.logger Then LogColor(LastException, Colors.Red)
		End Try
	End Try
	'Tabla para la bitacora de errores
	If Not(Starter.errorLog.IsInitialized) Then Starter.errorLog.Initialize(Starter.ruta, "errorLog.db", True)
	Starter.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)
	''	if starter.logger then 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
	If Starter.logger Then Log("Hoy="&h)
	Return h
End Sub

'Guardamos "texto" a la bitacora
Sub log2DB(texto As String) 'ignore
	If Starter.logger Then LogColor(fechaKMT(DateTime.Now)&" - log2BD: '"&texto&"'", Colors.Magenta)
	If Starter.skmt.IsInitialized Then Starter.skmt.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
		'	if starter.logger then 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
'			if starter.logger then Log("+++ +++ "&fechaKMT(fechaKMT2Ticks(hora) + mins * DateTime.TicksPerMinute) & " < " & fechaKMT(DateTime.Now))
			Return True
		Else
			'		if starter.logger then 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
	If Starter.logger Then LogColor("Borramos BD de log", Colors.Magenta)
	Starter.skmt.ExecNonQuery("delete from bitacora")
	Starter.skmt.ExecNonQuery("vacuum;")
End Sub

'Monitoreamos los servicios PushService y Tracker para ver si estan activos (No pausados), y si no, los reniciamos
Sub Monitor 'ignore
'	Private monitorStatus As Boolean = True
'	if starter.logger then 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 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 ...
			Starter.skmt.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
End Sub

'Revisamos que el FLP (FusedLocationProvider) este inicializado y activo
Sub revisaFLP 'ignore
	If Starter.logger Then 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
	''	if starter.logger then 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(Starter.errorLog.IsInitialized) Then revisaBD
	If Starter.logger Then LogColor("Recortamos la tabla de Errores, limite de 100", Colors.Magenta)
	Starter.errorLog.ExecNonQuery("DELETE FROM errores WHERE fecha NOT in (SELECT fecha FROM errores ORDER BY fecha desc LIMIT 99 )")
	Starter.errorLog.ExecNonQuery("vacuum;")
'	if starter.logger then 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 = Starter.skmt.ExecQuery("select fecha from bitacora")
	c.Position = 0
	If c.RowCount > 650 Then
		Starter.skmt.ExecNonQuery("DELETE FROM bitacora WHERE fecha NOT in (SELECT fecha FROM bitacora ORDER BY fecha desc LIMIT 599 )")
		Starter.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
	If Not(Starter.errorLog.IsInitialized) Then revisaBD
	Log("insertamos 50 renglones a errorLog")
	For x = 1 To 50
		Starter.errorLog.ExecNonQuery2("INSERT INTO errores(fecha, error) VALUES (?,?)", Array As Object (fechaKMT(DateTime.now), "abc"))
		If Starter.logger Then 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
	If Starter.logger Then 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)
	If Starter.logger Then 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)
		If Starter.logger Then 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
		Dim safeDir As String = Starter.rp.GetSafeDirDefaultExternal("erroresDir")
		p = Starter.ruta
		Dim theDir As String
		Try
			File.MakeDir(p,"kmts")
			theDir = "/kmts"
		Catch
			theDir = ""
		End Try
		Try
			Private nombreDB As String = $"kell_${Starter.rutaPreventa}_kmt.db"$
			File.Copy(p,"kmt.db",File.DirInternal&theDir, nombreDB)
			File.Copy(p,"errorLog.db",File.DirInternal&theDir,"kell_errorLog.db")
			ToastMessageShow("BD copiada a " & File.DirInternal&theDir, False)
		Catch
			Try
				Private nombreDB As String = $"kell_${Starter.rutaPreventa}_kmt.db"$
				File.Copy(p,"kmt.db", safeDir, nombreDB)
				File.Copy(p,"errorLog.db", safeDir,"kell_errorLog.db")
				ToastMessageShow("BD copiada a " & safeDir, False)
			Catch
				ToastMessageShow("No se puedo copiar la base de datos.", False)
			End Try
'			ToastMessageShow("No se pudo hacer la copia: "&LastException, True)
		End Try
		If Starter.logger Then Log("rootExternal="&p)
		If Starter.logger Then Log("safe="&safeDir)
	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

'Oculta el panel especificado y lo manda al fondo
Sub panelOculto(panel As Panel) 'ignore
	panel.SendToBack
	panel.Visible = False
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 dentro de un elemento superior
Sub centraPanel(elemento As Panel, 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

'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 insertaEnErrores(error As String)  'ignore
	If Not(Starter.errorLog.IsInitialized) Then revisaBD
	Starter.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(Starter.skmt.IsInitialized) Then revisaBD
	c=Starter.skmt.ExecQuery("select USUARIO from usuarioa")
	c.Position=0
	If c.RowCount > 0 Then u = c.GetString("USUARIO")
	c.Close
	Return u
End Sub

'Regresa el tiempo transcurrido entre la primera vez que se llama (paso 1) y la segunda (paso 2), usa el mapa global "Main.tiempos"
Sub cronoX(thisCrono As String, paso As Int) As String 'ignore
	If paso = 1 Then
		Starter.tiempos.Put(thisCrono, DateTime.Now)
	Else
		Return $"Tiempo transcurrido ${thisCrono}: ${DateTime.Now-Starter.tiempos.Get(thisCrono)} "$
	End If
End Sub

'Cierra todas las actividades y sale de la aplicacion (Android 4.1+ - API 16+)
Sub cierraActividades
	If Starter.logger Then Log("closing activities")
	Dim jo As JavaObject
	jo.InitializeContext
	jo.RunMethod("finishAffinity", Null)
End Sub

'Regresa el cliente actual de CUENTAA como un string, y si no encuenta nada regresa "".
Sub clienteActual As String
	Private x As String = ""
	Private c As Cursor = Starter.skmt.ExecQuery("Select CUENTA from cuentaa")
	If c.RowCount > 0 Then
		c.Position = 0
		x = c.GetString("CUENTA")
	End If
	c.Close
	Return x
End Sub

'Marca un cliente como impreso, lo agrega a la tabla "CLIENTE_IMPRESO" y lo actualiza en "kmt_info" (IMPRESION).
Sub ponImpreso (cliente As String)
	Starter.skmt.ExecNonQuery2("UPDATE kmt_info set IMPRESION = 1  where CAT_CL_CODIGO = ?", Array As String(cliente))
	Starter.skmt.ExecNonQuery2("insert into CLIENTE_IMPRESO (CI_CUENTA) values (?)",Array As String(cliente))
	If Starter.logger Then Log($"Cliente ${cliente} agegado a CLIENTE_IMPRESO y actualizado en kmt_info."$)
End Sub

'Guarda el nombre de la ultima actividad mostrada "ultimaActividad(Me)".
Sub ultimaActividad(ea As String)
	Private x() As String = Regex.Split("\.", ea)
	Starter.ultimaActividad = ""
	If x.Length = 4 Then
		Starter.ultimaActividad = x(3)
		File.WriteString(File.DirInternal, "ua.txt", Starter.ultimaActividad)
	End If
'	If File.Exists(File.DirInternal, "ua.txt") Then Starter.ultimaActividad = File.ReadString(File.DirInternal, "ua.txt")
'	if starter.logger then LogColor($"${DateTime.Now} - Ultima actividad=${Starter.ultimaActividad}"$, Colors.Magenta)
End Sub

'Guarda el nombre de la ultima actividad en base de datos "ultimaActividad(Me)".
Sub guardaUltimaActividadBD(ua As String)
'	if starter.logger then LogColor($"Guardamos ultima actividad '${ua}'"$, Colors.Blue)
	Starter.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("ULTIMOMODULO"))
	Starter.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("ULTIMOMODULO", ua))
End Sub

'Regresa la ultima actividad guardada en base de datos.
Sub traeUltimaActividadBD As String
'	if starter.logger then LogColor("Buscamos ultima actividad en BD", Colors.Magenta)
	Private c As Cursor
	Private x As String
	c = Starter.skmt.ExecQuery("select CAT_VA_VALOR from CAT_VARIABLES where CAT_VA_DESCRIPCION = 'ULTIMOMODULO'")
	If c.RowCount > 0 Then
		c.Position = 0
		x = c.GetString("CAT_VA_VALOR")
'		if starter.logger then Log("Encontramos: " & x)
	End If
	c.Close
	Return x
End Sub

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

'Guarda el nombre de la actividad en base de datos e inicia la actividad.
Sub iniciaActividad2(ia As String)
'	if starter.logger then LogColor($"Guardamos en BD '${ia}'"$, Colors.Yellow)
	Starter.skmt.ExecNonQuery2("delete from CAT_VARIABLES where CAT_VA_DESCRIPCION = ?", Array As Object ("ULTIMOMODULO"))
	Starter.skmt.ExecNonQuery2("INSERT INTO CAT_VARIABLES(CAT_VA_DESCRIPCION, CAT_VA_VALOR) VALUES (?,?)", Array As Object ("ULTIMOMODULO", ia))
	StartActivity(ia)
'	B4XPages.ShowPage(ia)
'	if starter.logger then LogColor("Iniciamos --> " & ia, Colors.Blue)
End Sub

'Agrega la variable al mapa de sesion.
Sub agregaASesion(nombre As String, valor As String)
	Starter.sesion.put(nombre, valor)
'	if starter.logger then Log(Starter.sesion)
End Sub

'Regresa una lista con las coordenadas ( latitud y longitud) guardadas en BD.
Sub traeCoordsDeBD As List
	Private c As Cursor
	c=Starter.skmt.ExecQuery("SELECT HGLAT as HGLAT, HGLON as HGLON FROM HIST_GPS")
	c.Position=0
	Private lon As String = c.GetString("HGLON")
	Private lat As String = c.GetString("HGLAT")
	c.Close
	Private coords As List
	coords.Initialize
	coords.Add(lat)
	coords.Add(lon)
	Return coords
End Sub

'Modifica el ancho y alto de un panel dado con el ancho y alto proporcionados.
Sub panelAnchoAlto(p As Panel, w As Int, h As Int)
	If Starter.logger Then Log($"panel:${p}, alncho=${w}, alto=${h}"$)
	p.Top = 0
	p.Left = 0
	p.Width = w
	p.Height = h
End Sub

'Regresa el dia de HOY como string, en español y mayusculas.
Sub dameDiaSemana As String
	Private ds As String = DateTime.GetDayOfWeek(DateTime.Now)
	If ds = "1" Then
		ds = "DOMINGO"
	else if ds = "2" Then
		ds = "LUNES"
	else if ds = "3" Then
		ds = "MARTES"
	else if ds = "4" Then
		ds = "MIERCOLES"
	else if ds = "5" Then
		ds = "JUEVES"
	else if ds = "6" Then
		ds = "VIERNES"
	Else
		ds = "SABADO"
	End If
	Return ds
End Sub

'Regresa cuentos clientes fuera de frecuencia hay con venta.
Sub dameClientesFueraDeFrecuencia As String
	Private dia_visita As String = dameDiaSemana
	Private f As Cursor = Starter.skmt.ExecQuery("SELECT CAT_VA_VALOR as dia_visita FROM CAT_VARIABLES WHERE CAT_VA_DESCRIPCION = 'DIA_VISITA'")
	If f.RowCount > 0 Then
		f.Position=0
		dia_visita = f.GetString("dia_visita")
	End If
'	Log("-> "&dia_visita)
	f = Starter.skmt.ExecQuery("Select count(distinct PE_CLIENTE) as cff FROM kmt_info INNER JOIN PEDIDO ON kmt_info.CAT_CL_CODIGO = PEDIDO.PE_CLIENTE WHERE kmt_info.CAT_CL_DIAS_VISITA <> '"&dia_visita&"'")
	f.Position=0
'	Log(f.GetString("cff"))
	Private cff As String = f.GetString("cff")
	f.Close
	Return cff
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

'Regresa la ruta de venta actual de la base de datos.
Sub traeRuta As String 'ignore
	Private c As Cursor
	Private r As String
	c=Starter.skmt.ExecQuery("select CAT_CL_RUTA from kmt_info 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 el cliente actual desde la base de datos
Sub traeCliente As String 'ignore
	Private c As Cursor
	Private cl As String
	c=Starter.skmt.ExecQuery("Select CUENTA from cuentaa")
	c.Position=0
	cl = c.GetString("CUENTA")
	c.Close
	Return cl
End Sub

Sub revisaimpreso (cliente As String) As Boolean
	Dim c1, c2 As Cursor
	Dim yaimpreso As Boolean = False
	c1 = Starter.skmt.ExecQuery2("select count(*) as IMPRESOS FROM kmt_info WHERE IMPRESION = 1 AND CAT_CL_CODIGO = ?", Array As String(cliente))
	c2 = Starter.skmt.ExecQuery2("SELECT COUNT(*) AS IMPRESO2 FROM CLIENTE_IMPRESO WHERE CI_CUENTA= ?",Array As String(cliente))
	c1.Position = 0
	c2.Position = 0
	
	If c1.GetString("IMPRESOS") <> 0 Or c2.GetString("IMPRESO2") <> 0 Then
		yaimpreso = True
	End If
	Return yaimpreso
End Sub