let PassiveTrackingController = {
	passiveTracking: '',
	coordinates: [],
	mapFlag: false,
	infoFlag: false,
	startDate: '',
	startTime: '00:00',
	endDate: '',
	endTime: '23:59',
	country: '',
	tableFlag: false,
	map: '',
	markers: [],
	gpsInfoUser: '',
	mapRoute: null,
	gdpr: '',
	colorsHex: [],
	colorsRGB: [],
	fontSize: 0,
	fontName: '',
	wordCloudURL: '',
	primaryColor: '',
	secundaryColor: '',
	splitCharacter: '',
	googleMapsAPIKey: '',
	loaders: {},

	reset() {
		let self = this;

		self.passiveTracking = '';
		self.coordinates = [];
		self.mapFlag = false;
		self.infoFlag = false;
		self.startDate = '';
		self.startTime = '00:00';
		self.endDate = '';
		self.endTime = '23:59';
		self.country = '';
		self.tableFlag = false;
		self.map = '';
		self.markers = [];
		self.gpsInfoUser = '';
		self.mapRoute = null;
		self.gdpr = sessionStorage['STCMBackEnd:GDPR'];
		self.colorsHex = [];
		self.colorsRGB = [];
		self.fontSize = 0;
		self.fontName = '';
		self.wordCloudURL = '';
		self.primaryColor = '';
		self.secundaryColor = '';
		self.splitCharacter = '';
		self.googleMapsAPIKey = '';
		self.loaders = {
			getAnalyticsPassiveTracking: false,
			getPassiveTrackingGraphs: false
		};
	},

	setEvents() {
		let self = this;
		self.reset();

		// Ampliación de Header
		headerSectionHeight(0);

		// Añadir titulo
		$('#page-title').text(pf.const.language.RSC1271);

		// Opciones de header
		let jsonsubopt = {
			country: {
				id: 'country',
				deleteBtn: true,
				style: 'style="line-height: initial; padding-top: 1.75rem!important;"'
			},
			refresh: 'previewTables',
			startEndDate: {
				class: 'form-group m-0 ml-1',
				style: 'max-width: 160px; line-height: initial;'
			},
			startEndTime: {
				class: 'form-group m-0 ml-1',
				style: 'min-width: 115px; line-height: initial;'
			},
			report: 'exportData'
		};

		let nav = `
			<div class="d-flex justify-content-end row m-0">
				${createSubOptionStartEndDateTime(jsonsubopt.startEndDate.class, jsonsubopt.startEndTime.class, jsonsubopt.startEndDate.style, jsonsubopt.startEndTime.style)}
				${createSubOptionCountry(jsonsubopt.country.id, jsonsubopt.country.deleteBtn, jsonsubopt.country.style)}
				<div class="pa-to-28 d-flex align-items-center">
					${createSubOptionRefresh(jsonsubopt.refresh)}
					${createSubOptionGetReport(jsonsubopt.report)}
				</div>
			</div>
		`;

		// Acciones de página (flex-row-reverse)s
		$('#page-actions').html(nav);

		// Botón principal
		$('#exportData').addClass('main-button');

		// Botones secundarios
		$('#previewTables').addClass('secondary-button');

		$('#countryDelete').hide();
		$('#resultMap').hide();
		$('#resultData').hide();
		$('#resultEdades').hide();
		$('#resultGeneros').hide();
		$('#resultStates').hide();
		$('#resultRedes').hide();
		$('#resultMapa').hide();
		$('#resultNSE').hide();
		$('#resultDispositivos').hide();
		$('#resultWordcloud').hide();

		self.loadLang();

		// Inputs de fecha y hora
		let now = new Date();
		let nYear = now.getFullYear();
		let nMonth = now.getMonth() + 1;
		if(nMonth < 10) {
			nMonth = '0' + nMonth;
		}
		let nDay = now.getDate();
		if(nDay < 10) {
			nDay = '0' + nDay;
		}
		self.today = nYear + '-' + nMonth + '-' + nDay;
		self.startDate = self.today;
		self.endDate = self.today;

		$('#startDate').val(self.startDate);
		$('#startTime').val(self.startTime);
		$('#endDate').val(self.endDate);
		$('#endTime').val(self.endTime);

		self.applyEvents();

		// Control de países permitidos por el administrador si solo hay uno
		let countries = JSON.parse(atob(sessionStorage['STCMBackEnd:userCountries']));
		let countriesDesc = atob(sessionStorage['STCMBackEnd:userCountriesDesc']);
		if(countries.length === 1) {
			self.country = countries[0];
			$('#country').val(countriesDesc);
			$('#country').attr('title', countriesDesc);
			$('#countryDelete').show();
		}

		// Collapse
		addEventArrowTableHeader();

		// Pintar texto de label
		changeLabelsColor();
	},

	loadLang() {
		pf.rsc.translateByTag('text');

		// TITLES
		$('.exportTitle').attr('title', pf.const.language.RSC47);
	},

	applyEvents() {
		let self = this;

		// Valores de configuración
		let config = JSON.parse(atob(sessionStorage.getItem('STCMBackEnd:config')));
		self.typeColors = config.UsarColorDegradado;
		self.fontSize = config.SizeFontAnalytics;
		self.fontName = config.FontAnalytics;
		self.wordCloudURL = config.URLWordCloud;
		self.primaryColor = config.tenant[0].colorPrimario.replace('#', '');
		self.secundaryColor = config.tenant[0].colorSecundario.replace('#', '');
		self.splitCharacter = config.splitCharacter;
		self.googleMapsAPIKey = config.googleMapsAPIKey;

		if(!config.RGBFin || config.RGBFin === '') {
			self.colorsRGB[0] = '000,000,255';
			self.colorsHex[0] = rgb2hex('rgb(000,000,255)');
		} else {
			if(self.typeColors == 1) {
				self.colorsRGB[0] = config.RGBFin;
				self.colorsHex[0] = rgb2hex('rgb(' + config.RGBFin + ')');
			} else {
				self.colorsRGB = [];
				for(let i = 0; i < config.colores.length; i++) {
					self.colorsRGB.push(config.colores[i].color);
				}
			}
		}
		if(!config.RGBInicio || config.RGBInicio === '') {
			self.colorsRGB[1] = '255,000,000';
			self.colorsHex[1] = rgb2hex('rgb(255,000,000)');
		} else {
			if(self.typeColors == 1) {
				self.colorsRGB[1] = config.RGBInicio;
				self.colorsHex[1] = rgb2hex('rgb(' + config.RGBInicio + ')');
			} else {
				self.colorsHex = [];
				for(let i = 0; i < config.colores.length; i++) {
					self.colorsHex.push(rgb2hex('rgb(' + config.colores[i].color + ')'));
				}
			}
		}

		// Fecha de inicio
		$('#startDate').on('change', function() {
			self.startDate = $('#startDate').val();
			if(self.startDate > self.endDate) {
				$('#startDate').val(self.endDate);
				self.startDate = $('#startDate').val();
			}
		});

		// Hora de inicio
		$('#startTime').on('change', function() {
			self.startTime = $('#startTime').val();
		});

		// Fecha de fin
		$('#endDate').on('change', function() {
			self.endDate = $('#endDate').val();
			if(self.endDate < self.startDate) {
				$('#endDate').val(self.startDate);
				self.endDate = $('#endDate').val();
			}
		});

		// Hora de fin
		$('#endTime').on('change', function() {
			self.endTime = $('#endTime').val();
		});

		// País
		let countryModal = new TableCountries(0, 'passiveTracking');
		$('#searchCountry').on('click', function() {
			countryModal.setEvents();
		});

		// Limpiar país
		$('#countryDelete').on('click', function() {
			self.country = '';
			$('#country').val('');
			$('#country').attr('title', '');
			$('#countryDelete').hide();
		});

		// Visualizar datos de tracking
		$('#previewTables').on('click', function() {
			$('#infoTitle').text('');
			$('#routeInfo').html('');
			$('#tableInfo').html('');
			self.mapRoute = null;
			$('#resultData').show();
			$('#resultEdades').show();
			$('#resultGeneros').show();
			$('#resultStates').show();
			$('#resultRedes').show();
			$('#resultMapa').show();
			$('#resultNSE').show();
			$('#resultDispositivos').show();
			$('#resultWordcloud').show();

			self.loaders = {
				getAnalyticsPassiveTracking: false,
				getPassiveTrackingGraphs: false
			};

			addLoader();
			self.getAnalyticsPassiveTracking(false);
			self.requestPassiveTrackingGraphs();
		});

		// Documento de excel
		$('#exportData').on('click', function() {
			let reports = {
				ppt: false,
				excel: false
			};

			let content = `
				<p>${pf.const.language.RSC59}</p>

				<div class="row mt-3 mx-0 mb-0">
					<div class="col-6 mb-0 pl-0">
						<!-- Powerpoint -->
						<label for="cbppt" class="form-check form-check-inline option-box-check w-100 h-100 c-pointer">
							<input id="cbppt" type="checkbox" class="form-check-input reportCheck" name="cbword">

							<div class="ml-2">
								<span class="fw-500">${pf.const.language.RSC538}</span><br>
								<span class="fs-14">${pf.const.language.RSC539}</span>
							</div>
						</label>
					</div>

					<div class="col-6 mb-0 pr-0">
						<!-- Excel -->
						<label for="cbexcel" class="form-check form-check-inline option-box-check w-100 h-100 c-pointer">
							<input id="cbexcel" type="checkbox" class="form-check-input reportCheck" name="cbwc">

							<div class="ml-2">
								<span class="fw-500">${pf.const.language.RSC540}</span><br>
								<span class="fs-14">${pf.const.language.RSC541}</span>
							</div>
						</label>
					</div>
				</div>

				<script>
					$('.reportCheck').on('click', function() {
						if($(this).prop('checked') == false) {
							$(this).parent().removeClass('option-box-check-selected');
						} else {
							$(this).parent().addClass('option-box-check-selected');
						}
					});
				</script>
			`;	

			let objInfo = {
				accept: {
					text: pf.const.language.RSC60
				},
				cancel: {
					text: pf.const.language.RSC57
				}
			};

			let alertTitle = pf.const.language.RSC48;
			self.alert = pf.utils.showInfoDialogCustomWidthAcceptCancelNewStyle(alertTitle, content, objInfo, '750px', function() {
				let reports = {
					ppt: false,
					excel: false
				};
				reports.excel = $('#cbexcel').prop('checked') ? true : false;
				reports.ppt = $('#cbppt').prop('checked') ? true : false;

				let idTenant = sessionStorage['STCMBackEnd:idTenant'];
				if(reports.excel == true || reports.ppt == true) {
					self.getAnalyticsPassiveTracking(true, reports.excel, reports.ppt, idTenant);
				}
			}, function() {});
		});
	},

	// parámetro toExport indica si se descarga el excel/ppt (true) o se cargan los datos en el resultTable (false)
	getAnalyticsPassiveTracking(toExport, bExcel, bPPT, idTenant) {
		let self = this;

		let nameExcel = pf.const.language.RSC1271;
		let cabeceras = [];
		let parametros = [];

		let token = sessionStorage['STCMBackEnd:token'];
		let reqName = 'getAnalyticsPassiveTracking';
		let reqNameExport = 'getAnalyticsPassiveTrackingExport';
		let country = self.country;
		let fechaInicio = self.startDate + ' ' + self.startTime;
		let fechaFin = self.endDate + ' ' + self.endTime;

		// Generador de documento en excel
		if(toExport) {
			reqName = 'getAnalyticsPassiveTrackingExport';
			// Añadido imágenes power point
			let graphs = [];
			let graphsNames = ['graphAge', 'graphGender', 'graphStates', 'graphNSE', 'graphDevice', 'graphNetwork', 'graphWordcloud'];
			for(let i = 0; i < graphsNames.length; i++) {
				let element = graphsNames[i];
				let url_base64jp = document.getElementById('ppt' + element).toDataURL('image/jpg');
				graphs.push(url_base64jp);
			}
			// Añadido mapas a ppt
			let resultMap = $('#result_map').children();
			for(let i = 0; i < resultMap.length; i++) {
				let element = resultMap[i];
				let url_base64jp = document.getElementById(element.id).toDataURL('image/jpg');
				graphs.push(url_base64jp);
			}

			cabeceras = [pf.const.language.RSC550, pf.const.language.RSC123, pf.const.language.RSC185 + ' 1', pf.const.language.RSC185 + ' 2', pf.const.language.RSC136, pf.const.language.RSC564, pf.const.language.RSC565, pf.const.language.RSC151, pf.const.language.RSC598];

			parametros = ['questionPais', 'userNombre', 'userApellidos', 'userApellidos2', 'email', 'questionEstado', 'questionEdad', 'questionGenero', 'questionABI'];

			// Control GDPR
			if(self.gdpr = '1') {
				cabeceras = [pf.const.language.RSC550, pf.const.language.RSC136, pf.const.language.RSC564, pf.const.language.RSC565, pf.const.language.RSC151, pf.const.language.RSC598];

				parametros = ['questionPais', 'email', 'questionEstado', 'questionEdad', 'questionGenero', 'questionABI'];
			}

			values = {
				action: reqName,
				actionExport: reqNameExport,
				token: token,
				country: country,
				fechaInicio: fechaInicio,
				fechaFin: fechaFin,
				nameExcel: nameExcel,
				cabeceras: cabeceras,
				parametros: parametros,
				bExcel: bExcel,
				bPPT: bPPT,
				graphs: graphs,
				idTenant: idTenant
			};

			let generator = new DocumentsGenerator(values);
			generator.start();
			return;
		}

		let parameters = {
			fechaInicio: fechaInicio,
			fechaFin: fechaFin,
			country: country,
			toExport: toExport,
			nameExcel: nameExcel,
			cabeceras: cabeceras,
			parametros: parametros
		};
		ajaxComunCallWithCallback('getAnalyticsPassiveTracking', parameters, function(ajaxReturn) {
			if(ajaxReturn) {
				$('#exportData').prop('disabled', false);
				self.passiveTracking = ajaxReturn.result;

				self.coordinates = [];
				let size = self.passiveTracking.length;
				for(let i = 0; i < size; i++) {
					let latitude = self.passiveTracking[i].last_latitude;
					let longitude = self.passiveTracking[i].last_longitude;
					if(latitude != '' && latitude != null && longitude != '' && longitude != null) {
						let name = self.passiveTracking[i].userNombre + ' ' + self.passiveTracking[i].userApellidos + ' ' + self.passiveTracking[i].userApellidos2;
						let lastCoor = {
							name: name,
							latitude: latitude.trim(),
							longitude: longitude.trim()
						};

						self.coordinates.push(lastCoor);
					}
				}

				self.renderPassiveTracking();
				self.generatePerfilationVariablesGraphs(ajaxReturn);

				// Control de spinner
				self.loaders.getAnalyticsPassiveTracking = true;
				spinnerControl(self.loaders);
			}
		}, false);
	},

	generatePerfilationVariablesGraphs(resp) {
		let self = this;

		// CREACIÓN DE MAPA
		if(!self.mapFlag) {
			self.renderMapPassiveTracking();
		} else {
			self.initMap();
		}

		let age = resp.graphs.age;
		self.createGraphs(age, 0);

		let gender = resp.graphs.gender;
		self.createGraphs(gender, 2);

		let states = resp.graphs.states;
		self.createGraphs(states, 3);

		let nse = resp.graphs.nse;
		self.createGraphs(nse, 4);

		let map = resp.graphs.mapData;
		self.createMapGraph(map);
	},

	requestPassiveTrackingGraphs() {
		let self = this;

		let country = self.country;
		let fechaInicio = self.startDate + ' ' + self.startTime;
		let fechaFin = self.endDate + ' ' + self.endTime;
		let nameExcel = pf.const.language.RSC1271;
		let cabeceras = [];
		let parametros = [];

		let parameters = {
			fechaInicio: fechaInicio,
			fechaFin: fechaFin,
			country: country,
			toExport: false,
			nameExcel: nameExcel,
			cabeceras: cabeceras,
			parametros: parametros
		};
		ajaxComunCallWithCallback('getPassiveTrackingGraphs', parameters, function(ajaxReturn) {
			if(ajaxReturn) {
				let devices = ajaxReturn.devices;
				self.createGraphs(devices, 5);

				let network = ajaxReturn.network;
				self.createGraphs(network, 6);

				let apps = ajaxReturn.apps;
				self.createGraphs(apps, 8);

				// Control de spinner
				self.loaders.getPassiveTrackingGraphs = true;
				spinnerControl(self.loaders);
			}
		}, false);
	},

	// GRÁFICAS
	createGraphs(data, n) {
		let self = this;

		let ctx;
		let quantity = data.labels.length;
		let gradient = [];
		if(quantity == 2) {
			gradient = self.colorsHex;
		} else {
			if(self.typeColors == 1) {
				gradient = generateColors(self.colorsHex[0], self.colorsHex[1], quantity);
			} else {
				gradient = arrayColors(self.colorsHex, quantity);
			}
		}

		let options = '';
		if(typeof data.options == 'object') {
			options = data.options;
		} else {
			options = JSON.parse(data.options);
		}

		options.title.display = true;
		options.title.padding = 30;

		let width = 800;
		let height = 400;

		switch(n) {
			case 0:
				options.title.text = pf.const.language.RSC152 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphAge');
				hiddencanvas = document.getElementById('pptgraphAge');
				width = 600;
				break;
			case 2:
				options.title.text = pf.const.language.RSC151 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphGender');
				hiddencanvas = document.getElementById('pptgraphGender');
				width = 300;
				break;
			case 3:
				options.title.text = pf.const.language.RSC153 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphStates');
				hiddencanvas = document.getElementById('pptgraphStates');
				width = 1100;
				break;
			case 4:
				options.title.text = pf.const.language.RSC598 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphNSE');
				hiddencanvas = document.getElementById('pptgraphNSE');
				break;
			case 5:
				options.title.text = pf.const.language.RSC1993 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphDevice');
				hiddencanvas = document.getElementById('pptgraphDevice');
				width = 600;
				break;
			case 6:
				options.title.text = pf.const.language.RSC1995 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphNetwork');
				hiddencanvas = document.getElementById('pptgraphNetwork');
				width = 300;
				break;
			case 7:
				options.title.text = pf.const.language.RSC153 + ' %';
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphMap');
				hiddencanvas = document.getElementById('pptgraphMap');
				break;
			case 8:
				options.title.text = pf.const.language.RSC1465;
				data.datasets.backgroundColor = gradient;
				canvas = document.getElementById('graphWordcloud');
				hiddencanvas = document.getElementById('pptgraphWordcloud');
				break;
		}

		// Truco para borrar gráficas al recargarlas
		let html = `<canvas id="${canvas.id}" class="bg-white min-he-300"></canvas>`;

		let hiddenhtml = `<canvas id="${hiddencanvas.id}" width="${width}" height="${height}"></canvas>`;

		$('#' + canvas.id).remove();
		$('.' + canvas.id + 'Parent').append(html);

		$('#' + hiddencanvas.id).remove();
		$('#pptCanvas').append(hiddenhtml);

		ctx = document.getElementById(canvas.id).getContext('2d');
		hiddenctx = document.getElementById(hiddencanvas.id).getContext('2d');

		options['tooltips'] = {
			enabled: false
		};

		if(n == 1) {
			options['plugins'] = {
				datalabels: {
					color: 'black',
					font: {
						weight: 'bold',
						size: self.fontSize
					},
					clamp: true,
					formatter(value, context) {
						return value;
					}
				}
			};
		} else {
			options['plugins'] = {
				datalabels: {
					color: 'black',
					font: {
						weight: 'bold',
						size: self.fontSize
					},
					anchor: 'end',
					align: 'end',
					formatter(value, context) {
						return value;
					}
				}
			};
		}

		if(n !== 2 && n != 4 && n != 6) {
			// Ancho de columnas
			options.scales.xAxes[0]['barPercentage'] = 0.5;

			// Ticks X en horizontal con salto de linea
			// Ancho del contenedor de la gráfica
			let divParentWidth = 800;
			// Número de columnas de la gráfica
			let nColumns = quantity;
			// Ancho del contenedor entre el número de columnas
			let maxwidth = Math.floor(parseInt(divParentWidth) / nColumns);
			// Ancho de la columna entre el tamaño de la fuente
			let maxwidthFont = Math.ceil(maxwidth / self.fontSize) + 2;

			// Etiquetas del eje X
			let xLabels = data.labels;
			// Comprobación de la primera palabra de los labels para ejecutar formato horizontal o diagonal
			let controlH = true;
			let xSize = xLabels.length;
			// Utilización de caracter de corte si lo hay
			if(self.splitCharacter != '') {
				for(let i = 0; i < xSize; i++) {
					if(xLabels[i] == null || xLabels[i] == undefined) {
						continue;
					}

					let indexCut = xLabels[i].toString().indexOf(self.splitCharacter);
					if(indexCut != -1) {
						xLabels[i] = xLabels[i].toString().slice(0, indexCut);
					}
				}
			}

			for(let i = 0; i < xSize; i++) {
				if(xLabels[i] == null || xLabels[i] == undefined) {
					continue;
				}

				let words = xLabels[i].toString().split(' ');
				let wSize = words.length;
				for(let j = 0; j < wSize; j++) {
					if(words[j].length > maxwidthFont) {
						controlH = false;
						break;
					}
				}
				if(!controlH) {
					break;
				}
			}

			if(controlH) {
				options.scales.xAxes[0].ticks['minRotation'] = 0;
				options.scales.xAxes[0].ticks['maxRotation'] = 0;

				options.scales.xAxes[0].ticks['callback'] = function(value, index, values) {
					let formmatedvalue = formatLabel(value, maxwidthFont);
					return formmatedvalue;
				}
			} else {
				options.scales.xAxes[0].ticks['callback'] = function(value, index, values) {
					if(value == null || value == undefined || value == '') {
						return value;
					} else if(value.length < 18) {
						return value;
					} else {
						// Se pasa a string porque puede haber nombres de apps que sean solo números (juego 2048 por ejemplo)
						value = String(value);
						return value.substr(0, 14) + '...';
					}
				};
			}
		}

		Chart.defaults.global.defaultFontSize = parseInt(self.fontSize);
		Chart.defaults.global.defaultFontFamily = self.fontName;

		// Radio de ángulos
		options.cornerRadius = 6;

		let graphElement = {
			// The type of chart we want to create
			type: data.type,
			// The data for our dataset
			data: {
				datasets: [data.datasets],
				labels: data.labels
			},
			// Configuration options go here
			options: options
		};

		let graph;
		graph = new Chart(ctx, graphElement);

		// Gráfica oculta para ppt con tamaño fijo
		graphElement.options.responsive = false;
		// Quitar radio de ángulos para powerpoint
		graphElement.options.cornerRadius = 0;
		graph = new Chart(hiddenctx, graphElement);

		if(data.datasets.data.length == 0) {
			ctx.fillText(pf.const.language.RSC2111, document.getElementById(canvas.id).width/2-380, document.getElementById(canvas.id).height/2);
			ctx.textAlign = 'center';
			hiddenctx.fillText(pf.const.language.RSC2111, document.getElementById(hiddencanvas.id).width/2-380, document.getElementById(hiddencanvas.id).height/2);
			hiddenctx.textAlign = 'center';
		}
	},

	renderPassiveTracking() {
		let self = this;

		$('#registers').html('');
		if(self.tableFlag) {
			$('#registers').dataTable().fnDestroy();
		} else {
			self.tableFlag = true;
		}

		let nameSurnameHead = `
			<th scope="col" title="${pf.const.language.RSC123}">${pf.const.language.RSC123}</th>
			<th scope="col" title="${pf.const.language.RSC1626}">${pf.const.language.RSC1626}</th>
			<th scope="col" title="${pf.const.language.RSC1627}">${pf.const.language.RSC1627}</th>
		`;
		let nameSurnameFilter = `
			<td>nombre</td>
			<td>apellido 1</td>
			<td>apellido 2</td>
		`;

		// Control GDPR
		if(self.gdpr == '1') {
			nameSurnameHead = '';
			nameSurnameFilter = '';
		}

		let table = `
			<thead>
				<tr>
					<th scope="col" title="${pf.const.language.RSC550}">${pf.const.language.RSC550}</th>
					${nameSurnameHead}
					<th scope="col" title="${pf.const.language.RSC136}">${pf.const.language.RSC136}</th>
					<th scope="col" title="${pf.const.language.RSC564}">${pf.const.language.RSC564}</th>
					<th scope="col" title="${pf.const.language.RSC565}">${pf.const.language.RSC565}</th>
					<th scope="col" title="${pf.const.language.RSC151}">${pf.const.language.RSC151}</th>
					<th scope="col" title="${pf.const.language.RSC598}">${pf.const.language.RSC598}</th>
					<th scope="col" title="${pf.const.language.RSC1392}">${pf.const.language.RSC1392}</th>
					<th scope="col" title="${pf.const.language.RSC1228}">${pf.const.language.RSC1228}</th>
					<th scope="col" title="${pf.const.language.RSC1393}">${pf.const.language.RSC1393}</th>
					<th scope="col" title="${pf.const.language.RSC1394}">${pf.const.language.RSC1394}</th>
					<th scope="col" title="${pf.const.language.RSC1465}">${pf.const.language.RSC1465}</th>
				</tr>
			</thead>

			<thead class="filtersResult">
				<tr>
					<td>pais</td>
					${nameSurnameFilter}
					<td>email</td>
					<td>Estado</td>
					<td>Edad</td>
					<td>Genero</td>
					<td>nse</td>
					<td>gps</td>
					<td>movil</td>
					<td>bateria</td>
					<td>network</td>
					<td>Apps</td>
				</tr>
			</thead>

			<tbody>
		`;

		// Obtener países permitidos
		let allowedCountries = JSON.parse(atob(sessionStorage['STCMBackEnd:userCountries']));
		let size = self.passiveTracking.length;
		for(let i = 0; i < size; i++) {
			// Control de países permitidos
			if(allowedCountries.indexOf(self.passiveTracking[i].country) == -1) {
				continue;
			}

			let gpsData = `
				<button type="button" class="btn table-button main-color-text searchInfo" data-infoType="gps" data-toggle="modal" data-target="#infoModal">
					${stcmbackend.svg.bigGeoAltWithoutColor}
				</button>
			`;

			let deviceData = `
				<button type="button" class="btn table-button main-color-text searchInfo" data-infoType="device" data-toggle="modal" data-target="#infoModal">
					${stcmbackend.svg.bigPhoneWithoutColor}
				</button>
			`;

			let batteryData = `
				<button type="button" class="btn table-button main-color-text searchInfo" data-infoType="battery" data-toggle="modal" data-target="#infoModal">
					${stcmbackend.svg.bigBatteryWithoutColor}
				</button>
			`;

			let networkData = `
				<button type="button" class="btn table-button main-color-text searchInfo" data-infoType="network" data-toggle="modal" data-target="#infoModal">
					${stcmbackend.svg.bigBroadcastPinWithoutColor}
				</button>
			`;

			let appsData = `
				<button type="button" class="btn table-button main-color-text searchInfo" data-infoType="apps" data-toggle="modal" data-target="#infoModal">
					${stcmbackend.svg.bigStackWithoutColor}
				</button>
			`;

			let nameSurnameBody = `
				<td class="text-truncate" title="${(self.passiveTracking[i].userNombre || '')}">
					${(self.passiveTracking[i].userNombre || '')}
				</td>
				<td class="text-truncate" title="${(self.passiveTracking[i].userApellidos || '')}">
					${(self.passiveTracking[i].userApellidos || '')}
				</td>
				<td class="text-truncate" title="${(self.passiveTracking[i].userApellidos2 || '')}">
					${(self.passiveTracking[i].userApellidos2 || '')}
				</td>
			`;

			// Control GDPR
			if(self.gdpr == '1') {
				nameSurnameBody = '';
			}

			table += `
				<tr data-id="${i}">
					<td class="text-truncate" title="${(self.passiveTracking[i].country || '')}">${(self.passiveTracking[i].country || '')}</td>
					${nameSurnameBody}
					<td class="text-truncate" title="${(self.passiveTracking[i].email || '')}">${(self.passiveTracking[i].email || '')}</td>
					<td class="text-truncate" title="${(self.passiveTracking[i].questionEstado || '')}">${(self.passiveTracking[i].questionEstado || '')}</td>
					<td class="text-truncate" title="${(self.passiveTracking[i].questionEdad || '')}">${(self.passiveTracking[i].questionEdad || '')}</td>
					<td class="text-truncate" title="${(self.passiveTracking[i].questionGenero || '')}">${(self.passiveTracking[i].questionGenero || '')}</td>
					<td class="text-truncate" title="${(self.passiveTracking[i].questionABI || '')}">${(self.passiveTracking[i].questionABI || '')}</td>
					<td class="text-center">${gpsData}</td>
					<td class="text-center">${deviceData}</td>
					<td class="text-center">${batteryData}</td>
					<td class="text-center">${networkData}</td>
					<td class="text-center">${appsData}</td>
				</tr>
			`;
		}

		table += `
			</tbody>
		`;

		$('#registers').html(table);

		// Setup - add a text input to each footer cell
		$('#registers .filtersResult td').each(function() {
			$(this).html('<input type="text" class="form-control form-control-sm">');
		});

		let columnDefs = [
			{width: '50px', targets: 0},
			{width: '50px', targets: 6},
			{width: '60px', targets: 7},
			{width: '40px', targets: 8},
			{width: '30px', targets: 9},
			{width: '60px', targets: 10},
			{width: '50px', targets: 11},
			{width: '60px', targets: 12},
			{width: '40px', targets: 13}
		];

		// Control GDPR
		if(self.gdpr == '1') {
			columnDefs = [
				{width: '50px', targets: 0},
				{width: '50px', targets: 3},
				{width: '60px', targets: 4},
				{width: '40px', targets: 5},
				{width: '30px', targets: 6},
				{width: '60px', targets: 7},
				{width: '50px', targets: 8},
				{width: '60px', targets: 9},
				{width: '40px', targets: 10}
			];
		}

		let dataTable = $('#registers').DataTable({
			retrieve: true,
			language: pf.const.language_table,
			pageLength: 25,
			autoWidth: false,
			columnDefs: columnDefs,
			aaSorting: [],
			dom: 'Bfrtip',
			buttons: [{
				extend: 'excelHtml5',
				text: pf.const.language.RSC1271,
				className: 'd-none',
				title: pf.const.language.RSC1271,
				customize(xlsx) {
					dataTablesExcelCustomize(xlsx);
				}
			}]
		});

		setTimeout(function() {
			dataTable.columns.adjust();
		}, 200);

		// Ocultar botones de tabla
		hideTableButtons('registers');

		// Exportar tabla
		$('#export').off().on('click', function() {
			$('#resultTableData .buttons-excel').trigger('click');
		});

		// Apply the search
		applyTheSearch(dataTable, 'filtersResult');

		self.tableEvents();

		// Eventos con cambio de página de tabla
		$('#resultTableData').on('draw.dt', function() {
			self.tableEvents();
		});
	},

	createMapGraph(data) {
		let self = this;

		$('#result_graphs_maps').html('');
		/* 
			Recoge los datos de cada usuario que ha respondido la encuesta, recoge la participación
			por estado de cada país y lo recopila todo en un mapa.
		*/
		let mapaPaisesRegiones = new Map();

		for(let i = 0; i < data.length; i++) {
			let pais = data[i][0];
			let estado = data[i][1];

			if(!mapaPaisesRegiones.has(pais)) {
				mapaPaisesRegiones.set(pais, new Map());
			}

			let objPais = {
				s1: pais,
				s2: {
					s3: estado,
					s4: 0
				}
			}

			objPais.s2.s4++;
			let a = mapaPaisesRegiones.get(objPais.s1).has(objPais.s2.s3);
			if(a) {
				mapaPaisesRegiones.get(objPais.s1).set(objPais.s2.s3 , mapaPaisesRegiones.get(objPais.s1).get(objPais.s2.s3) + 1);
			} else {
				mapaPaisesRegiones.get(objPais.s1).set(objPais.s2.s3,1);
			}
		}

		// Por cada país crea un canvas dentro de '#result_graph_ppt_aux' y crea el HeatMap
		for(let [key, value] of mapaPaisesRegiones) {
			let arrayLabels = [];
			let arrayLabelsData = [];
			for(let [key2, value2] of value) {
				arrayLabels.push(key2);
				arrayLabelsData.push(value2);
			}
			// Pasar a porcentajes los datos
			let total = 0;
			for(let i = 0; i < arrayLabelsData.length; i++) {
				total += arrayLabelsData[i];
			}
			for(let i = 0; i < arrayLabelsData.length; i++) {
				// '* 10) / 10' 1 decimal, '* 100) / 100' 2 decimales, etc.
				arrayLabelsData[i] = Math.round((((100 * arrayLabelsData[i]) / total)) * 1) / 1;
			}

			let idPpt = 'result_graphs_maps_' + key;
			let idPptPpt = 'result_graphs_maps_ppt_' + key;

			// Truco para borrar gráficas al recargarlas
			canvas = document.getElementById(idPpt);
			hiddencanvas = document.getElementById(idPptPpt);

			let html = `<canvas id="${idPpt}" class="bg-white min-he-300"></canvas>`;

			let hiddenhtml = `<canvas id="${idPptPpt}" class="wi-800 he-400"></canvas>`;

			$('#' + idPpt).remove();
			$('.graphMapParent').append(html);

			$('#' + idPptPpt).remove();
			$('#result_map').append(hiddenhtml);

			let ctx = document.getElementById(idPpt).getContext('2d');
			let hiddenctx = document.getElementById(idPptPpt).getContext('2d');

			// Array de colores para representar el mapa (10 opciones)
			let quantity = 10;
			let colors = generateColors(self.primaryColor, self.secundaryColor, quantity);

			let heatMap = new HeatMap(key, 0, '', arrayLabels, arrayLabelsData, ctx, true, colors);
			heatMap.createHeatMap();

			let heatMap2 = new HeatMap(key, 0, '', arrayLabels, arrayLabelsData, hiddenctx, false, colors);
			heatMap2.createHeatMap();
		}
	},

	tableEvents() {
		let self = this;

		// Botones de información
		$('.searchInfo').off().on('click', function() {
			let infoType = $(this).attr('data-infoType');
			let index = $(this).parent().parent().data('id');

			self.getInfoUserDetails(self.passiveTracking[index], infoType);
		});
	},

	getInfoUserDetails(userInfo, type) {
		let self = this;

		// Limpieza de modal
		if(self.infoFlag) {
			$('#tableInfo').dataTable().fnDestroy();
		} else {
			self.infoFlag = true;
		}

		let tableTab = `
			<div id="resultTableTabsContent" class="tab-content">
				<div id="firsttable" class="tab-pane fade show active">
					<table id="tableInfo" class="table table-hover tRegs"></table>
				</div>
			</div>
		`;

		// Modal con tabs para el caso de GPS
		if(type == 'gps') {
			tableTab = `
				<ul id="resultTableTabs" class="nav nav-tabs">
					<li>
						<a href="#firsttable" class="nav-link active perfTabs" data-toggle="tab">
							${pf.const.language.RSC1073}
						</a>
					</li>
				</ul>

				<div id="resultTableTabsContent" class="tab-content">
					<div id="firsttable" class="tab-pane fade show active">
						<table id="tableInfo" class="table table-hover tRegs"></table>
					</div>
				</div>
			`;
		}

		$('#infoTable').html(tableTab);
		$('#infoTitle').text('');
		$('#routeInfo').html('');
		$('#tableInfo').html('');

		let fechaInicio = self.startDate + ' ' + self.startTime;
		let fechaFin = self.endDate + ' ' + self.endTime;

		let parameters = {
			fechaInicio: fechaInicio,
			fechaFin: fechaFin,
			country: userInfo.country,
			email: userInfo.email,
			type: type
		};
		ajaxComunCallWithCallbackOne('getAnalyticsPassiveTrackingDetails', parameters, function(ajaxReturn) {
			if(ajaxReturn) {
				let resp = JSON.parse(ajaxReturn.resData.response);
				if(resp.status === 'OK') {
					self.infoTable(type, resp.result);
				} else {
					self.infoFlag = false;
					let errorTitle = '';
					let errorContent = '';
					if(resp.result == 'GoogleAPIKey not defined') {
						errorTitle = pf.const.language.RSC1271;
						errorContent = pf.const.language.RSC2088;

						setTimeout(function() {
							$('#infoModal').modal('hide');
						}, 200);
					} else {
						errorTitle = pf.const.language.RSC61;
						errorContent = resp.result;
					}

					pf.utils.showInfoDialog(errorTitle, errorContent);
					if(resp.result == 'Not authorized') {
							pf.ajax.setWindowLocationIndex();
					}
				}
			}
		});
	},

	renderMapPassiveTracking() {
		let self = this;

		$('#mapContainer').html('');
		let apiKey = self.googleMapsAPIKey;

		let map = `
			<div id="map" class="he-500"></div>
			<script async defer src="https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMap"></script>
		`;

		$('#mapContainer').html(map);
		$('#resultMap').show();
	},

	// Método para creación de mapa
	initMap() {
		let self = PassiveTrackingController;

		self.map = new google.maps.Map(document.getElementById('map'), {
			center: {lat: 0, lng: 0},
			zoom: 2
		});

		if(!self.mapFlag) {
			self.mapFlag = true;
		} else {
			// Eliminar marcadores
			for(let i = 0; i < self.markers.length; i++) {
				self.markers[i].setMap(null);
			}
			self.markers = [];
		}

		let marker;
		let size = self.coordinates.length;
		for(let i = 0; i < size; i++) {
			marker = new google.maps.Marker({
				position: new google.maps.LatLng(self.coordinates[i].latitude, self.coordinates[i].longitude),
				map: self.map,
				title: self.coordinates[i].name
			});
			self.markers.push(marker);
		}
	},

	// Método para creación de mapa de ruta
	initMapRoute() {
		let self = PassiveTrackingController;

		let route = [];
		let size = self.gpsInfoUser.length;
		for(let i = 0; i < size; i++) {
			if(self.gpsInfoUser[i].latitude !== '' && self.gpsInfoUser[i].longitude !== '') {
				let obj = {
					lat: parseFloat(self.gpsInfoUser[i].latitude),
					lng: parseFloat(self.gpsInfoUser[i].longitude),
					title: self.gpsInfoUser[i].date
				};

				route.push(obj);
			}
		}

		if(route.length > 0) {
			self.mapRoute = new google.maps.Map(document.getElementById('mapRoute'), {
				center: {lat: route[0].lat, lng: route[0].lng},
				zoom: 18
			});

			// Información de la ruta (coordenadas, color de línea, etc...)
			let routePoints = new google.maps.Polyline({
				path: route,
				geodesic: true,
				strokeColor: '#BB0000',
				strokeOpacity: 1.0,
				strokeWeight: 4
			});

			// Creando la ruta en el mapa
			routePoints.setMap(self.mapRoute);

			// Creación de markas
			let marker;
			let markers = [];
			let size = route.length;
			for(let i = 0; i < size; i++) {
				marker = new google.maps.Marker({
					position: new google.maps.LatLng(route[i].lat, route[i].lng),
					map: self.mapRoute,
					title: route[i].title
				});
				markers.push(marker);
			}
		}
	},

	infoTable(infoType, infoUser) {
		let self = this;

		let excelTitle = '';
		let table = '';
		let size = 0;
		let columnDefs = [];
		switch(infoType) {
			case 'gps':
				$('#infoTitle').text(pf.const.language.RSC1431);
				excelTitle = pf.const.language.RSC1431;

				// Activación de botón de ruta
				$('#routeInfo').prop('disabled', false);

				table = `
					<thead>
						<tr>
							<th scope="col" title="${pf.const.language.RSC457}">${pf.const.language.RSC457}</th>
							<th class="d-none" scope="col" title="${pf.const.language.RSC1432}">${pf.const.language.RSC1432}</th>
							<th class="d-none" scope="col" title="${pf.const.language.RSC1435}">${pf.const.language.RSC1435}</th>
							<th class="d-none" scope="col" title="${pf.const.language.RSC1433}">${pf.const.language.RSC1433}</th>
							<th class="d-none" scope="col" title="${pf.const.language.RSC1434}">${pf.const.language.RSC1434}</th>
							<th scope="col" title="${pf.const.language.RSC1229}">${pf.const.language.RSC1229}</th>
							<th scope="col" title="${pf.const.language.RSC438}">${pf.const.language.RSC438}</th>
							<th scope="col" title="${pf.const.language.RSC1343}">${pf.const.language.RSC1343}</th>
							<th scope="col" title="${pf.const.language.RSC1759}">${pf.const.language.RSC1759}</th>
						</tr>
					</thead>

					<thead class="filtersInfo">
						<tr>
							<td>date</td>
							<td class="d-none">accuracy</td>
							<td class="d-none">longitude</td>
							<td class="d-none">altitude</td>
							<td class="d-none">latitude</td>
							<td>dirección</td>
							<td>tipo</td>
							<td>icono</td>
							<td>enlace</td>
						</tr>
					</thead>

					<tbody>
				`;

				size = infoUser.length;

				// Botón de ruta
				if(size > 1) {
					let route = stcmbackend.svg.bigMapWithoutColor;

					$('#routeInfo').html(route);

					// Mapa con la ruta del usuario
					$('#routeInfo').off().on('click', function() {
						self.gpsInfoUser = infoUser;

						self.createMapRoute();
					});
				}

				for(let i = 0; i < size; i++) {
					let icon = '-';
					if(infoUser[i].place_data !== undefined) {
						if(infoUser[i].place_data.icon !== undefined) {
							icon = `<img src="${infoUser[i].place_data.icon}" class="img-fluid wi-16 he-16">`;
						}
					}
					let link = '-';
					if(infoUser[i].place_data !== undefined) {
						if(infoUser[i].place_data.url !== undefined) {
							link = `<a href="#" class="iFrameMap" data-url="${infoUser[i].place_data.url}" data-index="i_${i}">${infoUser[i].place_data.name}</a>`;
						}
					}
					table += `
						<tr>
							<td class="text-truncate" title="${(infoUser[i].date || '-')}">${(infoUser[i].date || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].accuracy ? parseFloat(infoUser[i].accuracy).toFixed(6) : '-')}">${(infoUser[i].accuracy ? parseFloat(infoUser[i].accuracy).toFixed(6) : '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].longitude ? parseFloat(infoUser[i].longitude).toFixed(6) : '-')}">${(infoUser[i].longitude ? parseFloat(infoUser[i].longitude).toFixed(6) : '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].altitude ? parseFloat(infoUser[i].altitude).toFixed(6) : '-')}">${(infoUser[i].altitude ? parseFloat(infoUser[i].altitude).toFixed(6) : '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].latitude ? parseFloat(infoUser[i].latitude).toFixed(6) : '-')}">${(infoUser[i].latitude ? parseFloat(infoUser[i].latitude).toFixed(6) : '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].formatted_address || '-')}">${(infoUser[i].formatted_address || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].types || '-')}">${(infoUser[i].types || '-')}</td>
							<td class="text-truncate text-center">
								<span class="d-none">-</span>
								${icon}
							</td>
							<td class="text-truncate">${link}</td>
						</tr>
					`;
				}

				table += `
					</tbody>
				`;

				columnDefs = [
					{width: '130px', targets: 0},
					{width: '80px', visible: false, targets: 1},
					{width: '80px', visible: false, targets: 2},
					{width: '80px', visible: false, targets: 3},
					{width: '80px', visible: false, targets: 4},
					{width: '45px', targets: 7}
				];

				break;
			case 'device':
				$('#infoTitle').text(pf.const.language.RSC1228);
				excelTitle = pf.const.language.RSC1228;

				table = `
					<thead>
						<tr>
							<th scope="col" title="${pf.const.language.RSC457}">${pf.const.language.RSC457}</th>
							<th scope="col" title="${pf.const.language.RSC1419}">${pf.const.language.RSC1419}</th>
							<th scope="col" title="${pf.const.language.RSC1420}">${pf.const.language.RSC1420}</th>
							<th scope="col" title="${pf.const.language.RSC1421}">${pf.const.language.RSC1421}</th>
							<th scope="col" title="${pf.const.language.RSC1422}">${pf.const.language.RSC1422}</th>
							<th scope="col" title="${pf.const.language.RSC1423}">${pf.const.language.RSC1423}</th>
							<th scope="col" title="${pf.const.language.RSC1424}">${pf.const.language.RSC1424}</th>
							<th scope="col" title="${pf.const.language.RSC1425}">${pf.const.language.RSC1425}</th>
							<th scope="col" title="${pf.const.language.RSC1426}">${pf.const.language.RSC1426}</th>
							<th scope="col" title="${pf.const.language.RSC1427}">${pf.const.language.RSC1427}</th>
						</tr>
					</thead>

					<thead class="filtersInfo">
						<tr>
							<td>date</td>
							<td>available</td>
							<td>cordova</td>
							<td>isVirtual</td>
							<td>manufacturer</td>
							<td>model</td>
							<td>platform</td>
							<td>serial</td>
							<td>uuid</td>
							<td>version</td>
						</tr>
					</thead>

					<tbody>
				`;

				size = infoUser.length;
				for(let i = 0; i < size; i++) {
					table += `
						<tr>
							<td class="text-truncate" title="${(infoUser[i].date || '-')}">${(infoUser[i].date || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].available || '-')}">${(infoUser[i].available || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].cordova || '-')}">${(infoUser[i].cordova || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].isVirtual || '_')}">${(infoUser[i].isVirtual || '_')}</td>
							<td class="text-truncate" title="${(infoUser[i].manufacturer || '-')}">${(infoUser[i].manufacturer || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].model || '-')}">${(infoUser[i].model || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].platform || '-')}">${(infoUser[i].platform || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].serial || '-')}">${(infoUser[i].serial || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].uuid || '-')}">${(infoUser[i].uuid || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].version || '-')}">${(infoUser[i].version || '-')}</td>
						</tr>
					`;
				}

				table += `
					</tbody>
				`;

				columnDefs = [
					{width: '130px', targets: 0}
				];
				break;
			case 'battery':
				$('#infoTitle').text(pf.const.language.RSC1393);
				excelTitle = pf.const.language.RSC1393;

				table = `
					<thead>
						<tr>
							<th scope="col" title="${pf.const.language.RSC457}">${pf.const.language.RSC457}</th>
							<th scope="col" title="${pf.const.language.RSC1428}">${pf.const.language.RSC1428}</th>
							<th scope="col" title="${pf.const.language.RSC1429}">${pf.const.language.RSC1429}</th>
						</tr>
					</thead>

					<thead class="filtersInfo">
						<tr>
							<td>date</td>
							<td>charging</td>
							<td>level</td>
						</tr>
					</thead>

					<tbody>
				`;

				size = infoUser.length;
				for(let i = 0; i < size; i++) {
					table += `
						<tr>
							<td class="text-truncate" title="${(infoUser[i].date || '-')}">${(infoUser[i].date || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].charging == 1 ? pf.const.language.RSC120 : pf.const.language.RSC121)}">${(infoUser[i].charging == 1 ? pf.const.language.RSC120 : pf.const.language.RSC121)}</td>
							<td class="text-truncate" title="${(infoUser[i].level || '-')}">${(infoUser[i].level || '-')}</td>
						</tr>
					`;
				}

				table += `
					</tbody>
				`;

				columnDefs = [
					{width: '130px', targets: 0}
				];
				break;
			case 'network':
				$('#infoTitle').text(pf.const.language.RSC1394);
				excelTitle = pf.const.language.RSC1394;

				table = `
					<thead>
						<tr>
							<th scope="col" title="${pf.const.language.RSC457}">${pf.const.language.RSC457}</th>
							<th scope="col" title="${pf.const.language.RSC438}">${pf.const.language.RSC438}</th>
						</tr>
					</thead>

					<thead class="filtersInfo">
						<tr>
							<td>date</td>
							<td>type</td>
						</tr>
					</thead>

					<tbody>
				`;

				size = infoUser.length;
				for(let i = 0; i < size; i++) {
					table += `
						<tr>
							<td class="text-truncate" title="${(infoUser[i].date || '-')}">${(infoUser[i].date || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].type || '-')}">${(infoUser[i].type || '-')}</td>
						</tr>
					`;
				}

				table += `
					</tbody>
				`;

				columnDefs = [];
				break;
			case 'apps':
				$('#infoTitle').text(pf.const.language.RSC1465);
				excelTitle = pf.const.language.RSC1465;

				table = `
					<thead>
						<tr>
							<th scope="col" title="${pf.const.language.RSC457}">${pf.const.language.RSC457}</th>
							<th scope="col" title="${pf.const.language.RSC123}">${pf.const.language.RSC123}</th>
							<th scope="col" title="${pf.const.language.RSC1247}">${pf.const.language.RSC1247}</th>
							<th scope="col" title="${pf.const.language.RSC1466}">${pf.const.language.RSC1466}</th>
							<th scope="col" title="${pf.const.language.RSC408}">${pf.const.language.RSC408}</th>
							<th scope="col" title="${pf.const.language.RSC1427}">${pf.const.language.RSC1427}</th>
							<th scope="col" title="${pf.const.language.RSC437}">${pf.const.language.RSC437}</th>
						</tr>
					</thead>

					<thead class="filtersInfo">
						<tr>
							<td>date</td>
							<td>appName</td>
							<td>dataDir</td>
							<td>packageName</td>
							<td>systemApp</td>
							<td>versionName</td>
							<td>versionCode</td>
						</tr>
					</thead>

					<tbody>
				`;

				size = infoUser.length;
				for(let i = 0; i < size; i++) {
					table += `
						<tr>
							<td class="text-truncate" title="${(infoUser[i].date || '-')}">${(infoUser[i].date || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].appName || '-')}">${(infoUser[i].appName || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].dataDir || '-')}">${(infoUser[i].dataDir || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].packageName || '_')}">${(infoUser[i].packageName || '_')}</td>
							<td class="text-truncate" title="${(infoUser[i].systemApp || '-')}">${(infoUser[i].systemApp || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].versionName || '-')}">${(infoUser[i].versionName || '-')}</td>
							<td class="text-truncate" title="${(infoUser[i].versionCode || '-')}">${(infoUser[i].versionCode || '-')}</td>
						</tr>
					`;
				}

				table += `
					</tbody>
				`;

				columnDefs = [
					{width: '130px', targets: 0}
				];
				break;
		}

		$('#tableInfo').html(table);

		// Setup - add a text input to each footer cell
		$('#tableInfo .filtersInfo td').each(function() {
			let title = $(this).text();
			let value = `<input type="text" class="form-control form-control-sm">`;

			if(title == 'icono') {
				value = `<input type="text" class="form-control form-control-sm" disabled>`;
			}

			$(this).html(value);
		});

		let tableInfo = $('#tableInfo').DataTable({
			language: pf.const.language_table,
			retrieve: true,
			scrollY: 300,
			scroller: false,
			paging: true,
			pageLength: 25,
			autoWidth: false,
			columnDefs: columnDefs,
			dom: 'Bfrtip',
			buttons: [{
				extend: 'excelHtml5',
				footer: true,
				text: excelTitle,
				className: 'd-none',
				title: excelTitle,
				customize(xlsx) {
					dataTablesExcelCustomize(xlsx);
				}
			}],
			aaSorting: []
		});

		setTimeout(function() {
			tableInfo.columns.adjust();
		}, 200);

		// Ocultar botones de tabla
		hideTableButtons('tableInfo');

		// Apply the search
		applyTheSearch(tableInfo, 'filtersInfo');

		// Exportar tabla
		$('#exportInfo').off().on('click', function() {
			$('#infoTable .buttons-excel').trigger('click');
		});

		self.tableGPSEvents();

		$('#infoTable').on('draw.dt', function() {
			self.tableGPSEvents();
		});
	},

	tableGPSEvents() {
		// Evento de iframe
		$('.iFrameMap').off().on('click', function(event) {
			event.preventDefault();
			let url = $(this).data('url');
			let name = $(this).text();
			let index = $(this).data('index');

			// Pestaña
			let tab = `
				<li>
					<a id="${index}_link" href="#${index}" class="nav-link perfTabs" data-toggle="tab">
						${name}
					</a>
				</li>
			`;

			// Cuerpo
			let tabContent = `
				<div id="${index}" class="tab-pane fade">
					<iframe id="sIframe" src="${url}&output=embed" class="he-70vh" allowfullscreen></iframe>
				</div>
			`;

			$('#resultTableTabs').append(tab);
			$('#resultTableTabsContent').append(tabContent);

			$('#' + index + '_link').click();
		});
	},

	clickEvents(data) {
		let self = this;

		self.country = data.isocode;
		$('#country').val(data.descripcion);
		$('#country').attr('title', data.descripcion);
		$('#countryDelete').show();
	},

	createMapRoute() {
		let self = this;

		// Anulación de botón de ruta
		$('#routeInfo').prop('disabled', true);

		let apiKey = self.googleMapsAPIKey;

		// Pestaña
		let tab = `
			<li>
				<a id="routeMapLink" href="#routeMap" class="nav-link perfTabs" data-toggle="tab">
					routeMap
				</a>
			</li>
		`;

		// Cuerpo
		let tabContent = `
			<div id="routeMap" class="tab-pane fade">
				<div id="mapRoute" class="he-70vh"></div>
				<script async defer src="https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMapRoute"></script>
			</div>
		`;

		$('#resultTableTabs').append(tab);
		$('#resultTableTabsContent').append(tabContent);

		$('#routeMapLink').click();
	}
};

function initMap() {
	PassiveTrackingController.initMap();
}

function initMapRoute() {
	PassiveTrackingController.initMapRoute();
}