<?php
/**
* Controlador AJAX para Working English System
*/
if (!defined('ABSPATH')) {
exit;
}
class WES_Ajax {
public function __construct() {
$this->init_hooks();
}
/**
* Inicializar hooks AJAX
*/
private function init_hooks() {
// AJAX para usuarios logueados
add_action('wp_ajax_wes_get_levels_by_language', array($this, 'get_levels_by_language'));
add_action('wp_ajax_wes_get_groups_by_filters', array($this, 'get_groups_by_filters'));
add_action('wp_ajax_wes_search_students', array($this, 'search_students'));
add_action('wp_ajax_wes_get_student_info', array($this, 'get_student_info'));
add_action('wp_ajax_wes_delete_record', array($this, 'delete_record'));
add_action('wp_ajax_wes_update_student_status', array($this, 'update_student_status'));
add_action('wp_ajax_wes_calculate_prorated_fee', array($this, 'calculate_prorated_fee'));
add_action('wp_ajax_wes_get_student_charges', array($this, 'get_student_charges'));
add_action('wp_ajax_wes_validate_receipt_number', array($this, 'validate_receipt_number'));
// AJAX público (si es necesario)
add_action('wp_ajax_nopriv_wes_public_search', array($this, 'public_search'));
}
/**
* Verificar nonce y permisos
*/
private function verify_request($capability = 'manage_wes_system') {
if (!wp_verify_nonce($_POST['nonce'], 'wes_nonce')) {
wp_die(__('Error de seguridad', 'working-english-system'));
}
if (!current_user_can($capability)) {
wp_die(__('No tienes permisos para realizar esta acción', 'working-english-system'));
}
}
/**
* Obtener niveles por idioma
*/
public function get_levels_by_language() {
$this->verify_request('view_wes_students');
$language_id = intval($_POST['language_id']);
if (!$language_id) {
wp_send_json_error(__('ID de idioma inválido', 'working-english-system'));
}
global $wpdb;
$levels = $wpdb->get_results($wpdb->prepare(
"SELECT id, name, code FROM {$wpdb->prefix}wes_levels
WHERE language_id = %d AND status = 'active'
ORDER BY order_number",
$language_id
));
if ($levels) {
wp_send_json_success($levels);
} else {
wp_send_json_error(__('No se encontraron niveles para este idioma', 'working-english-system'));
}
}
/**
* Obtener grupos por filtros
*/
public function get_groups_by_filters() {
$this->verify_request('view_wes_groups');
$language_id = intval($_POST['language_id'] ?? 0);
$program_id = intval($_POST['program_id'] ?? 0);
$level_id = intval($_POST['level_id'] ?? 0);
$branch_id = intval($_POST['branch_id'] ?? 0);
global $wpdb;
$where_conditions = array("g.status = 'active'");
$where_values = array();
if ($language_id) {
$where_conditions[] = "g.language_id = %d";
$where_values[] = $language_id;
}
if ($program_id) {
$where_conditions[] = "g.program_id = %d";
$where_values[] = $program_id;
}
if ($level_id) {
$where_conditions[] = "g.level_id = %d";
$where_values[] = $level_id;
}
if ($branch_id) {
$where_conditions[] = "g.branch_id = %d";
$where_values[] = $branch_id;
}
$sql = "SELECT g.id, g.code, g.name,
l.name as language_name,
p.name as program_name,
lv.name as level_name,
CONCAT(t.first_name, ' ', t.last_name) as teacher_name,
g.current_students, g.max_students
FROM {$wpdb->prefix}wes_groups g
LEFT JOIN {$wpdb->prefix}wes_languages l ON g.language_id = l.id
LEFT JOIN {$wpdb->prefix}wes_programs p ON g.program_id = p.id
LEFT JOIN {$wpdb->prefix}wes_levels lv ON g.level_id = lv.id
LEFT JOIN {$wpdb->prefix}wes_teachers t ON g.teacher_id = t.id
WHERE " . implode(' AND ', $where_conditions) . "
ORDER BY g.code";
if (!empty($where_values)) {
$groups = $wpdb->get_results($wpdb->prepare($sql, $where_values));
} else {
$groups = $wpdb->get_results($sql);
}
wp_send_json_success($groups);
}
/**
* Buscar estudiantes
*/
public function search_students() {
$this->verify_request('view_wes_students');
$search_term = sanitize_text_field($_POST['search_term'] ?? '');
$search_type = sanitize_text_field($_POST['search_type'] ?? 'name');
if (empty($search_term)) {
wp_send_json_error(__('Término de búsqueda requerido', 'working-english-system'));
}
global $wpdb;
switch ($search_type) {
case 'code':
$sql = "SELECT s.*, b.name as branch_name, g.name as group_name
FROM {$wpdb->prefix}wes_students s
LEFT JOIN {$wpdb->prefix}wes_branches b ON s.branch_id = b.id
LEFT JOIN {$wpdb->prefix}wes_groups g ON s.current_group_id = g.id
WHERE s.student_code LIKE %s
ORDER BY s.student_code";
$students = $wpdb->get_results($wpdb->prepare($sql, '%' . $search_term . '%'));
break;
case 'email':
$sql = "SELECT s.*, b.name as branch_name, g.name as group_name
FROM {$wpdb->prefix}wes_students s
LEFT JOIN {$wpdb->prefix}wes_branches b ON s.branch_id = b.id
LEFT JOIN {$wpdb->prefix}wes_groups g ON s.current_group_id = g.id
WHERE s.email LIKE %s
ORDER BY s.last_name, s.first_name";
$students = $wpdb->get_results($wpdb->prepare($sql, '%' . $search_term . '%'));
break;
default: // name
$sql = "SELECT s.*, b.name as branch_name, g.name as group_name
FROM {$wpdb->prefix}wes_students s
LEFT JOIN {$wpdb->prefix}wes_branches b ON s.branch_id = b.id
LEFT JOIN {$wpdb->prefix}wes_groups g ON s.current_group_id = g.id
WHERE CONCAT(s.first_name, ' ', s.last_name) LIKE %s
OR CONCAT(s.last_name, ' ', s.first_name) LIKE %s
ORDER BY s.last_name, s.first_name";
$students = $wpdb->get_results($wpdb->prepare($sql, '%' . $search_term . '%', '%' . $search_term . '%'));
break;
}
if ($students) {
// Agregar badges de estado
foreach ($students as &$student) {
$student->status_badge = WES_Utils::get_student_status_badge($student->status);
$student->full_name = $student->first_name . ' ' . $student->last_name;
}
wp_send_json_success($students);
} else {
wp_send_json_error(__('No se encontraron estudiantes', 'working-english-system'));
}
}
/**
* Obtener información completa del estudiante
*/
public function get_student_info() {
$this->verify_request('view_wes_students');
$student_id = intval($_POST['student_id']);
if (!$student_id) {
wp_send_json_error(__('ID de estudiante inválido', 'working-english-system'));
}
global $wpdb;
// Información básica del estudiante
$student = $wpdb->get_row($wpdb->prepare(
"SELECT s.*, b.name as branch_name, g.name as group_name, g.code as group_code
FROM {$wpdb->prefix}wes_students s
LEFT JOIN {$wpdb->prefix}wes_branches b ON s.branch_id = b.id
LEFT JOIN {$wpdb->prefix}wes_groups g ON s.current_group_id = g.id
WHERE s.id = %d",
$student_id
));
if (!$student) {
wp_send_json_error(__('Estudiante no encontrado', 'working-english-system'));
}
// Historial de inscripciones
$enrollments = $wpdb->get_results($wpdb->prepare(
"SELECT e.*, g.name as group_name, g.code as group_code,
l.name as language_name, p.name as program_name, lv.name as level_name
FROM {$wpdb->prefix}wes_enrollments e
JOIN {$wpdb->prefix}wes_groups g ON e.group_id = g.id
LEFT JOIN {$wpdb->prefix}wes_languages l ON g.language_id = l.id
LEFT JOIN {$wpdb->prefix}wes_programs p ON g.program_id = p.id
LEFT JOIN {$wpdb->prefix}wes_levels lv ON g.level_id = lv.id
WHERE e.student_id = %d
ORDER BY e.enrollment_date DESC",
$student_id
));
// Cargos pendientes
$charges = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wes_student_charges
WHERE student_id = %d AND status IN ('pending', 'partial')
ORDER BY due_date",
$student_id
));
// Últimos pagos
$payments = $wpdb->get_results($wpdb->prepare(
"SELECT p.*, c.description as charge_description
FROM {$wpdb->prefix}wes_student_payments p
LEFT JOIN {$wpdb->prefix}wes_student_charges c ON p.charge_id = c.id
WHERE p.student_id = %d
ORDER BY p.payment_date DESC
LIMIT 5",
$student_id
));
$data = array(
'student' => $student,
'enrollments' => $enrollments,
'charges' => $charges,
'payments' => $payments
);
wp_send_json_success($data);
}
/**
* Eliminar registro
*/
public function delete_record() {
$this->verify_request('delete_wes_data');
$table = sanitize_text_field($_POST['table']);
$record_id = intval($_POST['record_id']);
if (!$record_id || !$table) {
wp_send_json_error(__('Datos insuficientes para eliminar', 'working-english-system'));
}
// Tablas permitidas para eliminación
$allowed_tables = array(
'wes_students',
'wes_teachers',
'wes_groups',
'wes_student_charges',
'wes_student_payments'
);
if (!in_array($table, $allowed_tables)) {
wp_send_json_error(__('Tabla no permitida', 'working-english-system'));
}
global $wpdb;
$result = $wpdb->delete(
$wpdb->prefix . $table,
array('id' => $record_id),
array('%d')
);
if ($result !== false) {
WES_Utils::log("Registro eliminado: {$table} ID {$record_id} por usuario " . get_current_user_id());
wp_send_json_success(__('Registro eliminado correctamente', 'working-english-system'));
} else {
wp_send_json_error(__('Error al eliminar el registro', 'working-english-system'));
}
}
/**
* Actualizar estado del estudiante
*/
public function update_student_status() {
$this->verify_request('manage_wes_students');
$student_id = intval($_POST['student_id']);
$new_status = sanitize_text_field($_POST['status']);
$allowed_statuses = array('active', 'inactive', 'graduated', 'suspended');
if (!$student_id || !in_array($new_status, $allowed_statuses)) {
wp_send_json_error(__('Datos inválidos', 'working-english-system'));
}
global $wpdb;
$result = $wpdb->update(
$wpdb->prefix . 'wes_students',
array('status' => $new_status),
array('id' => $student_id),
array('%s'),
array('%d')
);
if ($result !== false) {
$badge = WES_Utils::get_student_status_badge($new_status);
wp_send_json_success(array(
'message' => __('Estado actualizado correctamente', 'working-english-system'),
'badge' => $badge
));
} else {
wp_send_json_error(__('Error al actualizar el estado', 'working-english-system'));
}
}
/**
* Calcular cuota prorrateada
*/
public function calculate_prorated_fee() {
$this->verify_request('manage_wes_finance');
$start_date = sanitize_text_field($_POST['start_date']);
$end_date = sanitize_text_field($_POST['end_date']);
$monthly_fee = floatval($_POST['monthly_fee']);
$modality = sanitize_text_field($_POST['modality']);
if (!$start_date || !$end_date || !$monthly_fee) {
wp_send_json_error(__('Datos insuficientes para el cálculo', 'working-english-system'));
}
if (!WES_Utils::validate_date($start_date) || !WES_Utils::validate_date($end_date)) {
wp_send_json_error(__('Fechas inválidas', 'working-english-system'));
}
$prorated_amount = WES_Utils::calculate_prorated_fees($start_date, $end_date, $monthly_fee, $modality);
wp_send_json_success(array(
'prorated_amount' => $prorated_amount,
'formatted_amount' => wes_format_currency($prorated_amount)
));
}
/**
* Obtener cargos del estudiante
*/
public function get_student_charges() {
$this->verify_request('view_wes_finance');
$student_id = intval($_POST['student_id']);
if (!$student_id) {
wp_send_json_error(__('ID de estudiante inválido', 'working-english-system'));
}
global $wpdb;
$charges = $wpdb->get_results($wpdb->prepare(
"SELECT c.*, g.name as group_name
FROM {$wpdb->prefix}wes_student_charges c
LEFT JOIN {$wpdb->prefix}wes_groups g ON c.group_id = g.id
WHERE c.student_id = %d
ORDER BY c.due_date DESC",
$student_id
));
if ($charges) {
foreach ($charges as &$charge) {
$charge->status_badge = WES_Utils::get_payment_status_badge($charge->status);
$charge->formatted_amount = wes_format_currency($charge->final_amount);
$charge->formatted_paid = wes_format_currency($charge->paid_amount);
$charge->balance = $charge->final_amount - $charge->paid_amount;
$charge->formatted_balance = wes_format_currency($charge->balance);
}
wp_send_json_success($charges);
} else {
wp_send_json_error(__('No se encontraron cargos para este estudiante', 'working-english-system'));
}
}
/**
* Validar número de recibo
*/
public function validate_receipt_number() {
$this->verify_request('process_payments');
$receipt_number = sanitize_text_field($_POST['receipt_number']);
if (!$receipt_number) {
wp_send_json_error(__('Número de recibo requerido', 'working-english-system'));
}
global $wpdb;
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}wes_student_payments WHERE receipt_number = %s",
$receipt_number
));
if ($exists > 0) {
wp_send_json_error(__('Este número de recibo ya existe', 'working-english-system'));
} else {
wp_send_json_success(__('Número de recibo disponible', 'working-english-system'));
}
}
/**
* Búsqueda pública (si es necesaria)
*/
public function public_search() {
// Implementar si se necesita búsqueda pública
wp_send_json_error(__('Funcionalidad no disponible', 'working-english-system'));
}
/**
* Obtener dashboard stats via AJAX
*/
public function get_dashboard_stats() {
$this->verify_request('manage_wes_system');
global $wpdb;
$stats = array();
// Estudiantes por estado
$student_stats = $wpdb->get_results(
"SELECT status, COUNT(*) as count FROM {$wpdb->prefix}wes_students GROUP BY status"
);
foreach ($student_stats as $stat) {
$stats['students'][$stat->status] = $stat->count;
}
// Ingresos por mes (últimos 6 meses)
$revenue_stats = $wpdb->get_results("
SELECT DATE_FORMAT(payment_date, '%Y-%m') as month, SUM(amount) as total
FROM {$wpdb->prefix}wes_student_payments
WHERE payment_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY DATE_FORMAT(payment_date, '%Y-%m')
ORDER BY month
");
foreach ($revenue_stats as $stat) {
$stats['revenue'][$stat->month] = floatval($stat->total);
}
// Grupos por idioma
$group_stats = $wpdb->get_results("
SELECT l.name, COUNT(g.id) as count
FROM {$wpdb->prefix}wes_groups g
JOIN {$wpdb->prefix}wes_languages l ON g.language_id = l.id
WHERE g.status = 'active'
GROUP BY l.id, l.name
");
foreach ($group_stats as $stat) {
$stats['groups'][$stat->name] = $stat->count;
}
wp_send_json_success($stats);
}
/**
* Obtener datos para select dependiente
*/
public function get_dependent_options() {
$this->verify_request('view_wes_students');
$parent_field = sanitize_text_field($_POST['parent_field']);
$parent_value = intval($_POST['parent_value']);
$target_field = sanitize_text_field($_POST['target_field']);
global $wpdb;
$options = array();
switch ($parent_field . '_' . $target_field) {
case 'language_levels':
$options = $wpdb->get_results($wpdb->prepare(
"SELECT id, name, code FROM {$wpdb->prefix}wes_levels
WHERE language_id = %d AND status = 'active'
ORDER BY order_number",
$parent_value
));
break;
case 'branch_teachers':
$options = $wpdb->get_results($wpdb->prepare(
"SELECT t.id, CONCAT(t.first_name, ' ', t.last_name) as name
FROM {$wpdb->prefix}wes_teachers t
JOIN {$wpdb->prefix}wes_groups g ON t.id = g.teacher_id
WHERE g.branch_id = %d AND t.status = 'active'
GROUP BY t.id
ORDER BY t.first_name, t.last_name",
$parent_value
));
break;
case 'teacher_groups':
$options = $wpdb->get_results($wpdb->prepare(
"SELECT id, code as name, name as description
FROM {$wpdb->prefix}wes_groups
WHERE teacher_id = %d AND status = 'active'
ORDER BY code",
$parent_value
));
break;
}
wp_send_json_success($options);
}
/**
* Exportar datos
*/
public function export_data() {
$this->verify_request('export_wes_data');
$export_type = sanitize_text_field($_POST['export_type']);
$format = sanitize_text_field($_POST['format']) ?: 'csv';
global $wpdb;
switch ($export_type) {
case 'students':
$data = $wpdb->get_results("
SELECT s.student_code, s.first_name, s.last_name, s.email, s.phone,
s.registration_date, s.status, b.name as branch, g.name as current_group
FROM {$wpdb->prefix}wes_students s
LEFT JOIN {$wpdb->prefix}wes_branches b ON s.branch_id = b.id
LEFT JOIN {$wpdb->prefix}wes_groups g ON s.current_group_id = g.id
ORDER BY s.student_code
", ARRAY_A);
$filename = 'estudiantes_' . date('Y-m-d') . '.csv';
$headers = array('Código', 'Nombre', 'Apellido', 'Email', 'Teléfono', 'Fecha Registro', 'Estado', 'Sede', 'Grupo Actual');
break;
case 'payments':
$data = $wpdb->get_results("
SELECT p.receipt_number, CONCAT(s.first_name, ' ', s.last_name) as student_name,
s.student_code, p.amount, p.payment_method, p.payment_date,
c.description as charge_description
FROM {$wpdb->prefix}wes_student_payments p
JOIN {$wpdb->prefix}wes_students s ON p.student_id = s.id
LEFT JOIN {$wpdb->prefix}wes_student_charges c ON p.charge_id = c.id
ORDER BY p.payment_date DESC
", ARRAY_A);
$filename = 'pagos_' . date('Y-m-d') . '.csv';
$headers = array('Recibo', 'Estudiante', 'Código', 'Monto', 'Método', 'Fecha', 'Descripción');
break;
default:
wp_send_json_error(__('Tipo de exportación no válido', 'working-english-system'));
}
if ($format === 'csv') {
WES_Utils::export_to_csv($data, $filename, $headers);
} else {
wp_send_json_error(__('Formato no soportado', 'working-english-system'));
}
}
}