From 24ae0e327fa55cf20b8210aa0cde65f7b95b921c Mon Sep 17 00:00:00 2001 From: Axel Axt Date: Tue, 4 Aug 2026 19:20:18 -0300 Subject: [PATCH] Carpeta controllers --- app/controllers/cajaController.php | 472 ++++++++++ app/controllers/chatBotController.php | 252 ++++++ app/controllers/clienteController.php | 332 +++++++ app/controllers/configuracionController.php | 495 ++++++++++ app/controllers/informeController.php | 81 ++ app/controllers/logInController.php | 646 ++++++++++++++ app/controllers/pageController.php | 324 +++++++ app/controllers/pedidoController.php | 554 ++++++++++++ app/controllers/presupuestoController.php | 729 +++++++++++++++ app/controllers/productoController.php | 541 +++++++++++ app/controllers/proveedorController.php | 300 +++++++ app/controllers/servicioController.php | 915 +++++++++++++++++++ app/controllers/usuarioController.php | 386 ++++++++ app/controllers/ventaController.php | 944 ++++++++++++++++++++ 14 files changed, 6971 insertions(+) create mode 100644 app/controllers/cajaController.php create mode 100644 app/controllers/chatBotController.php create mode 100644 app/controllers/clienteController.php create mode 100644 app/controllers/configuracionController.php create mode 100644 app/controllers/informeController.php create mode 100644 app/controllers/logInController.php create mode 100644 app/controllers/pageController.php create mode 100644 app/controllers/pedidoController.php create mode 100644 app/controllers/presupuestoController.php create mode 100644 app/controllers/productoController.php create mode 100644 app/controllers/proveedorController.php create mode 100644 app/controllers/servicioController.php create mode 100644 app/controllers/usuarioController.php create mode 100644 app/controllers/ventaController.php diff --git a/app/controllers/cajaController.php b/app/controllers/cajaController.php new file mode 100644 index 0000000..bdfbb1c --- /dev/null +++ b/app/controllers/cajaController.php @@ -0,0 +1,472 @@ + + + // AGREGAR PRODUCTO A LA CAJA + + case 'agregar_producto_caja': + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + header('Location: index.php?opt=crear_venta'); + exit; + } + + $ids = $_POST['ids'] ?? []; + $cantidades = $_POST['cantidades'] ?? []; + + if (empty($ids)) { + $_SESSION['flash_error'] = 'No se seleccionó ningún producto.'; + header('Location: index.php?opt=agregar_producto'); + exit; + } + + $agregados = 0; + $errores = []; + + // Evitar duplicados + + $ids = array_unique($ids); + + foreach ($ids as $idProducto) { + + $idProducto = (int) $idProducto; + $cantidad = isset($cantidades[$idProducto]) ? (int) $cantidades[$idProducto] : 1; + + if ($idProducto <= 0 || $cantidad <= 0) { + $errores[] = 'Datos inválidos.'; + continue; + } + + $producto = $productomodel->obtenerPorIdParaCaja($idProducto); + + if (!$producto) { + $errores[] = 'Producto inexistente.'; + continue; + } + + if ($cajamodel->agregarProducto($producto, $cantidad)) { + $agregados++; + } else { + $errores[] = $cajamodel->getError(); + } + } + + // Condiciones para mostrar mensajes + + if ($agregados === 1) { + $_SESSION['flash_success'] = "Se agregó un producto correctamente."; + } else if ($agregados > 1) { + $_SESSION['flash_success'] = "Se agregaron {$agregados} productos correctamente."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', array_unique($errores)); + header('Location: index.php?opt=agregar_producto'); + exit; + } + + header('Location: index.php?opt=crear_venta'); + exit; + + // ELIMINAR PRODUCTO DE CAJA + + case 'eliminar_producto': + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + + if ($idProducto <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + if (!$cajamodel->eliminarProducto($idProducto)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo eliminar el producto.'; + } else { + $_SESSION['flash_success'] = 'Producto eliminado correctamente.'; + } + + header('Location: index.php?opt=crear_venta'); + exit; + + // EDITAR CANTIDAD DE PRODUCTO + + case 'editar_cantidad': + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + $nuevaCantidad = (int) ($_POST['cantidad'] ?? 0); + + if ($idProducto <= 0 || $nuevaCantidad <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + if (!$cajamodel->editarCantidad($idProducto, $nuevaCantidad)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo actualizar la cantidad.'; + } else { + $_SESSION['flash_success'] = 'Cantidad editada correctamente.'; + } + + header('Location: index.php?opt=crear_venta'); + exit; + + // VACIAR CAJA DE PRODUCTOS + + case 'vaciar_caja_venta': + + if ($cajamodel->vaciarCajaVenta()) { + $_SESSION['flash_success'] = 'Carrito vaciado.'; + } else { + $_SESSION['flash_error'] = 'No se pudo vaciar el carrito.'; + } + + header('Location: index.php?opt=crear_venta'); + exit; + + // <---------------------------------- MANEJO DE CAJA PARA PEDIDO ----------------------------------> + + // AGREGAR PRODUCTO A CAJA DE PEDIDO + + case 'agregar_producto_pedido_caja': + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + header('Location: index.php?opt=crear_pedido'); + exit; + } + + $ids = $_POST['ids'] ?? []; + $costos = $_POST['costos'] ?? []; + $cantidades = $_POST['cantidades'] ?? []; + + if (empty($ids)) { + $_SESSION['flash_error'] = 'No se seleccionó ningún producto.'; + header('Location: index.php?opt=agregar_producto_pedido'); + exit; + } + + $errores = []; + $agregados = 0; + + // Evitar duplicados + + $ids = array_unique($ids); + + foreach ($ids as $idProducto) { + + $idProducto = (int) $idProducto; + $costo = isset($costos[$idProducto]) ? (float) $costos[$idProducto] : 0; + $cantidad = isset($cantidades[$idProducto]) ? (int) $cantidades[$idProducto] : 1; + + if ($idProducto <= 0 || $cantidad <= 0 || $costo <= 0) { + $errores[] = 'Datos inválidos.'; + continue; + } + + $producto = $productomodel->obtenerPorIdParaCajaPedido($idProducto); + + if (!$producto) { + $errores[] = 'Producto inexistente.'; + continue; + } + + if ($cajamodel->agregarProductoPedido($producto, $costo, $cantidad)) { + $agregados++; + } else { + $errores[] = $cajamodel->getError(); + } + } + + // Condiciones para manejo de mensajes + + if ($agregados === 1) { + $_SESSION['flash_success'] = "Se agregó un producto correctamente."; + } else if ($agregados > 1) { + $_SESSION['flash_success'] = "Se agregaron {$agregados} productos correctamente."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', array_unique($errores)); + header('Location: index.php?opt=agregar_producto_pedido'); + exit; + } + + header('Location: index.php?opt=crear_pedido'); + exit; + + // VACIAR CAJA DE PRODUCTOS PARA PEDIDO + + case 'vaciar_caja_pedido': + + // Guardo datos en $_SESSION para mantener persistencia + + $_SESSION['pedido']['cond_iva'] = $_POST['cond_iva'] ?? ''; + $_SESSION['pedido']['cuit'] = $_POST['cuit'] ?? ''; + + if ($cajamodel->vaciarCajaPedido()) { + $_SESSION['flash_success'] = 'Carrito vaciado.'; + } else { + $_SESSION['flash_error'] = 'No se pudo vaciar el carrito.'; + } + + header('Location: index.php?opt=crear_pedido'); + exit; + + // ELIMINAR PRODUCTO DE PEDIDO + + case 'eliminar_producto_pedido': + + // Guardo datos en $_SESSION para mantener persistencia + + $_SESSION['pedido']['cond_iva'] = $_POST['cond_iva'] ?? ''; + $_SESSION['pedido']['cuit'] = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + + if ($idProducto <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$cajamodel->eliminarProductoPedido($idProducto)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo eliminar el producto.'; + } + + header('Location: index.php?opt=crear_pedido'); + exit; + + // EDITAR DATOS DE PRODUCTOS PARA PEDIDO + + case 'editar_datos_producto': + + // Guardo datos para mantener consistencia + + $_SESSION['pedido']['cond_iva'] = $_POST['cond_iva'] ?? ''; + $_SESSION['pedido']['cuit'] = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + // Saneamiento de datos + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + $nuevaCantidad = $_POST['cantidad'][$idProducto] ?? null; + $nuevoCosto = $_POST['costo'][$idProducto] ?? null; + + if ($idProducto <= 0 || $nuevoCosto <= 0 || $nuevaCantidad <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$cajamodel->editarDatosProductoPedido($idProducto, $nuevoCosto, $nuevaCantidad)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudieron actualizar los datos.'; + } else { + $_SESSION['flash_success'] = 'Datos actualizados.'; + } + + header('Location: index.php?opt=crear_pedido'); + exit; + + // <---------------------------------- MANEJO DE CAJA PARA SERVICIO ----------------------------------> + + // AGREGAR INSUMOS PARA SERVICIO EN LA CAJA + + case 'agregar_insumo_caja': + + $idServicio = (int) ($_GET['id'] ?? 0); + + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + header('Location: index.php?opt=actualizar_servicio'); + exit; + } + + $ids = $_POST['ids'] ?? []; + $cantidades = $_POST['cantidades'] ?? []; + + if (empty($ids)) { + $_SESSION['flash_error'] = 'No se seleccionó ningún insumo.'; + header('Location: index.php?opt=agregar_insumo'); + exit; + } + + $agregados = 0; + $errores = []; + + // Evitar duplicados + + $ids = array_unique($ids); + + foreach ($ids as $idInsumo) { + + $idInsumo = (int) $idInsumo; + $cantidad = isset($cantidades[$idInsumo]) ? (int) $cantidades[$idInsumo] : 1; + + if ($idInsumo <= 0 || $cantidad <= 0) { + $errores[] = 'Datos inválidos.'; + continue; + } + + $insumo = $productomodel->obtenerPorIdParaCajaServicio($idInsumo); + + if (!$insumo) { + $errores[] = 'Producto inexistente.'; + continue; + } + + if ($cajamodel->agregarInsumo($insumo, $cantidad)) { + $agregados++; + } else { + $errores[] = $cajamodel->getError(); + } + } + + // Condiciones para mensajes + + if ($agregados === 1) { + $_SESSION['flash_success'] = "Se agregó un insumo correctamente."; + } else if ($agregados > 1) { + $_SESSION['flash_success'] = "Se agregaron {$agregados} insumos correctamente."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', array_unique($errores)); + header('Location: index.php?opt=agregar_insumo'); + exit; + } + + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + + // VACIAR CAJA DE INSUMOS PARA SERVICIO + + case 'vaciar_caja_servicio': + + $idServicio = (int) ($_POST['id'] ?? 0); + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $_SESSION['servicio']['detalle_realizado'] = $_POST['detalle_realizado'] ?? ''; + $_SESSION['servicio']['mano_obra'] = (float) $_POST['mano_obra'] ?? 0; + } + + $manoObra = $_SESSION['servicio']['mano_obra'] ?? 0; + + if ($manoObra < 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + if ($cajamodel->vaciarCajaServicio()) { + $_SESSION['flash_success'] = 'Carrito vaciado.'; + } else { + $_SESSION['flash_error'] = 'No se pudo vaciar el carrito.'; + } + + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + + // ELIMINAR INSUMO DE SERVICIO + + case 'eliminar_insumo': + + $idServicio = (int) ($_GET['id'] ?? 0); + $idInsumo = (int) ($_POST['id_producto'] ?? 0); + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $_SESSION['servicio']['detalle_realizado'] = $_POST['detalle_realizado'] ?? ''; + $_SESSION['servicio']['mano_obra'] = (float) $_POST['mano_obra'] ?? 0; + } + + $manoObra = $_SESSION['servicio']['mano_obra'] ?? 0; + + // Validaciones + + if ($idInsumo <= 0 || $manoObra < 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + if (!$cajamodel->eliminarInsumoServicio($idInsumo)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo eliminar el producto.'; + } else { + $_SESSION['flash_success'] = 'Insumo eliminado correctamente.'; + } + + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + + // EDITAR CANTIDAD DE INSUMO DE SERVICIO + + case 'editar_cantidad_insumo': + + $idServicio = (int) ($_GET['id'] ?? 0); + $idInsumo = (int) ($_POST['id_producto'] ?? 0); + $nuevaCantidad = $_POST['cantidad'][$idInsumo] ?? null; + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $_SESSION['servicio']['detalle_realizado'] = $_POST['detalle_realizado'] ?? ''; + $_SESSION['servicio']['mano_obra'] = (float) $_POST['mano_obra'] ?? 0; + } + + $manoObra = $_SESSION['servicio']['mano_obra'] ?? 0; + + // Validaciones + + if ($idInsumo <= 0 || $nuevaCantidad <= 0 || $manoObra < 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + if (!$cajamodel->editarCantidadInsumo($idInsumo, $nuevaCantidad)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo actualizar la cantidad.'; + } else { + $_SESSION['flash_success'] = 'Cantidad editada correctamente.'; + } + + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + + // DEFAULT (404) + + default: + + ob_start(); + header("Location: index.php?opt=404View.php"); + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/chatBotController.php b/app/controllers/chatBotController.php new file mode 100644 index 0000000..d909d5f --- /dev/null +++ b/app/controllers/chatBotController.php @@ -0,0 +1,252 @@ + false + ]); + + exit; + } + + // Obtener mensaje enviado desde JS + // trim() elimina espacios innecesarios + + $mensaje = trim($_POST['mensaje'] ?? ''); + + // Validar mensaje vacío + + if (empty($mensaje)) { + + echo json_encode([ + 'success' => false + ]); + + exit; + } + + // Limitar longitud máxima del mensaje para evitar abuso o consumo excesivo de tokens + + if (mb_strlen($mensaje) > 500) { + + echo json_encode([ + 'success' => false, + 'error' => 'La consulta es demasiado larga.' + ]); + + exit; + } + + // Inicializar historial del chat en sesión si aún no existe + + if (!isset($_SESSION['chat_historial'])) { + $_SESSION['chat_historial'] = []; + } + + // Detectar automáticamente el módulo relacionado con la consulta + + $modulo = detectarModulo($mensaje); + + // Cache estática para evitar leer archivos en cada consulta + + static $docsCache = []; + + // Cargar documentación general solamente una vez + + if (!isset($docsCache['general'])) { + + $docsCache['general'] = file_get_contents( + __DIR__ . '/../docs/general.txt' + ); + } + + // Cargar documentación específica del módulo detectado + + if (!isset($docsCache[$modulo])) { + + $rutaModulo = __DIR__ . "/../docs/{$modulo}.txt"; + + // Verificar si existe el archivo + + if (file_exists($rutaModulo)) { + + $docsCache[$modulo] = file_get_contents($rutaModulo); + + } else { + + // Si no existe, guardar vacío + + $docsCache[$modulo] = ''; + } + } + + // Unir documentación general + documentación específica del módulo + + $contexto = $docsCache['general'] . "\n\n" . $docsCache[$modulo]; + + // Prompt del sistema + // Define comportamiento y restricciones de la IA + + $systemPrompt = " + Sos el asistente interno del sistema ByteCenter. + + Tu función es ayudar a operadores y administradores + utilizando únicamente la documentación proporcionada. + + REGLAS IMPORTANTES: + + - Respondé únicamente usando información presente en la documentación. + + - No inventes funcionalidades, botones, módulos, + pantallas, acciones, permisos ni procesos. + + - No asumas comportamientos típicos de otros sistemas. + + - Si el usuario consulta sobre una funcionalidad del sistema que no aparece documentada, + indicá brevemente que no encontraste información específica sobre esa funcionalidad. + + - Nunca completes información faltante usando suposiciones. + + - Cuando exista un procedimiento documentado, + explicalo paso a paso. + + - Utilizá exactamente los nombres de botones, + módulos y acciones documentadas. + + - La documentación puede contener símbolos como: + #, ##, ###, *, ** + + - Esos símbolos son únicamente separadores internos + de títulos y subtítulos. + + - Nunca escribas esos símbolos en la respuesta. + + - Respondé siempre en texto plano. + + - No uses markdown. + + - No uses encabezados markdown. + + - No uses listas markdown. + + - Para enumerar pasos, utilizá solamente: + 1. + 2. + 3. + + - Mantené respuestas claras, naturales y breves. + + EJEMPLOS DE RESPUESTAS INCORRECTAS: + - 'Exportar clientes' + - 'Venta rápida' + - 'Dashboard' + - 'Panel avanzado' + - 'Modo express' + - 'CSV' + - 'Métricas automáticas' + + DOCUMENTACIÓN: + $contexto + "; + + // Recuperar últimos mensajes para mantener contexto conversacional + + $historial = array_slice( + $_SESSION['chat_historial'], + -10 + ); + + // Array final enviado a la IA + + $messages = []; + + // Primer mensaje: prompt del sistema + + $messages[] = [ + 'role' => 'system', + 'content' => $systemPrompt + ]; + + // Agregar historial previo + + foreach ($historial as $msg) { + $messages[] = $msg; + } + + // Agregar mensaje actual del usuario + + $messages[] = [ + 'role' => 'user', + 'content' => $mensaje + ]; + + // Consultar API + + $resultado = consultarChatbot($messages); + + // Validar errores + + if (!$resultado['success']) { + + echo json_encode([ + 'success' => false, + 'error' => $resultado['error'] + ]); + + exit; + } + + // Obtener contenido de respuesta + + $respuesta = $resultado['content']; + + // Guardar mensaje usuario + + $_SESSION['chat_historial'][] = [ + 'role' => 'user', + 'content' => $mensaje + ]; + + // Guardar respuesta IA + + $_SESSION['chat_historial'][] = [ + 'role' => 'assistant', + 'content' => $respuesta + ]; + + // Limitar tamaño del historial para evitar exceso de tokens + + $_SESSION['chat_historial'] = array_slice( + $_SESSION['chat_historial'], + -20 + ); + + // Respuesta final al frontend (JSON) + + echo json_encode([ + 'success' => true, + 'respuesta' => $respuesta + ]); + + exit; + } +} \ No newline at end of file diff --git a/app/controllers/clienteController.php b/app/controllers/clienteController.php new file mode 100644 index 0000000..334bbe0 --- /dev/null +++ b/app/controllers/clienteController.php @@ -0,0 +1,332 @@ +listarClientes(); + ob_start(); + require __DIR__ . '/../views/clientes/listaClientesView.php'; + return ob_get_clean(); + + // CREAR CLIENTE NUEVO + + case 'crear_cliente': + + // Se requiere la view del form + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $_SESSION['return_opt'] = $_GET['from'] ?? 'clientes'; + + ob_start(); + require __DIR__ . '/../views/clientes/crearClienteView.php'; + return ob_get_clean(); + } + + // Datos recibidos del POST del formulario + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $nombre = trim($_POST['nombre'] ?? ''); + $apellido = trim($_POST['apellido'] ?? ''); + $telefono = trim($_POST['telefono'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $condIVA = trim($_POST['cond_iva'] ?? 'consumidor_final'); + + $cuit1 = trim($_POST['cuit_1'] ?? ''); + $cuit2 = trim($_POST['cuit_2'] ?? ''); + $cuit3 = trim($_POST['cuit_3'] ?? ''); + + $cuit = $cuit1 . $cuit2 . $cuit3; + + $errores = []; + + // Validaciones + + if ($nombre === '') + $errores[] = "El nombre no puede estar vacío."; + else if (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $nombre)) { + $errores[] = "El nombre debe comenzar con mayúscula y solo contener letras y espacios."; + } else if (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } else if (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($apellido === '') + $errores[] = "El apellido no puede estar vacío."; + else if (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $apellido)) { + $errores[] = "El apellido debe comenzar con mayúscula y solo contener letras y espacios."; + } else if (strlen($apellido) < 2) { + $errores[] = "El apellido debe tener al menos 2 caracteres."; + } else if (strlen($apellido) > 50) { + $errores[] = "El apellido no puede superar los 50 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } else if (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + $condicionesPermitidas = [ + 'consumidor_final', + 'monotributo', + 'responsable_inscripto' + ]; + + if (!in_array($condIVA, $condicionesPermitidas, true)) { + + $errores[] = 'Condición de IVA inválida.'; + } + + if ($cuit !== '') { + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $errores[] = 'El CUIT debe contener 11 dígitos.'; + } + } else { + + $cuit = null; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Se guarda en $_SESSION el error + header("Location: index.php?opt=crear_cliente"); + exit; + } + + // Verificación de existencia de cliente + + if ($clientemodel->verificarExistencia([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'cuit' => $cuit + ])) { + $_SESSION['flash_error'] = "El cliente ya existe o el email ya está registrado."; + + header("Location: index.php?opt=crear_cliente"); + exit; + } + + // Se llama a la función para crear el cliente + + if ($clientemodel->crearCliente([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'telefono' => $telefono, + 'email' => $email, + 'cuit' => $cuit, + 'cond_iva' => $condIVA + ])) { + $_SESSION['flash_success'] = 'Cliente creado correctamente.'; + } else { + $_SESSION['flash_error'] = 'Ocurrió un error al crear el cliente.'; + } + + $opt = $_SESSION['return_opt']; + + header("Location: index.php?opt=$opt"); + exit; + } + + // EDITAR DATOS DE CLIENTE + + case 'editar_cliente': + + $idEditar = (int) ($_GET['id'] ?? 0); + + if ($idEditar <= 0) { + header("Location: index.php?opt=clientes"); + exit; + } + + // Busca el cliente en la base de datos mediante el id + + $cliente = $clientemodel->obtenerPorId($idEditar); + + if (!$cliente) { + $_SESSION['flash_error'] = 'No se encontró el cliente.'; + header("Location: index.php?opt=clientes"); + exit; + } + + // Mostrar datos de cliente + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + ob_start(); + require __DIR__ . '/../views/clientes/editarClienteView.php'; + return ob_get_clean(); + } + + // En caso de modificar datos del cliente + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $nombre = trim($_POST['nombre'] ?? ''); + $apellido = trim($_POST['apellido'] ?? ''); + $telefono = trim($_POST['telefono'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $condIVA = trim($_POST['cond_iva'] ?? 'consumidor_final'); + + $cuit1 = trim($_POST['cuit_1'] ?? ''); + $cuit2 = trim($_POST['cuit_2'] ?? ''); + $cuit3 = trim($_POST['cuit_3'] ?? ''); + + $cuit = $cuit1 . $cuit2 . $cuit3; + + $errores = []; + + // Validaciones + + if ($nombre === '') { + $errores[] = "El nombre no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $nombre)) { + $errores[] = "El nombre debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } elseif (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($apellido === '') { + $errores[] = "El apellido no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $apellido)) { + $errores[] = "El apellido debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($apellido) < 2) { + $errores[] = "El apellido debe tener al menos 2 caracteres."; + } elseif (strlen($apellido) > 50) { + $errores[] = "El apellido no puede superar los 50 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } elseif (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + $condicionesPermitidas = [ + 'consumidor_final', + 'monotributo', + 'responsable_inscripto' + ]; + + if (!in_array($condIVA, $condicionesPermitidas, true)) { + + $errores[] = 'Condición de IVA inválida.'; + } + + if (($condIVA !== 'consumidor_final') && ($cuit === '')) { + + $errores[] = 'Debe ingresar el CUIT'; + } + + if ($cuit !== '') { + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $errores[] = 'El CUIT debe contener 11 dígitos.'; + } + } else { + + $cuit = null; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Se guarda en $_SESSION el error + header("Location: index.php?opt=editar_cliente&id=$idEditar"); + exit; + } + + // Verificación de existencia de cliente + + if ($clientemodel->verificarExistencia([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'cuit' => $cuit + ], $idEditar)) { + $_SESSION['flash_error'] = "El cliente ya existe o el email ya está registrado."; + + header("Location: index.php?opt=editar_cliente&id=$idEditar"); + exit; + } + + $guardarData = [ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'telefono' => $telefono, + 'email' => $email, + 'cuit' => $cuit, + 'cond_iva' => $condIVA + ]; + + // Actualizar los datos del cliente + + if ($clientemodel->actualizarCliente($cliente['id'], $guardarData)) { + $_SESSION['flash_success'] = 'Datos actualizados correctamente'; // Se guarda en $_SESSION el succes + } else { + $_SESSION['flash_error'] = 'No se pudo actualizar los datos.'; + } + + header("Location: index.php?opt=clientes"); + exit; + } + + // DEFAULT (404) + + default: + ob_start(); + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/configuracionController.php b/app/controllers/configuracionController.php new file mode 100644 index 0000000..8c11588 --- /dev/null +++ b/app/controllers/configuracionController.php @@ -0,0 +1,495 @@ +obtenerConfiguracion(); + + // Desencriptación de contraseña emisor + + if (isset($configuracion['contrasenia_emisor'])) { + $configuracion['contrasenia_emisor_formateada'] = decryptData($configuracion['contrasenia_emisor']); + } else { + $configuracion['contrasenia_emisor_formateada'] = 'Sin Setear'; + } + + // Saneamiento de email emisor + + if (isset($configuracion['email_emisor'])) { + $configuracion['email_emisor'] = $configuracion['email_emisor']; + } else { + $configuracion['email_emisor'] = 'Sin Setear'; + } + + // Validaciones + + if (empty($configuracion)) { + $_SESSION['flash_error'] = 'Ocurrió un error al cargar la configuración.'; + header("Location: index.php?opt=configuracion"); + exit; + } + + // Saneamiento de fecha + + if (!empty($configuracion['inicio_act'])) { + $dt = new DateTime($configuracion['inicio_act']); + $configuracion['inicio_act_formateada'] = $dt->format('d/m/Y'); + } else { + $configuracion['inicio_act_formateada'] = '-'; + } + + ob_start(); + require_once __DIR__ . '/../views/configuracion/configuracionView.php'; + return ob_get_clean(); + } + + // ACTUALIZAR CONFIGURACIÓN + + case 'actualizar_config': + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $configuracion = [ + + 'nombre' => trim($_POST['nombre'] ?? ''), + 'direccion' => trim($_POST['direccion'] ?? ''), + 'ciudad' => trim($_POST['ciudad'] ?? ''), + 'codigo_postal' => trim($_POST['codigo_postal'] ?? ''), + 'provincia' => trim($_POST['provincia'] ?? ''), + 'telefono' => trim($_POST['telefono'] ?? ''), + 'email' => trim($_POST['email'] ?? ''), + 'cond_iva' => trim($_POST['cond_iva'] ?? 'responsable_inscripto'), + 'cuit' => ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''), + 'iva' => $_POST['iva'] ?? 21, + 'hora_apertura_maniana' => trim($_POST['hora_apertura_maniana'] ?? ''), + 'hora_cierre_maniana' => trim($_POST['hora_cierre_maniana'] ?? ''), + 'hora_apertura_tarde' => trim($_POST['hora_apertura_tarde'] ?? ''), + 'hora_cierre_tarde' => trim($_POST['hora_cierre_tarde'] ?? ''), + 'mail_automatico' => isset($_POST['mail_automatico']) ? 1 : 0, + 'cargo_cancelacion_servicio' => $_POST['cargo_cancelacion_servicio'] ?? 0, + 'email_emisor' => trim($_POST['email_emisor'] ?? ''), + 'contrasenia_emisor' => trim($_POST['contrasenia_emisor'] ?? ''), + ]; + + $errores = []; + + // Validaciones + + // NOMBRE + + if ($configuracion['nombre'] === '') { + $errores[] = "El nombre no puede estar vacío."; + } else if (!preg_match('/^[A-Za-zÁÉÍÓÚáéíóú0-9\s\.\-]{2,100}$/', $configuracion['nombre'])) { + $errores[] = "El nombre contiene caracteres inválidos."; + } else if (strlen($configuracion['nombre']) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } else if (strlen($configuracion['nombre']) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + // DIRECCIÓN + + if ($configuracion['direccion'] === '') { + $errores[] = "La dirección no puede estar vacía."; + } else if (strlen($configuracion['direccion']) < 3) { + $errores[] = "La dirección debe tener al menos 3 caracteres."; + } else if (strlen($configuracion['direccion']) > 100) { + $errores[] = "La dirección no puede superar los 100 caracteres."; + } + + // CIUDAD + + if ($configuracion['ciudad'] === '') { + $errores[] = "La ciudad no puede estar vacía."; + } else if (!preg_match('/^[A-Za-zÁÉÍÓÚáéíóúñÑ\s]{2,100}$/', $configuracion['ciudad'])) { + $errores[] = "La ciudad solo puede contener letras y espacios."; + } + + // CODIGO POSTAL + + if ($configuracion['codigo_postal'] === '') { + $errores[] = "El código postal no puede estar vacío."; + } else if (!preg_match('/^[0-9]{4}$/', $configuracion['codigo_postal'])) { + $errores[] = "El código postal debe tener 4 dígitos."; + } + + // PROVINCIA + + if ($configuracion['provincia'] === '') { + $errores[] = "La provincia no puede estar vacía."; + } else if (!preg_match('/^[A-Za-zÁÉÍÓÚáéíóúñÑ\s]{2,100}$/', $configuracion['provincia'])) { + $errores[] = "La provincia contiene caracteres inválidos."; + } + + // TELÉFONO + + if ($configuracion['telefono'] === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $configuracion['telefono'])) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + // EMAIL + + if ($configuracion['email'] === '') { + $errores[] = "El email no puede estar vacío."; + } else if (!filter_var($configuracion['email'], FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } else if (strlen($configuracion['email']) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + // CONDICIÓN_IVA + + $condicionesValidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + if (!in_array($configuracion['cond_iva'], $condicionesValidas)) { + $errores[] = "Condición de IVA inválida."; + } + + // CUIT + + if (!preg_match('/^\d{10,11}$/', $configuracion['cuit'])) { + $errores[] = 'CUIT inválido. Debe contener solo números.'; + } + + // IVA + + if (!is_numeric($configuracion['iva'])) { + $errores[] = "El IVA debe ser numérico."; + } + + // HORA DE APERTURA Y CIERRE + + if ($configuracion['hora_apertura_maniana'] && $configuracion['hora_cierre_maniana']) { + + if ($configuracion['hora_apertura_maniana'] >= $configuracion['hora_cierre_maniana']) { + $errores[] = "El horario de apertura debe ser menor al de cierre."; + } + + } + + if ($configuracion['hora_apertura_tarde'] && $configuracion['hora_cierre_tarde']) { + + if ($configuracion['hora_apertura_tarde'] >= $configuracion['hora_cierre_tarde']) { + $errores[] = "El horario de apertura debe ser menor al de cierre."; + } + + } + + // CARGO POR CANCELACIÓN DE SERVICIO + + if (!is_numeric($configuracion['cargo_cancelacion_servicio'])) { + $errores[] = "El cargo por cancelación debe ser numérico."; + } + + // EMAIL EMISOR + + if ($configuracion['email_emisor'] === '') { + $errores[] = "El email no puede estar vacío."; + } else if (!filter_var($configuracion['email_emisor'], FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } else if (strlen($configuracion['email_emisor']) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + // CONTRASEÑA EMISOR + + if (strlen($configuracion['contrasenia_emisor']) > 100) { + $errores[] = "Contraseña demasiado larga."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Se guarda en $_SESSION el error + header("Location: index.php?opt=configuracion"); + exit; + } + + // ENCRIPTACION DE CONTRASEÑA + + if ($configuracion['contrasenia_emisor'] !== '') { + $passLimpia = str_replace(' ', '', $configuracion['contrasenia_emisor']); + $configuracion['contrasenia_emisor'] = encryptData($passLimpia); + } + + // Actualizamos datos de configuración + + if ($configuracionmodel->actualizarConfiguracion($configuracion)) { + $_SESSION['flash_success'] = 'Configuración actualizada correctamente.'; + header("Location: index.php?opt=configuracion"); + exit; + } else { + $_SESSION['flash_error'] = 'Error al actualizar la configuración.'; + header("Location: index.php?opt=configuracion"); + exit; + } + } + + // GENERAR BACKUP + + case 'generar_backup': + + // Obtenemos desde donde viene la petición (puede ser backup automático) + + $return = $_GET['from'] ?? 'backups'; + + $backupDir = $_ENV['BACKUP_PATH'] ?? (__DIR__ . '/../backups/'); + + $fecha = date('Y-m-d_H-i-s'); + $archivo = "backup_$fecha.sql"; // Generación de nombre de archivo con extensión + $rutaBackup = $backupDir . $archivo; // Generación de ruta completa de archivo backup + + // Datos de conexión + + $rutaBackup = escapeshellarg($rutaBackup); // Permite omitir espacios o caracteres no deseados (puede romper ruta o comando) + $host = escapeshellarg($_ENV['DB_HOST']); + $usuario = escapeshellarg($_ENV['DB_USER']); + $database = escapeshellarg($_ENV['DB_NAME']); + $password = escapeshellarg($_ENV['DB_PASSWORD']); + + // Comando pg_dump + + $comando = "PGPASSWORD=$password pg_dump --clean --if-exists --no-owner -h $host -U $usuario -d $database > $rutaBackup"; // Guardamos el comando a ejecutar mediante pg_dump + + exec($comando, $output, $resultado); // Lo ejecutamos + + if ($resultado === 0) { // Entra al if solo si el (exec) dio como resultado 0 (éxito) en la variable $resultado + + // Limitar cantidad de backups + + $maxBackups = 4; + + $archivos = glob($backupDir . '*.sql'); // Permite obtener todos los archivos ubicados en esa ruta + + if (count($archivos) > $maxBackups) { // Contamos la cantidad de archivos (comparando con la cantidad máxima definida) + + usort($archivos, function ($a, $b) { + return filemtime($a) - filemtime($b); + }); // Se ordenan en base a la fecha de modificación de mas antiguo a mas nuevo + + while (count($archivos) > $maxBackups) { + $archivoEliminar = array_shift($archivos); // Se elimina el archivo del array + unlink($archivoEliminar); // Se elimina fisicamente el archivo + } + } + + if (isset($_GET['auto'])) { + + $_SESSION['flash_success'] = "Copia de seguridad automática generada con éxito."; + } else { + + $_SESSION['flash_success'] = "Backup generado correctamente."; + } + + } else { + + $_SESSION['flash_error'] = "Error al generar el backup."; + } + + header("Location: index.php?opt=$return"); + exit; + + // LISTAR BACKUPS + + case 'backups': + + $backupDir = $_ENV['BACKUP_PATH'] ?? (__DIR__ . '/../backups/'); + + $archivos = glob($backupDir . '*.sql'); + + $backups = []; + $totalSize = 0; + + $ultimoBackup = null; + + if ($archivos) { + + usort($archivos, function ($a, $b) { + return filemtime($b) <=> filemtime($a); + }); + + $ultimoBackup = date('d/m/Y H:i', filemtime($archivos[0])); // Guardamos el archivo mas reciente para informar en la vista + + foreach ($archivos as $archivo) { + + $size = filesize($archivo); // Obtenemos el tamaño total del archivo + + $backups[] = [ + 'nombre' => basename($archivo), // Obtenemos nombre de archivo + 'fecha' => date('d/m/Y H:i', filemtime($archivo)), // Obtenemos la fecha del archivo + 'size' => round($size / 1024, 2) . ' KB' // Obtenemos el tamaño del archivo en KB + ]; + + $totalSize += $size; // Acumulamos el tamaño de los archivos para obtener el total + } + } + + $totalSize = round($totalSize / 1024 / 1024, 2); // Total utilizado en MB + + ob_start(); + require __DIR__ . '/../views/configuracion/buckupsView.php'; + return ob_get_clean(); + + // DESCARGAR BACKUP + + case 'descargar_backup': + + $file = basename($_GET['file']); + + $backupDir = $_ENV['BACKUP_PATH'] ?? (__DIR__ . '/../backups/'); + $ruta = $backupDir . $file; + + if (!file_exists($ruta)) { + header("Location: index.php?opt=backups"); + exit; + } + + header('Content-Type: application/sql'); // Indica el tipo de contenido del archivo (SQL en este caso) + header('Content-Disposition: attachment; filename="' . $file . '"'); // Fuerza la descarga del archivo en lugar de mostrarlo en el navegador y define el nombre con el que se va a guardar + header('Content-Length: ' . filesize($ruta)); // Informa el tamaño del archivo (mejora la descarga/progreso) + + readfile($ruta); // Envía el contenido del archivo al navegador + exit; + + // ELIMINAR BACKUP + + case 'eliminar_backup': + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $backupDir = $_ENV['BACKUP_PATH'] ?? (__DIR__ . '/../backups/'); + + $archivo = basename($_POST['file']); // Evita rutas maliciosas + + $rutaArchivo = $backupDir . $archivo; + + if (file_exists($rutaArchivo)) { + + unlink($rutaArchivo); + + $_SESSION['flash_success'] = "Backup eliminado correctamente."; + + } else { + + $_SESSION['flash_error'] = "El archivo no existe."; + + } + + header("Location: index.php?opt=backups"); + exit; + } + + // RESTAURAR BACKUP + + case 'restaurar_backup': + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $backupDir = $_ENV['BACKUP_PATH'] ?? (__DIR__ . '/../backups/'); + + $archivo = basename($_POST['file']); + $rutaBackup = $backupDir . $archivo; + + if (!file_exists($rutaBackup)) { + + $_SESSION['flash_error'] = "El archivo de backup no existe."; + header("Location: index.php?opt=backups"); + exit; + + } + + $host = escapeshellarg($_ENV['DB_HOST']); + $usuario = escapeshellarg($_ENV['DB_USER']); + $database = escapeshellarg($_ENV['DB_NAME']); + $password = escapeshellarg($_ENV['DB_PASSWORD']); + + // 1. BACKUP AUTOMÁTICO PREVIO + + $backupSeguridad = $backupDir . 'auto_pre_restore_' . date('Y-m-d_H-i-s') . '.sql'; + $backupSeguridadEscaped = escapeshellarg($backupSeguridad); + + $comandoBackup = "PGPASSWORD=$password pg_dump --clean --if-exists --no-owner -h $host -U $usuario -d $database > $backupSeguridadEscaped"; + + exec($comandoBackup, $outBackup, $resultadoBackup); + + if ($resultadoBackup !== 0) { + + $_SESSION['flash_error'] = "No se pudo crear el backup de seguridad antes de restaurar."; + header("Location: index.php?opt=backups"); + exit; + + } + + // 2. RESTAURAR COPIA DE SEGURIDAD SELECCIONADA + + $rutaBackupEscaped = escapeshellarg($rutaBackup); + + $comandoRestore = "PGPASSWORD=$password psql -h $host -U $usuario -d $database < $rutaBackupEscaped"; + + exec($comandoRestore, $output, $resultadoRestore); + + if ($resultadoRestore === 0) { + + $_SESSION['flash_success'] = "Backup restaurado correctamente. Se creó una copia de seguridad previa."; + + } else { + + $_SESSION['flash_error'] = "Error al restaurar el backup."; + + } + + header("Location: index.php?opt=backups"); + exit; + } +} \ No newline at end of file diff --git a/app/controllers/informeController.php b/app/controllers/informeController.php new file mode 100644 index 0000000..ebe07ed --- /dev/null +++ b/app/controllers/informeController.php @@ -0,0 +1,81 @@ +obtenerBalanceAnual($anio); + + header('Content-Type: application/json'); // Indica al navegador que se mandará JSON + echo json_encode($datos); // Convierte el array de PHP en JSON + exit; + + // GENERAR CUENTAS A COBRAR (SERVICIOS) + + case 'generar_cuentas_por_cobrar': + + if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + header("Location:index.php?opt=home_operador"); + exit; + } + + $anio = isset($_GET['anio']) ? (int) $_GET['anio'] : date('Y'); + + // REQUIERO INFORME DE MODELO + + require_once __DIR__ . '/../models/informeModel.php'; + $informemodel = new informeModel(); + + $datos = $informemodel->obtenerACobrar($anio); + + header('Content-Type: application/json'); // Indica al navegador que se mandará JSON + echo json_encode($datos); // Convierte el array de PHP en JSON + exit; +} \ No newline at end of file diff --git a/app/controllers/logInController.php b/app/controllers/logInController.php new file mode 100644 index 0000000..d41d80c --- /dev/null +++ b/app/controllers/logInController.php @@ -0,0 +1,646 @@ + $tiempoBloqueo) { + $_SESSION['login_intentos'] = 0; + } + + // Verificar bloqueo + + if ($_SESSION['login_intentos'] >= $maxIntentos) { + $_SESSION['flash_error'] = "Demasiados intentos. Espere 1 minuto antes de volver a intentar."; + header("Location: index.php?opt=login_operador"); + exit; + } + + // Si existen errores de validación, se informan definiendo flash_error en el array asociativo $_SESSION + + if (!empty($errores)) { + + // Se suma el intento erroneo de inicio de sesión y se guarda el tiempo + + $_SESSION['login_intentos']++; + $_SESSION['login_ultimo_intento'] = time(); + + $_SESSION['flash_error'] = implode('
', $errores); // Agrega un salto de linea entre cada error encontrado y los guarda en $_SESSION + header("Location: index.php?opt=login_operador"); + exit; + } + + // Si no existen errores, buscamos el usuario mediante logInModel + + $user = $loginmodel->buscarPorUsuario($usuario); + + if (!$user || !password_verify($password, $user['password'])) { + + $_SESSION['login_intentos']++; + $_SESSION['login_ultimo_intento'] = time(); + + $_SESSION['flash_error'] = 'Usuario o contraseña incorrectos'; + header("Location: index.php?opt=login_operador"); + exit; + } + + // Verificamos el rol del usuario para cargar la vista correspondiente + + if ($user['rol'] === 'operador') { + + // Se resetean los intentos + + $_SESSION['login_intentos'] = 0; + $_SESSION['login_ultimo_intento'] = time(); + + $_SESSION['user'] = $user; // Se define $user dentro del array asociativo $_SESSION para tenerlo disponible en todo el sistema + header("Location: index.php?opt=inicio_operador"); + exit; + + } else if ($user['rol'] === 'admin') { + + // Se resetean los intentos + + $_SESSION['login_intentos'] = 0; + $_SESSION['login_ultimo_intento'] = time(); + + $_SESSION['user'] = $user; // Se define $user dentro del array asociativo $_SESSION para tenerlo disponible en todo el sistema + header("Location: index.php?opt=inicio_admin"); + exit; + } + + // HOME OPERADOR + + case 'home_operador': + + // Validar sesión + + if (!isset($_SESSION['user'])) { + header("Location: index.php?opt=login_operador"); + exit; + } + + // Si el rol es admin, se redirigue al home_admin (en caso de ingresar por URL) + + if ($_SESSION['user']['rol'] !== 'operador') { + header("Location: index.php?opt=home_admin"); + exit; + } + + // Se carga en caché el home operador y se envía el HTML + + ob_start(); + require __DIR__ . '/../views/login/homeOperadorView.php'; + return ob_get_clean(); + + // HOME ADMIN + + case 'home_admin': + + // Validar sesión + + if (!isset($_SESSION['user'])) { + header("Location: index.php?opt=login_admin"); + exit; + } + + // Si el rol es operador, se redirigue al home_operador (en caso de ingresar por URL) + + if ($_SESSION['user']['rol'] !== 'admin') { + header("Location: index.php?opt=home_operador"); + exit; + } + + // Se carga en caché el home admin y se envía el HTML + + ob_start(); + require __DIR__ . '/../views/login/homeAdminView.php'; + return ob_get_clean(); + + // INICIO OPERADOR + + case 'inicio_operador': + + // Validar sesión + + if (!isset($_SESSION['user'])) { + header("Location: index.php?opt=login_operador"); + exit; + } + + // Si el rol es admin, se redirigue al inicio_admin (en caso de ingresar por URL) + + if ($_SESSION['user']['rol'] !== 'operador') { + header("Location: index.php?opt=inicio_admin"); + exit; + } + + // Se carga en caché el inicio operador y se envía el HTML + + ob_start(); + require __DIR__ . '/../views/login/inicioOperadorView.php'; + return ob_get_clean(); + + // INICIO ADMIN + + case 'inicio_admin': + + // Validar sesión + + if (!isset($_SESSION['user'])) { + header("Location: index.php?opt=login_admin"); + exit; + } + + // Si el rol es operador, se redirigue al inicio_operador (en caso de ingresar por URL) + + if ($_SESSION['user']['rol'] !== 'admin') { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + // Calcular si mostrar alerta de backup (solo a admin) + + if ($_SESSION['user']['rol'] === 'admin') { + + require __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $fechaUltimoBackup = $configuracionmodel->obtenerFechaUltimoBackup(); + + if ($fechaUltimoBackup) { + + $dias = (new DateTime($fechaUltimoBackup))->diff(new DateTime())->days; + + if ($dias >= 7) { + + header("Location: index.php?opt=generar_backup&from=inicio_admin&auto=1"); + exit; + } + + } else { + + $ahora = time(); + + // Si nunca se mostró o ya pasó 1 hora + + if (!isset($_SESSION['ultimo_aviso_backup']) || ($ahora - $_SESSION['ultimo_aviso_backup']) >= 3600) { + + $_SESSION['flash_error'] = "No se encontraron copias de seguridad. Por favor, verifique."; + + // Guardamos cuándo mostramos el mensaje + + $_SESSION['ultimo_aviso_backup'] = $ahora; + } + } + } + + // Se carga en caché el inicio admin y se envía el HTML + + ob_start(); + require __DIR__ . '/../views/login/inicioAdminView.php'; + return ob_get_clean(); + + // LOGOUT + + case 'logout': + + session_unset(); // Libera las variables almacenadas en $_SESSION + session_destroy(); // Destruye la sesión del servidor + setcookie(session_name(), '', time() - 3600, '/'); // Borra la cookie en el navegaodor + + header("Location: index.php?opt=login"); + exit; + + // CARGAR VISTA README (AYUDA) + + case 'readme': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + ob_start(); + require __DIR__ . '/../views/ayudas/readMe.php'; + return ob_get_clean(); + } + + // RECUPERACIÓN DE CUENTA + + case 'recuperar_cuenta': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + ob_start(); + require __DIR__ . '/../views/login/recuperarCuentaView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { // Recorre todos los caracteres (siempre) y retorna true or false + $_SESSION['flash_error'] = 'Acción inválida.'; + header('Location: index.php?opt=login'); + exit; + } + + // Requiero modelos necesarios + + require_once __DIR__ . '/../models/usuarioModel.php'; + $usuariomodel = new usuarioModel(); + + require_once __DIR__ . '/../models/logInModel.php'; + $loginmodel = new logInModel(); + + require_once __DIR__ . '/../helpers/email_recuperar_cuenta.php'; + + $email = trim($_POST['email'] ?? $_SESSION['reset_email'] ?? ''); + + // Cooltime de envío de mails + + if (isset($_SESSION['ultimo_envio_codigo'])) { + + $segundosRestantes = 60 - (time() - $_SESSION['ultimo_envio_codigo']); + + if ($segundosRestantes > 0) { + + $_SESSION['flash_success'] = "Ya se envió un código recientemente. Espere {$segundosRestantes} segundos."; + + header('Location: index.php?opt=verificar_codigo'); + exit; + } + } + + // Validación + + if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $_SESSION['flash_error'] = 'Email inválido'; + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + // Buscar usuario + + $usuario = $usuariomodel->obtenerPorEmail($email); + + // Siempre respuesta genérica (seguridad) + + if (!$usuario || $usuario['rol'] !== 'admin') { + $_SESSION['flash_error'] = 'Si el correo ingresado es válido, recibirás un código de verificación.'; + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + // Generar código + + $codigo = random_int(100000, 999999); + $codigoHash = password_hash($codigo, PASSWORD_DEFAULT); + + $expira = date('Y-m-d H:i:s', strtotime('+10 minutes')); + + // Invalidar códigos anteriores + + if (!$loginmodel->invalidarPorUsuario($usuario['id'])) { + $_SESSION['flash_error'] = 'Ocurrió un error inesperado.'; + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + // Guardar nuevo código + + if (!$loginmodel->crear([ + 'id_usuario' => $usuario['id'], + 'codigo' => $codigoHash, + 'expira_a' => $expira + ])) { + $_SESSION['flash_error'] = 'Ocurrió un error inesperado.'; + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + // Guardar email en sesión (UX) + + $_SESSION['reset_email'] = $email; + + // Enviar mail + + if (!enviarCodigoRecuperacion($email, $codigo)) { + $_SESSION['flash_error'] = 'Ocurrió un error inesperado al enviar el email.'; + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + $_SESSION['ultimo_envio_codigo'] = time(); + + // Redirigir a verificación + + $_SESSION['flash_success'] = 'Si el correo ingresado es válido, recibirás un código de verificación.'; + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + case 'verificar_codigo': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + if (empty($_SESSION['reset_email'])) { + header('Location: index.php?opt=recuperar_cuenta'); + exit; + } + + ob_start(); + require __DIR__ . '/../views/login/verificarCodigoView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { + $_SESSION['flash_error'] = 'Acción inválida.'; + header('Location: index.php?opt=login'); + exit; + } + + // Inicializamos contador + + if (!isset($_SESSION['codigo_intentos'])) { + $_SESSION['codigo_intentos'] = 0; + } + + // VALIDAMOS BLOQUEO ACTIVO + + // SI EL BLOQUEO YA EXPIRÓ, RESETEAMOS + + if (isset($_SESSION['codigo_bloqueado_hasta']) && time() >= $_SESSION['codigo_bloqueado_hasta']) { + + unset($_SESSION['codigo_bloqueado_hasta']); + $_SESSION['codigo_intentos'] = 0; + } + + // SI SIGUE BLOQUEADO + + if (isset($_SESSION['codigo_bloqueado_hasta']) && time() < $_SESSION['codigo_bloqueado_hasta']) { + + $segundos = $_SESSION['codigo_bloqueado_hasta'] - time(); + + $minutos = ceil($segundos / 60); + + $_SESSION['flash_error'] = "Demasiados intentos fallidos. Espere {$minutos} minuto(s)."; + + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + require_once __DIR__ . '/../models/usuarioModel.php'; + $usuariomodel = new usuarioModel(); + + require_once __DIR__ . '/../models/logInModel.php'; + $loginmodel = new logInModel(); + + $codigo = trim($_POST['codigo'] ?? ''); + $email = $_SESSION['reset_email'] ?? null; + + if (empty($codigo) || empty($email)) { + $_SESSION['flash_error'] = 'Debe ingresar el código.'; + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + $usuario = $usuariomodel->obtenerPorEmail($email); + + if (!$usuario) { + $_SESSION['flash_error'] = 'Ocurrió un error.'; + header('Location: index.php?opt=login'); + exit; + } + + $registro = $loginmodel->obtenerCodigoValido($usuario['id']); + + if (!$registro) { + + $_SESSION['codigo_intentos']++; + + // BLOQUEO AUTOMÁTICO + + if ($_SESSION['codigo_intentos'] >= 5) { + + $_SESSION['codigo_bloqueado_hasta'] = time() + (15 * 60); + + $_SESSION['flash_error'] = 'Demasiados intentos fallidos. Intente nuevamente en 15 minutos.'; + + } else { + $_SESSION['flash_error'] = 'El código no es válido.'; + } + + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + // EXPIRACIÓN + + if (strtotime($registro['expira_a']) < time()) { + + $loginmodel->marcarComoUsado($registro['id']); + + $_SESSION['flash_error'] = 'El código ha expirado.'; + + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + // VALIDACIÓN HASH + + if (!password_verify($codigo, $registro['codigo'])) { + + $_SESSION['codigo_intentos']++; + + // BLOQUEAR SI LLEGA AL LÍMITE + + if ($_SESSION['codigo_intentos'] >= 5) { + + $_SESSION['codigo_bloqueado_hasta'] = time() + (15 * 60); + + $_SESSION['flash_error'] = 'Demasiados intentos fallidos. Intente nuevamente en 15 minutos.'; + + } else { + $_SESSION['flash_error'] = 'Código incorrecto.'; + } + + header('Location: index.php?opt=verificar_codigo'); + exit; + } + + // ÉXITO + + $loginmodel->marcarComoUsado($registro['id']); + + $_SESSION['reset_autorizado'] = $usuario['id']; + + // Limpiar protección + + unset($_SESSION['codigo_intentos']); + unset($_SESSION['codigo_bloqueado_hasta']); + + header('Location: index.php?opt=restablecer_contrasenia'); + exit; + } + + // RESTABLECER CONTRASENIA + + case 'restablecer_contrasenia': + + if (!isset($_SESSION['reset_autorizado'])) { + header('Location: index.php?opt=login'); + exit; + } + + // MOSTRAR VISTA + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + ob_start(); + require __DIR__ . '/../views/login/restablecerContraseniaView.php'; + return ob_get_clean(); + } + + // GUARDAR NUEVA CONTRASENIA + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { + $_SESSION['flash_error'] = 'Acción inválida.'; + header('Location: index.php?opt=login'); + exit; + } + + require_once __DIR__ . '/../models/usuarioModel.php'; + $usuariomodel = new usuarioModel(); + + $password = trim($_POST['password'] ?? ''); + $confirmar = trim($_POST['confirmar_password'] ?? ''); + + $errores = []; + + // VALIDACIONES + + if (empty($password) || empty($confirmar)) { + $_SESSION['flash_error'] = 'Complete todos los campos.'; + header('Location: index.php?opt=restablecer_contrasenia'); + exit; + } + + // VALIDACIONES DE CONTRASENIA + + if ($password === '') { + $errores[] = "La contraseña no puede estar vacía."; + } elseif (strpos($password, ' ') !== false) { + $errores[] = "La contraseña no puede contener espacios."; + } elseif (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$/', $password)) { + $errores[] = "La contraseña debe tener al menos una mayúscula, una minúscula y un número, y mínimo 6 caracteres."; + } + + if ($password !== $confirmar) { + $_SESSION['flash_error'] = 'Las contraseñas no coinciden.'; + header('Location: index.php?opt=restablecer_contrasenia'); + exit; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=restablecer_contrasenia"); + exit; + } + + // HASH + + $passwordHash = password_hash($password, PASSWORD_DEFAULT); + + // ACTUALIZAR + + if (!$usuariomodel->actualizarContrasenia ( + $_SESSION['reset_autorizado'], $passwordHash + )) { + $_SESSION['flash_error'] = 'Ocurrió un error inesperado.'; + header('Location: index.php?opt=restablecer_contrasenia'); + exit; + } + + // INVALIDAMOS TODOS LOS CODIGOS (PARA PREVENIR) + + $loginmodel->invalidarPorUsuario($_SESSION['reset_autorizado']); + + // Limpiar sesión temporal + + unset($_SESSION['reset_autorizado']); + unset($_SESSION['reset_email']); + + // REGENERACION DE SESSION ID + + session_regenerate_id(true); + + $_SESSION['flash_success'] = 'Contraseña actualizada correctamente.'; + + header('Location: index.php?opt=login'); + exit; + } + + // DEFAULT (404) + + default: + ob_start(); + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/pageController.php b/app/controllers/pageController.php new file mode 100644 index 0000000..15b528f --- /dev/null +++ b/app/controllers/pageController.php @@ -0,0 +1,324 @@ +buscarPorUsuario($_SESSION['user']['usuario']); + + if (!$usuario || !(bool)$usuario['activo']) { + session_unset(); + session_destroy(); + setcookie(session_name(), '', time() - 3600, '/'); + + header("Location: index.php?opt=login"); + exit; + } + + $_SESSION['user'] = $usuario; + + if ($_SESSION['user']['activo'] !== true) { + header("Location:index.php?opt=logout"); + } + } + + + /* ----- OPCIONES DE ACCIONES EN SISTEMA ----- */ + + + // USUARIO LOGUEADO (HOME / LOGOUT) + + $opcioneslogueado = [ + 'inicio_admin', + 'inicio_operador', + 'home_operador', + 'home_admin', + 'logout', + 'readme' + ]; + + if (in_array($opt, $opcioneslogueado, true)) { // Parámetro $strict en true, PHP compara el valor y también el tipo de dato + return require __DIR__ . '/../controllers/logInController.php'; + } + + // OPCIONES DE USUARIO (PERFIL + ADMINISTRACION) + + $opcionesusuarios = [ + 'datos_usuario', + 'usuarios', + 'crear_usuario', + 'eliminar_usuario' + ]; + + if (in_array($opt, $opcionesusuarios, true)) { + return require __DIR__ . '/../controllers/usuarioController.php'; + } + + // CLIENTES + + $opcionesclientes = [ + 'clientes', + 'crear_cliente', + 'editar_cliente' + ]; + + if (in_array($opt, $opcionesclientes, true)) { + return require __DIR__ . '/../controllers/clienteController.php'; + } + + // PROVEEDORES + + $opcionesproveedores = [ + 'proveedores', + 'crear_proveedor', + 'editar_proveedor' + ]; + + if (in_array($opt, $opcionesproveedores, true)) { + return require __DIR__ . '/../controllers/proveedorController.php'; + } + + // PRODUCTOS + + $opcionesproductos = [ + + // Manejo de productos + + 'productos', + 'productos_inactivos', + 'crear_producto', + 'editar_producto', + 'cambiar_estado', + + // Manejo de categoría de productos + + 'categorias', + 'crear_categoria', + 'editar_categoria' + ]; + + if (in_array($opt, $opcionesproductos, true)) { + return require __DIR__ . '/../controllers/productoController.php'; + } + + // VENTAS + + $opcionesventas = [ + 'listar_ventas', + 'crear_venta', + 'agregar_producto', + 'seleccionar_cliente', + 'eliminar_cliente', + 'resumen_venta', + 'guardar_datos_venta', + 'generar_venta_pdf', + 'continuar_sin_comprobante', + 'confirmar_venta', + 'detalle_venta' + ]; + + if (in_array($opt, $opcionesventas, true)) { + return require __DIR__ . '/../controllers/ventaController.php'; + } + + // PEDIDOS + + $opcionespedidos = [ + 'listar_pedidos', + 'detalle_pedido', + 'crear_pedido', + 'agregar_producto_pedido', + 'seleccionar_proveedor', + 'eliminar_proveedor', + 'generar_remito_pdf', + 'continuar_sin_remito', + 'confirmar_pedido' + ]; + + if (in_array($opt, $opcionespedidos, true)) { + return require __DIR__ . '/../controllers/pedidoController.php'; + } + + // SERVICIOS + + $opcionesservicios = [ + 'crear_servicio', + 'guardar_servicio', // Guardado inicial del servicio (cuando viene el cliente al local) + 'generar_servicio_pdf', + 'seleccionar_cliente_servicio', + 'eliminar_cliente_servicio', + 'listar_servicios', + 'actualizar_servicio', // Actualización del servicio (se cargan los datos a realizar e insumos a utilizar) + 'agregar_insumo', + 'finalizar_servicio', // Actualización del servicio (el cliente decidió llevar a cabo el servicio, al terminar se marca como finalizado en la base de datos) + 'cancelar_servicio', // Según regla de negocio (caso en donde el cliente decide no realizar el servicio) + 'detalle_servicio', + 'detalle_insumos_servicio', + 'resetear_datos_servicio', // Para resetar caja y datos en $_SESSION al ingresar a la vista de actualización + 'agregar_mano_obra' + ]; + + if (in_array($opt, $opcionesservicios, true)) { + return require __DIR__ . '/../controllers/servicioController.php'; + } + + // MANEJO GENERAL DE CAJA + + $opcionescaja = [ + + // MANEJO DE VENTAS + + 'agregar_producto_caja', + 'eliminar_producto', + 'editar_cantidad', + 'vaciar_caja_venta', + + // MANEJO DE PEDIDOS + + 'agregar_producto_pedido_caja', + 'eliminar_producto_pedido', + 'editar_datos_producto', + 'vaciar_caja_pedido', + + // MANEJO DE SERVICIOS + + 'agregar_insumo_caja', + 'vaciar_caja_servicio', + 'eliminar_insumo', + 'editar_cantidad_insumo' + ]; + + if (in_array($opt, $opcionescaja, true)) { + return require __DIR__ . '/../controllers/cajaController.php'; + } + + // PRESUPUESTOS + + $opcionespresupuestos = [ + 'crear_presupuesto', + 'seleccionar_cliente_presupuesto', + 'eliminar_cliente_presupuesto', + 'agregar_producto_presupuesto', + 'eliminar_producto_presupuesto', + 'vaciar_caja_presupuesto', + 'editar_cantidad_presupuesto', + 'resumen_presupuesto', + 'guardar_datos_presupuesto', + 'generar_presupuesto_pdf' + ]; + + if (in_array($opt, $opcionespresupuestos, true)) { + return require __DIR__ . '/../controllers/presupuestoController.php'; + } + + // INFORMES + + $opcionesinformes = [ + 'generar_balance_anual', + 'generar_cuentas_por_cobrar', + 'informe' + ]; + + if (in_array($opt, $opcionesinformes, true)) { + return require __DIR__ . '/../controllers/informeController.php'; + } + + // CONFIGURACIÓN DEL SISTEMA (controla configuración general y también backups) + + $opcionesconfiguracion = [ + 'configuracion', + 'actualizar_config', + 'backups', + 'generar_backup', + 'descargar_backup', + 'eliminar_backup', + 'restaurar_backup' + ]; + + if (in_array($opt, $opcionesconfiguracion, true)) { + return require __DIR__ . '/../controllers/configuracionController.php'; + } + + // CHATBOT + + $opcioneschatbot = [ + 'chatbot' + ]; + + if (in_array($opt, $opcioneschatbot, true)) { + + ob_start(); + + require __DIR__ . '/../views/chatbot/chatbotView.php'; + + return ob_get_clean(); + } + + // DEFAULT (404) + + ob_start(); // Inicia búfer de salida en PHP, lo guarda en memoria + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); // Devuelve el contenido almacenado en el búfer como string +} \ No newline at end of file diff --git a/app/controllers/pedidoController.php b/app/controllers/pedidoController.php new file mode 100644 index 0000000..f2bac67 --- /dev/null +++ b/app/controllers/pedidoController.php @@ -0,0 +1,554 @@ +listarPedidos($anioSeleccionado); + + // Reordenamiento de fecha (para poder ordenar correctamente con dataTables) + + foreach ($pedidos as $i => $p) { + + if (!empty($p['fecha'])) { + + $dt = new DateTime($p['fecha']); + + // Para ordenar + + $pedidos[$i]['fecha'] = $dt->format('Y-m-d'); + + // Para mostrar + + $pedidos[$i]['fecha_formateada'] = $dt->format('d/m/Y'); + + } else { + $pedidos[$i]['fecha'] = '-'; + $pedidos[$i]['fecha_formateada'] = '-'; + } + } + + ob_start(); + require __DIR__ . '/../views/pedidos/listaPedidosView.php'; + return ob_get_clean(); + + // MOSTRAR DETALLE DE PRODUCTOS DE UN PEDIDO + + case 'detalle_pedido': + + $idPedido = (int) ($_GET['id'] ?? 0); + + if ($idPedido <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=listar_pedidos'); + exit; + } + + require_once __DIR__ . '/../models/pedidoModel.php'; + $pedidomodel = new pedidoModel(); + + $pedido_detalle = $pedidomodel->getDetallePedido($idPedido); + + ob_start(); + require __DIR__ . '/../views/pedidos/pedidoDetalleView.php'; + return ob_get_clean(); + + // CREAR PEDIDO + + case 'crear_pedido': + + // Se muestra vista de carga de pedidos + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $condIVA = $_SESSION['pedido']['cond_iva'] ?? $_SESSION['pedido']['proveedor']['cond_iva'] ?? 'consumidor_final'; + $cuit = $_SESSION['pedido']['cuit'] ?? $_SESSION['pedido']['proveedor']['cuit'] ?? '00000000000'; + + // Se obtienen los productos almacenados en caja (puede estar vacía) + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + $productos = $cajamodel->listarProductosPedido(); + + if (isset($_SESSION['pedido']['proveedor'])) { + $proveedor = $_SESSION['pedido']['proveedor']; + } else { + $proveedor = ''; + } + + // Cálculo de total + + $total = $cajamodel->calcularPrecioTotalPedido() ?? 0; + + ob_start(); + require __DIR__ . '/../views/pedidos/crearPedidoView.php'; + return ob_get_clean(); + } + + // AGREGAR PRODUCTO AL PEDIDO + + case 'agregar_producto_pedido': + + // Guardo datos en $_SESSION para persistencia + + $_SESSION['pedido']['cond_iva'] = $_POST['cond_iva'] ?? ''; + $_SESSION['pedido']['cuit'] = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + // Requiero el modelo de producto + + require_once __DIR__ . '/../models/productoModel.php'; + $productomodel = new productoModel(); + + $productos = $productomodel->listarProductosPedido(); + + foreach ($productos as $i => $p) { + + if (!isset($p['id'])) { + unset($productos[$i]); + continue; + } + + if ($p['stock'] === null || $p['stock'] === 0) { + $productos[$i]['stock'] = '-'; + } + } + + ob_start(); + require __DIR__ . '/../views/pedidos/agregarProductoPedidoView.php'; + return ob_get_clean(); + + // SELECCIONAR PROVEEDOR + + case 'seleccionar_proveedor': + + // Requiero el modelo de proveedor + + require_once __DIR__ . '/../models/proveedorModel.php'; + $proveedormodel = new proveedorModel(); + + // Se muestra la vista de selección de cliente + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $proveedores = $proveedormodel->listarProveedores(); + + ob_start(); + require __DIR__ . '/../views/pedidos/seleccionarProveedorView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $idProveedor = (int) ($_POST['id_proveedor'] ?? 0); + + if ($idProveedor <= 0) { + $_SESSION['flash_error'] = "No se seleccionó ningún proveedor."; + header("Location: index.php?opt=seleccionar_proveedor"); + exit; + } + + $proveedor = $proveedormodel->obtenerPorId($idProveedor); + + if (!$proveedor) { + $_SESSION['flash_error'] = "Proveedor inválido."; + header("Location: index.php?opt=seleccionar_proveedor"); + exit; + } + + // Guardo proveedor en $_SESSION + + $_SESSION['pedido']['proveedor'] = [ + 'id' => $proveedor['id'], + 'razon_social' => $proveedor['razon_social'], + 'telefono' => $proveedor['telefono'], + 'email' => $proveedor['email'], + 'cuit' => $proveedor['cuit'], + 'cond_iva' => $proveedor['cond_iva'] + ]; + + $_SESSION['pedido']['cond_iva'] = $proveedor['cond_iva']; + $_SESSION['pedido']['cuit'] = $proveedor['cuit']; + + $_SESSION['flash_success'] = "Proveedor agregado correctamente."; + header("Location: index.php?opt=crear_pedido"); + exit; + } + + // ELIMINAR PROVEEDOR + + case 'eliminar_proveedor': + + unset($_SESSION['pedido']['cond_iva']); + unset($_SESSION['pedido']['cuit']); + + if (isset($_SESSION['pedido']['proveedor'])) { + unset($_SESSION['pedido']['proveedor']); + + $_SESSION['flash_success'] = "Proveedor eliminado correctamente."; + } else { + $_SESSION['flash_error'] = "No había proveedor seleccionado."; + } + + header("Location: index.php?opt=crear_pedido"); + exit; + + // GENERACION DE REMITO PDF MEDIANTE USO DE LIBRERIA DOMPDF + + case 'generar_remito_pdf': + + // CARGAMOS LA VISTA + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $numeroRemito = $_SESSION['ultimo_pedido_id']; + + ob_start(); + require __DIR__ . '/../views/pedidos/descargarRemito.php'; + return ob_get_clean(); + } + + // GENERACIÓN DEL PDF + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // Requiero datos de empresa y configuración + + require __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $datosEmpresa = $configuracionmodel->obtenerConfiguracion(); + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $datosEmpresa['cond_iva'] = $mapCondIVA[$datosEmpresa['cond_iva']]; + + // Obtenemos en la variable los datos guardados en $_SESSION durante la confirmación del pedido + + $datosRemito = $_SESSION['remito_pdf'] ?? null; + + if (!$datosRemito) { + $_SESSION['flash_error'] = 'No hay datos disponibles para generar el remito.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // Preparamos las variables con los datos + + $productos = $datosRemito['productos']; + $proveedor = $datosRemito['proveedor']; + $vendedor = $datosRemito['vendedor']; + $total = $datosRemito['total']; + $condIVA = $datosRemito['cond_iva']; + $cuit = $datosRemito['cuit']; + + // VALIDACIONES BASE + + if (!$proveedor || empty($productos)) { + $_SESSION['flash_error'] = 'Datos incompletos para generar el remito.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // VALIDACIÓN CONDICIÓN IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + if (!in_array($condIVA, $condIvaPermitidas, true)) { + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // VALIDACIÓN CUIT + + $cuit = trim($cuit); + + if (!preg_match('/^\d{10,11}$/', $cuit)) { + $_SESSION['flash_error'] = 'CUIT inválido.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if ($total <= 0) { + $_SESSION['flash_error'] = 'Total inválido.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // MAPEOS PARA PDF + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $condIVALabel = $mapCondIVA[$condIVA]; + + // Obtenemos código ID del pedido generado + + $numero = $_SESSION['ultimo_pedido_id'] ?? null; + + if (!$numero) { + $_SESSION['flash_error'] = 'No se encontró el número de remito.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // Guardamos en la vatiable para mostrar el código en la vista + + $numeroRemito = '0001-' . str_pad($numero, 8, '0', STR_PAD_LEFT); + + // RENDER PDF + + ob_start(); + require __DIR__ . '/../pdf/pedidos/remitoPDF.php'; + $html = ob_get_clean(); + + $cssPath = '/var/www/html/css/stylesRemitoPDF.css'; + $css = file_get_contents($cssPath); + + // Configuración de opciones + + $options = new Options(); + $options->set('isRemoteEnabled', true); // Permite cargar recursos externos (CSS, imágenes, etc.) + $options->set('chroot', '/var/www/html'); // Define directorios base para recursos locales + $options->set('defaultFont', 'DejaVu Sans'); + + // Creación de instancia Dompdf con las opciones seteadas + + $dompdf = new Dompdf($options); + + // Cargar el HTML que se va a convertir en PDF + + $dompdf->loadHtml(" + + $html + "); + + $dompdf->setPaper('A4', 'portrait'); // Define el tamanio de hoja y la orientación + $dompdf->render(); // Renderiza el PDF (procesa el HTML) + + // Enviar el PDF al browser + + $dompdf->stream( + 'Remito N° ' . $numeroRemito . '.pdf', + ['Attachment' => true] + ); + + // Liberamos datos del array $_SESSION + + unset($_SESSION['remito_pdf']); + + exit; + } + + // CONTINUAR SIN DESCARGAR EL REMITO PDF + + case 'continuar_sin_remito': + + // Liberamos $_SESSION + + unset($_SESSION['remito_pdf']); + unset($_SESSION['ultimo_pedido_id']); + + $_SESSION['flash_success'] = 'Pedido finalizado correctamente.'; + + header('Location: index.php?opt=crear_pedido'); + exit; + + // CONFIRMAR PEDIDO + + case 'confirmar_pedido': + + // Requiero modelos necesarios + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + require_once __DIR__ . '/../models/proveedorModel.php'; + $proveedormodel = new proveedorModel(); + + require_once __DIR__ . '/../models/pedidoModel.php'; + $pedidomodel = new pedidoModel(); + + // Condición IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + $condIva = $_POST['cond_iva'] ?? 'consumidor_final'; + + if (!in_array($condIva, $condIvaPermitidas, true)) { + + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // CUIT + + $cuit = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + $cuit = trim($cuit); + + if ($cuit === '') { + + $_SESSION['flash_error'] = 'Debe ingresar un CUIT.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $_SESSION['flash_error'] = 'El CUIT debe contener 11 dígitos.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + $idProveedor = $_SESSION['pedido']['proveedor']['id'] ?? null; + + $vendedor = $_SESSION['user'] ?? null; + + if ($idProveedor === null) { + $_SESSION['flash_error'] = 'No seleccionó un proveedor.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + $productosFinales = $cajamodel->listarProductosPedido(); + $proveedor = $proveedormodel->obtenerPorId($idProveedor); + $total = $cajamodel->calcularPrecioTotalPedido(); + + if (empty($productosFinales)) { + $_SESSION['flash_error'] = 'No hay productos en el pedido.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + foreach ($productosFinales as $p) { + if (empty($p['id_producto']) || empty($p['cantidad']) || $p['cantidad'] <= 0) { + $_SESSION['flash_error'] = 'Pedido con productos inválidos.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + } + + if (!$proveedor) { + $_SESSION['flash_error'] = 'Proveedor inválido.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if ($total <= 0) { + $_SESSION['flash_error'] = 'El total del pedido es inválido.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // Guardamos el pedido y obtenemos el ID autoincremental desde la base de datos + + $idPedido = $pedidomodel->guardarPedido($productosFinales, $proveedor, $vendedor, $total); + + // Verificamos si se obtuvo el ID o si ocurrió algún error + + if ($idPedido !== false) { + + // Guardamos datos para utilizar en la generación del PDF + + $_SESSION['ultimo_pedido_id'] = $idPedido; + + $_SESSION['remito_pdf'] = [ + 'productos' => $productosFinales, + 'proveedor' => $proveedor, + 'vendedor' => $vendedor, + 'total' => $total, + 'cond_iva' => $condIva, + 'cuit' => $cuit + ]; + + // Liberamos + + $cajamodel->vaciarCajaPedido(); + unset($_SESSION['pedido']); + + $_SESSION['flash_success'] = "Pedido guardado correctamente."; + + } else { + $_SESSION['flash_error'] = $pedidomodel->getError() ?? 'No se pudo guardar el pedido.'; + } + + header("Location: index.php?opt=generar_remito_pdf"); + exit; + + // DEFAULT + + default: + + ob_start(); + header("Location: index.php?opt=404View.php"); + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/presupuestoController.php b/app/controllers/presupuestoController.php new file mode 100644 index 0000000..5d181f5 --- /dev/null +++ b/app/controllers/presupuestoController.php @@ -0,0 +1,729 @@ +listarProductos(); + + if (isset($_SESSION['presupuesto']['cliente'])) { + $cliente = $_SESSION['presupuesto']['cliente']; + } else { + $cliente = ''; + } + + // Llamamos a los métodos del modelo para obtener calculos preliminares + + $subtotal = $presupuestomodel->calcularSubtotal() ?? 0; + $descuento = $presupuestomodel->calcularDescuentoTotal() ?? 0; + $total = $presupuestomodel->calcularPrecioTotal() ?? 0; + + ob_start(); + require __DIR__ . '/../views/presupuestos/crearPresupuestoView.php'; + return ob_get_clean(); + } + + // AGREGAR PRODUCTO AL PRESUPUESTO (en casos de presupuesto, el manejo de productos es manejado por modelo de presupuesto) + + case 'agregar_producto_presupuesto': + + require_once __DIR__ . '/../models/productoModel.php'; + $productomodel = new productoModel(); + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $productos = $productomodel->listarProductosVenta(); + + foreach ($productos as $i => $p) { + + if (!isset($p['id'])) { + unset($productos[$i]); + continue; + } + + if ($p['stock'] === null || $p['stock'] === 0) { + $productos[$i]['stock'] = '-'; + } + + $productos[$i]['descuento_total'] = number_format($p['descuento_total'], 2, '.', '') . ' %'; + } + + ob_start(); + require __DIR__ . '/../views/presupuestos/agregarProductoView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $ids = $_POST['ids'] ?? []; + $cantidades = $_POST['cantidades'] ?? []; + + if (empty($ids)) { + $_SESSION['flash_error'] = 'No se seleccionó ningún producto.'; + header('Location: index.php?opt=agregar_producto_presupuesto'); + exit; + } + + $agregados = 0; + $errores = []; + + // Evitar duplicados + + $ids = array_unique($ids); + + foreach ($ids as $idProducto) { + + $idProducto = (int) $idProducto; + $cantidad = isset($cantidades[$idProducto]) ? (int) $cantidades[$idProducto] : 1; + + if ($idProducto <= 0 || $cantidad <= 0) { + $errores[] = 'Datos inválidos.'; + continue; + } + + $producto = $productomodel->obtenerPorIdParaCaja($idProducto); + + if (!$producto) { + $errores[] = 'Producto inexistente.'; + continue; + } + + if ($presupuestomodel->agregarProducto($producto, $cantidad)) { + $agregados++; + } else { + $errores[] = $presupuestomodel->getError(); + } + } + + if ($agregados === 1) { + $_SESSION['flash_success'] = "Se agregó un producto correctamente."; + } else if ($agregados > 1) { + $_SESSION['flash_success'] = "Se agregaron {$agregados} productos correctamente."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', array_unique($errores)); + header('Location: index.php?opt=agregar_producto_presupuesto'); + exit; + } + + header('Location: index.php?opt=crear_presupuesto'); + exit; + } + + // ELIMINAR PRODUCTO DE CAJA + + case 'eliminar_producto_presupuesto': + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + + if ($idProducto <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_presupuesto'); + exit; + } + + if (!$presupuestomodel->eliminarProducto($idProducto)) { + $_SESSION['flash_error'] = $cajamodel->getError() ?? 'No se pudo eliminar el producto.'; + } else { + $_SESSION['flash_success'] = 'Producto eliminado correctamente.'; + } + + header('Location: index.php?opt=crear_presupuesto'); + exit; + + // EDITAR CANTIDAD DEL PRODUCTO + + case 'editar_cantidad_presupuesto': + + $idProducto = (int) ($_POST['id_producto'] ?? 0); + $nuevaCantidad = (int) ($_POST['cantidad'] ?? 0); + + if ($idProducto <= 0 || $nuevaCantidad <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=crear_presupuesto'); + exit; + } + + if (!$presupuestomodel->editarCantidad($idProducto, $nuevaCantidad)) { + $_SESSION['flash_error'] = $presupuestomodel->getError() ?? 'No se pudo actualizar la cantidad.'; + } else { + $_SESSION['flash_success'] = 'Cantidad editada correctamente.'; + } + + header('Location: index.php?opt=crear_presupuesto'); + exit; + + // SELECCIONAR CLIENTE PARA EL PRESUPUESTO + + case 'seleccionar_cliente_presupuesto': + + require_once __DIR__ . '/../models/clienteModel.php'; + + $clientemodel = new clienteModel(); + + // Se muestra la vista de selección de cliente + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $clientes = $clientemodel->listarClientes(); + + ob_start(); + require __DIR__ . '/../views/presupuestos/seleccionarClienteView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $idCliente = (int) ($_POST['id_cliente'] ?? 0); + + if ($idCliente <= 0) { + $_SESSION['flash_error'] = "No se seleccionó ningún cliente."; + header("Location: index.php?opt=seleccionar_cliente_presupuesto"); + exit; + } + + $cliente = $clientemodel->obtenerPorId($idCliente); + + if (!$cliente) { + $_SESSION['flash_error'] = "Cliente inválido."; + header("Location: index.php?opt=seleccionar_cliente_presupuesto"); + exit; + } + + $_SESSION['presupuesto']['cliente'] = [ + 'id' => $cliente['id'], + 'nombre' => $cliente['nombre'], + 'apellido' => $cliente['apellido'], + 'telefono' => $cliente['telefono'], + 'email' => $cliente['email'], + 'cuit' => $cliente['cuit'], + 'cond_iva' => $cliente['cond_iva'] + ]; + + $_SESSION['presupuesto_extra']['cond_iva'] = $cliente['cond_iva']; + $_SESSION['presupuesto_extra']['cuit'] = $cliente['cuit']; + + $_SESSION['flash_success'] = "Cliente agregado correctamente."; + header("Location: index.php?opt=crear_presupuesto"); + exit; + } + + // ELIMINAR CLIENTE YA SELECCIONADO + + case 'eliminar_cliente_presupuesto': + + if (isset($_SESSION['presupuesto']['cliente'])) { + unset($_SESSION['presupuesto']['cliente']); + + // También se eliminan datos del presupuesto guardados desde vista resumen + + if (isset($_SESSION['presupuesto_extra'])) { + unset($_SESSION['presupuesto_extra']); + unset($_SESSION['presupuesto_final']); + } + + $_SESSION['flash_success'] = "Cliente eliminado correctamente."; + } else { + $_SESSION['flash_error'] = "No había cliente seleccionado."; + } + + header("Location: index.php?opt=crear_presupuesto"); + exit; + + // VACIAR CAJA DE PRODUCTOS + + case 'vaciar_caja_presupuesto': + + if ($presupuestomodel->vaciarCajaPresupuesto()) { + $_SESSION['flash_success'] = 'Carrito vaciado.'; + } else { + $_SESSION['flash_error'] = 'No se pudo vaciar el carrito.'; + } + + header('Location: index.php?opt=crear_presupuesto'); + exit; + + // GENERAR VISTA RESUMEN DE PRESUPUESTO + + case 'resumen_presupuesto': + + $cliente = ($_SESSION['presupuesto']['cliente'] ?? null); + + $_SESSION['presupuesto_extra']['cond_iva'] = $_SESSION['presupuesto_extra']['cond_iva'] ?? $cliente['cond_iva'] ?? 'consumidor_final'; + $_SESSION['presupuesto_extra']['cuit'] = $_SESSION['presupuesto_extra']['cuit'] ?? $cliente['cuit'] ?? '00000000000'; + + $productos = $presupuestomodel->listarProductos(); + $descuento = $presupuestomodel->calcularDescuentoTotal(); + + if (!$cliente || empty($productos)) { + $_SESSION['flash_error'] = "Debe seleccionar el cliente y al menos un producto."; + header("Location: index.php?opt=crear_presupuesto"); + exit; + } + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + // 1. Subtotal SIEMPRE se recalcula (pueden haber cambiado productos/cantidades) + + $subtotal = $presupuestomodel->calcularSubtotal() ?? 0; + + // 2. Datos extra guardados en sesión (si existen) + + $descuentoIngresado = isset($_SESSION['presupuesto_extra']['descuento']) ? (float) $_SESSION['presupuesto_extra']['descuento'] : 0; + + $recargoIngresado = isset($_SESSION['presupuesto_extra']['recargo']) ? (float) $_SESSION['presupuesto_extra']['recargo'] : 0; + + // 3. Recalculo siempre con subtotal actual + + $datosProvisorios = $presupuestomodel->actualizarDatosProvisorio($descuentoIngresado, $recargoIngresado, $subtotal, $descuento); + + $descuento = $datosProvisorios['descuento'] ?? 0; + $recargo = $datosProvisorios['recargo'] ?? 0; + $total = $datosProvisorios['total'] ?? 0; + + // Guardo en SESSION + + $_SESSION['presupuesto_final'] = [ + 'descuento' => $descuento, + 'recargo' => $recargo, + 'total' => $total + ]; + + ob_start(); + require __DIR__ . '/../views/presupuestos/resumenPresupuestoView.php'; + return ob_get_clean(); + } + + // GUARDAR DATOS DEL PRESUPUESTO (permite volver hacia atrás en el proceso y no perder datos) + + case 'guardar_datos_presupuesto': + + // Validaciones de reglas de negocio + + // Condición IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + $condIva = $_POST['cond_iva'] ?? 'consumidor_final'; + + if (!in_array($condIva, $condIvaPermitidas, true)) { + + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // CUIT + + $cuit = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + $cuit = trim($cuit); + + if ($cuit === '') { + + $_SESSION['flash_error'] = 'Debe ingresar un CUIT.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $_SESSION['flash_error'] = 'El CUIT debe contener 11 dígitos.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // Forma de pago + tipo de pago + + $reglasPago = [ + 'efectivo' => ['pago_unico'], + 'transferencia' => ['pago_unico'], + 'echeq' => ['pago_unico'], + 'debito' => ['pago_unico'], + 'credito' => ['pago_unico', '3_cuotas', '6_cuotas', '12_cuotas'], + ]; + + $formaPago = $_POST['forma_pago'] ?? 'efectivo'; + $tipoPago = $_POST['tipo_pago'] ?? 'pago_unico'; + + if (!isset($reglasPago[$formaPago]) || !in_array($tipoPago, $reglasPago[$formaPago], true)) { + $_SESSION['flash_error'] = 'Combinación de forma y tipo de pago inválida.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // Validación de descuento y recargo + + $descuentoPresupuesto = isset($_POST['descuento']) ? (float) $_POST['descuento'] : 0; + $recargoPresupuesto = isset($_POST['recargo']) ? (float) $_POST['recargo'] : 0; + + if ($descuentoPresupuesto < 0 || $descuentoPresupuesto > 100) { + $_SESSION['flash_error'] = 'El descuento debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + if ($recargoPresupuesto < 0 || $recargoPresupuesto > 100) { + $_SESSION['flash_error'] = 'El recargo debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // Guardado de datos + + // Datos extra del presupuesto + + $datosExtraPresupuesto = [ + 'cond_iva' => $condIva, + 'cuit' => $cuit, + 'forma_pago' => $formaPago, + 'tipo_pago' => $tipoPago, + 'descuento' => $descuentoPresupuesto, + 'recargo' => $recargoPresupuesto + ]; + + // También los persisto en sesión para mantener estado + + $_SESSION['presupuesto_extra'] = $datosExtraPresupuesto; + + // Datos para la vista + + require_once __DIR__ . '/../models/clienteModel.php'; + $clientemodel = new clienteModel(); + + $idCliente = ($_SESSION['presupuesto']['cliente']['id'] ?? null); + $productos = $presupuestomodel->listarProductos(); + + if ($idCliente === null) { + $_SESSION['flash_error'] = 'No seleccionó un cliente.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + $vendedor = $_SESSION['user'] ?? null; + $cliente = $clientemodel->obtenerPorId($idCliente); + + if (!$cliente) { + $_SESSION['flash_error'] = 'No se pudo identificar el cliente.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + /* Subtotal que no contiene descuentos. + (Al ingresar descuento o recargo, lo guardo en un array separado, para no sobreescribir datos) */ + + $subtotal = $presupuestomodel->calcularSubtotal() ?? 0; + $descuento = $presupuestomodel->calcularDescuentoTotal() ?? 0; + + // Validación del subtotal + + if ($subtotal <= 0) { + $_SESSION['flash_error'] = 'El presupuesto no contiene productos válidos.'; + header('Location: index.php?opt=crear_presupuesto'); + exit; + } + + // Llamamos a la función que devuelve los datos actualizados en un array nuevo + + $datosProvisorios = $presupuestomodel->actualizarDatosProvisorio($descuentoPresupuesto, $recargoPresupuesto, $subtotal, $descuento); + + $recargo = $datosProvisorios['recargo'] ?? 0; + $descuento = $datosProvisorios['descuento'] ?? 0; + $total = $datosProvisorios['total'] ?? 0; + + $_SESSION['presupuesto_final'] = [ + 'recargo' => $recargo, + 'descuento' => $descuento, + 'total' => $total + ]; + + $_SESSION['flash_success'] = "Datos guardados de manera provisoria."; + header("Location: index.php?opt=resumen_presupuesto"); + exit; + + // GENERACION DE PRESUPUESTO PDF MEDIANTE USO DE LIBRERIA DOMPDF + + case 'generar_presupuesto_pdf': + + // Requiero datos de empresa y configuración + + require __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $datosEmpresa = $configuracionmodel->obtenerConfiguracion(); + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $datosEmpresa['cond_iva'] = $mapCondIVA[$datosEmpresa['cond_iva']]; + + // Requiero modelos de presupuesto y cliente + + require_once __DIR__ . '/../models/presupuestoModel.php'; + $presupuestomodel = new presupuestoModel(); + + require_once __DIR__ . '/../models/clienteModel.php'; + $clientemodel = new clienteModel(); + + $productos = $presupuestomodel->listarProductos(); + $idCliente = $_SESSION['presupuesto']['cliente']['id'] ?? null; + + if ($idCliente === null) { + $_SESSION['flash_error'] = 'No seleccionó un cliente.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + $vendedor = $_SESSION['user'] ?? null; + $cliente = $clientemodel->obtenerPorId($idCliente); + + // DATOS FORM (requiero todo de nuevo desde el formulario mediante POST, no utilizo los datos ya almacenados en $_SESSION (son unicamente para mantener estado)) + + $condIVA = $_POST['cond_iva'] ?? ''; + $cuit = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + $formaPago = $_POST['forma_pago'] ?? ''; + $tipoPago = $_POST['tipo_pago'] ?? ''; + + $descuentoPresupuesto = $_POST['descuento'] ?? 0; + $recargoPresupuesto = $_POST['recargo'] ?? 0; + + // VALIDACIONES BASE + + if (!$cliente || empty($productos)) { + $_SESSION['flash_error'] = 'Datos incompletos para generar el presupuesto.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // VALIDACIÓN CONDICIÓN IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + if (!in_array($condIVA, $condIvaPermitidas, true)) { + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // VALIDACIÓN CUIT + + $cuit = trim($cuit); + + if (!preg_match('/^\d{10,11}$/', $cuit)) { + $_SESSION['flash_error'] = 'CUIT inválido. Debe contener solo números.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // VALIDACIÓN FORMA + TIPO DE PAGO + + $reglasPago = [ + 'efectivo' => ['pago_unico'], + 'transferencia' => ['pago_unico'], + 'echeq' => ['pago_unico'], + 'debito' => ['pago_unico'], + 'credito' => ['pago_unico', '3_cuotas', '6_cuotas', '12_cuotas'], + ]; + + if (!isset($reglasPago[$formaPago]) || !in_array($tipoPago, $reglasPago[$formaPago], true)) { + $_SESSION['flash_error'] = 'Combinación de forma y tipo de pago inválida.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // VALIDACIÓN DESCUENTO / RECARGO + + if ($descuentoPresupuesto < 0 || $descuentoPresupuesto > 100) { + $_SESSION['flash_error'] = 'El descuento debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + if ($recargoPresupuesto < 0 || $recargoPresupuesto > 100) { + $_SESSION['flash_error'] = 'El recargo debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // RECÁLCULO DE TOTALES (SIEMPRE) + + $subtotal = $presupuestomodel->calcularSubtotal() ?? 0; + + if ($subtotal <= 0) { + $_SESSION['flash_error'] = 'El presupuesto no contiene productos válidos.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + $descuentoProductos = $presupuestomodel->calcularDescuentoTotal() ?? 0; + + $datosProvisorios = $presupuestomodel->actualizarDatosProvisorio($descuentoPresupuesto, $recargoPresupuesto, $subtotal, $descuentoProductos); + + $recargo = $datosProvisorios['recargo'] ?? 0; + $descuento = $datosProvisorios['descuento'] ?? 0; + $total = $datosProvisorios['total'] ?? 0; + + if ($total <= 0) { + $_SESSION['flash_error'] = 'Total inválido.'; + header('Location: index.php?opt=resumen_presupuesto'); + exit; + } + + // MAPEOS PARA PDF + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $mapFormaPago = [ + 'efectivo'=> 'Efectivo', + 'transferencia' => 'Transferencia', + 'debito' => 'Débito', + 'credito' => 'Crédito', + 'echeq' => 'Echeq', + ]; + + $mapTipoPago = [ + 'pago_unico' => 'Pago Único', + '3_cuotas' => '3 Cuotas', + '6_cuotas' => '6 Cuotas', + '12_cuotas' => '12 Cuotas', + ]; + + $condIVALabel = $mapCondIVA[$condIVA]; + $formaPagoLabel = $mapFormaPago[$formaPago]; + $tipoPagoLabel = $mapTipoPago[$tipoPago]; + + // CÁLCULO DE CUOTAS + + if ($tipoPago !== 'pago_unico') { + $cuota = (int) explode('_', $tipoPago)[0]; + $valorCuota = $total / $cuota; + } + + // NÚMERO DE PRESUPUESTO (RANDOM) (decidí dejarlo con código aleatorio, se podría modificar perfectamente) + + $parte2 = str_pad((string) rand(0, 99999999), 8, '0', STR_PAD_LEFT); + $numeroPresupuesto = '0001' . '-' . $parte2; + + // RENDER PDF + + ob_start(); + require __DIR__ . '/../pdf/presupuestos/presupuestoPDF.php'; + $html = ob_get_clean(); + + $cssPath = '/var/www/html/css/stylesPDF.css'; + $css = file_get_contents($cssPath); + + // Configuración de opciones + + $options = new Options(); + $options->set('isRemoteEnabled', true); // Permite cargar recursos externos (CSS, imágenes, etc.) + $options->set('chroot', '/var/www/html'); // Define directorios base para recursos locales + $options->set('defaultFont', 'DejaVu Sans'); + + // Creación de instancia Dompdf con las opciones seteadas + + $dompdf = new Dompdf($options); + + // Cargar el HTML que se va a convertir en PDF + + $dompdf->loadHtml(" + + $html + "); + + $dompdf->setPaper('A4', 'portrait'); // Define el tamanio de hoja y la orientación + $dompdf->render(); // Renderiza el PDF (procesa el HTML) + + // Enviar el PDF al browser + + $dompdf->stream( + 'Presupuesto N° ' . $numeroPresupuesto . '.pdf', + ['Attachment' => true] // false = se muestra en el navegador | true = descarga directa + ); + + // Eliminamos datos ingresados y vaciamos caja + + $presupuestomodel->vaciarCajaPresupuesto(); + unset(($_SESSION)['presupuesto']['cliente']); + unset($_SESSION['presupuesto_final']); + unset($_SESSION['presupuesto_extra']); + unset($_SESSION['prov_presupuesto']); + + exit; + + // DEFAULT (404) + + default: + + ob_start(); + header("Location: index.php?opt=404View.php"); + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/productoController.php b/app/controllers/productoController.php new file mode 100644 index 0000000..d95411d --- /dev/null +++ b/app/controllers/productoController.php @@ -0,0 +1,541 @@ +listarProductos(); + + // Saneamiento de datos para enviar a la view + + foreach ($productos as $i => $p) { + + if (($p['precio'] === null) || ($p['precio'] == 0)) { + $productos[$i]['precio'] = '-'; + } else { + $productos[$i]['precio'] = '$ ' . number_format($p['precio'], 2, '.', ','); + } + + if ($p['stock'] === null || $p['stock'] === 0) { + $productos[$i]['stock'] = '-'; + } + + $productos[$i]['descuento_total'] = number_format($p['descuento_total'], 2, '.', '') . ' %'; + } + + ob_start(); + require __DIR__ . '/../views/productos/listaProductosView.php'; + return ob_get_clean(); + + // LISTAR PRODUCTOS INACTIVOS + + case 'productos_inactivos': + + // Utilizamos el modelo para obtener la lista completa de productos + + $productos = $productomodel->listarProductosInactivos(); + + ob_start(); + require __DIR__ . '/../views/productos/listaProductosInactivosView.php'; + return ob_get_clean(); + + // CREAR PRODUCTO + + case 'crear_producto': + + // Mostramos la vista con los datos correspondientes + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $categorias = $productomodel->listarCategorias(); + $tipos = $productomodel->listarTipos(); + + ob_start(); + require __DIR__ . '/../views/productos/crearProductoView.php'; + return ob_get_clean(); + } + + // En caso de modificación de datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $marca = trim($_POST['marca'] ?? ''); + $modelo = trim($_POST['modelo'] ?? ''); + $id_categoria = trim($_POST['categoria'] ?? ''); + $id_tipo = trim($_POST['tipo'] ?? ''); + + // Normalización de datos + + $descuentoRaw = trim($_POST['descuento'] ?? ''); + $descuento = $descuentoRaw === '' ? null : $descuentoRaw; + + $errores = []; + + // Validaciones + + if ($marca === '') { + $errores[] = "La marca no puede estar vacía."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+)*$/", $marca)) { + $errores[] = "La marca solo puede contener letras, números y espacios."; + } else if (strlen($marca) < 2) { + $errores[] = "La marca debe tener al menos 2 caracteres."; + } else if (strlen($marca) > 50) { + $errores[] = "La marca no puede superar los 50 caracteres."; + } + + if ($modelo === '') { + $errores[] = "El modelo no puede estar vacío."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9.\-]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9.\-]+)*$/", $modelo)) { + $errores[] = "El modelo solo puede contener letras, números, espacios, guiones y puntos"; + } else if (strlen($modelo) < 2) { + $errores[] = "El modelo debe tener al menos 2 caracteres."; + } else if (strlen($modelo) > 50) { + $errores[] = "El modelo no puede superar los 50 caracteres."; + } + + if ($descuento !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $descuento)) { + $errores[] = "El descuento debe ser un número válido con hasta 2 decimales."; + } else if ((float)$descuento <= 0 || (float)$descuento > 100) { + $errores[] = "El descuento debe estar entre 0 a 100."; + } + } + + if ($id_categoria === '') { + $errores[] = "Debe seleccionar una categoría."; + } else if (!filter_var($id_categoria, FILTER_VALIDATE_INT) || $id_categoria <= 0) { + $errores[] = "La categoría seleccionada no es válida."; + } else if (!$productomodel->existeCategoria($id_categoria)) { + $errores[] = "La categoría ingresada no existe."; + } + + if ($id_tipo === '') { + $errores[] = "Debe seleccionar un tipo."; + } else if (!filter_var($id_tipo, FILTER_VALIDATE_INT) || $id_tipo <= 0) { + $errores[] = "El tipo seleccionado no es válido."; + } else if (!$productomodel->existeTipo($id_tipo)) { + $errores[] = "El tipo ingresado no existe."; + } + + if (($id_tipo === 2) && ($descuento !== null)) { + $errores[] = "No se permite descuento si el tipo de producto es Servicio."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=crear_producto"); + exit; + } + + // Utilizamos el método del modelo para crear el producto + + if ($productomodel->crearProducto([ + 'marca' => $marca, + 'modelo' => $modelo, + 'precio' => null, + 'stock' => null, + 'descuento' => $descuento !== null ? (float)$descuento : null, + 'id_categoria' => $id_categoria, + 'id_tipo' => $id_tipo + ])) { + $_SESSION['flash_success'] = "Producto creado correctamente."; + } else { + $_SESSION['flash_error'] = "No se pudo crear el producto."; + } + + header("Location: index.php?opt=productos"); + exit; + } + + // EDITAR PRODUCTO + + case 'editar_producto': + + $idEditar = (int) ($_GET['id'] ?? 0); + + if ($idEditar <= 0) { + header("Location: index.php?opt=productos"); + exit; + } + + // Busco el producto en la base de datos mediante el modelo + + $producto = $productomodel->obtenerPorId($idEditar); + + if (!$producto) { + $_SESSION['flash_error'] = "No se encontró el producto."; + header("Location: index.php?opt=productos"); + exit; + }; + + // (Para indicar que aún no existen pedidos que incluyan dicho producto (no existe ID de pedido)) + + if ($producto['id_pedido'] === null) { + $producto['id_pedido'] = 'Aún no existe pedido que incluya este producto'; + } + + // Si el descuento es null lo seteamos en 0 + + if ($producto['descuento'] === null) { + $producto['descuento'] = 0; + } + + // Mostrar datos de producto + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $categorias = $productomodel->listarCategorias(); + $tipos = $productomodel->listarTipos(); + + ob_start(); + require __DIR__ . '/../views/productos/editarProductoView.php'; + return ob_get_clean(); + } + + // Caso en donde se modifican los datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // Normalización de datos + + $descuentoRaw = trim($_POST['descuento'] ?? ''); + $descuento = $descuentoRaw === '' ? null : $descuentoRaw; + + $errores = []; + + // Validaciones + + if ($descuento !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $descuento)) { + $errores[] = "El descuento debe ser un número válido con hasta 2 decimales."; + } else if ((float)$descuento < 0 || (float)$descuento > 100) { + $errores[] = "El descuento debe estar entre 0 a 100."; + } + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=editar_producto&id=" . $idEditar); + exit; + } + + // Recalculoamos precio (en caso de que se modifique descuento) + + require_once __DIR__ . '/../models/pedidoModel.php'; + $pedidomodel = new pedidoModel(); + + $costoUnitario = $pedidomodel->obtenerCosto($_GET['id']); + $categoria = $productomodel->obtenerPorIdCategoria($producto['id_categoria']); + + // Antes de guardar los datos verificamos que la suma del descuento no sea mayor a 100 + + if (($categoria['porcentaje_ganancia'] + $descuento) > 100) { + $_SESSION['flash_error'] = "La suma entre el descuento ingresado y el descuento de la categoria supera el 100%. Verifique."; + header("Location:index.php?opt=editar_producto&id=$idEditar"); + exit; + } + + // Creamos el array con los datos correspondientes + + $guardarData = [ + 'precio' => $costoUnitario !== null ? (100 + $categoria['porcentaje_ganancia']) * $costoUnitario / 100 : null, + 'descuento' => $descuento !== null ? (float) $descuento : null, + 'id_categoria' => $producto['id_categoria'] + ]; + + // Utilizamos la función del modelo para actualizar el producto + + if ($productomodel->actualizarProducto($_GET['id'], $guardarData)) { + $_SESSION['flash_success'] = "Datos actualizados correctamente."; + } else { + $_SESSION['flash_error'] = "No se pudo actualizar los datos."; + } + + header("Location: index.php?opt=productos"); + exit; + } + + // CAMBIAR ESTADO DEL PRODUCTO (ACTIVO / INACTIVO) + + case 'cambiar_estado': + + $idProducto = (int) ($_POST['id'] ?? 0); + $nuevoEstado = isset($_POST['estado']) ? (bool) $_POST['estado'] : null; + + if ($idProducto <= 0 || !is_bool($nuevoEstado)) { + header("Location: index.php?opt=productos"); + exit; + }; + + $productomodel->actualizarEstado($idProducto, $nuevoEstado); + + $_SESSION['flash_success'] = "Estado actualizado correctamente."; + header("Location: index.php?opt=productos"); + exit; + + // LISTAR LAS CATEGORIAS DE PRODUCTOS + + case 'categorias': + + $categorias = $productomodel->listarCategorias(); + + foreach ($categorias as $i => $c) { + + if (($categorias)[$i]['porcentaje_ganancia'] === null) { + $c['porcentaje_ganancia'] = 0; + } + + if (($categorias)[$i]['descuento'] === null) { + $c['descuento'] = 0; + } + + $categorias[$i]['porcentaje_ganancia'] = number_format($c['porcentaje_ganancia'], 2, '.', '') . ' %'; + $categorias[$i]['descuento'] = number_format($c['descuento'], 2, '.', '') . ' %'; + } + + ob_start(); + require __DIR__ . '/../views/productos/listaCategoriasView.php'; + return ob_get_clean(); + + // CREAR NUEVA CATEGORIA DE PRODUCTOS + + case 'crear_categoria': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + ob_start(); + require __DIR__ . '/../views/productos/crearCategoriaView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $nombre = trim($_POST['nombre'] ?? ''); + + // Normalización + + $porcentaje_gananciaRaw = trim($_POST['porcentaje_ganancia'] ?? ''); + $porcentaje_ganancia = $porcentaje_gananciaRaw === '' ? null : $porcentaje_gananciaRaw; + + $descuentoRaw = trim($_POST['descuento'] ?? ''); + $descuento = $descuentoRaw === '' ? null : $descuentoRaw; + + $errores = []; + + // Validaciones + + if ($nombre === '') { + $errores[] = "El nombre no puede estar vacío."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+)*$/", $nombre)) { + $errores[] = "El nombre solo puede contener letras, números y espacios."; + } else if (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } else if (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($porcentaje_ganancia !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $porcentaje_ganancia)) { + $errores[] = "La ganancia debe ser un número válido con hasta 2 decimales."; + } else if ((float)$porcentaje_ganancia <= 0 || (float)$porcentaje_ganancia > 100) { + $errores[] = "La ganancia debe estar entre 0 a 100."; + } + } + + if ($descuento !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $descuento)) { + $errores[] = "El descuento debe ser un número válido con hasta 2 decimales."; + } else if ((float)$descuento < 0 || (float)$descuento > 100) { + $errores[] = "El descuento debe estar entre 0 a 100."; + } + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=crear_categoria"); + exit; + } + + if ($productomodel->crearCategoria([ + 'nombre' => $nombre, + 'porcentaje_ganancia' => $porcentaje_ganancia !== null ? (float) $porcentaje_ganancia : null, + 'descuento' => $descuento !== null ? (float) $descuento : null + ])) { + $_SESSION['flash_success'] = "Categoría creada correctamente."; + } else { + $_SESSION['flash_error'] = "No se pudo crear la categoría."; + } + + header("Location: index.php?opt=categorias"); + exit; + } + + // EDITAR CATEGORIA + + case 'editar_categoria': + + $idEditar = (int) ($_GET['id'] ?? 0); + + if ($idEditar <= 0) { + header("Location: index.php?opt=categorias"); + exit; + } + + // Busco la categoría en la base de datos mediante el modelo + + $categorias = $productomodel->obtenerPorIdCategoria($idEditar); + + if (!$categorias) { + header("Location: index.php?opt=categorias"); + exit; + }; + + // Si el descuento o el porcentaje_ganancia es null lo seteamos en 0 + + if ($categorias['porcentaje_ganancia'] === null) { + $categorias['porcentaje_ganancia'] = 0; + } + + if ($categorias['descuento'] === null) { + $categorias['descuento'] = 0; + } + + // Mostrar datos de producto + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + ob_start(); + require __DIR__ . '/../views/productos/editarCategoriaView.php'; + return ob_get_clean(); + } + + // Caso en donde se modifican los datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $nombre = trim($_POST['nombre'] ?? ''); + + // Normalización + + $porcentaje_gananciaRaw = trim($_POST['porcentaje_ganancia'] ?? ''); + $porcentaje_ganancia = $porcentaje_gananciaRaw === '' ? null : $porcentaje_gananciaRaw; + + $descuentoRaw = trim($_POST['descuento'] ?? ''); + $descuento = $descuentoRaw === '' ? null : $descuentoRaw; + + $errores = []; + + // Validaciones + + if ($nombre === '') { + $errores[] = "El nombre no puede estar vacío."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9]+)*$/", $nombre)) { + $errores[] = "El nombre solo puede contener letras, números y espacios."; + } else if (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } else if (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($porcentaje_ganancia !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $porcentaje_ganancia)) { + $errores[] = "La ganancia debe ser un número válido con hasta 2 decimales."; + } else if ((float)$porcentaje_ganancia <= 0 || (float)$porcentaje_ganancia > 100) { + $errores[] = "La ganancia debe estar entre 0 a 100."; + } + } + + if ($descuento !== null) { + + if (!preg_match("/^\d{1,3}(\.\d{1,2})?$/", $descuento)) { + $errores[] = "El descuento debe ser un número válido con hasta 2 decimales."; + } else if ((float)$descuento < 0 || (float)$descuento > 100) { + $errores[] = "El descuento debe estar entre 0 a 100."; + } + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=editar_categoria&id=" . $idEditar); + exit; + } + + // Verificamos que la suma entre los descuentos no supere el 100% + + require_once __DIR__ . '/../models/productoModel.php'; + $productomodel = new productoModel(); + + $productosDescuentos = $productomodel->obtenerDescuento($idEditar); + + foreach ($productosDescuentos as $i => $p) { + + if (($productosDescuentos[$i]['descuento'] + $descuento) > 100) { + $_SESSION['flash_error'] = "Existe al menos un producto que supera el 100% de sumatoria en el descuento. Verifique."; + header("Location:index.php?opt=editar_categoria&id=$idEditar"); + exit; + } + } + + $guardarData = [ + 'nombre' => $nombre, + 'porcentaje_ganancia' => $porcentaje_ganancia !== null ? (float) $porcentaje_ganancia : null, + 'descuento' => $descuento !== null ? (float) $descuento : null + ]; + + // Obtenemos productos con sus respectivos costos para recalcular el precio de venta + + require_once __DIR__ . '/../models/pedidoModel.php'; + $pedidomodel = new pedidoModel(); + + $costosProductos = $pedidomodel->obtenerCostosPorCategoria($idEditar); + + if ($productomodel->actualizarPreciosPorCategoria($idEditar, $guardarData, $costosProductos)) { + $_SESSION['flash_success'] = "Datos actualizados correctamente."; + } else { + $_SESSION['flash_error'] = "No se pudo actualizar la categoría."; + } + + header("Location: index.php?opt=categorias"); + exit; + } + + // DEFAULT (404) + + default: + + ob_start(); + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/proveedorController.php b/app/controllers/proveedorController.php new file mode 100644 index 0000000..2289449 --- /dev/null +++ b/app/controllers/proveedorController.php @@ -0,0 +1,300 @@ +listarProveedores(); + + ob_start(); + require __DIR__ . '/../views/proveedores/listaProveedoresView.php'; + return ob_get_clean(); + + // CREAR PROVEEDOR + + case 'crear_proveedor': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $_SESSION['return_opt'] = $_GET['from'] ?? 'proveedores'; + + ob_start(); + require __DIR__ . '/../views/proveedores/crearProveedorView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $razon_social = trim($_POST['razon_social'] ?? ''); + $telefono = trim($_POST['telefono'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $condIVA = trim($_POST['cond_iva'] ?? 'consumidor_final'); + + $cuit1 = trim($_POST['cuit_1'] ?? ''); + $cuit2 = trim($_POST['cuit_2'] ?? ''); + $cuit3 = trim($_POST['cuit_3'] ?? ''); + + $cuit = $cuit1 . $cuit2 . $cuit3; + + $errores = []; + + // Validaciones + + if ($razon_social === '') { + $errores[] = "La razón social no puede estar vacía."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9&',.\-]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9&',.\-]+)*(?: (S\.A\.|S\.R\.L\.|S\.C\.|S\.C\.P\.|S\.A\.S\.))?$/", $razon_social)) { + $errores[] = "La razón social contiene caracteres inválidos o la abreviatura legal final no es correcta."; + } else if (strlen($razon_social) < 2) { + $errores[] = "La razón social debe tener al menos 2 caracteres."; + } else if (strlen($razon_social) > 100) { + $errores[] = "La razón social no puede superar los 100 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } else if (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + $condicionesPermitidas = [ + 'consumidor_final', + 'monotributo', + 'responsable_inscripto' + ]; + + if (!in_array($condIVA, $condicionesPermitidas, true)) { + + $errores[] = 'Condición de IVA inválida.'; + } + + if ($cuit !== '') { + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $errores[] = 'El CUIT debe contener 11 dígitos.'; + } + } else { + + $cuit = null; + } + + // Guardamos errores para mostrarlos por pantalla + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Se guarda en $_SESSION el error + header("Location: index.php?opt=crear_proveedor"); + exit; + } + + // Verificación de existencia de proveedor + + if ($proveedormodel->verificarExistencia([ + 'razon_social' => $razon_social, + 'email' => $email, + 'cuit' => $cuit + ])) { + $_SESSION['flash_error'] = "El proveedor ya existe."; + + header("Location: index.php?opt=crear_proveedor"); + exit; + } + + if ($proveedormodel->crearProveedor([ + 'razon_social' => $razon_social, + 'telefono' => $telefono, + 'email' => $email, + 'cuit' => $cuit, + 'cond_iva' => $condIVA + ])) { + $_SESSION['flash_succes'] = "Proveedor creado correctamente."; + } else { + $_SESSION['flash_error'] = "Ocurrió un error al crear el proveedor."; + } + + $opt = $_SESSION['return_opt']; + + header("Location: index.php?opt=$opt"); + exit; + } + + // EDITAR PROVEEDOR + + case 'editar_proveedor': + + $idEditar = (int) ($_GET['id'] ?? 0); + + if ($idEditar < 0) { + header("Location: index.php?opt=proveedores"); + exit; + }; + + // Busco el cliente en la base de datos mediante el modelo + + $proveedor = $proveedormodel->obtenerPorId($idEditar); + + if (!$proveedor) { + header("Location: index.php?opt=proveedores"); + exit; + }; + + // Mostrar datos de proveedor + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + ob_start(); + require __DIR__ . '/../views/proveedores/editarProveedorView.php'; + return ob_get_clean(); + } + + // Caso en donde se modifican los datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $razon_social = trim($_POST['razon_social']) ?? ''; + $telefono = trim($_POST['telefono']) ?? ''; + $email = trim($_POST['email']) ?? ''; + $condIVA = trim($_POST['cond_iva'] ?? 'consumidor_final'); + + $cuit1 = trim($_POST['cuit_1'] ?? ''); + $cuit2 = trim($_POST['cuit_2'] ?? ''); + $cuit3 = trim($_POST['cuit_3'] ?? ''); + + $cuit = $cuit1 . $cuit2 . $cuit3; + + $errores = []; + + // Validaciones + + if ($razon_social === '') { + $errores[] = "La razón social no puede estar vacía."; + } else if (!preg_match("/^[A-Za-zÁÉÍÓÚÑáéíóúñ0-9&',.\-]+(?: [A-Za-zÁÉÍÓÚÑáéíóúñ0-9&',.\-]+)*(?: (S\.A\.|S\.R\.L\.|S\.C\.|S\.C\.P\.|S\.A\.S\.))?$/", $razon_social)) { + $errores[] = "La razón social contiene caracteres inválidos o la abreviatura legal final no es correcta."; + } else if (strlen($razon_social) < 2) { + $errores[] = "La razón social debe tener al menos 2 caracteres."; + } else if (strlen($razon_social) > 100) { + $errores[] = "La razón social no puede superar los 100 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } else if (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + $condicionesPermitidas = [ + 'consumidor_final', + 'monotributo', + 'responsable_inscripto' + ]; + + if (!in_array($condIVA, $condicionesPermitidas, true)) { + + $errores[] = 'Condición de IVA inválida.'; + } + + if (($condIVA !== 'consumidor_final') && ($cuit === '')) { + + $errores[] = 'Debe ingresar el CUIT'; + } + + if ($cuit !== '') { + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $errores[] = 'El CUIT debe contener 11 dígitos.'; + } + } else { + + $cuit = null; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Guardamos los errores en $_SESSION + header("Location: index.php?opt=editar_proveedor"); + exit; + } + + // Verificación de existencia de proveedor + + if ($proveedormodel->verificarExistencia([ + 'razon_social' => $razon_social, + 'email' => $email, + 'cuit' => $cuit + ], $idEditar)) { + $_SESSION['flash_error'] = "El proveedor ya existe."; + + header("Location: index.php?opt=editar_proveedor&id=$idEditar"); + exit; + } + + $guardarData = [ + 'razon_social' => $razon_social, + 'telefono' => $telefono, + 'email' => $email, + 'cuit' => $cuit, + 'cond_iva' => $condIVA + ]; + + if ($proveedormodel->actualizarProveedor($proveedor['id'], $guardarData)) { + $_SESSION['flash_success'] = "Datos actualizados correctamente."; // Guardo en $_SESSION el success + } else { + $_SESSION['flash_error'] = "No se pudo actualizar el proveedor."; + } + + header("Location: index.php?opt=proveedores"); + exit; + } + + // DEFAULT (404) + + default: + ob_start(); + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/servicioController.php b/app/controllers/servicioController.php new file mode 100644 index 0000000..2d60b16 --- /dev/null +++ b/app/controllers/servicioController.php @@ -0,0 +1,915 @@ +busquedaParaServicio(); + + ob_start(); + require __DIR__ . '/../views/servicios/crearServicioView.php'; + return ob_get_clean(); + } + + // GUARDADO INICIAL DEL SERVICIO + + case 'guardar_servicio': + + // Cliente y vendedor + + $cliente = $_SESSION['servicio']['cliente'] ?? null; + $vendedor = $_SESSION['user'] ?? ''; + + // Tomo los datos del form + + $descripcionEquipo = trim($_POST['descripcion_equipo']) ?? ''; + $contraseniaEquipo = trim($_POST['contrasenia_equipo']) ?? ''; + $cuenta = trim($_POST['cuenta']) ?? ''; + $contraseniaCuenta = trim($_POST['contrasenia_cuenta']) ?? ''; + $descripcionProblema = trim($_POST['descripcion_problema']) ?? ''; + $observacion = trim($_POST['observacion']) ?? ''; + + if (!$cliente) { + $_SESSION['flash_error'] = "Debe seleccionar el cliente."; + header("Location: index.php?opt=crear_servicio"); + exit; + } + + // Validaciones + + $errores = []; + + if ($descripcionEquipo === '') { + $errores[] = "La descripción del equipo es obligatoria."; + } + + if ($descripcionProblema === '') { + $errores[] = "Debe describir el problema."; + } + + // Validacion de longitud + + if (strlen($descripcionEquipo) > 100) { + $errores[] = "La descripción del equipo es demasiado larga."; + } + + if (strlen($descripcionProblema) > 2000) { + $errores[] = "La descripción del problema es demasiada larga."; + } + + if (strlen($contraseniaEquipo) > 100) { + $errores[] = "Contraseña demasiado larga."; + } + + if (strlen($observacion) > 500) { + $errores[] = "La descripción de la observación es demasiada larga."; + } + + if (strlen($cuenta) > 100) { + $errores[] = "Cuenta demasiado larga."; + } + + if (strlen($contraseniaCuenta) > 100) { + $errores[] = "Contraseña de cuenta demasiado larga."; + } + + // Otras validaciones + + if (!empty($cuenta) && !filter_var($cuenta, FILTER_VALIDATE_EMAIL)) { + $errores[] = "El email de la cuenta no es válido."; + } + + if ($contraseniaEquipo !== '' && $descripcionEquipo === '') { + $errores[] = "Debe indicar el equipo si ingresa contraseña."; + } + + if ($contraseniaCuenta !== '' && $cuenta === '') { + $errores[] = "Debe indicar la cuenta si ingresa contraseña."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location:index.php?opt=crear_servicio"); + exit; + } + + // Guardado de datos en array + + $datosServicio = [ + 'descripcion_equipo' => $descripcionEquipo, + 'cuenta' => $cuenta, + 'descripcion_problema' => $descripcionProblema, + 'observacion' => $observacion + ]; + + // Encriptamos las contraseñas + + if ($contraseniaEquipo !== '') { + $datosServicio['contrasenia_equipo'] = encryptData($contraseniaEquipo); + } + + if ($contraseniaCuenta !== '') { + $datosServicio['contrasenia_cuenta'] = encryptData($contraseniaCuenta); + } + + // Llamado a modelo de servicio + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + // NÚMERO DE SERVICIO (OBTENIDO MEDIANTE SECUENCIA PREVIEW) + + if (isset($_SESSION['servicio_numero'])) { + $numero = $_SESSION['servicio_numero']; + } else { + $numero = $serviciomodel->getNumeroServicioPreview(); + $_SESSION['servicio_numero'] = $numero; + } + + if ($serviciomodel->guardarServicio($numero, $datosServicio, $cliente, $vendedor)) { + $_SESSION['flash_success'] = "Servicio guardado correctamente."; + + unset($_SESSION['servicio']['cliente']); + } else { + $_SESSION['flash_error'] = "Hubo un error al crear el servicio."; + } + + header("Location:index.php?opt=crear_servicio"); + exit; + + // SELECCIONAR CLIENTE PARA EL SERVICIO + + case 'seleccionar_cliente_servicio': + + require_once __DIR__ . '/../models/clienteModel.php'; + $clientemodel = new clienteModel(); + + // Se muestra la vista de selección de cliente + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $clientes = $clientemodel->listarClientes(); + + ob_start(); + require __DIR__ . '/../views/servicios/seleccionarClienteView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $idCliente = (int) ($_POST['id_cliente'] ?? 0); + + if ($idCliente <= 0) { + $_SESSION['flash_error'] = "No se seleccionó ningún cliente."; + header("Location: index.php?opt=seleccionar_cliente"); + exit; + } + + $cliente = $clientemodel->obtenerPorId($idCliente); + + if (!$cliente) { + $_SESSION['flash_error'] = "Cliente inválido."; + header("Location: index.php?opt=seleccionar_cliente"); + exit; + } + + $_SESSION['servicio']['cliente'] = [ + 'id' => $cliente['id'], + 'nombre' => $cliente['nombre'], + 'apellido' => $cliente['apellido'], + 'telefono' => $cliente['telefono'], + 'email' => $cliente['email'] + ]; + + $_SESSION['flash_success'] = "Cliente agregado correctamente."; + header("Location: index.php?opt=crear_servicio"); + exit; + } + + // ELIMINAR CLIENTE YA SELECCIONADO + + case 'eliminar_cliente_servicio': + + if (isset($_SESSION['servicio']['cliente'])) { + unset($_SESSION['servicio']['cliente']); + + $_SESSION['flash_success'] = "Cliente eliminado correctamente."; + } else { + $_SESSION['flash_error'] = "No había cliente seleccionado."; + } + + header("Location: index.php?opt=crear_servicio"); + exit; + + // GENERACION DE COMRPOBANTE DE SERVCIO PDF MEDIANTE USO DE LIBRERIA DOMPDF + + case 'generar_servicio_pdf': + + // Requiero datos de empresa y configuración + + require __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $datosEmpresa = $configuracionmodel->obtenerConfiguracion(); + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $datosEmpresa['cond_iva'] = $mapCondIVA[$datosEmpresa['cond_iva']]; + + $cliente = $_SESSION['servicio']['cliente'] ?? null; + $vendedor = $_SESSION['user'] ?? null; + + // DATOS FORM + + $descripcionEquipo = trim($_POST['descripcion_equipo']) ?? ''; + $contraseniaEquipo = trim($_POST['contrasenia_equipo']) ?? ''; + $cuenta = trim($_POST['cuenta']) ?? ''; + $contraseniaCuenta = trim($_POST['contrasenia_cuenta']) ?? ''; + $descripcionProblema = trim($_POST['descripcion_problema']) ?? ''; + $observacion = trim($_POST['observacion']) ?? ''; + + // VALIDACIONES + + $errores = []; + + if ($descripcionEquipo === '') { + $errores[] = "La descripción del equipo es obligatoria."; + } + + if ($descripcionProblema === '') { + $errores[] = "Debe describir el problema."; + } + + // Validacion de longitud + + if (strlen($descripcionEquipo) > 100) { + $errores[] = "La descripción del equipo es demasiado larga."; + } + + if (strlen($descripcionProblema) > 2000) { + $errores[] = "La descripción del problema es demasiada larga."; + } + + if (strlen($contraseniaEquipo) > 100) { + $errores[] = "Contraseña demasiado larga."; + } + + if (strlen($observacion) > 500) { + $errores[] = "La descripción de la observación es demasiada larga."; + } + + if (strlen($cuenta) > 100) { + $errores[] = "Cuenta demasiado larga."; + } + + if (strlen($contraseniaCuenta) > 100) { + $errores[] = "Contraseña de cuenta demasiado larga."; + } + + // Otras validaciones + + if (!empty($cuenta) && !filter_var($cuenta, FILTER_VALIDATE_EMAIL)) { + $errores[] = "El email de la cuenta no es válido."; + } + + if ($contraseniaEquipo !== '' && $descripcionEquipo === '') { + $errores[] = "Debe indicar el equipo si ingresa contraseña."; + } + + if ($contraseniaCuenta !== '' && $cuenta === '') { + $errores[] = "Debe indicar la cuenta si ingresa contraseña."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location:index.php?opt=crear_servicio"); + exit; + } + + // Guardado de datos en array + + $datosServicio = [ + 'descripcion_equipo' => $descripcionEquipo, + 'contrasenia_equipo' => $contraseniaEquipo, + 'cuenta' => $cuenta, + 'contrasenia_cuenta' => $contraseniaCuenta, + 'descripcion_problema' => $descripcionProblema, + 'observacion' => $observacion + ]; + + // NÚMERO DE SERVICIO (OBTENIDO MEDIANTE SECUENCIA PREVIEW) + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + $numero = $serviciomodel->getNumeroServicioPreview(); + $_SESSION['servicio_numero'] = $numero; + + $numeroServicio = '0001-' . str_pad($numero, 8, '0', STR_PAD_LEFT); + + // RENDER PDF + + ob_start(); + require __DIR__ . '/../pdf/servicios/servicioPDF.php'; + $html = ob_get_clean(); + + $cssPath = '/var/www/html/css/stylesPDF.css'; + $css = file_get_contents($cssPath); + + // Configuración de opciones + + $options = new Options(); + $options->set('isRemoteEnabled', true); // Permite cargar recursos externos (CSS, imágenes, etc.) + $options->set('chroot', '/var/www/html'); // Define directorios base para recursos locales + $options->set('defaultFont', 'DejaVu Sans'); + + // Creación de instancia Dompdf con las opciones seteadas + + $dompdf = new Dompdf($options); + + // Cargar el HTML que se va a convertir en PDF + + $dompdf->loadHtml(" + + $html + "); + + $dompdf->setPaper('A4', 'portrait'); // Define el tamanio de hoja y la orientación + $dompdf->render(); // Renderiza el PDF (procesa el HTML) + + // Enviar el PDF al browser + + $dompdf->stream( + 'Servicio N° ' . $numeroServicio . '.pdf', + ['Attachment' => true] // false = se muestra en el navegador | true = descarga directa + ); + + exit; + + // OBTENER LISTADO DE SERVICIOS + + case 'listar_servicios': + + // Requiero el modelo de servicio + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + $anioActual = date('Y'); + $anioSeleccionado = $_GET['anio'] ?? $anioActual; + + $servicios = $serviciomodel->listarServicios($anioSeleccionado); + + foreach ($servicios as $i => $s) { + + // Saneamiento de fecha (para poder ordenar correctamente el campo con dataTables) + + if (!empty($s['fecha_registro'])) { + + $dt = new DateTime($s['fecha_registro']); + + // Para ordenar + + $servicios[$i]['fecha_registro'] = $dt->format('Y-m-d'); + + // Para mostrar + + $servicios[$i]['fecha_formateada'] = $dt->format('d/m/Y'); + + } else { + $servicios[$i]['fecha_registro'] = '-'; + $servicios[$i]['fecha_formateada'] = '-'; + } + + if (!empty($s['fecha_terminado'])) { + + $dt = new DateTime($s['fecha_terminado']); + + // Para ordenar + + $servicios[$i]['fecha_terminado'] = $dt->format('Y-m-d'); + + // Para mostrar + + $servicios[$i]['fecha_formateada_term'] = $dt->format('d/m/Y'); + + } else { + $servicios[$i]['fecha_terminado'] = '-'; + $servicios[$i]['fecha_formateada_term'] = '-'; + } + + // Saneamiento de tipo de pago + + $servicios[$i]['estado'] = ucwords($servicios[$i]['estado']); + } + + ob_start(); + require __DIR__ . '/../views/servicios/listaServiciosView.php'; + return ob_get_clean(); + + // RESETEAR DATOS DE SERVICIO (al querer actualizar un servicio) (básicamente limpia todo para que los campos del form y la caja estén vacíos) + + case 'resetear_datos_servicio': + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + $idServicio = (int) ($_GET['id'] ?? 0); + + if ($idServicio <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=listar_servicios'); + exit; + } + + // Se resetean los campos + + $manoObra = 0; + $detalleRealizado = ''; + $total = 0; + + $_SESSION['servicio']['mano_obra'] = $manoObra; + $_SESSION['servicio']['detalle_realizado'] = $detalleRealizado; + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + // Se vacia la caja + + $cajamodel->vaciarCajaServicio(); + $insumos = $cajamodel->listarInsumos(); + + ob_start(); + require __DIR__ . '/../views/servicios/actualizarServicioView.php'; + return ob_get_clean(); + } + + // ACTUALIZACIÓN DEL SERVICIO + + case 'actualizar_servicio': + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $idServicio = (int) ($_GET['id'] ?? 0); + + if ($idServicio <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=listar_servicios'); + exit; + } + + // Validamos si el servicio se encuentra en estado 'pendiente' + + if (!($serviciomodel->getEstadoServicio($idServicio))) { + $_SESSION['flash_error'] = "El servicio no se encuentra en el estado correspondiente."; + header("Location: index.php?opt=listar_servicios"); + exit; + } + + $manoObra = isset($_SESSION['servicio']['mano_obra']) ? (float) $_SESSION['servicio']['mano_obra'] : 0; + $detalleRealizado = isset($_SESSION['servicio']['detalle_realizado']) ? trim($_SESSION['servicio']['detalle_realizado']) : ''; + + // Requiero modelo de caja + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + $insumos = $cajamodel->listarInsumos(); + + $total = $cajamodel->calcularPrecioTotalServicio() ?? 0; + + if (isset($_SESSION['servicio']['mano_obra'])) { + $total = $total + $_SESSION['servicio']['mano_obra']; + } + + ob_start(); + require __DIR__ . '/../views/servicios/actualizarServicioView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $idServicio = (int) ($_GET['id'] ?? 0); + $detalleRealizado = trim($_POST['detalle_realizado'] ?? ''); + $manoObra = (float) ($_POST['mano_obra'] ?? 0); + + // VALIDACIONES + + $errores = []; + + if ($idServicio <= 0 || $manoObra < 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + if ($detalleRealizado === '') { + $errores[] = "El detalle es obligatorio."; + } + + if (strlen($detalleRealizado) > 2000) { + $errores[] = "El detalle es demasiado largo."; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location:index.php?opt=actualizar_servicio&id=" . $idServicio); + exit; + } + + // Requiero modelo de caja + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + $insumos = $cajamodel->listarInsumos(); + $total = $cajamodel->calcularPrecioTotalServicio(); + + $totalFinal = $total + $manoObra; + + if ($totalFinal < 0) { + $_SESSION['flash_error'] = 'Total inválido.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + $datosServicio = [ + 'id_servicio' => $idServicio, + 'detalle_realizado' => $detalleRealizado, + 'mano_obra' => $manoObra, + 'total_final' => $totalFinal + ]; + + // REQUERIMOS EL MODELO SERVICIO Y EL HELPER DEL MAIL + + require_once __DIR__ . '/../models/servicioModel.php'; + require_once __DIR__ . '/../helpers/email_actualizar.php'; + + $serviciomodel = new servicioModel(); + + if ($serviciomodel->actualizarServicio($datosServicio, $insumos)) { + + $success = []; + + $success[] = "Servicio actualizado correctamente."; + + unset($_SESSION['servicio']['mano_obra']); + unset($_SESSION['servicio']['detalle_realizado']); + $cajamodel->vaciarCajaServicio(); + + // OBTENEMOS DATOS DEL SERVICIO (SERVICIO, INSUMO UTILIZADOS Y CLIENTE) + + $servicio = $serviciomodel->getDetalleServicio($idServicio); + $insumos = $serviciomodel->getDetalleInsumos($idServicio); + $cliente = $serviciomodel->getClienteServicio($idServicio); + + // LLAMO MÉTODO PARA ENVÍO DEL EMAIL + + $resultadoEnvioMail = enviarMailServicioFinalizado($servicio, $insumos, $cliente); + + // OBTENGO DATO DE CONFIGURACIÓN PARA DECIDIR QUE MENSAJE MOSTRAR + + $configuracionmodel = new configuracionModel(); + $datosConfig = $configuracionmodel->obtenerDatosEnvioMail(); + + if (((int) $datosConfig['mail_automatico'] === 1) && $resultadoEnvioMail) { + $success[] = "Email automático enviado correctamente."; + } + + if (!$resultadoEnvioMail) { + $success[] = "No fué posible enviar el email automático, por favor verifique manualmente."; + } + + $_SESSION['flash_success'] = implode('
', $success); + + } else { + $_SESSION['flash_error'] = $serviciomodel->getError() ?? 'Hubo un error al actualizar el servicio.'; + } + + header("Location:index.php?opt=listar_servicios"); + exit; + } + + // AGREGAR INSUMO AL SERVICIO (el post es atendido por CAJA) + + case 'agregar_insumo': + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + // Guardo datos en $_SESSION para mantener persistencia de los datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $_SESSION['servicio']['detalle_realizado'] = $_POST['detalle_realizado'] ?? ''; + $_SESSION['servicio']['mano_obra'] = (float) $_POST['mano_obra'] ?? 0; + } + + $idServicio = (int) ($_GET['id'] ?? 0); + + // Requerimos modelo de producto + + require_once __DIR__ . '/../models/productoModel.php'; + $productomodel = new productoModel(); + + $insumos = $productomodel->listarInsumosServicio(); + + foreach ($insumos as $j => $i) { + + if (!isset($i['id'])) { + unset($insumos[$j]); + continue; + } + + if ($i['stock'] === null || $i['stock'] === 0) { + $insumos[$j]['stock'] = '-'; + } + } + + ob_start(); + require __DIR__ . '/../views/servicios/agregarInsumoServicioView.php'; + return ob_get_clean(); + + // FINALIZACIÓN DE SERVICIO + + case 'finalizar_servicio': + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + $idServicio = (int) ($_POST['id'] ?? 0); + + // VALIDACIONES + + if ($idServicio <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + // REQUERIMOS EL MODELO SERVICIO Y EL HELPER DEL MAIL + + require_once __DIR__ . '/../models/servicioModel.php'; + require_once __DIR__ . '/../helpers/email_finalizado.php'; + + $serviciomodel = new servicioModel(); + + if ($serviciomodel->finalizarServicio($idServicio)) { + + $success = []; + + $success[] = "Servicio actualizado correctamente."; + + // OBTENEMOS DATOS DEL SERVICIO (SERVICIO, INSUMO UTILIZADOS Y CLIENTE) + + $servicio = $serviciomodel->getDetalleServicio($idServicio); + $insumos = $serviciomodel->getDetalleInsumos($idServicio); + $cliente = $serviciomodel->getClienteServicio($idServicio); + + // LLAMADA A MÉTODO PARA ENVÍO DE EMAIL + + $resultadoEnvioMail = enviarMailServicioFinalizado($servicio, $insumos, $cliente); + + // OBTENGO DATO DE CONFIGURACIÓN PARA DECIDIR QUE MENSAJE MOSTRAR + + $configuracionmodel = new configuracionModel(); + $datosConfig = $configuracionmodel->obtenerDatosEnvioMail(); + + if (((int) $datosConfig['mail_automatico'] === 1) && $resultadoEnvioMail) { + $success[] = "Email automático enviado correctamente."; + } + + if (!$resultadoEnvioMail) { + $success[] = "No fué posible enviar el email automático, por favor verifique manualmente."; + } + + $_SESSION['flash_success'] = implode('
', $success); + + } else { + $_SESSION['flash_error'] = $serviciomodel->getError() ?? 'Hubo un error al finalizar el servicio.'; + } + + header("Location:index.php?opt=listar_servicios"); + exit; + + // CANCELAR SERVICIO + + case 'cancelar_servicio': + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + $idServicio = (int) ($_POST['id'] ?? 0); + + if ($idServicio <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=listar_servicios'); + exit; + } + + // OBTENGO EL CARGO POR CANCELACIÓN DE SERVICIO (desde configuración) + + require_once __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $cargoCancelacion = $configuracionmodel->obtenerCargoCancelacion(); + + // Requiero modelo de servicio y llamo al método de cancelación + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + if ($serviciomodel->cancelarServicio($idServicio, $cargoCancelacion)) { + $_SESSION['flash_success'] = 'Servicio cancelado correctamente.'; + } else { + $_SESSION['flash_error'] = 'No se pudo cancelar el servicio.'; + } + + header("Location:index.php?opt=listar_servicios"); + exit; + + // VER EL DETALLE TOTAL DEL SERVICIO REALIZADO + + case 'detalle_servicio': + + $idServicio = (int) ($_GET['id'] ?? 0); + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + $datosServicio = $serviciomodel->getDetalleServicio($idServicio); + + if (empty($datosServicio)) { + $_SESSION['flash_error'] = "Hubo un error al cargar el detalle del servicio."; + header("Location:index.php?opt=listar_servicios"); + exit; + } + + // Saneamiento de datos + + if ($datosServicio['detalle_realizado'] === null) { + $datosServicio['detalle_realizado'] = ''; + } + + if ($datosServicio['mano_obra'] === null) { + $datosServicio['mano_obra'] = 0; + } + + if ($datosServicio['total'] === null) { + $datosServicio['total'] = 0; + } + + // Desencriptacion de contraseñas + + if ($datosServicio['contrasenia_equipo'] !== null) { + $datosServicio['contrasenia_equipo'] = decryptData($datosServicio['contrasenia_equipo']); + } + + if ($datosServicio['contrasenia_cuenta'] !== null) { + $datosServicio['contrasenia_cuenta'] = decryptData($datosServicio['contrasenia_cuenta']); + } + + ob_start(); + require_once __DIR__ . '/../views/servicios/detalleServicioView.php'; + return ob_get_clean(); + + // MOSTRAR EL DETALLE DE INSUMOS + + case 'detalle_insumos_servicio': + + $idServicio = (int) ($_GET['id'] ?? 0); + + require_once __DIR__ . '/../models/servicioModel.php'; + $serviciomodel = new servicioModel(); + + $servicio_detalle = $serviciomodel->getDetalleInsumos($idServicio); + + ob_start(); + require_once __DIR__ . '/../views/servicios/detalleInsumosServicioView.php'; + return ob_get_clean(); + + // ACTUALIZAR EL TOTAL AGREGANDO LA MANO DE OBRA + + case 'agregar_mano_obra': + + // Verifico si es admin + + if (!$esAdmin) { + header("Location: index.php?opt=inicio_operador"); + exit; + } + + $idServicio = (int) ($_GET['id'] ?? 0); + + // Obtengo datos + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $detalleRealizado = $_POST['detalle_realizado'] ?? ''; + $manoObra = (float) $_POST['mano_obra'] ?? 0; + } + + if ($idServicio <= 0 || $manoObra < 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + } + + $_SESSION['servicio']['detalle_realizado'] = $detalleRealizado; + $_SESSION['servicio']['mano_obra'] = $manoObra; + + $_SESSION['flash_success'] = 'Mano de obra agregada correctamente.'; + header('Location: index.php?opt=actualizar_servicio&id=' . $idServicio); + exit; + + // DEFAULT (404) + + default: + + ob_start(); + header("Location: index.php?opt=404View.php"); + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/usuarioController.php b/app/controllers/usuarioController.php new file mode 100644 index 0000000..188726d --- /dev/null +++ b/app/controllers/usuarioController.php @@ -0,0 +1,386 @@ +obtenerPorId($idEditar); + + if (!$usuario) { + $_SESSION['flash_error'] = "No se encontró el usuario."; + header("Location: index.php?opt=usuarios"); // Si no se encontró al usuario se redirige a la lista de usuarios + exit; + } + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + if ($esAdmin) { + $return = 'inicio_admin'; + } else { + $return = 'inicio_operador'; + } + + $_SESSION['return_opt'] = $_GET['from'] ?? "$return"; + $return = $_SESSION['return_opt']; + + ob_start(); + require __DIR__ . '/../views/usuarios/datosUsuarioView.php'; + return ob_get_clean(); + } + + // En caso de modificar datos de un usuario + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // Si no es admin no puede editar usuarios + + if (!$esAdmin) { + header("Location: index.php?opt=home_operador"); + exit; + } + + $nombre = trim($_POST['nombre'] ?? ''); + $apellido = trim($_POST['apellido'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $telefono = trim($_POST['telefono'] ?? ''); + $password = trim($_POST['password'] ?? ''); + $usuarioInput = $usuario['usuario'] ?? ''; + $rol = trim($_POST['rol']) ?? 'operador'; + + $errores = []; + + // Validaciones + + if ($nombre === '') { + $errores[] = "El nombre no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $nombre)) { + $errores[] = "El nombre debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } elseif (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($apellido === '') { + $errores[] = "El apellido no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $apellido)) { + $errores[] = "El apellido debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($apellido) < 2) { + $errores[] = "El apellido debe tener al menos 2 caracteres."; + } elseif (strlen($apellido) > 50) { + $errores[] = "El apellido no puede superar los 50 caracteres."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } elseif (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + // Password (si se ingresa) + + if (!empty($password)) { + if ($password === '') { + $errores[] = "La contraseña no puede estar vacía."; + } elseif (strpos($password, ' ') !== false) { + $errores[] = "La contraseña no puede contener espacios."; + } elseif (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$/', $password)) { + $errores[] = "La contraseña debe tener al menos una mayúscula, una minúscula y un número, y mínimo 6 caracteres."; + } + } + + if (!in_array($rol, ['admin', 'operador'], true)) { + $errores[] = "El rol es incorrecto."; + } + + // Un admin no puede modificar su rol + + if (($idEditar === $user['id']) && ($rol === 'operador')) { + $_SESSION['flash_error'] = 'No puede modificar su propio rol'; + header("Location: index.php?opt=datos_usuario&id=$idEditar&from=usuarios"); + exit; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); // Se guarda en $_SESSION el error + header("Location: index.php?opt=datos_usuario&id=$idEditar&from=usuarios"); + exit; + } + + // Verificación de existencia de usuario + + if ($usuariomodel->verificarExistencia([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'usuario' => $usuarioInput + ], $idEditar)) { + $_SESSION['flash_error'] = "El usuario ya existe o el mail ya está registrado."; + + header("Location: index.php?opt=datos_usuario&id=$idEditar&from=usuarios"); + exit; + } + + $guardarData = [ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'telefono' => $telefono, + 'usuario' => $usuarioInput, + 'rol' => $rol + ]; + + if (!empty($password)) { + $guardarData['password'] = password_hash($password, PASSWORD_DEFAULT); // Hasheo de contraseña + } + + // Actualiza los datos del usuario + + if ($usuariomodel->actualizarUsuario($idEditar, $guardarData)) { + $_SESSION['flash_success'] = 'Datos actualizados correctamente.'; // Se guarda en $_SESSION el success + } else { + $_SESSION['flash_error'] = 'No se pudo actualizar los datos.'; + } + + if ($idEditar === $user['id']) { + $_SESSION['user'] = $usuariomodel->obtenerPorId($idEditar); + } + + header("Location: index.php?opt=usuarios"); + exit; + } + + // LISTAR USUARIOS + + case 'usuarios': + + if (!$esAdmin) { + header("Location: index.php?opt=home_operador"); + exit; + } + + // Utilizamos el modelo para obtener la lista completa de usuarios + + $usuarios = $usuariomodel->listarUsuarios(); + + ob_start(); + require __DIR__ . '/../views/usuarios/listaUsuariosView.php'; + return ob_get_clean(); + + // CREACIÓN DE NUEVO USUARIO + + case 'crear_usuario': + + if (!$esAdmin) { + header("Location: index.php?opt=home_operador"); + exit; + } + + // Se requiere la view del form + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + ob_start(); + require __DIR__ . '/../views/usuarios/crearUsuarioView.php'; + return ob_get_clean(); + } + + // Datos recibidos del POST del formulario + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $nombre = trim($_POST['nombre'] ?? ''); + $apellido = trim($_POST['apellido'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $telefono = trim($_POST['telefono'] ?? ''); + $usuarioInput = trim($_POST['usuario'] ?? ''); + $password = trim($_POST['password'] ?? ''); + $rol = trim($_POST['rol']) ?? 'operador'; + + $errores = []; + + // Validaciones + + if ($nombre === '') { + $errores[] = "El nombre no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $nombre)) { + $errores[] = "El nombre debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($nombre) < 2) { + $errores[] = "El nombre debe tener al menos 2 caracteres."; + } elseif (strlen($nombre) > 50) { + $errores[] = "El nombre no puede superar los 50 caracteres."; + } + + if ($apellido === '') { + $errores[] = "El apellido no puede estar vacío."; + } elseif (!preg_match("/^[A-ZÁÉÍÓÚÑ][a-záéíóúñ]+(?: [A-ZÁÉÍÓÚÑ][a-záéíóúñ]+)*$/", $apellido)) { + $errores[] = "El apellido debe comenzar con mayúscula y solo contener letras y espacios."; + } elseif (strlen($apellido) < 2) { + $errores[] = "El apellido debe tener al menos 2 caracteres."; + } elseif (strlen($apellido) > 50) { + $errores[] = "El apellido no puede superar los 50 caracteres."; + } + + if ($email === '') { + $errores[] = "El email no puede estar vacío."; + } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errores[] = "Formato de email inválido."; + } elseif (strlen($email) > 100) { + $errores[] = "El email no puede superar los 100 caracteres."; + } + + if ($telefono === '') { + $errores[] = "El teléfono no puede estar vacío."; + } else if (!preg_match('/^[0-9]{10}$/', $telefono)) { + $errores[] = "El teléfono debe tener 10 dígitos."; + } + + if ($usuarioInput === '') { + $errores[] = "El usuario no puede estar vacío."; + } elseif (!preg_match('/^[a-zA-Z0-9._]+$/', $usuarioInput)) { + $errores[] = "El usuario solo puede contener letras, números, puntos y guiones bajos."; + } elseif (strlen($usuarioInput) < 5) { + $errores[] = "El usuario debe tener al menos 5 caracteres."; + } elseif (strlen($usuarioInput) > 30) { + $errores[] = "El usuario no puede superar los 30 caracteres."; + } + + if ($password === '') { + $errores[] = "La contraseña no puede estar vacía."; + } elseif (strpos($password, ' ') !== false) { + $errores[] = "La contraseña no puede contener espacios."; + } elseif (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$/', $password)) { + $errores[] = "La contraseña debe tener al menos una mayúscula, una minúscula y un número, y mínimo 6 caracteres."; + } + + if (!in_array($rol, ['admin', 'operador'], true)) { + $rol = 'operador'; + } + + if (!empty($errores)) { + $_SESSION['flash_error'] = implode('
', $errores); + header("Location: index.php?opt=crear_usuario"); + exit; + } + + // Verificación de existencia de usuario + + if ($usuariomodel->verificarExistencia([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'usuario' => $usuarioInput + ])) { + $_SESSION['flash_error'] = "El usuario ya existe o el mail ya está registrado."; + + header("Location: index.php?opt=crear_usuario"); + exit; + } + + // Se llama a la función para crear el usuario + + if ($usuariomodel->crearUsuario([ + 'nombre' => $nombre, + 'apellido' => $apellido, + 'email' => $email, + 'telefono' => $telefono, + 'usuario' => $usuarioInput, + 'password' => password_hash($password, PASSWORD_DEFAULT), + 'rol' => $rol + ])) { + $_SESSION['flash_success'] = 'Usuario creado correctamente'; + } else { + $_SESSION['flash_error'] = 'Ocurrió un error al crear el usuario.'; + } + + header("Location: index.php?opt=usuarios"); + exit; + } + + // ELIMINAR USUARIO (BAJA LÓGICA) + + case 'eliminar_usuario': + + $idEliminar = $_POST['id'] ?? 0; + + if ($idEliminar <= 0) { + header("Location: index.php?opt=usuarios"); + exit; + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + if ($idEliminar == $user['id']) { + $_SESSION['flash_error'] = "No puede eliminar su usuario."; + header("Location:index.php?opt=usuarios"); + exit; + } + + if ($usuariomodel->eliminarUsuario($idEliminar)) { + $_SESSION['flash_success'] = "Usuario eliminado correctamente."; + } else { + $_SESSION['flash_error'] = "Ocurrió un error al eliminar el usuario."; + } + + header("Location:index.php?opt=usuarios"); + exit; + } + + // DEFAULT (404) + + default: + ob_start(); + require __DIR__ . '/../views/404View.php'; + return ob_get_clean(); +} \ No newline at end of file diff --git a/app/controllers/ventaController.php b/app/controllers/ventaController.php new file mode 100644 index 0000000..66e8204 --- /dev/null +++ b/app/controllers/ventaController.php @@ -0,0 +1,944 @@ +listarProductos(); + + if (isset($_SESSION['venta']['cliente'])) { + $cliente = $_SESSION['venta']['cliente']; + } else { + $cliente = ''; + } + + // Obtengo cálculos preliminares + + $subtotal = $cajamodel->calcularSubtotal() ?? 0; + $descuento = $cajamodel->calcularDescuentoTotal() ?? 0; + $total = $cajamodel->calcularPrecioTotal() ?? 0; + + ob_start(); + require __DIR__ . '/../views/ventas/crearVentaView.php'; + return ob_get_clean(); + } + + // AGREGAR PRODUCTO A LA VENTA (el post es atendido por CAJA) + + case 'agregar_producto': + + // Requiero el productoModel + + require_once __DIR__ . '/../models/productoModel.php'; + $productomodel = new productoModel(); + + $productos = $productomodel->listarProductosVenta(); + + foreach ($productos as $i => $p) { + + if (!isset($p['id'])) { + unset($productos[$i]); + continue; + } + + if ($p['stock'] === null || $p['stock'] === 0) { + $productos[$i]['stock'] = '-'; + } + + $productos[$i]['descuento_total'] = number_format($p['descuento_total'], 2, '.', '') . ' %'; + } + + ob_start(); + require __DIR__ . '/../views/ventas/agregarProductoView.php'; + return ob_get_clean(); + + // SELECCIONAR CLIENTE PARA LA VENTA + + case 'seleccionar_cliente': + + // Requiero el modelo de cliente + + require_once __DIR__ . '/../models/clienteModel.php'; + $clientemodel = new clienteModel(); + + // Se muestra la vista de selección de cliente + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $clientes = $clientemodel->listarClientes(); + + ob_start(); + require __DIR__ . '/../views/ventas/seleccionarClienteView.php'; + return ob_get_clean(); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + $idCliente = (int) ($_POST['id_cliente'] ?? 0); + + if ($idCliente <= 0) { + $_SESSION['flash_error'] = "No se seleccionó ningún cliente."; + header("Location: index.php?opt=seleccionar_cliente"); + exit; + } + + $cliente = $clientemodel->obtenerPorId($idCliente); + + if (!$cliente) { + $_SESSION['flash_error'] = "Cliente inválido."; + header("Location: index.php?opt=seleccionar_cliente"); + exit; + } + + $_SESSION['venta']['cliente'] = [ + 'id' => $cliente['id'], + 'nombre' => $cliente['nombre'], + 'apellido' => $cliente['apellido'], + 'telefono' => $cliente['telefono'], + 'email' => $cliente['email'], + 'cuit' => $cliente['cuit'], + 'cond_iva' => $cliente['cond_iva'] + ]; + + $_SESSION['venta_extra']['cond_iva'] = $cliente['cond_iva']; + $_SESSION['venta_extra']['cuit'] = $cliente['cuit']; + + $_SESSION['flash_success'] = "Cliente agregado correctamente."; + header("Location: index.php?opt=crear_venta"); + exit; + } + + // ELIMINAR CLIENTE YA SELECCIONADO + + case 'eliminar_cliente': + + if (isset($_SESSION['venta']['cliente'])) { + unset($_SESSION['venta']['cliente']); + + // También se eliminan datos de la venta guardados desde vista resumen + + if (isset($_SESSION['venta_extra'])) { + unset($_SESSION['venta_extra']); + unset($_SESSION['venta_final']); + } + + $_SESSION['flash_success'] = "Cliente eliminado correctamente."; + } else { + $_SESSION['flash_error'] = "No había cliente seleccionado."; + } + + header("Location: index.php?opt=crear_venta"); + exit; + + // GENERAR VISTA RESUMEN DE VENTA + + case 'resumen_venta': + + $cliente = ($_SESSION['venta']['cliente'] ?? null); + + $_SESSION['venta_extra']['cond_iva'] = $_SESSION['venta_extra']['cond_iva'] ?? $cliente['cond_iva'] ?? 'consumidor_final'; + $_SESSION['venta_extra']['cuit'] = $_SESSION['venta_extra']['cuit'] ?? $cliente['cuit'] ?? '00000000000'; + + // Requiero el modelo de caja + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + $productos = $cajamodel->listarProductos(); + $descuento = $cajamodel->calcularDescuentoTotal(); + + if (!$cliente || empty($productos)) { + $_SESSION['flash_error'] = "Debe seleccionar el cliente y al menos un producto."; + header("Location: index.php?opt=crear_venta"); + exit; + } + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + require_once __DIR__ . '/../models/ventaModel.php'; + + $ventamodel = new ventaModel(); + + // 1. Subtotal SIEMPRE se recalcula (pueden haber cambiado productos/cantidades) + + $subtotal = $cajamodel->calcularSubtotal() ?? 0; + + // 2. Datos extra guardados en sesión (si existen) + + $descuentoIngresado = isset($_SESSION['venta_extra']['descuento']) ? (float) $_SESSION['venta_extra']['descuento'] : 0; + + $recargoIngresado = isset($_SESSION['venta_extra']['recargo']) ? (float) $_SESSION['venta_extra']['recargo'] : 0; + + // 3. Recalculo siempre con subtotal actual + + $datosProvisorios = $ventamodel->actualizarDatosProvisorio($descuentoIngresado, $recargoIngresado, $subtotal, $descuento); + + $descuento = $datosProvisorios['descuento'] ?? 0; + $recargo = $datosProvisorios['recargo'] ?? 0; + $total = $datosProvisorios['total'] ?? 0; + + // Guardo en SESSION + + $_SESSION['venta_final'] = [ + 'descuento' => $descuento, + 'recargo' => $recargo, + 'total' => $total + ]; + + ob_start(); + require __DIR__ . '/../views/ventas/resumenVentaView.php'; + return ob_get_clean(); + } + + // GUARDAR DATOS DE VENTA (permite volver hacia atrás en el proceso y no perder datos) + + case 'guardar_datos_venta': + + // Validaciones de reglas de negocio + + // Condición IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + $condIva = $_POST['cond_iva'] ?? 'consumidor_final'; + + if (!in_array($condIva, $condIvaPermitidas, true)) { + + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // CUIT + + $cuit = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + $cuit = trim($cuit); + + if ($cuit === '') { + + $_SESSION['flash_error'] = 'Debe ingresar un CUIT.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $_SESSION['flash_error'] = 'El CUIT debe contener 11 dígitos.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Forma de pago + tipo de pago + + $reglasPago = [ + 'efectivo' => ['pago_unico'], + 'transferencia' => ['pago_unico'], + 'echeq' => ['pago_unico'], + 'debito' => ['pago_unico'], + 'credito' => ['pago_unico', '3_cuotas', '6_cuotas', '12_cuotas'], + ]; + + $formaPago = $_POST['forma_pago'] ?? 'efectivo'; + $tipoPago = $_POST['tipo_pago'] ?? 'pago_unico'; + + if (!isset($reglasPago[$formaPago]) || !in_array($tipoPago, $reglasPago[$formaPago], true)) { + $_SESSION['flash_error'] = 'Combinación de forma y tipo de pago inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Validación de descuento y recargo + + $descuentoVenta = isset($_POST['descuento']) ? (float) $_POST['descuento'] : 0; + $recargoVenta = isset($_POST['recargo']) ? (float) $_POST['recargo'] : 0; + + if ($descuentoVenta < 0 || $recargoVenta > 100) { + $_SESSION['flash_error'] = 'El descuento debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if ($descuentoVenta < 0 || $recargoVenta > 100) { + $_SESSION['flash_error'] = 'El recargo debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Guardado de datos + + // Guardo datos extra de la venta para ser reutilizados en la vista + + $datosExtraVenta = [ + 'cond_iva' => $condIva, + 'cuit' => $cuit, + 'forma_pago' => $formaPago, + 'tipo_pago' => $tipoPago, + 'descuento' => $descuentoVenta, + 'recargo' => $recargoVenta + ]; + + // También los persisto en sesión para mantener estado + + $_SESSION['venta_extra'] = $datosExtraVenta; + + // Datos para la vista + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + $cliente = ($_SESSION['venta']['cliente'] ?? null); + $productos = $cajamodel->listarProductos(); + + $vendedor = $_SESSION['user'] ?? null; + + // VALIDACIONES BASE + + if (!$cliente || empty($productos)) { + $_SESSION['flash_error'] = 'Datos incompletos para generar la venta.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + /* Subtotal que no contiene descuentos. + (Al ingresar descuento o recargo, lo guardo en un array separado, para no sobreescribir datos) */ + + $subtotal = $cajamodel->calcularSubtotal() ?? 0; + $descuento = $cajamodel->calcularDescuentoTotal() ?? 0; + + // Validación del subtotal + + if ($subtotal <= 0) { + $_SESSION['flash_error'] = 'La venta no contiene productos válidos.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + // Llamamos a la función que devuelve los datos actualizados en un array nuevo + + require_once __DIR__ . '/../models/ventaModel.php'; + + $ventamodel = new ventaModel(); + + $datosProvisorios = $ventamodel->actualizarDatosProvisorio($descuentoVenta, $recargoVenta, $subtotal, $descuento); + + $recargo = $datosProvisorios['recargo'] ?? 0; + $descuento = $datosProvisorios['descuento'] ?? 0; + $total = $datosProvisorios['total'] ?? 0; + + $_SESSION['venta_final'] = [ + 'recargo' => $recargo, + 'descuento' => $descuento, + 'total' => $total + ]; + + $_SESSION['flash_success'] = "Datos guardados de manera provisoria."; + header("Location: index.php?opt=resumen_venta"); + exit; + + // GENERACION DE VENTA PDF MEDIANTE USO DE LIBRERIA DOMPDF + + case 'generar_venta_pdf': + + // OBTENEMOS LA VISTA + + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + + $numeroVenta = $_SESSION['ultima_venta_id']; + + ob_start(); + require __DIR__ . '/../views/ventas/descargarComprobante.php'; + return ob_get_clean(); + } + + // GENERACIÓN DEL PDF + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // Requiero datos de empresa y configuración + + require __DIR__ . '/../models/configuracionModel.php'; + $configuracionmodel = new configuracionModel(); + + $datosEmpresa = $configuracionmodel->obtenerConfiguracion(); + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $datosEmpresa['cond_iva'] = $mapCondIVA[$datosEmpresa['cond_iva']]; + + // Obtenemos array con datos necesarios, que fueron guardados durante la confirmación de venta en $_SESSION + + $datosVenta = $_SESSION['venta_pdf'] ?? null; + + if (!$datosVenta) { + $_SESSION['flash_error'] = 'No existen datos para generar el comprobante.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + // Asignamos datos a variables y validamos + + $cliente = $datosVenta['cliente']; + $vendedor = $datosVenta['vendedor']; + $productos = $datosVenta['productos']; + + // VALIDACIONES BASE + + if (!$cliente || empty($productos)) { + $_SESSION['flash_error'] = 'Datos incompletos para generar la venta.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=crear_pedido'); + exit; + } + + // Asignamos datos a variables y validamos + + $condIVA = $datosVenta['cond_iva']; + $cuit = $datosVenta['cuit']; + + $formaPago = $datosVenta['forma_pago']; + $tipoPago = $datosVenta['tipo_pago']; + + $descuentoVenta = $datosVenta['descuentoVenta']; + $recargoVenta = $datosVenta['recargoVenta']; + + // VALIDACIÓN CONDICIÓN IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + if (!in_array($condIVA, $condIvaPermitidas, true)) { + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Validación de CUIT + + $cuit = trim($cuit); + + if (!preg_match('/^\d{10,11}$/', $cuit)) { + $_SESSION['flash_error'] = 'CUIT inválido.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // VALIDACIÓN FORMA + TIPO DE PAGO + + $reglasPago = [ + 'efectivo' => ['pago_unico'], + 'transferencia' => ['pago_unico'], + 'echeq' => ['pago_unico'], + 'debito' => ['pago_unico'], + 'credito' => ['pago_unico', '3_cuotas', '6_cuotas', '12_cuotas'], + ]; + + if (!isset($reglasPago[$formaPago]) || !in_array($tipoPago, $reglasPago[$formaPago], true)) { + $_SESSION['flash_error'] = 'Combinación de forma y tipo de pago inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // VALIDACIÓN DESCUENTO / RECARGO + + if ($descuentoVenta < 0 || $descuentoVenta > 100) { + $_SESSION['flash_error'] = 'El descuento debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if ($recargoVenta < 0 || $recargoVenta > 100) { + $_SESSION['flash_error'] = 'El recargo debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + $subtotal = $datosVenta['subtotal']; + + if ($subtotal <= 0) { + $_SESSION['flash_error'] = 'La venta no contiene productos válidos.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Asignamos datos a variables y validamos + + $descuento = $datosVenta['descuento']; + $recargo = $datosVenta['recargo']; + $total = $datosVenta['total']; + + if ($total <= 0) { + $_SESSION['flash_error'] = 'Total inválido.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // MAPEOS PARA PDF + + $mapCondIVA = [ + 'consumidor_final' => 'Consumidor Final', + 'responsable_inscripto' => 'Responsable Inscripto', + 'monotributo' => 'Monotributo', + ]; + + $mapFormaPago = [ + 'efectivo'=> 'Efectivo', + 'transferencia' => 'Transferencia', + 'debito' => 'Débito', + 'credito' => 'Crédito', + 'echeq' => 'Echeq', + ]; + + $mapTipoPago = [ + 'pago_unico' => 'Pago Único', + '3_cuotas' => '3 Cuotas', + '6_cuotas' => '6 Cuotas', + '12_cuotas' => '12 Cuotas', + ]; + + $condIVALabel = $mapCondIVA[$condIVA]; + $formaPagoLabel = $mapFormaPago[$formaPago]; + $tipoPagoLabel = $mapTipoPago[$tipoPago]; + + // CÁLCULO DE CUOTAS + + if ($tipoPago !== 'pago_unico') { + $cuota = (int) explode('_', $tipoPago)[0]; + $valorCuota = $total / $cuota; + } + + // Obtenemos el número de venta que se obtuvo desde la base de datos al momento de guardar la venta + + $numero = $_SESSION['ultima_venta_id'] ?? null; + + if (!$numero) { + $_SESSION['flash_error'] = 'No se encontró el número de venta.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + // Variable para mostrar el código en la vista + + $numeroVenta = '0001-' . str_pad($numero, 8, '0', STR_PAD_LEFT); + + // RENDER PDF + + ob_start(); + require __DIR__ . '/../pdf/ventas/ventaPDF.php'; + $html = ob_get_clean(); + + $cssPath = '/var/www/html/css/stylesPDF.css'; + $css = file_get_contents($cssPath); + + // Configuración de opciones + + $options = new Options(); + $options->set('isRemoteEnabled', true); // Permite cargar recursos externos (CSS, imágenes, etc.) + $options->set('chroot', '/var/www/html'); // Define directorios base para recursos locales + $options->set('defaultFont', 'DejaVu Sans'); + + // Creación de instancia Dompdf con las opciones seteadas + + $dompdf = new Dompdf($options); + + // Cargar el HTML que se va a convertir en PDF + + $dompdf->loadHtml(" + + $html + "); + + $dompdf->setPaper('A4', 'portrait'); // Define el tamanio de hoja y la orientación + $dompdf->render(); // Renderiza el PDF (procesa el HTML) + + // Enviar el PDF al browser + + $dompdf->stream( + 'Venta N° ' . $numeroVenta . '.pdf', + ['Attachment' => true] // false = se muestra en el navegador | true = descarga directa + ); + + exit; + } + + // CONTINUAR SIN DESCARGAR EL COMPROBANTE PDF + + case 'continuar_sin_comprobante': + + // Liberamos $_SESSION + + unset($_SESSION['venta_pdf']); + unset($_SESSION['ultima_venta_id']); + + $_SESSION['flash_success'] = 'Venta finalizada correctamente.'; + + header('Location: index.php?opt=crear_venta'); + exit; + + // CONFIRMAR VENTA + + case 'confirmar_venta': + + // Validaciones de regla de negocio + + // Condición IVA + + $condIvaPermitidas = [ + 'consumidor_final', + 'responsable_inscripto', + 'monotributo' + ]; + + $condIva = $_POST['cond_iva'] ?? 'consumidor_final'; + + if (!in_array($condIva, $condIvaPermitidas, true)) { + + $_SESSION['flash_error'] = 'Condición de IVA inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // CUIT + + $cuit = ($_POST['cuit_1'] ?? '') . ($_POST['cuit_2'] ?? '') . ($_POST['cuit_3'] ?? ''); + + $cuit = trim($cuit); + + if ($cuit === '') { + + $_SESSION['flash_error'] = 'Debe ingresar un CUIT.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if (!preg_match('/^\d{11}$/', $cuit)) { + + $_SESSION['flash_error'] = 'El CUIT debe contener 11 dígitos.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Forma de pago + tipo de pago + + $reglasPago = [ + 'efectivo' => ['pago_unico'], + 'transferencia' => ['pago_unico'], + 'echeq' => ['pago_unico'], + 'debito' => ['pago_unico'], + 'credito' => ['pago_unico', '3_cuotas', '6_cuotas', '12_cuotas'], + ]; + + $formaPago = $_POST['forma_pago'] ?? 'efectivo'; + $tipoPago = $_POST['tipo_pago'] ?? 'pago_unico'; + + if (!isset($reglasPago[$formaPago]) || !in_array($tipoPago, $reglasPago[$formaPago], true)) { + $_SESSION['flash_error'] = 'Combinación de forma y tipo de pago inválida.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Validación de descuento y recargo + + $descuentoVenta = isset($_POST['descuento']) ? (float) $_POST['descuento'] : 0; + $recargoVenta = isset($_POST['recargo']) ? (float) $_POST['recargo'] : 0; + + if ($descuentoVenta < 0 || $descuentoVenta > 100) { + $_SESSION['flash_error'] = 'El descuento debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if ($recargoVenta < 0 || $recargoVenta > 100) { + $_SESSION['flash_error'] = 'El recargo debe estar entre 0 y 100%.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Guardado de datos de la venta + + $datosExtraVenta = [ + 'forma_pago' => $formaPago, + 'tipo_pago' => $tipoPago, + 'descuento' => $descuentoVenta, + 'recargo' => $recargoVenta, + ]; + + // Requiero los modelos necesarios + + require_once __DIR__ . '/../models/cajaModel.php'; + $cajamodel = new cajaModel(); + + require_once __DIR__ . '/../models/clienteModel.php'; + $clientemodel = new clienteModel(); + + require_once __DIR__ . '/../models/ventaModel.php'; + $ventamodel = new ventaModel(); + + $idCliente = $_SESSION['venta']['cliente']['id'] ?? null; + + if ($idCliente === null) { + $_SESSION['flash_error'] = 'No seleccionó un cliente.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + $vendedor = $_SESSION['user'] ?? null; + $cliente = $clientemodel->obtenerPorId($idCliente); + + if (!$cliente) { + $_SESSION['flash_error'] = 'No se pudo identificar el cliente.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + if (!$vendedor) { + $_SESSION['flash_error'] = 'No se pudo identificar el usuario.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + $productosFinales = $cajamodel->listarProductos(); + + if (empty($productosFinales)) { + $_SESSION['flash_error'] = 'No hay productos en la venta.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + foreach ($productosFinales as $p) { + if (empty($p['id_producto']) || empty($p['cantidad']) || $p['cantidad'] <= 0) { + $_SESSION['flash_error'] = 'Venta con productos inválidos.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + } + + /* Subtotal que no contiene descuentos. + (Al ingresar descuento o recargo, lo guardo en un array separado, para no sobreescribir datos) */ + + $subtotal = $cajamodel->calcularSubtotal() ?? 0; + $descuento = $cajamodel->calcularDescuentoTotal() ?? 0; + + // Validación del subtotal + + if ($subtotal <= 0) { + $_SESSION['flash_error'] = 'La venta no contiene productos válidos.'; + header('Location: index.php?opt=crear_venta'); + exit; + } + + // Llamamos a la función que devuelve los datos actualizados en un array nuevo + + $datosProvisorios = $ventamodel->actualizarDatosProvisorio($descuentoVenta, $recargoVenta, $subtotal, $descuento); + + if ($datosProvisorios['total'] <= 0) { + $_SESSION['flash_error'] = 'El total de la venta es inválido.'; + header('Location: index.php?opt=resumen_venta'); + exit; + } + + // Obtenemos el ID de venta desde la base de datos + + $idVenta = $ventamodel->guardarVenta($productosFinales, $vendedor, $cliente, $datosExtraVenta, $datosProvisorios); + + // Verificamos si se obtuvo el ID desde la base o si ocurrió un error + + if ($idVenta !== false) { + + // Guardamos en $_SESSION los datos necesarios para la generación del PDF + + $_SESSION['ultima_venta_id'] = $idVenta; + + $_SESSION['venta_pdf'] = [ + + 'cliente' => $cliente, + 'vendedor' => $vendedor, + 'productos' => $productosFinales, + + 'cond_iva' => $condIva, + 'cuit' => $cuit, + + 'forma_pago' => $formaPago, + 'tipo_pago' => $tipoPago, + + 'descuentoVenta' => $descuentoVenta, + 'recargoVenta' => $recargoVenta, + + 'descuento' => $datosProvisorios['descuento'], + 'recargo' => $datosProvisorios['recargo'], + + 'subtotal' => $subtotal, + 'total' => $datosProvisorios['total'] + ]; + + // Liberamos + + $cajamodel->vaciarCajaVenta(); + unset($_SESSION['venta']['cliente']); + unset($_SESSION['venta_extra']); + unset($_SESSION['venta_final']); + unset($_SESSION['prov_venta']); + + $_SESSION['flash_success'] = "Venta guardada correctamente."; + + } else { + $_SESSION['flash_error'] = $ventamodel->getError() ?? 'No se pudo guardar la venta.'; + } + + header("Location: index.php?opt=generar_venta_pdf"); + exit; + + // LISTAR VENTAS + + case 'listar_ventas': + + // Requiero el modelo de venta + + require_once __DIR__ . '/../models/ventaModel.php'; + $ventamodel = new ventaModel(); + + $anioActual = date('Y'); + $anioSeleccionado = $_GET['anio'] ?? $anioActual; + + $ventas = $ventamodel->listarVentas($anioSeleccionado); + + foreach ($ventas as $i => $v) { + + // Saneamiento de fecha (para poder ordenar correctamente el campo con dataTables) + + if (!empty($v['fecha'])) { + + $dt = new DateTime($v['fecha']); + + // Para ordenar + + $ventas[$i]['fecha'] = $dt->format('Y-m-d'); + + // Para mostrar + + $ventas[$i]['fecha_formateada'] = $dt->format('d/m/Y'); + + } else { + $ventas[$i]['fecha'] = '-'; + $ventas[$i]['fecha_formateada'] = '-'; + } + + // Mapeos de forma y tipo de pago + + $mapFormaPago = [ + 'efectivo'=> 'Efectivo', + 'transferencia' => 'Transferencia', + 'debito' => 'Débito', + 'credito' => 'Crédito', + 'echeq' => 'Echeq', + ]; + + $mapTipoPago = [ + 'pago_unico' => 'Pago Único', + '3_cuotas' => '3 Cuotas', + '6_cuotas' => '6 Cuotas', + '12_cuotas' => '12 Cuotas', + ]; + + // Saneamiento de forma de pago + + $ventas[$i]['forma_pago'] = $mapFormaPago[$ventas[$i]['forma_pago']]; + + // Saneamiento de tipo de pago + + $ventas[$i]['tipo_pago'] = $mapTipoPago[$ventas[$i]['tipo_pago']]; + } + + ob_start(); + require __DIR__ . '/../views/ventas/listaVentasView.php'; + return ob_get_clean(); + + // MOSTRAR DETALLE DE PRODUCTOS DE UNA VENTA + + case 'detalle_venta': + + $idVenta = (int) ($_GET['id'] ?? 0); + + if ($idVenta <= 0) { + $_SESSION['flash_error'] = 'Datos inválidos.'; + header('Location: index.php?opt=listar_ventas'); + exit; + } + + require_once __DIR__ . '/../models/ventaModel.php'; + $ventamodel = new ventaModel(); + + $venta_detalle = $ventamodel->getDetalleVenta($idVenta); + + ob_start(); + require __DIR__ . '/../views/ventas/ventaDetalleView.php'; + return ob_get_clean(); + + // DEFAULT (404) + + default: + + ob_start(); + header("Location: index.php?opt=404View.php"); + return ob_get_clean(); +} \ No newline at end of file