<?php
/**
* Working English System - Módulo de Configuración
*
* @package WorkingEnglishSystem
* @subpackage Modules/Config
* @version 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
class WES_Config_Module {
private $db;
private $table_branches;
private $table_languages;
private $table_branch_languages;
private $table_programs;
private $table_levels;
public function __construct() {
global $wpdb;
$this->db = $wpdb;
$this->table_branches = $wpdb->prefix . 'wes_branches';
$this->table_languages = $wpdb->prefix . 'wes_languages';
$this->table_branch_languages = $wpdb->prefix . 'wes_branch_languages';
$this->table_programs = $wpdb->prefix . 'wes_programs';
$this->table_levels = $wpdb->prefix . 'wes_levels';
$this->init_hooks();
}
/**
* Inicializar hooks y acciones
*/
private function init_hooks() {
// AJAX handlers
add_action('wp_ajax_wes_get_branches', array($this, 'ajax_get_branches'));
add_action('wp_ajax_wes_save_branch', array($this, 'ajax_save_branch'));
add_action('wp_ajax_wes_delete_branch', array($this, 'ajax_delete_branch'));
add_action('wp_ajax_wes_get_languages', array($this, 'ajax_get_languages'));
add_action('wp_ajax_wes_save_language', array($this, 'ajax_save_language'));
add_action('wp_ajax_wes_delete_language', array($this, 'ajax_delete_language'));
add_action('wp_ajax_wes_get_programs', array($this, 'ajax_get_programs'));
add_action('wp_ajax_wes_save_program', array($this, 'ajax_save_program'));
add_action('wp_ajax_wes_delete_program', array($this, 'ajax_delete_program'));
add_action('wp_ajax_wes_get_levels', array($this, 'ajax_get_levels'));
add_action('wp_ajax_wes_save_level', array($this, 'ajax_save_level'));
add_action('wp_ajax_wes_delete_level', array($this, 'ajax_delete_level'));
add_action('wp_ajax_wes_get_countries', array($this, 'ajax_get_countries'));
add_action('wp_ajax_wes_get_branch_languages', array($this, 'ajax_get_branch_languages'));
add_action('wp_ajax_wes_toggle_branch_language', array($this, 'ajax_toggle_branch_language'));
// AJAX para frontend (no-priv)
add_action('wp_ajax_nopriv_wes_get_branches', array($this, 'ajax_get_branches'));
add_action('wp_ajax_nopriv_wes_save_branch', array($this, 'ajax_save_branch'));
add_action('wp_ajax_nopriv_wes_delete_branch', array($this, 'ajax_delete_branch'));
add_action('wp_ajax_nopriv_wes_get_languages', array($this, 'ajax_get_languages'));
add_action('wp_ajax_nopriv_wes_save_language', array($this, 'ajax_save_language'));
add_action('wp_ajax_nopriv_wes_delete_language', array($this, 'ajax_delete_language'));
add_action('wp_ajax_nopriv_wes_get_programs', array($this, 'ajax_get_programs'));
add_action('wp_ajax_nopriv_wes_save_program', array($this, 'ajax_save_program'));
add_action('wp_ajax_nopriv_wes_delete_program', array($this, 'ajax_delete_program'));
add_action('wp_ajax_nopriv_wes_get_levels', array($this, 'ajax_get_levels'));
add_action('wp_ajax_nopriv_wes_save_level', array($this, 'ajax_save_level'));
add_action('wp_ajax_nopriv_wes_delete_level', array($this, 'ajax_delete_level'));
add_action('wp_ajax_nopriv_wes_get_countries', array($this, 'ajax_get_countries'));
add_action('wp_ajax_nopriv_wes_get_branch_languages', array($this, 'ajax_get_branch_languages'));
add_action('wp_ajax_nopriv_wes_toggle_branch_language', array($this, 'ajax_toggle_branch_language'));
}
// ==========================================
// AJAX HANDLERS - PAÍSES
// ==========================================
public function ajax_get_countries() {
check_ajax_referer('wes_nonce', 'nonce');
// Obtener países únicos de la tabla de estudiantes
$countries = $this->db->get_results(
"SELECT DISTINCT country_of_origin as country
FROM {$this->db->prefix}wes_students
WHERE country_of_origin IS NOT NULL
AND country_of_origin != ''
AND deleted_at IS NULL
ORDER BY country_of_origin"
);
$country_list = array();
foreach ($countries as $country) {
if (!empty(trim($country->country))) {
$country_list[] = trim($country->country);
}
}
// Agregar algunos países adicionales comunes si no están
$additional_countries = array(
'Guatemala', 'México', 'Estados Unidos', 'Honduras', 'El Salvador',
'Nicaragua', 'Costa Rica', 'Panamá', 'Colombia', 'España'
);
foreach ($additional_countries as $add_country) {
if (!in_array($add_country, $country_list)) {
$country_list[] = $add_country;
}
}
sort($country_list);
wp_send_json_success($country_list);
}
// ==========================================
// AJAX HANDLERS - SEDES
// ==========================================
public function ajax_get_branches() {
check_ajax_referer('wes_nonce', 'nonce');
$branches = $this->db->get_results(
"SELECT * FROM {$this->table_branches}
WHERE deleted_at IS NULL
ORDER BY name"
);
wp_send_json_success($branches);
}
public function ajax_save_branch() {
check_ajax_referer('wes_nonce', 'nonce');
$branch_id = intval($_POST['branch_id'] ?? 0);
$data = array(
'name' => sanitize_text_field($_POST['name'] ?? ''),
'code' => sanitize_text_field($_POST['code'] ?? ''),
'address' => sanitize_textarea_field($_POST['address'] ?? ''),
'city' => sanitize_text_field($_POST['city'] ?? ''),
'country' => sanitize_text_field($_POST['country'] ?? ''),
'manager_name' => sanitize_text_field($_POST['manager_name'] ?? ''),
'phone' => sanitize_text_field($_POST['phone'] ?? ''),
'email' => sanitize_email($_POST['email'] ?? ''),
'status' => sanitize_text_field($_POST['status'] ?? 'active')
);
// Validar datos requeridos
if (empty($data['name']) || empty($data['code'])) {
wp_send_json_error('Nombre y código son requeridos');
}
// Verificar código único - MEJORADO para detectar sedes eliminadas
$existing = $this->db->get_row($this->db->prepare(
"SELECT id, deleted_at FROM {$this->table_branches}
WHERE code = %s AND id != %d",
$data['code'], $branch_id
));
if ($existing) {
if ($existing->deleted_at !== null) {
// Sede existe pero está eliminada (soft delete)
wp_send_json_error(array(
'type' => 'deleted_conflict',
'message' => 'Este código pertenece a una sede previamente eliminada. Para usar este código nuevamente, contacte al administrador de TI para eliminar definitivamente la sede anterior, o use un código diferente.'
));
} else {
// Sede existe y está activa
wp_send_json_error(array(
'type' => 'active_conflict',
'message' => 'El código ya existe en otra sede activa'
));
}
}
if ($branch_id > 0) {
// Actualizar
$data['updated_at'] = current_time('mysql');
$result = $this->db->update($this->table_branches, $data, array('id' => $branch_id));
$message = 'Sede actualizada correctamente';
} else {
// Crear
$data['created_at'] = current_time('mysql');
$data['updated_at'] = current_time('mysql');
$result = $this->db->insert($this->table_branches, $data);
$message = 'Sede creada correctamente';
}
if ($result !== false) {
wp_send_json_success($message);
} else {
wp_send_json_error('Error al guardar la sede');
}
}
public function ajax_delete_branch() {
check_ajax_referer('wes_nonce', 'nonce');
$branch_id = intval($_POST['branch_id'] ?? 0);
if ($branch_id <= 0) {
wp_send_json_error('ID inválido');
}
// Verificar si tiene cursos activos
$courses_count = $this->db->get_var($this->db->prepare(
"SELECT COUNT(*) FROM {$this->db->prefix}wes_courses
WHERE branch_id = %d AND deleted_at IS NULL",
$branch_id
));
if ($courses_count > 0) {
wp_send_json_error('No se puede eliminar: tiene cursos asociados');
}
// Soft delete
$result = $this->db->update(
$this->table_branches,
array('deleted_at' => current_time('mysql')),
array('id' => $branch_id)
);
if ($result !== false) {
wp_send_json_success('Sede eliminada correctamente');
} else {
wp_send_json_error('Error al eliminar la sede');
}
}
// ==========================================
// AJAX HANDLERS - IDIOMAS
// ==========================================
public function ajax_get_languages() {
check_ajax_referer('wes_nonce', 'nonce');
$languages = $this->db->get_results(
"SELECT * FROM {$this->table_languages}
WHERE deleted_at IS NULL
ORDER BY name"
);
wp_send_json_success($languages);
}
public function ajax_save_language() {
check_ajax_referer('wes_nonce', 'nonce');
$language_id = intval($_POST['language_id'] ?? 0);
$data = array(
'name' => sanitize_text_field($_POST['name'] ?? ''),
'code' => strtoupper(sanitize_text_field($_POST['code'] ?? '')),
'status' => sanitize_text_field($_POST['status'] ?? 'active')
);
if (empty($data['name']) || empty($data['code'])) {
wp_send_json_error('Nombre y código son requeridos');
}
// Verificar código único - MEJORADO
$existing = $this->db->get_row($this->db->prepare(
"SELECT id, deleted_at FROM {$this->table_languages}
WHERE code = %s AND id != %d",
$data['code'], $language_id
));
if ($existing) {
if ($existing->deleted_at !== null) {
wp_send_json_error(array(
'type' => 'deleted_conflict',
'message' => 'Este código pertenece a un idioma previamente eliminado. Para usar este código nuevamente, contacte al administrador de TI para eliminar definitivamente el idioma anterior, o use un código diferente.'
));
} else {
wp_send_json_error(array(
'type' => 'active_conflict',
'message' => 'El código ya existe en otro idioma activo'
));
}
}
if ($language_id > 0) {
$data['updated_at'] = current_time('mysql');
$result = $this->db->update($this->table_languages, $data, array('id' => $language_id));
$message = 'Idioma actualizado correctamente';
} else {
$data['created_at'] = current_time('mysql');
$data['updated_at'] = current_time('mysql');
$result = $this->db->insert($this->table_languages, $data);
$message = 'Idioma creado correctamente';
}
if ($result !== false) {
wp_send_json_success($message);
} else {
wp_send_json_error('Error al guardar el idioma');
}
}
public function ajax_delete_language() {
check_ajax_referer('wes_nonce', 'nonce');
$language_id = intval($_POST['language_id'] ?? 0);
// Verificar si tiene programas
$programs_count = $this->db->get_var($this->db->prepare(
"SELECT COUNT(*) FROM {$this->table_programs}
WHERE language_id = %d AND deleted_at IS NULL",
$language_id
));
if ($programs_count > 0) {
wp_send_json_error('No se puede eliminar: tiene programas asociados');
}
$result = $this->db->update(
$this->table_languages,
array('deleted_at' => current_time('mysql')),
array('id' => $language_id)
);
if ($result !== false) {
wp_send_json_success('Idioma eliminado correctamente');
} else {
wp_send_json_error('Error al eliminar el idioma');
}
}
// ==========================================
// AJAX HANDLERS - PROGRAMAS
// ==========================================
public function ajax_get_programs() {
check_ajax_referer('wes_nonce', 'nonce');
$language_id = intval($_POST['language_id'] ?? 0);
$query = "SELECT p.*, l.name as language_name
FROM {$this->table_programs} p
LEFT JOIN {$this->table_languages} l ON p.language_id = l.id
WHERE p.deleted_at IS NULL";
if ($language_id > 0) {
$query .= $this->db->prepare(" AND p.language_id = %d", $language_id);
}
$query .= " ORDER BY l.name, p.name";
$programs = $this->db->get_results($query);
wp_send_json_success($programs);
}
public function ajax_save_program() {
check_ajax_referer('wes_nonce', 'nonce');
$program_id = intval($_POST['program_id'] ?? 0);
$data = array(
'language_id' => intval($_POST['language_id'] ?? 0),
'name' => sanitize_text_field($_POST['name'] ?? ''),
'code' => strtoupper(sanitize_text_field($_POST['code'] ?? '')),
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
'status' => sanitize_text_field($_POST['status'] ?? 'active')
);
if (empty($data['name']) || empty($data['code']) || $data['language_id'] <= 0) {
wp_send_json_error('Idioma, nombre y código son requeridos');
}
// Verificar código único dentro del idioma - MEJORADO
$existing = $this->db->get_row($this->db->prepare(
"SELECT id, deleted_at FROM {$this->table_programs}
WHERE language_id = %d AND code = %s AND id != %d",
$data['language_id'], $data['code'], $program_id
));
if ($existing) {
if ($existing->deleted_at !== null) {
wp_send_json_error(array(
'type' => 'deleted_conflict',
'message' => 'Este código pertenece a un programa previamente eliminado en este idioma. Para usar este código nuevamente, contacte al administrador de TI para eliminar definitivamente el programa anterior, o use un código diferente.'
));
} else {
wp_send_json_error(array(
'type' => 'active_conflict',
'message' => 'El código ya existe en otro programa activo de este idioma'
));
}
}
if ($program_id > 0) {
$data['updated_at'] = current_time('mysql');
$result = $this->db->update($this->table_programs, $data, array('id' => $program_id));
$message = 'Programa actualizado correctamente';
} else {
$data['created_at'] = current_time('mysql');
$data['updated_at'] = current_time('mysql');
$result = $this->db->insert($this->table_programs, $data);
$message = 'Programa creado correctamente';
}
if ($result !== false) {
wp_send_json_success($message);
} else {
wp_send_json_error('Error al guardar el programa');
}
}
public function ajax_delete_program() {
check_ajax_referer('wes_nonce', 'nonce');
$program_id = intval($_POST['program_id'] ?? 0);
// Verificar si tiene niveles
$levels_count = $this->db->get_var($this->db->prepare(
"SELECT COUNT(*) FROM {$this->table_levels}
WHERE program_id = %d AND deleted_at IS NULL",
$program_id
));
if ($levels_count > 0) {
wp_send_json_error('No se puede eliminar: tiene niveles asociados');
}
$result = $this->db->update(
$this->table_programs,
array('deleted_at' => current_time('mysql')),
array('id' => $program_id)
);
if ($result !== false) {
wp_send_json_success('Programa eliminado correctamente');
} else {
wp_send_json_error('Error al eliminar el programa');
}
}
// ==========================================
// AJAX HANDLERS - NIVELES
// ==========================================
public function ajax_get_levels() {
check_ajax_referer('wes_nonce', 'nonce');
$program_id = intval($_POST['program_id'] ?? 0);
$query = "SELECT lv.*, p.name as program_name, l.name as language_name
FROM {$this->table_levels} lv
LEFT JOIN {$this->table_programs} p ON lv.program_id = p.id
LEFT JOIN {$this->table_languages} l ON p.language_id = l.id
WHERE lv.deleted_at IS NULL";
if ($program_id > 0) {
$query .= $this->db->prepare(" AND lv.program_id = %d", $program_id);
}
$query .= " ORDER BY l.name, p.name, lv.order_number";
$levels = $this->db->get_results($query);
wp_send_json_success($levels);
}
public function ajax_save_level() {
check_ajax_referer('wes_nonce', 'nonce');
$level_id = intval($_POST['level_id'] ?? 0);
$data = array(
'program_id' => intval($_POST['program_id'] ?? 0),
'name' => sanitize_text_field($_POST['name'] ?? ''),
'code' => strtoupper(sanitize_text_field($_POST['code'] ?? '')),
'order_number' => intval($_POST['order_number'] ?? 1),
'status' => sanitize_text_field($_POST['status'] ?? 'active')
);
if (empty($data['name']) || empty($data['code']) || $data['program_id'] <= 0) {
wp_send_json_error('Programa, nombre y código son requeridos');
}
// Verificar código único dentro del programa - MEJORADO
$existing = $this->db->get_row($this->db->prepare(
"SELECT id, deleted_at FROM {$this->table_levels}
WHERE program_id = %d AND code = %s AND id != %d",
$data['program_id'], $data['code'], $level_id
));
if ($existing) {
if ($existing->deleted_at !== null) {
wp_send_json_error(array(
'type' => 'deleted_conflict',
'message' => 'Este código pertenece a un nivel previamente eliminado en este programa. Para usar este código nuevamente, contacte al administrador de TI para eliminar definitivamente el nivel anterior, o use un código diferente.'
));
} else {
wp_send_json_error(array(
'type' => 'active_conflict',
'message' => 'El código ya existe en otro nivel activo de este programa'
));
}
}
if ($level_id > 0) {
$data['updated_at'] = current_time('mysql');
$result = $this->db->update($this->table_levels, $data, array('id' => $level_id));
$message = 'Nivel actualizado correctamente';
} else {
$data['created_at'] = current_time('mysql');
$data['updated_at'] = current_time('mysql');
$result = $this->db->insert($this->table_levels, $data);
$message = 'Nivel creado correctamente';
}
if ($result !== false) {
wp_send_json_success($message);
} else {
wp_send_json_error('Error al guardar el nivel');
}
}
public function ajax_delete_level() {
check_ajax_referer('wes_nonce', 'nonce');
$level_id = intval($_POST['level_id'] ?? 0);
// Verificar si tiene cursos
$courses_count = $this->db->get_var($this->db->prepare(
"SELECT COUNT(*) FROM {$this->db->prefix}wes_courses
WHERE level_id = %d AND deleted_at IS NULL",
$level_id
));
if ($courses_count > 0) {
wp_send_json_error('No se puede eliminar: tiene cursos asociados');
}
$result = $this->db->update(
$this->table_levels,
array('deleted_at' => current_time('mysql')),
array('id' => $level_id)
);
if ($result !== false) {
wp_send_json_success('Nivel eliminado correctamente');
} else {
wp_send_json_error('Error al eliminar el nivel');
}
}
// ==========================================
// AJAX HANDLERS - ASIGNACIÓN SEDE-IDIOMAS
// ==========================================
public function ajax_get_branch_languages() {
check_ajax_referer('wes_nonce', 'nonce');
$branch_id = intval($_POST['branch_id'] ?? 0);
if ($branch_id <= 0) {
wp_send_json_error('ID de sede requerido');
}
$query = "SELECT l.id, l.name, l.code,
CASE WHEN bl.id IS NOT NULL THEN 1 ELSE 0 END as assigned
FROM {$this->table_languages} l
LEFT JOIN {$this->table_branch_languages} bl ON l.id = bl.language_id AND bl.branch_id = %d
WHERE l.deleted_at IS NULL
ORDER BY l.name";
$languages = $this->db->get_results($this->db->prepare($query, $branch_id));
wp_send_json_success($languages);
}
public function ajax_toggle_branch_language() {
check_ajax_referer('wes_nonce', 'nonce');
$branch_id = intval($_POST['branch_id'] ?? 0);
$language_id = intval($_POST['language_id'] ?? 0);
$assign = intval($_POST['assign'] ?? 0);
if ($branch_id <= 0 || $language_id <= 0) {
wp_send_json_error('IDs requeridos');
}
if ($assign) {
// Asignar idioma a sede
$result = $this->db->replace(
$this->table_branch_languages,
array(
'branch_id' => $branch_id,
'language_id' => $language_id,
'status' => 'active',
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
)
);
$message = 'Idioma asignado a la sede';
} else {
// Desasignar idioma de sede
$result = $this->db->delete(
$this->table_branch_languages,
array(
'branch_id' => $branch_id,
'language_id' => $language_id
)
);
$message = 'Idioma desasignado de la sede';
}
if ($result !== false) {
wp_send_json_success($message);
} else {
wp_send_json_error('Error al procesar la asignación');
}
}
}
// Inicializar el módulo
new WES_Config_Module();