Last active
July 25, 2025 22:34
-
-
Save caysoasesores/55e97b3a3dd5a6fe35789c55544d806b to your computer and use it in GitHub Desktop.
bakerypos-system plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| class BakeryPOS_API { | |
| public function __construct() { | |
| // Constructor, aunque esté vacío, es una buena práctica tenerlo. | |
| } | |
| public function register_routes() { | |
| // --- TUS RUTAS ORIGINALES DEL TPV (INTACTAS) --- | |
| register_rest_route('bakerypos/v1', '/login', [ | |
| 'methods' => 'POST', | |
| 'callback' => [$this, 'handle_login_and_get_data'], | |
| 'permission_callback' => '__return_true', | |
| ]); | |
| register_rest_route('bakerypos/v1', '/sale', [ | |
| 'methods' => 'POST', | |
| 'callback' => [$this, 'handle_sale'], | |
| 'permission_callback' => function () { return is_user_logged_in(); } | |
| ]); | |
| register_rest_route('bakerypos/v1', '/tasks', [ | |
| 'methods' => 'GET', | |
| 'callback' => [$this, 'get_tasks'], | |
| 'permission_callback' => function () { return is_user_logged_in(); } | |
| ]); | |
| register_rest_route('bakerypos/v1', '/update-task', [ | |
| 'methods' => 'POST', | |
| 'callback' => [$this, 'update_task_status'], | |
| 'permission_callback' => function () { return is_user_logged_in(); } | |
| ]); | |
| register_rest_route('bakerypos/v1', '/expenses', [ | |
| 'methods' => 'GET', | |
| 'callback' => [$this, 'get_expense_categories'], | |
| 'permission_callback' => function () { return is_user_logged_in(); } | |
| ]); | |
| register_rest_route('bakerypos/v1', '/scrap-products', [ | |
| 'methods' => 'GET', | |
| 'callback' => [$this, 'get_scrap_products'], | |
| 'permission_callback' => function () { return is_user_logged_in(); } | |
| ]); | |
| // --- NUEVAS RUTAS PARA EL PANEL DE ADMINISTRACIÓN --- | |
| register_rest_route('bakerypos/v1', '/branches', array( | |
| 'methods' => 'GET', | |
| 'callback' => array($this, 'get_all_branches'), | |
| 'permission_callback' => function () { return current_user_can('manage_options'); } | |
| )); | |
| register_rest_route('bakerypos/v1', '/products', array( | |
| 'methods' => 'GET', | |
| 'callback' => array($this, 'get_all_products'), | |
| 'permission_callback' => function () { return current_user_can('manage_options'); } | |
| )); | |
| register_rest_route('bakerypos/v1', '/reports/sales', array( | |
| 'methods' => 'GET', | |
| 'callback' => array($this, 'get_sales_report'), | |
| 'permission_callback' => function () { return current_user_can('manage_options'); } | |
| )); | |
| register_rest_route('bakerypos/v1', '/reports/expenses', array( | |
| 'methods' => 'GET', | |
| 'callback' => array($this, 'get_expenses_report'), | |
| 'permission_callback' => function () { return current_user_can('manage_options'); } | |
| )); | |
| } | |
| // --- TUS FUNCIONES ORIGINALES DEL TPV (INTACTAS) --- | |
| public function handle_login_and_get_data(WP_REST_Request $request) { | |
| $username = sanitize_text_field($request->get_param('username')); | |
| $password = $request->get_param('password'); | |
| $user = wp_authenticate($username, $password); | |
| if (is_wp_error($user)) { | |
| return new WP_Error('login_failed', 'Usuario o contraseña incorrectos.', ['status' => 401]); | |
| } | |
| wp_set_current_user($user->ID); | |
| $products = $this->get_products_data_privately(); | |
| $tasks = $this->get_tasks_privately(); | |
| $expense_categories = $this->get_expense_categories_privately(); | |
| $scrap_products = $this->get_scrap_products_privately(); | |
| return new WP_REST_Response([ | |
| 'success' => true, | |
| 'user' => ['display_name' => $user->display_name], | |
| 'products' => $products, | |
| 'tasks' => $tasks, | |
| 'expense_categories' => $expense_categories, | |
| 'scrap_products' => $scrap_products, | |
| ], 200); | |
| } | |
| public function handle_sale(WP_REST_Request $request) { | |
| // Tu lógica para manejar la venta | |
| return new WP_REST_Response(['success' => true, 'message' => 'Venta registrada.'], 200); | |
| } | |
| public function get_tasks(WP_REST_Request $request) { | |
| return new WP_REST_Response($this->get_tasks_privately(), 200); | |
| } | |
| public function update_task_status(WP_REST_Request $request) { | |
| // Tu lógica para actualizar tareas | |
| return new WP_REST_Response(['success' => true], 200); | |
| } | |
| public function get_expense_categories(WP_REST_Request $request) { | |
| return new WP_REST_Response($this->get_expense_categories_privately(), 200); | |
| } | |
| public function get_scrap_products(WP_REST_Request $request) { | |
| return new WP_REST_Response($this->get_scrap_products_privately(), 200); | |
| } | |
| private function get_products_data_privately() { | |
| $products = wc_get_products(['status' => 'publish', 'limit' => -1]); | |
| $formatted_products = []; | |
| foreach ($products as $product) { | |
| $formatted_products[] = [ | |
| 'id' => $product->get_id(), | |
| 'name' => $product->get_name(), | |
| 'price' => wc_format_decimal($product->get_price(), 2), | |
| 'image' => wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') ?: wc_placeholder_img_src(), | |
| ]; | |
| } | |
| return $formatted_products; | |
| } | |
| private function get_tasks_privately() { | |
| return get_option('bakerypos_tasks', [['id' => 1, 'text' => 'Limpiar mostrador', 'completed' => false]]); | |
| } | |
| private function get_expense_categories_privately() { | |
| return get_option('bakerypos_expense_categories', ['Limpieza', 'Insumos', 'Servicios', 'Otros']); | |
| } | |
| private function get_scrap_products_privately() { | |
| $products = wc_get_products(['status' => 'publish', 'limit' => -1]); | |
| $product_list = []; | |
| foreach($products as $product) { | |
| $product_list[] = ['id' => $product->get_id(), 'name' => $product->get_name()]; | |
| } | |
| return $product_list; | |
| } | |
| // --- NUEVAS FUNCIONES DE CALLBACK PARA EL ADMIN --- | |
| public function get_all_branches(WP_REST_Request $request) { | |
| global $wpdb; | |
| $table_name = $wpdb->prefix . 'bakerypos_sucursales'; | |
| $results = $wpdb->get_results("SELECT sucursal_id, name FROM $table_name ORDER BY name ASC"); | |
| return new WP_REST_Response($results, 200); | |
| } | |
| public function get_all_products(WP_REST_Request $request) { | |
| if (!class_exists('WooCommerce')) { | |
| return new WP_Error('woocommerce_inactive', 'WooCommerce no está activo.', array('status' => 500)); | |
| } | |
| $products = wc_get_products(array('status' => 'publish', 'limit' => -1)); | |
| $formatted_products = array(); | |
| foreach ($products as $product) { | |
| $formatted_products[] = array( | |
| 'id' => $product->get_id(), | |
| 'name' => $product->get_name(), | |
| 'sku' => $product->get_sku(), | |
| 'stock' => $product->get_stock_quantity() ?? 0, | |
| ); | |
| } | |
| return new WP_REST_Response($formatted_products, 200); | |
| } | |
| public function get_sales_report(WP_REST_Request $request) { | |
| $dummy_sales = [ | |
| ['session_id' => 1, 'user_name' => 'Admin', 'sucursal' => 'Centro', 'total' => 150.50, 'time' => '2025-07-25 10:00:00'], | |
| ['session_id' => 2, 'user_name' => 'Vendedor 1', 'sucursal' => 'Norte', 'total' => 85.00, 'time' => '2025-07-25 11:30:00'], | |
| ]; | |
| return new WP_REST_Response($dummy_sales, 200); | |
| } | |
| public function get_expenses_report(WP_REST_Request $request) { | |
| $dummy_expenses = [ | |
| ['expense_id' => 1, 'user_name' => 'Vendedor 1', 'sucursal' => 'Norte', 'category' => 'Limpieza', 'amount' => 25.00, 'time' => '2025-07-25 09:15:00'], | |
| ['expense_id' => 2, 'user_name' => 'Admin', 'sucursal' => 'Centro', 'category' => 'Insumos', 'amount' => 50.75, 'time' => '2025-07-25 12:00:00'], | |
| ]; | |
| return new WP_REST_Response($dummy_expenses, 200); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* --- Estilos para el Gestor de Tareas --- */ | |
| #task-list-sortable { | |
| list-style: none; | |
| margin: 1em 0; | |
| padding: 0; | |
| } | |
| .task-item { | |
| display: flex; | |
| align-items: center; | |
| background: #f9f9f9; | |
| border: 1px solid #ddd; | |
| padding: 10px; | |
| margin-bottom: 5px; | |
| } | |
| .task-item .handle { | |
| cursor: move; | |
| color: #a0a5aa; | |
| margin-right: 10px; | |
| } | |
| .task-item .task-description { | |
| flex-grow: 1; | |
| } | |
| #add-task-form { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 1em; | |
| } | |
| #add-task-form input { | |
| flex-grow: 1; | |
| } | |
| /* Estilos para páginas de admin (Reportes, Tareas, Sucursales, Inventario) */ | |
| .report-section { margin: 2em 0; background: #fff; padding: 1.5em; border: 1px solid #c3c4c7; box-shadow: 0 1px 1px rgba(0,0,0,.04); } | |
| .report-table-wrapper { margin-top: 1em; } | |
| .report-table-wrapper .error { color: #d63638; } | |
| #task-list-sortable { list-style: none; margin: 1em 0; padding: 0; } | |
| .task-item { display: flex; align-items: center; background: #f9f9f9; border: 1px solid #ddd; padding: 10px; margin-bottom: 5px; } | |
| .task-item .handle { cursor: move; color: #a0a5aa; margin-right: 10px; } | |
| .task-item .task-description { flex-grow: 1; } | |
| #add-task-form, #add-branch-form { display: flex; gap: 10px; margin-top: 1em; } | |
| #add-task-form input, #add-branch-form input { flex-grow: 1; } | |
| #inventory-manager .stock-input { width: 100%; text-align: right; } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* --- ESTILOS GENERALES Y LOGIN --- */ | |
| .pos-container { | |
| max-width: 1200px; | |
| margin: 20px auto; | |
| } | |
| #login-screen { | |
| background: #fff; | |
| padding: 2em; | |
| border: 1px solid #ddd; | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, .07); | |
| max-width: 380px; | |
| margin: 2em auto; | |
| } | |
| #login-screen h3 { | |
| margin-top: 0; | |
| border-bottom: 1px solid #eee; | |
| padding-bottom: 15px; | |
| margin-bottom: 20px; | |
| } | |
| .form-group { | |
| margin-bottom: 1.2em; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-weight: 600; | |
| } | |
| .form-group .regular-text { | |
| width: 100%; | |
| padding: 8px 10px; | |
| } | |
| .button.button-large { | |
| width: 100%; | |
| padding: 10px; | |
| height: auto; | |
| font-size: 1.1em; | |
| } | |
| /* --- CABECERA DEL TPV --- */ | |
| #pos-header { | |
| margin-bottom: 20px; | |
| padding-bottom: 15px; | |
| border-bottom: 1px solid #ddd; | |
| display: flex; | |
| gap: 10px; | |
| } | |
| #pos-header .dashicons { | |
| vertical-align: middle; | |
| margin-top: -3px; | |
| } | |
| #register-scrap-btn { | |
| background: #f8f9fa; | |
| border-color: #d1d3d5; | |
| color: #495057; | |
| } | |
| #register-scrap-btn:hover { | |
| background: #e2e6ea; | |
| } | |
| /* --- INTERFAZ PRINCIPAL DEL TPV --- */ | |
| #pos-main-interface { | |
| display: flex; | |
| gap: 20px; | |
| } | |
| #pos-products-grid { | |
| flex: 3; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| align-content: flex-start; | |
| } | |
| #pos-ticket { | |
| flex: 1; | |
| background: #fdfdfd; | |
| border: 1px solid #ddd; | |
| padding: 20px; | |
| height: fit-content; | |
| border-radius: 4px; | |
| } | |
| .pos-product-card { | |
| width: 140px; | |
| background: #fff; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| padding: 10px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: box-shadow 0.2s, transform 0.2s; | |
| } | |
| .pos-product-card:hover { | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| transform: translateY(-2px); | |
| } | |
| .pos-product-card img { max-width: 100%; height: 100px; object-fit: cover; margin-bottom: 10px; } | |
| .pos-product-card .product-name { font-weight: 600; margin-bottom: 5px; height: 40px; overflow: hidden; font-size: 0.9em; } | |
| .pos-product-card .product-price { color: #2271b1; font-weight: bold; font-size: 1.1em; } | |
| /* --- ESTILOS DEL TICKET --- */ | |
| #pos-ticket h2 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px; } | |
| #pos-ticket-items { min-height: 200px; margin-bottom: 15px; } | |
| .ticket-empty-message { color: #888; text-align: center; padding-top: 50px; } | |
| .ticket-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 4px; border-bottom: 1px dashed #ddd; gap: 10px; } | |
| .ticket-item .item-name { flex-grow: 1; font-size: 0.95em; } | |
| .ticket-item .item-subtotal { font-weight: 600; min-width: 65px; text-align: right; } | |
| .ticket-item .remove-btn { background: transparent; border: none; color: #a02020; font-size: 22px; font-weight: bold; cursor: pointer; border-radius: 4px; padding: 0 5px; } | |
| .ticket-item .remove-btn:hover { background: #f8d7da; } | |
| #pos-ticket-total { border-top: 2px solid #333; padding-top: 15px; font-size: 1.5em; display: flex; justify-content: space-between; } | |
| #pos-ticket-actions { margin-top: 20px; display: flex; gap: 10px; } | |
| .button-clear-ticket { flex-grow: 1; background: #787c82 !important; border-color: #787c82 !important; color: #fff !important; text-shadow: none !important; box-shadow: none !important; } | |
| .button-clear-ticket:hover { background: #5c6166 !important; border-color: #5c6166 !important; } | |
| .button-pay { flex-grow: 2; } | |
| /* --- ESTILOS PARA TODOS LOS MODALES --- */ | |
| .modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.6); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| #payment-modal, #expense-modal, #scrap-modal, #task-modal { | |
| background: #fff; | |
| padding: 2em; | |
| border-radius: 8px; | |
| width: 90%; | |
| max-width: 450px; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.3); | |
| } | |
| #payment-modal h2, #expense-modal h2, #scrap-modal h2, #task-modal h2 { margin-top: 0; } | |
| .modal-total { font-size: 1.8em; font-weight: bold; color: #333; text-align: center; margin: 1em 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; padding: 0.5em 0; } | |
| .payment-methods { display: grid; grid-template-columns: 1fr 1fr; gap: 1em; margin-bottom: 1.5em; } | |
| .payment-method-btn { padding: 1em; font-size: 1.1em; cursor: pointer; border: 2px solid #ddd; background: #f9f9f9; border-radius: 5px; } | |
| .payment-method-btn.active { border-color: #007cba; background: #f0f6fc; font-weight: bold; } | |
| #cash-details { margin-bottom: 1.5em; } | |
| #cash-details input, #expense-modal input, #scrap-modal input, #scrap-modal select { width: 100%; padding: 0.8em; font-size: 1.1em; box-sizing: border-box; } | |
| #cash-details input { text-align: right; } | |
| .cash-change { font-size: 1.4em; text-align: right; margin-top: 0.5em; } | |
| .cash-change span { font-weight: bold; } | |
| .modal-actions { display: flex; justify-content: space-between; gap: 1em; margin-top: 1.5em; } | |
| .modal-actions button { flex-grow: 1; } | |
| /* --- ESTILOS ESPECÍFICOS PARA MODAL DE TAREAS --- */ | |
| #task-modal-overlay { z-index: 1001; } | |
| #task-modal { max-width: 500px; } | |
| #task-checklist { list-style: none; margin: 1.5em 0; padding: 0; max-height: 50vh; overflow-y: auto; } | |
| .task-checklist-item { display: flex; align-items: center; font-size: 1.2em; padding: 0.8em 0.5em; border-bottom: 1px solid #eee; } | |
| .task-checklist-item input[type="checkbox"] { width: 20px; height: 20px; margin-right: 15px; } | |
| .task-checklist-item label { flex-grow: 1; } | |
| /* --- RESPONSIVIDAD --- */ | |
| @media (max-width: 960px) { | |
| #pos-main-interface { flex-direction: column; } | |
| #pos-products-grid, #pos-ticket { flex: 1 1 100%; } | |
| } | |
| @media (max-width: 480px) { | |
| .pos-product-card { width: calc(50% - 10px); } | |
| #pos-ticket, #payment-modal, #expense-modal, #scrap-modal, #task-modal { padding: 15px; } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function ($) { | |
| 'use strict'; | |
| $(function () { | |
| const appContainer = $('#branches-manager'); | |
| let branches = []; | |
| function loadBranches() { | |
| appContainer.html('<p>Cargando sucursales...</p>'); | |
| $.ajax({ | |
| url: bakerypos_data.api_url + 'branches', | |
| method: 'GET', | |
| beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }).done(function(response) { | |
| if (response.success) { | |
| branches = response.branches; | |
| renderManager(); | |
| } | |
| }).fail(function() { | |
| appContainer.html('<p>Error al cargar las sucursales.</p>'); | |
| }); | |
| } | |
| function renderManager() { | |
| const managerHTML = ` | |
| <div class="report-section"> | |
| <h2>Sucursales Activas</h2> | |
| <table class="wp-list-table widefat fixed striped"> | |
| <thead><tr><th>ID</th><th>Nombre de la Sucursal</th></tr></thead> | |
| <tbody id="branches-list"></tbody> | |
| </table> | |
| </div> | |
| <div class="report-section"> | |
| <h2>Añadir Nueva Sucursal</h2> | |
| <form id="add-branch-form"> | |
| <input type="text" id="new-branch-name" placeholder="Nombre de la nueva sucursal" required> | |
| <button type="submit" class="button button-primary">Añadir Sucursal</button> | |
| </form> | |
| </div> | |
| `; | |
| appContainer.html(managerHTML); | |
| renderList(); | |
| } | |
| function renderList() { | |
| const listContainer = $('#branches-list'); | |
| listContainer.empty(); | |
| if (branches.length > 0) { | |
| branches.forEach(function(branch) { | |
| listContainer.append(`<tr><td>${branch.branch_id}</td><td>${branch.branch_name}</td></tr>`); | |
| }); | |
| } else { | |
| listContainer.html('<tr><td colspan="2">No hay sucursales registradas.</td></tr>'); | |
| } | |
| } | |
| appContainer.on('submit', '#add-branch-form', function(e) { | |
| e.preventDefault(); | |
| const newName = $('#new-branch-name').val().trim(); | |
| if (newName) { | |
| $(this).find('button').text('Añadiendo...').prop('disabled', true); | |
| $.ajax({ | |
| url: bakerypos_data.api_url + 'branches', | |
| method: 'POST', | |
| beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); }, | |
| contentType: 'application/json', | |
| data: JSON.stringify({ branch_name: newName }) | |
| }).done(function() { | |
| loadBranches(); // Recargamos la lista | |
| }).fail(function() { | |
| alert('Error al añadir la sucursal.'); | |
| }); | |
| } | |
| }); | |
| loadBranches(); | |
| }); | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function ($) { | |
| 'use strict'; | |
| $(function () { | |
| const appContainer = $('#inventory-manager'); | |
| if (!appContainer.length) return; | |
| // --- Almacenaremos las promesas de las llamadas AJAX --- | |
| let productsRequest; | |
| let branchesRequest; | |
| function renderLayout() { | |
| const layoutHTML = ` | |
| <div class="inventory-controls"> | |
| <label for="branch-select">Selecciona una Sucursal:</label> | |
| <select id="branch-select" disabled> | |
| <option>Cargando sucursales...</option> | |
| </select> | |
| </div> | |
| <div id="product-inventory-list"><p>Por favor, selecciona una sucursal para ver la lista de productos.</p></div> | |
| `; | |
| appContainer.html(layoutHTML); | |
| } | |
| function loadInitialData() { | |
| // Hacemos las llamadas a la API y guardamos las promesas | |
| branchesRequest = $.ajax({ | |
| url: bakerypos_data.api_url + 'branches', | |
| method: 'GET', | |
| beforeSend: function (xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }); | |
| productsRequest = $.ajax({ | |
| url: bakerypos_data.api_url + 'products', | |
| method: 'GET', | |
| beforeSend: function (xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }); | |
| } | |
| function populateBranchesSelect() { | |
| const select = $('#branch-select'); | |
| branchesRequest.done(function (branches) { | |
| select.empty().prop('disabled', false); | |
| if (branches && branches.length > 0) { | |
| select.append('<option value="">-- Elige una sucursal --</option>'); | |
| branches.forEach(branch => { | |
| select.append(`<option value="${branch.sucursal_id}">${branch.name}</option>`); | |
| }); | |
| } else { | |
| select.append('<option>No hay sucursales creadas.</option>'); | |
| } | |
| }).fail(function () { | |
| select.empty().append('<option>Error al cargar sucursales.</option>'); | |
| }); | |
| } | |
| function renderProductTable() { | |
| const productListContainer = $('#product-inventory-list'); | |
| productListContainer.html('<p>Cargando inventario...</p>'); | |
| // --- ESTA ES LA MAGIA --- | |
| // Le decimos a jQuery que espere a que la llamada de 'products' termine | |
| productsRequest.done(function (allProducts) { | |
| if (!allProducts || allProducts.length === 0) { | |
| productListContainer.html('<p>No se encontraron productos en WooCommerce.</p>'); | |
| return; | |
| } | |
| const tableRows = allProducts.map(product => ` | |
| <tr> | |
| <td>${product.name} (${product.sku || 'N/A'})</td> | |
| <td>${product.stock}</td> | |
| <td><input type="number" class="small-text" data-product-id="${product.id}" placeholder="0" min="0"></td> | |
| </tr> | |
| `).join(''); | |
| const tableHTML = ` | |
| <table class="wp-list-table widefat fixed striped"> | |
| <thead> | |
| <tr> | |
| <th>Producto (SKU)</th> | |
| <th>Stock General (WooCommerce)</th> | |
| <th>Cantidad a Asignar</th> | |
| </tr> | |
| </thead> | |
| <tbody>${tableRows}</tbody> | |
| </table> | |
| <p><button class="button button-primary">Asignar Inventario a Sucursal</button></p> | |
| `; | |
| productListContainer.html(tableHTML); | |
| }).fail(function () { | |
| productListContainer.html('<p style="color:red;">Error al cargar la lista de productos desde la API.</p>'); | |
| }); | |
| } | |
| // --- Eventos --- | |
| appContainer.on('change', '#branch-select', function() { | |
| if ($(this).val()) { | |
| renderProductTable(); | |
| } else { | |
| $('#product-inventory-list').html('<p>Por favor, selecciona una sucursal para ver la lista de productos.</p>'); | |
| } | |
| }); | |
| // --- Inicialización --- | |
| renderLayout(); | |
| loadInitialData(); | |
| populateBranchesSelect(); | |
| }); | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function ($) { | |
| 'use strict'; | |
| $(function () { | |
| const appContainer = $('#bakery-pos-app'); | |
| if (!appContainer.length) return; | |
| // --- ESTADO GLOBAL DE LA APP --- | |
| let ticketItems = []; | |
| let currentTotal = 0; | |
| let initialProducts = []; | |
| // --- LÓGICA DE INICIO --- | |
| function init() { | |
| if (bakerypos_data.is_logged_in) { | |
| appContainer.html('<div class="pos-container"><p>Cargando datos iniciales...</p></div>'); | |
| fetchInitialData(); | |
| } else { | |
| renderLoginScreen(); | |
| } | |
| } | |
| function fetchInitialData() { | |
| $.ajax({ | |
| url: bakerypos_data.api_url + 'initial-data', | |
| method: 'GET', | |
| beforeSend: function (xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }).done(function (response) { | |
| if (response.success) { | |
| initialProducts = response.products; | |
| if (response.tasks && response.tasks.length > 0 && !sessionStorage.getItem('bakerypos_tasks_completed')) { | |
| renderTaskModal(response.tasks); | |
| } else { | |
| renderMainPosScreen(initialProducts); | |
| } | |
| } | |
| }).fail(function() { | |
| appContainer.html('<p>Error al cargar los datos iniciales.</p>'); | |
| }); | |
| } | |
| // --- RENDERIZADO DE PANTALLAS --- | |
| function renderLoginScreen() { | |
| const loginHTML = ` | |
| <div class="pos-container"> | |
| <h2>Bienvenido al Punto de Venta</h2> | |
| <div id="login-screen"> | |
| <h3>Iniciar Sesión</h3> | |
| <form id="pos-login-form"> | |
| <div class="form-group"><label for="pos-username">Usuario</label><input type="text" id="pos-username" class="regular-text" name="username" required></div> | |
| <div class="form-group"><label for="pos-password">Contraseña</label><input type="password" id="pos-password" class="regular-text" name="password" required></div> | |
| <button type="submit" class="button button-primary button-large">Entrar</button> | |
| <div id="login-error" style="display: none; color: red; margin-top: 10px;"></div> | |
| </form> | |
| </div> | |
| </div>`; | |
| appContainer.html(loginHTML); | |
| } | |
| function renderMainPosScreen(products) { | |
| const mainPosHTML = ` | |
| <div id="pos-header"> | |
| <button class="button" id="register-expense-btn"><span class="dashicons dashicons-money-alt"></span> Registrar Gasto</button> | |
| <button class="button" id="register-scrap-btn"><span class="dashicons dashicons-trash"></span> Registrar Merma</button> | |
| </div> | |
| <div id="pos-main-interface"> | |
| <div id="pos-products-grid"></div> | |
| <div id="pos-ticket"> | |
| <h2>Ticket de Venta</h2> | |
| <div id="pos-ticket-items"><p class="ticket-empty-message">Selecciona un producto.</p></div> | |
| <div id="pos-ticket-total"><strong>Total:</strong><span id="ticket-total-amount">$0.00</span></div> | |
| <div id="pos-ticket-actions"><button class="button button-large button-clear-ticket">Limpiar</button><button class="button button-primary button-large button-pay" disabled>Pagar</button></div> | |
| </div> | |
| </div> | |
| <!-- Contenedores de Modales --> | |
| <div id="payment-modal-overlay" class="modal-overlay" style="display: none;"></div> | |
| <div id="expense-modal-overlay" class="modal-overlay" style="display: none;"></div> | |
| <div id="scrap-modal-overlay" class="modal-overlay" style="display: none;"></div> | |
| `; | |
| appContainer.html(mainPosHTML); | |
| // Renderizar productos | |
| const productsContainer = $('#pos-products-grid'); | |
| if (!products || products.length === 0) { | |
| productsContainer.html('<p>No se encontraron productos.</p>'); | |
| } else { | |
| products.forEach(function (product) { | |
| const productCardHTML = `<div class="pos-product-card" data-product-id="${product.id}" data-name="${product.name}" data-price="${product.price}"><img src="${product.image}" alt="${product.name}"><div class="product-name">${product.name}</div><div class="product-price">$${product.price}</div></div>`; | |
| productsContainer.append(productCardHTML); | |
| }); | |
| } | |
| } | |
| function renderTaskModal(tasks) { | |
| let taskItemsHTML = tasks.map((task, index) => ` | |
| <li class="task-checklist-item"> | |
| <input type="checkbox" id="task-${index}" class="task-checkbox"> | |
| <label for="task-${index}">${task.task_description}</label> | |
| </li>`).join(''); | |
| const taskModalHTML = ` | |
| <div id="task-modal-overlay" class="modal-overlay"> | |
| <div id="task-modal"> | |
| <h2>Tareas Pendientes del Turno</h2> | |
| <ul id="task-checklist">${taskItemsHTML}</ul> | |
| <div class="modal-actions"> | |
| <button id="confirm-tasks-btn" class="button button-primary button-large" disabled>Confirmar y Continuar</button> | |
| </div> | |
| </div> | |
| </div>`; | |
| appContainer.html(taskModalHTML); | |
| } | |
| function renderTicket() { | |
| const ticketItemsContainer = $('#pos-ticket-items'); | |
| const totalAmountSpan = $('#ticket-total-amount'); | |
| const payButton = $('.button-pay'); | |
| currentTotal = 0; | |
| ticketItemsContainer.empty(); | |
| if (ticketItems.length === 0) { | |
| ticketItemsContainer.html('<p class="ticket-empty-message">Selecciona un producto.</p>'); | |
| payButton.prop('disabled', true); | |
| } else { | |
| payButton.prop('disabled', false); | |
| ticketItems.forEach(function(item) { | |
| const itemSubtotal = item.price * item.quantity; | |
| currentTotal += itemSubtotal; | |
| const ticketItemHTML = `<div class="ticket-item" data-product-id="${item.id}"><span class="item-name">${item.name} (x${item.quantity})</span><span class="item-subtotal">$${itemSubtotal.toFixed(2)}</span><button class="control-btn remove-btn">×</button></div>`; | |
| ticketItemsContainer.append(ticketItemHTML); | |
| }); | |
| } | |
| totalAmountSpan.text(`$${currentTotal.toFixed(2)}`); | |
| } | |
| // --- MANEJO DE EVENTOS --- | |
| // Login | |
| appContainer.on('submit', '#pos-login-form', function (e) { | |
| e.preventDefault(); | |
| const $button = $(this).find('button[type="submit"]'); | |
| $button.text('Verificando...').prop('disabled', true); | |
| $.ajax({ url: bakerypos_data.api_url + 'login', method: 'POST', contentType: 'application/json', data: JSON.stringify({ username: $('#pos-username').val(), password: $('#pos-password').val() }) }) | |
| .done(response => { if (response.success) window.location.reload(); }) | |
| .fail(jqXHR => { $('#login-error').text(jqXHR.responseJSON?.message || 'Error').show(); $button.text('Entrar').prop('disabled', false); }); | |
| }); | |
| // Tareas | |
| appContainer.on('change', '.task-checkbox', function() { | |
| const allChecked = $('.task-checkbox').length === $('.task-checkbox:checked').length; | |
| $('#confirm-tasks-btn').prop('disabled', !allChecked); | |
| }); | |
| appContainer.on('click', '#confirm-tasks-btn', function() { | |
| sessionStorage.setItem('bakerypos_tasks_completed', 'true'); | |
| renderMainPosScreen(initialProducts); | |
| }); | |
| // Ticket | |
| appContainer.on('click', '.pos-product-card', function() { const productId = $(this).data('product-id'); const productName = $(this).data('name'); const productPrice = parseFloat($(this).data('price')); const existingItem = ticketItems.find(item => item.id === productId); if (existingItem) { existingItem.quantity++; } else { ticketItems.push({ id: productId, name: productName, price: productPrice, quantity: 1 }); } renderTicket(); }); | |
| appContainer.on('click', '.remove-btn', function() { const productId = $(this).closest('.ticket-item').data('product-id'); ticketItems = ticketItems.filter(i => i.id !== productId); renderTicket(); }); | |
| appContainer.on('click', '.button-clear-ticket', function() { if (confirm('¿Estás seguro?')) { ticketItems = []; renderTicket(); } }); | |
| // Modal de Pago | |
| let selectedPaymentMethod = null; | |
| appContainer.on('click', '.button-pay', function() { | |
| const paymentModalHTML = `<div id="payment-modal"><h2>Finalizar Venta</h2><div class="modal-total">Total: <span id="modal-total-amount">$${currentTotal.toFixed(2)}</span></div><div class="payment-methods"><button class="payment-method-btn" data-method="Efectivo">Efectivo</button><button class="payment-method-btn" data-method="Tarjeta">Tarjeta</button></div><div id="cash-details" style="display: none;"><label for="cash-received">Recibido:</label><input type="number" id="cash-received" step="0.01" min="0"><div class="cash-change">Cambio: <span id="cash-change-amount">$0.00</span></div></div><div class="modal-actions"><button id="cancel-payment-btn" class="button">Cancelar</button><button id="finalize-sale-btn" class="button button-primary" disabled>Finalizar Venta</button></div></div>`; | |
| $('#payment-modal-overlay').html(paymentModalHTML).fadeIn(200); | |
| }); | |
| appContainer.on('click', '#cancel-payment-btn, #payment-modal-overlay', function(e) { if (e.target === this || e.target.id === 'cancel-payment-btn') $('#payment-modal-overlay').fadeOut(200); }); | |
| appContainer.on('click', '.payment-method-btn', function() { selectedPaymentMethod = $(this).data('method'); $('.payment-method-btn').removeClass('active'); $(this).addClass('active'); if (selectedPaymentMethod === 'Efectivo') { $('#cash-details').slideDown(100); $('#cash-received').focus(); $('#finalize-sale-btn').prop('disabled', (parseFloat($('#cash-received').val()) || 0) < currentTotal); } else { $('#cash-details').slideUp(100); $('#finalize-sale-btn').prop('disabled', false); } }); | |
| appContainer.on('input', '#cash-received', function() { const received = parseFloat($(this).val()) || 0; const change = received - currentTotal; $('#cash-change-amount').text(`$${change > 0 ? change.toFixed(2) : '0.00'}`); $('#finalize-sale-btn').prop('disabled', received < currentTotal); }); | |
| appContainer.on('click', '#finalize-sale-btn', function() { const $button = $(this); $button.text('Procesando...').prop('disabled', true); $.ajax({ url: bakerypos_data.api_url + 'sale', method: 'POST', beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); }, contentType: 'application/json', data: JSON.stringify({ items: ticketItems, total: currentTotal, payment_method: selectedPaymentMethod }) }).done(() => { alert('¡Venta registrada!'); ticketItems = []; renderTicket(); $('#payment-modal-overlay').fadeOut(200); }).fail(jqXHR => { alert("Error: " + (jqXHR.responseJSON?.message || 'desconocido')); $button.text('Finalizar Venta').prop('disabled', false); }); }); | |
| // Modal de Gastos | |
| appContainer.on('click', '#register-expense-btn', function() { | |
| const expenseModalHTML = `<div id="expense-modal"><h2>Registrar Gasto</h2><div class="form-group"><label for="expense-amount">Monto ($):</label><input type="number" id="expense-amount" step="0.01" min="0.01" required></div><div class="form-group"><label for="expense-description">Descripción:</label><input type="text" id="expense-description" required maxlength="250"></div><div class="modal-actions"><button id="cancel-expense-btn" class="button">Cancelar</button><button id="finalize-expense-btn" class="button button-primary" disabled>Guardar</button></div></div>`; | |
| $('#expense-modal-overlay').html(expenseModalHTML).fadeIn(200); | |
| }); | |
| appContainer.on('click', '#cancel-expense-btn, #expense-modal-overlay', function(e) { if(e.target === this || e.target.id === 'cancel-expense-btn') $('#expense-modal-overlay').fadeOut(200); }); | |
| appContainer.on('input', '#expense-amount, #expense-description', function() { const amount = parseFloat($('#expense-amount').val()) || 0; const description = $('#expense-description').val().trim(); $('#finalize-expense-btn').prop('disabled', amount <= 0 || description === ''); }); | |
| appContainer.on('click', '#finalize-expense-btn', function() { const $button = $(this); $button.text('Guardando...').prop('disabled', true); $.ajax({ url: bakerypos_data.api_url + 'expense', method: 'POST', beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); }, contentType: 'application/json', data: JSON.stringify({ amount: $('#expense-amount').val(), description: $('#expense-description').val() }) }).done(() => { alert('¡Gasto registrado!'); $('#expense-modal-overlay').fadeOut(200); }).fail(jqXHR => { alert("Error: " + (jqXHR.responseJSON?.message || 'desconocido')); $button.text('Guardar').prop('disabled', false); }); }); | |
| // Modal de Merma (Scrap) | |
| appContainer.on('click', '#register-scrap-btn', function() { | |
| let productOptions = initialProducts.map(p => `<option value="${p.id}">${p.name}</option>`).join(''); | |
| const scrapModalHTML = `<div id="scrap-modal"><h2>Registrar Merma</h2><div class="form-group"><label for="scrap-product">Producto:</label><select id="scrap-product" required><option value="">-- Selecciona --</option>${productOptions}</select></div><div class="form-group"><label for="scrap-quantity">Cantidad:</label><input type="number" id="scrap-quantity" min="1" step="1" required></div><div class="form-group"><label for="scrap-reason">Motivo:</label><select id="scrap-reason" required><option value="">-- Selecciona --</option><option value="Dañado">Dañado</option><option value="Caducado">Caducado</option><option value="Otro">Otro</option></select></div><div class="modal-actions"><button id="cancel-scrap-btn" class="button">Cancelar</button><button id="finalize-scrap-btn" class="button button-primary" disabled>Registrar</button></div></div>`; | |
| $('#scrap-modal-overlay').html(scrapModalHTML).fadeIn(200); | |
| }); | |
| appContainer.on('click', '#cancel-scrap-btn, #scrap-modal-overlay', function(e) { if(e.target === this || e.target.id === 'cancel-scrap-btn') $('#scrap-modal-overlay').fadeOut(200); }); | |
| appContainer.on('change input', '#scrap-product, #scrap-quantity, #scrap-reason', function() { const product = $('#scrap-product').val(); const quantity = parseInt($('#scrap-quantity').val()) || 0; const reason = $('#scrap-reason').val(); $('#finalize-scrap-btn').prop('disabled', !product || quantity <= 0 || !reason); }); | |
| appContainer.on('click', '#finalize-scrap-btn', function() { const $button = $(this); $button.text('Registrando...').prop('disabled', true); $.ajax({ url: bakerypos_data.api_url + 'scrap', method: 'POST', beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); }, contentType: 'application/json', data: JSON.stringify({ product_id: $('#scrap-product').val(), quantity: $('#scrap-quantity').val(), reason: $('#scrap-reason').val() }) }).done(() => { alert('¡Merma registrada!'); $('#scrap-modal-overlay').fadeOut(200); }).fail(jqXHR => { alert("Error: " + (jqXHR.responseJSON?.message || 'desconocido')); $button.text('Registrar').prop('disabled', false); }); }); | |
| // --- INICIAR LA APLICACIÓN --- | |
| init(); | |
| }); | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function ($) { | |
| 'use strict'; | |
| $(function () { | |
| const salesContainer = $('#sales-report-table'); | |
| const expensesContainer = $('#expenses-report-table'); | |
| if (!salesContainer.length || !expensesContainer.length) { | |
| $('#reports-container').html('<p style="color:red;">Error de configuración del plugin: No se encontraron los contenedores de reportes.</p>'); | |
| return; | |
| } | |
| function loadSales() { | |
| $.ajax({ | |
| url: bakerypos_data.api_url + 'reports/sales', | |
| method: 'GET', | |
| beforeSend: function (xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }) | |
| .done(function (sales) { | |
| if (!sales || sales.length === 0) { | |
| salesContainer.html('<p>No hay datos de ventas disponibles.</p>'); | |
| return; | |
| } | |
| const tableRows = sales.map(sale => ` | |
| <tr> | |
| <td>${sale.session_id}</td> | |
| <td>${sale.user_name}</td> | |
| <td>${sale.sucursal}</td> | |
| <td>$${sale.total.toFixed(2)}</td> | |
| <td>${sale.time}</td> | |
| </tr> | |
| `).join(''); | |
| const tableHTML = ` | |
| <table class="wp-list-table widefat fixed striped"> | |
| <thead><tr><th>Sesión ID</th><th>Vendedor</th><th>Sucursal</th><th>Total</th><th>Fecha</th></tr></thead> | |
| <tbody>${tableRows}</tbody> | |
| </table>`; | |
| salesContainer.html(tableHTML); | |
| }) | |
| .fail(function () { | |
| salesContainer.html('<p style="color:red;">Error al cargar el reporte de ventas.</p>'); | |
| }); | |
| } | |
| function loadExpenses() { | |
| $.ajax({ | |
| url: bakerypos_data.api_url + 'reports/expenses', | |
| method: 'GET', | |
| beforeSend: function (xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_data.nonce); } | |
| }) | |
| .done(function (expenses) { | |
| if (!expenses || expenses.length === 0) { | |
| expensesContainer.html('<p>No hay datos de gastos disponibles.</p>'); | |
| return; | |
| } | |
| const tableRows = expenses.map(exp => ` | |
| <tr> | |
| <td>${exp.expense_id}</td> | |
| <td>${exp.user_name}</td> | |
| <td>${exp.sucursal}</td> | |
| <td>${exp.category}</td> | |
| <td>$${exp.amount.toFixed(2)}</td> | |
| <td>${exp.time}</td> | |
| </tr> | |
| `).join(''); | |
| const tableHTML = ` | |
| <table class="wp-list-table widefat fixed striped"> | |
| <thead><tr><th>ID</th><th>Vendedor</th><th>Sucursal</th><th>Categoría</th><th>Monto</th><th>Fecha</th></tr></thead> | |
| <tbody>${tableRows}</tbody> | |
| </table>`; | |
| expensesContainer.html(tableHTML); | |
| }) | |
| .fail(function () { | |
| expensesContainer.html('<p style="color:red;">Error al cargar el reporte de gastos.</p>'); | |
| }); | |
| } | |
| loadSales(); | |
| loadExpenses(); | |
| }); | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function ($) { | |
| 'use strict'; | |
| $(function () { | |
| const appContainer = $('#task-manager'); | |
| let taskList = []; | |
| function loadTasks() { | |
| appContainer.html('<p>Cargando tareas...</p>'); | |
| $.ajax({ | |
| url: bakerypos_tasks_data.api_url + 'tasks', | |
| method: 'GET', | |
| beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_tasks_data.nonce); } | |
| }).done(function(response) { | |
| if (response.success) { | |
| taskList = response.tasks; | |
| renderTaskManager(); | |
| } | |
| }).fail(function() { | |
| appContainer.html('<p>Error al cargar las tareas.</p>'); | |
| }); | |
| } | |
| function renderTaskManager() { | |
| const managerHTML = ` | |
| <div class="report-section"> | |
| <h2>Checklist Actual (Turno Matutino)</h2> | |
| <ul id="task-list-sortable"></ul> | |
| <form id="add-task-form"> | |
| <input type="text" id="new-task-description" placeholder="Añadir nueva tarea..." required> | |
| <button type="submit" class="button button-primary">Añadir Tarea</button> | |
| </form> | |
| </div> | |
| `; | |
| appContainer.html(managerHTML); | |
| renderTaskList(); | |
| } | |
| function renderTaskList() { | |
| const listContainer = $('#task-list-sortable'); | |
| listContainer.empty(); | |
| if (taskList.length > 0) { | |
| taskList.forEach(function(task) { | |
| const taskHTML = ` | |
| <li class="task-item" data-task-id="${task.task_id}"> | |
| <span class="dashicons dashicons-menu handle"></span> | |
| <span class="task-description">${task.task_description}</span> | |
| <button class="button button-small delete-task-btn">Eliminar</button> | |
| </li> | |
| `; | |
| listContainer.append(taskHTML); | |
| }); | |
| } else { | |
| listContainer.html('<li>No hay tareas en la lista.</li>'); | |
| } | |
| } | |
| function saveTasks() { | |
| $.ajax({ | |
| url: bakerypos_tasks_data.api_url + 'tasks', | |
| method: 'POST', | |
| beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', bakerypos_tasks_data.nonce); }, | |
| contentType: 'application/json', | |
| data: JSON.stringify({ tasks: taskList }) | |
| }).done(function() { | |
| // Opcional: mostrar un mensaje de "Guardado" | |
| }).fail(function() { | |
| alert('Error al guardar las tareas.'); | |
| }); | |
| } | |
| // --- Eventos --- | |
| appContainer.on('submit', '#add-task-form', function(e) { | |
| e.preventDefault(); | |
| const description = $('#new-task-description').val().trim(); | |
| if (description) { | |
| taskList.push({ task_id: 'new_' + Date.now(), task_description: description }); | |
| renderTaskList(); | |
| saveTasks(); | |
| $('#new-task-description').val(''); | |
| } | |
| }); | |
| appContainer.on('click', '.delete-task-btn', function() { | |
| if (confirm('¿Seguro que quieres eliminar esta tarea?')) { | |
| const taskId = $(this).closest('.task-item').data('task-id'); | |
| taskList = taskList.filter(task => task.task_id != taskId); | |
| renderTaskList(); | |
| saveTasks(); | |
| } | |
| }); | |
| $('#task-list-sortable').sortable({ | |
| handle: '.handle', | |
| update: function(event, ui) { | |
| let newOrder = []; | |
| $('#task-list-sortable .task-item').each(function() { | |
| const taskId = $(this).data('task-id'); | |
| const task = taskList.find(t => t.task_id == taskId); | |
| if (task) { | |
| newOrder.push(task); | |
| } | |
| }); | |
| taskList = newOrder; | |
| saveTasks(); | |
| } | |
| }).disableSelection(); | |
| loadTasks(); | |
| }); | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: BakeryPOS System | |
| * Description: Sistema de Punto de Venta para panaderías con multi-inventario. | |
| * Version: 3.1.0 | |
| * Author: Tu Nombre | |
| */ | |
| if ( ! defined( 'WPINC' ) ) die; | |
| define( 'BAKERYPOS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); | |
| define( 'BAKERYPOS_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); | |
| require_once BAKERYPOS_PLUGIN_DIR . 'api/class-bakerypos-api.php'; | |
| class BakeryPOS_System_Final { | |
| public function __construct() { | |
| add_action( 'admin_menu', array( $this, 'add_admin_pages' ) ); | |
| add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); | |
| add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) ); | |
| add_shortcode( 'bakery_pos', array( $this, 'render_pos_shortcode' ) ); | |
| add_action( 'rest_api_init', function () { (new BakeryPOS_API())->register_routes(); }); | |
| } | |
| public function add_admin_pages() { | |
| add_menu_page('BakeryPOS', 'BakeryPOS', 'manage_options', 'bakerypos-reports', array($this, 'display_reports_page'), 'dashicons-chart-pie', 25); | |
| add_submenu_page('bakerypos-reports', 'Reportes', 'Reportes', 'manage_options', 'bakerypos-reports', array($this, 'display_reports_page')); | |
| // --- NUEVOS SUBMENÚS --- | |
| add_submenu_page('bakerypos-reports', 'Sucursales', 'Sucursales', 'manage_options', 'bakerypos-branches', array($this, 'display_branches_page')); | |
| add_submenu_page('bakerypos-reports', 'Asignar Inventario', 'Asignar Inventario', 'manage_options', 'bakerypos-inventory', array($this, 'display_inventory_page')); | |
| add_submenu_page('bakerypos-reports', 'Gestor de Tareas', 'Gestor de Tareas', 'manage_options', 'bakerypos-tasks', array($this, 'display_tasks_page')); | |
| } | |
| public function display_reports_page() { echo '<div class="wrap" id="bakerypos-reports-app"><h1>Reportes</h1><div id="reports-container"><div class="report-section"><h2>Ventas</h2><div id="sales-report-table"><p>Cargando...</p></div></div><div class="report-section"><h2>Gastos</h2><div id="expenses-report-table"><p>Cargando...</p></div></div></div></div>'; } | |
| public function display_tasks_page() { echo '<div class="wrap" id="bakerypos-tasks-app"><h1>Gestor de Tareas</h1><div id="task-manager"><p>Cargando...</p></div></div>'; } | |
| public function display_branches_page() { echo '<div class="wrap" id="bakerypos-branches-app"><h1>Gestor de Sucursales</h1><div id="branches-manager">Cargando...</div></div>'; } | |
| public function display_inventory_page() { echo '<div class="wrap" id="bakerypos-inventory-app"><h1>Asignar Inventario por Sucursal</h1><div id="inventory-manager">Cargando...</div></div>'; } | |
| public function enqueue_admin_assets($hook) { | |
| $version = '3.1.0'; | |
| if ($hook === 'toplevel_page_bakerypos-reports' || $hook === 'bakerypos_page_bakerypos-reports') { | |
| wp_enqueue_style('bakerypos-admin-style', BAKERYPOS_PLUGIN_URL . 'assets/css/admin-style.css', array(), $version); | |
| wp_enqueue_script('bakerypos-reports-app', BAKERYPOS_PLUGIN_URL . 'assets/js/reports-app.js', array('jquery'), $version, true); | |
| wp_localize_script('bakerypos-reports-app', 'bakerypos_data', array('api_url' => rest_url('bakerypos/v1/'), 'nonce' => wp_create_nonce('wp_rest'))); | |
| } | |
| if ($hook === 'bakerypos_page_bakerypos-tasks') { | |
| wp_enqueue_style('bakerypos-admin-style', BAKERYPOS_PLUGIN_URL . 'assets/css/admin-style.css', array(), $version); | |
| wp_enqueue_script('bakerypos-tasks-app', BAKERYPOS_PLUGIN_URL . 'assets/js/tasks-app.js', array('jquery', 'jquery-ui-sortable'), $version, true); | |
| wp_localize_script('bakerypos-tasks-app', 'bakerypos_data', array('api_url' => rest_url('bakerypos/v1/'), 'nonce' => wp_create_nonce('wp_rest'))); | |
| } | |
| if ($hook === 'bakerypos_page_bakerypos-branches') { | |
| wp_enqueue_style('bakerypos-admin-style', BAKERYPOS_PLUGIN_URL . 'assets/css/admin-style.css', array(), $version); | |
| wp_enqueue_script('bakerypos-branches-app', BAKERYPOS_PLUGIN_URL . 'assets/js/branches-app.js', array('jquery'), $version, true); | |
| wp_localize_script('bakerypos-branches-app', 'bakerypos_data', array('api_url' => rest_url('bakerypos/v1/'), 'nonce' => wp_create_nonce('wp_rest'))); | |
| } | |
| if ($hook === 'bakerypos_page_bakerypos-inventory') { | |
| wp_enqueue_style('bakerypos-admin-style', BAKERYPOS_PLUGIN_URL . 'assets/css/admin-style.css', array(), $version); | |
| wp_enqueue_script('bakerypos-inventory-app', BAKERYPOS_PLUGIN_URL . 'assets/js/inventory-app.js', array('jquery'), $version, true); | |
| wp_localize_script('bakerypos-inventory-app', 'bakerypos_data', array('api_url' => rest_url('bakerypos/v1/'), 'nonce' => wp_create_nonce('wp_rest'))); | |
| } | |
| } | |
| public function enqueue_frontend_assets() { | |
| global $post; | |
| if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'bakery_pos')) { | |
| $script_handle = 'bakerypos-pos-app'; | |
| wp_enqueue_style('bakerypos-pos-style', BAKERYPOS_PLUGIN_URL . 'assets/css/pos-style.css', array(), '3.1.0'); | |
| wp_enqueue_script($script_handle, BAKERYPOS_PLUGIN_URL . 'assets/js/pos-app.js', array('jquery'), '3.1.0', true); | |
| wp_localize_script($script_handle, 'bakerypos_data', array('api_url' => rest_url('bakerypos/v1/'),'is_logged_in' => is_user_logged_in(),'nonce' => wp_create_nonce('wp_rest'))); | |
| } | |
| } | |
| public function render_pos_shortcode() { | |
| return '<div id="bakery-pos-app"><h1>Cargando...</h1></div>'; | |
| } | |
| } | |
| new BakeryPOS_System_Final(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment