<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Supervisor extends CI_Controller
{
	protected $goalMetricEmployeeCache = array();
	protected $goalMetricAllEmployeesCache = array();
	protected $goalMetricRankingCache = array();
	protected $groupGoalMetricCache = array();
	protected $groupGoalMetricRankingCache = array();

	public function __construct()
	{
		parent::__construct();
		$this->load->model('App_write_model');
		$this->load->model('Gala_read_model');
		$this->load->model('Tasks_model');
		$this->load->library('form_validation');
		$this->load->library('session');
		$this->App_write_model->run_migration();
		$this->Tasks_model->run_migration();

		$public_methods = array('login');
		$current_method = $this->router->fetch_method();
		if (!in_array($current_method, $public_methods, TRUE))
		{
			$this->require_supervisor_login();
		}
	}

	public function index()
	{
		$this->dashboard();
	}

	public function login()
	{
		if ($this->has_supervisor_session_access())
		{
			redirect('dashboard');
		}

		$this->form_validation->set_rules('username', 'Usuario', 'trim|required');
		$this->form_validation->set_rules('password', 'Contrasena', 'required');

		$data = array(
			'error' => '',
			'username' => (string) $this->input->post('username'),
			'continue' => (string) $this->input->get_post('continue'),
			'company_name' => $this->Gala_read_model->get_company_name_for_visor(1),
		);

		if ($this->input->method(TRUE) === 'POST' && $this->form_validation->run())
		{
			$user = $this->Gala_read_model->authenticate_employee(
				(string) $this->input->post('username'),
				(string) $this->input->post('password')
			);

			if ($user && $this->can_employee_access_supervisor((int) $user['person_id']))
			{
				$this->App_write_model->touch_app_user_login($user['person_id'], array(
					'username' => isset($user['username']) ? $user['username'] : '',
					'full_name' => isset($user['full_name']) ? $user['full_name'] : '',
				));

				$this->session->sess_regenerate(TRUE);
				$this->session->set_userdata('visor_auth', array(
					'employee_id' => (int) $user['person_id'],
					'username' => isset($user['username']) ? $user['username'] : '',
					'full_name' => isset($user['full_name']) ? $user['full_name'] : '',
					'email' => isset($user['email']) ? $user['email'] : '',
					'logged_in_at' => date('Y-m-d H:i:s'),
				));

				$continue = trim((string) $this->input->post('continue'));
				if ($continue !== '')
				{
					redirect($continue);
				}

				redirect('dashboard');
			}

			$data['error'] = 'Usuario o contrasena invalida, o acceso no autorizado para Visores.';
		}

		$this->load->view('auth/login', $data);
	}

	public function logout()
	{
		$this->session->unset_userdata('visor_auth');
		$this->session->sess_destroy();
		redirect('login');
	}

	public function global_modulo()
	{
		$this->require_global_module_access();

		$employees = $this->get_active_attendance_employees();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$global_goals = array_values(array_filter($this->App_write_model->get_goal_definitions(TRUE), function ($goal) {
			return empty($goal['employee_mode']) || (string) $goal['employee_mode'] === 'global';
		}));
		$global_rules = array_values(array_filter($this->decorate_task_rules($this->Tasks_model->get_rules(), $employees, $groups), function ($rule) {
			return !empty($rule['assignment_mode']) && (string) $rule['assignment_mode'] === 'global';
		}));
		$period_reference = date('Y-m-d');

		foreach ($global_goals as &$goal)
		{
			$is_active = !empty($goal['is_active']);
			$is_available = $this->goal_is_currently_available($goal, $period_reference);
			$goal['status_label'] = $is_active
				? ($is_available ? 'Activa' : 'Terminada')
				: 'Inactiva';
			$goal['current_value'] = 0.0;
			$goal['target_value'] = 0.0;
			$goal['progress_percent'] = 0;

			if (!$is_active || !$is_available)
			{
				continue;
			}

			list($date_start, $date_end) = $this->get_goal_period_range(
				isset($goal['period_type']) ? $goal['period_type'] : 'daily',
				$goal,
				$period_reference
			);

			$current_value = $this->get_goal_metric_store_total_cached(
				isset($goal['scope_type']) ? $goal['scope_type'] : 'general',
				isset($goal['scope_id']) ? $goal['scope_id'] : null,
				isset($goal['metric_type']) ? $goal['metric_type'] : 'amount',
				$date_start,
				$date_end
			);
			$target_value = $this->resolve_global_goal_target_value($goal, $date_start, $date_end, $period_reference);
			$progress_ratio = $target_value > 0 ? min(1, max(0, $current_value / $target_value)) : 0;

			$goal['current_value'] = (float) $current_value;
			$goal['target_value'] = (float) $target_value;
			$goal['progress_percent'] = max(0, min(100, (int) round($progress_ratio * 100)));
		}
		unset($goal);

		$data = array(
			'page_title' => 'Global',
			'current_tab' => 'global',
			'view' => 'supervisor/global',
			'global_goals' => $global_goals,
			'global_rules' => $global_rules,
			'flash_message' => $this->session->flashdata('flash_message'),
			'flash_error' => $this->session->flashdata('flash_error'),
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function global_meta_nueva()
	{
		$this->require_global_module_access();
		$this->handle_goal_definition_form(null, 'global');
	}

	public function global_meta_editar($goal_id)
	{
		$this->require_global_module_access();
		$this->handle_goal_definition_form((int) $goal_id, 'global');
	}

	public function global_meta_eliminar($goal_id)
	{
		$this->require_global_module_access();
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$goal = $this->App_write_model->get_goal_definition((int) $goal_id);
		if (!$goal || (!empty($goal['employee_mode']) && (string) $goal['employee_mode'] !== 'global'))
		{
			show_404();
		}

		$deleted = $this->App_write_model->delete_goal_definition((int) $goal_id);
		$this->session->set_flashdata($deleted ? 'flash_message' : 'flash_error', $deleted ? 'Meta deshabilitada.' : 'No se pudo deshabilitar la meta.');
		redirect('global');
	}

	public function global_tarea_nueva()
	{
		$this->require_global_module_access();
		$this->handle_tarea_rule_form(null, 'global');
	}

	public function global_tarea_editar($rule_id)
	{
		$this->require_global_module_access();
		$this->handle_tarea_rule_form((int) $rule_id, 'global');
	}

	public function global_tarea_eliminar($rule_id)
	{
		$this->require_global_module_access();
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$rule = $this->Tasks_model->get_rule((int) $rule_id);
		if (!$rule || (string) $rule['assignment_mode'] !== 'global')
		{
			show_404();
		}

		$result = $this->Tasks_model->delete_rule((int) $rule_id);
		$this->session->set_flashdata(!empty($result['success']) ? 'flash_message' : 'flash_error', !empty($result['message']) ? $result['message'] : 'No se pudo eliminar la tarea.');
		redirect('global');
	}

	public function dashboard()
	{
		$this->require_admin_module_permission('supervisor.dashboard.view');
		$filters = array(
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
			'period' => trim((string)$this->input->get('period')),
		);
		$data = $this->build_dashboard_data($filters);
		$this->hydrate_theme_settings($data);
		$data['page_title'] = 'Dashboard';
		$data['current_tab'] = 'dashboard';
		$data['view'] = 'supervisor/dashboard';
		$this->load->view('layouts/supervisor', $data);
	}

	public function attendance_toggle()
	{
		if (!$this->has_admin_module_permission('supervisor.asistencia.view') && !$this->has_admin_module_permission('supervisor.dashboard.view')) { show_error('No tienes acceso a este modulo.', 403, 'Acceso denegado'); }
		$employee_id = (int)$this->input->post('employee_id');
		$is_present = $this->input->post('is_present') ? TRUE : FALSE;
		$note = $this->input->post('note', TRUE);
		$timezone = new DateTimeZone('America/Caracas');
		$today = new DateTime('now', $timezone);
		$actor = $this->get_supervisor_actor();
		$success = $this->App_write_model->save_attendance($today->format('Y-m-d'), $employee_id, $is_present, $actor['employee_id'], $note, $actor['name']);

		$this->output
			->set_content_type('application/json')
			->set_output(json_encode(array(
				'success' => (bool)$success,
				'employee_id' => $employee_id,
				'is_present' => $is_present ? 1 : 0,
				'note' => $note !== null ? trim((string)$note) : '',
			)));
	}

	public function dashboard_global_task_action()
	{
		if (!$this->has_admin_module_permission('supervisor.global.view') && !$this->has_admin_module_permission('supervisor.dashboard.view')) { show_error('No tienes acceso a este modulo.', 403, 'Acceso denegado'); }
		$actor = $this->get_supervisor_actor();
		if (empty($actor['employee_id']))
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'No encontramos al usuario actual.',
			), 403);
		}

		$assignment_id = (int)$this->input->post('assignment_id');
		$action = trim((string)$this->input->post('action_type'));
		$assignment = $this->Tasks_model->get_assignment_for_admin($assignment_id);
		if (!$assignment)
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'No encontramos la tarea global.',
			), 404);
		}

		$rule = !empty($assignment['rule_id']) ? $this->Tasks_model->get_rule((int)$assignment['rule_id']) : null;
		if (!$rule || empty($rule['assignment_mode']) || (string)$rule['assignment_mode'] !== 'global')
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'La tarea indicada no es global.',
			), 403);
		}

		if ((int)(isset($assignment['employee_id']) ? $assignment['employee_id'] : 0) !== (int)$actor['employee_id'])
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'Esta tarea global no esta asignada al usuario actual.',
			), 403);
		}

		if ($action === 'reactivate')
		{
			$result = $this->Tasks_model->reactivate_assignment($assignment_id);
		}
		else
		{
			$result = $this->Tasks_model->complete_assignment($assignment_id, (int)$actor['employee_id'], '', TRUE);
		}

		if (empty($result['success']))
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => !empty($result['message']) ? $result['message'] : 'No pudimos actualizar la tarea global.',
			), 422);
		}

		$updated_assignment = $this->Tasks_model->get_assignment_for_admin($assignment_id);
		return $this->respond_json(array(
			'success' => TRUE,
			'message' => !empty($result['message']) ? $result['message'] : 'Tarea global actualizada.',
			'assignment' => $this->build_task_assignment_payload($updated_assignment),
		));
	}

	public function asistencia()
	{
		$this->require_admin_module_permission('supervisor.asistencia.view');
		$timezone = new DateTimeZone('America/Caracas');
		$today = new DateTime('now', $timezone);
		$can_view_attendance_history = $this->has_admin_module_permission('supervisor.asistencia.history.view');
		$selected_month = trim((string)$this->input->get('month'));
		if (!$can_view_attendance_history || $selected_month === '' || !preg_match('/^\d{4}\-\d{2}$/', $selected_month))
		{
			$selected_month = $today->format('Y-m');
		}

		$data = $this->build_attendance_month_summary_data($selected_month);
		$data['can_view_attendance_history'] = $can_view_attendance_history;
		$data['current_tab'] = 'asistencia';
		$data['view'] = 'supervisor/asistencia';
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function asistencia_detalle($employee_id = 0)
	{
		$this->require_admin_module_permission('supervisor.asistencia.view');
		$employee_id = (int)$employee_id;
		$timezone = new DateTimeZone('America/Caracas');
		$today = new DateTime('now', $timezone);
		$can_view_attendance_history = $this->has_admin_module_permission('supervisor.asistencia.history.view');
		$selected_month = trim((string)$this->input->get('month'));
		if (!$can_view_attendance_history || $selected_month === '' || !preg_match('/^\d{4}\-\d{2}$/', $selected_month))
		{
			$selected_month = $today->format('Y-m');
		}

		$data = $this->build_attendance_employee_month_detail_data($employee_id, $selected_month);
		$data['can_view_attendance_history'] = $can_view_attendance_history;
		{
			show_404();
			return;
		}

		$data['page_title'] = 'Detalle de asistencia';
		$data['current_tab'] = 'asistencia';
		$data['view'] = 'supervisor/asistencia_detalle';
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function asistencia_guardar()
	{
		$attendance_date = trim((string)$this->input->post('attendance_date'));
		$employee_id = (int)$this->input->post('employee_id');
		$is_present = $this->input->post('is_present') ? TRUE : FALSE;
		$note = trim((string)$this->input->post('note', TRUE));
		$actor = $this->get_supervisor_actor();
		$expects_json = $this->request_wants_json();

		if ($attendance_date === '' || !preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $attendance_date) || $employee_id <= 0)
		{
			if ($expects_json)
			{
				return $this->respond_json(array(
					'success' => FALSE,
					'message' => 'Datos de asistencia invalidos.',
				), 422);
			}

			$this->session->set_flashdata('flash_error', 'Datos de asistencia invalidos.');
			redirect('asistencia?date='.rawurlencode($attendance_date));
			return;
		}

		$success = $this->App_write_model->save_attendance($attendance_date, $employee_id, $is_present, $actor['employee_id'], $note, $actor['name']);
		$attendance_index = $this->App_write_model->get_attendance_for_date($attendance_date);
		$row = isset($attendance_index[$employee_id]) ? $attendance_index[$employee_id] : null;
		$all_employees = $this->Gala_read_model->get_employees_for_visor();
		$active_employees = array_values(array_filter($all_employees, function ($employee) {
			return !empty($employee['is_active_in_gala']);
		}));
		$decorated_row = $row ? $this->decorate_attendance_row($employee_id, $row, $all_employees) : null;

		if ($expects_json)
		{
			$summary = $this->build_attendance_summary($active_employees, $attendance_index);
			return $this->respond_json(array(
				'success' => (bool)$success,
				'message' => $success ? 'Asistencia guardada.' : 'No se pudo guardar la asistencia.',
				'row' => $decorated_row,
				'summary' => $summary,
			), $success ? 200 : 422);
		}

		$this->session->set_flashdata($success ? 'flash_message' : 'flash_error', $success ? 'Asistencia guardada.' : 'No se pudo guardar la asistencia.');
		redirect('asistencia?date='.rawurlencode($attendance_date));
	}

	protected function request_wants_json()
	{
		if ($this->input->is_ajax_request())
		{
			return TRUE;
		}

		$accept = strtolower((string) $this->input->get_request_header('Accept'));
		return strpos($accept, 'application/json') !== FALSE;
	}

	protected function respond_json($payload, $status_code = 200)
	{
		$this->output
			->set_status_header((int) $status_code)
			->set_content_type('application/json')
			->set_output(json_encode($payload));
	}

	protected function get_task_status_meta($status)
	{
		$status = (string) $status;
		$meta = array(
			'status' => 'pending',
			'label' => 'Pendiente',
			'class' => 'warning',
			'action_type' => 'complete',
			'action_label' => 'Completar',
		);

		if ($status === 'overdue')
		{
			$meta['status'] = 'overdue';
			$meta['label'] = 'Vencida';
			$meta['class'] = 'danger';
			return $meta;
		}

		if ($status === 'in_progress')
		{
			$meta['status'] = 'in_progress';
			$meta['label'] = 'En progreso';
			$meta['class'] = 'warning';
			return $meta;
		}

		if ($status === 'completed')
		{
			$meta['status'] = 'completed';
			$meta['label'] = 'Completada';
			$meta['class'] = 'success';
			$meta['action_type'] = 'reactivate';
			$meta['action_label'] = 'Reactivar';
		}

		return $meta;
	}

	protected function prepare_task_assignments($assignments, $employees, $groups, $filters = array())
	{
		$filters = is_array($filters) ? $filters : array();
		$q = isset($filters['q']) ? trim((string) $filters['q']) : '';
		$can_override_verified_tasks = $this->has_task_verification_override_access();


		$employee_index = array();
		foreach ((array) $employees as $row)
		{
			$employee_index[(int) $row['person_id']] = $row;
		}

		$group_index = array();
		foreach ((array) $groups as $group)
		{
			$group_index[(int) $group['id']] = $group;
		}

		$prepared = array();
		foreach ((array) $assignments as $assignment)
		{
			$status_value = isset($assignment['status']) ? (string) $assignment['status'] : 'pending';
			$completed_at_value = isset($assignment['completed_at']) ? trim((string) $assignment['completed_at']) : '';
			$due_date_value = isset($assignment['due_date']) ? trim((string) $assignment['due_date']) : '';
			$today_key = date('Y-m-d');
			if ($status_value === 'completed' && $due_date_value !== '' && $due_date_value < $today_key)
			{
				$completed_date_key = $completed_at_value !== '' ? substr($completed_at_value, 0, 10) : '';
				if ($completed_date_key === '' || $completed_date_key < $today_key)
				{
					continue;
				}
			}

			$employee_id = isset($assignment['employee_id']) ? (int) $assignment['employee_id'] : 0;
			$group_id = isset($assignment['rule_group_id']) ? (int) $assignment['rule_group_id'] : 0;
			$employee_name = isset($employee_index[$employee_id]['full_name']) ? $employee_index[$employee_id]['full_name'] : 'Empleado';
			$group_name = $group_id > 0 && isset($group_index[$group_id]['name']) ? $group_index[$group_id]['name'] : '';

			if ($q !== '')
			{
				$haystack = $employee_name.' '.(isset($assignment['title']) ? $assignment['title'] : '').' '.$group_name;
				if (stripos($haystack, $q) === FALSE)
				{
					continue;
				}
			}

			$status_meta = $this->get_task_status_meta(isset($assignment['status']) ? $assignment['status'] : 'pending');
			$is_verified = !empty($assignment['verified_at']);
			$verified_by_name = !empty($assignment['verified_by_name']) ? trim((string) $assignment['verified_by_name']) : '';
			$verified_at_label = !empty($assignment['verified_at']) ? date('d/m/Y H:i', strtotime($assignment['verified_at'])) : '';
			$assignment['employee_name'] = $employee_name;
			$assignment['group_name'] = $group_name;
			$assignment['status_label'] = $status_meta['label'];
			$assignment['status_class'] = $status_meta['class'];
			$assignment['action_type'] = $status_meta['action_type'];
			$assignment['action_label'] = $status_meta['action_label'];
			$assignment['assigned_date_label'] = !empty($assignment['assigned_date']) ? date('d/m/Y', strtotime($assignment['assigned_date'])) : '-';
			$assignment['due_date_label'] = !empty($assignment['due_date']) ? date('d/m/Y', strtotime($assignment['due_date'])) : '-';
			$assignment['detail_label'] = !empty($assignment['description']) ? (string) $assignment['description'] : 'Sin detalle';
			$assignment['is_verified'] = $is_verified;
			$assignment['can_verify'] = isset($assignment['status']) && (string) $assignment['status'] === 'completed' && !$is_verified;
			$assignment['can_reactivate'] = isset($assignment['status']) && (string) $assignment['status'] === 'completed' && (!$is_verified || $can_override_verified_tasks);
			$assignment['secondary_action_type'] = '';
			$assignment['secondary_action_label'] = '';
			if (isset($assignment['status']) && (string) $assignment['status'] === 'completed')
			{
				if ($assignment['can_reactivate'])
				{
					$assignment['action_type'] = 'reactivate';
					$assignment['action_label'] = 'Reactivar';
				}
				else
				{
					$assignment['action_type'] = '';
					$assignment['action_label'] = '';
				}

				if ($assignment['can_verify'])
				{
					$assignment['secondary_action_type'] = 'verify';
					$assignment['secondary_action_label'] = 'Verificar';
				}
			}
			$assignment['verified_by_label'] = $is_verified
				? ($verified_by_name !== '' ? 'Verificada por '.$verified_by_name : 'Tarea verificada')
				: '';
			$assignment['verified_at_label'] = $verified_at_label;
			$prepared[] = $assignment;
		}

		return $prepared;
	}

	protected function index_rows($rows, $key)
	{
		$index = array();
		foreach ((array) $rows as $row)
		{
			if (isset($row[$key]))
			{
				$index[(int) $row[$key]] = $row;
			}
		}

		return $index;
	}

	protected function decorate_task_rules($rules, $employees, $groups)
	{
		$employees_index = $this->index_rows($employees, 'person_id');
		$groups_index = $this->index_rows($groups, 'id');
		$decorated = array();
		foreach ((array) $rules as $rule)
		{
			$rule['template_name'] = !empty($rule['template']['name']) ? $rule['template']['name'] : 'Plantilla';
			$rule['display_name'] = !empty($rule['title']) ? $rule['title'] : $rule['template_name'];
			$rule['target_label'] = 'Sin destino';
			if (isset($rule['assignment_mode']) && $rule['assignment_mode'] === 'global')
			{
				$rule['target_label'] = 'Todos los empleados activos';
			}
			elseif (isset($rule['assignment_mode']) && $rule['assignment_mode'] === 'employee' && !empty($rule['employee_id']) && isset($employees_index[(int) $rule['employee_id']]))
			{
				$rule['target_label'] = 'Empleado: '.$employees_index[(int) $rule['employee_id']]['full_name'];
			}
			elseif (!empty($rule['group_id']) && isset($groups_index[(int) $rule['group_id']]))
			{
				$rule['target_label'] = 'Grupo: '.$groups_index[(int) $rule['group_id']]['name'];
			}

			$rule['recurrence_label'] = $this->build_task_recurrence_label($rule);
			$rule['next_due_date'] = $this->Tasks_model->get_next_due_date_for_rule($rule);
			$rule['next_due_date_label'] = !empty($rule['next_due_date']) ? date('d/m/Y', strtotime($rule['next_due_date'])) : 'Sin fecha';
			$decorated[] = $rule;
		}

		return $decorated;
	}

	protected function build_task_recurrence_label($rule)
	{
		switch (isset($rule['recurrence_type']) ? $rule['recurrence_type'] : 'one_time')
		{
			case 'daily':
				return 'Diaria';
			case 'weekly':
				return 'Semanal';
			case 'monthly':
				return 'Mensual';
			case 'quarterly':
				return 'Trimestral';
			case 'semiannual':
				return 'Semestral';
			case 'annual':
				return 'Anual';
			case 'special_date':
				$label = !empty($rule['special_date']['name']) ? 'Fecha especial: '.$rule['special_date']['name'] : 'Fecha especial';
				if (!empty($rule['special_date_lead_days']))
				{
					$label .= ' ('.(int) $rule['special_date_lead_days'].' dias antes)';
				}
				return $label;
			case 'one_time':
			default:
				return !empty($rule['one_time_date']) ? 'Unica: '.$rule['one_time_date'] : 'Unica';
		}
	}

	protected function get_supervisor_actor()
	{
		$session_user = $this->session->userdata('visor_auth');
		$session_user = is_array($session_user) ? $session_user : array();

		$employee_id = !empty($session_user['employee_id']) ? (int) $session_user['employee_id'] : 0;
		$name = '';

		if (!empty($session_user['full_name']))
		{
			$name = trim((string) $session_user['full_name']);
		}
		elseif (!empty($session_user['name']))
		{
			$name = trim((string) $session_user['name']);
		}
		elseif (!empty($session_user['username']))
		{
			$name = trim((string) $session_user['username']);
		}

		if ($employee_id > 0 && $name === '')
		{
			$employees = $this->Gala_read_model->get_employees_for_visor();
			foreach ((array) $employees as $employee)
			{
				if ((int) $employee['person_id'] === $employee_id)
				{
					$name = !empty($employee['full_name'])
						? trim((string) $employee['full_name'])
						: (!empty($employee['username']) ? trim((string) $employee['username']) : '');
					break;
				}
			}
		}

		if ($name === '')
		{
			$name = 'Supervisor';
		}

		return array(
			'employee_id' => $employee_id,
			'name' => $name,
		);
	}

	protected function require_supervisor_login()
	{
		if (!$this->has_supervisor_session_access())
		{
			$continue = current_url();
			$query_string = $this->input->server('QUERY_STRING');
			if ($query_string)
			{
				$continue .= '?'.$query_string;
			}

			redirect('login?continue='.rawurlencode($continue));
		}
	}

	protected function has_supervisor_session_access()
	{
		$session_user = $this->session->userdata('visor_auth');
		$session_user = is_array($session_user) ? $session_user : array();
		$employee_id = !empty($session_user['employee_id']) ? (int) $session_user['employee_id'] : 0;
		if ($employee_id <= 0)
		{
			return FALSE;
		}

		return $this->can_employee_access_supervisor($employee_id);
	}

	protected function can_employee_access_supervisor($employee_id)
	{
		$employee_id = (int) $employee_id;
		if ($employee_id <= 0)
		{
			return FALSE;
		}

		if (!$this->App_write_model->can_app_user_login($employee_id))
		{
			return FALSE;
		}

		$app_user = $this->App_write_model->get_app_user($employee_id);
		return $app_user && !empty($app_user['is_admin']);
	}

	protected function require_global_module_access()
	{
		$this->require_supervisor_login();
		if ($this->has_global_module_access())
		{
			return;
		}

		show_error('No tienes acceso al modulo Global.', 403, 'Acceso denegado');
	}


	protected function get_current_admin_permission_state()
	{
		$session_user = $this->session->userdata('visor_auth');
		$session_user = is_array($session_user) ? $session_user : array();
		$employee_id = !empty($session_user['employee_id']) ? (int)$session_user['employee_id'] : 0;
		if ($employee_id <= 0)
		{
			return array();
		}

		$app_user = $this->App_write_model->get_app_user($employee_id);
		if (!$app_user || empty($app_user['is_admin']))
		{
			return array();
		}

		return $this->resolve_permission_state(
			!empty($app_user['admin_permissions_json']) ? $this->App_write_model->get_app_user_admin_permissions($employee_id) : array(),
			$this->get_default_admin_permissions(),
			$this->get_admin_permission_labels()
		);
	}

	protected function has_admin_module_permission($key)
	{
		$permissions = $this->get_current_admin_permission_state();
		return array_key_exists($key, $permissions) ? !empty($permissions[$key]) : FALSE;
	}

	protected function require_admin_module_permission($key, $message = 'No tienes acceso a este modulo.')
	{
		$this->require_supervisor_login();
		if (!$this->has_admin_module_permission($key))
		{
			show_error($message, 403, 'Acceso denegado');
		}
	}

	protected function get_supervisor_allowed_tabs()
	{
		$items = array(
			'dashboard' => array('label' => 'Dashboard', 'route' => 'dashboard', 'permission' => 'supervisor.dashboard.view'),
			'asistencia' => array('label' => 'Asistencia', 'route' => 'asistencia', 'permission' => 'supervisor.asistencia.view'),
			'metas' => array('label' => 'Metas', 'route' => 'metas', 'permission' => 'supervisor.metas.view'),
			'tareas' => array('label' => 'Tareas', 'route' => 'tareas', 'permission' => 'supervisor.tareas.view'),
			'grupos' => array('label' => 'Grupos', 'route' => 'grupos', 'permission' => 'supervisor.grupos.view'),
			'empleados' => array('label' => 'Empleados', 'route' => 'empleados', 'permission' => 'supervisor.empleados.view'),
			'notificaciones' => array('label' => 'Notificaciones', 'route' => 'notificaciones', 'permission' => 'supervisor.notificaciones.view'),
			'global' => array('label' => 'Global', 'route' => 'global', 'permission' => 'supervisor.global.view'),
			'configuracion' => array('label' => 'Configuracion', 'route' => 'configuracion', 'permission' => 'supervisor.configuracion.view'),
		);

		$allowed = array();
		foreach ($items as $key => $item)
		{
			if ($this->has_admin_module_permission($item['permission']))
			{
				$allowed[$key] = array(
					'label' => $item['label'],
					'route' => $item['route'],
				);
			}
		}

		return $allowed;
	}
	protected function has_global_module_access()
	{
		$session_user = $this->session->userdata('visor_auth');
		$session_user = is_array($session_user) ? $session_user : array();
		$employee_id = !empty($session_user['employee_id']) ? (int) $session_user['employee_id'] : 0;
		if ($employee_id <= 0)
		{
			return FALSE;
		}

		$app_user = $this->App_write_model->get_app_user($employee_id);
		if ($app_user && !empty($app_user['is_admin']))
		{
			return TRUE;
		}

		$permissions = $this->App_write_model->get_app_user_permissions($employee_id);
		$admin_permissions = array(
			'configuracion.view',
			'configuracion.descuentos.view',
			'configuracion.grupos.view',
			'configuracion.metas.view',
			'configuracion.tareas.view',
			'configuracion.notificaciones.view',
			'configuracion.ventas.view',
			'configuracion.empleados.view',
		);

		foreach ($admin_permissions as $permission_key)
		{
			if (!empty($permissions[$permission_key]))
			{
				return TRUE;
			}
		}

		return FALSE;
	}
	protected function has_task_verification_override_access()
	{
		return $this->has_admin_module_permission('supervisor.tareas.verified.reactivate');
	}

	protected function is_employee_enabled_in_visor($app_user)
	{
		return !$app_user || !isset($app_user['is_enabled']) || (int)$app_user['is_enabled'] === 1;
	}

	protected function is_employee_in_statistics($app_user)
	{
		return !$app_user || !isset($app_user['include_in_statistics']) || (int)$app_user['include_in_statistics'] === 1;
	}

	protected function get_statistics_employees($require_active_in_gala = TRUE)
	{
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$app_users = $this->App_write_model->get_all_app_users_index();
		return array_values(array_filter($employees, function ($row) use ($app_users, $require_active_in_gala) {
			$employee_id = isset($row['person_id']) ? (int)$row['person_id'] : 0;
			if ($employee_id <= 0)
			{
				return FALSE;
			}
			if ($require_active_in_gala && empty($row['is_active_in_gala']))
			{
				return FALSE;
			}

			$app_user = isset($app_users[$employee_id]) ? $app_users[$employee_id] : null;
			return $this->is_employee_enabled_in_visor($app_user) && $this->is_employee_in_statistics($app_user);
		}));
	}

	protected function get_active_attendance_employees()
	{
		return $this->get_statistics_employees(TRUE);
	}

	protected function build_attendance_month_summary_data($selected_month)
	{
		$employees = $this->get_active_attendance_employees();
		$window = $this->get_attendance_month_window($selected_month);
		$records = $this->App_write_model->get_attendance_between($window['month_start']->format('Y-m-d'), $window['month_end']->format('Y-m-d'));
		$attendance_index = $this->index_attendance_records_by_date_employee($records);
		$present_days_index = $this->get_attendance_present_days_index($attendance_index);
		$rows = $this->build_attendance_month_summary_rows($employees, $attendance_index, $present_days_index, $window);
		$summary = array(
			'total_employees' => count($rows),
			'total_present' => 0,
			'total_absent' => 0,
			'total_free' => 0,
			'total_notes' => 0,
		);

		foreach ($rows as $row)
		{
			$summary['total_present'] += isset($row['present_count']) ? (int)$row['present_count'] : 0;
			$summary['total_absent'] += isset($row['absent_count']) ? (int)$row['absent_count'] : 0;
			$summary['total_free'] += isset($row['free_count']) ? (int)$row['free_count'] : 0;
			$summary['total_notes'] += isset($row['notes_count']) ? (int)$row['notes_count'] : 0;
		}

		return array(
			'selected_month' => $window['month_key'],
			'selected_month_label' => $window['month_label'],
			'attendance_summary_rows' => $rows,
			'attendance_summary' => $summary,
			'flash_message' => $this->session->flashdata('flash_message'),
			'flash_error' => $this->session->flashdata('flash_error'),
		);
	}

	protected function build_attendance_employee_month_detail_data($employee_id, $selected_month)
	{
		$employee_id = (int)$employee_id;
		$employees = $this->get_active_attendance_employees();
		$employee = null;
		foreach ($employees as $row)
		{
			if ((int)(isset($row['person_id']) ? $row['person_id'] : 0) === $employee_id)
			{
				$employee = $row;
				break;
			}
		}

		if (!$employee)
		{
			return array('employee' => null);
		}

		$window = $this->get_attendance_month_window($selected_month);
		$records = $this->App_write_model->get_attendance_between($window['month_start']->format('Y-m-d'), $window['month_end']->format('Y-m-d'));
		$attendance_index = $this->index_attendance_records_by_date_employee($records);
		$present_days_index = $this->get_attendance_present_days_index($attendance_index);
		$detail_rows = $this->build_attendance_employee_detail_rows($employee_id, $employees, $attendance_index, $present_days_index, $window);
		$summary = array(
			'present' => 0,
			'absent' => 0,
			'free' => 0,
			'notes' => 0,
			'future' => 0,
		);

		foreach ($detail_rows as $row)
		{
			if ($row['status_key'] === 'present')
			{
				$summary['present']++;
			}
			elseif ($row['status_key'] === 'absent')
			{
				$summary['absent']++;
			}
			elseif ($row['status_key'] === 'free')
			{
				$summary['free']++;
			}
			elseif ($row['status_key'] === 'future')
			{
				$summary['future']++;
			}

			if (!empty($row['note']))
			{
				$summary['notes']++;
			}
		}

		return array(
			'employee' => $employee,
			'selected_month' => $window['month_key'],
			'selected_month_label' => $window['month_label'],
			'attendance_detail_rows' => $detail_rows,
			'attendance_detail_summary' => $summary,
			'flash_message' => $this->session->flashdata('flash_message'),
			'flash_error' => $this->session->flashdata('flash_error'),
		);
	}
	protected function get_attendance_month_window($month_key)
	{
		$timezone = new DateTimeZone('America/Caracas');
		$month_key = (string)$month_key;
		if ($month_key === '' || !preg_match('/^\d{4}\-\d{2}$/', $month_key))
		{
			$month_key = date('Y-m');
		}

		$month_start = DateTime::createFromFormat('Y-m-d H:i:s', $month_key.'-01 00:00:00', $timezone);
		if (!$month_start)
		{
			$month_start = new DateTime(date('Y-m-01 00:00:00'), $timezone);
		}
		$month_end = clone $month_start;
		$month_end->modify('last day of this month')->setTime(0, 0, 0);
		$today = new DateTime('today', $timezone);
		$considered_end = clone $month_end;
		if ($today < $considered_end)
		{
			$considered_end = clone $today;
		}

		return array(
			'month_key' => $month_start->format('Y-m'),
			'month_label' => $month_start->format('F Y'),
			'month_start' => $month_start,
			'month_end' => $month_end,
			'considered_end' => $considered_end,
			'today_key' => $today->format('Y-m-d'),
		);
	}

	protected function index_attendance_records_by_date_employee($records)
	{
		$index = array();
		foreach ((array)$records as $record)
		{
			$date_key = isset($record['attendance_date']) ? (string)$record['attendance_date'] : '';
			$employee_id = isset($record['employee_id']) ? (int)$record['employee_id'] : 0;
			if ($date_key === '' || $employee_id <= 0)
			{
				continue;
			}

			if (!isset($index[$date_key]))
			{
				$index[$date_key] = array();
			}
			$index[$date_key][$employee_id] = $record;
		}

		return $index;
	}

	protected function get_attendance_present_days_index($attendance_index)
	{
		$days = array();
		foreach ((array)$attendance_index as $date_key => $rows)
		{
			foreach ((array)$rows as $row)
			{
				if (!empty($row['is_present']))
				{
					$days[(string)$date_key] = TRUE;
					break;
				}
			}
		}

		return $days;
	}

	protected function build_attendance_month_summary_rows($employees, $attendance_index, $present_days_index, $window)
	{
		$rows = array();
		foreach ((array)$employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}

			$summary_row = array(
				'employee_id' => $employee_id,
				'employee_name' => !empty($employee['full_name']) ? (string)$employee['full_name'] : 'Empleado',
				'employee_username' => !empty($employee['username']) ? (string)$employee['username'] : '',
				'present_count' => 0,
				'absent_count' => 0,
				'free_count' => 0,
				'notes_count' => 0,
			);

			$current = clone $window['month_start'];
			while ($current <= $window['considered_end'])
			{
				$date_key = $current->format('Y-m-d');
				$record = isset($attendance_index[$date_key][$employee_id]) ? $attendance_index[$date_key][$employee_id] : null;
				if ($record)
				{
					if (!empty($record['is_present']))
					{
						$summary_row['present_count']++;
					}
					else
					{
						$summary_row['absent_count']++;
					}

					if (!empty($record['note']))
					{
						$summary_row['notes_count']++;
					}
				}
				elseif (!empty($present_days_index[$date_key]))
				{
					$summary_row['absent_count']++;
				}
				else
				{
					$summary_row['free_count']++;
				}

				$current->modify('+1 day');
			}

			$rows[] = $summary_row;
		}

		usort($rows, function ($left, $right) {
			$left_absent = isset($left['absent_count']) ? (int)$left['absent_count'] : 0;
			$right_absent = isset($right['absent_count']) ? (int)$right['absent_count'] : 0;
			if ($left_absent !== $right_absent)
			{
				return $left_absent > $right_absent ? -1 : 1;
			}

			return strcasecmp((string)$left['employee_name'], (string)$right['employee_name']);
		});

		return $rows;
	}

	protected function build_attendance_employee_detail_rows($employee_id, $employees, $attendance_index, $present_days_index, $window)
	{
		$rows = array();
		$current = clone $window['month_start'];
		while ($current <= $window['month_end'])
		{
			$date_key = $current->format('Y-m-d');
			$record = isset($attendance_index[$date_key][$employee_id]) ? $attendance_index[$date_key][$employee_id] : array();
			$decorated = $this->decorate_attendance_row($employee_id, $record, $employees);
			if ($date_key > $window['today_key'] && empty($record))
			{
				$decorated['status_key'] = 'future';
				$decorated['status_label'] = 'Pendiente';
				$decorated['status_class'] = 'warning';
			}
			elseif (empty($record) && empty($present_days_index[$date_key]))
			{
				$decorated['status_key'] = 'free';
				$decorated['status_label'] = 'Libre';
				$decorated['status_class'] = 'note';
			}
			elseif (empty($record))
			{
				$decorated['status_key'] = 'absent';
				$decorated['status_label'] = 'Faltante';
				$decorated['status_class'] = 'danger';
			}

			$decorated['attendance_date'] = $date_key;
			$decorated['attendance_date_label'] = $current->format('d/m/Y');
			$decorated['attendance_day_label'] = $this->get_attendance_weekday_label($current);
			$rows[] = $decorated;
			$current->modify('+1 day');
		}

		return $rows;
	}
	protected function get_attendance_weekday_label(DateTime $date)
	{
		$labels = array(
			'Monday' => 'Lunes',
			'Tuesday' => 'Martes',
			'Wednesday' => 'Miercoles',
			'Thursday' => 'Jueves',
			'Friday' => 'Viernes',
			'Saturday' => 'Sabado',
			'Sunday' => 'Domingo',
		);
		$key = $date->format('l');
		return isset($labels[$key]) ? $labels[$key] : $key;
	}

	protected function build_attendance_summary($employees, $attendance_index)
	{
		$summary = array(
			'total' => 0,
			'present' => 0,
			'absent' => 0,
			'unmarked' => 0,
		);

		foreach ((array)$employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}

			$summary['total']++;
			if (!isset($attendance_index[$employee_id]))
			{
				$summary['unmarked']++;
				continue;
			}

			if (!empty($attendance_index[$employee_id]['is_present']))
			{
				$summary['present']++;
			}
			else
			{
				$summary['absent']++;
			}
		}

		return $summary;
	}

	protected function decorate_attendance_row($employee_id, $attendance_row, $employees)
	{
		$employee_id = (int)$employee_id;
		$attendance_row = is_array($attendance_row) ? $attendance_row : array();
		$employee_name = 'Empleado';
		$employee_username = '';
		foreach ((array)$employees as $employee)
		{
			if ((int)(isset($employee['person_id']) ? $employee['person_id'] : 0) !== $employee_id)
			{
				continue;
			}

			$employee_name = !empty($employee['full_name']) ? (string)$employee['full_name'] : 'Empleado';
			$employee_username = !empty($employee['username']) ? (string)$employee['username'] : '';
			break;
		}

		$has_record = !empty($attendance_row);
		$is_present = $has_record ? !empty($attendance_row['is_present']) : null;
		$status_key = $has_record ? ($is_present ? 'present' : 'absent') : 'unmarked';
		$status_label = $status_key === 'present' ? 'Asistio' : 'Falto';
		$status_class = $status_key === 'present' ? 'success' : 'danger';
		$status_rank = $status_key === 'absent' ? 0 : 1;
		$marked_by = '';
		if (!empty($attendance_row['marked_by_name']))
		{
			$marked_by = (string)$attendance_row['marked_by_name'];
		}
		elseif (!empty($attendance_row['marked_by_employee_id']))
		{
			$marked_by_id = (int)$attendance_row['marked_by_employee_id'];
			foreach ((array)$employees as $employee)
			{
				if ((int)(isset($employee['person_id']) ? $employee['person_id'] : 0) === $marked_by_id)
				{
					$marked_by = !empty($employee['full_name']) ? (string)$employee['full_name'] : '';
					break;
				}
			}
		}

		return array(
			'employee_id' => $employee_id,
			'employee_name' => $employee_name,
			'employee_username' => $employee_username,
			'status_key' => $status_key,
			'status_label' => $status_label,
			'status_class' => $status_class,
			'status_rank' => $status_rank,
			'is_present' => $is_present,
			'note' => isset($attendance_row['note']) ? (string)$attendance_row['note'] : '',
			'attendance_date' => isset($attendance_row['attendance_date']) ? (string)$attendance_row['attendance_date'] : '',
			'updated_at' => isset($attendance_row['updated_at']) ? (string)$attendance_row['updated_at'] : '',
			'updated_at_label' => !empty($attendance_row['updated_at']) ? date('d/m/Y H:i', strtotime($attendance_row['updated_at'])) : '',
			'marked_by_label' => $marked_by,
		);
	}

	protected function decorate_task_assignment($assignment)
	{
		if (!is_array($assignment))
		{
			return $assignment;
		}

		$status_meta = $this->get_task_status_meta(isset($assignment['status']) ? $assignment['status'] : 'pending');
		$assignment['status_label'] = $status_meta['label'];
		$assignment['status_class'] = $status_meta['class'];
		$assignment['action_type'] = $status_meta['action_type'];
		$assignment['action_label'] = $status_meta['action_label'];
		$assignment['assigned_date_label'] = !empty($assignment['assigned_date']) ? date('d/m/Y', strtotime($assignment['assigned_date'])) : '-';
		$assignment['due_date_label'] = !empty($assignment['due_date']) ? date('d/m/Y', strtotime($assignment['due_date'])) : '-';
		return $assignment;
	}

	protected function build_task_assignment_payload($assignment)
	{
		if (!is_array($assignment) || empty($assignment))
		{
			return array();
		}

		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$prepared = $this->prepare_task_assignments(array($assignment), $employees, $groups);
		if (!empty($prepared[0]))
		{
			return $prepared[0];
		}

		$assignment = $this->decorate_task_assignment($assignment);
		return array(
			'id' => isset($assignment['id']) ? (int) $assignment['id'] : 0,
			'status' => isset($assignment['status']) ? (string) $assignment['status'] : 'pending',
			'status_label' => isset($assignment['status_label']) ? (string) $assignment['status_label'] : 'Pendiente',
			'status_class' => isset($assignment['status_class']) ? (string) $assignment['status_class'] : 'warning',
			'action_type' => isset($assignment['action_type']) ? (string) $assignment['action_type'] : 'complete',
			'action_label' => isset($assignment['action_label']) ? (string) $assignment['action_label'] : 'Completar',
			'secondary_action_type' => '',
			'secondary_action_label' => '',
			'title' => isset($assignment['title']) ? (string) $assignment['title'] : '',
			'description' => isset($assignment['description']) ? (string) $assignment['description'] : '',
			'detail_label' => !empty($assignment['description']) ? (string) $assignment['description'] : 'Sin detalle',
			'assigned_date_label' => isset($assignment['assigned_date_label']) ? (string) $assignment['assigned_date_label'] : '-',
			'due_date_label' => isset($assignment['due_date_label']) ? (string) $assignment['due_date_label'] : '-',
			'verified_by_label' => !empty($assignment['verified_by_name']) ? (string) $assignment['verified_by_name'] : '',
			'verified_at_label' => !empty($assignment['verified_at']) ? date('d/m/Y H:i', strtotime($assignment['verified_at'])) : '',
		);
	}

	public function exportar_ventas()
	{
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$all_employees = $employees;
		$attendance_employees = $this->get_active_attendance_employees();
		$rows = array();
		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}
			$rows[] = array(
				'Empleado' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
				'Ventas_hoy' => number_format((float)$this->Gala_read_model->get_vendor_sales_total_for_today($employee_id), 2, '.', ''),
				'Transacciones_hoy' => (int)$this->Gala_read_model->count_vendor_sales_for_today($employee_id),
				'Ventas_mes' => number_format((float)$this->Gala_read_model->get_vendor_sales_total_for_current_month($employee_id), 2, '.', ''),
			);
		}

		$filename = 'ventas_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Empleado', 'Ventas_hoy', 'Transacciones_hoy', 'Ventas_mes'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function exportar_ventas_diarias()
	{
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$today = new DateTime('now', new DateTimeZone('America/Caracas'));
		$days = array();
		for ($i = 6; $i >= 0; $i--)
		{
			$day = clone $today;
			$day->modify('-'.$i.' days');
			$days[] = $day->format('Y-m-d');
		}
		$range_start = $days[0].' 00:00:00';
		$range_end = end($days).' 23:59:59';

		$totals = array_fill_keys($days, 0.0);
		$counts = array_fill_keys($days, 0);
		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}
			$daily_totals = $this->Gala_read_model->get_goal_metric_daily_totals_for_employee(
				$employee_id,
				'general',
				null,
				'amount',
				$range_start,
				$range_end
			);
			foreach ($daily_totals as $date_key => $value)
			{
				if (!isset($totals[$date_key]))
				{
					continue;
				}
				$totals[$date_key] += (float)$value;
			}

			$daily_counts = $this->Gala_read_model->get_sales_counts_by_day($range_start, $range_end, $employee_id);
			foreach ($daily_counts as $date_key => $count)
			{
				if (!isset($counts[$date_key]))
				{
					continue;
				}
				$counts[$date_key] += (int)$count;
			}
		}

		$filename = 'ventas_diarias_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Fecha', 'Ventas', 'Transacciones'));
		foreach ($days as $date_key)
		{
			fputcsv($handle, array(
				$date_key,
				number_format((float)$totals[$date_key], 2, '.', ''),
				(int)$counts[$date_key],
			));
		}
		fclose($handle);
	}

	public function metas()
	{
		$this->require_admin_module_permission('supervisor.metas.view');
		$data = $this->build_metas_screen_data(FALSE);
		$data['page_title'] = 'Metas';
		$data['current_tab'] = 'metas';
		$data['view'] = 'supervisor/metas';
		$data['history_mode'] = FALSE;
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function metas_historicas()
	{
		$this->require_admin_module_permission('supervisor.metas.view');
		$data = $this->build_metas_screen_data(TRUE);
		$data['page_title'] = 'Metas historicas';
		$data['current_tab'] = 'metas';
		$data['view'] = 'supervisor/metas';
		$data['history_mode'] = TRUE;
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function meta_eliminar($goal_id)
	{
		$this->require_admin_module_permission('supervisor.metas.view');
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$goal = $this->App_write_model->get_goal_definition((int) $goal_id);
		if (!$goal || (empty($goal['employee_mode']) || (string) $goal['employee_mode'] === 'global'))
		{
			show_404();
		}

		$disabled = $this->App_write_model->delete_goal_definition((int) $goal_id);
		$this->session->set_flashdata($disabled ? 'flash_message' : 'flash_error', $disabled ? 'Meta deshabilitada.' : 'No se pudo deshabilitar la meta.');
		redirect('metas');
	}

	private function build_metas_screen_data($historical_only = FALSE)
	{
		$this->App_write_model->run_migration();
		$period_reference = date('Y-m-01');

		$goal_definitions = array_values(array_filter($this->App_write_model->get_goal_definitions(FALSE), function ($definition) {
			return !empty($definition['employee_mode']) && (string) $definition['employee_mode'] !== 'global';
		}));
		$all_employees = array_values(array_filter($this->Gala_read_model->get_employees_for_visor(), function ($row) {
			return !empty($row['is_active_in_gala']);
		}));
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();
		$employees = $all_employees;
		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);
		$goal_rows = array();
		$snapshot_bundle = !$historical_only
			? $this->build_scoped_goal_snapshot_bundle($goal_definitions, $employees, $employee_groups_index, $group_members_index, $period_reference)
			: array();

		foreach ($goal_definitions as $definition)
		{
			$rows = array();
			$goal_id = isset($definition['id']) ? (int) $definition['id'] : 0;
			$is_active = !empty($definition['is_active']);
			$is_available = $this->goal_is_currently_available($definition, $period_reference);
			list($date_start, $date_end) = $this->get_goal_period_range(
				isset($definition['period_type']) ? $definition['period_type'] : 'daily',
				$definition,
				$period_reference
			);

			if ($goal_id > 0 && isset($snapshot_bundle[$goal_id]))
			{
				$date_start = $snapshot_bundle[$goal_id]['date_start'];
				$date_end = $snapshot_bundle[$goal_id]['date_end'];
			}

			$belongs_to_history = (!$is_active || !$is_available);
			if ((bool) $historical_only !== $belongs_to_history)
			{
				continue;
			}

			if ($is_active && $is_available && $goal_id > 0 && isset($snapshot_bundle[$goal_id]['rows']))
			{
				$rows = array_values($snapshot_bundle[$goal_id]['rows']);
			}

			usort($rows, function ($a, $b) {
				if ($a['progress_percent'] === $b['progress_percent'])
				{
					return strcasecmp($a['employee_name'], $b['employee_name']);
				}
				return ($a['progress_percent'] < $b['progress_percent']) ? 1 : -1;
			});

			$goal_rows[] = array(
				'definition' => $definition,
				'date_start' => $date_start,
				'date_end' => $date_end,
				'is_active' => $is_active,
				'is_available' => $is_available,
				'rows' => $rows,
			);
		}

		usort($goal_rows, function ($a, $b) use ($historical_only) {
			if ($historical_only)
			{
				$a_rank = !empty($a['is_active']) ? 0 : 1;
				$b_rank = !empty($b['is_active']) ? 0 : 1;
				if ($a_rank !== $b_rank)
				{
					return $a_rank <=> $b_rank;
				}
			}
			$a_name = isset($a['definition']['name']) ? (string) $a['definition']['name'] : '';
			$b_name = isset($b['definition']['name']) ? (string) $b['definition']['name'] : '';
			return strcasecmp($a_name, $b_name);
		});

		return array(
			'goal_rows' => $goal_rows,
			'period_label' => date('F Y', strtotime($period_reference)),
			'all_employees' => $all_employees,
			'groups' => $groups,
			'historical_route' => $historical_only ? 'metas' : 'metas/historicas',
		);
	}
	public function meta_nueva()
	{
		$this->handle_goal_definition_form();
	}

	public function meta_editar($goal_id)
	{
		$this->handle_goal_definition_form((int) $goal_id);
	}

	public function exportar_metas()
	{
		$this->App_write_model->run_migration();
		$filters = array(
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
			'period' => trim((string)$this->input->get('period')),
		);
		$period_filter = $filters['period'];
		$period_reference = $period_filter !== '' && preg_match('/^\d{4}\-\d{2}$/', $period_filter)
			? $period_filter.'-01'
			: date('Y-m-01');

		$goal_definitions = array_values(array_filter($this->App_write_model->get_goal_definitions(TRUE), function ($definition) {
			return !empty($definition['employee_mode']) && (string) $definition['employee_mode'] !== 'global';
		}));
		$all_employees = array_values(array_filter($this->Gala_read_model->get_employees_for_visor(), function ($row) {
			return !empty($row['is_active_in_gala']);
		}));
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();

		$employees = $all_employees;
		if (!empty($filters['employee_id']))
		{
			$employees = array_values(array_filter($employees, function ($row) use ($filters) {
				return (int)(isset($row['person_id']) ? $row['person_id'] : 0) === (int)$filters['employee_id'];
			}));
		}
		if (!empty($filters['group_id']) && !empty($group_members_index[(int)$filters['group_id']]))
		{
			$allowed_ids = array_flip(array_map('intval', (array)$group_members_index[(int)$filters['group_id']]));
			$employees = array_values(array_filter($employees, function ($row) use ($allowed_ids) {
				$id = (int)(isset($row['person_id']) ? $row['person_id'] : 0);
				return $id > 0 && isset($allowed_ids[$id]);
			}));
		}

		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);
		$rows = array();
		foreach ($goal_definitions as $definition)
		{
			if (!$this->goal_is_currently_available($definition, $period_reference))
			{
				continue;
			}
			list($date_start, $date_end) = $this->get_goal_period_range(
				isset($definition['period_type']) ? $definition['period_type'] : 'daily',
				$definition,
				$period_reference
			);

			foreach ($employees as $employee)
			{
				$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
				if ($employee_id <= 0 || !$this->goal_applies_to_employee($definition, $employee_id, $employee_groups_index))
				{
					continue;
				}

				$selected_group_ids = $this->get_goal_selected_group_ids($definition);
				$employee_group_id = !empty($selected_group_ids)
					? $this->resolve_employee_matching_group_id($employee_id, $selected_group_ids, $group_members_index)
					: 0;
				$is_group_total = !empty($definition['employee_mode'])
					&& $definition['employee_mode'] === 'group'
					&& !empty($employee_group_id)
					&& isset($definition['target_scope'])
					&& $definition['target_scope'] === 'group_total';

				$current_value = $is_group_total
					? $this->get_group_goal_metric_value_cached($employee_group_id, $definition, $date_start, $date_end)
					: $this->get_goal_metric_for_employee_cached(
						$employee_id,
						isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
						isset($definition['scope_id']) ? $definition['scope_id'] : null,
						isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
						$date_start,
						$date_end
					);

				$target_value = $this->resolve_goal_target_value($definition, $employee_id, $date_start, $date_end, $period_reference, $employee_group_id);
				$progress = $target_value > 0 ? min(1, max(0, $current_value / $target_value)) : 0;

				$rows[] = array(
					'Meta' => isset($definition['name']) ? $definition['name'] : 'Meta',
					'Empleado' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
					'Actual' => number_format((float)$current_value, 2, '.', ''),
					'Meta_valor' => number_format((float)$target_value, 2, '.', ''),
					'Avance' => (int)round($progress * 100),
					'Periodo' => $date_start.' - '.$date_end,
				);
			}
		}

		$filename = 'metas_'.$period_reference.'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Meta', 'Empleado', 'Actual', 'Meta_valor', 'Avance', 'Periodo'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function exportar_resumen_metas()
	{
		$this->App_write_model->run_migration();
		$filters = array(
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
			'period' => trim((string)$this->input->get('period')),
		);
		$period_filter = $filters['period'];
		$period_reference = $period_filter !== '' && preg_match('/^\d{4}\-\d{2}$/', $period_filter)
			? $period_filter.'-01'
			: date('Y-m-01');

		$goal_definitions = array_values(array_filter($this->App_write_model->get_goal_definitions(TRUE), function ($definition) {
			return !empty($definition['employee_mode']) && (string) $definition['employee_mode'] !== 'global';
		}));
		$all_employees = array_values(array_filter($this->Gala_read_model->get_employees_for_visor(), function ($row) {
			return !empty($row['is_active_in_gala']);
		}));
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();

		$employees = $all_employees;
		if (!empty($filters['employee_id']))
		{
			$employees = array_values(array_filter($employees, function ($row) use ($filters) {
				return (int)(isset($row['person_id']) ? $row['person_id'] : 0) === (int)$filters['employee_id'];
			}));
		}
		if (!empty($filters['group_id']) && !empty($group_members_index[(int)$filters['group_id']]))
		{
			$allowed_ids = array_flip(array_map('intval', (array)$group_members_index[(int)$filters['group_id']]));
			$employees = array_values(array_filter($employees, function ($row) use ($allowed_ids) {
				$id = (int)(isset($row['person_id']) ? $row['person_id'] : 0);
				return $id > 0 && isset($allowed_ids[$id]);
			}));
		}

		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);
		$employee_totals = array();
		foreach ($goal_definitions as $definition)
		{
			if (!$this->goal_is_currently_available($definition, $period_reference))
			{
				continue;
			}
			list($date_start, $date_end) = $this->get_goal_period_range(
				isset($definition['period_type']) ? $definition['period_type'] : 'daily',
				$definition,
				$period_reference
			);
			foreach ($employees as $employee)
			{
				$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
				if ($employee_id <= 0 || !$this->goal_applies_to_employee($definition, $employee_id, $employee_groups_index))
				{
					continue;
				}

				$selected_group_ids = $this->get_goal_selected_group_ids($definition);
				$employee_group_id = !empty($selected_group_ids)
					? $this->resolve_employee_matching_group_id($employee_id, $selected_group_ids, $group_members_index)
					: 0;
				$is_group_total = !empty($definition['employee_mode'])
					&& $definition['employee_mode'] === 'group'
					&& !empty($employee_group_id)
					&& isset($definition['target_scope'])
					&& $definition['target_scope'] === 'group_total';

				$current_value = $is_group_total
					? $this->get_group_goal_metric_value_cached($employee_group_id, $definition, $date_start, $date_end)
					: $this->get_goal_metric_for_employee_cached(
						$employee_id,
						isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
						isset($definition['scope_id']) ? $definition['scope_id'] : null,
						isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
						$date_start,
						$date_end
					);
				$target_value = $this->resolve_goal_target_value($definition, $employee_id, $date_start, $date_end, $period_reference, $employee_group_id);

				if (!isset($employee_totals[$employee_id]))
				{
					$employee_totals[$employee_id] = array(
						'Empleado' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
						'Actual' => 0.0,
						'Meta' => 0.0,
					);
				}
				$employee_totals[$employee_id]['Actual'] += (float)$current_value;
				$employee_totals[$employee_id]['Meta'] += (float)$target_value;
			}
		}

		$employee_rows = array();
		foreach ($employee_totals as $row)
		{
			$progress = $row['Meta'] > 0 ? (int)round(($row['Actual'] / $row['Meta']) * 100) : 0;
			$employee_rows[] = array(
				'Empleado' => $row['Empleado'],
				'Actual' => number_format((float)$row['Actual'], 2, '.', ''),
				'Meta' => number_format((float)$row['Meta'], 2, '.', ''),
				'Avance' => $progress,
			);
		}

		$group_rows = array();
		foreach ($groups as $group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			if ($group_id <= 0)
			{
				continue;
			}
			$members = isset($group_members_index[$group_id]) ? array_map('intval', $group_members_index[$group_id]) : array();
			$sum = 0.0;
			$count = 0;
			foreach ($employee_rows as $row)
			{
				foreach ($employees as $employee)
				{
					if (isset($row['Empleado']) && isset($employee['full_name']) && $row['Empleado'] === $employee['full_name'])
					{
						if (in_array((int)$employee['person_id'], $members, TRUE))
						{
							$sum += (float)$row['Avance'];
							$count++;
						}
					}
				}
			}
			$avg = $count > 0 ? (int)round($sum / $count) : 0;
			$group_rows[] = array(
				'Grupo' => isset($group['name']) ? $group['name'] : 'Grupo',
				'Avance' => $avg,
			);
		}

		$filename = 'resumen_metas_'.$period_reference.'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Empleado', 'Actual', 'Meta', 'Avance'));
		foreach ($employee_rows as $row)
		{
			fputcsv($handle, $row);
		}
		fputcsv($handle, array());
		fputcsv($handle, array('Grupo', 'Avance'));
		foreach ($group_rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function tareas()
	{
		$this->require_admin_module_permission('supervisor.tareas.view');
		$can_view_task_templates = $this->has_admin_module_permission('supervisor.tareas.templates.view');
		$can_view_task_rules = $this->has_admin_module_permission('supervisor.tareas.rules.view');
		$can_export_tasks = $this->has_admin_module_permission('supervisor.tareas.export.view');
		$flash_message = '';
		$flash_error = '';
		$filters = array(
			'q' => trim((string)$this->input->get_post('q')),
			'status' => trim((string)$this->input->get_post('status')),
			'employee_id' => (int)$this->input->get_post('employee_id'),
			'group_id' => (int)$this->input->get_post('group_id'),
		);
		if ($filters['status'] === '')
		{
			$filters['status'] = 'all';
		}

		$expects_json = $this->request_wants_json();

		if ($this->input->method(TRUE) === 'POST')
		{
			$action = trim((string)$this->input->post('action_type'));
			$actor = $this->get_supervisor_actor();
			$selected_ids = array_values(array_unique(array_filter(array_map('intval', (array)$this->input->post('assignment_ids')))));
			$assignment_id = (int)$this->input->post('assignment_id');
			if ($assignment_id > 0 && empty($selected_ids))
			{
				$selected_ids = array($assignment_id);
			}

			if (empty($selected_ids))
			{
				$flash_error = 'Debes seleccionar al menos una tarea.';
			}
			else
			{
				$success_count = 0;
				foreach ($selected_ids as $id)
				{
					$assignment = $this->Tasks_model->get_assignment_for_admin($id);
					if (!$assignment)
					{
						continue;
					}
					if (!empty($assignment['verified_at']) && $action === 'reactivate' && !$this->has_task_verification_override_access())
					{
						$result = array('success' => FALSE, 'message' => 'Esta tarea ya fue verificada y no puedes cambiar su estado.');
					}
					elseif ($action === 'complete')
					{
						$result = $this->Tasks_model->complete_assignment(
							$id,
							(int)$assignment['employee_id'],
							(string)$this->input->post('completion_note'),
							TRUE
						);
						if (!empty($result['success']))
						{
							$success_count++;
						}
					}
					elseif ($action === 'reactivate')
					{
						$result = $this->Tasks_model->reactivate_assignment($id);
						if (!empty($result['success']))
						{
							$success_count++;
						}
					}
					elseif ($action === 'verify')
					{
						if (empty($actor['employee_id']))
						{
							$result = array('success' => FALSE, 'message' => 'No encontramos al usuario que verifica.');
						}
						else
						{
							$result = $this->Tasks_model->verify_assignment($id, (int)$actor['employee_id'], (string)$actor['name']);
						}
						if (!empty($result['success']))
						{
							$success_count++;
						}
					}
				}

				if ($success_count > 0)
				{
					$flash_message = 'Accion aplicada a '.$success_count.' tareas.';
				}
				else
				{
					$flash_error = 'No se pudo aplicar la accion.';
				}
			}

			if ($expects_json)
			{
				$employees = $this->Gala_read_model->get_employees_for_visor();
				$groups = $this->App_write_model->get_employee_groups(TRUE);
				$updated_assignments = array();
				foreach ($selected_ids as $id)
				{
					$assignment = $this->Tasks_model->get_assignment_for_admin($id);
					if (!$assignment)
					{
						continue;
					}

					$prepared = $this->prepare_task_assignments(array($assignment), $employees, $groups);
					if (!empty($prepared[0]))
					{
						$updated_assignments[] = $prepared[0];
					}
				}

				return $this->respond_json(array(
					'success' => $flash_error === '',
					'message' => $flash_error === '' ? $flash_message : $flash_error,
					'action' => $action,
					'assignment_ids' => $selected_ids,
					'assignments' => $updated_assignments,
				), $flash_error === '' ? 200 : 422);
			}
		}

		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$assignments = array_values(array_filter($this->Tasks_model->get_assignments_for_admin($filters), function ($assignment) {
			return empty($assignment['assignment_mode']) || (string) $assignment['assignment_mode'] !== 'global';
		}));
		$filtered = $this->prepare_task_assignments($assignments, $employees, $groups, $filters);

		$data = array(
			'page_title' => 'Tareas',
			'current_tab' => 'tareas',
			'view' => 'supervisor/tareas',
			'filters' => $filters,
			'assignments' => $filtered,
			'employees' => $employees,
			'groups' => $groups,
			'all_employees' => $employees,
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
				'compact_header' => TRUE,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function tarea_plantillas()
	{
		$this->require_admin_module_permission('supervisor.tareas.templates.view');
		$data = array(
			'page_title' => 'Plantillas',
			'current_tab' => 'tareas',
			'view' => 'supervisor/tarea_templates',
			'templates' => $this->Tasks_model->get_templates(),
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function tarea_configuradas()
	{
		$this->require_admin_module_permission('supervisor.tareas.rules.view');
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$rules = array_values(array_filter($this->decorate_task_rules($this->Tasks_model->get_rules(), $employees, $groups), function ($rule) {
			return empty($rule['assignment_mode']) || (string) $rule['assignment_mode'] !== 'global';
		}));

		$data = array(
			'page_title' => 'Tareas configuradas',
			'current_tab' => 'tareas',
			'view' => 'supervisor/tarea_rules',
			'rules' => $rules,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function exportar_tareas()
	{
		$this->require_admin_module_permission('supervisor.tareas.export.view');
		$filters = array(
			'q' => trim((string)$this->input->get('q')),
			'status' => trim((string)$this->input->get('status')),
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
		);
		if ($filters['status'] === '')
		{
			$filters['status'] = 'all';
		}

		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$assignments = array_values(array_filter($this->Tasks_model->get_assignments_for_admin($filters), function ($assignment) {
			return empty($assignment['assignment_mode']) || (string) $assignment['assignment_mode'] !== 'global';
		}));

		$employee_index = array();
		foreach ($employees as $row)
		{
			$employee_index[(int)$row['person_id']] = $row;
		}
		$group_index = array();
		foreach ($groups as $group)
		{
			$group_index[(int)$group['id']] = $group;
		}

		$rows = array();
		foreach ($assignments as $assignment)
		{
			$employee_id = isset($assignment['employee_id']) ? (int)$assignment['employee_id'] : 0;
			$group_id = isset($assignment['rule_group_id']) ? (int)$assignment['rule_group_id'] : 0;
			$employee_name = isset($employee_index[$employee_id]['full_name']) ? $employee_index[$employee_id]['full_name'] : 'Empleado';
			$group_name = $group_id > 0 && isset($group_index[$group_id]['name']) ? $group_index[$group_id]['name'] : '';

			if ($filters['q'] !== '')
			{
				$haystack = $employee_name.' '.(isset($assignment['title']) ? $assignment['title'] : '').' '.$group_name;
				if (stripos($haystack, $filters['q']) === FALSE)
				{
					continue;
				}
			}

			$rows[] = array(
				'Empleado' => $employee_name,
				'Tarea' => isset($assignment['title']) ? $assignment['title'] : '',
				'Estado' => isset($assignment['status']) ? $assignment['status'] : '',
				'Asignada' => isset($assignment['assigned_date']) ? $assignment['assigned_date'] : '',
				'Vence' => isset($assignment['due_date']) ? $assignment['due_date'] : '',
				'Grupo' => $group_name,
			);
		}

		$filename = 'tareas_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Empleado', 'Tarea', 'Estado', 'Asignada', 'Vence', 'Grupo'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function tarea_nueva()
	{
		$this->require_admin_module_permission('supervisor.tareas.rules.view');
		$this->handle_tarea_rule_form();
	}

	public function tarea_editar($rule_id)
	{
		$this->require_admin_module_permission('supervisor.tareas.rules.view');
		$this->handle_tarea_rule_form((int) $rule_id);
	}

	public function tarea_eliminar($rule_id)
	{
		$this->require_admin_module_permission('supervisor.tareas.rules.view');
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$rule = $this->Tasks_model->get_rule((int) $rule_id);
		if (!$rule)
		{
			show_404();
		}
		if (!empty($rule['assignment_mode']) && (string) $rule['assignment_mode'] === 'global')
		{
			show_404();
		}

		$result = $this->Tasks_model->delete_rule((int) $rule_id);
		$this->session->set_flashdata(!empty($result['success']) ? 'flash_message' : 'flash_error', !empty($result['message']) ? $result['message'] : 'No se pudo eliminar la tarea.');
		redirect('tareas/configuradas');
	}

	public function tarea_plantilla_nueva()
	{
		$this->require_admin_module_permission('supervisor.tareas.templates.view');
		$this->handle_tarea_template_form();
	}

	public function tarea_plantilla_editar($template_id)
	{
		$this->require_admin_module_permission('supervisor.tareas.templates.view');
		$this->handle_tarea_template_form((int) $template_id);
	}

	public function tarea_plantilla_eliminar($template_id)
	{
		$this->require_admin_module_permission('supervisor.tareas.templates.view');
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$template = $this->Tasks_model->get_template((int) $template_id);
		if (!$template)
		{
			show_404();
		}

		$result = $this->Tasks_model->delete_template((int) $template_id);
		$this->session->set_flashdata(!empty($result['success']) ? 'flash_message' : 'flash_error', !empty($result['message']) ? $result['message'] : 'No se pudo eliminar la plantilla.');
		redirect('tareas/plantillas');
	}

	public function tarea_rapida()
	{
		if ($this->input->method(TRUE) !== 'POST')
		{
			show_404();
		}

		$employee_id = (int) $this->input->post('employee_id');
		$title = trim((string) $this->input->post('title', TRUE));
		if ($employee_id <= 0 || $title === '')
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'Debes escribir el nombre de la tarea y seleccionar un empleado.',
			), 422);
		}

		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$allowed_employee_ids = array();
		foreach ((array) $employees as $employee)
		{
			$row_employee_id = isset($employee['person_id']) ? (int) $employee['person_id'] : 0;
			if ($row_employee_id > 0)
			{
				$allowed_employee_ids[$row_employee_id] = TRUE;
			}
		}

		if (!isset($allowed_employee_ids[$employee_id]))
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => 'El empleado seleccionado no es valido.',
			), 422);
		}

		$result = $this->Tasks_model->create_quick_assignment($employee_id, $title);
		if (empty($result['success']) || empty($result['assignment_id']))
		{
			return $this->respond_json(array(
				'success' => FALSE,
				'message' => !empty($result['message']) ? $result['message'] : 'No pudimos crear la tarea rapida.',
			), 422);
		}

		$assignment = $this->Tasks_model->get_assignment_for_admin((int) $result['assignment_id']);
		$prepared = $this->prepare_task_assignments(array($assignment), $employees, $groups);

		return $this->respond_json(array(
			'success' => TRUE,
			'message' => !empty($result['message']) ? $result['message'] : 'Tarea creada correctamente.',
			'assignment' => !empty($prepared[0]) ? $prepared[0] : null,
		));
	}

	public function tarea($assignment_id)
	{
		$assignment_id = (int)$assignment_id;
		$flash_message = '';
		$flash_error = '';
		$expects_json = $this->request_wants_json();
		$assignment = $this->Tasks_model->get_assignment_for_admin($assignment_id);
		if (!$assignment)
		{
			show_404();
		}

		if ($this->input->method(TRUE) === 'POST')
		{
			$action = trim((string)$this->input->post('action_type'));
			$actor = $this->get_supervisor_actor();
			if ($action === 'toggle_item')
			{
				$item_id = (int)$this->input->post('item_id');
				$target = $this->input->post('is_completed') ? TRUE : FALSE;
				$result = $this->Tasks_model->update_assignment_item_status(
					$assignment_id,
					$item_id,
					(int)$assignment['employee_id'],
					$target
				);
				if (!empty($result['success']))
				{
					$flash_message = 'Subtarea actualizada.';
				}
				else
				{
					$flash_error = isset($result['message']) ? $result['message'] : 'No pudimos actualizar la subtarea.';
				}
			}
			elseif ($action === 'add_note')
			{
				$result = $this->Tasks_model->add_assignment_note_admin(
					$assignment_id,
					(string)$this->input->post('note_text')
				);
				if (!empty($result['success']))
				{
					$flash_message = 'Nota agregada.';
				}
				else
				{
					$flash_error = isset($result['message']) ? $result['message'] : 'No pudimos agregar la nota.';
				}
			}
			elseif ($action === 'complete')
			{
				$result = $this->Tasks_model->complete_assignment(
					$assignment_id,
					(int)$assignment['employee_id'],
					(string)$this->input->post('completion_note'),
					TRUE
				);
				if (!empty($result['success']))
				{
					$flash_message = isset($result['message']) ? $result['message'] : 'Tarea completada.';
				}
				else
				{
					$flash_error = isset($result['message']) ? $result['message'] : 'No pudimos completar la tarea.';
				}
			}
			elseif ($action === 'reactivate')
			{
				$result = $this->Tasks_model->reactivate_assignment($assignment_id);
				if (!empty($result['success']))
				{
					$flash_message = 'Tarea reactivada.';
				}
				else
				{
					$flash_error = isset($result['message']) ? $result['message'] : 'No pudimos reactivar la tarea.';
				}
			}
			elseif ($action === 'update_assignment')
			{
				$result = $this->Tasks_model->update_assignment_admin($assignment_id, array(
					'title' => (string)$this->input->post('title'),
					'description' => (string)$this->input->post('description'),
					'due_date' => (string)$this->input->post('due_date'),
				));
				if (!empty($result['success']))
				{
					$flash_message = isset($result['message']) ? $result['message'] : 'Tarea actualizada.';
				}
				else
				{
					$flash_error = isset($result['message']) ? $result['message'] : 'No pudimos actualizar la tarea.';
				}
			}
		}

		$assignment = $this->Tasks_model->get_assignment_for_admin($assignment_id);
		$assignment = $this->decorate_task_assignment($assignment);

		if ($expects_json)
		{
			$html = $this->load->view('supervisor/partials/tarea_modal', array(
				'assignment' => $assignment,
				'flash_message' => $flash_message,
				'flash_error' => $flash_error,
			), TRUE);

			return $this->respond_json(array(
				'success' => $flash_error === '',
				'assignment_id' => $assignment_id,
				'title' => isset($assignment['title']) ? (string) $assignment['title'] : 'Detalle de tarea',
				'html' => $html,
				'message' => $flash_error === '' ? $flash_message : $flash_error,
				'assignment' => $this->build_task_assignment_payload($assignment),
			), $flash_error === '' ? 200 : 422);
		}

		$data = array(
			'page_title' => 'Detalle de tarea',
			'current_tab' => 'tareas',
			'view' => 'supervisor/tareas_detalle',
			'assignment' => $assignment,
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function grupos()
	{
		$this->require_admin_module_permission('supervisor.grupos.view');
		$filters = array(
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
			'period' => trim((string)$this->input->get('period')),
		);
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$members_index = $this->App_write_model->get_employee_group_members_index();
		$statistics_ids = array_flip(array_map('intval', array_column($this->get_statistics_employees(TRUE), 'person_id')));
		foreach ($groups as &$group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			$members = !empty($members_index[$group_id]) ? array_map('intval', $members_index[$group_id]) : array();
			$group['member_count'] = count(array_values(array_filter($members, function ($member_id) use ($statistics_ids) {
				return isset($statistics_ids[(int)$member_id]);
			})));
		}
		unset($group);

		if (!empty($filters['group_id']))
		{
			$groups = array_values(array_filter($groups, function ($group) use ($filters) {
				return (int)$group['id'] === (int)$filters['group_id'];
			}));
		}

		$data = array(
			'page_title' => 'Grupos',
			'current_tab' => 'grupos',
			'view' => 'supervisor/grupos',
			'groups' => $groups,
			'filters' => $filters,
			'all_employees' => $this->Gala_read_model->get_employees_for_visor(),
			'groups_all' => $this->App_write_model->get_employee_groups(TRUE),
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function exportar_grupos()
	{
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$members_index = $this->App_write_model->get_employee_group_members_index();
		$statistics_ids = array_flip(array_map('intval', array_column($this->get_statistics_employees(TRUE), 'person_id')));

		$rows = array();
		foreach ($groups as $group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			$members = !empty($members_index[$group_id]) ? array_map('intval', $members_index[$group_id]) : array();
			$member_count = count(array_values(array_filter($members, function ($member_id) use ($statistics_ids) {
				return isset($statistics_ids[(int)$member_id]);
			})));
			$rows[] = array(
				'Grupo' => isset($group['name']) ? $group['name'] : 'Grupo',
				'Miembros' => $member_count,
				'Color' => isset($group['color']) ? $group['color'] : '',
				'Activo' => !empty($group['is_active']) ? 'Si' : 'No',
			);
		}

		$filename = 'grupos_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Grupo', 'Miembros', 'Color', 'Activo'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function grupo_nuevo()
	{
		$this->handle_grupo_form();
	}

	public function grupo_editar($group_id)
	{
		$this->handle_grupo_form((int)$group_id);
	}

	public function empleados()
	{
		$this->require_admin_module_permission('supervisor.empleados.view');
		$filters = array(
			'employee_id' => (int)$this->input->get('employee_id'),
			'group_id' => (int)$this->input->get('group_id'),
			'period' => trim((string)$this->input->get('period')),
		);
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$app_users = $this->App_write_model->get_all_app_users_index();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();
		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);

		if (!empty($filters['employee_id']))
		{
			$employees = array_values(array_filter($employees, function ($row) use ($filters) {
				return (int)(isset($row['person_id']) ? $row['person_id'] : 0) === (int)$filters['employee_id'];
			}));
		}
		if (!empty($filters['group_id']) && !empty($group_members_index[(int)$filters['group_id']]))
		{
			$allowed_ids = array_flip(array_map('intval', (array)$group_members_index[(int)$filters['group_id']]));
			$employees = array_values(array_filter($employees, function ($row) use ($allowed_ids) {
				$id = (int)(isset($row['person_id']) ? $row['person_id'] : 0);
				return $id > 0 && isset($allowed_ids[$id]);
			}));
		}

		$rows = array();
		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			$app_user = isset($app_users[$employee_id]) ? $app_users[$employee_id] : null;
			$rows[] = array(
				'employee_id' => $employee_id,
				'full_name' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
				'is_active_in_gala' => !empty($employee['is_active_in_gala']),
				'is_active_in_visor' => !$app_user || !isset($app_user['is_enabled']) || (int)$app_user['is_enabled'] === 1,
				'is_admin' => $app_user && !empty($app_user['is_admin']),
				'groups' => isset($employee_groups_index[$employee_id]) ? $employee_groups_index[$employee_id] : array(),
				'completed_goals_count' => $app_user && isset($app_user['completed_goals_count']) ? (int)$app_user['completed_goals_count'] : 0,
				'completed_tasks_count' => $app_user && isset($app_user['completed_tasks_count']) ? (int)$app_user['completed_tasks_count'] : 0,
			);
		}

		$data = array(
			'page_title' => 'Empleados',
			'current_tab' => 'empleados',
			'view' => 'supervisor/empleados',
			'employees' => $rows,
			'filters' => $filters,
			'all_employees' => $this->Gala_read_model->get_employees_for_visor(),
			'groups' => $groups,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function exportar_empleados()
	{
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$app_users = $this->App_write_model->get_all_app_users_index();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();
		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);

		$rows = array();
		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			$app_user = isset($app_users[$employee_id]) ? $app_users[$employee_id] : null;
			$group_names = array();
			if (isset($employee_groups_index[$employee_id]))
			{
				foreach ($employee_groups_index[$employee_id] as $group)
				{
					$group_names[] = isset($group['name']) ? $group['name'] : 'Grupo';
				}
			}

			$rows[] = array(
				'Empleado' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
				'Usuario' => isset($employee['username']) ? $employee['username'] : '',
				'Gala_activo' => !empty($employee['is_active_in_gala']) ? 'Si' : 'No',
				'Visor_activo' => !$app_user || !isset($app_user['is_enabled']) || (int)$app_user['is_enabled'] === 1 ? 'Si' : 'No',
				'Admin' => $app_user && !empty($app_user['is_admin']) ? 'Si' : 'No',
				'Grupos' => implode(', ', $group_names),
			);
		}

		$filename = 'empleados_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Empleado', 'Usuario', 'Gala_activo', 'Visor_activo', 'Admin', 'Grupos'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	public function empleado_detalle($employee_id)
	{
		$this->require_admin_module_permission('supervisor.empleados.view');
		$this->App_write_model->run_migration();
		$employee_id = (int)$employee_id;
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$employee = null;
		foreach ($employees as $row)
		{
			if ((int)$row['person_id'] === $employee_id)
			{
				$employee = $row;
				break;
			}
		}
		if (!$employee)
		{
			show_404();
		}

		$flash_message = (string)$this->session->flashdata('employee_detail_message');
		$flash_error = (string)$this->session->flashdata('employee_detail_error');
		$allowed_tabs = array('general', 'puntos', 'grupos', 'permisos_visor', 'permisos_admin');
		$active_tab = trim((string)$this->input->get('tab'));
		if (!in_array($active_tab, $allowed_tabs, TRUE))
		{
			$active_tab = 'general';
		}

		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();
		$selected_group_ids = array();
		foreach ((array)$groups as $group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			$current_members = isset($group_members_index[$group_id]) ? array_map('intval', $group_members_index[$group_id]) : array();
			if ($group_id > 0 && in_array($employee_id, $current_members, TRUE))
			{
				$selected_group_ids[] = $group_id;
			}
		}

		if ($this->input->method(TRUE) === 'POST')
		{
			$action_type = trim((string)$this->input->post('action_type'));
			$redirect_tab = trim((string)$this->input->post('employee_tab'));
			if (!in_array($redirect_tab, $allowed_tabs, TRUE))
			{
				$redirect_tab = 'general';
			}

			$profile = array(
				'username' => isset($employee['username']) ? $employee['username'] : '',
				'full_name' => isset($employee['full_name']) ? $employee['full_name'] : '',
			);
			$app_user = $this->App_write_model->get_app_user($employee_id);

			if ($action_type === 'add_points')
			{
				$points_delta = (int)$this->input->post('points_delta');
				$points_reason = trim((string)$this->input->post('points_reason'));
				$actor = $this->get_supervisor_actor();
				if ($points_delta === 0)
				{
					$this->session->set_flashdata('employee_detail_error', 'Indica un ajuste de puntos distinto de cero.');
				}
				elseif ($points_reason === '')
				{
					$this->session->set_flashdata('employee_detail_error', 'Describe como obtuvo esos puntos.');
				}
				else
				{
					$result = $this->App_write_model->add_employee_points_movement(
						$employee_id,
						$points_delta,
						$points_reason,
						isset($actor['employee_id']) ? (int)$actor['employee_id'] : null,
						isset($actor['name']) ? $actor['name'] : null,
						'Ajuste manual'
					);
					$this->session->set_flashdata($result ? 'employee_detail_message' : 'employee_detail_error', $result ? 'Movimiento de puntos registrado.' : 'No pudimos guardar el movimiento de puntos.');
				}
			}
			elseif ($action_type === 'save_groups')
			{
				$posted_groups = array_values(array_filter(array_map('intval', (array)$this->input->post('group_ids'))));
				foreach ($groups as $group)
				{
					$group_id = isset($group['id']) ? (int)$group['id'] : 0;
					if ($group_id <= 0)
					{
						continue;
					}
					$current_members = isset($group_members_index[$group_id]) ? array_map('intval', $group_members_index[$group_id]) : array();
					$should_have = in_array($group_id, $posted_groups, TRUE);
					$has_now = in_array($employee_id, $current_members, TRUE);
					if ($should_have && !$has_now)
					{
						$current_members[] = $employee_id;
						$this->App_write_model->replace_employee_group_members($group_id, $current_members);
					}
					elseif (!$should_have && $has_now)
					{
						$current_members = array_values(array_diff($current_members, array($employee_id)));
						$this->App_write_model->replace_employee_group_members($group_id, $current_members);
					}
				}
				$this->session->set_flashdata('employee_detail_message', 'Grupos actualizados.');
			}
			elseif ($action_type === 'save_visor_permissions')
			{
				$current_enabled = !$app_user || !isset($app_user['is_enabled']) || (int)$app_user['is_enabled'] === 1;
				if ($current_enabled)
				{
					$result = $this->App_write_model->save_app_user_config($employee_id, $profile, array(
						'permissions' => $this->normalize_permission_post((array)$this->input->post('permissions'), $this->get_default_visor_permissions()),
					));
					$this->session->set_flashdata($result ? 'employee_detail_message' : 'employee_detail_error', $result ? 'Permisos de Visor actualizados.' : 'No pudimos guardar los permisos de Visor.');
				}
				else
				{
					$this->session->set_flashdata('employee_detail_error', 'Activa el empleado en Visor antes de editar sus permisos.');
				}
			}
			elseif ($action_type === 'save_admin_permissions')
			{
				$current_admin = !empty($app_user['is_admin']);
				if ($current_admin)
				{
					$admin_permissions = $this->normalize_permission_post((array)$this->input->post('admin_permissions'), $this->get_default_admin_permissions());
					$session_user = $this->session->userdata('visor_auth');
					$session_user = is_array($session_user) ? $session_user : array();
					$current_user_id = !empty($session_user['employee_id']) ? (int)$session_user['employee_id'] : 0;
					if ($current_user_id > 0 && $current_user_id === $employee_id)
					{
						// Avoid locking the current admin out of the employee editor.
						$admin_permissions['supervisor.empleados.view'] = TRUE;
						$admin_permissions['supervisor.dashboard.view'] = TRUE;
					}
					$result = $this->App_write_model->save_app_user_config($employee_id, $profile, array(
						'admin_permissions' => $admin_permissions,
					));
					$this->session->set_flashdata($result ? 'employee_detail_message' : 'employee_detail_error', $result ? 'Permisos administrativos actualizados.' : 'No pudimos guardar los permisos administrativos.');
				}
				else
				{
					$this->session->set_flashdata('employee_detail_error', 'Marca Administrador en Visor para editar estos permisos.');
				}
			}
			else
			{
				$session_user = $this->session->userdata('visor_auth');
				$session_user = is_array($session_user) ? $session_user : array();
				$current_user_id = !empty($session_user['employee_id']) ? (int)$session_user['employee_id'] : 0;
				$is_self = $current_user_id > 0 && $current_user_id === $employee_id;
				$result = $this->App_write_model->save_app_user_config($employee_id, $profile, array(
					'is_enabled' => $is_self ? 1 : ($this->input->post('is_enabled') ? 1 : 0),
					'is_admin' => ($is_self && !empty($app_user['is_admin'])) ? 1 : ($this->input->post('is_admin') ? 1 : 0),
					'include_in_statistics' => $this->input->post('include_in_statistics') ? 1 : 0,
				));
				$this->session->set_flashdata($result ? 'employee_detail_message' : 'employee_detail_error', $result ? 'General actualizado.' : 'No pudimos guardar los cambios generales.');
			}

			redirect('empleados/'.$employee_id.'?tab='.$redirect_tab);
			return;
		}

		$app_user = $this->App_write_model->get_app_user($employee_id);
		$permission_labels = $this->get_permission_labels();
		$permission_groups = $this->get_permission_groups();
		$permissions = $this->resolve_permission_state(
			$app_user && !empty($app_user['permissions_json']) ? $this->App_write_model->get_app_user_permissions($employee_id) : array(),
			$this->get_default_visor_permissions(),
			$permission_labels
		);
		$admin_permission_labels = $this->get_admin_permission_labels();
		$admin_permission_groups = $this->get_admin_permission_groups();
		$admin_permissions = $this->resolve_permission_state(
			$app_user && !empty($app_user['admin_permissions_json']) ? $this->App_write_model->get_app_user_admin_permissions($employee_id) : array(),
			$this->get_default_admin_permissions(),
			$admin_permission_labels
		);
		$completion_counters = $this->App_write_model->get_completion_counters($employee_id);
		$points_total = (int)(isset($completion_counters['goals']) ? $completion_counters['goals'] : 0) + (int)(isset($completion_counters['tasks']) ? $completion_counters['tasks'] : 0);
		$points_history = $this->build_employee_points_activity($employee_id, 150);
		$assigned_groups = array();
		foreach ((array)$groups as $group)
		{
			if (in_array((int)$group['id'], $selected_group_ids, TRUE))
			{
				$assigned_groups[] = $group;
			}
		}

		$data = array(
			'page_title' => 'Empleado',
			'current_tab' => 'empleados',
			'view' => 'supervisor/empleado_detalle',
			'employee' => $employee,
			'app_user' => $app_user,
			'active_tab' => $active_tab,
			'permissions' => $permissions,
			'permission_labels' => $permission_labels,
			'permission_groups' => $permission_groups,
			'admin_permissions' => $admin_permissions,
			'admin_permission_labels' => $admin_permission_labels,
			'admin_permission_groups' => $admin_permission_groups,
			'points_total' => $points_total,
			'points_history' => $points_history,
			'completion_counters' => $completion_counters,
			'groups' => $groups,
			'assigned_groups' => $assigned_groups,
			'selected_group_ids' => $selected_group_ids,
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}
	public function configuracion()
	{
		$this->require_admin_module_permission('supervisor.configuracion.view');
		$data = array(
			'page_title' => 'Configuracion',
			'current_tab' => 'configuracion',
			'view' => 'supervisor/configuracion',
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function configuracion_tema()
	{
		$this->require_admin_module_permission('supervisor.configuracion.view');
		$this->App_write_model->run_migration();
		$flash_message = '';
		$flash_error = '';

		if ($this->input->method(TRUE) === 'POST')
		{
			$theme_color = trim((string)$this->input->post('theme_color'));
			$accent_color = trim((string)$this->input->post('accent_color'));
			$surface_color = trim((string)$this->input->post('surface_color'));
			$panel_color = trim((string)$this->input->post('panel_color'));

			$this->App_write_model->save_setting('visor_supervisor_theme_color', $theme_color);
			$this->App_write_model->save_setting('visor_supervisor_accent_color', $accent_color);
			$this->App_write_model->save_setting('visor_supervisor_surface_color', $surface_color);
			$this->App_write_model->save_setting('visor_supervisor_panel_color', $panel_color);
			$flash_message = 'Tema actualizado.';
		}

		$data = array(
			'page_title' => 'Tema',
			'current_tab' => 'configuracion',
			'view' => 'supervisor/configuracion_tema',
			'theme_color' => $this->App_write_model->get_setting('visor_supervisor_theme_color', '#4d74c3'),
			'accent_color' => $this->App_write_model->get_setting('visor_supervisor_accent_color', '#5f8fa8'),
			'surface_color' => $this->App_write_model->get_setting('visor_supervisor_surface_color', '#f6f7f9'),
			'panel_color' => $this->App_write_model->get_setting('visor_supervisor_panel_color', '#ffffff'),
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function notificaciones()
	{
		$this->require_admin_module_permission('supervisor.notificaciones.view');
		$this->App_write_model->run_migration();
		$flash_message = '';
		$flash_error = '';
		$draft_message = '';
		$show_create_modal = FALSE;
		$actor = $this->get_supervisor_actor();
		$active_tab = trim((string)$this->input->get('tab'));
		$active_tab = $active_tab === 'reviewed' ? 'reviewed' : 'pending';

		if ($this->input->method(TRUE) === 'POST')
		{
			$action = trim((string)$this->input->post('action_type'));
			$actor = $this->get_supervisor_actor();
			if ($action === 'create')
			{
				$message = trim((string)$this->input->post('message'));
				$draft_message = $message;
				$show_create_modal = TRUE;
				if ($message === '')
				{
					$flash_error = 'Debes escribir un mensaje.';
				}
				else
				{
					if ($this->App_write_model->create_notification((int) $actor['employee_id'], (string) $actor['name'], $message))
					{
						$flash_message = 'Notificacion creada.';
						$active_tab = 'pending';
						$draft_message = '';
						$show_create_modal = FALSE;
					}
					else
					{
						$flash_error = 'No pudimos guardar la notificacion.';
					}
				}
			}
			elseif ($action === 'review')
			{
				$selected_ids = array_values(array_filter(array_map('intval', (array)$this->input->post('notification_ids'))));
				$notification_id = (int)$this->input->post('notification_id');
				if ($notification_id > 0 && empty($selected_ids))
				{
					$selected_ids = array($notification_id);
				}

				if (empty($selected_ids))
				{
					if ($this->input->is_ajax_request())
					{
						return $this->respond_json(array(
							'success' => FALSE,
							'message' => 'Selecciona al menos una notificacion.',
						), 422);
					}
					$flash_error = 'Selecciona al menos una notificacion.';
				}
				else
				{
					$success_count = 0;
					foreach ($selected_ids as $id)
					{
						if ($this->App_write_model->mark_notification_reviewed($id, (int) $actor['employee_id'], (string) $actor['name']))
						{
							$success_count++;
						}
					}
					if ($success_count > 0)
					{
						if ($this->input->is_ajax_request())
						{
							return $this->respond_json(array(
								'success' => TRUE,
								'message' => 'Notificacion revisada.',
								'reviewed_ids' => $selected_ids,
							));
						}
						$flash_message = 'Marcadas '.$success_count.' notificaciones.';
					}
					else
					{
						if ($this->input->is_ajax_request())
						{
							return $this->respond_json(array(
								'success' => FALSE,
								'message' => 'No pudimos actualizar las notificaciones.',
							), 500);
						}
						$flash_error = 'No pudimos actualizar las notificaciones.';
					}
				}
			}
		}

		$notifications = $this->App_write_model->get_notifications();
		$all_employees = $this->Gala_read_model->get_employees_for_visor();
		$filtered = array();
		foreach ($notifications as $notification)
		{
			$is_reviewed = !empty($notification['is_reviewed']);
			if ($active_tab === 'reviewed' && $is_reviewed)
			{
				$filtered[] = $notification;
			}
			if ($active_tab === 'pending' && !$is_reviewed)
			{
				$filtered[] = $notification;
			}
		}

		$data = array(
			'page_title' => 'Notificaciones',
			'current_tab' => 'notificaciones',
			'view' => 'supervisor/notificaciones',
			'notifications' => $filtered,
			'active_tab' => $active_tab,
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
			'draft_message' => $draft_message,
			'show_create_modal' => $show_create_modal,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	public function exportar_notificaciones()
	{
		$notifications = $this->App_write_model->get_notifications();
		$rows = array();
		foreach ($notifications as $notification)
		{
			$rows[] = array(
				'Mensaje' => isset($notification['message']) ? $notification['message'] : '',
				'Creado_por' => isset($notification['created_by_name']) ? $notification['created_by_name'] : '',
				'Creado_el' => isset($notification['created_at']) ? $notification['created_at'] : '',
				'Revisado' => !empty($notification['is_reviewed']) ? 'Si' : 'No',
				'Revisado_por' => isset($notification['reviewed_by_name']) ? $notification['reviewed_by_name'] : '',
				'Revisado_el' => isset($notification['reviewed_at']) ? $notification['reviewed_at'] : '',
			);
		}

		$filename = 'notificaciones_'.date('Ymd_His').'.csv';
		$this->output
			->set_content_type('text/csv')
			->set_header('Content-Disposition: attachment; filename="'.$filename.'"');

		$handle = fopen('php://output', 'w');
		fputcsv($handle, array('Mensaje', 'Creado_por', 'Creado_el', 'Revisado', 'Revisado_por', 'Revisado_el'));
		foreach ($rows as $row)
		{
			fputcsv($handle, $row);
		}
		fclose($handle);
	}

	private function render_placeholder($title, $tab_key)
	{
		$data = array(
			'page_title' => $title,
			'current_tab' => $tab_key,
			'view' => 'supervisor/placeholder',
			'placeholder_title' => $title,
		);
		$this->load->view('layouts/supervisor', $data);
	}

	private function handle_goal_definition_form($goal_id = null, $form_context = 'default')
	{
		$this->App_write_model->run_migration();
		$goal = $goal_id ? $this->App_write_model->get_goal_definition((int)$goal_id) : null;
		if ($goal_id && !$goal)
		{
			show_404();
		}
		$is_global_context = $form_context === 'global';
		if ($is_global_context && $goal && !empty($goal['employee_mode']) && (string) $goal['employee_mode'] !== 'global')
		{
			show_404();
		}
		if (!$is_global_context && $goal && (!empty($goal['employee_mode']) && (string) $goal['employee_mode'] === 'global'))
		{
			show_404();
		}
		if ($goal && !isset($goal['group_ids']))
		{
			$group_ids = $this->App_write_model->get_goal_definition_group_ids((int)$goal['id']);
			if (empty($group_ids) && !empty($goal['group_id']))
			{
				$group_ids = array((int)$goal['group_id']);
			}
			$goal['group_ids'] = $group_ids;
		}

		$options = $this->get_goal_form_options();
		$this->form_validation->set_rules('name', 'Nombre', 'trim|required');
		$this->form_validation->set_rules('period_type', 'Periodo', 'trim|required');
		$this->form_validation->set_rules('scope_type', 'Filtro', 'trim|required');
		$this->form_validation->set_rules('metric_type', 'Medicion', 'trim|required');
		$this->form_validation->set_rules('goal_method', 'Metodo', 'trim|required');
		$this->form_validation->set_rules('display_mode', 'Estilo', 'trim|required');
		if ($is_global_context)
		{
			$_POST['employee_mode'] = 'global';
		}
		$this->form_validation->set_rules('employee_mode', 'Aplicacion', 'trim|required');
		$this->form_validation->set_rules('goal_amount', 'Monto meta', 'trim|numeric');
		$this->form_validation->set_rules('goal_percent', 'Porcentaje', 'trim|numeric');
		$this->form_validation->set_rules('sort_order', 'Orden', 'trim|integer');
		$goal_error = '';

		if ($this->input->method(TRUE) === 'POST' && $this->form_validation->run())
		{
			$scope_type = trim((string)$this->input->post('scope_type'));
			$scope_id = null;
			switch ($scope_type)
			{
				case 'category':
					$scope_id = $this->input->post('scope_category_id');
					break;
				case 'item':
					$scope_id = $this->input->post('scope_item_id');
					break;
				case 'manufacturer':
					$scope_id = $this->input->post('scope_manufacturer_id');
					break;
				case 'supplier':
					$scope_id = $this->input->post('scope_supplier_id');
					break;
				default:
					$scope_id = null;
					break;
			}

			$run_once = $this->input->post('run_once') ? 1 : 0;
			$run_once_mode = $run_once ? trim((string)$this->input->post('run_once_mode')) : null;
			$custom_period_start_date = null;
			$custom_period_end_date = null;
			if ($this->input->post('period_type') === 'custom')
			{
				$custom_period_start_date = trim((string)$this->input->post('custom_period_start_date'));
				$custom_period_end_date = trim((string)$this->input->post('custom_period_end_date'));
				if ($custom_period_start_date === '' || $custom_period_end_date === '')
				{
					$goal_error = 'Debes indicar fecha inicial y fecha final para el periodo personalizado.';
				}
				elseif ($custom_period_end_date < $custom_period_start_date)
				{
					$goal_error = 'La fecha final del periodo no puede ser menor que la fecha inicial.';
				}
				if ($run_once && $run_once_mode === 'period')
				{
					$run_once_mode = 'custom';
				}
			}

			$run_once_start_date = null;
			$run_once_end_date = null;
			if ($run_once && $run_once_mode === 'custom')
			{
				$run_once_start_date = $this->input->post('period_type') === 'custom'
					? $custom_period_start_date
					: trim((string)$this->input->post('run_once_start_date'));
				$run_once_end_date = $this->input->post('period_type') === 'custom'
					? $custom_period_end_date
					: trim((string)$this->input->post('run_once_end_date'));
				if ($run_once_start_date === '' || $run_once_end_date === '')
				{
					$goal_error = 'Debes indicar fecha inicial y fecha final para la duracion personalizada.';
				}
				elseif ($run_once_end_date < $run_once_start_date)
				{
					$goal_error = 'La fecha final no puede ser menor que la fecha inicial.';
				}
			}

			$employee_mode = $is_global_context ? 'global' : trim((string)$this->input->post('employee_mode'));
			if ($goal_error === '' && !$is_global_context && $employee_mode === 'global')
			{
				$goal_error = 'Las metas globales se gestionan desde el modulo Global.';
			}
			$selected_group_ids = array_values(array_filter(array_map('intval', (array)$this->input->post('group_ids')), function ($group_id) {
				return $group_id > 0;
			}));
			if ($goal_error === '' && $employee_mode === 'group' && empty($selected_group_ids))
			{
				$goal_error = 'Debes seleccionar al menos un grupo para esta meta.';
			}

			if ($goal_error === '')
			{
				$payload = array(
					'name' => $this->input->post('name'),
					'is_active' => $this->input->post('is_active') ? 1 : 0,
					'period_type' => $this->input->post('period_type'),
					'custom_period_start_date' => $custom_period_start_date,
					'custom_period_end_date' => $custom_period_end_date,
					'scope_type' => $scope_type,
					'scope_id' => $scope_id,
					'metric_type' => $this->input->post('metric_type'),
					'goal_method' => $this->input->post('goal_method'),
					'goal_amount' => (float)$this->input->post('goal_amount'),
					'goal_percent' => (float)$this->input->post('goal_percent'),
					'display_mode' => $this->input->post('display_mode'),
					'employee_mode' => $employee_mode,
					'employee_id' => $employee_mode === 'employee' ? (int)$this->input->post('employee_id') : null,
					'group_id' => $employee_mode === 'group' && !empty($selected_group_ids) ? (int)$selected_group_ids[0] : null,
					'target_scope' => $employee_mode === 'group' ? trim((string)$this->input->post('target_scope')) : 'individual',
					'sort_order' => (int)$this->input->post('sort_order'),
					'run_once' => $run_once,
					'run_once_mode' => $run_once_mode,
					'run_once_start_date' => $run_once_start_date,
					'run_once_end_date' => $run_once_end_date,
				);
				if ($is_global_context)
				{
					$payload['employee_mode'] = 'global';
					$payload['employee_id'] = null;
					$payload['group_id'] = null;
					$payload['target_scope'] = 'individual';
					$selected_group_ids = array();
				}

				$saved_goal_id = $this->App_write_model->save_goal_definition($payload, $goal_id);
				if ($saved_goal_id && $employee_mode === 'group')
				{
					$this->App_write_model->replace_goal_definition_groups($saved_goal_id, $selected_group_ids);
				}
				elseif ($saved_goal_id)
				{
					$this->App_write_model->replace_goal_definition_groups($saved_goal_id, array());
				}
				redirect($is_global_context ? 'global' : 'metas');
			}
		}

		$data = array(
			'page_title' => $goal ? ($is_global_context ? 'Editar meta global' : 'Editar meta') : ($is_global_context ? 'Nueva meta global' : 'Nueva meta'),
			'current_tab' => $is_global_context ? 'global' : 'metas',
			'view' => 'supervisor/meta_form',
			'goal' => $goal,
			'goal_options' => $options,
			'goal_error' => $goal_error,
			'form_context' => $form_context,
			'return_route' => $is_global_context ? 'global' : 'metas',
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	private function handle_tarea_rule_form($rule_id = null, $form_context = 'default')
	{
		if ($form_context !== 'global')
		{
			$this->require_admin_module_permission('supervisor.tareas.rules.view');
		}
		$rule = $rule_id ? $this->Tasks_model->get_rule((int)$rule_id) : null;
		if ($rule_id && !$rule)
		{
			show_404();
		}
		$is_global_context = $form_context === 'global';
		if ($is_global_context && $rule && (!isset($rule['assignment_mode']) || (string) $rule['assignment_mode'] !== 'global'))
		{
			show_404();
		}
		if (!$is_global_context && $rule && (!empty($rule['assignment_mode']) && (string) $rule['assignment_mode'] === 'global'))
		{
			show_404();
		}

		$templates = $this->Tasks_model->get_templates(TRUE);
		$special_dates = $this->Tasks_model->get_special_dates();
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		if ($is_global_context)
		{
			$_POST['assignment_mode'] = 'global';
		}
		$selected_assignment_mode = (string) $this->input->post('assignment_mode');
		if ($selected_assignment_mode === '' && !empty($rule['assignment_mode']))
		{
			$selected_assignment_mode = (string) $rule['assignment_mode'];
		}
		$selected_recurrence = (string) $this->input->post('recurrence_type');
		if ($selected_recurrence === '' && !empty($rule['recurrence_type']))
		{
			$selected_recurrence = (string) $rule['recurrence_type'];
		}

		$this->form_validation->set_rules('template_id', 'Plantilla', 'required|integer');
		$this->form_validation->set_rules('title', 'Nombre de la tarea', 'trim');
		$this->form_validation->set_rules('assignment_mode', 'Modo de asignacion', 'required');
		$this->form_validation->set_rules('recurrence_type', 'Recurrencia', 'required');
		if ($selected_assignment_mode === 'group')
		{
			$this->form_validation->set_rules('group_id', 'Grupo', 'required|integer');
		}
		elseif ($selected_assignment_mode !== 'global')
		{
			$this->form_validation->set_rules('employee_id', 'Empleado', 'required|integer');
		}

		if ($selected_recurrence === 'special_date')
		{
			$this->form_validation->set_rules('special_date_code', 'Fecha especial', 'required');
			$this->form_validation->set_rules('special_date_lead_days', 'Dias de anticipacion', 'trim|integer');
		}
		elseif ($selected_recurrence === 'one_time')
		{
			$this->form_validation->set_rules('one_time_date', 'Fecha unica', 'required');
		}
		else
		{
			$this->form_validation->set_rules('starts_on', 'Fecha base de inicio', 'required');
		}

		if ($this->input->method(TRUE) === 'POST' && $this->form_validation->run())
		{
			if (!$is_global_context && (string) $this->input->post('assignment_mode') === 'global')
			{
				show_error('Las tareas globales se gestionan desde el modulo Global.', 403, 'Acceso denegado');
			}

			$saved_rule_id = $this->Tasks_model->save_rule(array(
				'title' => $this->input->post('title'),
				'template_id' => $this->input->post('template_id'),
				'assignment_mode' => $is_global_context ? 'global' : $this->input->post('assignment_mode'),
				'employee_id' => $is_global_context ? null : $this->input->post('employee_id'),
				'group_id' => $is_global_context ? null : $this->input->post('group_id'),
				'group_distribution_mode' => $is_global_context ? 'all_members' : $this->input->post('group_distribution_mode'),
				'distribution_algorithm' => $is_global_context ? 'random' : $this->input->post('distribution_algorithm'),
				'recurrence_type' => $this->input->post('recurrence_type'),
				'special_date_code' => $this->input->post('special_date_code'),
				'special_date_lead_days' => $this->input->post('special_date_lead_days'),
				'one_time_date' => $this->input->post('one_time_date'),
				'starts_on' => $this->input->post('starts_on'),
				'ends_on' => $this->input->post('ends_on'),
				'requires_note_on_complete' => $this->input->post('requires_note_on_complete') ? 1 : 0,
				'is_active' => $this->input->post('is_active') ? 1 : 0,
			), $rule_id);

			$this->Tasks_model->replace_rule_excluded_items($saved_rule_id, (array) $this->input->post('ignored_item_ids'));
			$this->Tasks_model->sync_due_assignments();
			redirect($is_global_context ? 'global' : 'tareas/configuradas');
		}

		$data = array(
			'page_title' => !empty($rule['id']) ? ($is_global_context ? 'Editar tarea global' : 'Editar tarea') : ($is_global_context ? 'Nueva tarea global' : 'Nueva tarea'),
			'current_tab' => $is_global_context ? 'global' : 'tareas',
			'view' => 'supervisor/tarea_rule_form',
			'rule' => $rule,
			'templates' => $templates,
			'employees' => $employees,
			'groups' => $groups,
			'special_dates' => $special_dates,
			'ignored_item_ids' => !empty($rule['excluded_item_ids']) ? array_map('intval', $rule['excluded_item_ids']) : array(),
			'form_context' => $form_context,
			'return_route' => $is_global_context ? 'global' : 'tareas/configuradas',
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	private function handle_tarea_template_form($template_id = null)
	{
		$this->require_admin_module_permission('supervisor.tareas.templates.view');
		$template = $template_id ? $this->Tasks_model->get_template((int)$template_id) : null;
		if ($template_id && !$template)
		{
			show_404();
		}

		$this->form_validation->set_rules('name', 'Nombre', 'trim|required');
		$this->form_validation->set_rules('description', 'Descripcion', 'trim');
		$this->form_validation->set_rules('items_text', 'Subtareas', 'trim');

		if ($this->input->method(TRUE) === 'POST' && $this->form_validation->run())
		{
			$saved_template_id = $this->Tasks_model->save_template(array(
				'name' => $this->input->post('name'),
				'description' => $this->input->post('description'),
				'is_active' => $this->input->post('is_active') ? 1 : 0,
			), $template_id);

			$this->Tasks_model->replace_template_items($saved_template_id, $this->parse_task_items_text($this->input->post('items_text')));
			redirect('tareas/plantillas');
		}

		$items_text = '';
		if ($template && !empty($template['assignable_items']))
		{
			$names = array();
			foreach ($template['assignable_items'] as $item)
			{
				$names[] = isset($item['name']) ? $item['name'] : '';
			}
			$items_text = implode(PHP_EOL, $names);
		}

		$data = array(
			'page_title' => !empty($template['id']) ? 'Editar plantilla' : 'Nueva plantilla',
			'current_tab' => 'tareas',
			'view' => 'supervisor/tarea_template_form',
			'template' => $template,
			'items_text' => $items_text,
			'return_route' => 'tareas/plantillas',
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	private function parse_task_items_text($text)
	{
		$text = str_replace("\r\n", "\n", (string) $text);
		$lines = explode("\n", $text);
		$items = array();
		foreach ($lines as $line)
		{
			$raw_line = rtrim((string) $line);
			if (trim($raw_line) === '')
			{
				continue;
			}

			$name = preg_replace('/^\s*-\s*/', '', trim($raw_line));
			if ($name !== '')
			{
				$key = 'item:'.$name.':'.count($items);
				$items[$key] = array(
					'name' => $name,
					'level' => 0,
				);
			}
		}

		return array_values($items);
	}

	private function build_employee_groups_index($groups, $group_members_index)
	{
		$index = array();
		foreach ($groups as $group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			if ($group_id <= 0 || empty($group_members_index[$group_id]))
			{
				continue;
			}

			foreach ($group_members_index[$group_id] as $employee_id)
			{
				if (!isset($index[$employee_id]))
				{
					$index[$employee_id] = array();
				}

				$index[$employee_id][] = array(
					'id' => $group_id,
					'name' => isset($group['name']) ? $group['name'] : 'Grupo',
					'color' => !empty($group['color']) ? $group['color'] : '#4d74c3',
				);
			}
		}

		return $index;
	}

	private function get_goal_form_options()
	{
		return array(
			'period_types' => array(
				'daily' => 'Diaria',
				'weekly' => 'Semanal',
				'biweekly' => 'Quincenal',
				'monthly' => 'Mensual',
				'custom' => 'Personalizado',
			),
			'scope_types' => array(
				'general' => 'Monto general',
				'category' => 'Categoria',
				'item' => 'Articulo',
				'manufacturer' => 'Fabricante',
				'supplier' => 'Proveedor',
			),
			'metric_types' => array(
				'amount' => 'Monto vendido',
				'quantity' => 'Cantidad vendida',
			),
			'goal_methods' => array(
				'fixed' => 'Monto fijo',
				'last_year_same_period' => 'Ano pasado',
				'last_year_same_period_plus_percent' => 'Ano pasado + porcentaje',
				'competitive_leader' => 'Competitivo contra lider',
				'competitive_target' => 'Competitivo por monto objetivo',
			),
			'display_modes' => array(
				'bar' => 'Barra',
				'number' => 'Numero',
				'rank' => 'Puesto',
			),
			'employee_modes' => array(
				'global' => 'Todos',
				'employee' => 'Empleado especifico',
				'group' => 'Grupo',
			),
			'target_scopes' => array(
				'individual' => 'Meta individual del grupo',
				'group_total' => 'Total del grupo',
			),
			'run_once_modes' => array(
				'period' => 'Solo este periodo',
				'custom' => 'Personalizado',
			),
			'categories' => $this->Gala_read_model->get_goal_categories(),
			'items' => $this->Gala_read_model->get_goal_items(),
			'manufacturers' => $this->Gala_read_model->get_goal_manufacturers(),
			'suppliers' => $this->Gala_read_model->get_goal_suppliers(),
			'employees' => $this->Gala_read_model->get_employees_for_visor(),
			'groups' => $this->App_write_model->get_employee_groups(TRUE),
		);
	}
	private function normalize_permission_post($posted_permissions, $defaults)
	{
		$normalized_posted = array();
		foreach ((array)$posted_permissions as $permission_key => $value)
		{
			$normalized_key = str_replace('__dot__', '.', (string)$permission_key);
			$normalized_posted[$normalized_key] = $value;
		}

		$state = array();
		foreach ((array)$defaults as $permission_key => $default_value)
		{
			$state[$permission_key] = !empty($normalized_posted[$permission_key]);
		}
		return $state;
	}

	private function resolve_permission_state($stored_permissions, $defaults, $labels)
	{
		$state = array();
		foreach ((array)$labels as $permission_key => $label)
		{
			if (array_key_exists($permission_key, (array)$stored_permissions))
			{
				$state[$permission_key] = !empty($stored_permissions[$permission_key]);
			}
			elseif (array_key_exists($permission_key, (array)$defaults))
			{
				$state[$permission_key] = !empty($defaults[$permission_key]);
			}
			else
			{
				$state[$permission_key] = FALSE;
			}
		}
		return $state;
	}


	private function get_default_visor_permissions()
	{
		return array(
			'dashboard.view' => TRUE,
			'notificar.view' => TRUE,
			'tareas.view' => TRUE,
			'ventas.view' => TRUE,
			'ventas.rank.view' => TRUE,
			'metas.view' => TRUE,
			'metas.report.view' => TRUE,
			'personalizar.view' => TRUE,
			'personalizar.visor.view' => TRUE,
			'personalizar.aplicacion.view' => TRUE,
			'configuracion.view' => FALSE,
			'configuracion.descuentos.view' => FALSE,
			'configuracion.grupos.view' => FALSE,
			'configuracion.metas.view' => FALSE,
			'configuracion.tareas.view' => FALSE,
			'configuracion.notificaciones.view' => FALSE,
			'configuracion.ventas.view' => FALSE,
			'configuracion.empleados.view' => FALSE,
		);
	}

	private function get_admin_permission_labels()
	{
		return array(
			'supervisor.dashboard.view' => 'Puede ver Dashboard',
			'supervisor.asistencia.history.view' => 'Puede cambiar mes en Asistencia',
			'supervisor.asistencia.view' => 'Puede ver Asistencia',
			'supervisor.metas.view' => 'Puede ver Metas',
			'supervisor.tareas.view' => 'Puede ver Tareas',
			'supervisor.tareas.templates.view' => 'Puede ver Plantillas de tareas',
			'supervisor.tareas.rules.view' => 'Puede ver Tareas configuradas',
			'supervisor.tareas.export.view' => 'Puede exportar CSV de tareas',
			'supervisor.tareas.verified.reactivate' => 'Puede reactivar tareas verificadas',
			'supervisor.grupos.view' => 'Puede ver Grupos',
			'supervisor.empleados.view' => 'Puede ver Empleados',
			'supervisor.notificaciones.view' => 'Puede ver Notificaciones',
			'supervisor.global.view' => 'Puede ver Global',
			'supervisor.configuracion.view' => 'Puede ver Configuracion',
		);
	}

	private function get_admin_permission_groups()
	{
		return array(
			'Visores' => array(
				'supervisor.dashboard.view',
				'supervisor.asistencia.history.view',
				'supervisor.asistencia.view',
				'supervisor.metas.view',
				'supervisor.tareas.view',
				'supervisor.tareas.templates.view',
				'supervisor.tareas.rules.view',
				'supervisor.tareas.export.view',
				'supervisor.grupos.view',
				'supervisor.tareas.verified.reactivate',
				'supervisor.empleados.view',
				'supervisor.notificaciones.view',
				'supervisor.global.view',
				'supervisor.configuracion.view',
			),
		);
	}

		protected function build_employee_points_activity($employee_id, $limit = 120)
	{
		$employee_id = (int)$employee_id;
		$limit = (int)$limit;
		if ($employee_id <= 0)
		{
			return array();
		}
		if ($limit <= 0)
		{
			$limit = 120;
		}

		$history = array();
		foreach ((array)$this->App_write_model->get_employee_goal_completion_history($employee_id, $limit) as $goal)
		{
			$occurred_at = !empty($goal['updated_at']) ? (string)$goal['updated_at'] : (!empty($goal['created_at']) ? (string)$goal['created_at'] : (isset($goal['period_end']) ? (string)$goal['period_end'] : ''));
			$history[] = array(
				'type' => 'goal',
				'type_label' => 'Meta completada',
				'points_delta' => 1,
				'item_name' => !empty($goal['goal_name']) ? (string)$goal['goal_name'] : 'Meta',
				'detail' => 'Meta alcanzada',
				'occurred_at' => $occurred_at,
			);
		}

		foreach ((array)$this->App_write_model->get_employee_task_completion_history($employee_id, $limit) as $task)
		{
			$history[] = array(
				'type' => 'task',
				'type_label' => 'Tarea completada',
				'points_delta' => 1,
				'item_name' => !empty($task['title']) ? (string)$task['title'] : 'Tarea',
				'detail' => !empty($task['description']) ? (string)$task['description'] : '',
				'occurred_at' => !empty($task['completed_at']) ? (string)$task['completed_at'] : (isset($task['assigned_date']) ? (string)$task['assigned_date'] : ''),
			);
		}

		usort($history, function ($left, $right) {
			$left_time = !empty($left['occurred_at']) ? strtotime((string)$left['occurred_at']) : 0;
			$right_time = !empty($right['occurred_at']) ? strtotime((string)$right['occurred_at']) : 0;
			if ($left_time === $right_time)
			{
				return strcasecmp((string)$left['item_name'], (string)$right['item_name']);
			}
			return ($left_time < $right_time) ? 1 : -1;
		});

		if (count($history) > $limit)
		{
			$history = array_slice($history, 0, $limit);
		}

		$running_total = 0;
		$ordered = array_reverse($history);
		foreach ($ordered as &$row)
		{
			$running_total += isset($row['points_delta']) ? (int)$row['points_delta'] : 0;
			$row['balance_after'] = $running_total;
			$row['occurred_at_label'] = !empty($row['occurred_at']) ? date('d/m/Y H:i', strtotime((string)$row['occurred_at'])) : '-';
		}
		unset($row);

		return array_reverse($ordered);
	}
private function get_default_admin_permissions()
	{
		$defaults = array();
		foreach ($this->get_admin_permission_labels() as $permission_key => $label)
		{
			$defaults[$permission_key] = TRUE;
		}
		return $defaults;
	}
	private function handle_grupo_form($group_id = null)
	{
		$this->App_write_model->run_migration();
		$group = $group_id ? $this->App_write_model->get_employee_group((int)$group_id) : null;
		if ($group_id && !$group)
		{
			show_404();
		}

		$employees = $this->get_statistics_employees(TRUE);
		$selected_members = $group ? $this->App_write_model->get_employee_group_members((int)$group['id']) : array();
		$flash_message = '';
		$flash_error = '';

		if ($this->input->method(TRUE) === 'POST')
		{
			$name = trim((string)$this->input->post('name'));
			if ($name === '')
			{
				$flash_error = 'Debes indicar un nombre.';
			}
			else
			{
				$saved_group_id = $this->App_write_model->save_employee_group(array(
					'name' => $name,
					'is_active' => $this->input->post('is_active') ? 1 : 0,
					'color' => $this->input->post('color'),
					'sort_order' => (int)$this->input->post('sort_order'),
				), $group_id);
				$this->App_write_model->replace_employee_group_members($saved_group_id, (array)$this->input->post('members'));
				$group_id = $saved_group_id;
				$group = $this->App_write_model->get_employee_group((int)$group_id);
				$selected_members = $this->App_write_model->get_employee_group_members((int)$group_id);
				$flash_message = 'Grupo guardado.';
			}
		}

		$data = array(
			'page_title' => $group ? 'Editar grupo' : 'Nuevo grupo',
			'current_tab' => 'grupos',
			'view' => 'supervisor/grupo_form',
			'group' => $group,
			'employees' => $employees,
			'selected_members' => $selected_members,
			'flash_message' => $flash_message,
			'flash_error' => $flash_error,
		);
		$this->hydrate_theme_settings($data);
		$this->load->view('layouts/supervisor', $data);
	}

	private function hydrate_theme_settings(array &$data)
	{
		$data['theme_color'] = $this->App_write_model->get_setting('visor_supervisor_theme_color', '#4d74c3');
		$accent_color = $this->App_write_model->get_setting('visor_supervisor_accent_color', '#5f8fa8');
		if ($accent_color === '#f47d2b')
		{
			$accent_color = '#5f8fa8';
		}
		$data['accent_color'] = $accent_color;
		$data['surface_color'] = $this->App_write_model->get_setting('visor_supervisor_surface_color', '#f6f7f9');
		$data['panel_color'] = $this->App_write_model->get_setting('visor_supervisor_panel_color', '#ffffff');
		$data['can_manage_global'] = $this->has_global_module_access();
		$data['allowed_supervisor_tabs'] = $this->get_supervisor_allowed_tabs();
	}

	private function get_permission_labels()
	{
		return array(
			'dashboard.view' => 'Puede ver Dashboard',
			'notificar.view' => 'Puede ver Notificar',
			'tareas.view' => 'Puede ver Tareas',
			'ventas.view' => 'Puede ver Ventas',
			'ventas.rank.view' => 'Ventas - Puede ver Posicion',
			'metas.view' => 'Puede ver Metas',
			'metas.report.view' => 'Metas - Puede ver boton Reporte',
			'personalizar.view' => 'Puede ver Personalizar',
			'personalizar.visor.view' => 'Personalizar - Visor',
			'personalizar.aplicacion.view' => 'Personalizar - Aplicacion',
			'configuracion.view' => 'Puede ver Configuracion',
			'configuracion.descuentos.view' => 'Configuracion - Descuentos',
			'configuracion.grupos.view' => 'Configuracion - Grupos',
			'configuracion.metas.view' => 'Configuracion - Metas',
			'configuracion.tareas.view' => 'Configuracion - Tareas',
			'configuracion.notificaciones.view' => 'Configuracion - Notificaciones',
			'configuracion.ventas.view' => 'Configuracion - Ventas',
			'configuracion.empleados.view' => 'Configuracion - Empleados',
		);
	}

	private function get_permission_groups()
	{
		return array(
			'General' => array('dashboard.view', 'notificar.view'),
			'Tareas' => array('tareas.view'),
			'Ventas' => array('ventas.view', 'ventas.rank.view'),
			'Metas' => array('metas.view', 'metas.report.view'),
			'Personalizar' => array('personalizar.view', 'personalizar.visor.view', 'personalizar.aplicacion.view'),
			'Configuracion' => array(
				'configuracion.view',
				'configuracion.descuentos.view',
				'configuracion.grupos.view',
				'configuracion.metas.view',
				'configuracion.tareas.view',
				'configuracion.notificaciones.view',
				'configuracion.ventas.view',
				'configuracion.empleados.view',
			),
		);
	}

	private function get_goal_selected_group_ids($definition)
	{
		if (!empty($definition['group_ids']))
		{
			return array_map('intval', (array)$definition['group_ids']);
		}
		if (!empty($definition['group_id']))
		{
			return array((int)$definition['group_id']);
		}
		return array();
	}

	private function resolve_employee_matching_group_id($employee_id, $group_ids, $group_members_index)
	{
		foreach ((array)$group_ids as $group_id)
		{
			$members = isset($group_members_index[(int)$group_id]) ? $group_members_index[(int)$group_id] : array();
			if (in_array((int)$employee_id, $members, TRUE))
			{
				return (int)$group_id;
			}
		}

		return 0;
	}

	private function goal_applies_to_employee($definition, $employee_id, $employee_groups_index)
	{
		if (empty($definition['employee_mode']) || $definition['employee_mode'] === 'global')
		{
			return TRUE;
		}

		if ($definition['employee_mode'] === 'group')
		{
			$group_ids = $this->get_goal_selected_group_ids($definition);
			if (empty($group_ids))
			{
				return FALSE;
			}
			foreach ($group_ids as $group_id)
			{
				$memberships = isset($employee_groups_index[(int)$employee_id]) ? $employee_groups_index[(int)$employee_id] : array();
				foreach ($memberships as $membership)
				{
					if ((int)$membership['id'] === (int)$group_id)
					{
						return TRUE;
					}
				}
			}
			return FALSE;
		}

		return (int)$definition['employee_id'] === (int)$employee_id;
	}

	private function goal_is_currently_available($definition, $reference_date = null)
	{
		if (empty($definition['run_once']) || empty($definition['run_once_mode']) || $definition['run_once_mode'] !== 'custom')
		{
			return TRUE;
		}

		$today = $reference_date ? date('Y-m-d', strtotime($reference_date)) : date('Y-m-d');
		$start_date = !empty($definition['run_once_start_date']) ? (string)$definition['run_once_start_date'] : '';
		$end_date = !empty($definition['run_once_end_date']) ? (string)$definition['run_once_end_date'] : '';

		if ($start_date !== '' && $today < $start_date)
		{
			return FALSE;
		}

		if ($end_date !== '' && $today > $end_date)
		{
			return FALSE;
		}

		return TRUE;
	}

	private function get_goal_period_range($period_type, $definition = array(), $reference_date = null)
	{
		$timezone = new DateTimeZone('America/Caracas');
		$today = $reference_date ? new DateTime($reference_date.' 12:00:00', $timezone) : new DateTime('now', $timezone);
		switch ((string)$period_type)
		{
			case 'custom':
				$start_value = !empty($definition['custom_period_start_date']) ? $definition['custom_period_start_date'] : $today->format('Y-m-d');
				$end_value = !empty($definition['custom_period_end_date']) ? $definition['custom_period_end_date'] : $start_value;
				$start = new DateTime($start_value.' 00:00:00', $timezone);
				$end = new DateTime($end_value.' 23:59:59', $timezone);
				break;
			case 'weekly':
				$start = clone $today;
				$start->modify('monday this week')->setTime(0, 0, 0);
				$end = clone $start;
				$end->modify('+6 days')->setTime(23, 59, 59);
				break;
			case 'biweekly':
				$day = (int)$today->format('d');
				if ($day <= 15)
				{
					$start = new DateTime($today->format('Y-m-01 00:00:00'), $timezone);
					$end = new DateTime($today->format('Y-m-15 23:59:59'), $timezone);
				}
				else
				{
					$start = new DateTime($today->format('Y-m-16 00:00:00'), $timezone);
					$end = new DateTime($today->format('Y-m-t 23:59:59'), $timezone);
				}
				break;
			case 'monthly':
				$start = new DateTime($today->format('Y-m-01 00:00:00'), $timezone);
				$end = new DateTime($today->format('Y-m-t 23:59:59'), $timezone);
				break;
			case 'daily':
			default:
				$start = new DateTime($today->format('Y-m-d 00:00:00'), $timezone);
				$end = new DateTime($today->format('Y-m-d 23:59:59'), $timezone);
				break;
		}

		return array($start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'));
	}

	private function get_goal_historical_period_range($period_type, $definition = array(), $reference_date = null)
	{
		$timezone = new DateTimeZone('America/Caracas');
		$today = $reference_date ? new DateTime($reference_date.' 12:00:00', $timezone) : new DateTime('now', $timezone);
		switch ((string)$period_type)
		{
			case 'custom':
				$start_value = !empty($definition['custom_period_start_date']) ? $definition['custom_period_start_date'] : $today->format('Y-m-d');
				$end_value = !empty($definition['custom_period_end_date']) ? $definition['custom_period_end_date'] : $start_value;
				$start = new DateTime($start_value.' 00:00:00', $timezone);
				$end = new DateTime($end_value.' 23:59:59', $timezone);
				$start->modify('-1 year');
				$end->modify('-1 year');
				break;
			case 'weekly':
				$isoWeek = (int)$today->format('W');
				$isoYear = (int)$today->format('o') - 1;
				$start = new DateTime('now', $timezone);
				$start->setISODate($isoYear, $isoWeek, 1)->setTime(0, 0, 0);
				$end = clone $start;
				$end->modify('+6 days')->setTime(23, 59, 59);
				break;
			case 'biweekly':
				$year = (int)$today->format('Y') - 1;
				$month = (int)$today->format('m');
				$day = (int)$today->format('d');
				if ($day <= 15)
				{
					$start = new DateTime(sprintf('%04d-%02d-01 00:00:00', $year, $month), $timezone);
					$end = new DateTime(sprintf('%04d-%02d-15 23:59:59', $year, $month), $timezone);
				}
				else
				{
					$start = new DateTime(sprintf('%04d-%02d-16 00:00:00', $year, $month), $timezone);
					$end = new DateTime(sprintf('%04d-%02d-01 00:00:00', $year, $month), $timezone);
					$end->modify('last day of this month')->setTime(23, 59, 59);
				}
				break;
			case 'monthly':
				$year = (int)$today->format('Y') - 1;
				$month = (int)$today->format('m');
				$start = new DateTime(sprintf('%04d-%02d-01 00:00:00', $year, $month), $timezone);
				$end = clone $start;
				$end->modify('last day of this month')->setTime(23, 59, 59);
				break;
			case 'daily':
			default:
				$isoWeek = (int)$today->format('W');
				$isoDay = (int)$today->format('N');
				$isoYear = (int)$today->format('o') - 1;
				$start = new DateTime('now', $timezone);
				$start->setISODate($isoYear, $isoWeek, $isoDay)->setTime(0, 0, 0);
				$end = clone $start;
				$end->setTime(23, 59, 59);
				break;
		}

		return array($start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'));
	}

	private function needs_goal_ranking($definition)
	{
		$method = isset($definition['goal_method']) ? $definition['goal_method'] : 'fixed';
		$display = isset($definition['display_mode']) ? $definition['display_mode'] : 'bar';
		return in_array($method, array('competitive_leader', 'competitive_target'), TRUE) || $display === 'rank';
	}

	private function resolve_goal_target_value($definition, $employee_id, $date_start, $date_end, $reference_date = null, $employee_group_id = 0)
	{
		$method = isset($definition['goal_method']) ? $definition['goal_method'] : 'fixed';
		$selected_group_ids = $this->get_goal_selected_group_ids($definition);
		$is_group_total = !empty($definition['employee_mode'])
			&& $definition['employee_mode'] === 'group'
			&& !empty($employee_group_id)
			&& isset($definition['target_scope'])
			&& $definition['target_scope'] === 'group_total';

		switch ($method)
		{
			case 'last_year_same_period':
				list($history_start, $history_end) = $this->get_goal_historical_period_range(
					isset($definition['period_type']) ? $definition['period_type'] : 'daily',
					$definition,
					$reference_date
				);
				if ($is_group_total)
				{
					return $this->get_group_goal_metric_value_cached($employee_group_id, $definition, $history_start, $history_end);
				}
				return $this->get_goal_metric_for_employee_cached(
					$employee_id,
					isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
					isset($definition['scope_id']) ? $definition['scope_id'] : null,
					isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
					$history_start,
					$history_end
				);
			case 'last_year_same_period_plus_percent':
				list($history_start, $history_end) = $this->get_goal_historical_period_range(
					isset($definition['period_type']) ? $definition['period_type'] : 'daily',
					$definition,
					$reference_date
				);
				$base_value = $is_group_total
					? $this->get_group_goal_metric_value_cached($employee_group_id, $definition, $history_start, $history_end)
					: $this->get_goal_metric_for_employee_cached(
						$employee_id,
						isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
						isset($definition['scope_id']) ? $definition['scope_id'] : null,
						isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
						$history_start,
						$history_end
					);
				$percent = isset($definition['goal_percent']) ? (float)$definition['goal_percent'] : 0.0;
				return $base_value + ($base_value * ($percent / 100));
			case 'competitive_leader':
			case 'competitive_target':
				$ranking = $is_group_total
					? $this->get_group_goal_metric_ranking_cached($definition, $date_start, $date_end, $selected_group_ids)
					: $this->get_goal_metric_ranking_cached(
						isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
						isset($definition['scope_id']) ? $definition['scope_id'] : null,
						isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
						$date_start,
						$date_end
					);
				return !empty($ranking) && isset($ranking[0]['metric_value']) ? (float)$ranking[0]['metric_value'] : 0.0;
			case 'fixed':
			default:
				return isset($definition['goal_amount']) ? (float)$definition['goal_amount'] : 0.0;
		}
	}

	private function get_group_goal_metric_value($group_id, $definition, $date_start, $date_end)
	{
		$group_id = (int)$group_id;
		if ($group_id <= 0)
		{
			return 0.0;
		}

		$members = $this->App_write_model->get_employee_group_members($group_id);
		$total = 0.0;
		foreach ($members as $member_id)
		{
			$total += $this->get_goal_metric_for_employee_cached(
				(int)$member_id,
				isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
				isset($definition['scope_id']) ? $definition['scope_id'] : null,
				isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
				$date_start,
				$date_end
			);
		}

		return (float)$total;
	}

	private function get_goal_metric_for_employee_cached($employee_id, $scope_type, $scope_id, $metric_type, $date_start, $date_end)
	{
		$key = implode('|', array((int)$employee_id, (string)$scope_type, (string)$scope_id, (string)$metric_type, (string)$date_start, (string)$date_end));
		if (!array_key_exists($key, $this->goalMetricEmployeeCache))
		{
			$all_totals = $this->get_goal_metric_all_employees_cached($scope_type, $scope_id, $metric_type, $date_start, $date_end);
			$employee_id = (int)$employee_id;
			$this->goalMetricEmployeeCache[$key] = isset($all_totals[$employee_id]) ? (float)$all_totals[$employee_id] : 0.0;
		}

		return $this->goalMetricEmployeeCache[$key];
	}

	private function get_goal_metric_all_employees_cached($scope_type, $scope_id, $metric_type, $date_start, $date_end)
	{
		$key = implode('|', array((string)$scope_type, (string)$scope_id, (string)$metric_type, (string)$date_start, (string)$date_end));
		if (!array_key_exists($key, $this->goalMetricAllEmployeesCache))
		{
			$this->goalMetricAllEmployeesCache[$key] = $this->Gala_read_model->get_goal_metric_totals_by_employee($scope_type, $scope_id, $metric_type, $date_start, $date_end);
		}

		return $this->goalMetricAllEmployeesCache[$key];
	}
	private function get_goal_metric_store_total_cached($scope_type, $scope_id, $metric_type, $date_start, $date_end)
	{
		return (float) $this->Gala_read_model->get_goal_metric_store_total($scope_type, $scope_id, $metric_type, $date_start, $date_end);
	}

	private function resolve_global_goal_target_value($definition, $date_start, $date_end, $reference_date = null)
	{
		$method = isset($definition['goal_method']) ? (string) $definition['goal_method'] : 'fixed';
		switch ($method)
		{
			case 'last_year_same_period':
				list($history_start, $history_end) = $this->get_goal_historical_period_range(
					isset($definition['period_type']) ? $definition['period_type'] : 'daily',
					$definition,
					$reference_date
				);
				return $this->get_goal_metric_store_total_cached(
					isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
					isset($definition['scope_id']) ? $definition['scope_id'] : null,
					isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
					$history_start,
					$history_end
				);
			case 'last_year_same_period_plus_percent':
				list($history_start, $history_end) = $this->get_goal_historical_period_range(
					isset($definition['period_type']) ? $definition['period_type'] : 'daily',
					$definition,
					$reference_date
				);
				$base_value = $this->get_goal_metric_store_total_cached(
					isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
					isset($definition['scope_id']) ? $definition['scope_id'] : null,
					isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
					$history_start,
					$history_end
				);
				$percent = isset($definition['goal_percent']) ? (float) $definition['goal_percent'] : 0.0;
				return $base_value + ($base_value * ($percent / 100));
			case 'competitive_leader':
			case 'competitive_target':
				$ranking = $this->get_goal_metric_ranking_cached(
					isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
					isset($definition['scope_id']) ? $definition['scope_id'] : null,
					isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
					$date_start,
					$date_end
				);
				return !empty($ranking) && isset($ranking[0]['metric_value']) ? (float) $ranking[0]['metric_value'] : 0.0;
			case 'fixed':
			default:
				return isset($definition['goal_amount']) ? (float) $definition['goal_amount'] : 0.0;
		}
	}

	private function get_goal_metric_ranking_cached($scope_type, $scope_id, $metric_type, $date_start, $date_end)
	{
		$key = implode('|', array((string)$scope_type, (string)$scope_id, (string)$metric_type, (string)$date_start, (string)$date_end));
		if (!array_key_exists($key, $this->goalMetricRankingCache))
		{
			$this->goalMetricRankingCache[$key] = $this->Gala_read_model->get_goal_metric_ranking($scope_type, $scope_id, $metric_type, $date_start, $date_end);
		}

		return $this->goalMetricRankingCache[$key];
	}

	private function get_group_goal_metric_value_cached($group_id, $definition, $date_start, $date_end)
	{
		$key = implode('|', array((int)$group_id, (int)$definition['id'], (string)$date_start, (string)$date_end));
		if (!array_key_exists($key, $this->groupGoalMetricCache))
		{
			$this->groupGoalMetricCache[$key] = $this->get_group_goal_metric_value($group_id, $definition, $date_start, $date_end);
		}

		return $this->groupGoalMetricCache[$key];
	}

	private function get_group_goal_metric_ranking_cached($definition, $date_start, $date_end, $selected_group_ids = array())
	{
		$clean_group_ids = array_values(array_unique(array_map('intval', (array)$selected_group_ids)));
		sort($clean_group_ids);
		$key = implode('|', array((int)$definition['id'], (string)$date_start, (string)$date_end, implode(',', $clean_group_ids)));
		if (!array_key_exists($key, $this->groupGoalMetricRankingCache))
		{
			$this->groupGoalMetricRankingCache[$key] = $this->get_group_goal_metric_ranking($definition, $date_start, $date_end, $clean_group_ids);
		}

		return $this->groupGoalMetricRankingCache[$key];
	}

	private function get_group_goal_metric_ranking($definition, $date_start, $date_end, $selected_group_ids = array())
	{
		$rows = array();
		$selected_group_ids = !empty($selected_group_ids) ? array_values(array_unique(array_map('intval', $selected_group_ids))) : array();
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		foreach ($groups as $group)
		{
			$group_id = isset($group['id']) ? (int)$group['id'] : 0;
			if ($group_id <= 0 || (!empty($selected_group_ids) && !in_array($group_id, $selected_group_ids, TRUE)))
			{
				continue;
			}

			$rows[] = array(
				'group_id' => $group_id,
				'metric_value' => $this->get_group_goal_metric_value_cached($group_id, $definition, $date_start, $date_end),
			);
		}

		usort($rows, function ($a, $b) {
			$a_value = isset($a['metric_value']) ? (float)$a['metric_value'] : 0.0;
			$b_value = isset($b['metric_value']) ? (float)$b['metric_value'] : 0.0;
			if ($a_value === $b_value)
			{
				return 0;
			}
			return $a_value > $b_value ? -1 : 1;
		});

		return $rows;
	}

	private function build_goal_snapshot_key($goal_id, $period_start, $period_end)
	{
		return implode('|', array((int) $goal_id, (string) $period_start, (string) $period_end));
	}

	private function should_refresh_goal_snapshot($snapshot_row, $period_end, $refresh_minutes = 10)
	{
		if (empty($snapshot_row) || !is_array($snapshot_row))
		{
			return TRUE;
		}

		$period_end_time = strtotime((string) $period_end);
		if ($period_end_time !== FALSE && $period_end_time < time())
		{
			return FALSE;
		}

		$updated_at = !empty($snapshot_row['updated_at']) ? strtotime((string) $snapshot_row['updated_at']) : FALSE;
		if ($updated_at === FALSE)
		{
			return TRUE;
		}

		return $updated_at < strtotime('-'.max(1, (int) $refresh_minutes).' minutes');
	}

	private function evaluate_scoped_goal_snapshot_payload($definition, $employee_id, $employee_group_id, $date_start, $date_end, $reference_date = null)
	{
		$is_group_total = !empty($definition['employee_mode'])
			&& $definition['employee_mode'] === 'group'
			&& !empty($employee_group_id)
			&& isset($definition['target_scope'])
			&& $definition['target_scope'] === 'group_total';

		$current_value = $is_group_total
			? $this->get_group_goal_metric_value_cached($employee_group_id, $definition, $date_start, $date_end)
			: $this->get_goal_metric_for_employee_cached(
				$employee_id,
				isset($definition['scope_type']) ? $definition['scope_type'] : 'general',
				isset($definition['scope_id']) ? $definition['scope_id'] : null,
				isset($definition['metric_type']) ? $definition['metric_type'] : 'amount',
				$date_start,
				$date_end
			);

		$target_value = $this->resolve_goal_target_value($definition, $employee_id, $date_start, $date_end, $reference_date, $employee_group_id);
		$progress_ratio = $target_value > 0 ? min(1, max(0, $current_value / $target_value)) : 0;

		return array(
			'goal_id' => isset($definition['id']) ? (int) $definition['id'] : 0,
			'employee_id' => (int) $employee_id,
			'period_type' => isset($definition['period_type']) ? (string) $definition['period_type'] : 'daily',
			'period_start' => (string) $date_start,
			'period_end' => (string) $date_end,
			'current_value' => (float) $current_value,
			'target_value' => (float) $target_value,
			'progress_ratio' => (float) $progress_ratio,
			'is_completed' => $target_value > 0 && $current_value >= $target_value,
			'rank_position' => 0,
			'updated_at' => date('Y-m-d H:i:s'),
		);
	}

	private function build_scoped_goal_snapshot_bundle($definitions, $employees, $employee_groups_index, $group_members_index, $reference_date, $refresh_minutes = 10)
	{
		$bundle = array();
		$employee_names = array();
		foreach ((array) $employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int) $employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}
			$employee_names[$employee_id] = isset($employee['full_name']) ? $employee['full_name'] : 'Empleado';
		}

		foreach ((array) $definitions as $definition)
		{
			$goal_id = isset($definition['id']) ? (int) $definition['id'] : 0;
			if ($goal_id <= 0 || empty($definition['employee_mode']) || (string) $definition['employee_mode'] === 'global')
			{
				continue;
			}
			if (empty($definition['is_active']) || !$this->goal_is_currently_available($definition, $reference_date))
			{
				continue;
			}

			list($date_start, $date_end) = $this->get_goal_period_range(
				isset($definition['period_type']) ? $definition['period_type'] : 'daily',
				$definition,
				$reference_date
			);
			$selected_group_ids = $this->get_goal_selected_group_ids($definition);
			$applicable_employee_ids = array();
			$employee_group_matches = array();

			foreach ((array) $employees as $employee)
			{
				$employee_id = isset($employee['person_id']) ? (int) $employee['person_id'] : 0;
				if ($employee_id <= 0 || !$this->goal_applies_to_employee($definition, $employee_id, $employee_groups_index))
				{
					continue;
				}

				$applicable_employee_ids[] = $employee_id;
				$employee_group_matches[$employee_id] = !empty($selected_group_ids)
					? $this->resolve_employee_matching_group_id($employee_id, $selected_group_ids, $group_members_index)
					: 0;
			}

			if (empty($applicable_employee_ids))
			{
				continue;
			}

			$snapshot_index = $this->App_write_model->get_goal_snapshots_index_for_employees($applicable_employee_ids, $date_start, $date_end);
			$rows = array();

			foreach ($applicable_employee_ids as $employee_id)
			{
				$snapshot_key = $this->build_goal_snapshot_key($goal_id, $date_start, $date_end);
				$snapshot_row = isset($snapshot_index[$employee_id][$snapshot_key]) ? $snapshot_index[$employee_id][$snapshot_key] : array();

				if ($this->should_refresh_goal_snapshot($snapshot_row, $date_end, $refresh_minutes))
				{
					$snapshot_row = $this->evaluate_scoped_goal_snapshot_payload(
						$definition,
						$employee_id,
						isset($employee_group_matches[$employee_id]) ? (int) $employee_group_matches[$employee_id] : 0,
						$date_start,
						$date_end,
						$reference_date
					);
					$this->App_write_model->save_goal_snapshot($snapshot_row);
				}

				$rows[$employee_id] = array(
					'employee_id' => $employee_id,
					'employee_name' => isset($employee_names[$employee_id]) ? $employee_names[$employee_id] : 'Empleado',
					'current_value' => isset($snapshot_row['current_value']) ? (float) $snapshot_row['current_value'] : 0.0,
					'target_value' => isset($snapshot_row['target_value']) ? (float) $snapshot_row['target_value'] : 0.0,
					'progress_ratio' => isset($snapshot_row['progress_ratio']) ? (float) $snapshot_row['progress_ratio'] : 0.0,
					'progress_percent' => max(0, min(100, (int) round((isset($snapshot_row['progress_ratio']) ? (float) $snapshot_row['progress_ratio'] : 0.0) * 100))),
					'is_completed' => !empty($snapshot_row['is_completed']),
				);
			}

			$bundle[$goal_id] = array(
				'definition' => $definition,
				'date_start' => $date_start,
				'date_end' => $date_end,
				'rows' => $rows,
			);
		}

		return $bundle;
	}
	private function build_dashboard_data($filters = array())
	{
		$this->Tasks_model->sync_due_assignments();
		$filters = is_array($filters) ? $filters : array();
		$company_name = $this->Gala_read_model->get_company_name_for_visor(1);
		$actor = $this->get_supervisor_actor();
		$employees = $this->Gala_read_model->get_employees_for_visor();
		$all_employees = $employees;
		$attendance_employees = $this->get_active_attendance_employees();
		$notifications = $this->App_write_model->get_notifications();
		$goal_definitions = $this->App_write_model->get_goal_definitions(FALSE);
		$groups = $this->App_write_model->get_employee_groups(TRUE);
		$group_members_index = $this->App_write_model->get_employee_group_members_index();
		$global_goal_definitions = array_values(array_filter($goal_definitions, function ($definition) {
			return empty($definition['employee_mode']) || (string) $definition['employee_mode'] === 'global';
		}));
		$scoped_goal_definitions = array_values(array_filter($goal_definitions, function ($definition) {
			return !empty($definition['employee_mode']) && (string) $definition['employee_mode'] !== 'global' && !empty($definition['is_active']) && (!isset($definition['target_scope']) || (string) $definition['target_scope'] !== 'group_total');
		}));

		$employee_id_filter = !empty($filters['employee_id']) ? (int)$filters['employee_id'] : 0;
		$group_id_filter = !empty($filters['group_id']) ? (int)$filters['group_id'] : 0;
		$period_filter = !empty($filters['period']) ? (string)$filters['period'] : '';

		if ($employee_id_filter > 0)
		{
			$employees = array_values(array_filter($employees, function ($row) use ($employee_id_filter) {
				return (int) (isset($row['person_id']) ? $row['person_id'] : 0) === $employee_id_filter;
			}));
		}

		if ($group_id_filter > 0 && !empty($group_members_index[$group_id_filter]))
		{
			$allowed_ids = array_flip(array_map('intval', (array)$group_members_index[$group_id_filter]));
			$employees = array_values(array_filter($employees, function ($row) use ($allowed_ids) {
				$id = (int) (isset($row['person_id']) ? $row['person_id'] : 0);
				return $id > 0 && isset($allowed_ids[$id]);
			}));
		}

		$employee_groups_index = $this->build_employee_groups_index($groups, $group_members_index);
		$assignment_filters = array('status' => 'all');
		if ($employee_id_filter > 0)
		{
			$assignment_filters['employee_id'] = $employee_id_filter;
		}
		if ($group_id_filter > 0)
		{
			$assignment_filters['group_id'] = $group_id_filter;
		}
		$assignments = $this->Tasks_model->get_assignments_for_admin($assignment_filters);

		$task_counts = array(
			'open' => 0,
			'overdue' => 0,
			'completed_today' => 0,
		);
		$today_key = date('Y-m-d');
		foreach ($assignments as $assignment)
		{
			$status = isset($assignment['status']) ? (string)$assignment['status'] : '';
			if ($status === 'overdue')
			{
				$task_counts['overdue']++;
				$task_counts['open']++;
			}
			elseif ($status === 'pending' || $status === 'in_progress')
			{
				$task_counts['open']++;
			}
			elseif ($status === 'completed' && !empty($assignment['completed_at']))
			{
				if (strpos((string)$assignment['completed_at'], $today_key) === 0)
				{
					$task_counts['completed_today']++;
				}
			}
		}

		$attendance_rows = array_slice($attendance_employees, 0, 8);
		$timezone = new DateTimeZone('America/Caracas');
		$today = new DateTime('now', $timezone);
		$attendance_index = $this->App_write_model->get_attendance_for_date($today->format('Y-m-d'));
		$attendance = array();
		foreach ($attendance_rows as $row)
		{
			$employee_id = isset($row['person_id']) ? (int)$row['person_id'] : 0;
			$stored = isset($attendance_index[$employee_id]) ? $attendance_index[$employee_id] : null;
			$attendance[] = array(
				'name' => isset($row['full_name']) ? $row['full_name'] : 'Empleado',
				'is_present' => $stored ? !empty($stored['is_present']) : FALSE,
				'employee_id' => $employee_id,
				'note' => $stored && isset($stored['note']) ? (string)$stored['note'] : '',
			);
		}

		$pending_notifications = 0;
		foreach ($notifications as $notification)
		{
			if (empty($notification['is_reviewed']))
			{
				$pending_notifications++;
			}
		}

		$kpis = array(
			array('label' => 'Empleados', 'value' => count($employees)),
			array('label' => 'Metas activas', 'value' => count($scoped_goal_definitions)),
			array('label' => 'Tareas abiertas', 'value' => $task_counts['open']),
			array('label' => 'Tareas vencidas', 'value' => $task_counts['overdue'], 'is_alert' => $task_counts['overdue'] > 0),
			array('label' => 'Completadas hoy', 'value' => $task_counts['completed_today']),
			array('label' => 'Notificaciones', 'value' => $pending_notifications, 'is_alert' => $pending_notifications > 0),
		);
		$sales_kpis = array(
			'today_total' => 0.0,
			'today_count' => 0,
			'month_total' => 0.0,
		);
		$sales_ranking = array();
		$sales_by_day = array();
		$sales_avg_total = 0.0;

				$task_cards = array();
		$employee_names_index = array();
		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int)$employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}

			$employee_names_index[$employee_id] = isset($employee['full_name']) ? (string)$employee['full_name'] : 'Empleado';
		}

		foreach ((array)$assignments as $assignment)
		{
			$employee_id = isset($assignment['employee_id']) ? (int)$assignment['employee_id'] : 0;
			if ($employee_id <= 0 || !isset($employee_names_index[$employee_id]))
			{
				continue;
			}

			$status = isset($assignment['status']) ? (string)$assignment['status'] : 'pending';
			$is_completed_today = $status === 'completed' && !empty($assignment['completed_at']) && strpos((string)$assignment['completed_at'], $today_key) === 0;
			$is_active = in_array($status, array('pending', 'in_progress', 'overdue'), TRUE);
			if (!$is_active && !$is_completed_today)
			{
				continue;
			}

			if (!isset($task_cards[$employee_id]))
			{
				$task_cards[$employee_id] = array(
					'employee_id' => $employee_id,
					'name' => $employee_names_index[$employee_id],
					'completed_count' => 0,
					'total_count' => 0,
					'active_count' => 0,
				);
			}

			$task_cards[$employee_id]['total_count']++;
			if ($is_active)
			{
				$task_cards[$employee_id]['active_count']++;
			}
			if ($is_completed_today)
			{
				$task_cards[$employee_id]['completed_count']++;
			}
		}

		$task_cards = array_values(array_filter($task_cards, function ($card) {
			return isset($card['active_count']) && (int)$card['active_count'] > 0;
		}));
		foreach ($task_cards as &$task_card)
		{
			$total_count = isset($task_card['total_count']) ? max(0, (int)$task_card['total_count']) : 0;
			$completed_count = isset($task_card['completed_count']) ? max(0, (int)$task_card['completed_count']) : 0;
			$active_count = isset($task_card['active_count']) ? max(0, (int)$task_card['active_count']) : 0;
			$task_card['completion_label'] = $completed_count.'/'.max(1, $total_count);
			$task_card['completion_percent'] = $total_count > 0 ? max(0, min(100, (int) round(($completed_count / $total_count) * 100))) : 0;
			$task_card['active_label'] = $active_count.' '.($active_count === 1 ? 'activa' : 'activas');
		}
		unset($task_card);
		usort($task_cards, function ($a, $b) {
			$a_active = isset($a['active_count']) ? (int)$a['active_count'] : 0;
			$b_active = isset($b['active_count']) ? (int)$b['active_count'] : 0;
			if ($a_active !== $b_active)
			{
				return $b_active <=> $a_active;
			}

			$a_total = isset($a['total_count']) ? (int)$a['total_count'] : 0;
			$b_total = isset($b['total_count']) ? (int)$b['total_count'] : 0;
			if ($a_total !== $b_total)
			{
				return $b_total <=> $a_total;
			}

			$a_name = isset($a['name']) ? (string)$a['name'] : '';
			$b_name = isset($b['name']) ? (string)$b['name'] : '';
			return strcasecmp($a_name, $b_name);
		});
		$goal_cards = array();
		$global_goals_dashboard = array();
		$global_rules_dashboard = array();
		$ranking = array();
		$period_start = date('Y-m-01 00:00:00');
		$period_end = date('Y-m-t 23:59:59');
		$dashboard_period_reference = date('Y-m-01');
		if ($period_filter !== '' && preg_match('/^\d{4}\-\d{2}$/', $period_filter))
		{
			$period_start = $period_filter.'-01 00:00:00';
			$period_end = date('Y-m-t 23:59:59', strtotime($period_filter.'-01'));
			$dashboard_period_reference = $period_filter.'-01';
		}

		$scoped_snapshot_bundle = $this->build_scoped_goal_snapshot_bundle(
			$scoped_goal_definitions,
			$employees,
			$employee_groups_index,
			$group_members_index,
			$dashboard_period_reference
		);

		foreach (array_slice($scoped_goal_definitions, 0, 4) as $goal)
		{
			if (!$this->goal_is_currently_available($goal, $dashboard_period_reference))
			{
				continue;
			}

			$goal_id = isset($goal['id']) ? (int) $goal['id'] : 0;
			if ($goal_id <= 0 || !isset($scoped_snapshot_bundle[$goal_id]))
			{
				continue;
			}

			$participant_count = 0;
			$completed_count = 0;
			$total_ratio = 0.0;
			foreach ($scoped_snapshot_bundle[$goal_id]['rows'] as $snapshot_row)
			{
				$participant_count++;
				$total_ratio += isset($snapshot_row['progress_ratio']) ? (float) $snapshot_row['progress_ratio'] : 0.0;
				if (!empty($snapshot_row['is_completed']))
				{
					$completed_count++;
				}
			}

			$goal_cards[] = array(
				'goal_id' => $goal_id,
				'name' => isset($goal['name']) ? $goal['name'] : 'Meta',
				'participants' => $participant_count,
				'completed_count' => $completed_count,
				'completion_label' => $completed_count.'/'.max(1, $participant_count),
				'completion_percent' => $participant_count > 0 ? max(0, min(100, (int) round(($completed_count / $participant_count) * 100))) : 0,
				'average_progress_percent' => $participant_count > 0 ? max(0, min(100, (int) round(($total_ratio / $participant_count) * 100))) : 0,
			);
		}

		foreach ($employees as $employee)
		{
			$employee_id = isset($employee['person_id']) ? (int) $employee['person_id'] : 0;
			if ($employee_id <= 0)
			{
				continue;
			}

			$sum = 0.0;
			$count = 0;
			foreach ($scoped_snapshot_bundle as $goal_bundle)
			{
				if (!isset($goal_bundle['rows'][$employee_id]))
				{
					continue;
				}

				$sum += isset($goal_bundle['rows'][$employee_id]['progress_ratio']) ? (float) $goal_bundle['rows'][$employee_id]['progress_ratio'] : 0.0;
				$count++;
			}

			$percent = $count > 0 ? (int) round(($sum / $count) * 100) : 0;
			$ranking[] = array(
				'employee_id' => $employee_id,
				'name' => isset($employee['full_name']) ? $employee['full_name'] : 'Empleado',
				'percent' => max(0, min(100, $percent)),
			);
		}
		foreach ($global_goal_definitions as $goal)
		{
			$is_active = !empty($goal['is_active']);
			if (!$is_active)
			{
				continue;
			}

			$is_available = $this->goal_is_currently_available($goal, $dashboard_period_reference);
			$goal['status_label'] = $is_available ? 'Activa' : 'Terminada';
			$goal['status_class'] = $is_available ? 'active' : 'finished';
			$goal['current_value'] = 0.0;
			$goal['target_value'] = 0.0;
			$goal['progress_percent'] = 0;

			if ($is_available)
			{
				list($date_start, $date_end) = $this->get_goal_period_range(
					isset($goal['period_type']) ? $goal['period_type'] : 'daily',
					$goal,
					$dashboard_period_reference
				);

				$current_value = $this->get_goal_metric_store_total_cached(
					isset($goal['scope_type']) ? $goal['scope_type'] : 'general',
					isset($goal['scope_id']) ? $goal['scope_id'] : null,
					isset($goal['metric_type']) ? $goal['metric_type'] : 'amount',
					$date_start,
					$date_end
				);
				$target_value = $this->resolve_global_goal_target_value($goal, $date_start, $date_end, $dashboard_period_reference);
				$progress_ratio = $target_value > 0 ? min(1, max(0, $current_value / $target_value)) : 0;

				$goal['current_value'] = (float) $current_value;
				$goal['target_value'] = (float) $target_value;
				$goal['progress_percent'] = max(0, min(100, (int) round($progress_ratio * 100)));
			}

			$global_goals_dashboard[] = $goal;
		}

		usort($global_goals_dashboard, function ($a, $b) {
			$a_rank = (isset($a['status_label']) ? (string)$a['status_label'] : '') === 'Activa' ? 0 : 1;
			$b_rank = (isset($b['status_label']) ? (string)$b['status_label'] : '') === 'Activa' ? 0 : 1;
			if ($a_rank !== $b_rank)
			{
				return $a_rank <=> $b_rank;
			}

			$a_name = isset($a['name']) ? (string)$a['name'] : '';
			$b_name = isset($b['name']) ? (string)$b['name'] : '';
			return strcasecmp($a_name, $b_name);
		});
		$global_goals_dashboard = array_slice($global_goals_dashboard, 0, 3);

		$actor_global_assignments = array();
		if (!empty($actor['employee_id']))
		{
			$today_key = date('Y-m-d');
			$raw_actor_assignments = $this->Tasks_model->get_assignments_for_admin(array(
				'employee_id' => (int)$actor['employee_id'],
				'status' => 'all',
			));
			foreach ((array)$raw_actor_assignments as $assignment)
			{
				if (empty($assignment['assignment_mode']) || (string)$assignment['assignment_mode'] !== 'global')
				{
					continue;
				}

				$status = isset($assignment['status']) ? (string)$assignment['status'] : 'pending';
				$assigned_date = !empty($assignment['assigned_date']) ? (string)$assignment['assigned_date'] : '';
				$completed_today = !empty($assignment['completed_at']) && strpos((string)$assignment['completed_at'], $today_key) === 0;
				if ($status === 'completed' && $assigned_date !== $today_key && !$completed_today)
				{
					continue;
				}

				$rule_id = isset($assignment['rule_id']) ? (int)$assignment['rule_id'] : 0;
				if ($rule_id <= 0)
				{
					continue;
				}

				$priority = 3;
				if ($status === 'overdue')
				{
					$priority = 0;
				}
				elseif ($status === 'pending')
				{
					$priority = 1;
				}
				elseif ($status === 'in_progress')
				{
					$priority = 2;
				}
				elseif ($status === 'completed')
				{
					$priority = 4;
				}

				$assignment['_dashboard_priority'] = $priority;
				if (!isset($actor_global_assignments[$rule_id]))
				{
					$actor_global_assignments[$rule_id] = $assignment;
					continue;
				}

				$current = $actor_global_assignments[$rule_id];
				$current_priority = isset($current['_dashboard_priority']) ? (int)$current['_dashboard_priority'] : 99;
				if ($priority < $current_priority)
				{
					$actor_global_assignments[$rule_id] = $assignment;
					continue;
				}

				if ($priority === $current_priority)
				{
					$current_sort_date = !empty($current['due_date']) ? (string)$current['due_date'] : (!empty($current['assigned_date']) ? (string)$current['assigned_date'] : '');
					$new_sort_date = !empty($assignment['due_date']) ? (string)$assignment['due_date'] : ($assigned_date !== '' ? $assigned_date : '');
					if ($new_sort_date > $current_sort_date)
					{
						$actor_global_assignments[$rule_id] = $assignment;
					}
				}
			}
		}

		$dashboard_rules = array_values(array_filter($this->decorate_task_rules($this->Tasks_model->get_rules(), $employees, $groups), function ($rule) {
			return !empty($rule['assignment_mode']) && (string) $rule['assignment_mode'] === 'global' && !empty($rule['is_active']);
		}));
		foreach ($dashboard_rules as &$dashboard_rule)
		{
			$is_active_rule = !empty($dashboard_rule['is_active']);
			$has_next_due = !empty($dashboard_rule['next_due_date']);
			$dashboard_rule['status_label'] = ($is_active_rule && $has_next_due) ? 'Activa' : 'Terminado';
			$dashboard_rule['status_class'] = $dashboard_rule['status_label'] === 'Activa' ? 'active' : 'finished';
			$dashboard_rule['assignment'] = null;
			$dashboard_rule['assignment_id'] = 0;
			$dashboard_rule['assignment_status_label'] = '';
			$dashboard_rule['assignment_status_class'] = '';
			$dashboard_rule['assignment_action_type'] = '';
			$dashboard_rule['assignment_action_label'] = '';
			$dashboard_rule['assignment_due_date_label'] = '';
			$rule_id = isset($dashboard_rule['id']) ? (int)$dashboard_rule['id'] : 0;
			if ($rule_id > 0 && isset($actor_global_assignments[$rule_id]))
			{
				$assignment_payload = $this->build_task_assignment_payload($actor_global_assignments[$rule_id]);
				$dashboard_rule['assignment'] = $assignment_payload;
				$dashboard_rule['assignment_id'] = isset($assignment_payload['id']) ? (int)$assignment_payload['id'] : 0;
				$dashboard_rule['assignment_status_label'] = isset($assignment_payload['status_label']) ? (string)$assignment_payload['status_label'] : '';
				$dashboard_rule['assignment_status_class'] = isset($assignment_payload['status_class']) ? (string)$assignment_payload['status_class'] : '';
				$dashboard_rule['assignment_action_type'] = isset($assignment_payload['action_type']) ? (string)$assignment_payload['action_type'] : '';
				$dashboard_rule['assignment_action_label'] = isset($assignment_payload['action_label']) ? (string)$assignment_payload['action_label'] : '';
				$dashboard_rule['assignment_due_date_label'] = isset($assignment_payload['due_date_label']) ? (string)$assignment_payload['due_date_label'] : '';
			}
		}
		unset($dashboard_rule);
		usort($dashboard_rules, function ($a, $b) {
			$a_rank = (isset($a['status_label']) ? (string)$a['status_label'] : '') === 'Activa' ? 0 : 1;
			$b_rank = (isset($b['status_label']) ? (string)$b['status_label'] : '') === 'Activa' ? 0 : 1;
			if ($a_rank !== $b_rank)
			{
				return $a_rank <=> $b_rank;
			}

			$a_name = !empty($a['display_name']) ? (string)$a['display_name'] : '';
			$b_name = !empty($b['display_name']) ? (string)$b['display_name'] : '';
			return strcasecmp($a_name, $b_name);
		});
		$global_rules_dashboard = array_slice($dashboard_rules, 0, 3);

		return array(
			'company_name' => $company_name,
			'kpis' => $kpis,
			'notifications' => array_slice($notifications, 0, 4),
			'global_goals_dashboard' => $global_goals_dashboard,
			'global_rules_dashboard' => $global_rules_dashboard,
			'attendance' => $attendance,
			'task_cards' => $task_cards,
			'goal_cards' => $goal_cards,
			'ranking' => array_slice($ranking, 0, 5),
			'sales_kpis' => $sales_kpis,
			'sales_ranking' => array_slice($sales_ranking, 0, 5),
			'sales_by_day' => $sales_by_day,
			'sales_avg_total' => $sales_avg_total,
			'filters' => array(
				'employee_id' => $employee_id_filter,
				'group_id' => $group_id_filter,
				'period' => $period_filter,
			),
			'groups' => $groups,
			'all_employees' => $all_employees,
		);
	}
}



















