1ra Versión
This commit is contained in:
@@ -0,0 +1,618 @@
|
|||||||
|
# Sistema de Gestión - Complejo Cap1tan ⚽🎾
|
||||||
|
|
||||||
|
Sistema integral de gestión para un complejo deportivo que incluye reserva de canchas, gestión de cantina, inventario, proveedores y reportes.
|
||||||
|
|
||||||
|
## 📋 Índice
|
||||||
|
|
||||||
|
- [Descripción General](#descripción-general)
|
||||||
|
- [Características Principales](#características-principales)
|
||||||
|
- [Requisitos del Sistema](#requisitos-del-sistema)
|
||||||
|
- [Levantar el Proyecto](#levantar-el-proyecto)
|
||||||
|
- [Instalación](#instalación)
|
||||||
|
- [Configuración](#configuración)
|
||||||
|
- [Estructura del Proyecto](#estructura-del-proyecto)
|
||||||
|
- [Módulos Principales](#módulos-principales)
|
||||||
|
- [Base de Datos](#base-de-datos)
|
||||||
|
- [Roles y Permisos](#roles-y-permisos)
|
||||||
|
- [Uso del Sistema](#uso-del-sistema)
|
||||||
|
- [API Endpoints](#api-endpoints)
|
||||||
|
- [Tecnologías Utilizadas](#tecnologías-utilizadas)
|
||||||
|
|
||||||
|
## 📖 Descripción General
|
||||||
|
|
||||||
|
Sistema web desarrollado para la gestión integral del **Complejo Cap1tan**, permitiendo:
|
||||||
|
- Administración de usuarios con diferentes roles
|
||||||
|
- Reserva y gestión de canchas
|
||||||
|
- Control de inventario de la cantina
|
||||||
|
- Gestión de proveedores y órdenes de compra
|
||||||
|
- Reportes de ventas y ganancias
|
||||||
|
- Sistema de cupones de descuento
|
||||||
|
- Notificaciones vía WhatsApp
|
||||||
|
|
||||||
|
## ✨ Características Principales
|
||||||
|
|
||||||
|
### Gestión de Usuarios
|
||||||
|
- ✅ Sistema de autenticación seguro
|
||||||
|
- ✅ Tres roles: Administrador General, Administrador Cancha y Administrador Cantina
|
||||||
|
- ✅ Perfiles de usuarios con datos personales
|
||||||
|
- ✅ Edición rápida desde el modal de perfil del usuario conectado
|
||||||
|
- ✅ Gestión completa desde la interfaz administrativa
|
||||||
|
|
||||||
|
### Reservas de Canchas
|
||||||
|
- ✅ Reserva de turnos en canchas
|
||||||
|
- ✅ Visualización de disponibilidad
|
||||||
|
- ✅ Sistema de cupones para clientes frecuentes
|
||||||
|
- ✅ Registro de faltas y asistencias
|
||||||
|
- ✅ Descuentos automáticos por cupones
|
||||||
|
- ✅ Verificación del cliente por WhatsApp antes de confirmar la reserva
|
||||||
|
|
||||||
|
### Gestión de Cantina
|
||||||
|
- ✅ Catálogo de productos
|
||||||
|
- ✅ Control de stock
|
||||||
|
- ✅ Registro de ventas en tiempo real
|
||||||
|
- ✅ Alertas visuales de stock bajo
|
||||||
|
- ✅ Cálculo automático de ganancias
|
||||||
|
- ✅ Actualización dinámica del stock sin recargar completamente la página
|
||||||
|
|
||||||
|
### Gestión de Proveedores
|
||||||
|
- ✅ Registro de proveedores
|
||||||
|
- ✅ Órdenes de compra
|
||||||
|
- ✅ Aprobación de órdenes por el administrador
|
||||||
|
- ✅ Asociación de productos por proveedor
|
||||||
|
- ✅ Historial de compras
|
||||||
|
- ✅ Envío de órdenes por WhatsApp para comunicación rápida
|
||||||
|
|
||||||
|
### Reportes y Análisis
|
||||||
|
- ✅ Reporte de ventas
|
||||||
|
- ✅ Cálculo de ganancia neta
|
||||||
|
- ✅ Seguimiento de gastos
|
||||||
|
- ✅ Resumen por período
|
||||||
|
- ✅ **Generación de reportes por período: Diario, Semanal, Mensual y Anual**
|
||||||
|
- ✅ **Exportación de reportes a PDF y Excel con formato profesional**
|
||||||
|
- ✅ Correcciones y mejoras en la generación visual de reportes
|
||||||
|
|
||||||
|
## 💻 Requisitos del Sistema
|
||||||
|
|
||||||
|
### Software Necesario
|
||||||
|
- **PHP** >= 7.4
|
||||||
|
- **MySQL/MariaDB** >= 5.7
|
||||||
|
- **Apache** (incluido en XAMPP)
|
||||||
|
- **Python** 3.8+ (para envío de mensajes WhatsApp)
|
||||||
|
- **Navegador** moderno (Chrome, Firefox, Edge)
|
||||||
|
|
||||||
|
### Dependencias de Python
|
||||||
|
```bash
|
||||||
|
selenium
|
||||||
|
webdriver-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
### Servidor Local
|
||||||
|
- **XAMPP** o equivalente (Apache + MySQL + PHP)
|
||||||
|
|
||||||
|
## ▶️ Levantar el Proyecto
|
||||||
|
|
||||||
|
|
||||||
|
Para usar el sistema localmente, basta con dejar el proyecto en la carpeta de XAMPP, iniciar Apache y MySQL, importar la base de datos y abrir la interfaz desde el navegador.
|
||||||
|
|
||||||
|
### Pasos
|
||||||
|
1. Ubica el proyecto en:
|
||||||
|
```bash
|
||||||
|
C:\xampp\htdocs\TP-Taller\ProyectoTaller
|
||||||
|
```
|
||||||
|
2. Inicia **Apache** y **MySQL** desde XAMPP.
|
||||||
|
3. Crea la base de datos `complejo_cap1tan` y carga los scripts SQL indicados en la sección de instalación.
|
||||||
|
4. Ajusta las credenciales en [php/conexion.php](php/conexion.php) si es necesario.
|
||||||
|
5. Abre la aplicación en:
|
||||||
|
```text
|
||||||
|
http://localhost/TP-Taller/ProyectoTaller/html/Login.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uso inicial
|
||||||
|
Una vez abierta la aplicación, podés comenzar a gestionar reservas, ventas, productos, proveedores, órdenes y reportes desde el panel principal.
|
||||||
|
|
||||||
|
## 📦 Instalación
|
||||||
|
|
||||||
|
### 1. Requisitos Previos
|
||||||
|
Asegúrate de tener **XAMPP** instalado y ejecutando:
|
||||||
|
- Apache en puerto 80
|
||||||
|
- MySQL en puerto 3306
|
||||||
|
|
||||||
|
### 2. Clonar/Descargar Proyecto
|
||||||
|
```bash
|
||||||
|
# Ubicar el proyecto en la carpeta htdocs de XAMPP
|
||||||
|
c:\xampp\htdocs\TP-Taller\ProyectoTaller
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Crear Base de Datos
|
||||||
|
```bash
|
||||||
|
# Acceder a phpMyAdmin
|
||||||
|
# http://localhost/phpmyadmin
|
||||||
|
|
||||||
|
# Crear base de datos: complejo_cap1tan
|
||||||
|
CREATE DATABASE complejo_cap1tan;
|
||||||
|
USE complejo_cap1tan;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Importar Tablas
|
||||||
|
1. En phpMyAdmin, selecciona la base de datos `complejo_cap1tan`
|
||||||
|
2. Ve a la pestaña **Importar**
|
||||||
|
3. Selecciona el archivo `sql/Tablas.sql`
|
||||||
|
4. Haz clic en **Ejecutar**
|
||||||
|
|
||||||
|
### 5. Importar Datos Iniciales (Opcional)
|
||||||
|
```bash
|
||||||
|
# Para cargar datos de prueba
|
||||||
|
sql/Insertar\ Datos.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Importar Triggers
|
||||||
|
```bash
|
||||||
|
# Para funcionalidades avanzadas
|
||||||
|
sql/Triggers.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Configuración
|
||||||
|
|
||||||
|
### Conexión a Base de Datos
|
||||||
|
|
||||||
|
Editar archivo `php/conexion.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$host = 'localhost'; // Servidor MySQL
|
||||||
|
$user = 'root'; // Usuario MySQL
|
||||||
|
$password = 'admin'; // Contraseña MySQL
|
||||||
|
$db = 'complejo_cap1tan'; // Nombre de la base de datos
|
||||||
|
|
||||||
|
$conexion = new mysqli($host, $user, $password, $db);
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
die('Error de conexión: ' . $conexion->connect_error);
|
||||||
|
}
|
||||||
|
$conexion->set_charset('utf8');
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuración de WhatsApp
|
||||||
|
|
||||||
|
Para habilitar notificaciones vía WhatsApp:
|
||||||
|
|
||||||
|
1. Instalar dependencias Python:
|
||||||
|
```bash
|
||||||
|
pip install selenium webdriver-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Descargar ChromeDriver compatible con tu versión de Chrome
|
||||||
|
|
||||||
|
3. Editar `python/Enviar_WSP.py` con rutas correctas
|
||||||
|
|
||||||
|
## 📁 Estructura del Proyecto
|
||||||
|
|
||||||
|
```
|
||||||
|
ProyectoTaller/
|
||||||
|
├── backups/ # Copias de seguridad
|
||||||
|
│ └── *.zip # Archivos de backup automático
|
||||||
|
├── css/ # Estilos CSS
|
||||||
|
│ ├── Cantina.css # Estilos del módulo de cantina
|
||||||
|
│ ├── Login.css # Estilos de login
|
||||||
|
│ └── Reserva_Clientes.css # Estilos de reservas
|
||||||
|
├── html/ # Archivos HTML
|
||||||
|
│ ├── Cantina.html # Página principal (cantina)
|
||||||
|
│ ├── Login.html # Página de inicio de sesión
|
||||||
|
│ └── Reserva_Clientes.html # Página de reservas
|
||||||
|
├── js/ # Archivos JavaScript
|
||||||
|
│ ├── Gestion_Canchas.js # Lógica de gestión de canchas
|
||||||
|
│ ├── Gestion_Usuarios.js # Lógica de gestión de usuarios
|
||||||
|
│ ├── Orden_Compra.js # Lógica de órdenes de compra
|
||||||
|
│ ├── Reservar_Turno.js # Lógica de reservas
|
||||||
|
│ ├── Resumen_Ventas.js # Lógica de resumen de ventas
|
||||||
|
│ ├── Tabla_Gastos.js # Lógica de tabla de gastos
|
||||||
|
│ ├── Tabla_Productos.js # Lógica de tabla de productos
|
||||||
|
│ ├── Tabla_Proveedor.js # Lógica de tabla de proveedores
|
||||||
|
│ ├── Tabla_Reservas.js # Lógica de tabla de reservas
|
||||||
|
│ └── Tabla_Ventas.js # Lógica de tabla de ventas
|
||||||
|
├── php/ # Archivos backend PHP
|
||||||
|
│ ├── conexion.php # Conexión a base de datos
|
||||||
|
│ ├── Login.php # Autenticación
|
||||||
|
│ ├── Logout.php # Cierre de sesión
|
||||||
|
│ ├── Session_Info.php # Información de sesión
|
||||||
|
│ │
|
||||||
|
│ ├── *Usuarios*
|
||||||
|
│ ├── Guardar_Usuario.php # Crear usuario
|
||||||
|
│ ├── Obtener_Usuarios.php # Listar usuarios
|
||||||
|
│ ├── Actualizar_Usuario.php # Editar usuario
|
||||||
|
│ ├── Eliminar_Usuario.php # Eliminar usuario
|
||||||
|
│ ├── Actualizar_Perfil.php # Actualizar perfil propio
|
||||||
|
│ │
|
||||||
|
│ ├── *Canchas*
|
||||||
|
│ ├── Obtener_Canchas.php # Listar todas las canchas
|
||||||
|
│ ├── Obtener_Canchas_Numeros.php # Obtener números de canchas
|
||||||
|
│ ├── Actualizar_Cancha.php # Actualizar datos de cancha
|
||||||
|
│ ├── Obtener_Disponibilidad_Cancha.php
|
||||||
|
│ ├── Obtener_Disponibilidad_Quincho.php
|
||||||
|
│ ├── Obtener_Horarios.php # Obtener horarios disponibles
|
||||||
|
│ ├── Actualizar_Horarios.php # Editar horarios
|
||||||
|
│ │
|
||||||
|
│ ├── *Reservas*
|
||||||
|
│ ├── Guardar_Reserva.php # Crear reserva
|
||||||
|
│ ├── Obtener_Reservas.php # Listar reservas
|
||||||
|
│ ├── Actualizar_Estado_Reserva.php # Cambiar estado de reserva
|
||||||
|
│ ├── Tabla_Reservas.js # Frontend de reservas
|
||||||
|
│ │
|
||||||
|
│ ├── *Productos (Cantina)*
|
||||||
|
│ ├── Agregar_Producto.php # Crear producto
|
||||||
|
│ ├── Obtener_Productos.php # Listar productos
|
||||||
|
│ ├── Editar_Producto.php # Actualizar producto
|
||||||
|
│ ├── Eliminar_Producto.php # Eliminar producto
|
||||||
|
│ ├── Buscar_Producto_Ventas.php
|
||||||
|
│ ├── Buscar_Producto_Orden.php
|
||||||
|
│ │
|
||||||
|
│ ├── *Ventas*
|
||||||
|
│ ├── Registrar_Venta.php # Registrar venta en cantina
|
||||||
|
│ ├── Obtener_Ventas.php # Obtener histórico de ventas
|
||||||
|
│ ├── Ganacia_Neta.php # Calcular ganancias
|
||||||
|
│ │
|
||||||
|
│ ├── *Proveedores*
|
||||||
|
│ ├── Guardar_Proveedor.php # Crear proveedor
|
||||||
|
│ ├── Obtener_Proveedores.php # Listar proveedores
|
||||||
|
│ ├── Editar_Proveedor.php # Actualizar proveedor
|
||||||
|
│ ├── Eliminar_Proveedor.php # Eliminar proveedor
|
||||||
|
│ ├── Buscar_Proveedor.php # Buscar proveedor
|
||||||
|
│ ├── Buscar_Proveedor_Editar.php
|
||||||
|
│ │
|
||||||
|
│ ├── *Órdenes de Compra*
|
||||||
|
│ ├── Registrar_Orden_Compra.php # Crear orden
|
||||||
|
│ ├── Obtener_Ordenes_Compra.php # Listar órdenes
|
||||||
|
│ ├── Actualizar_Orden_Compra.php # Cambiar estado de orden
|
||||||
|
│ ├── Obtener_Productos_Orden.php # Productos de una orden
|
||||||
|
│ ├── Agregar_Producto_Proveedor.php
|
||||||
|
│ ├── Eliminar_Producto_Proveedor.php
|
||||||
|
│ ├── Obtener_Productos_Proveedor.php
|
||||||
|
│ │
|
||||||
|
│ ├── *Gastos*
|
||||||
|
│ ├── Registrar_Gastos.php # Registrar gasto
|
||||||
|
│ ├── Obtener_Gastos.php # Listar gastos
|
||||||
|
│ ├── Editar_Gasto.php # Editar gasto
|
||||||
|
│ ├── Eliminar_Gasto.php # Eliminar gasto
|
||||||
|
│ │
|
||||||
|
│ ├── *Cupones*
|
||||||
|
│ ├── Obtener_Cupon.php # Obtener datos de cupón
|
||||||
|
│ │
|
||||||
|
│ ├── *Utilidades*
|
||||||
|
│ ├── Enviar_Codigo_WSP.php # Enviar código de verificación
|
||||||
|
│ ├── Obtener_Reporte.php # Generar reportes
|
||||||
|
│ ├── backup.php # Hacer backup de BD
|
||||||
|
│ ├── Test_Conexion.php # Prueba de conexión
|
||||||
|
│
|
||||||
|
├── python/ # Scripts Python
|
||||||
|
│ └── Enviar_WSP.py # Envío automático de mensajes WhatsApp
|
||||||
|
├── sql/ # Scripts SQL
|
||||||
|
│ ├── Tablas.sql # Estructura de base de datos
|
||||||
|
│ ├── Insertar\ Datos.sql # Datos iniciales de prueba
|
||||||
|
│ ├── Consultas.sql # Consultas útiles
|
||||||
|
│ └── Triggers.sql # Triggers de base de datos
|
||||||
|
└── README.md # Este archivo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗄️ Módulos Principales
|
||||||
|
|
||||||
|
### 1. **Autenticación**
|
||||||
|
- `php/Login.php`: Valida credenciales de usuario
|
||||||
|
- `php/Logout.php`: Cierra sesión actual
|
||||||
|
- Sistema de sesiones con roles
|
||||||
|
|
||||||
|
### 2. **Gestión de Usuarios**
|
||||||
|
- Crear, editar, listar y eliminar usuarios
|
||||||
|
- Asignación de roles (Admin, Cancha, Cantina)
|
||||||
|
- Perfiles con datos personales
|
||||||
|
- Edición rápida del perfil desde el panel principal
|
||||||
|
|
||||||
|
### 3. **Gestión de Canchas**
|
||||||
|
- Gestión de canchas (CRUD)
|
||||||
|
- Gestión de horarios disponibles
|
||||||
|
- Seguimiento de disponibilidad
|
||||||
|
|
||||||
|
### 4. **Sistema de Reservas**
|
||||||
|
- Reserva de turnos en canchas
|
||||||
|
- Sistema de cupones para descuentos
|
||||||
|
- Registro de asistencia/faltas
|
||||||
|
- Cálculo automático de descuentos
|
||||||
|
- Confirmación con verificación por WhatsApp
|
||||||
|
|
||||||
|
### 5. **Cantina**
|
||||||
|
- Gestión de productos (CRUD)
|
||||||
|
- Control de inventario
|
||||||
|
- Registro de ventas
|
||||||
|
- Alertas de stock bajo
|
||||||
|
|
||||||
|
### 6. **Proveedores y Compras**
|
||||||
|
- Registro de proveedores
|
||||||
|
- Órdenes de compra con aprobación
|
||||||
|
- Asociación de productos por proveedor
|
||||||
|
- Envío de órdenes y seguimiento desde la interfaz
|
||||||
|
|
||||||
|
### 7. **Reportes**
|
||||||
|
- Resumen de ventas por período
|
||||||
|
- Cálculo de ganancias netas
|
||||||
|
- Tracking de gastos
|
||||||
|
- Exportación a PDF y Excel
|
||||||
|
- Visualización optimizada para reportes diarios, semanales, mensuales y anuales
|
||||||
|
|
||||||
|
### 8. **Seguridad y Operaciones Avanzadas**
|
||||||
|
- Verificación de clientes por WhatsApp
|
||||||
|
- Gestión del perfil del usuario autenticado
|
||||||
|
- Copias de seguridad automáticas de la base de datos
|
||||||
|
- Alertas de productos con stock bajo
|
||||||
|
- Mejoras en el flujo de validación y control de operaciones
|
||||||
|
|
||||||
|
## 📊 Base de Datos
|
||||||
|
|
||||||
|
### Tablas Principales
|
||||||
|
|
||||||
|
| Tabla | Descripción |
|
||||||
|
|-------|-----------|
|
||||||
|
| `usuarios` | Usuarios del sistema con roles |
|
||||||
|
| `clientes` | Clientes que reservan canchas |
|
||||||
|
| `canchas` | Canchas disponibles para reserva |
|
||||||
|
| `reservas` | Histórico de reservas |
|
||||||
|
| `productos` | Productos de la cantina |
|
||||||
|
| `ventas` | Registro de ventas |
|
||||||
|
| `ventas_productos` | Detalle de productos en cada venta |
|
||||||
|
| `proveedores` | Proveedores de productos |
|
||||||
|
| `ordenes_compra` | Órdenes de compra a proveedores |
|
||||||
|
| `ordenes_productos` | Detalle de productos en órdenes |
|
||||||
|
| `gastos` | Gastos operativos |
|
||||||
|
| `cupones` | Cupones de descuento |
|
||||||
|
| `alertas_stock` | Alertas de stock bajo |
|
||||||
|
|
||||||
|
### Roles de Base de Datos
|
||||||
|
- `Admin`: Acceso total a todo el sistema
|
||||||
|
- `Cancha`: Gestión de reservas y canchas
|
||||||
|
- `Cantina`: Gestión de productos y ventas
|
||||||
|
|
||||||
|
## 👥 Roles y Permisos
|
||||||
|
|
||||||
|
### Administrador
|
||||||
|
- ✅ Gestión completa de usuarios
|
||||||
|
- ✅ Gestión de canchas y horarios
|
||||||
|
- ✅ Acceso a reportes
|
||||||
|
- ✅ Gestión de proveedores
|
||||||
|
- ✅ Backup de base de datos
|
||||||
|
|
||||||
|
### Cancha
|
||||||
|
- ✅ Gestionar reservas
|
||||||
|
- ✅ Ver disponibilidad de canchas
|
||||||
|
- ✅ Consultar historial de reservas
|
||||||
|
- ❌ Gestión de usuarios
|
||||||
|
- ❌ Reportes financieros
|
||||||
|
|
||||||
|
### Cantina
|
||||||
|
- ✅ Gestionar productos
|
||||||
|
- ✅ Registrar ventas
|
||||||
|
- ✅ Ver resumen de ventas
|
||||||
|
- ✅ Gestionar proveedores
|
||||||
|
- ✅ Crear órdenes de compra
|
||||||
|
- ❌ Gestión de usuarios
|
||||||
|
- ❌ Gestión de reservas
|
||||||
|
|
||||||
|
## 🚀 Uso del Sistema
|
||||||
|
|
||||||
|
### 1. Acceso al Sistema
|
||||||
|
```
|
||||||
|
URL: http://localhost/TP-Taller/ProyectoTaller/html/Login.html
|
||||||
|
Usuario: admin
|
||||||
|
Contraseña: admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Crear un Usuario
|
||||||
|
1. Inicia sesión como Admin
|
||||||
|
2. Ve a Gestión de Usuarios
|
||||||
|
3. Haz clic en "Nuevo Usuario"
|
||||||
|
4. Completa el formulario con:
|
||||||
|
- Usuario (nombre de login)
|
||||||
|
- Contraseña
|
||||||
|
- Nombre y Apellido
|
||||||
|
- DNI
|
||||||
|
- Email y Teléfono
|
||||||
|
- Rol
|
||||||
|
|
||||||
|
### 3. Registrar una Reserva
|
||||||
|
1. Ve a Reservas > Nueva Reserva
|
||||||
|
2. Selecciona la cancha
|
||||||
|
3. Elige la fecha y hora disponible
|
||||||
|
4. Si el cliente tiene cupón, selecciona el descuento
|
||||||
|
5. El sistema solicita verificación por WhatsApp del cliente
|
||||||
|
6. Completa la verificación e ingresa el código recibido
|
||||||
|
7. Confirma la reserva
|
||||||
|
|
||||||
|
### 4. Registrar una Venta
|
||||||
|
1. Ve a Cantina > Nueva Venta
|
||||||
|
2. Busca y agrega productos (el sistema alertará si el stock es bajo)
|
||||||
|
3. El sistema calcula automáticamente el total
|
||||||
|
4. Registra la venta
|
||||||
|
|
||||||
|
### 5. Crear Orden de Compra
|
||||||
|
1. Ve a Proveedores > Nueva Orden
|
||||||
|
2. Selecciona proveedor
|
||||||
|
3. Agrega productos
|
||||||
|
4. Envía para aprobación
|
||||||
|
5. (Opcional) Envía la orden por WhatsApp desde el sistema
|
||||||
|
6. Admin aprueba o rechaza la orden
|
||||||
|
|
||||||
|
### 6. Generar Reportes
|
||||||
|
1. Ve a Reportes > Generar Reportes
|
||||||
|
2. Selecciona el tipo de reporte:
|
||||||
|
- **Diario**: Control de operación y cierre de caja
|
||||||
|
- **Semanal**: Flujo de caja y rendimiento
|
||||||
|
- **Mensual**: Rentabilidad y estado de resultados
|
||||||
|
- **Anual**: Balance general y visión estratégica
|
||||||
|
3. Visualiza el reporte en pantalla
|
||||||
|
4. Exporta a **PDF** o **Excel** según necesites
|
||||||
|
|
||||||
|
## 🔌 API Endpoints
|
||||||
|
|
||||||
|
Todos los endpoints devuelven JSON y utilizan `GET` o `POST`.
|
||||||
|
|
||||||
|
### Usuarios
|
||||||
|
```
|
||||||
|
POST /php/Guardar_Usuario.php - Crear usuario
|
||||||
|
GET /php/Obtener_Usuarios.php - Listar usuarios
|
||||||
|
POST /php/Actualizar_Usuario.php - Editar usuario
|
||||||
|
POST /php/Eliminar_Usuario.php - Eliminar usuario
|
||||||
|
POST /php/Actualizar_Perfil.php - Actualizar perfil propio
|
||||||
|
GET /php/Session_Info.php - Obtener datos de sesión actual
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autenticación
|
||||||
|
```
|
||||||
|
POST /php/Login.php - Iniciar sesión
|
||||||
|
GET /php/Logout.php - Cerrar sesión
|
||||||
|
```
|
||||||
|
|
||||||
|
### Canchas
|
||||||
|
```
|
||||||
|
GET /php/Obtener_Canchas.php - Listar canchas
|
||||||
|
GET /php/Obtener_Canchas_Numeros.php - Obtener números
|
||||||
|
POST /php/Actualizar_Cancha.php - Editar cancha
|
||||||
|
GET /php/Obtener_Disponibilidad_Cancha.php
|
||||||
|
GET /php/Obtener_Horarios.php - Listar horarios
|
||||||
|
POST /php/Actualizar_Horarios.php - Editar horarios
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reservas
|
||||||
|
```
|
||||||
|
POST /php/Guardar_Reserva.php - Crear reserva
|
||||||
|
GET /php/Obtener_Reservas.php - Listar reservas
|
||||||
|
POST /php/Actualizar_Estado_Reserva.php - Cambiar estado
|
||||||
|
GET /php/Obtener_Cupon.php - Obtener estado del cupón
|
||||||
|
POST /php/Enviar_Codigo_WSP.php - Enviar código de verificación WhatsApp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Productos
|
||||||
|
```
|
||||||
|
POST /php/Agregar_Producto.php - Crear producto
|
||||||
|
GET /php/Obtener_Productos.php - Listar productos
|
||||||
|
POST /php/Editar_Producto.php - Editar producto
|
||||||
|
POST /php/Eliminar_Producto.php - Eliminar producto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ventas
|
||||||
|
```
|
||||||
|
POST /php/Registrar_Venta.php - Registrar venta
|
||||||
|
GET /php/Obtener_Ventas.php - Listar ventas
|
||||||
|
GET /php/Ganacia_Neta.php - Calcular ganancias
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proveedores
|
||||||
|
```
|
||||||
|
POST /php/Guardar_Proveedor.php - Crear proveedor
|
||||||
|
GET /php/Obtener_Proveedores.php - Listar proveedores
|
||||||
|
POST /php/Editar_Proveedor.php - Editar proveedor
|
||||||
|
POST /php/Eliminar_Proveedor.php - Eliminar proveedor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Órdenes de Compra
|
||||||
|
```
|
||||||
|
POST /php/Registrar_Orden_Compra.php - Crear orden
|
||||||
|
GET /php/Obtener_Ordenes_Compra.php - Listar órdenes
|
||||||
|
POST /php/Actualizar_Orden_Compra.php - Cambiar estado (aprobar/rechazar)
|
||||||
|
GET /php/Obtener_Productos_Orden.php - Productos de orden
|
||||||
|
POST /php/Agregar_Producto_Proveedor.php - Asociar producto a proveedor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gastos
|
||||||
|
```
|
||||||
|
POST /php/Registrar_Gastos.php - Registrar gasto
|
||||||
|
GET /php/Obtener_Gastos.php - Listar gastos
|
||||||
|
POST /php/Editar_Gasto.php - Editar gasto
|
||||||
|
POST /php/Eliminar_Gasto.php - Eliminar gasto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reportes y Operaciones
|
||||||
|
```
|
||||||
|
GET /php/Obtener_Reporte.php - Generar reportes (diario, semanal, mensual, anual)
|
||||||
|
GET /php/Ganacia_Neta.php - Ganancias por período
|
||||||
|
POST /php/backup.php - Crear backup de base de datos
|
||||||
|
GET /php/Test_Conexion.php - Probar conexión a base de datos
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Tecnologías Utilizadas
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **PHP 7.4+** - Lenguaje servidor
|
||||||
|
- **MySQL/MariaDB** - Base de datos relacional
|
||||||
|
- **MySQLi** - Conexión a base de datos
|
||||||
|
- **Prepared Statements** - Protección contra SQL injection
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **HTML5** - Estructura
|
||||||
|
- **CSS3** - Estilos
|
||||||
|
- **JavaScript (Vanilla)** - Interactividad
|
||||||
|
- **AJAX** - Comunicación asíncrona
|
||||||
|
|
||||||
|
### Utilidades
|
||||||
|
- **Python 3.8+** - Scripts de automatización
|
||||||
|
- **Selenium** - Automatización de WhatsApp Web
|
||||||
|
- **XAMPP** - Stack local (Apache, MySQL, PHP)
|
||||||
|
|
||||||
|
## 🔒 Seguridad
|
||||||
|
|
||||||
|
- Autenticación con sesiones
|
||||||
|
- Prepared Statements para prevenir SQL injection
|
||||||
|
- Validación de entrada en servidor
|
||||||
|
- Control de roles y permisos
|
||||||
|
|
||||||
|
## 📝 Mantenimiento
|
||||||
|
|
||||||
|
### Backup Automático
|
||||||
|
```bash
|
||||||
|
# Ejecutar backup manual
|
||||||
|
php/backup.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Los backups se guardan en la carpeta `backups/` con timestamp.
|
||||||
|
|
||||||
|
### Prueba de Conexión
|
||||||
|
```bash
|
||||||
|
# Verificar conexión a base de datos
|
||||||
|
http://localhost/TP-Taller/ProyectoTaller/php/Test_Conexion.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Error: "Error de conexión"
|
||||||
|
- Verificar que MySQL esté corriendo
|
||||||
|
- Revisar credenciales en `php/conexion.php`
|
||||||
|
- Verificar que la base de datos existe
|
||||||
|
|
||||||
|
### Error: "Sesión expirada"
|
||||||
|
- El usuario fue desconectado por inactividad
|
||||||
|
- Volver a iniciar sesión
|
||||||
|
|
||||||
|
### Productos no se visualizan
|
||||||
|
- Verificar que hay productos en la base de datos
|
||||||
|
- Revisar permisos de usuario
|
||||||
|
- Inspeccionar la consola de navegador para errores
|
||||||
|
|
||||||
|
### WhatsApp no envía mensajes
|
||||||
|
- Verificar instalación de Python y Selenium
|
||||||
|
- Verificar ruta a ChromeDriver
|
||||||
|
- Revisar formato de número telefónico
|
||||||
|
|
||||||
|
## 📱 Integración WhatsApp
|
||||||
|
|
||||||
|
El sistema puede enviar notificaciones vía WhatsApp:
|
||||||
|
- Códigos de verificación
|
||||||
|
- Confirmaciones de reserva
|
||||||
|
- Recordatorios
|
||||||
|
|
||||||
|
**Nota:** Requiere ChromeDriver y sesión activa de WhatsApp Web.
|
||||||
|
|
||||||
|
## 📄 Licencia
|
||||||
|
|
||||||
|
Proyecto privado para uso interno del Complejo Cap1tan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización:** Julio 2026
|
||||||
|
**Versión:** 1.0
|
||||||
+1164
File diff suppressed because it is too large
Load Diff
+326
@@ -0,0 +1,326 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #7a897a, #000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 40px 35px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
box-shadow: 0 6px 20px rgb(36 255 91 / 40%);
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #1e7d1e;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formularios */
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
border: 1px solid rgba(255,255,255,0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 55px;
|
||||||
|
border-radius: 35px;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box input {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box input::placeholder {
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box:focus-within {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 0 8px rgba(30,125,30,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recordarme y link */
|
||||||
|
.remember-password {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-password a {
|
||||||
|
color: #d9f2d9;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-password a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #1e7d1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botón */
|
||||||
|
.btnI {
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #145214;
|
||||||
|
color: white;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnI:hover {
|
||||||
|
background-color: #1e7d1e;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Registro */
|
||||||
|
.register {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register a {
|
||||||
|
color: #1e7d1e;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 5px;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilos del Modal de Recuperación */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border: 2px solid rgba(30, 125, 30, 0.5);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px 40px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: 0 6px 20px rgb(36 255 91 / 40%);
|
||||||
|
position: relative;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateY(-50px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 20px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover {
|
||||||
|
color: #1e7d1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: #1e7d1e;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion p {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion input {
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
outline: none;
|
||||||
|
height: 55px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion input::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion input:focus {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 0 8px rgba(30, 125, 30, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contenedor de botones para paso 2 */
|
||||||
|
.paso-recuperacion .botones-grupo {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paso-recuperacion .botones-grupo button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnRecuperar {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #145214;
|
||||||
|
color: white;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
height: 55px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnRecuperar:hover:not(:disabled) {
|
||||||
|
background-color: #1e7d1e;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnRecuperar:disabled {
|
||||||
|
background-color: #0a5a0a;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnVolver {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
height: 55px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnVolver:hover {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
color: #1e7d1e;
|
||||||
|
background-color: rgba(30, 125, 30, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mensaje {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mensaje.success {
|
||||||
|
background-color: rgba(30, 125, 30, 0.3);
|
||||||
|
color: #90ee90;
|
||||||
|
border: 1px solid rgba(30, 125, 30, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mensaje.error {
|
||||||
|
background-color: rgba(255, 0, 0, 0.2);
|
||||||
|
color: #ff6b6b;
|
||||||
|
border: 1px solid rgba(255, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,814 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #7a897a, #000);
|
||||||
|
color: #f0f0f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(180deg, #145214, #0b3d0b);
|
||||||
|
padding: 1rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #d9f2d9;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #b8d9b8;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contenedor principal */
|
||||||
|
.contenedor-principal {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secciones */
|
||||||
|
.seccion {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid rgba(30, 125, 30, 0.2);
|
||||||
|
animation: fadeIn 0.4s ease;
|
||||||
|
background-color: #181818;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seccion h2,
|
||||||
|
.seccion h3 {
|
||||||
|
color: #d9f2d9;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-left: 5px solid #1e7d1e;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seccion h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
border-bottom: 2px solid rgba(30, 125, 30, 0.3);
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seccion h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botones de tipo de cancha */
|
||||||
|
.botones-cancha {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo {
|
||||||
|
padding: 2rem;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo .icono {
|
||||||
|
font-size: 3rem;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo .nombre {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo:hover {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
background: rgba(30, 125, 30, 0.15);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 6px 20px rgba(30, 125, 30, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo.activo {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 8px 25px rgba(30, 125, 30, 0.6);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendario de días */
|
||||||
|
.dias-disponibles {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia div:first-child {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #b8d9b8;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia div:last-child {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia:hover {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
background: rgba(30, 125, 30, 0.15);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia.activo {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 6px 20px rgba(30, 125, 30, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dia.deshabilitado {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: rgba(150, 42, 42, 0.15);
|
||||||
|
border-color: rgba(150, 42, 42, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid de horarios */
|
||||||
|
#grid-horarios {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horario-slot {
|
||||||
|
padding: 1.2rem;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
white-space: pre-line;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horario-slot:hover:not(.ocupado) {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 8px 25px rgba(30, 125, 30, 0.8);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horario-slot.ocupado
|
||||||
|
.horario-slot.no-disponible {
|
||||||
|
background: rgba(150, 42, 42, 0.25);
|
||||||
|
border-color: rgba(150, 42, 42, 0.5);
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horario-slot.activo {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 8px 25px rgba(30, 125, 30, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botones de selección de número de cancha */
|
||||||
|
.botones-canchas-numeros {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-numero-cancha {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-numero-cancha:hover:not(:disabled) {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
background: rgba(30, 125, 30, 0.15);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-numero-cancha.activo {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 6px 20px rgba(30, 125, 30, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-numero-cancha:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: rgba(150, 42, 42, 0.15);
|
||||||
|
border-color: rgba(150, 42, 42, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resumen de información */
|
||||||
|
.resumen-info {
|
||||||
|
background: rgba(30, 125, 30, 0.1);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-left: 5px solid #1e7d1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.8rem 0;
|
||||||
|
border-bottom: 1px solid rgba(30, 125, 30, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item.total {
|
||||||
|
background: rgba(30, 125, 30, 0.15);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .label {
|
||||||
|
color: #d9f2d9;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .valor {
|
||||||
|
color: #f0f0f0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item.total .valor {
|
||||||
|
color: #d9f2d9;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Opciones adicionales */
|
||||||
|
.opciones-adicionales {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opciones-adicionales h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container:hover {
|
||||||
|
background: rgba(30, 125, 30, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #1e7d1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text {
|
||||||
|
color: #f0f0f0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.precio-quincho {
|
||||||
|
color: #1e7d1e;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quincho-info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background: rgba(30, 125, 30, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #d9f2d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quincho-alerta {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background: rgba(200, 100, 30, 0.25);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f0a060;
|
||||||
|
border-left: 3px solid #d97020;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datos del cliente */
|
||||||
|
.datos-cliente {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
/*padding: 1.5rem;*/
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
background-color: #181818;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datos-cliente h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-cliente {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-cliente input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.9rem;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reprogramar modal styles */
|
||||||
|
.reprogramar-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.reprogramar-modal {
|
||||||
|
width: 900px;
|
||||||
|
max-width: 95%;
|
||||||
|
background: #121212;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(30,125,30,0.25);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
.reprogramar-modal h3 {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #d9f2d9;
|
||||||
|
border-left: 4px solid #1e7d1e;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.dias-grid, .horarios-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.dia-btn, .horario-btn {
|
||||||
|
padding: 14px;
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 2px solid rgba(255,255,255,0.06);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: pre-line;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.dia-btn:hover, .horario-btn:hover { transform: translateY(-4px); border-color: #1e7d1e; background: rgba(30,125,30,0.12); }
|
||||||
|
.dia-btn.selected, .horario-btn.selected { background: linear-gradient(135deg,#1e7d1e,#145214); border-color: #1e7d1e; box-shadow: 0 8px 20px rgba(30,125,30,0.5); }
|
||||||
|
.reprogramar-actions { display:flex; gap:10px; justify-content:flex-end; margin-top:8px; }
|
||||||
|
.reprogramar-actions .btn-cancelar, .reprogramar-actions .btn-confirmar { padding:10px 16px; border-radius:8px; border: none; cursor:pointer; font-weight:700; }
|
||||||
|
.reprogramar-actions .btn-cancelar { background: rgba(255,255,255,0.06); color:#fff; }
|
||||||
|
.reprogramar-actions .btn-confirmar { background: #1e7d1e; color:#fff; }
|
||||||
|
.reprogramar-actions .btn-confirmar[disabled] { opacity:0.5; cursor:not-allowed; }
|
||||||
|
|
||||||
|
/* Reprogramar button in table */
|
||||||
|
.reprogramar-btn { margin-left:8px; padding:6px 8px; border-radius:6px; background: rgba(30,125,30,0.12); border:1px solid rgba(30,125,30,0.2); color:#d9f2d9; cursor:pointer; font-weight:600; }
|
||||||
|
.reprogramar-btn.disabled { opacity:0.5; cursor:not-allowed; }
|
||||||
|
|
||||||
|
/* Estilos para reprogramar desde tabla de reservas */
|
||||||
|
.reprogramar-btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 0.45rem 0.7rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid rgba(255,255,255,0.12);
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
color: #d9f2d9;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.reprogramar-btn:hover:not(:disabled) {
|
||||||
|
background: rgba(30,125,30,0.12);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.reprogramar-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay modal */
|
||||||
|
.reprogramar-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.reprogramar-modal {
|
||||||
|
width: min(980px, 96%);
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: auto;
|
||||||
|
background: #111;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 18px;
|
||||||
|
border: 1px solid rgba(30,125,30,0.18);
|
||||||
|
box-shadow: 0 8px 30px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
.reprogramar-modal h3 {
|
||||||
|
color: #d9f2d9;
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
border-left: 4px solid #1e7d1e;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.dias-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.dia-btn {
|
||||||
|
width: 110px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 2px solid rgba(255,255,255,0.08);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: pre-line;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
.dia-btn:hover:not(.deshabilitado) {
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
background: rgba(30,125,30,0.12);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
.dia-btn.selected {
|
||||||
|
background: linear-gradient(135deg,#1e7d1e,#145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 8px 22px rgba(30,125,30,0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.horarios-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px,1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.horario-btn {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 2px solid rgba(255,255,255,0.08);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: pre-line;
|
||||||
|
transition: all 0.18s ease;
|
||||||
|
}
|
||||||
|
.horario-btn:hover:not(.ocupado) {
|
||||||
|
background: rgba(30,125,30,0.12);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
.horario-btn.selected {
|
||||||
|
background: linear-gradient(135deg,#1e7d1e,#145214);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 8px 22px rgba(30,125,30,0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reprogramar-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.reprogramar-actions .btn-cancelar,
|
||||||
|
.reprogramar-actions .btn-confirmar {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 2px solid rgba(255,255,255,0.08);
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.reprogramar-actions .btn-cancelar:hover { background: rgba(255,255,255,0.06); }
|
||||||
|
.reprogramar-actions .btn-confirmar:hover:not(:disabled) { background: rgba(30,125,30,0.14); border-color: #1e7d1e; }
|
||||||
|
.reprogramar-actions .btn-confirmar:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
@media (max-width:600px) {
|
||||||
|
.dias-grid { justify-content: center; }
|
||||||
|
.dia-btn { width: 90px; height: 64px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-cliente input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 0 8px rgba(30, 125, 30, 0.6);
|
||||||
|
background: rgba(30, 125, 30, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-cliente input::placeholder {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botones de reserva */
|
||||||
|
.botones-reserva {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-verificar,
|
||||||
|
.btn-volver,
|
||||||
|
.btn-confirmar {
|
||||||
|
padding: 0.9rem 2rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-volver {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-volver:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-verificar,
|
||||||
|
.btn-confirmar {
|
||||||
|
background: linear-gradient(135deg, #1e7d1e, #145214);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4px 15px rgba(30, 125, 30, 0.4);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-verificar:hover,
|
||||||
|
.btn-confirmar:hover {
|
||||||
|
background: linear-gradient(135deg, #2a9d2a, #1e7d1e);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(30, 125, 30, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-verificar:active,
|
||||||
|
.btn-confirmar:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #b8d9b8;
|
||||||
|
border-top: 1px solid rgba(30, 125, 30, 0.2);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seccion {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.botones-cancha {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancha-tipo {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.botones-reserva {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-verificar,
|
||||||
|
.btn-confirmar {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-volver {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid-horarios {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .valor {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown de Estado en Tabla */
|
||||||
|
.estado-dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border: 2px solid rgba(30, 125, 30, 0.5);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(30, 125, 30, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-btn:hover {
|
||||||
|
background: rgba(30, 125, 30, 0.2);
|
||||||
|
border-color: #1e7d1e;
|
||||||
|
box-shadow: 0 2px 8px rgba(30, 125, 30, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-texto {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-flecha {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-btn[aria-expanded="true"] .estado-flecha {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-opciones {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 2px solid #1e7d1e;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5), 0 0 0 9999px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1001;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: -2px;
|
||||||
|
min-width: 100%;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-opcion {
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-bottom: 1px solid rgba(30, 125, 30, 0.2);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-opcion:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-opcion:hover {
|
||||||
|
background: rgba(30, 125, 30, 0.3);
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-opcion.activo {
|
||||||
|
background: rgba(30, 125, 30, 0.5);
|
||||||
|
font-weight: 700;
|
||||||
|
color: #d9f2d9;
|
||||||
|
border-left: 3px solid #1e7d1e;
|
||||||
|
padding-left: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animaciones */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,952 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2478.22" height="1948.42" viewBox="0 0 2478.22 1948.42">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-0">
|
||||||
|
<path clip-rule="nonzero" d="M 398 1651 L 2097 1651 L 2097 1844 L 398 1844 Z M 398 1651 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-1">
|
||||||
|
<path clip-rule="nonzero" d="M 2096.269531 1747.328125 C 2096.269531 1800.339844 1716.238281 1843.308594 1247.441406 1843.308594 C 778.644531 1843.308594 398.609375 1800.339844 398.609375 1747.328125 C 398.609375 1694.320312 778.644531 1651.351562 1247.441406 1651.351562 C 1716.238281 1651.351562 2096.269531 1694.320312 2096.269531 1747.328125 Z M 2096.269531 1747.328125 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-2">
|
||||||
|
<path clip-rule="nonzero" d="M 2096.269531 1747.328125 C 2096.269531 1800.339844 1716.238281 1843.308594 1247.441406 1843.308594 C 778.644531 1843.308594 398.609375 1800.339844 398.609375 1747.328125 C 398.609375 1694.320312 778.644531 1651.351562 1247.441406 1651.351562 C 1716.238281 1651.351562 2096.269531 1694.320312 2096.269531 1747.328125 "/>
|
||||||
|
</clipPath>
|
||||||
|
<radialGradient id="radial-pattern-0" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="494.237449" gradientTransform="matrix(0.0970999, 0.995275, -0.995275, 0.0970999, 1040.48, 1525.38)">
|
||||||
|
<stop offset="0" stop-color="rgb(91.372681%, 91.215515%, 91.255188%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(91.372681%, 91.215515%, 91.255188%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(91.372681%, 91.215515%, 91.255188%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.628906" stop-color="rgb(91.317749%, 91.159058%, 91.19873%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.632812" stop-color="rgb(91.085815%, 90.924072%, 90.963745%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.636719" stop-color="rgb(90.734863%, 90.565491%, 90.608215%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.640625" stop-color="rgb(90.388489%, 90.213013%, 90.257263%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.644531" stop-color="rgb(90.04364%, 89.862061%, 89.907837%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.648438" stop-color="rgb(89.704895%, 89.517212%, 89.564514%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.652344" stop-color="rgb(89.36615%, 89.173889%, 89.221191%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.65625" stop-color="rgb(89.035034%, 88.835144%, 88.885498%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.660156" stop-color="rgb(88.705444%, 88.499451%, 88.549805%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.664062" stop-color="rgb(88.378906%, 88.168335%, 88.220215%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.667969" stop-color="rgb(88.056946%, 87.840271%, 87.893677%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.671875" stop-color="rgb(87.739563%, 87.516785%, 87.571716%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.675781" stop-color="rgb(87.423706%, 87.194824%, 87.251282%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.679688" stop-color="rgb(87.110901%, 86.875916%, 86.933899%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.683594" stop-color="rgb(86.804199%, 86.56311%, 86.624146%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.6875" stop-color="rgb(86.499023%, 86.253357%, 86.314392%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.691406" stop-color="rgb(86.199951%, 85.948181%, 86.010742%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.695312" stop-color="rgb(85.900879%, 85.644531%, 85.708618%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.699219" stop-color="rgb(85.60791%, 85.346985%, 85.411072%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.703125" stop-color="rgb(85.317993%, 85.050964%, 85.116577%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.707031" stop-color="rgb(85.032654%, 84.759521%, 84.82666%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.710938" stop-color="rgb(84.74884%, 84.47113%, 84.539795%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.714844" stop-color="rgb(84.468079%, 84.185791%, 84.255981%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.71875" stop-color="rgb(84.194946%, 83.906555%, 83.978271%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.722656" stop-color="rgb(83.920288%, 83.628845%, 83.700562%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.726562" stop-color="rgb(83.653259%, 83.355713%, 83.430481%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.730469" stop-color="rgb(83.389282%, 83.087158%, 83.161926%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.734375" stop-color="rgb(83.126831%, 82.820129%, 82.896423%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.738281" stop-color="rgb(82.870483%, 82.559204%, 82.637024%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.742188" stop-color="rgb(82.615662%, 82.298279%, 82.377625%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.746094" stop-color="rgb(82.365417%, 82.043457%, 82.124329%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(82.119751%, 81.794739%, 81.87561%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.753906" stop-color="rgb(81.877136%, 81.547546%, 81.628418%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.757812" stop-color="rgb(81.639099%, 81.304932%, 81.387329%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.761719" stop-color="rgb(81.402588%, 81.063843%, 81.147766%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.765625" stop-color="rgb(81.169128%, 80.827332%, 80.912781%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.769531" stop-color="rgb(80.941772%, 80.595398%, 80.680847%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.773438" stop-color="rgb(80.717468%, 80.366516%, 80.453491%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.777344" stop-color="rgb(80.49469%, 80.13916%, 80.227661%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.78125" stop-color="rgb(80.278015%, 79.919434%, 80.00946%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.785156" stop-color="rgb(80.065918%, 79.702759%, 79.792786%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.789062" stop-color="rgb(79.853821%, 79.486084%, 79.577637%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.792969" stop-color="rgb(79.646301%, 79.277039%, 79.368591%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.796875" stop-color="rgb(79.444885%, 79.071045%, 79.164124%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.800781" stop-color="rgb(79.244995%, 78.868103%, 78.961182%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.804688" stop-color="rgb(79.049683%, 78.669739%, 78.764343%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.808594" stop-color="rgb(78.858948%, 78.474426%, 78.570557%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.8125" stop-color="rgb(78.671265%, 78.283691%, 78.379822%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.816406" stop-color="rgb(78.485107%, 78.094482%, 78.192139%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.820312" stop-color="rgb(78.305054%, 77.909851%, 78.009033%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.824219" stop-color="rgb(78.129578%, 77.732849%, 77.830505%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.828125" stop-color="rgb(77.955627%, 77.554321%, 77.653503%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.832031" stop-color="rgb(77.703857%, 77.297974%, 77.398682%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.839844" stop-color="rgb(77.381897%, 76.96991%, 77.072144%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.847656" stop-color="rgb(77.070618%, 76.654053%, 76.756287%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.855469" stop-color="rgb(76.777649%, 76.356506%, 76.460266%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.863281" stop-color="rgb(76.498413%, 76.069641%, 76.176453%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.871094" stop-color="rgb(76.23291%, 75.801086%, 75.907898%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.878906" stop-color="rgb(76.045227%, 75.608826%, 75.717163%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.882812" stop-color="rgb(75.923157%, 75.485229%, 75.593567%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.886719" stop-color="rgb(75.697327%, 75.254822%, 75.364685%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.898438" stop-color="rgb(75.382996%, 74.934387%, 75.045776%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.910156" stop-color="rgb(75.141907%, 74.690247%, 74.801636%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.917969" stop-color="rgb(74.969482%, 74.514771%, 74.627686%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.925781" stop-color="rgb(74.746704%, 74.287415%, 74.401855%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.941406" stop-color="rgb(74.519348%, 74.055481%, 74.171448%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.953125" stop-color="rgb(74.276733%, 73.809814%, 73.925781%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(74.121094%, 73.649597%, 73.76709%)" stop-opacity="1"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip-3">
|
||||||
|
<path clip-rule="nonzero" d="M 1184 199 L 1681 199 L 1681 361 L 1184 361 Z M 1184 199 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-4">
|
||||||
|
<path clip-rule="nonzero" d="M 1975.851562 980.382812 C 1975.851562 1411.570312 1626.300781 1761.121094 1195.121094 1761.121094 C 763.925781 1761.121094 414.378906 1411.570312 414.378906 980.382812 C 414.378906 549.191406 763.925781 199.648438 1195.121094 199.648438 C 1626.300781 199.648438 1975.851562 549.191406 1975.851562 980.382812 Z M 1975.851562 980.382812 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-5">
|
||||||
|
<path clip-rule="nonzero" d="M 414 863 L 562 863 L 562 1421 L 414 1421 Z M 414 863 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-6">
|
||||||
|
<path clip-rule="nonzero" d="M 1975.851562 980.382812 C 1975.851562 1411.570312 1626.300781 1761.121094 1195.121094 1761.121094 C 763.925781 1761.121094 414.378906 1411.570312 414.378906 980.382812 C 414.378906 549.191406 763.925781 199.648438 1195.121094 199.648438 C 1626.300781 199.648438 1975.851562 549.191406 1975.851562 980.382812 Z M 1975.851562 980.382812 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-7">
|
||||||
|
<path clip-rule="nonzero" d="M 815 1663 L 1070 1663 L 1070 1761 L 815 1761 Z M 815 1663 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-8">
|
||||||
|
<path clip-rule="nonzero" d="M 1975.851562 980.382812 C 1975.851562 1411.570312 1626.300781 1761.121094 1195.121094 1761.121094 C 763.925781 1761.121094 414.378906 1411.570312 414.378906 980.382812 C 414.378906 549.191406 763.925781 199.648438 1195.121094 199.648438 C 1626.300781 199.648438 1975.851562 549.191406 1975.851562 980.382812 Z M 1975.851562 980.382812 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-9">
|
||||||
|
<path clip-rule="nonzero" d="M 1476 1355 L 1890 1355 L 1890 1725 L 1476 1725 Z M 1476 1355 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-10">
|
||||||
|
<path clip-rule="nonzero" d="M 1975.851562 980.382812 C 1975.851562 1411.570312 1626.300781 1761.121094 1195.121094 1761.121094 C 763.925781 1761.121094 414.378906 1411.570312 414.378906 980.382812 C 414.378906 549.191406 763.925781 199.648438 1195.121094 199.648438 C 1626.300781 199.648438 1975.851562 549.191406 1975.851562 980.382812 Z M 1975.851562 980.382812 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-11">
|
||||||
|
<path clip-rule="nonzero" d="M 1489 646 L 1617 646 L 1617 993 L 1489 993 Z M 1489 646 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-12">
|
||||||
|
<path clip-rule="nonzero" d="M 1489.601562 685.148438 C 1489.601562 685.148438 1520.21875 765.46875 1523.691406 846.960938 C 1527.160156 928.441406 1528.699219 964.652344 1528.699219 964.652344 L 1616.050781 992.679688 C 1616.050781 992.679688 1580.730469 809.160156 1569.441406 749.78125 C 1534.378906 565.328125 1489.601562 685.148438 1489.601562 685.148438 Z M 1489.601562 685.148438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-13">
|
||||||
|
<path clip-rule="nonzero" d="M 1489.601562 685.148438 C 1489.601562 685.148438 1520.21875 765.46875 1523.691406 846.960938 C 1527.160156 928.441406 1528.699219 964.652344 1528.699219 964.652344 L 1616.050781 992.679688 C 1616.050781 992.679688 1580.730469 809.160156 1569.441406 749.78125 C 1534.378906 565.328125 1489.601562 685.148438 1489.601562 685.148438 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-0" gradientUnits="userSpaceOnUse" x1="0.664577" y1="0" x2="1.31303" y2="0" gradientTransform="matrix(307.241, -57.3093, 57.3093, 307.241, 1256.91, 877.95)">
|
||||||
|
<stop offset="0" stop-color="rgb(40.727234%, 39.648438%, 39.915466%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.00390625" stop-color="rgb(40.536499%, 39.454651%, 39.723206%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0078125" stop-color="rgb(40.34729%, 39.26239%, 39.532471%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0117187" stop-color="rgb(40.158081%, 39.068604%, 39.34021%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.015625" stop-color="rgb(39.968872%, 38.876343%, 39.147949%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0195312" stop-color="rgb(39.778137%, 38.68103%, 38.954163%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0234375" stop-color="rgb(39.587402%, 38.487244%, 38.760376%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0273438" stop-color="rgb(39.396667%, 38.294983%, 38.568115%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.03125" stop-color="rgb(39.205933%, 38.09967%, 38.374329%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0351563" stop-color="rgb(39.015198%, 37.905884%, 38.182068%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0390625" stop-color="rgb(38.822937%, 37.710571%, 37.986755%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0429688" stop-color="rgb(38.632202%, 37.515259%, 37.792969%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.046875" stop-color="rgb(38.439941%, 37.319946%, 37.597656%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0507813" stop-color="rgb(38.247681%, 37.124634%, 37.40387%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0546875" stop-color="rgb(38.05542%, 36.929321%, 37.208557%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0585938" stop-color="rgb(37.863159%, 36.732483%, 37.013245%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0625" stop-color="rgb(37.670898%, 36.53717%, 36.819458%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0664063" stop-color="rgb(37.478638%, 36.340332%, 36.62262%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0703125" stop-color="rgb(37.286377%, 36.14502%, 36.427307%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0742188" stop-color="rgb(37.09259%, 35.948181%, 36.231995%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.078125" stop-color="rgb(36.898804%, 35.749817%, 36.035156%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0820312" stop-color="rgb(36.705017%, 35.552979%, 35.839844%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0859375" stop-color="rgb(36.509705%, 35.354614%, 35.641479%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0898437" stop-color="rgb(36.317444%, 35.157776%, 35.446167%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.09375" stop-color="rgb(36.122131%, 34.959412%, 35.247803%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0976562" stop-color="rgb(35.926819%, 34.761047%, 35.050964%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.101562" stop-color="rgb(35.733032%, 34.562683%, 34.854126%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.105469" stop-color="rgb(35.53772%, 34.364319%, 34.655762%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.109375" stop-color="rgb(35.342407%, 34.165955%, 34.458923%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.113281" stop-color="rgb(35.147095%, 33.966064%, 34.259033%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.117187" stop-color="rgb(34.950256%, 33.766174%, 34.060669%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.121094" stop-color="rgb(34.754944%, 33.56781%, 33.863831%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(34.559631%, 33.36792%, 33.66394%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.128906" stop-color="rgb(34.362793%, 33.16803%, 33.46405%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.132813" stop-color="rgb(34.165955%, 32.96814%, 33.265686%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.136719" stop-color="rgb(33.969116%, 32.766724%, 33.065796%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.140625" stop-color="rgb(33.772278%, 32.566833%, 32.865906%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.144531" stop-color="rgb(33.575439%, 32.365417%, 32.666016%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.148438" stop-color="rgb(33.377075%, 32.165527%, 32.466125%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.152344" stop-color="rgb(33.180237%, 31.964111%, 32.266235%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(32.981873%, 31.761169%, 32.064819%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.160156" stop-color="rgb(32.783508%, 31.561279%, 31.864929%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.164062" stop-color="rgb(32.585144%, 31.358337%, 31.663513%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.167969" stop-color="rgb(32.38678%, 31.155396%, 31.462097%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(32.18689%, 30.953979%, 31.260681%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.175781" stop-color="rgb(31.988525%, 30.751038%, 31.057739%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.179687" stop-color="rgb(31.788635%, 30.54657%, 30.856323%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.183594" stop-color="rgb(31.590271%, 30.345154%, 30.653381%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(31.390381%, 30.140686%, 30.451965%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.191406" stop-color="rgb(31.188965%, 29.937744%, 30.249023%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.195312" stop-color="rgb(30.989075%, 29.733276%, 30.046082%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.199219" stop-color="rgb(30.789185%, 29.530334%, 29.84314%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(30.589294%, 29.325867%, 29.640198%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.207031" stop-color="rgb(30.387878%, 29.121399%, 29.43573%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.210937" stop-color="rgb(30.186462%, 28.915405%, 29.231262%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.214844" stop-color="rgb(29.985046%, 28.710938%, 29.026794%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(29.78363%, 28.50647%, 28.823853%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.222656" stop-color="rgb(29.582214%, 28.300476%, 28.619385%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.226563" stop-color="rgb(29.380798%, 28.096008%, 28.414917%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.230469" stop-color="rgb(29.177856%, 27.890015%, 28.208923%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(28.974915%, 27.682495%, 28.004456%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.238281" stop-color="rgb(28.773499%, 27.478027%, 27.799988%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.242188" stop-color="rgb(28.572083%, 27.272034%, 27.593994%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.246094" stop-color="rgb(28.367615%, 27.062988%, 27.388%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(28.164673%, 26.856995%, 27.182007%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.253906" stop-color="rgb(27.960205%, 26.649475%, 26.974487%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.257812" stop-color="rgb(27.757263%, 26.441956%, 26.768494%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.261719" stop-color="rgb(27.554321%, 26.235962%, 26.5625%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(27.349854%, 26.026917%, 26.356506%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.269531" stop-color="rgb(27.14386%, 25.817871%, 26.147461%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.273437" stop-color="rgb(26.940918%, 25.610352%, 25.941467%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.277344" stop-color="rgb(26.734924%, 25.401306%, 25.732422%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(26.528931%, 25.192261%, 25.524902%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.285156" stop-color="rgb(26.324463%, 24.983215%, 25.315857%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.289063" stop-color="rgb(26.118469%, 24.77417%, 25.108337%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.292969" stop-color="rgb(25.912476%, 24.563599%, 24.899292%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(25.706482%, 24.354553%, 24.690247%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.300781" stop-color="rgb(25.500488%, 24.143982%, 24.481201%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.304688" stop-color="rgb(25.294495%, 23.934937%, 24.273682%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.308594" stop-color="rgb(25.088501%, 23.724365%, 24.06311%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(24.880981%, 23.513794%, 23.852539%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.316406" stop-color="rgb(24.673462%, 23.303223%, 23.643494%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.320313" stop-color="rgb(24.467468%, 23.092651%, 23.434448%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.324219" stop-color="rgb(24.258423%, 22.880554%, 23.222351%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(24.050903%, 22.668457%, 23.01178%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.332031" stop-color="rgb(23.843384%, 22.45636%, 22.801208%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.335938" stop-color="rgb(23.634338%, 22.245789%, 22.590637%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.339844" stop-color="rgb(23.426819%, 22.033691%, 22.380066%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(23.219299%, 21.821594%, 22.169495%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.347656" stop-color="rgb(23.010254%, 21.607971%, 21.955872%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.351562" stop-color="rgb(22.799683%, 21.395874%, 21.743774%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.355469" stop-color="rgb(22.590637%, 21.182251%, 21.531677%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(22.381592%, 20.968628%, 21.31958%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.363281" stop-color="rgb(22.172546%, 20.756531%, 21.107483%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.367187" stop-color="rgb(21.961975%, 20.541382%, 20.89386%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.371094" stop-color="rgb(21.75293%, 20.329285%, 20.681763%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(21.542358%, 20.114136%, 20.46814%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.378906" stop-color="rgb(21.331787%, 19.898987%, 20.256042%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.382813" stop-color="rgb(21.121216%, 19.685364%, 20.042419%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.386719" stop-color="rgb(20.910645%, 19.470215%, 19.828796%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.390625" stop-color="rgb(20.698547%, 19.255066%, 19.613647%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.394531" stop-color="rgb(20.487976%, 19.039917%, 19.400024%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.398438" stop-color="rgb(20.275879%, 18.824768%, 19.184875%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.402344" stop-color="rgb(20.063782%, 18.609619%, 18.969727%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.40625" stop-color="rgb(19.851685%, 18.392944%, 18.756104%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.410156" stop-color="rgb(19.638062%, 18.17627%, 18.539429%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.414062" stop-color="rgb(19.425964%, 17.959595%, 18.32428%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.417969" stop-color="rgb(19.213867%, 17.74292%, 18.107605%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.421875" stop-color="rgb(19.000244%, 17.526245%, 17.892456%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.425781" stop-color="rgb(18.786621%, 17.308044%, 17.675781%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.429687" stop-color="rgb(18.572998%, 17.09137%, 17.459106%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.433594" stop-color="rgb(18.360901%, 16.874695%, 17.243958%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.4375" stop-color="rgb(18.145752%, 16.656494%, 17.025757%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.441406" stop-color="rgb(17.932129%, 16.438293%, 16.810608%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.445312" stop-color="rgb(17.718506%, 16.220093%, 16.592407%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.449219" stop-color="rgb(17.503357%, 16.001892%, 16.374207%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.453125" stop-color="rgb(17.288208%, 15.783691%, 16.157532%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.457031" stop-color="rgb(17.074585%, 15.565491%, 15.939331%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.460937" stop-color="rgb(16.85791%, 15.345764%, 15.72113%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.464844" stop-color="rgb(16.642761%, 15.126038%, 15.50293%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.46875" stop-color="rgb(16.427612%, 14.906311%, 15.284729%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.472656" stop-color="rgb(16.212463%, 14.686584%, 15.066528%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.476563" stop-color="rgb(15.994263%, 14.465332%, 14.845276%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.480469" stop-color="rgb(15.779114%, 14.245605%, 14.627075%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.484375" stop-color="rgb(15.562439%, 14.025879%, 14.407349%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.488281" stop-color="rgb(15.345764%, 13.804626%, 14.187622%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.492188" stop-color="rgb(15.129089%, 13.583374%, 13.967896%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.496094" stop-color="rgb(14.910889%, 13.362122%, 13.748169%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(14.694214%, 13.142395%, 13.526917%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.503906" stop-color="rgb(14.477539%, 12.921143%, 13.30719%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.507812" stop-color="rgb(14.257812%, 12.698364%, 13.085938%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.511719" stop-color="rgb(14.039612%, 12.475586%, 12.864685%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.515625" stop-color="rgb(13.821411%, 12.252808%, 12.641907%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.53125" stop-color="rgb(13.775635%, 12.205505%, 12.59613%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5625" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip-14">
|
||||||
|
<path clip-rule="nonzero" d="M 966 1114 L 1274 1114 L 1274 1280 L 966 1280 Z M 966 1114 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-15">
|
||||||
|
<path clip-rule="nonzero" d="M 966.808594 1114.558594 L 1270.371094 1214.238281 L 1273.390625 1279.179688 L 974.359375 1179.5 Z M 966.808594 1114.558594 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-16">
|
||||||
|
<path clip-rule="nonzero" d="M 966.808594 1114.558594 L 1270.371094 1214.238281 L 1273.390625 1279.179688 L 974.359375 1179.5 L 966.808594 1114.558594 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-1" gradientUnits="userSpaceOnUse" x1="0.466075" y1="0" x2="1.500953" y2="0" gradientTransform="matrix(-64.3774, 225.321, -225.321, -64.3774, 1183.42, 975.265)">
|
||||||
|
<stop offset="0" stop-color="rgb(54.873657%, 54.052734%, 54.257202%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.00390625" stop-color="rgb(54.598999%, 53.771973%, 53.977966%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0078125" stop-color="rgb(54.324341%, 53.492737%, 53.69873%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0117188" stop-color="rgb(54.048157%, 53.211975%, 53.419495%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.015625" stop-color="rgb(53.771973%, 52.931213%, 53.140259%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0195312" stop-color="rgb(53.495789%, 52.648926%, 52.859497%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0234375" stop-color="rgb(53.218079%, 52.366638%, 52.578735%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0273438" stop-color="rgb(52.940369%, 52.084351%, 52.296448%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.03125" stop-color="rgb(52.661133%, 51.799011%, 52.01416%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0351562" stop-color="rgb(52.383423%, 51.516724%, 51.731873%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0390625" stop-color="rgb(52.102661%, 51.231384%, 51.448059%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0429688" stop-color="rgb(51.823425%, 50.947571%, 51.164246%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.046875" stop-color="rgb(51.542664%, 50.660706%, 50.878906%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0507812" stop-color="rgb(51.260376%, 50.37384%, 50.593567%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0546875" stop-color="rgb(50.978088%, 50.086975%, 50.308228%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0585938" stop-color="rgb(50.697327%, 49.80011%, 50.022888%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0625" stop-color="rgb(50.413513%, 49.511719%, 49.736023%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0664062" stop-color="rgb(50.1297%, 49.221802%, 49.447632%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0703125" stop-color="rgb(49.845886%, 48.931885%, 49.159241%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0742188" stop-color="rgb(49.560547%, 48.643494%, 48.87085%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.078125" stop-color="rgb(49.275208%, 48.352051%, 48.580933%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0820312" stop-color="rgb(48.989868%, 48.062134%, 48.292542%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0859375" stop-color="rgb(48.703003%, 47.769165%, 48.001099%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0898438" stop-color="rgb(48.417664%, 47.477722%, 47.711182%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.09375" stop-color="rgb(48.130798%, 47.186279%, 47.421265%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0976562" stop-color="rgb(47.842407%, 46.893311%, 47.128296%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.101562" stop-color="rgb(47.55249%, 46.59729%, 46.835327%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.105469" stop-color="rgb(47.264099%, 46.304321%, 46.542358%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.109375" stop-color="rgb(46.974182%, 46.009827%, 46.24939%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.113281" stop-color="rgb(46.682739%, 45.713806%, 45.954895%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.117188" stop-color="rgb(46.392822%, 45.41626%, 45.658875%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.121094" stop-color="rgb(46.099854%, 45.118713%, 45.362854%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(45.808411%, 44.821167%, 45.066833%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.128906" stop-color="rgb(45.515442%, 44.523621%, 44.770813%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.132812" stop-color="rgb(45.222473%, 44.226074%, 44.473267%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.136719" stop-color="rgb(44.927979%, 43.927002%, 44.17572%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.140625" stop-color="rgb(44.63501%, 43.62793%, 43.878174%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.144531" stop-color="rgb(44.340515%, 43.327332%, 43.579102%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.148438" stop-color="rgb(44.046021%, 43.026733%, 43.280029%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.152344" stop-color="rgb(43.748474%, 42.726135%, 42.979431%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(43.452454%, 42.424011%, 42.678833%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.160156" stop-color="rgb(43.154907%, 42.120361%, 42.378235%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.164062" stop-color="rgb(42.857361%, 41.818237%, 42.076111%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.167969" stop-color="rgb(42.559814%, 41.514587%, 41.773987%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(42.260742%, 41.209412%, 41.470337%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.175781" stop-color="rgb(41.96167%, 40.905762%, 41.168213%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.179688" stop-color="rgb(41.661072%, 40.59906%, 40.863037%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.183594" stop-color="rgb(41.360474%, 40.292358%, 40.557861%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(41.059875%, 39.987183%, 40.254211%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.191406" stop-color="rgb(40.757751%, 39.678955%, 39.94751%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.195312" stop-color="rgb(40.455627%, 39.372253%, 39.640808%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.199219" stop-color="rgb(40.153503%, 39.065552%, 39.335632%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(39.849854%, 38.755798%, 39.027405%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.207031" stop-color="rgb(39.546204%, 38.446045%, 38.719177%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.210938" stop-color="rgb(39.242554%, 38.136292%, 38.41095%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.214844" stop-color="rgb(38.935852%, 37.825012%, 38.101196%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(38.630676%, 37.513733%, 37.791443%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.222656" stop-color="rgb(38.323975%, 37.202454%, 37.480164%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.226562" stop-color="rgb(38.017273%, 36.889648%, 37.17041%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.230469" stop-color="rgb(37.710571%, 36.576843%, 36.859131%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(37.402344%, 36.264038%, 36.546326%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.238281" stop-color="rgb(37.094116%, 35.948181%, 36.233521%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.242188" stop-color="rgb(36.785889%, 35.635376%, 35.920715%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.246094" stop-color="rgb(36.474609%, 35.319519%, 35.606384%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(36.164856%, 35.003662%, 35.292053%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.253906" stop-color="rgb(35.855103%, 34.687805%, 34.977722%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.257812" stop-color="rgb(35.542297%, 34.370422%, 34.661865%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.261719" stop-color="rgb(35.232544%, 34.05304%, 34.346008%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(34.919739%, 33.735657%, 34.030151%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.269531" stop-color="rgb(34.606934%, 33.416748%, 33.712769%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.273438" stop-color="rgb(34.294128%, 33.097839%, 33.395386%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.277344" stop-color="rgb(33.979797%, 32.777405%, 33.076477%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(33.66394%, 32.45697%, 32.757568%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.285156" stop-color="rgb(33.349609%, 32.136536%, 32.43866%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.289062" stop-color="rgb(33.033752%, 31.814575%, 32.118225%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.292969" stop-color="rgb(32.717896%, 31.494141%, 31.797791%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(32.400513%, 31.170654%, 31.47583%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.300781" stop-color="rgb(32.08313%, 30.847168%, 31.15387%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.304688" stop-color="rgb(31.764221%, 30.523682%, 30.831909%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.308594" stop-color="rgb(31.446838%, 30.198669%, 30.508423%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(31.12793%, 29.873657%, 30.186462%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.316406" stop-color="rgb(30.809021%, 29.550171%, 29.862976%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.320312" stop-color="rgb(30.488586%, 29.223633%, 29.537964%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.324219" stop-color="rgb(30.168152%, 28.897095%, 29.212952%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(29.846191%, 28.569031%, 28.886414%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.332031" stop-color="rgb(29.524231%, 28.242493%, 28.559875%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.335938" stop-color="rgb(29.202271%, 27.912903%, 28.233337%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.339844" stop-color="rgb(28.878784%, 27.583313%, 27.905273%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(28.553772%, 27.253723%, 27.577209%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.347656" stop-color="rgb(28.230286%, 26.924133%, 27.249146%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.351562" stop-color="rgb(27.905273%, 26.593018%, 26.919556%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.355469" stop-color="rgb(27.580261%, 26.261902%, 26.589966%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(27.255249%, 25.930786%, 26.260376%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.363281" stop-color="rgb(26.927185%, 25.598145%, 25.927734%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.367188" stop-color="rgb(26.600647%, 25.265503%, 25.596619%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.371094" stop-color="rgb(26.274109%, 24.932861%, 25.265503%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(25.946045%, 24.598694%, 24.934387%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.378906" stop-color="rgb(25.617981%, 24.264526%, 24.60022%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.382812" stop-color="rgb(25.288391%, 23.928833%, 24.266052%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.386719" stop-color="rgb(24.957275%, 23.591614%, 23.931885%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.390625" stop-color="rgb(24.627686%, 23.25592%, 23.596191%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.394531" stop-color="rgb(24.29657%, 22.918701%, 23.262024%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.398438" stop-color="rgb(23.965454%, 22.581482%, 22.924805%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.402344" stop-color="rgb(23.632812%, 22.242737%, 22.587585%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.40625" stop-color="rgb(23.300171%, 21.905518%, 22.251892%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.410156" stop-color="rgb(22.967529%, 21.565247%, 21.914673%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.414062" stop-color="rgb(22.633362%, 21.224976%, 21.574402%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.417969" stop-color="rgb(22.299194%, 20.884705%, 21.235657%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.421875" stop-color="rgb(21.963501%, 20.542908%, 20.895386%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.425781" stop-color="rgb(21.629333%, 20.202637%, 20.556641%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.429688" stop-color="rgb(21.29364%, 19.86084%, 20.21637%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.433594" stop-color="rgb(20.956421%, 19.517517%, 19.874573%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.4375" stop-color="rgb(20.619202%, 19.174194%, 19.532776%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.441406" stop-color="rgb(20.281982%, 18.830872%, 19.192505%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.445312" stop-color="rgb(19.943237%, 18.487549%, 18.849182%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.449219" stop-color="rgb(19.604492%, 18.141174%, 18.504333%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.453125" stop-color="rgb(19.264221%, 17.7948%, 18.161011%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.457031" stop-color="rgb(18.925476%, 17.449951%, 17.816162%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.460938" stop-color="rgb(18.583679%, 17.102051%, 17.469788%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.464844" stop-color="rgb(18.243408%, 16.755676%, 17.124939%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.46875" stop-color="rgb(17.900085%, 16.40625%, 16.778564%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.472656" stop-color="rgb(17.558289%, 16.05835%, 16.430664%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.476562" stop-color="rgb(17.216492%, 15.710449%, 16.08429%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.480469" stop-color="rgb(16.874695%, 15.361023%, 15.736389%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.484375" stop-color="rgb(16.529846%, 15.011597%, 15.388489%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.488281" stop-color="rgb(16.184998%, 14.660645%, 15.039062%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.492188" stop-color="rgb(15.840149%, 14.309692%, 14.689636%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.496094" stop-color="rgb(15.493774%, 13.957214%, 14.338684%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(15.148926%, 13.604736%, 13.987732%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.503906" stop-color="rgb(14.801025%, 13.250732%, 13.635254%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.507812" stop-color="rgb(14.454651%, 12.898254%, 13.284302%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.511719" stop-color="rgb(14.105225%, 12.542725%, 12.930298%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.515625" stop-color="rgb(13.757324%, 12.187195%, 12.57782%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.53125" stop-color="rgb(13.743591%, 12.173462%, 12.564087%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5625" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip-17">
|
||||||
|
<path clip-rule="nonzero" d="M 781 649 L 1008 649 L 1008 750 L 781 750 Z M 781 649 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-18">
|
||||||
|
<path clip-rule="nonzero" d="M 849.007812 726.410156 L 889.78125 749.070312 L 1007.589844 649.390625 L 781.042969 699.230469 Z M 849.007812 726.410156 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-19">
|
||||||
|
<path clip-rule="nonzero" d="M 849.007812 726.410156 L 889.78125 749.070312 L 1007.589844 649.390625 L 781.042969 699.230469 L 849.007812 726.410156 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-2" gradientUnits="userSpaceOnUse" x1="0.553254" y1="0" x2="1.174376" y2="0" gradientTransform="matrix(-22.6542, -199.357, 199.357, -22.6542, 916.68, 871.12)">
|
||||||
|
<stop offset="0" stop-color="rgb(48.828125%, 47.895813%, 48.127747%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.00390625" stop-color="rgb(48.655701%, 47.720337%, 47.953796%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0078125" stop-color="rgb(48.483276%, 47.544861%, 47.77832%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0117188" stop-color="rgb(48.312378%, 47.370911%, 47.60437%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.015625" stop-color="rgb(48.138428%, 47.193909%, 47.428894%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0195313" stop-color="rgb(47.966003%, 47.018433%, 47.253418%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0234375" stop-color="rgb(47.793579%, 46.842957%, 47.079468%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0273438" stop-color="rgb(47.619629%, 46.665955%, 46.902466%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.03125" stop-color="rgb(47.445679%, 46.488953%, 46.72699%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0351563" stop-color="rgb(47.271729%, 46.311951%, 46.549988%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0390625" stop-color="rgb(47.097778%, 46.134949%, 46.374512%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0429687" stop-color="rgb(46.923828%, 45.957947%, 46.199036%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.046875" stop-color="rgb(46.749878%, 45.780945%, 46.022034%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0507813" stop-color="rgb(46.575928%, 45.603943%, 45.845032%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0546875" stop-color="rgb(46.400452%, 45.425415%, 45.666504%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0585938" stop-color="rgb(46.226501%, 45.248413%, 45.491028%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0625" stop-color="rgb(46.051025%, 45.069885%, 45.314026%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0664063" stop-color="rgb(45.877075%, 44.891357%, 45.135498%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0703125" stop-color="rgb(45.700073%, 44.711304%, 44.95697%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0742188" stop-color="rgb(45.524597%, 44.534302%, 44.779968%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.078125" stop-color="rgb(45.349121%, 44.354248%, 44.60144%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0820313" stop-color="rgb(45.172119%, 44.174194%, 44.422913%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0859375" stop-color="rgb(44.996643%, 43.995667%, 44.244385%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0898437" stop-color="rgb(44.821167%, 43.817139%, 44.065857%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.09375" stop-color="rgb(44.642639%, 43.635559%, 43.885803%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0976562" stop-color="rgb(44.467163%, 43.455505%, 43.707275%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.101563" stop-color="rgb(44.288635%, 43.275452%, 43.527222%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.105469" stop-color="rgb(44.113159%, 43.095398%, 43.348694%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.109375" stop-color="rgb(43.934631%, 42.913818%, 43.16864%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.113281" stop-color="rgb(43.757629%, 42.733765%, 42.987061%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.117188" stop-color="rgb(43.579102%, 42.552185%, 42.807007%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.121094" stop-color="rgb(43.400574%, 42.370605%, 42.625427%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(43.222046%, 42.189026%, 42.445374%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.128906" stop-color="rgb(43.045044%, 42.008972%, 42.26532%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.132812" stop-color="rgb(42.866516%, 41.825867%, 42.08374%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.136719" stop-color="rgb(42.686462%, 41.644287%, 41.903687%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.140625" stop-color="rgb(42.507935%, 41.461182%, 41.722107%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.144531" stop-color="rgb(42.329407%, 41.279602%, 41.540527%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.148438" stop-color="rgb(42.147827%, 41.094971%, 41.357422%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.152344" stop-color="rgb(41.967773%, 40.911865%, 41.174316%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(41.789246%, 40.730286%, 40.992737%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.160156" stop-color="rgb(41.609192%, 40.545654%, 40.809631%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.164063" stop-color="rgb(41.429138%, 40.362549%, 40.626526%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.167969" stop-color="rgb(41.247559%, 40.179443%, 40.444946%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(41.065979%, 39.993286%, 40.260315%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.175781" stop-color="rgb(40.885925%, 39.810181%, 40.077209%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.179687" stop-color="rgb(40.705872%, 39.627075%, 39.894104%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.183594" stop-color="rgb(40.524292%, 39.442444%, 39.710999%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(40.342712%, 39.256287%, 39.526367%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.191406" stop-color="rgb(40.161133%, 39.071655%, 39.341736%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.195312" stop-color="rgb(39.979553%, 38.887024%, 39.15863%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.199219" stop-color="rgb(39.796448%, 38.700867%, 38.972473%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(39.614868%, 38.516235%, 38.787842%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.207031" stop-color="rgb(39.431763%, 38.328552%, 38.60321%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.210938" stop-color="rgb(39.248657%, 38.142395%, 38.417053%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.214844" stop-color="rgb(39.065552%, 37.956238%, 38.232422%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(38.882446%, 37.770081%, 38.046265%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.222656" stop-color="rgb(38.699341%, 37.583923%, 37.861633%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.226562" stop-color="rgb(38.514709%, 37.39624%, 37.67395%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.230469" stop-color="rgb(38.331604%, 37.208557%, 37.487793%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(38.148499%, 37.0224%, 37.301636%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.238281" stop-color="rgb(37.963867%, 36.834717%, 37.115479%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.242187" stop-color="rgb(37.779236%, 36.647034%, 36.927795%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.246094" stop-color="rgb(37.594604%, 36.459351%, 36.741638%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(37.409973%, 36.270142%, 36.553955%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.253906" stop-color="rgb(37.225342%, 36.082458%, 36.366272%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.257813" stop-color="rgb(37.039185%, 35.89325%, 36.178589%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.261719" stop-color="rgb(36.854553%, 35.705566%, 35.990906%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(36.668396%, 35.516357%, 35.801697%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.269531" stop-color="rgb(36.482239%, 35.327148%, 35.614014%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.273438" stop-color="rgb(36.297607%, 35.137939%, 35.426331%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.277344" stop-color="rgb(36.109924%, 34.947205%, 35.237122%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(35.925293%, 34.757996%, 35.047913%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.285156" stop-color="rgb(35.73761%, 34.568787%, 34.858704%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.289062" stop-color="rgb(35.551453%, 34.378052%, 34.669495%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.292969" stop-color="rgb(35.36377%, 34.187317%, 34.480286%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(35.177612%, 33.996582%, 34.289551%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.300781" stop-color="rgb(34.988403%, 33.805847%, 34.098816%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.304688" stop-color="rgb(34.80072%, 33.613586%, 33.908081%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.308594" stop-color="rgb(34.613037%, 33.422852%, 33.718872%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(34.425354%, 33.232117%, 33.528137%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.316406" stop-color="rgb(34.237671%, 33.039856%, 33.337402%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.320313" stop-color="rgb(34.048462%, 32.849121%, 33.146667%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.324219" stop-color="rgb(33.860779%, 32.65686%, 32.955933%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(33.67157%, 32.4646%, 32.763672%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.332031" stop-color="rgb(33.480835%, 32.270813%, 32.571411%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.335937" stop-color="rgb(33.293152%, 32.078552%, 32.380676%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.339844" stop-color="rgb(33.103943%, 31.886292%, 32.188416%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(32.913208%, 31.692505%, 31.994629%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.347656" stop-color="rgb(32.722473%, 31.498718%, 31.802368%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.351562" stop-color="rgb(32.533264%, 31.306458%, 31.611633%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.355469" stop-color="rgb(32.342529%, 31.111145%, 31.417847%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(32.15332%, 30.918884%, 31.225586%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.363281" stop-color="rgb(31.962585%, 30.723572%, 31.031799%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.367188" stop-color="rgb(31.771851%, 30.529785%, 30.838013%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.371094" stop-color="rgb(31.581116%, 30.335999%, 30.644226%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(31.388855%, 30.13916%, 30.450439%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.378906" stop-color="rgb(31.19812%, 29.945374%, 30.256653%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.382812" stop-color="rgb(31.005859%, 29.750061%, 30.06134%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.386719" stop-color="rgb(30.813599%, 29.554749%, 29.867554%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.390625" stop-color="rgb(30.621338%, 29.35791%, 29.672241%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.394531" stop-color="rgb(30.429077%, 29.162598%, 29.476929%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.398438" stop-color="rgb(30.236816%, 28.967285%, 29.281616%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.402344" stop-color="rgb(30.044556%, 28.771973%, 29.08783%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.40625" stop-color="rgb(29.850769%, 28.573608%, 28.890991%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.410156" stop-color="rgb(29.656982%, 28.37677%, 28.695679%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.414063" stop-color="rgb(29.464722%, 28.181458%, 28.500366%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.417969" stop-color="rgb(29.270935%, 27.984619%, 28.303528%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.421875" stop-color="rgb(29.077148%, 27.786255%, 28.106689%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.425781" stop-color="rgb(28.883362%, 27.589417%, 27.911377%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.429687" stop-color="rgb(28.689575%, 27.392578%, 27.714539%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.433594" stop-color="rgb(28.494263%, 27.194214%, 27.5177%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.4375" stop-color="rgb(28.300476%, 26.99585%, 27.319336%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.441406" stop-color="rgb(28.105164%, 26.797485%, 27.122498%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.445312" stop-color="rgb(27.909851%, 26.599121%, 26.924133%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.449219" stop-color="rgb(27.716064%, 26.400757%, 26.727295%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.453125" stop-color="rgb(27.519226%, 26.200867%, 26.528931%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.457031" stop-color="rgb(27.325439%, 26.002502%, 26.330566%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.460938" stop-color="rgb(27.128601%, 25.802612%, 26.132202%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.464844" stop-color="rgb(26.931763%, 25.602722%, 25.933838%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.46875" stop-color="rgb(26.73645%, 25.402832%, 25.733948%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.472656" stop-color="rgb(26.539612%, 25.202942%, 25.535583%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.476562" stop-color="rgb(26.344299%, 25.003052%, 25.337219%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.480469" stop-color="rgb(26.145935%, 24.803162%, 25.135803%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.484375" stop-color="rgb(25.950623%, 24.603271%, 24.937439%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.488281" stop-color="rgb(25.752258%, 24.401855%, 24.737549%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.492187" stop-color="rgb(25.55542%, 24.200439%, 24.537659%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.496094" stop-color="rgb(25.358582%, 23.999023%, 24.337769%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(25.160217%, 23.799133%, 24.136353%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.503906" stop-color="rgb(24.961853%, 23.596191%, 23.934937%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.507813" stop-color="rgb(24.763489%, 23.39325%, 23.733521%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.511719" stop-color="rgb(24.565125%, 23.193359%, 23.53363%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.515625" stop-color="rgb(24.365234%, 22.988892%, 23.330688%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.519531" stop-color="rgb(24.16687%, 22.787476%, 23.129272%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.523438" stop-color="rgb(23.968506%, 22.584534%, 22.927856%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.527344" stop-color="rgb(23.768616%, 22.380066%, 22.724915%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.53125" stop-color="rgb(23.568726%, 22.17865%, 22.523499%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.535156" stop-color="rgb(23.368835%, 21.974182%, 22.320557%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.539062" stop-color="rgb(23.168945%, 21.77124%, 22.117615%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.542969" stop-color="rgb(22.969055%, 21.566772%, 21.914673%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.546875" stop-color="rgb(22.769165%, 21.363831%, 21.711731%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.550781" stop-color="rgb(22.569275%, 21.159363%, 21.510315%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.554688" stop-color="rgb(22.367859%, 20.956421%, 21.307373%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.558594" stop-color="rgb(22.166443%, 20.750427%, 21.102905%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5625" stop-color="rgb(21.966553%, 20.545959%, 20.898438%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.566406" stop-color="rgb(21.765137%, 20.339966%, 20.69397%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.570313" stop-color="rgb(21.563721%, 20.135498%, 20.489502%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.574219" stop-color="rgb(21.362305%, 19.93103%, 20.28656%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.578125" stop-color="rgb(21.160889%, 19.725037%, 20.082092%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.582031" stop-color="rgb(20.957947%, 19.519043%, 19.876099%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.585937" stop-color="rgb(20.756531%, 19.314575%, 19.671631%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.589844" stop-color="rgb(20.553589%, 19.107056%, 19.467163%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.59375" stop-color="rgb(20.350647%, 18.901062%, 19.261169%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.597656" stop-color="rgb(20.147705%, 18.695068%, 19.055176%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.601562" stop-color="rgb(19.943237%, 18.487549%, 18.849182%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.605469" stop-color="rgb(19.741821%, 18.280029%, 18.643188%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.609375" stop-color="rgb(19.537354%, 18.07251%, 18.437195%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.613281" stop-color="rgb(19.334412%, 17.866516%, 18.231201%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.617188" stop-color="rgb(19.129944%, 17.658997%, 18.023682%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.621094" stop-color="rgb(18.927002%, 17.451477%, 17.817688%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(18.722534%, 17.242432%, 17.610168%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.628906" stop-color="rgb(18.516541%, 17.033386%, 17.402649%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.632812" stop-color="rgb(18.313599%, 16.825867%, 17.195129%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.636719" stop-color="rgb(18.107605%, 16.616821%, 16.98761%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.640625" stop-color="rgb(17.901611%, 16.407776%, 16.778564%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.644531" stop-color="rgb(17.697144%, 16.19873%, 16.571045%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.648438" stop-color="rgb(17.49115%, 15.989685%, 16.363525%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.652344" stop-color="rgb(17.286682%, 15.78064%, 16.15448%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.65625" stop-color="rgb(17.079163%, 15.570068%, 15.945435%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.660156" stop-color="rgb(16.874695%, 15.361023%, 15.736389%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.664063" stop-color="rgb(16.667175%, 15.150452%, 15.527344%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.667969" stop-color="rgb(16.461182%, 14.93988%, 15.318298%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.671875" stop-color="rgb(16.253662%, 14.729309%, 15.109253%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.675781" stop-color="rgb(16.047668%, 14.520264%, 14.898682%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.679687" stop-color="rgb(15.840149%, 14.309692%, 14.689636%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.683594" stop-color="rgb(15.632629%, 14.097595%, 14.479065%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.6875" stop-color="rgb(15.42511%, 13.885498%, 14.268494%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.691406" stop-color="rgb(15.21759%, 13.674927%, 14.057922%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.695312" stop-color="rgb(15.010071%, 13.46283%, 13.847351%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.699219" stop-color="rgb(14.801025%, 13.250732%, 13.635254%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.703125" stop-color="rgb(14.593506%, 13.038635%, 13.424683%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.707031" stop-color="rgb(14.38446%, 12.826538%, 13.214111%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.710938" stop-color="rgb(14.175415%, 12.612915%, 13.002014%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.714844" stop-color="rgb(13.96637%, 12.400818%, 12.789917%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.71875" stop-color="rgb(13.757324%, 12.187195%, 12.57782%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(13.743591%, 12.173462%, 12.564087%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip-20">
|
||||||
|
<path clip-rule="nonzero" d="M 426 928 L 519 928 L 519 1305 L 426 1305 Z M 426 928 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-21">
|
||||||
|
<path clip-rule="nonzero" d="M 427.636719 928.789062 C 427.636719 928.789062 418.578125 1073.78125 450.292969 1173.460938 C 482.007812 1273.140625 504.664062 1304.851562 504.664062 1304.851562 L 518.253906 1209.699219 C 518.253906 1209.699219 463.886719 1141.738281 463.886719 1055.660156 C 463.886719 969.570312 456.335938 942.378906 456.335938 942.378906 Z M 427.636719 928.789062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-22">
|
||||||
|
<path clip-rule="nonzero" d="M 427.636719 928.789062 C 427.636719 928.789062 418.578125 1073.78125 450.292969 1173.460938 C 482.007812 1273.140625 504.664062 1304.851562 504.664062 1304.851562 L 518.253906 1209.699219 C 518.253906 1209.699219 463.886719 1141.738281 463.886719 1055.660156 C 463.886719 969.570312 456.335938 942.378906 456.335938 942.378906 L 427.636719 928.789062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-3" gradientUnits="userSpaceOnUse" x1="0.803755" y1="0" x2="1.055912" y2="0" gradientTransform="matrix(521.046, -45.3084, 45.3084, 521.046, -16.0863, 1158.76)">
|
||||||
|
<stop offset="0" stop-color="rgb(29.997253%, 28.723145%, 29.039001%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0078125" stop-color="rgb(29.919434%, 28.643799%, 28.961182%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.015625" stop-color="rgb(29.760742%, 28.483582%, 28.800964%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0234375" stop-color="rgb(29.605103%, 28.323364%, 28.642273%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.03125" stop-color="rgb(29.447937%, 28.164673%, 28.483582%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0390625" stop-color="rgb(29.290771%, 28.00293%, 28.323364%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.046875" stop-color="rgb(29.133606%, 27.844238%, 28.164673%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0546875" stop-color="rgb(28.974915%, 27.682495%, 28.004456%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0625" stop-color="rgb(28.819275%, 27.523804%, 27.845764%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0703125" stop-color="rgb(28.660583%, 27.362061%, 27.685547%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.078125" stop-color="rgb(28.504944%, 27.203369%, 27.526855%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0859375" stop-color="rgb(28.344727%, 27.041626%, 27.365112%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.09375" stop-color="rgb(28.187561%, 26.879883%, 27.204895%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.101563" stop-color="rgb(28.02887%, 26.719666%, 27.044678%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.109375" stop-color="rgb(27.870178%, 26.556396%, 26.882935%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.117187" stop-color="rgb(27.711487%, 26.396179%, 26.722717%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(27.554321%, 26.235962%, 26.5625%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.132813" stop-color="rgb(27.394104%, 26.072693%, 26.400757%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.140625" stop-color="rgb(27.236938%, 25.912476%, 26.242065%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.148437" stop-color="rgb(27.076721%, 25.750732%, 26.080322%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(26.91803%, 25.587463%, 25.918579%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.164062" stop-color="rgb(26.757812%, 25.424194%, 25.75531%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(26.597595%, 25.262451%, 25.593567%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.179688" stop-color="rgb(26.438904%, 25.100708%, 25.43335%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(26.280212%, 24.937439%, 25.271606%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.195312" stop-color="rgb(26.119995%, 24.775696%, 25.109863%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(25.959778%, 24.612427%, 24.946594%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.210937" stop-color="rgb(25.801086%, 24.450684%, 24.784851%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(25.639343%, 24.285889%, 24.623108%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.226562" stop-color="rgb(25.479126%, 24.12262%, 24.459839%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(25.318909%, 23.959351%, 24.29657%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.242187" stop-color="rgb(25.158691%, 23.796082%, 24.134827%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(24.996948%, 23.632812%, 23.971558%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.257812" stop-color="rgb(24.835205%, 23.468018%, 23.806763%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(24.674988%, 23.304749%, 23.64502%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.273437" stop-color="rgb(24.514771%, 23.139954%, 23.48175%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(24.353027%, 22.975159%, 23.316956%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.289063" stop-color="rgb(24.191284%, 22.81189%, 23.155212%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(24.029541%, 22.647095%, 22.990417%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.304687" stop-color="rgb(23.867798%, 22.4823%, 22.827148%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(23.706055%, 22.317505%, 22.662354%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.320313" stop-color="rgb(23.544312%, 22.15271%, 22.499084%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(23.382568%, 21.987915%, 22.33429%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.335937" stop-color="rgb(23.220825%, 21.82312%, 22.169495%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(23.057556%, 21.656799%, 22.0047%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.351562" stop-color="rgb(22.895813%, 21.492004%, 21.839905%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(22.732544%, 21.327209%, 21.676636%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.367187" stop-color="rgb(22.569275%, 21.160889%, 21.510315%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(22.407532%, 20.994568%, 21.34552%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.382812" stop-color="rgb(22.244263%, 20.829773%, 21.180725%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.390625" stop-color="rgb(22.080994%, 20.661926%, 21.014404%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.398437" stop-color="rgb(21.917725%, 20.495605%, 20.849609%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.40625" stop-color="rgb(21.754456%, 20.329285%, 20.683289%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.414062" stop-color="rgb(21.591187%, 20.162964%, 20.518494%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.421875" stop-color="rgb(21.426392%, 19.996643%, 20.350647%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.429687" stop-color="rgb(21.263123%, 19.830322%, 20.185852%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.4375" stop-color="rgb(21.098328%, 19.662476%, 20.019531%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.445312" stop-color="rgb(20.935059%, 19.496155%, 19.85321%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.453125" stop-color="rgb(20.770264%, 19.328308%, 19.68689%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.460937" stop-color="rgb(20.605469%, 19.160461%, 19.520569%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.46875" stop-color="rgb(20.4422%, 18.994141%, 19.352722%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.476562" stop-color="rgb(20.277405%, 18.826294%, 19.186401%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.484375" stop-color="rgb(20.11261%, 18.658447%, 19.020081%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.492187" stop-color="rgb(19.946289%, 18.489075%, 18.852234%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(19.781494%, 18.322754%, 18.684387%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.507812" stop-color="rgb(19.616699%, 18.154907%, 18.518066%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.515625" stop-color="rgb(19.451904%, 17.985535%, 18.35022%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.523437" stop-color="rgb(19.287109%, 17.817688%, 18.182373%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.53125" stop-color="rgb(19.120789%, 17.649841%, 18.014526%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.539063" stop-color="rgb(18.954468%, 17.478943%, 17.845154%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.546875" stop-color="rgb(18.788147%, 17.30957%, 17.677307%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.554687" stop-color="rgb(18.621826%, 17.141724%, 17.50946%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5625" stop-color="rgb(18.457031%, 16.972351%, 17.341614%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.570313" stop-color="rgb(18.29071%, 16.802979%, 17.172241%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.578125" stop-color="rgb(18.12439%, 16.635132%, 17.004395%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.585937" stop-color="rgb(17.958069%, 16.464233%, 16.835022%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.59375" stop-color="rgb(17.791748%, 16.294861%, 16.667175%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.601562" stop-color="rgb(17.623901%, 16.125488%, 16.497803%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.609375" stop-color="rgb(17.457581%, 15.95459%, 16.32843%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.617188" stop-color="rgb(17.289734%, 15.785217%, 16.159058%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(17.123413%, 15.614319%, 15.989685%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.632812" stop-color="rgb(16.955566%, 15.444946%, 15.820312%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.640625" stop-color="rgb(16.789246%, 15.274048%, 15.65094%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.648437" stop-color="rgb(16.621399%, 15.104675%, 15.481567%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.65625" stop-color="rgb(16.453552%, 14.932251%, 15.310669%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.664062" stop-color="rgb(16.285706%, 14.762878%, 15.141296%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.671875" stop-color="rgb(16.117859%, 14.590454%, 14.970398%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.679687" stop-color="rgb(15.950012%, 14.421082%, 14.801025%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.6875" stop-color="rgb(15.78064%, 14.247131%, 14.628601%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.695312" stop-color="rgb(15.611267%, 14.076233%, 14.457703%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.703125" stop-color="rgb(15.44342%, 13.903809%, 14.286804%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.710937" stop-color="rgb(15.274048%, 13.73291%, 14.115906%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.71875" stop-color="rgb(15.106201%, 13.560486%, 13.945007%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.726562" stop-color="rgb(14.938354%, 13.389587%, 13.774109%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.734375" stop-color="rgb(14.768982%, 13.218689%, 13.60321%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.742187" stop-color="rgb(14.599609%, 13.044739%, 13.430786%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(14.430237%, 12.87384%, 13.259888%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.757813" stop-color="rgb(14.260864%, 12.701416%, 13.088989%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.765625" stop-color="rgb(14.089966%, 12.52594%, 12.915039%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.78125" stop-color="rgb(13.867188%, 12.30011%, 12.689209%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.8125" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.875" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip-23">
|
||||||
|
<path clip-rule="nonzero" d="M 1560 1386 L 1833 1386 L 1833 1627 L 1560 1627 Z M 1560 1386 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-24">
|
||||||
|
<path clip-rule="nonzero" d="M 1560.351562 1626.539062 C 1560.351562 1626.539062 1723.460938 1522.328125 1777.828125 1463.429688 C 1832.199219 1404.53125 1832.199219 1386.410156 1832.199219 1386.410156 C 1832.199219 1386.410156 1768.769531 1554.050781 1560.351562 1626.539062 Z M 1560.351562 1626.539062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-25">
|
||||||
|
<path clip-rule="nonzero" d="M 1560.351562 1626.539062 C 1560.351562 1626.539062 1723.460938 1522.328125 1777.828125 1463.429688 C 1832.199219 1404.53125 1832.199219 1386.410156 1832.199219 1386.410156 C 1832.199219 1386.410156 1768.769531 1554.050781 1560.351562 1626.539062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-4" gradientUnits="userSpaceOnUse" x1="0.865715" y1="0" x2="1.009384" y2="0" gradientTransform="matrix(2022.24, -175.847, 175.847, 2022.24, -199.675, 1671.34)">
|
||||||
|
<stop offset="0" stop-color="rgb(25.004578%, 23.638916%, 23.977661%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.015625" stop-color="rgb(24.913025%, 23.547363%, 23.886108%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.03125" stop-color="rgb(24.729919%, 23.35968%, 23.699951%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.046875" stop-color="rgb(24.546814%, 23.173523%, 23.51532%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.0625" stop-color="rgb(24.362183%, 22.98584%, 23.327637%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.078125" stop-color="rgb(24.177551%, 22.798157%, 23.139954%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.09375" stop-color="rgb(23.994446%, 22.610474%, 22.955322%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.109375" stop-color="rgb(23.809814%, 22.422791%, 22.767639%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(23.625183%, 22.235107%, 22.581482%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.140625" stop-color="rgb(23.442078%, 22.04895%, 22.393799%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(23.25592%, 21.859741%, 22.206116%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(23.071289%, 21.672058%, 22.019958%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(22.886658%, 21.482849%, 21.83075%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(22.7005%, 21.29364%, 21.643066%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(22.514343%, 21.104431%, 21.455383%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(22.329712%, 20.915222%, 21.266174%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(22.143555%, 20.726013%, 21.078491%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(21.957397%, 20.53833%, 20.890808%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(21.77124%, 20.347595%, 20.701599%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(21.585083%, 20.158386%, 20.51239%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(21.398926%, 19.967651%, 20.323181%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(21.211243%, 19.778442%, 20.133972%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(21.025085%, 19.587708%, 19.944763%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(20.837402%, 19.396973%, 19.755554%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(20.649719%, 19.204712%, 19.564819%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.390625" stop-color="rgb(20.462036%, 19.015503%, 19.374084%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.40625" stop-color="rgb(20.275879%, 18.824768%, 19.184875%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.421875" stop-color="rgb(20.08667%, 18.632507%, 18.994141%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.4375" stop-color="rgb(19.900513%, 18.443298%, 18.804932%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.453125" stop-color="rgb(19.71283%, 18.251038%, 18.614197%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.46875" stop-color="rgb(19.522095%, 18.058777%, 18.421936%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.484375" stop-color="rgb(19.334412%, 17.866516%, 18.231201%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(19.146729%, 17.674255%, 18.040466%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.515625" stop-color="rgb(18.955994%, 17.481995%, 17.848206%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.53125" stop-color="rgb(18.768311%, 17.289734%, 17.657471%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.546875" stop-color="rgb(18.579102%, 17.097473%, 17.46521%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5625" stop-color="rgb(18.389893%, 16.903687%, 17.272949%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.578125" stop-color="rgb(18.200684%, 16.712952%, 17.082214%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.59375" stop-color="rgb(18.011475%, 16.519165%, 16.889954%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.609375" stop-color="rgb(17.819214%, 16.323853%, 16.694641%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.625" stop-color="rgb(17.630005%, 16.130066%, 16.50238%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.640625" stop-color="rgb(17.440796%, 15.937805%, 16.31012%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.65625" stop-color="rgb(17.248535%, 15.744019%, 16.117859%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.671875" stop-color="rgb(17.059326%, 15.550232%, 15.925598%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.6875" stop-color="rgb(16.868591%, 15.356445%, 15.731812%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.703125" stop-color="rgb(16.677856%, 15.161133%, 15.538025%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.71875" stop-color="rgb(16.487122%, 14.967346%, 15.344238%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.734375" stop-color="rgb(16.294861%, 14.772034%, 15.150452%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.75" stop-color="rgb(16.1026%, 14.575195%, 14.955139%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.765625" stop-color="rgb(15.911865%, 14.381409%, 14.761353%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.78125" stop-color="rgb(15.719604%, 14.186096%, 14.567566%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.796875" stop-color="rgb(15.52887%, 13.990784%, 14.373779%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.8125" stop-color="rgb(15.336609%, 13.795471%, 14.178467%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.828125" stop-color="rgb(15.144348%, 13.600159%, 13.983154%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.84375" stop-color="rgb(14.952087%, 13.404846%, 13.789368%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.859375" stop-color="rgb(14.759827%, 13.208008%, 13.594055%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.875" stop-color="rgb(14.564514%, 13.009644%, 13.397217%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.890625" stop-color="rgb(14.372253%, 12.814331%, 13.201904%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.90625" stop-color="rgb(14.179993%, 12.617493%, 13.005066%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.921875" stop-color="rgb(13.986206%, 12.420654%, 12.809753%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.9375" stop-color="rgb(13.809204%, 12.240601%, 12.631226%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip-26">
|
||||||
|
<path clip-rule="nonzero" d="M 834 1349 L 1147 1349 L 1147 1548 L 834 1548 Z M 834 1349 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-27">
|
||||||
|
<path clip-rule="nonzero" d="M 834.441406 1349.289062 C 834.441406 1349.289062 978.414062 1541.25 1014.410156 1547.25 C 1050.398438 1553.25 1146.378906 1511.25 1146.378906 1511.25 C 1146.378906 1511.25 1056.398438 1529.25 1008.410156 1505.261719 C 960.417969 1481.261719 834.441406 1349.289062 834.441406 1349.289062 Z M 834.441406 1349.289062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="clip-28">
|
||||||
|
<path clip-rule="nonzero" d="M 834.441406 1349.289062 C 834.441406 1349.289062 978.414062 1541.25 1014.410156 1547.25 C 1050.398438 1553.25 1146.378906 1511.25 1146.378906 1511.25 C 1146.378906 1511.25 1056.398438 1529.25 1008.410156 1505.261719 C 960.417969 1481.261719 834.441406 1349.289062 834.441406 1349.289062 "/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="linear-pattern-5" gradientUnits="userSpaceOnUse" x1="-0.780602" y1="0" x2="4.017965" y2="0" gradientTransform="matrix(30.003, -60.006, 60.006, 30.003, 933.445, 1544.2)">
|
||||||
|
<stop offset="0" stop-color="rgb(82.745361%, 82.43103%, 82.50885%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.125" stop-color="rgb(82.745361%, 82.43103%, 82.50885%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.15625" stop-color="rgb(82.745361%, 82.43103%, 82.50885%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.160156" stop-color="rgb(82.574463%, 82.25708%, 82.336426%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.164062" stop-color="rgb(82.402039%, 82.081604%, 82.16095%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.167969" stop-color="rgb(81.422424%, 81.085205%, 81.169128%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.171875" stop-color="rgb(80.429077%, 80.072021%, 80.160522%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.175781" stop-color="rgb(79.423523%, 79.049683%, 79.142761%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.179688" stop-color="rgb(78.407288%, 78.013611%, 78.111267%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.183594" stop-color="rgb(77.378845%, 76.966858%, 77.069092%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.1875" stop-color="rgb(76.33667%, 75.904846%, 76.011658%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.191406" stop-color="rgb(75.282288%, 74.833679%, 74.945068%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.195312" stop-color="rgb(74.217224%, 73.747253%, 73.864746%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.199219" stop-color="rgb(73.139954%, 72.651672%, 72.772217%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.203125" stop-color="rgb(72.050476%, 71.540833%, 71.66748%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.207031" stop-color="rgb(70.948792%, 70.420837%, 70.552063%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.210938" stop-color="rgb(69.8349%, 69.285583%, 69.421387%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.214844" stop-color="rgb(68.708801%, 68.139648%, 68.281555%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.21875" stop-color="rgb(67.56897%, 66.978455%, 67.124939%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.222656" stop-color="rgb(66.419983%, 65.808105%, 65.960693%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.226562" stop-color="rgb(65.257263%, 64.625549%, 64.782715%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.230469" stop-color="rgb(64.083862%, 63.42926%, 63.592529%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.234375" stop-color="rgb(62.895203%, 62.219238%, 62.387085%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.238281" stop-color="rgb(61.697388%, 61.000061%, 61.172485%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.242187" stop-color="rgb(60.484314%, 59.765625%, 59.944153%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.246094" stop-color="rgb(59.262085%, 58.522034%, 58.705139%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.25" stop-color="rgb(58.026123%, 57.263184%, 57.452393%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.253906" stop-color="rgb(56.77948%, 55.993652%, 56.188965%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.257813" stop-color="rgb(55.519104%, 54.708862%, 54.910278%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.261719" stop-color="rgb(54.248047%, 53.416443%, 53.622437%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.265625" stop-color="rgb(52.964783%, 52.108765%, 52.322388%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.269531" stop-color="rgb(51.669312%, 50.790405%, 51.008606%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.273438" stop-color="rgb(50.361633%, 49.458313%, 49.682617%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.277344" stop-color="rgb(49.041748%, 48.11554%, 48.345947%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.28125" stop-color="rgb(47.70813%, 46.757507%, 46.994019%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.285156" stop-color="rgb(46.365356%, 45.388794%, 45.631409%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.289062" stop-color="rgb(45.007324%, 44.006348%, 44.255066%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.292969" stop-color="rgb(43.640137%, 42.614746%, 42.869568%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.296875" stop-color="rgb(42.25769%, 41.20636%, 41.467285%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.300781" stop-color="rgb(40.866089%, 39.790344%, 40.057373%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.304688" stop-color="rgb(39.460754%, 38.35907%, 38.632202%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.308594" stop-color="rgb(38.044739%, 36.917114%, 37.19635%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.3125" stop-color="rgb(36.613464%, 35.4599%, 35.746765%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.316406" stop-color="rgb(35.173035%, 33.99353%, 34.286499%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.320312" stop-color="rgb(33.720398%, 32.513428%, 32.8125%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.324219" stop-color="rgb(32.254028%, 31.021118%, 31.32782%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.328125" stop-color="rgb(30.776978%, 29.516602%, 29.829407%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.332031" stop-color="rgb(29.28772%, 27.999878%, 28.320312%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.335938" stop-color="rgb(27.784729%, 26.469421%, 26.795959%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.339844" stop-color="rgb(26.271057%, 24.928284%, 25.262451%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.34375" stop-color="rgb(24.743652%, 23.373413%, 23.713684%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.347656" stop-color="rgb(23.205566%, 21.807861%, 22.155762%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.351562" stop-color="rgb(21.655273%, 20.230103%, 20.584106%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.355469" stop-color="rgb(20.092773%, 18.638611%, 19.000244%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.359375" stop-color="rgb(18.516541%, 17.033386%, 17.402649%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.363281" stop-color="rgb(16.931152%, 15.419006%, 15.794373%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.367187" stop-color="rgb(15.330505%, 13.790894%, 14.173889%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.371094" stop-color="rgb(14.532471%, 12.9776%, 13.363647%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.375" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="rgb(13.729858%, 12.159729%, 12.548828%)" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#clip-0)">
|
||||||
|
<g clip-path="url(#clip-1)">
|
||||||
|
<g clip-path="url(#clip-2)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#radial-pattern-0)" d="M 2080.265625 1487.285156 L 380.058594 1653.160156 L 414.617188 2007.375 L 2114.820312 1841.5 Z M 2080.265625 1487.285156 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path fill-rule="nonzero" fill="rgb(100%, 100%, 100%)" fill-opacity="1" d="M 1975.851562 980.382812 C 1975.851562 1411.570312 1626.300781 1761.121094 1195.121094 1761.121094 C 763.925781 1761.121094 414.378906 1411.570312 414.378906 980.382812 C 414.378906 549.191406 763.925781 199.648438 1195.121094 199.648438 C 1626.300781 199.648438 1975.851562 549.191406 1975.851562 980.382812 "/>
|
||||||
|
<path fill="none" stroke-width="45.3039" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 19758.515625 9680.371875 C 19758.515625 5368.496875 16263.007812 1872.989063 11951.210938 1872.989063 C 7639.257812 1872.989063 4143.789062 5368.496875 4143.789062 9680.371875 C 4143.789062 13992.285938 7639.257812 17487.715625 11951.210938 17487.715625 C 16263.007812 17487.715625 19758.515625 13992.285938 19758.515625 9680.371875 Z M 19758.515625 9680.371875 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 755.667969 429.191406 C 755.667969 429.191406 882.519531 374.820312 967.085938 356.699219 C 1051.648438 338.578125 1063.730469 356.699219 1075.808594 380.859375 C 1087.898438 405.019531 1148.300781 559.058594 1148.300781 559.058594 C 1148.300781 559.058594 918.761719 719.128906 906.679688 767.449219 L 665.058594 685.910156 C 665.058594 685.910156 698.28125 486.570312 755.667969 429.191406 "/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 11483.007812 13893.614063 C 11483.007812 13893.614063 13174.414062 13682.207813 14624.101562 12836.504688 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 9066.796875 11809.707813 C 9066.796875 11809.707813 9187.617188 9846.504688 9610.46875 8577.989063 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 12630.703125 7490.684375 C 12630.703125 7490.684375 14654.296875 8970.60625 15228.085938 9635.059375 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 18369.21875 11447.207813 C 18369.21875 11447.207813 18943.007812 11779.5125 19547.109375 11084.785938 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 12721.289062 17004.5125 C 12721.289062 17004.5125 12117.304688 17185.684375 10486.289062 15887.0125 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 6861.992188 12715.684375 C 6861.992188 12715.684375 5261.289062 11719.082813 4989.453125 10239.082813 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 5502.890625 7249.082813 C 5502.890625 7249.082813 6318.359375 6373.184375 7919.101562 6192.0125 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 9519.84375 2537.48125 L 9942.695312 4168.41875 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 12751.484375 4651.582813 C 12751.484375 4651.582813 14140.78125 3866.3875 15288.515625 3805.996875 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 16617.382812 13984.2 C 16315.390625 15313.184375 15741.601562 16007.79375 15741.601562 16007.79375 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 17704.6875 8970.60625 C 17704.6875 8970.60625 17946.40625 8155.098438 17886.015625 6886.582813 C 17825.507812 5618.10625 17825.507812 5618.10625 17825.507812 5618.10625 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill="none" stroke-width="30.2026" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(13.729858%, 12.159729%, 12.548828%)" stroke-opacity="1" stroke-miterlimit="10" d="M 6771.40625 15584.98125 C 6771.40625 15584.98125 7043.242188 15705.801563 7556.679688 15192.285938 " transform="matrix(0.1, 0, 0, -0.1, 0, 1948.42)"/>
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 1451.949219 657.351562 C 1451.949219 657.351562 1569.730469 602.988281 1596.921875 587.128906 C 1624.101562 571.28125 1671.660156 548.628906 1671.660156 548.628906 C 1671.660156 548.628906 1846.070312 766.078125 1850.601562 786.460938 C 1855.140625 806.851562 1859.671875 831.761719 1827.949219 926.898438 C 1796.238281 1022.03125 1791.710938 1053.738281 1775.859375 1060.539062 C 1760 1067.328125 1510.839844 990.316406 1510.839844 990.316406 C 1510.839844 990.316406 1508.578125 784.199219 1451.949219 657.351562 "/>
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 958.921875 1086.210938 C 958.921875 1086.210938 1044.238281 1123.960938 1134.839844 1155.671875 C 1225.441406 1187.378906 1282.070312 1189.648438 1282.070312 1189.648438 L 1288.871094 1493.171875 C 1288.871094 1493.171875 1121.25 1545.261719 1064.621094 1558.851562 C 1008 1572.441406 987.609375 1576.96875 924.1875 1513.550781 C 860.765625 1450.128906 772.425781 1364.058594 799.609375 1300.628906 C 826.789062 1237.210938 958.921875 1086.210938 958.921875 1086.210938 "/>
|
||||||
|
<g clip-path="url(#clip-3)">
|
||||||
|
<g clip-path="url(#clip-4)">
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 1214.121094 197.539062 C 1214.121094 197.539062 1250.359375 220.191406 1254.890625 226.988281 C 1259.421875 233.78125 1261.679688 249.640625 1261.679688 249.640625 C 1261.679688 249.640625 1345.488281 245.109375 1436.101562 279.078125 C 1526.699219 313.058594 1567.46875 360.628906 1567.46875 360.628906 L 1667.128906 356.101562 L 1680.71875 351.570312 L 1569.730469 263.230469 L 1374.941406 188.480469 L 1243.558594 172.621094 L 1184.671875 172.621094 L 1214.121094 197.539062 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-5)">
|
||||||
|
<g clip-path="url(#clip-6)">
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 502.882812 915.570312 C 502.882812 915.570312 441.726562 899.710938 416.808594 881.589844 C 391.894531 863.46875 391.894531 863.46875 391.894531 863.46875 C 391.894531 863.46875 389.628906 1096.78125 407.75 1162.460938 C 425.871094 1228.148438 428.136719 1284.78125 428.136719 1284.78125 L 480.230469 1359.53125 L 534.59375 1420.679688 C 534.59375 1420.679688 516.472656 1323.289062 530.0625 1284.78125 C 543.652344 1246.269531 561.773438 1228.148438 561.773438 1228.148438 C 561.773438 1228.148438 493.820312 1119.429688 502.882812 915.570312 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-7)">
|
||||||
|
<g clip-path="url(#clip-8)">
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 815.464844 1663.050781 C 815.464844 1663.050781 937.777344 1685.699219 955.898438 1681.171875 C 955.898438 1681.171875 992.140625 1719.671875 1030.648438 1740.058594 C 1069.148438 1760.449219 1069.148438 1760.449219 1069.148438 1760.449219 L 951.367188 1755.921875 C 951.367188 1755.921875 901.535156 1724.199219 894.742188 1721.941406 C 887.945312 1719.671875 824.523438 1692.488281 824.523438 1692.488281 L 815.464844 1663.050781 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-9)">
|
||||||
|
<g clip-path="url(#clip-10)">
|
||||||
|
<path fill-rule="nonzero" fill="rgb(6.266785%, 5.734253%, 5.047607%)" fill-opacity="1" d="M 1524.429688 1552.058594 C 1524.429688 1552.058594 1531.230469 1606.421875 1515.371094 1644.929688 C 1499.519531 1683.429688 1476.871094 1724.199219 1476.871094 1724.199219 C 1476.871094 1724.199219 1687.519531 1624.539062 1746.410156 1556.589844 C 1805.300781 1488.640625 1889.109375 1359.53125 1889.109375 1359.53125 C 1889.109375 1359.53125 1855.140625 1377.648438 1780.390625 1355 C 1780.390625 1355 1653.539062 1524.878906 1524.429688 1552.058594 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-11)">
|
||||||
|
<g clip-path="url(#clip-12)">
|
||||||
|
<g clip-path="url(#clip-13)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-0)" d="M 1412.566406 579.699219 L 1493.851562 1015.472656 L 1693.082031 978.3125 L 1611.796875 542.535156 Z M 1412.566406 579.699219 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-14)">
|
||||||
|
<g clip-path="url(#clip-15)">
|
||||||
|
<g clip-path="url(#clip-16)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-1)" d="M 1316.875 1126.984375 L 989.945312 1033.574219 L 923.324219 1266.753906 L 1250.25 1360.164062 Z M 1316.875 1126.984375 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-17)">
|
||||||
|
<g clip-path="url(#clip-18)">
|
||||||
|
<g clip-path="url(#clip-19)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-2)" d="M 783.929688 774.484375 L 1018.773438 747.800781 L 1004.703125 623.972656 L 769.859375 650.660156 Z M 783.929688 774.484375 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-20)">
|
||||||
|
<g clip-path="url(#clip-21)">
|
||||||
|
<g clip-path="url(#clip-22)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-3)" d="M 386.121094 931.613281 L 419.324219 1313.453125 L 550.710938 1302.027344 L 517.507812 920.1875 Z M 386.121094 931.613281 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-23)">
|
||||||
|
<g clip-path="url(#clip-24)">
|
||||||
|
<g clip-path="url(#clip-25)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-4)" d="M 1539.625 1388.210938 L 1562.390625 1650 L 1852.925781 1624.738281 L 1830.160156 1362.949219 Z M 1539.625 1388.210938 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#clip-26)">
|
||||||
|
<g clip-path="url(#clip-27)">
|
||||||
|
<g clip-path="url(#clip-28)">
|
||||||
|
<path fill-rule="nonzero" fill="url(#linear-pattern-5)" d="M 752.859375 1512.457031 L 1083.992188 1678.023438 L 1227.964844 1390.082031 L 896.832031 1224.515625 Z M 752.859375 1512.457031 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 595.3 595.3" enable-background="new 0 0 595.3 595.3" xml:space="preserve">
|
||||||
|
<g id="Capa_1">
|
||||||
|
</g>
|
||||||
|
<g id="Capa_2">
|
||||||
|
<g>
|
||||||
|
<circle fill="#F1EA71" cx="297" cy="296" r="230.6"/>
|
||||||
|
<path fill="#CFC761" d="M180.7,96.9c0,0-183.5,109.7-85.4,310.7c0,0,50.9,116.4,201.7,119c0,0-163.1-66-178.6-214.8
|
||||||
|
C118.4,311.8,100,210.3,180.7,96.9z"/>
|
||||||
|
|
||||||
|
<path fill="none" stroke="#FFFFFF" stroke-width="23.9535" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||||
|
M84.7,234.5c0,0,49.3-2.8,77.7,62.1c0,0,40.2,145.8,182,103.3c0,0,130.1-49.4,35.8-210.1c0,0-25.7-34.8-14.3-61.2l13.8-37.3"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 872 B |
+2391
File diff suppressed because it is too large
Load Diff
+272
@@ -0,0 +1,272 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="..\css\Login.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Iniciar Sesión</h1>
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="input-box">
|
||||||
|
<input type="text" id="usuario" name="usuario" placeholder="Usuario" required>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<input type="password" id="clave" name="clave" placeholder="Contraseña" required>
|
||||||
|
</div>
|
||||||
|
<div class="remember-password">
|
||||||
|
<!--<label><input type="checkbox">Recordarme</label>-->
|
||||||
|
<a href="#" id="btnOlvidoContrasena">¿Olvidaste tu contraseña?</a>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btnI">Ingresar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de Recuperación de Contraseña -->
|
||||||
|
<div id="modalRecuperacion" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close" id="cerrarModal">×</span>
|
||||||
|
|
||||||
|
<!-- Paso 1: Solicitar Correo -->
|
||||||
|
<div id="paso1" class="paso-recuperacion">
|
||||||
|
<h2>Recuperar Contraseña</h2>
|
||||||
|
<p>Ingresa tu correo para recibir un código de verificación</p>
|
||||||
|
<input type="email" id="correoRecuperacion" placeholder="Correo electrónico" required>
|
||||||
|
<button id="btnEnviarCodigo" class="btnRecuperar">Enviar Código</button>
|
||||||
|
<p id="msgPaso1" class="mensaje"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paso 2: Verificar Código y Nueva Contraseña -->
|
||||||
|
<div id="paso2" class="paso-recuperacion" style="display: none;">
|
||||||
|
<h2>Verificar Código</h2>
|
||||||
|
<p>Ingresa el código que recibiste en tu correo</p>
|
||||||
|
<input type="text" id="codigoVerificacion" placeholder="Código de 6 dígitos" maxlength="6" required>
|
||||||
|
<input type="password" id="nuevaContrasena" placeholder="Nueva contraseña" required>
|
||||||
|
<input type="password" id="confirmarContrasena" placeholder="Confirmar contraseña" required>
|
||||||
|
<div class="botones-grupo">
|
||||||
|
<button id="btnCambiarContrasena" class="btnRecuperar">Cambiar Contraseña</button>
|
||||||
|
<button id="btnVolver" class="btnVolver">Volver</button>
|
||||||
|
</div>
|
||||||
|
<p id="msgPaso2" class="mensaje"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Variables globales para el flujo de recuperación
|
||||||
|
let correoRecuperacion = '';
|
||||||
|
|
||||||
|
// Elementos del modal
|
||||||
|
const modalRecuperacion = document.getElementById('modalRecuperacion');
|
||||||
|
const btnOlvidoContrasena = document.getElementById('btnOlvidoContrasena');
|
||||||
|
const cerrarModal = document.getElementById('cerrarModal');
|
||||||
|
const paso1 = document.getElementById('paso1');
|
||||||
|
const paso2 = document.getElementById('paso2');
|
||||||
|
|
||||||
|
// Botones
|
||||||
|
const btnEnviarCodigo = document.getElementById('btnEnviarCodigo');
|
||||||
|
const btnCambiarContrasena = document.getElementById('btnCambiarContrasena');
|
||||||
|
const btnVolver = document.getElementById('btnVolver');
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
const correoInput = document.getElementById('correoRecuperacion');
|
||||||
|
const codigoInput = document.getElementById('codigoVerificacion');
|
||||||
|
const nuevaContrasenaInput = document.getElementById('nuevaContrasena');
|
||||||
|
const confirmarContrasenaInput = document.getElementById('confirmarContrasena');
|
||||||
|
|
||||||
|
// Mensajes
|
||||||
|
const msgPaso1 = document.getElementById('msgPaso1');
|
||||||
|
const msgPaso2 = document.getElementById('msgPaso2');
|
||||||
|
|
||||||
|
// Abrir modal
|
||||||
|
btnOlvidoContrasena.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
modalRecuperacion.style.display = 'block';
|
||||||
|
correoInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cerrar modal
|
||||||
|
cerrarModal.addEventListener('click', () => {
|
||||||
|
modalRecuperacion.style.display = 'none';
|
||||||
|
limpiarModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modalRecuperacion) {
|
||||||
|
modalRecuperacion.style.display = 'none';
|
||||||
|
limpiarModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Paso 1: Enviar Código
|
||||||
|
btnEnviarCodigo.addEventListener('click', async () => {
|
||||||
|
const correo = correoInput.value.trim();
|
||||||
|
|
||||||
|
if (!correo) {
|
||||||
|
msgPaso1.textContent = 'Por favor ingresa un correo';
|
||||||
|
msgPaso1.className = 'mensaje error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnEnviarCodigo.disabled = true;
|
||||||
|
btnEnviarCodigo.textContent = 'Enviando...';
|
||||||
|
msgPaso1.textContent = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Recuperar_Contrasena_Solicitar.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ correo })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
correoRecuperacion = correo;
|
||||||
|
msgPaso1.textContent = data.message;
|
||||||
|
msgPaso1.className = 'mensaje success';
|
||||||
|
|
||||||
|
// Cambiar al paso 2 después de 1.5 segundos
|
||||||
|
setTimeout(() => {
|
||||||
|
paso1.style.display = 'none';
|
||||||
|
paso2.style.display = 'block';
|
||||||
|
codigoInput.focus();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
msgPaso1.textContent = data.message;
|
||||||
|
msgPaso1.className = 'mensaje error';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
msgPaso1.textContent = 'Error al conectar con el servidor';
|
||||||
|
msgPaso1.className = 'mensaje error';
|
||||||
|
console.error('Error:', error);
|
||||||
|
} finally {
|
||||||
|
btnEnviarCodigo.disabled = false;
|
||||||
|
btnEnviarCodigo.textContent = 'Enviar Código';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Paso 2: Cambiar Contraseña
|
||||||
|
btnCambiarContrasena.addEventListener('click', async () => {
|
||||||
|
const codigo = codigoInput.value.trim();
|
||||||
|
const nuevaContrasena = nuevaContrasenaInput.value;
|
||||||
|
const confirmarContrasena = confirmarContrasenaInput.value;
|
||||||
|
|
||||||
|
if (!codigo || !nuevaContrasena || !confirmarContrasena) {
|
||||||
|
msgPaso2.textContent = 'Por favor completa todos los campos';
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codigo.length !== 6 || isNaN(codigo)) {
|
||||||
|
msgPaso2.textContent = 'El código debe tener 6 dígitos';
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nuevaContrasena.length < 6) {
|
||||||
|
msgPaso2.textContent = 'La contraseña debe tener al menos 6 caracteres';
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nuevaContrasena !== confirmarContrasena) {
|
||||||
|
msgPaso2.textContent = 'Las contraseñas no coinciden';
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnCambiarContrasena.disabled = true;
|
||||||
|
btnCambiarContrasena.textContent = 'Cambiando...';
|
||||||
|
msgPaso2.textContent = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Recuperar_Contrasena_Verificar.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
correo: correoRecuperacion,
|
||||||
|
codigo,
|
||||||
|
nueva_contrasena: nuevaContrasena
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
msgPaso2.textContent = data.message;
|
||||||
|
msgPaso2.className = 'mensaje success';
|
||||||
|
|
||||||
|
// Cerrar modal después de 2 segundos
|
||||||
|
setTimeout(() => {
|
||||||
|
modalRecuperacion.style.display = 'none';
|
||||||
|
limpiarModal();
|
||||||
|
alert('Contraseña cambiada exitosamente. Ahora puedes iniciar sesión');
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
msgPaso2.textContent = data.message;
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
msgPaso2.textContent = 'Error al conectar con el servidor';
|
||||||
|
msgPaso2.className = 'mensaje error';
|
||||||
|
console.error('Error:', error);
|
||||||
|
} finally {
|
||||||
|
btnCambiarContrasena.disabled = false;
|
||||||
|
btnCambiarContrasena.textContent = 'Cambiar Contraseña';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Volver a paso 1
|
||||||
|
btnVolver.addEventListener('click', () => {
|
||||||
|
paso2.style.display = 'none';
|
||||||
|
paso1.style.display = 'block';
|
||||||
|
correoInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Función para limpiar el modal
|
||||||
|
function limpiarModal() {
|
||||||
|
correoInput.value = '';
|
||||||
|
codigoInput.value = '';
|
||||||
|
nuevaContrasenaInput.value = '';
|
||||||
|
confirmarContrasenaInput.value = '';
|
||||||
|
msgPaso1.textContent = '';
|
||||||
|
msgPaso2.textContent = '';
|
||||||
|
paso1.style.display = 'block';
|
||||||
|
paso2.style.display = 'none';
|
||||||
|
correoRecuperacion = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script original de login
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(document.getElementById('loginForm'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Login.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Redirigir al panel
|
||||||
|
window.location.href = '../php/Cantina.php';
|
||||||
|
} else {
|
||||||
|
// Mostrar error sin cambiar de página
|
||||||
|
alert(data.message);
|
||||||
|
// Limpiar campos de contraseña
|
||||||
|
document.getElementById('clave').value = '';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error al conectar con el servidor');
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Reservar Turno - Complejo de Canchas</title>
|
||||||
|
<link rel="stylesheet" href="../css/Reserva_Clientes.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1 class="header-title">Complejo Deportivo Cap1tan</h1>
|
||||||
|
<p class="header-subtitle">Reserva tu turno ahora</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="contenedor-principal">
|
||||||
|
<!-- cancha -->
|
||||||
|
<section class="seccion selector-cancha">
|
||||||
|
<h2>Selecciona el tipo de cancha</h2>
|
||||||
|
<div class="botones-cancha">
|
||||||
|
<button type="button" class="btn-cancha-tipo" data-tipo="futbol">
|
||||||
|
<img src="../css/img/futbol.svg" alt="Fútbol" class="icono">
|
||||||
|
<span class="nombre">Fútbol</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn-cancha-tipo" data-tipo="padel">
|
||||||
|
<img src="../css/img/padel.svg" alt="Pádel" class="icono">
|
||||||
|
<span class="nombre">Pádel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- fecha -->
|
||||||
|
<section class="seccion selector-fecha" style="display: none;">
|
||||||
|
<h2>Elige un día</h2>
|
||||||
|
<div class="calendario-mini">
|
||||||
|
<div class="dias-disponibles" id="dias-disponibles"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- horario -->
|
||||||
|
<section class="seccion selector-horario" style="display: none;">
|
||||||
|
<h2>Selecciona horario</h2>
|
||||||
|
<div class="grid-horarios" id="grid-horarios"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- número de cancha -->
|
||||||
|
<section class="seccion selector-numero-cancha" style="display: none;">
|
||||||
|
<h2>Selecciona tu cancha</h2>
|
||||||
|
<div class="botones-canchas-numeros" id="botones-canchas-numeros"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="seccion datos-cliente" style="display: none;">
|
||||||
|
<!-- Datos del cliente -->
|
||||||
|
<h2>Tus Datos</h2>
|
||||||
|
<div class="datos-cliente">
|
||||||
|
|
||||||
|
<form id="form-reserva" class="form-cliente">
|
||||||
|
<input type="text" id="cliente-nombre" placeholder="Nombre Completo" required />
|
||||||
|
<input type="tel" id="cliente-telefono" placeholder="Teléfono" required />
|
||||||
|
<input type="text" id="cliente-dni" placeholder="DNI" required />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="botones-verificar">
|
||||||
|
<button type="button" class="btn-verificar">Verificar</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- reserva -->
|
||||||
|
<section class="seccion resumen-reserva" style="display: none;">
|
||||||
|
<h2>Resumen de Reserva</h2>
|
||||||
|
|
||||||
|
<div class="resumen-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Cancha:</span>
|
||||||
|
<span class="valor" id="res-cancha">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Fecha:</span>
|
||||||
|
<span class="valor" id="res-fecha">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Horario:</span>
|
||||||
|
<span class="valor" id="res-horario">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Duración:</span>
|
||||||
|
<span class="valor" id="res-duracion">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Cupón:</span>
|
||||||
|
<span class="valor" id="res-cupon">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item" id="precio-original" style="display: none;">
|
||||||
|
<span class="label">Precio Original:</span>
|
||||||
|
<span class="valor" id="res-precio-original" style="text-decoration: line-through; color: #888;">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item total">
|
||||||
|
<span class="label" id="label-precio">Precio:</span>
|
||||||
|
<span class="valor" id="res-precio">$0.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- quincho -->
|
||||||
|
<div class="opciones-adicionales">
|
||||||
|
<h3>Opciones Adicionales</h3>
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" id="incluir-quincho" />
|
||||||
|
<span class="checkbox-text">Incluir Quincho</span>
|
||||||
|
<span class="precio-quincho" style="display: none;">
|
||||||
|
+ $<span id="precio-quincho-val">0</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div id="quincho-reservas" style="display: none;" class="quincho-info">
|
||||||
|
<small>Reservas disponibles: <strong><span id="quincho-disp">-</span></strong></small>
|
||||||
|
</div>
|
||||||
|
<div id="quincho-no-disponible" style="display: none;" class="quincho-alerta">
|
||||||
|
<small>⚠️ No hay quinchos disponibles para esta fecha</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="seccion-verificacion" class="datos-cliente" style="display: none; border: 2px solid #4CAF50; padding: 15px; border-radius: 8px; margin-top: 15px;">
|
||||||
|
<h3>Verificación por WhatsApp</h3>
|
||||||
|
<p>Enviamos un código de 4 dígitos al <strong id="lbl-telefono"></strong>.</p>
|
||||||
|
<input type="text" id="codigo-verificacion" placeholder="Ingrese el código" maxlength="4" style="text-align: center; font-size: 20px; letter-spacing: 5px; margin-bottom: 10px; width: 100%;" />
|
||||||
|
<button type="button" class="btn-confirmar" id="btn-verificar-codigo" style="width: 100%;">Validar y Reservar</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="botones-reserva">
|
||||||
|
<button type="button" class="btn-volver">← Volver</button>
|
||||||
|
<button type="button" class="btn-confirmar" id="btn-solicitar-reserva">Confirmar Reserva</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="seccion-comprobante" class="seccion" style="display: none; text-align: center;">
|
||||||
|
<h2>¡Reserva Confirmada!</h2>
|
||||||
|
<p>Para abonar tu turno, podes pagar en efectivo o realizar una transferencia a este alias: <strong>ejemplo.mp</strong> y adjuntar la foto o captura de tu comprobante.</p>
|
||||||
|
|
||||||
|
<div class="datos-cliente" style="margin-top: 20px;">
|
||||||
|
<form id="form-comprobante" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" id="comprobante-id-reserva" value="" />
|
||||||
|
|
||||||
|
<input type="file" id="input-file-comprobante" accept="image/*" required style="margin-bottom: 15px; width: 100%; padding: 10px;" />
|
||||||
|
|
||||||
|
<button type="submit" class="btn-confirmar" style="width: 100%;">Enviar Comprobante</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="../js/Reservar_Turno.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
cargarConfiguracionCanchas();
|
||||||
|
cargarHorarios();
|
||||||
|
|
||||||
|
// Configurar el guardado para cada formulario
|
||||||
|
const formularios = document.querySelectorAll(
|
||||||
|
".cancha-formulario[data-tipo]"
|
||||||
|
);
|
||||||
|
|
||||||
|
formularios.forEach((form) => {
|
||||||
|
form.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const T = form.dataset.tipo; // Obtiene 'F', 'P' o 'Q'
|
||||||
|
|
||||||
|
// Capturamos los valores dinámicamente usando el prefijo del tipo
|
||||||
|
const datos = {
|
||||||
|
tipo: T,
|
||||||
|
precio: document.getElementById(`precio-${T}`)?.value || 0,
|
||||||
|
duracion: document.getElementById(`duracion-${T}`)?.value || 0,
|
||||||
|
cantidad: document.getElementById(`cantidad-${T}`)?.value || 0,
|
||||||
|
reservas_cupon:
|
||||||
|
document.getElementById(`reservas-cupon-${T}`)?.value || 0,
|
||||||
|
descuento: convertirDescuentoAPorcentaje(
|
||||||
|
document.getElementById(`descuento-${T}`)?.value || 0
|
||||||
|
),
|
||||||
|
faltas: document.getElementById(`faltas-${T}`)?.value || 0,
|
||||||
|
duracion_cupon: document.getElementById(`duracion-cupon-${T}`)?.value || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Caso especial para Quincho que llamaste al input "reservas-Q" en lugar de "reservas-cupon-Q"
|
||||||
|
if (T === "Q") {
|
||||||
|
datos.reservas_cupon =
|
||||||
|
document.getElementById(`reservas-Q`)?.value || 0;
|
||||||
|
}
|
||||||
|
actualizarCancha(datos);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Guardar configuración de Horarios
|
||||||
|
const formHorarios = document.getElementById("form-horarios");
|
||||||
|
if (formHorarios) {
|
||||||
|
formHorarios.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const datosHorarios = [
|
||||||
|
{
|
||||||
|
dia: "Lunes a Viernes",
|
||||||
|
ape: document.getElementById("apertura-l").value,
|
||||||
|
cie: document.getElementById("cierre-l").value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dia: "Sábado",
|
||||||
|
ape: document.getElementById("apertura-s").value,
|
||||||
|
cie: document.getElementById("cierre-s").value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dia: "Domingo",
|
||||||
|
ape: document.getElementById("apertura-d").value,
|
||||||
|
cie: document.getElementById("cierre-d").value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
actualizarHorarios(datosHorarios);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarHorarios() {
|
||||||
|
console.log('Cargando horarios...');
|
||||||
|
fetch("../php/Obtener_Horarios.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Datos de horarios recibidos:', data);
|
||||||
|
data.forEach((h) => {
|
||||||
|
if (h.Dia === "Lunes a Viernes") {
|
||||||
|
setVal("apertura-l", h.Hora_Apertura);
|
||||||
|
setVal("cierre-l", h.Hora_Cierre);
|
||||||
|
} else if (h.Dia === "Sábado") {
|
||||||
|
setVal("apertura-s", h.Hora_Apertura);
|
||||||
|
setVal("cierre-s", h.Hora_Cierre);
|
||||||
|
} else if (h.Dia === "Domingo") {
|
||||||
|
setVal("apertura-d", h.Hora_Apertura);
|
||||||
|
setVal("cierre-d", h.Hora_Cierre);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Horarios cargados exitosamente');
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al cargar horarios:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cargarHorarios = cargarHorarios;
|
||||||
|
|
||||||
|
function normalizarNumero(valor) {
|
||||||
|
if (valor === null || valor === undefined || valor === "") return 0;
|
||||||
|
const texto = String(valor).trim().replace(/\s+/g, "");
|
||||||
|
if (!texto) return 0;
|
||||||
|
const numero = parseFloat(texto.replace(/,/g, "."));
|
||||||
|
return Number.isFinite(numero) ? numero : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertirDescuentoAPorcentaje(valor) {
|
||||||
|
const numero = normalizarNumero(valor);
|
||||||
|
if (numero === 0) return 0;
|
||||||
|
return numero > 1 ? numero / 100 : numero;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertirDescuentoAFormulario(valor) {
|
||||||
|
const numero = normalizarNumero(valor);
|
||||||
|
if (numero === 0) return "";
|
||||||
|
return numero > 1 ? numero : numero * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarHorarios(datos) {
|
||||||
|
fetch("../php/Actualizar_Horarios.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(datos),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((msg) => alert(msg))
|
||||||
|
.catch((err) => console.error("Error:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarConfiguracionCanchas() {
|
||||||
|
console.log('Cargando configuración de canchas...');
|
||||||
|
fetch("../php/Obtener_Canchas.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Datos de canchas recibidos:', data);
|
||||||
|
data.forEach((cancha) => {
|
||||||
|
const T = cancha.Tipo; // 'F', 'P', 'Q'
|
||||||
|
|
||||||
|
// Usamos el operador ?. para no romper el código si un input no existe en un tipo
|
||||||
|
setVal(`precio-${T}`, cancha.Precio);
|
||||||
|
setVal(`duracion-${T}`, cancha.Duracion);
|
||||||
|
setVal(`cantidad-${T}`, cancha.Cant_Canchas);
|
||||||
|
setVal(`reservas-cupon-${T}`, cancha.Cant_Reservas_Cupon);
|
||||||
|
setVal(`descuento-${T}`, convertirDescuentoAFormulario(cancha.Descuento_Cupon));
|
||||||
|
setVal(`faltas-${T}`, cancha.Cant_Faltas);
|
||||||
|
setVal(`duracion-cupon-${T}`, cancha.Duracion_Cupon);
|
||||||
|
|
||||||
|
// Ajuste para ID específico de Quincho
|
||||||
|
if (T === "Q") setVal(`reservas-Q`, cancha.Cant_Reservas_Cupon);
|
||||||
|
});
|
||||||
|
console.log('Configuración de canchas cargada exitosamente');
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al cargar configuración:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cargarConfiguracionCanchas = cargarConfiguracionCanchas;
|
||||||
|
|
||||||
|
// Función auxiliar para asignar valores de forma segura
|
||||||
|
function setVal(id, valor) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.value = valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarCancha(datos) {
|
||||||
|
fetch("../php/Actualizar_Cancha.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(datos),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((msg) => {
|
||||||
|
alert(msg);
|
||||||
|
cargarConfiguracionCanchas(); // Recargar para confirmar cambios
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al actualizar:", err));
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
// Gestion de Usuarios
|
||||||
|
let usuarioEditando = null;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM cargado - Inicializando Gestion_Usuarios.js');
|
||||||
|
cargarUsuarios();
|
||||||
|
|
||||||
|
// Escuchar el envío del formulario
|
||||||
|
const formulario = document.getElementById('form-registrar-usuario');
|
||||||
|
if (formulario) {
|
||||||
|
formulario.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Formulario enviado');
|
||||||
|
if (usuarioEditando) {
|
||||||
|
actualizarUsuario();
|
||||||
|
} else {
|
||||||
|
registrarUsuario();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('No se encontró el formulario con ID form-registrar-usuario');
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnCancelar = document.getElementById('btn-cancelar-edicion');
|
||||||
|
btnCancelar.addEventListener('click', cancelarEdicion);
|
||||||
|
});
|
||||||
|
|
||||||
|
function cargarUsuarios() {
|
||||||
|
console.log('Cargando usuarios...');
|
||||||
|
|
||||||
|
fetch('../php/Obtener_Usuarios.php')
|
||||||
|
.then(response => {
|
||||||
|
console.log('Respuesta status:', response.status);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error HTTP: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Datos recibidos:', data);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.usuarios.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center;">No hay usuarios registrados</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.usuarios.forEach(usuario => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.dataset.user = JSON.stringify(usuario);
|
||||||
|
row.dataset.userId = usuario.ID_Usuario;
|
||||||
|
const esSupremo = usuario.ID_Usuario === 1;
|
||||||
|
const botonEliminar = esSupremo
|
||||||
|
? `<button class="btn-eliminar-usuario" title="El administrador supremo no puede eliminarse" disabled>🔒</button>`
|
||||||
|
: `<button onclick="eliminarUsuario(${usuario.ID_Usuario})" class="btn-eliminar-usuario" title="Eliminar">🗑️</button>`;
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${usuario.Usuario}</td>
|
||||||
|
<td>${usuario.Nombre} ${usuario.Apellido}</td>
|
||||||
|
<td>${usuario.DNI}</td>
|
||||||
|
<td>${usuario.Rol === 'admin' ? 'Administrador' : usuario.Rol === 'cantina' ? 'Trabajador Cantina' : 'Trabajador Cancha'}</td>
|
||||||
|
<td>${usuario.Telefono || '-'}</td>
|
||||||
|
<td>${usuario.Correo || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="editarUsuario(${usuario.ID_Usuario})" class="btn-editar" title="Editar">Editar</button>
|
||||||
|
<button onclick="eliminarUsuario(${usuario.ID_Usuario})" class="btn-eliminar-usuario" title="Eliminar">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Error al cargar usuarios:', data.message);
|
||||||
|
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||||
|
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: red;">Error al cargar usuarios</td></tr>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error en fetch:', error);
|
||||||
|
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||||
|
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: red;">Error de conexión</td></tr>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registrarUsuario() {
|
||||||
|
const formulario = document.getElementById('form-registrar-usuario');
|
||||||
|
const formData = new FormData(formulario);
|
||||||
|
const contraseña = formData.get('contraseña')?.toString().trim() || '';
|
||||||
|
const confirmarContraseña = formData.get('confirmar_contraseña')?.toString().trim() || '';
|
||||||
|
|
||||||
|
// Validar que el rol esté seleccionado
|
||||||
|
if (!formData.get('rol')) {
|
||||||
|
alert('Por favor selecciona un rol');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contraseña.length < 6) {
|
||||||
|
alert('La contraseña debe tener al menos 6 carácteres');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contraseña !== confirmarContraseña) {
|
||||||
|
alert('Las contraseñas no coinciden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Registrando usuario:', {
|
||||||
|
usuario: formData.get('usuario'),
|
||||||
|
rol: formData.get('rol'),
|
||||||
|
nombre: formData.get('nombre')
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('../php/Guardar_Usuario.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Respuesta status:', response.status);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error HTTP: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Respuesta del servidor:', data);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('✅ Usuario registrado exitosamente');
|
||||||
|
formulario.reset();
|
||||||
|
cargarUsuarios();
|
||||||
|
} else {
|
||||||
|
alert('❌ Error: ' + (data.message || 'No se pudo registrar el usuario'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error en fetch:', error);
|
||||||
|
alert('❌ Error al registrar el usuario: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editarUsuario(id) {
|
||||||
|
console.log('Editando usuario con ID:', id);
|
||||||
|
|
||||||
|
// Buscar la fila por el ID del usuario de forma más robusta
|
||||||
|
let row = null;
|
||||||
|
const filas = document.querySelectorAll('#tabla-usuarios tbody tr');
|
||||||
|
|
||||||
|
for (let fila of filas) {
|
||||||
|
if (fila.dataset.userId == id) { // Comparación flexible de tipos
|
||||||
|
row = fila;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row || !row.dataset.user) {
|
||||||
|
console.error('No se encontró el usuario con ID:', id);
|
||||||
|
alert('❌ Error: No se pudo encontrar el usuario. Por favor recarga la página e intenta de nuevo.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const usuario = JSON.parse(row.dataset.user);
|
||||||
|
|
||||||
|
document.getElementById('usuario-id').value = usuario.ID_Usuario;
|
||||||
|
document.getElementById('usuario-username').value = usuario.Usuario;
|
||||||
|
document.getElementById('usuario-password').value = usuario.Contraseña;
|
||||||
|
document.getElementById('usuario-password-confirm').value = usuario.Contraseña;
|
||||||
|
document.querySelector('input[name="nombre"]').value = usuario.Nombre || '';
|
||||||
|
document.querySelector('input[name="apellido"]').value = usuario.Apellido || '';
|
||||||
|
document.querySelector('input[name="dni"]').value = usuario.DNI || '';
|
||||||
|
document.querySelector('input[name="telefono"]').value = usuario.Telefono || '';
|
||||||
|
document.querySelector('input[name="correo"]').value = usuario.Correo || '';
|
||||||
|
document.querySelector('select[name="rol"]').value = usuario.Rol;
|
||||||
|
|
||||||
|
usuarioEditando = id;
|
||||||
|
document.getElementById('btn-registrar-usuario').textContent = 'Guardar Cambios';
|
||||||
|
document.getElementById('btn-cancelar-edicion').style.display = 'inline-block';
|
||||||
|
|
||||||
|
console.log('Usuario cargado para edición:', usuario.Usuario);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al parsear datos del usuario:', error);
|
||||||
|
alert('❌ Error: Los datos del usuario están corruptos. Por favor recarga la página.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelarEdicion() {
|
||||||
|
usuarioEditando = null;
|
||||||
|
document.getElementById('form-registrar-usuario').reset();
|
||||||
|
document.getElementById('usuario-id').value = '';
|
||||||
|
document.getElementById('btn-registrar-usuario').textContent = 'Registrar Usuario';
|
||||||
|
document.getElementById('btn-cancelar-edicion').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarUsuario() {
|
||||||
|
const formulario = document.getElementById('form-registrar-usuario');
|
||||||
|
const formData = new FormData(formulario);
|
||||||
|
const contraseña = formData.get('contraseña')?.toString().trim() || '';
|
||||||
|
const confirmarContraseña = formData.get('confirmar_contraseña')?.toString().trim() || '';
|
||||||
|
|
||||||
|
if (!usuarioEditando) {
|
||||||
|
alert('No hay usuario seleccionado para editar');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contraseña.length < 6) {
|
||||||
|
alert('La contraseña debe tener al menos 6 carácteres');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contraseña !== confirmarContraseña) {
|
||||||
|
alert('Las contraseñas no coinciden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('../php/Actualizar_Usuario.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error HTTP: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('✅ Usuario actualizado exitosamente');
|
||||||
|
cancelarEdicion();
|
||||||
|
cargarUsuarios();
|
||||||
|
} else {
|
||||||
|
alert('❌ Error: ' + (data.message || 'No se pudo actualizar el usuario'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error en fetch:', error);
|
||||||
|
alert('❌ Error al actualizar el usuario: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarUsuario(id) {
|
||||||
|
if (confirm('¿Estás seguro de que deseas eliminar este usuario? Esta acción no se puede deshacer.')) {
|
||||||
|
console.log('Eliminando usuario con ID:', id);
|
||||||
|
|
||||||
|
fetch('../php/Eliminar_Usuario.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: 'id=' + id
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Respuesta status:', response.status);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error HTTP: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Respuesta del servidor:', data);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('✅ Usuario eliminado exitosamente');
|
||||||
|
cargarUsuarios();
|
||||||
|
} else {
|
||||||
|
alert('❌ Error: ' + (data.message || 'No se pudo eliminar el usuario'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error en fetch:', error);
|
||||||
|
alert('❌ Error al eliminar el usuario: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
const productosOrden = [];
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const codigoProveedor = document.getElementById("codigo-proveedor");
|
||||||
|
const nombreProveedor = document.getElementById("nombre-proveedor");
|
||||||
|
const telefonoProveedor = document.getElementById("telefono-proveedor");
|
||||||
|
const codigoProducto = document.getElementById("codigo-producto");
|
||||||
|
const cantidadProducto = document.getElementById("cantidad-producto");
|
||||||
|
const btnAgregar = document.getElementById("btn-agregar-producto");
|
||||||
|
const btnGenerar = document.getElementById("btn-generar-orden");
|
||||||
|
const tabla = document.querySelector("#tabla-orden tbody");
|
||||||
|
const tablaOrdenes = document.querySelector("#tabla-ordenes tbody");
|
||||||
|
|
||||||
|
let ordenDetalleActual = null;
|
||||||
|
let modoEdicionDetalle = false;
|
||||||
|
|
||||||
|
if (codigoProveedor) {
|
||||||
|
codigoProveedor.addEventListener("blur", () => {
|
||||||
|
fetch(`../php/Buscar_Proveedor.php?codigo=${codigoProveedor.value.trim()}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.Nombre && data.Telefono) {
|
||||||
|
nombreProveedor.value = data.Nombre;
|
||||||
|
telefonoProveedor.value = data.Telefono;
|
||||||
|
} else {
|
||||||
|
nombreProveedor.value = "";
|
||||||
|
telefonoProveedor.value = "";
|
||||||
|
alert("Proveedor no encontrado.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnAgregar) {
|
||||||
|
btnAgregar.addEventListener("click", () => {
|
||||||
|
const codigo = codigoProducto.value.trim();
|
||||||
|
const cantidad = parseInt(cantidadProducto.value, 10);
|
||||||
|
|
||||||
|
if (!codigo || cantidad <= 0) {
|
||||||
|
alert("Código y cantidad válidos requeridos.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`../php/Buscar_Producto_Orden.php?codigo=${codigo}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Error en la solicitud al servidor.");
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((producto) => {
|
||||||
|
if (!producto || !producto.ID_Producto) {
|
||||||
|
alert("Producto no encontrado.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
productosOrden.push({
|
||||||
|
id: producto.ID_Producto,
|
||||||
|
descripcion: producto.Descripcion,
|
||||||
|
cantidad: cantidad,
|
||||||
|
});
|
||||||
|
|
||||||
|
actualizarTabla();
|
||||||
|
codigoProducto.value = "";
|
||||||
|
cantidadProducto.value = "";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error al obtener el producto:", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnGenerar) {
|
||||||
|
btnGenerar.addEventListener("click", () => {
|
||||||
|
if (!codigoProveedor.value || productosOrden.length === 0) {
|
||||||
|
alert("Proveedor y al menos un producto son necesarios.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("../php/Registrar_Orden_Compra.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
proveedor: codigoProveedor.value,
|
||||||
|
productos: productosOrden,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((msg) => {
|
||||||
|
alert(msg);
|
||||||
|
productosOrden.length = 0;
|
||||||
|
actualizarTabla();
|
||||||
|
document.getElementById("form-orden").reset();
|
||||||
|
cargarOrdenesCompra();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarTabla() {
|
||||||
|
if (!tabla) return;
|
||||||
|
tabla.innerHTML = "";
|
||||||
|
productosOrden.forEach((p, i) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${p.id}</td>
|
||||||
|
<td>${p.descripcion}</td>
|
||||||
|
<td>${p.cantidad}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn-eliminar" onclick="eliminarProductoOrden(${i})">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tabla.appendChild(fila);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarOrdenesCompra() {
|
||||||
|
if (!tablaOrdenes) return;
|
||||||
|
fetch("../php/Obtener_Ordenes_Compra.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((ordenes) => {
|
||||||
|
tablaOrdenes.innerHTML = "";
|
||||||
|
ordenes.forEach((orden) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
const estadoNumerico = parseInt(orden.Estado, 10);
|
||||||
|
const estaProcesada = estadoNumerico !== 0;
|
||||||
|
|
||||||
|
let textoEstado = "Pendiente";
|
||||||
|
if (estadoNumerico === 1) textoEstado = "Aprobada";
|
||||||
|
if (estadoNumerico === 2) textoEstado = "Denegada";
|
||||||
|
if (estadoNumerico === 3) textoEstado = "Confirmada";
|
||||||
|
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${orden.ID_Orden}</td>
|
||||||
|
<td>${orden.Fecha}</td>
|
||||||
|
<td>${orden.ID_Proveedor || "N/A"}</td>
|
||||||
|
<td>${orden.Proveedor || "Sin nombre"}</td>
|
||||||
|
<td>${orden.Telefono || "Sin registrar"}</td>
|
||||||
|
<td>${textoEstado}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn-ver-orden" data-id="${orden.ID_Orden}" data-estado="${orden.Estado || 0}">Ver Orden</button>
|
||||||
|
<button type="button" class="btn-aprobar"
|
||||||
|
data-id="${orden.ID_Orden}"
|
||||||
|
data-telefono="${orden.Telefono || ''}"
|
||||||
|
data-proveedor="${orden.Proveedor || ''}"
|
||||||
|
${estaProcesada ? "disabled" : ""}>Aprobar</button>
|
||||||
|
<button type="button" class="btn-denegar"
|
||||||
|
data-id="${orden.ID_Orden}"
|
||||||
|
${estaProcesada ? "disabled" : ""}>Denegar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tablaOrdenes.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
tablaOrdenes.querySelectorAll(".btn-aprobar").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
cambiarEstadoOrden(btn.dataset.id, true, btn.dataset.telefono, btn.dataset.proveedor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tablaOrdenes.querySelectorAll(".btn-denegar").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => cambiarEstadoOrden(btn.dataset.id, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
tablaOrdenes.querySelectorAll(".btn-ver-orden").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => verProductosOrden(btn.dataset.id, btn.dataset.estado));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error al cargar órdenes de compra:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cambiarEstadoOrden(idOrden, esAprobacion, telefono = "", nombreProv = "") {
|
||||||
|
const nuevoEstado = esAprobacion ? 1 : 2;
|
||||||
|
|
||||||
|
fetch("../php/Actualizar_Orden_Compra.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id: parseInt(idOrden, 10), estado: nuevoEstado }),
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
const text = await res.text();
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(text || `HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (jsonErr) {
|
||||||
|
throw new Error(`Respuesta inválida del servidor: ${text}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
if (esAprobacion && telefono) {
|
||||||
|
enviarOrdenPorWhatsApp(idOrden, telefono, nombreProv);
|
||||||
|
}
|
||||||
|
cargarOrdenesCompra();
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (data.error || "No se pudo actualizar"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error al actualizar la orden:", err);
|
||||||
|
alert("Error al actualizar la orden: " + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enviarOrdenPorWhatsApp(idOrden, telefono, nombreProv) {
|
||||||
|
const telefonoLimpio = telefono.replace(/[^0-9]/g, "");
|
||||||
|
|
||||||
|
fetch(`../php/Obtener_Productos_Orden.php?id=${idOrden}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((productos) => {
|
||||||
|
let mensaje = `*Orden de Compra #${idOrden}*\n`;
|
||||||
|
mensaje += `Proveedor: ${nombreProv}\n`;
|
||||||
|
mensaje += `--------------------------\n`;
|
||||||
|
|
||||||
|
productos.forEach((p) => {
|
||||||
|
mensaje += `- ${p.Cantidad} x ${p.Descripcion}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
mensaje += `--------------------------\n`;
|
||||||
|
mensaje += `Favor de confirmar recepción.`;
|
||||||
|
|
||||||
|
const mensajeUrl = encodeURIComponent(mensaje);
|
||||||
|
const urlWhatsApp = `https://web.whatsapp.com/send?phone=${telefonoLimpio}&text=${mensajeUrl}`;
|
||||||
|
|
||||||
|
window.open(urlWhatsApp, "_blank");
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al obtener productos para WhatsApp:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetalleOrden() {
|
||||||
|
const cuerpo = document.getElementById("productos-orden-body");
|
||||||
|
if (!cuerpo || !ordenDetalleActual) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cuerpo.innerHTML = "";
|
||||||
|
|
||||||
|
if (ordenDetalleActual.productos.length === 0) {
|
||||||
|
const filaVacia = document.createElement("tr");
|
||||||
|
filaVacia.innerHTML = '<td colspan="2">No hay productos registrados.</td>';
|
||||||
|
cuerpo.appendChild(filaVacia);
|
||||||
|
actualizarBotonesDetalleOrden();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ordenDetalleActual.productos.forEach((prod) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
const cantidadActual = prod.Cantidad || 0;
|
||||||
|
const celdaCantidad = modoEdicionDetalle
|
||||||
|
? `<td><input type="number" min="1" value="${cantidadActual}" data-product-id="${prod.ID_Producto}" class="input-cantidad-orden" /></td>`
|
||||||
|
: `<td>${cantidadActual}</td>`;
|
||||||
|
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${prod.Descripcion}</td>
|
||||||
|
${celdaCantidad}
|
||||||
|
`;
|
||||||
|
cuerpo.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
actualizarBotonesDetalleOrden();
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarBotonesDetalleOrden() {
|
||||||
|
const btnConfirmar = document.getElementById("btn-confirmar-recepcion");
|
||||||
|
const btnEditar = document.getElementById("btn-editar-orden");
|
||||||
|
const btnGuardar = document.getElementById("btn-guardar-orden");
|
||||||
|
const btnCancelar = document.getElementById("btn-cancelar-orden");
|
||||||
|
const btnCerrar = document.getElementById("cerrar-orden");
|
||||||
|
|
||||||
|
if (!btnConfirmar || !btnEditar || !btnGuardar || !btnCancelar || !btnCerrar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const estado = parseInt(ordenDetalleActual?.estado || 0, 10);
|
||||||
|
const esConfirmada = estado === 3;
|
||||||
|
const esBloqueada = estado === 2 || estado === 3;
|
||||||
|
|
||||||
|
if (modoEdicionDetalle) {
|
||||||
|
btnConfirmar.style.display = "none";
|
||||||
|
btnEditar.style.display = "none";
|
||||||
|
btnCerrar.style.display = "none";
|
||||||
|
btnGuardar.style.display = "inline-block";
|
||||||
|
btnCancelar.style.display = "inline-block";
|
||||||
|
} else {
|
||||||
|
btnConfirmar.style.display = "inline-block";
|
||||||
|
btnEditar.style.display = "inline-block";
|
||||||
|
btnCerrar.style.display = "inline-block";
|
||||||
|
btnGuardar.style.display = "none";
|
||||||
|
btnCancelar.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
btnConfirmar.disabled = esConfirmada || esBloqueada;
|
||||||
|
btnEditar.disabled = esConfirmada || esBloqueada;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verProductosOrden(idOrden, estadoActual = 0) {
|
||||||
|
fetch(`../php/Obtener_Productos_Orden.php?id=${idOrden}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((productos) => {
|
||||||
|
ordenDetalleActual = {
|
||||||
|
id: parseInt(idOrden, 10),
|
||||||
|
estado: parseInt(estadoActual, 10),
|
||||||
|
productos: productos || [],
|
||||||
|
};
|
||||||
|
modoEdicionDetalle = false;
|
||||||
|
renderDetalleOrden();
|
||||||
|
|
||||||
|
document.getElementById("tabla-ordenes").style.display = "none";
|
||||||
|
document.getElementById("productos-orden").style.display = "block";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error al cargar productos de la orden:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmarRecepcionOrden() {
|
||||||
|
if (!ordenDetalleActual) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("../php/Actualizar_Orden_Compra.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id: ordenDetalleActual.id, estado: 3 }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
ordenDetalleActual.estado = 3;
|
||||||
|
cargarOrdenesCompra();
|
||||||
|
if (window.cargarProductos) {
|
||||||
|
window.cargarProductos();
|
||||||
|
}
|
||||||
|
renderDetalleOrden();
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (data.error || "No se pudo confirmar la recepción"));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error al confirmar recepción:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activarEdicionDetalleOrden() {
|
||||||
|
modoEdicionDetalle = true;
|
||||||
|
renderDetalleOrden();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelarEdicionDetalleOrden() {
|
||||||
|
modoEdicionDetalle = false;
|
||||||
|
renderDetalleOrden();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function guardarEdicionDetalleOrden() {
|
||||||
|
if (!ordenDetalleActual) return;
|
||||||
|
|
||||||
|
const productosActualizados = ordenDetalleActual.productos.map((prod) => {
|
||||||
|
const input = document.querySelector(`input[data-product-id="${prod.ID_Producto}"]`);
|
||||||
|
const cantidad = input ? parseInt(input.value, 10) : parseInt(prod.Cantidad, 10);
|
||||||
|
|
||||||
|
if (!Number.isInteger(cantidad) || cantidad < 1) {
|
||||||
|
throw new Error(`La cantidad para ${prod.Descripcion} debe ser un número mayor a 0.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prod,
|
||||||
|
Cantidad: cantidad,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("../php/Actualizar_Productos_Orden.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: ordenDetalleActual.id,
|
||||||
|
productos: productosActualizados.map((prod) => ({
|
||||||
|
id: prod.ID_Producto,
|
||||||
|
cantidad: prod.Cantidad,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
ordenDetalleActual.productos = productosActualizados;
|
||||||
|
modoEdicionDetalle = false;
|
||||||
|
renderDetalleOrden();
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (data.error || "No se pudieron guardar los cambios"));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error al guardar edición:", err);
|
||||||
|
alert(err.message || "Ocurrió un error al guardar los cambios.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarOrdenesCompra();
|
||||||
|
|
||||||
|
document.getElementById("cerrar-orden").addEventListener("click", () => {
|
||||||
|
document.getElementById("tabla-ordenes").style.display = "table";
|
||||||
|
document.getElementById("productos-orden").style.display = "none";
|
||||||
|
ordenDetalleActual = null;
|
||||||
|
modoEdicionDetalle = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("btn-confirmar-recepcion").addEventListener("click", confirmarRecepcionOrden);
|
||||||
|
document.getElementById("btn-editar-orden").addEventListener("click", activarEdicionDetalleOrden);
|
||||||
|
document.getElementById("btn-guardar-orden").addEventListener("click", guardarEdicionDetalleOrden);
|
||||||
|
document.getElementById("btn-cancelar-orden").addEventListener("click", cancelarEdicionDetalleOrden);
|
||||||
|
});
|
||||||
|
|
||||||
|
function eliminarProductoOrden(index) {
|
||||||
|
productosOrden.splice(index, 1);
|
||||||
|
document.querySelector("#tabla-orden tbody").innerHTML = "";
|
||||||
|
productosOrden.forEach((p, i) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${p.id}</td>
|
||||||
|
<td>${p.descripcion}</td>
|
||||||
|
<td>${p.cantidad}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" onclick="eliminarProductoOrden(${i})">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
document.querySelector("#tabla-orden tbody").appendChild(fila);
|
||||||
|
});
|
||||||
|
}
|
||||||
+585
@@ -0,0 +1,585 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const tipoReporte = document.getElementById('tipo-reporte');
|
||||||
|
const parametros = document.getElementById('parametros');
|
||||||
|
const generarBtn = document.getElementById('generar-reporte');
|
||||||
|
const exportarBtn = document.getElementById('exportar-pdf');
|
||||||
|
const reporteContainer = document.getElementById('reporte-container');
|
||||||
|
const reporteContenido = document.getElementById('reporte-contenido');
|
||||||
|
const spinner = document.getElementById('spinner');
|
||||||
|
const errorMessage = document.getElementById('error-message');
|
||||||
|
|
||||||
|
// Variable global para almacenar el período actual
|
||||||
|
let periodoActual = {
|
||||||
|
tipo: 'diario',
|
||||||
|
fecha: new Date().toISOString().split('T')[0],
|
||||||
|
nombreArchivo: 'reporte'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========== FUNCIONES AUXILIARES ==========
|
||||||
|
|
||||||
|
function formatoMoneda(valor) {
|
||||||
|
return new Intl.NumberFormat('es-AR', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'ARS'
|
||||||
|
}).format(valor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function obtenerSemanaActual() {
|
||||||
|
const hoy = new Date();
|
||||||
|
const diaSemana = hoy.getDay();
|
||||||
|
const lunes = new Date(hoy);
|
||||||
|
const diferenciaDias = diaSemana === 0 ? -6 : 1 - diaSemana;
|
||||||
|
lunes.setDate(hoy.getDate() + diferenciaDias);
|
||||||
|
|
||||||
|
const fechaReferencia = new Date(lunes.getFullYear(), 0, 4);
|
||||||
|
const diaSemanaRef = fechaReferencia.getDay();
|
||||||
|
const lunesRef = new Date(fechaReferencia);
|
||||||
|
lunesRef.setDate(fechaReferencia.getDate() - (diaSemanaRef === 0 ? 6 : diaSemanaRef - 1));
|
||||||
|
|
||||||
|
const diffMs = lunes - lunesRef;
|
||||||
|
const diffDias = Math.floor(diffMs / (24 * 60 * 60 * 1000));
|
||||||
|
const numeroSemana = Math.floor(diffDias / 7) + 1;
|
||||||
|
const ano = lunes.getFullYear();
|
||||||
|
|
||||||
|
return `${ano}-W${String(numeroSemana).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarSpinner() {
|
||||||
|
spinner.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function ocultarSpinner() {
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarError(mensaje) {
|
||||||
|
errorMessage.textContent = mensaje;
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function ocultarError() {
|
||||||
|
errorMessage.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function generarNombresYMetadatos(tipo, data) {
|
||||||
|
let nombreArchivo = 'reporte';
|
||||||
|
|
||||||
|
if (tipo === 'diario') {
|
||||||
|
const fecha = new Date(data.periodo + 'T00:00:00');
|
||||||
|
const fechaCorta = fecha.getDate().toString().padStart(2, '0') + '-' +
|
||||||
|
(fecha.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
fecha.getFullYear().toString().slice(-2);
|
||||||
|
nombreArchivo = 'reporte_' + fechaCorta;
|
||||||
|
} else if (tipo === 'semanal') {
|
||||||
|
const inicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||||
|
const fin = new Date(data.fecha_fin + 'T00:00:00');
|
||||||
|
const diaInicio = inicio.getDate().toString().padStart(2, '0');
|
||||||
|
const diaFin = fin.getDate().toString().padStart(2, '0');
|
||||||
|
const mesNombre = inicio.toLocaleDateString('es-ES', { month: 'long' });
|
||||||
|
nombreArchivo = `reporte_semana_${diaInicio}-${diaFin}_${mesNombre}`;
|
||||||
|
} else if (tipo === 'mensual') {
|
||||||
|
const mesesNombres = ['', 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
|
||||||
|
let nombreMes = 'mensual';
|
||||||
|
let anio = new Date().getFullYear();
|
||||||
|
if (data.nombre_mes) {
|
||||||
|
nombreMes = data.nombre_mes;
|
||||||
|
anio = data.nombre_anio || anio;
|
||||||
|
} else if (data.fecha_inicio) {
|
||||||
|
const f = new Date(data.fecha_inicio + 'T00:00:00');
|
||||||
|
if (!isNaN(f.getTime())) {
|
||||||
|
nombreMes = f.toLocaleDateString('es-ES', { month: 'long' });
|
||||||
|
anio = f.getFullYear();
|
||||||
|
}
|
||||||
|
} else if (data.periodo && /^\d{4}-\d{2}$/.test(String(data.periodo))) {
|
||||||
|
const parts = String(data.periodo).split('-');
|
||||||
|
nombreMes = mesesNombres[parseInt(parts[1] || '0')] || nombreMes;
|
||||||
|
anio = parts[0] || anio;
|
||||||
|
}
|
||||||
|
nombreArchivo = 'reporte_' + nombreMes + '_' + anio;
|
||||||
|
} else if (tipo === 'anual') {
|
||||||
|
const anio = data.periodo || data.anio || (data.fecha_inicio ? (new Date(data.fecha_inicio + 'T00:00:00')).getFullYear() : new Date().getFullYear());
|
||||||
|
nombreArchivo = 'reporte_' + anio;
|
||||||
|
}
|
||||||
|
|
||||||
|
periodoActual.tipo = tipo;
|
||||||
|
periodoActual.nombreArchivo = nombreArchivo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderizarDiario(data) {
|
||||||
|
const fecha = new Date(data.periodo + 'T00:00:00');
|
||||||
|
const opciones = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||||||
|
const fechaFormato = fecha.toLocaleDateString('es-ES', opciones);
|
||||||
|
const fechaCorta = fecha.getDate().toString().padStart(2, '0') + '-' +
|
||||||
|
(fecha.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
fecha.getFullYear().toString().slice(-2);
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="reporte-titulo">
|
||||||
|
<h2>📅 Reporte Diario ${fechaCorta} - Cierre de Caja</h2>
|
||||||
|
<div class="reporte-periodo">Reporte ${fechaFormato}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hora</th>
|
||||||
|
<th>Tipo de Operación</th>
|
||||||
|
<th>Descripción / Detalle</th>
|
||||||
|
<th style="text-align: right;">Monto ($)</th>
|
||||||
|
<th>Usuario</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (data.transacciones && data.transacciones.length > 0) {
|
||||||
|
data.transacciones.forEach(tx => {
|
||||||
|
const monto = parseFloat(tx.monto);
|
||||||
|
const claseNegativo = monto < 0 ? 'negativo' : '';
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${tx.hora || '--:--'}</td>
|
||||||
|
<td><strong>${tx.tipo_operacion}</strong></td>
|
||||||
|
<td>${tx.descripcion || '--'}</td>
|
||||||
|
<td class="monto ${claseNegativo}">${formatoMoneda(monto)}</td>
|
||||||
|
<td>${tx.usuario_registro || 'Sistema'}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
html += `<tr><td colspan="5" style="text-align: center;">Sin transacciones registradas</td></tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="resumen-reporte">
|
||||||
|
<h3>Resumen del Día</h3>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ingresos Reservas:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_reservas)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ingresos Cantina:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_cantina)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Total Ingresos:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.resumen.total_ingresos)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Egresos Totales:</span>
|
||||||
|
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.egresos)}</span>
|
||||||
|
</div>
|
||||||
|
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">💰 SALDO NETO:</span>
|
||||||
|
<span class="resumen-valor ${data.resumen.saldo_neto < 0 ? 'negativo' : ''}">${formatoMoneda(data.resumen.saldo_neto)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
reporteContenido.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderizarSemanal(data) {
|
||||||
|
const inicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||||
|
const fin = new Date(data.fecha_fin + 'T00:00:00');
|
||||||
|
const opciones = { month: 'short', day: 'numeric' };
|
||||||
|
const inicioCorta = inicio.getDate().toString().padStart(2, '0') + '-' +
|
||||||
|
(inicio.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const finCorta = fin.getDate().toString().padStart(2, '0') + '-' +
|
||||||
|
(fin.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
fin.getFullYear().toString().slice(-2);
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="reporte-titulo">
|
||||||
|
<h2>📆 Reporte Semanal ${inicioCorta} al ${finCorta} - Flujo de Caja</h2>
|
||||||
|
<div class="reporte-periodo">Reporte Semana del ${inicio.toLocaleDateString('es-ES', opciones)} al ${fin.toLocaleDateString('es-ES', opciones)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Día de la Semana</th>
|
||||||
|
<th style="text-align: right;">Ingresos Reservas ($)</th>
|
||||||
|
<th style="text-align: right;">Ingresos Cantina ($)</th>
|
||||||
|
<th style="text-align: right;">Costo Mercadería ($)</th>
|
||||||
|
<th style="text-align: right;">Saldo Neto del Día ($)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (data.dias && data.dias.length > 0) {
|
||||||
|
data.dias.forEach(dia => {
|
||||||
|
const nombreDia = new Date(dia.dia + 'T00:00:00').toLocaleDateString('es-ES', { weekday: 'long' });
|
||||||
|
const saldoNeto = dia.saldo_neto;
|
||||||
|
const claseNegativo = saldoNeto < 0 ? 'negativo' : '';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${nombreDia.charAt(0).toUpperCase() + nombreDia.slice(1)} (${dia.dia})</strong></td>
|
||||||
|
<td class="monto">${formatoMoneda(dia.ingresos_reservas)}</td>
|
||||||
|
<td class="monto">${formatoMoneda(dia.ingresos_cantina)}</td>
|
||||||
|
<td class="monto negativo">${formatoMoneda(dia.costo_mercaderia)}</td>
|
||||||
|
<td class="monto ${claseNegativo}"><strong>${formatoMoneda(saldoNeto)}</strong></td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="5" style="background: #34495e; color: white; text-align: center;">Resumen de la Semana</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: right;"><strong>Ingresos Reservas Total:</strong></td>
|
||||||
|
<td style="text-align: right;" colspan="4" class="monto encabezado">${formatoMoneda(data.resumen.ingresos_reservas_totales)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: right;"><strong>Ingresos Cantina Total:</strong></td>
|
||||||
|
<td style="text-align: right;" colspan="4" class="monto encabezado">${formatoMoneda(data.resumen.ingresos_cantina_totales)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: right;"><strong>Costo Mercadería Total:</strong></td>
|
||||||
|
<td style="text-align: right;" colspan="4" class="monto encabezado negativo">${formatoMoneda(data.resumen.costo_mercaderia_total)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: right;"><strong>💰 Saldo Neto Semana:</strong></td>
|
||||||
|
<td style="text-align: right;" colspan="4" class="monto encabezado ${data.resumen.saldo_neto_semana < 0 ? 'negativo' : ''}">${formatoMoneda(data.resumen.saldo_neto_semana)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="resumen-reporte">
|
||||||
|
<h3>% Ocupación Promedio</h3>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Turnos Ocupados:</span>
|
||||||
|
<span class="resumen-valor neutral">${data.resumen.turnos_ocupados} / ${data.resumen.turnos_disponibles}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Porcentaje Ocupación:</span>
|
||||||
|
<span class="resumen-valor neutral">${data.resumen.ocupacion_promedio}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
reporteContenido.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderizarMensual(data) {
|
||||||
|
const fechaInicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||||
|
const mesCorta = (fechaInicio.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
fechaInicio.getFullYear().toString().slice(-2);
|
||||||
|
const nombreMes = data.nombre_mes || fechaInicio.toLocaleDateString('es-ES', { month: 'long' });
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="reporte-titulo">
|
||||||
|
<h2>📊 Reporte Mensual ${mesCorta} - Rentabilidad</h2>
|
||||||
|
<div class="reporte-periodo">Reporte de ${nombreMes.charAt(0).toUpperCase() + nombreMes.slice(1)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 30px; color: #2c3e50;">Ingreso por Categoría</h3>
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Concepto / Categoría</th>
|
||||||
|
<th style="text-align: right;">Operaciones</th>
|
||||||
|
<th style="text-align: right;">Monto ($)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (data.categorias && data.categorias.length > 0) {
|
||||||
|
data.categorias.forEach(cat => {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${cat.categoria}</strong></td>
|
||||||
|
<td style="text-align: right;">${cat.operaciones}</td>
|
||||||
|
<td class="monto">${formatoMoneda(cat.monto)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 30px; color: #2c3e50;">Gastos Fijos</h3>
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nombre del Gasto</th>
|
||||||
|
<th style="text-align: right;">Monto Gasto ($)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (data.gastos_fijos && data.gastos_fijos.length > 0) {
|
||||||
|
data.gastos_fijos.forEach(gasto => {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${gasto.descripcion}</td>
|
||||||
|
<td class="monto negativo">${formatoMoneda(gasto.monto)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="text-align: center; color: #7f8c8d;">Sin gastos registrados</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="resumen-reporte" style="margin-top: 30px;">
|
||||||
|
<h3>Estado de Resultados</h3>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ingresos Totales:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_totales)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Costo de Mercadería:</span>
|
||||||
|
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.costo_mercaderia)}</span>
|
||||||
|
</div>
|
||||||
|
<hr style="border: none; border-top: 1px solid #2c3e50; margin: 15px 0;">
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ganancia Bruta:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.resumen.ganancia_bruta)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Gastos Fijos:</span>
|
||||||
|
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.gastos_fijos_total)}</span>
|
||||||
|
</div>
|
||||||
|
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">💰 GANANCIA NETA:</span>
|
||||||
|
<span class="resumen-valor ${data.resumen.ganancia_neta < 0 ? 'negativo' : ''}" style="font-size: 1.3em;">${formatoMoneda(data.resumen.ganancia_neta)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
reporteContenido.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderizarAnual(data) {
|
||||||
|
let notaAnoActual = '';
|
||||||
|
if (data.es_ano_actual) {
|
||||||
|
notaAnoActual = `
|
||||||
|
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 12px; margin-bottom: 20px; color: #856404;">
|
||||||
|
<strong>ℹ️ Nota:</strong> Este es el año en curso. El reporte muestra datos hasta el ${data.mes_actual}° mes (mes actual).
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="reporte-titulo">
|
||||||
|
<h2>📈 Reporte Anual ${data.periodo} - Visión Estratégica</h2>
|
||||||
|
<div class="reporte-periodo">Reporte Año ${data.periodo}</div>
|
||||||
|
</div>
|
||||||
|
${notaAnoActual}
|
||||||
|
|
||||||
|
<table class="reporte-tabla">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Mes</th>
|
||||||
|
<th style="text-align: right;">Ingreso Operativo ($)</th>
|
||||||
|
<th style="text-align: right;">Costo Bruto ($)</th>
|
||||||
|
<th style="text-align: right;">Costo Operativo ($)</th>
|
||||||
|
<th style="text-align: right;">Ganancia Bruta ($)</th>
|
||||||
|
<th style="text-align: right;">Ganancia Neta ($)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (data.meses && data.meses.length > 0) {
|
||||||
|
data.meses.forEach(mes => {
|
||||||
|
const gananciaNeta = parseFloat(mes.ganancia_neta);
|
||||||
|
const claseNegativo = gananciaNeta < 0 ? 'negativo' : '';
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${mes.mes}</strong></td>
|
||||||
|
<td class="monto">${formatoMoneda(mes.ingreso_operativo)}</td>
|
||||||
|
<td class="monto negativo">${formatoMoneda(mes.costo_bruto)}</td>
|
||||||
|
<td class="monto negativo">${formatoMoneda(mes.costo_operativo)}</td>
|
||||||
|
<td class="monto">${formatoMoneda(mes.ganancia_bruta)}</td>
|
||||||
|
<td class="monto ${claseNegativo}"><strong>${formatoMoneda(gananciaNeta)}</strong></td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="resumen-reporte" style="margin-top: 30px;">
|
||||||
|
<h3>Totales Anuales</h3>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ingreso Operativo Total:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.totales.ingreso_operativo_total)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Costo Bruto Total:</span>
|
||||||
|
<span class="resumen-valor negativo">-${formatoMoneda(data.totales.costo_bruto_total)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Costo Operativo Total:</span>
|
||||||
|
<span class="resumen-valor negativo">-${formatoMoneda(data.totales.costo_operativo_total)}</span>
|
||||||
|
</div>
|
||||||
|
<hr style="border: none; border-top: 1px solid #2c3e50; margin: 15px 0;">
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">Ganancia Bruta Total:</span>
|
||||||
|
<span class="resumen-valor">${formatoMoneda(data.totales.ganancia_bruta_total)}</span>
|
||||||
|
</div>
|
||||||
|
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||||
|
<div class="resumen-fila">
|
||||||
|
<span class="resumen-label">💰 GANANCIA NETA ANUAL:</span>
|
||||||
|
<span class="resumen-valor ${data.totales.ganancia_neta_total < 0 ? 'negativo' : ''}" style="font-size: 1.3em;">${formatoMoneda(data.totales.ganancia_neta_total)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
reporteContenido.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== INICIALIZACIÓN ==========
|
||||||
|
|
||||||
|
// Establecer valores por defecto
|
||||||
|
const hoy = new Date();
|
||||||
|
document.getElementById('fecha-diario').valueAsDate = hoy;
|
||||||
|
|
||||||
|
const semanaActual = obtenerSemanaActual();
|
||||||
|
document.getElementById('semana-input').value = semanaActual;
|
||||||
|
|
||||||
|
const mesActual = hoy.toISOString().slice(0, 7);
|
||||||
|
document.getElementById('mes-input').value = mesActual;
|
||||||
|
|
||||||
|
document.getElementById('anio-input').value = hoy.getFullYear();
|
||||||
|
|
||||||
|
// Cambiar parámetros según tipo de reporte
|
||||||
|
tipoReporte.addEventListener('change', () => {
|
||||||
|
document.querySelectorAll('.param-section').forEach(el => el.style.display = 'none');
|
||||||
|
|
||||||
|
const tipo = tipoReporte.value;
|
||||||
|
if (tipo === 'diario') {
|
||||||
|
document.getElementById('param-diario').style.display = 'block';
|
||||||
|
} else if (tipo === 'semanal') {
|
||||||
|
document.getElementById('param-semanal').style.display = 'block';
|
||||||
|
} else if (tipo === 'mensual') {
|
||||||
|
document.getElementById('param-mensual').style.display = 'block';
|
||||||
|
} else if (tipo === 'anual') {
|
||||||
|
document.getElementById('param-anual').style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generar reporte
|
||||||
|
generarBtn.addEventListener('click', async () => {
|
||||||
|
const tipo = tipoReporte.value;
|
||||||
|
let url = '../php/Obtener_Reporte.php?tipo_reporte=' + tipo;
|
||||||
|
|
||||||
|
// Agregar parámetros según el tipo
|
||||||
|
if (tipo === 'diario') {
|
||||||
|
const fecha = document.getElementById('fecha-diario').value;
|
||||||
|
if (!fecha) {
|
||||||
|
mostrarError('Por favor selecciona una fecha');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url += '&fecha=' + fecha;
|
||||||
|
} else if (tipo === 'semanal') {
|
||||||
|
const semana = document.getElementById('semana-input').value;
|
||||||
|
if (!semana) {
|
||||||
|
mostrarError('Por favor selecciona una semana');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url += '&semana=' + semana;
|
||||||
|
} else if (tipo === 'mensual') {
|
||||||
|
const mes = document.getElementById('mes-input').value;
|
||||||
|
if (!mes) {
|
||||||
|
mostrarError('Por favor selecciona un mes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url += '&mes=' + mes;
|
||||||
|
} else if (tipo === 'anual') {
|
||||||
|
const anio = document.getElementById('anio-input').value;
|
||||||
|
if (!anio) {
|
||||||
|
mostrarError('Por favor selecciona un año');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
url += '&anio=' + anio;
|
||||||
|
}
|
||||||
|
|
||||||
|
ocultarError();
|
||||||
|
mostrarSpinner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
mostrarError(data.error);
|
||||||
|
ocultarSpinner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ocultarSpinner();
|
||||||
|
|
||||||
|
// Generar nombres de archivo
|
||||||
|
generarNombresYMetadatos(tipo, data);
|
||||||
|
|
||||||
|
// Renderizar según tipo
|
||||||
|
if (tipo === 'diario') {
|
||||||
|
renderizarDiario(data);
|
||||||
|
} else if (tipo === 'semanal') {
|
||||||
|
renderizarSemanal(data);
|
||||||
|
} else if (tipo === 'mensual') {
|
||||||
|
renderizarMensual(data);
|
||||||
|
} else if (tipo === 'anual') {
|
||||||
|
renderizarAnual(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
reporteContainer.style.display = 'block';
|
||||||
|
exportarBtn.style.display = 'inline-block';
|
||||||
|
} catch (error) {
|
||||||
|
mostrarError('Error al cargar el reporte: ' + error.message);
|
||||||
|
ocultarSpinner();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exportar a PDF
|
||||||
|
exportarBtn.addEventListener('click', () => {
|
||||||
|
const elemento = document.getElementById('reporte-contenido');
|
||||||
|
const nombreArchivo = periodoActual.nombreArchivo;
|
||||||
|
|
||||||
|
const opciones = {
|
||||||
|
margin: 10,
|
||||||
|
filename: nombreArchivo + '.pdf',
|
||||||
|
image: { type: 'jpeg', quality: 0.98 },
|
||||||
|
html2canvas: { scale: 2, useCORS: true },
|
||||||
|
jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4', compress: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
html2pdf()
|
||||||
|
.set(opciones)
|
||||||
|
.from(elemento)
|
||||||
|
.save(nombreArchivo + '.pdf');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,621 @@
|
|||||||
|
// Estado global de la reserva
|
||||||
|
let estadoReserva = {
|
||||||
|
tipoCancha: null,
|
||||||
|
fecha: null,
|
||||||
|
horario: null,
|
||||||
|
numeroCancha: null,
|
||||||
|
quincho: false,
|
||||||
|
descuento: 0,
|
||||||
|
cuponeado: false,
|
||||||
|
reservasRestantes: null,
|
||||||
|
cantidadReservas: 0,
|
||||||
|
umbral: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Obtener datos de las canchas al cargar
|
||||||
|
let datoscCanchas = { futbol: null, padel: null, quincho: null };
|
||||||
|
|
||||||
|
// Variable global para guardar el código de WhatsApp temporalmente
|
||||||
|
let codigoCorrecto = null;
|
||||||
|
|
||||||
|
// Cargar datos de las canchas desde la base de datos
|
||||||
|
async function cargarDatosCanchas() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Obtener_Canchas.php');
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Datos recibidos de Obtener_Canchas.php:', data);
|
||||||
|
|
||||||
|
const canchas = Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: (data && Array.isArray(data.canchas) ? data.canchas : []);
|
||||||
|
|
||||||
|
if (!canchas.length) {
|
||||||
|
console.warn('No se recibieron canchas válidas desde el servidor:', data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canchas.forEach(cancha => {
|
||||||
|
const rawTipo = String(cancha.Tipo ?? cancha.tipo ?? '').trim().toUpperCase();
|
||||||
|
const tipo = rawTipo === 'F' || rawTipo === 'FUTBOL' ? 'F'
|
||||||
|
: rawTipo === 'P' || rawTipo === 'PADEL' ? 'P'
|
||||||
|
: rawTipo === 'Q' || rawTipo === 'QUINCHO' ? 'Q'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!tipo) {
|
||||||
|
console.warn('Tipo de cancha desconocido en la respuesta:', rawTipo, cancha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const duracion = parseInt(cancha.Duracion ?? cancha.duracion, 10) || 0;
|
||||||
|
const precio = parseFloat(cancha.Precio ?? cancha.precio_venta ?? cancha.precio) || 0;
|
||||||
|
const apertura = cancha.Hora_Apertura ?? cancha.apertura ?? null;
|
||||||
|
const cierre = cancha.Hora_Cierre ?? cancha.cierre ?? null;
|
||||||
|
const cantidad = parseInt(cancha.Cant_Canchas ?? cancha.cantidad, 10) || 0;
|
||||||
|
const reservas = parseInt(cancha.Cant_Reservas_Cupon ?? cancha.reservas, 10) || 0;
|
||||||
|
const descuento = parseFloat(cancha.Descuento_Cupon ?? cancha.descuento_cupon) || 0;
|
||||||
|
const maxFaltas = parseInt(cancha.Cant_Faltas ?? cancha.cant_faltas, 10) || 0;
|
||||||
|
const duracionCupon = parseInt(cancha.Duracion_Cupon ?? cancha.duracion_cupon, 10) || 0;
|
||||||
|
|
||||||
|
if (tipo === 'F') {
|
||||||
|
datoscCanchas.futbol = {
|
||||||
|
duracion,
|
||||||
|
precio,
|
||||||
|
apertura,
|
||||||
|
cierre,
|
||||||
|
cantidad,
|
||||||
|
reservasParaCupon: reservas,
|
||||||
|
descuentoCupon: descuento,
|
||||||
|
maxFaltas,
|
||||||
|
duracionCupon
|
||||||
|
};
|
||||||
|
} else if (tipo === 'P') {
|
||||||
|
datoscCanchas.padel = {
|
||||||
|
duracion,
|
||||||
|
precio,
|
||||||
|
apertura,
|
||||||
|
cierre,
|
||||||
|
cantidad,
|
||||||
|
reservasParaCupon: reservas,
|
||||||
|
descuentoCupon: descuento,
|
||||||
|
maxFaltas,
|
||||||
|
duracionCupon
|
||||||
|
};
|
||||||
|
} else if (tipo === 'Q') {
|
||||||
|
datoscCanchas.quincho = {
|
||||||
|
precio,
|
||||||
|
reservasMax: reservas
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Datos de canchas cargados:', datoscCanchas);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cargar datos de canchas desde BD:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar la interfaz
|
||||||
|
function inicializarReserva() {
|
||||||
|
const botonesCancha = document.querySelectorAll('.btn-cancha-tipo');
|
||||||
|
|
||||||
|
botonesCancha.forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
botonesCancha.forEach(b => b.classList.remove('activo'));
|
||||||
|
btn.classList.add('activo');
|
||||||
|
estadoReserva.tipoCancha = btn.dataset.tipo;
|
||||||
|
estadoReserva.fecha = null;
|
||||||
|
estadoReserva.horario = null;
|
||||||
|
|
||||||
|
document.querySelector('.selector-fecha').style.display = 'block';
|
||||||
|
document.querySelector('.selector-horario').style.display = 'none';
|
||||||
|
document.querySelector('.datos-cliente').style.display = 'none';
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||||
|
|
||||||
|
generarDiasDisponibles();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Botón volver
|
||||||
|
document.querySelector('.btn-volver').addEventListener('click', () => {
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- LÓGICA DE DATOS DEL CLIENTE ---
|
||||||
|
document.querySelector('.btn-verificar').addEventListener('click', async () => {
|
||||||
|
const nombre = document.getElementById('cliente-nombre').value.trim();
|
||||||
|
const telefono = document.getElementById('cliente-telefono').value.trim();
|
||||||
|
const dni = document.getElementById('cliente-dni').value.trim();
|
||||||
|
|
||||||
|
if (!nombre || !telefono || !dni) {
|
||||||
|
alert('Por favor completa todos tus datos antes de continuar');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!estadoReserva.numeroCancha) {
|
||||||
|
alert('Selecciona primero el número de cancha disponible.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await obtenerEstadoCupon(dni, estadoReserva.tipoCancha);
|
||||||
|
actualizarResumen();
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Botón "Confirmar Reserva" (Pide el código)
|
||||||
|
const btnSolicitar = document.getElementById('btn-solicitar-reserva');
|
||||||
|
if (btnSolicitar) {
|
||||||
|
btnSolicitar.addEventListener('click', async () => {
|
||||||
|
await solicitarCodigoWhatsApp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Botón "Validar y Reservar" (Verifica el código)
|
||||||
|
const btnVerificarWA = document.getElementById('btn-verificar-codigo');
|
||||||
|
if (btnVerificarWA) {
|
||||||
|
btnVerificarWA.addEventListener('click', async () => {
|
||||||
|
const codigoIngresado = document.getElementById('codigo-verificacion').value;
|
||||||
|
if (codigoIngresado === codigoCorrecto) {
|
||||||
|
await guardarReservaFinal();
|
||||||
|
} else {
|
||||||
|
alert("Código incorrecto. Por favor, inténtelo de nuevo.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox quincho
|
||||||
|
document.getElementById('incluir-quincho').addEventListener('change', (e) => {
|
||||||
|
estadoReserva.quincho = e.target.checked;
|
||||||
|
const precioQuincho = datoscCanchas.quincho?.precio || 0;
|
||||||
|
|
||||||
|
if (e.target.checked && precioQuincho > 0) {
|
||||||
|
document.querySelector('.precio-quincho').style.display = 'inline-block';
|
||||||
|
document.getElementById('quincho-reservas').style.display = 'block';
|
||||||
|
document.getElementById('precio-quincho-val').textContent = precioQuincho.toFixed(2);
|
||||||
|
} else {
|
||||||
|
estadoReserva.quincho = false;
|
||||||
|
document.querySelector('.precio-quincho').style.display = 'none';
|
||||||
|
document.getElementById('quincho-reservas').style.display = 'none';
|
||||||
|
e.target.checked = false;
|
||||||
|
}
|
||||||
|
actualizarPrecioTotal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const formComprobante = document.getElementById('form-comprobante');
|
||||||
|
if (formComprobante) {
|
||||||
|
formComprobante.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await enviarComprobantePago();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar días disponibles
|
||||||
|
function generarDiasDisponibles() {
|
||||||
|
const contenedor = document.getElementById('dias-disponibles');
|
||||||
|
contenedor.innerHTML = '';
|
||||||
|
const hoy = new Date();
|
||||||
|
const diasSemana = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sab'];
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const fecha = new Date(hoy);
|
||||||
|
fecha.setDate(fecha.getDate() + i);
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'btn-dia';
|
||||||
|
|
||||||
|
btn.innerHTML = `<div>${diasSemana[fecha.getDay()]}</div><div>${fecha.getDate()}/${fecha.getMonth() + 1}</div>`;
|
||||||
|
btn.dataset.fecha = fecha.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
document.querySelectorAll('.btn-dia').forEach(b => b.classList.remove('activo'));
|
||||||
|
btn.classList.add('activo');
|
||||||
|
estadoReserva.fecha = btn.dataset.fecha;
|
||||||
|
estadoReserva.horario = null;
|
||||||
|
|
||||||
|
generarHorarios();
|
||||||
|
await actualizarDisponibilidadQuincho(btn.dataset.fecha);
|
||||||
|
document.querySelector('.selector-horario').style.display = 'block';
|
||||||
|
document.querySelector('.datos-cliente').style.display = 'none';
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||||
|
});
|
||||||
|
contenedor.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener disponibilidad de quinchos
|
||||||
|
async function obtenerDisponibilidadQuincho(fecha) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`../php/Obtener_Disponibilidad_Quincho.php?fecha=${fecha}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success ? data.disponibles : 0;
|
||||||
|
} catch (error) { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar disponibilidad de quincho en UI
|
||||||
|
async function actualizarDisponibilidadQuincho(fecha) {
|
||||||
|
const disponibles = await obtenerDisponibilidadQuincho(fecha);
|
||||||
|
const checkboxQuincho = document.getElementById('incluir-quincho');
|
||||||
|
const quinchoInfo = document.getElementById('quincho-reservas');
|
||||||
|
const quinchoAlerta = document.getElementById('quincho-no-disponible');
|
||||||
|
|
||||||
|
document.getElementById('quincho-disp').textContent = disponibles;
|
||||||
|
|
||||||
|
if (disponibles === 0) {
|
||||||
|
checkboxQuincho.disabled = true;
|
||||||
|
checkboxQuincho.checked = false;
|
||||||
|
estadoReserva.quincho = false;
|
||||||
|
quinchoInfo.style.display = 'none';
|
||||||
|
quinchoAlerta.style.display = 'block';
|
||||||
|
document.querySelector('.precio-quincho').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
checkboxQuincho.disabled = false;
|
||||||
|
quinchoAlerta.style.display = 'none';
|
||||||
|
quinchoInfo.style.display = 'block';
|
||||||
|
}
|
||||||
|
actualizarPrecioTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener disponibilidad de cancha
|
||||||
|
async function obtenerDisponibilidadCancha(fecha, horario, duracion, tipo) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`../php/Obtener_Disponibilidad_Cancha.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo === 'futbol' ? 'F' : 'P'}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success ? data.disponibles : 0;
|
||||||
|
} catch (error) { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener números de canchas
|
||||||
|
async function obtenerNumerosCanchas(fecha, horario, duracion, tipo) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`../php/Obtener_Canchas_Numeros.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo === 'futbol' ? 'F' : 'P'}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success ? data.canchas : [];
|
||||||
|
} catch (error) { return []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar selección de número de cancha
|
||||||
|
async function generarSeleccionNumeroCancha(horarioInicio) {
|
||||||
|
const contenedor = document.getElementById('botones-canchas-numeros');
|
||||||
|
contenedor.innerHTML = '';
|
||||||
|
const tipo = estadoReserva.tipoCancha;
|
||||||
|
const canchas = await obtenerNumerosCanchas(estadoReserva.fecha, horarioInicio, datoscCanchas[tipo].duracion, tipo);
|
||||||
|
|
||||||
|
canchas.forEach(cancha => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'btn-numero-cancha';
|
||||||
|
btn.textContent = `Cancha ${cancha.numero}`;
|
||||||
|
btn.dataset.numero = cancha.numero;
|
||||||
|
|
||||||
|
if (!cancha.disponible) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.classList.add('no-disponible');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.btn-numero-cancha').forEach(b => b.classList.remove('activo'));
|
||||||
|
btn.classList.add('activo');
|
||||||
|
estadoReserva.numeroCancha = cancha.numero;
|
||||||
|
|
||||||
|
document.querySelector('.datos-cliente').style.display = 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
contenedor.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar horarios
|
||||||
|
async function generarHorarios() {
|
||||||
|
const contenedor = document.getElementById('grid-horarios');
|
||||||
|
contenedor.innerHTML = '';
|
||||||
|
|
||||||
|
const tipo = estadoReserva.tipoCancha;
|
||||||
|
const datos = datoscCanchas[tipo];
|
||||||
|
|
||||||
|
if (!datos || typeof datos.duracion !== 'number' || !datos.apertura || !datos.cierre) {
|
||||||
|
console.error('No hay datos de configuración válidos para la cancha seleccionada:', tipo, datos);
|
||||||
|
alert('Error al cargar los horarios. Revisa la configuración de la cancha.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const duracion = datos.duracion;
|
||||||
|
|
||||||
|
const [aperturaH, aperturaM] = datos.apertura.split(':').map(Number);
|
||||||
|
const [cierreH, cierreM] = datos.cierre.split(':').map(Number);
|
||||||
|
const aperturaTotalMin = aperturaH * 60 + aperturaM;
|
||||||
|
const cierreTotalMin = cierreH * 60 + cierreM;
|
||||||
|
|
||||||
|
const ahora = new Date();
|
||||||
|
const [selYear, selMonth, selDay] = estadoReserva.fecha.split('-').map(Number);
|
||||||
|
const fechaSeleccionada = new Date(selYear, selMonth - 1, selDay);
|
||||||
|
const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate());
|
||||||
|
const fechaSelSoloFecha = new Date(fechaSeleccionada.getFullYear(), fechaSeleccionada.getMonth(), fechaSeleccionada.getDate());
|
||||||
|
const esHoy = fechaSelSoloFecha.getTime() === hoy.getTime();
|
||||||
|
const minutoActual = ahora.getHours() * 60 + ahora.getMinutes();
|
||||||
|
|
||||||
|
let horaActualMin = aperturaTotalMin;
|
||||||
|
const horarios = [];
|
||||||
|
|
||||||
|
while (horaActualMin + duracion <= cierreTotalMin) {
|
||||||
|
const horas = Math.floor(horaActualMin / 60);
|
||||||
|
const minutos = horaActualMin % 60;
|
||||||
|
const horaFin = Math.floor((horaActualMin + duracion) / 60);
|
||||||
|
const minutesFin = (horaActualMin + duracion) % 60;
|
||||||
|
|
||||||
|
horarios.push({
|
||||||
|
inicio: `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`,
|
||||||
|
fin: `${String(horaFin).padStart(2, '0')}:${String(minutesFin).padStart(2, '0')}`,
|
||||||
|
minutos: horaActualMin
|
||||||
|
});
|
||||||
|
horaActualMin += duracion;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const horario of horarios) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'horario-slot';
|
||||||
|
btn.textContent = `${horario.inicio}\n-\n${horario.fin}`;
|
||||||
|
|
||||||
|
const inicioArr = horario.inicio.split(':').map(Number);
|
||||||
|
const inicioMin = inicioArr[0] * 60 + inicioArr[1];
|
||||||
|
const esPasado = fechaSelSoloFecha.getTime() < hoy.getTime() || (esHoy && inicioMin < minutoActual);
|
||||||
|
|
||||||
|
let disponibles = 0;
|
||||||
|
if (!esPasado) {
|
||||||
|
disponibles = await obtenerDisponibilidadCancha(estadoReserva.fecha, genericNormalizeTipo(horario.inicio), duracion, tipo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esPasado || disponibles === 0) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.classList.add('no-disponible');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
document.querySelectorAll('.horario-slot').forEach(b => b.classList.remove('activo'));
|
||||||
|
btn.classList.add('activo');
|
||||||
|
estadoReserva.horario = `${horario.inicio} - ${horario.fin}`;
|
||||||
|
estadoReserva.numeroCancha = null;
|
||||||
|
|
||||||
|
document.getElementById('incluir-quincho').checked = false;
|
||||||
|
estadoReserva.quincho = false;
|
||||||
|
document.querySelector('.precio-quincho').style.display = 'none';
|
||||||
|
document.getElementById('quincho-reservas').style.display = 'none';
|
||||||
|
|
||||||
|
await generarSeleccionNumeroCancha(horario.inicio);
|
||||||
|
document.querySelector('.selector-numero-cancha').style.display = 'block';
|
||||||
|
document.querySelector('.datos-cliente').style.display = 'none';
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
contenedor.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function genericNormalizeTipo(inicio) {
|
||||||
|
return inicio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar resumen
|
||||||
|
function actualizarResumen() {
|
||||||
|
const tipo = estadoReserva.tipoCancha;
|
||||||
|
const datos = datoscCanchas[tipo];
|
||||||
|
|
||||||
|
document.getElementById('res-cancha').textContent = `${tipo === 'futbol' ? 'Fútbol' : 'Pádel'} - Cancha ${estadoReserva.numeroCancha}`;
|
||||||
|
document.getElementById('res-fecha').textContent = new Date(estadoReserva.fecha).toLocaleDateString('es-AR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
|
document.getElementById('res-horario').textContent = estadoReserva.horario;
|
||||||
|
|
||||||
|
const duracionHoras = Math.floor(datos.duracion / 60);
|
||||||
|
const duracionMinutos = datos.duracion % 60;
|
||||||
|
document.getElementById('res-duracion').textContent = duracionHoras > 0 ? (duracionMinutos > 0 ? `${duracionHoras}h ${duracionMinutos}m` : `${duracionHoras}h`) : `${duracionMinutos}m`;
|
||||||
|
|
||||||
|
if (estadoReserva.cuponeado) {
|
||||||
|
document.getElementById('res-cupon').textContent = `Cupón activo: ${(estadoReserva.descuento * 100).toFixed(0)}% aplicado`;
|
||||||
|
} else if (estadoReserva.reservasRestantes !== null) {
|
||||||
|
document.getElementById('res-cupon').textContent = estadoReserva.reservasRestantes > 0 ? `Faltan ${estadoReserva.reservasRestantes} reserva(s) para cupón` : 'Sin cupón activo';
|
||||||
|
} else {
|
||||||
|
document.getElementById('res-cupon').textContent = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
actualizarPrecioTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarPrecioTotal() {
|
||||||
|
const tipo = estadoReserva.tipoCancha;
|
||||||
|
const datos = datoscCanchas[tipo];
|
||||||
|
|
||||||
|
if (!datos || typeof datos.precio !== 'number') {
|
||||||
|
console.error('No se puede actualizar el precio porque no se cargaron los datos de cancha:', tipo, datos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePrecio = datos.precio;
|
||||||
|
const descuentoMonto = basePrecio * (estadoReserva.descuento || 0);
|
||||||
|
const precioCancha = basePrecio - descuentoMonto;
|
||||||
|
const totalPrecio = precioCancha + (estadoReserva.quincho ? (datoscCanchas.quincho?.precio || 0) : 0);
|
||||||
|
|
||||||
|
const precioOriginalElement = document.getElementById('precio-original');
|
||||||
|
const precioOriginalValor = document.getElementById('res-precio-original');
|
||||||
|
const labelPrecio = document.getElementById('label-precio');
|
||||||
|
|
||||||
|
if (estadoReserva.descuento > 0) {
|
||||||
|
// Mostrar precio original tachado
|
||||||
|
precioOriginalValor.textContent = `$${basePrecio.toFixed(2)}`;
|
||||||
|
precioOriginalElement.style.display = 'block';
|
||||||
|
labelPrecio.textContent = 'Precio Final:';
|
||||||
|
} else {
|
||||||
|
// Ocultar precio original
|
||||||
|
precioOriginalElement.style.display = 'none';
|
||||||
|
labelPrecio.textContent = 'Precio:';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('res-precio').textContent = `$${totalPrecio.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function obtenerEstadoCupon(dni, tipo) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`../php/Obtener_Cupon.php?dni=${encodeURIComponent(dni)}&tipo=${encodeURIComponent(tipo)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.message || 'No se pudo obtener el estado del cupón');
|
||||||
|
}
|
||||||
|
|
||||||
|
estadoReserva.cuponeado = data.aplicarDescuento;
|
||||||
|
estadoReserva.descuento = data.aplicarDescuento ? parseFloat(data.descuentoCanchas) : 0;
|
||||||
|
estadoReserva.cantidadReservas = data.cantidadReservas;
|
||||||
|
estadoReserva.umbral = data.umbral;
|
||||||
|
estadoReserva.reservasRestantes = data.reservasRestantes;
|
||||||
|
|
||||||
|
if (!estadoReserva.cuponeado && data.reservasRestantes === 0 && data.umbral === 0) {
|
||||||
|
estadoReserva.reservasRestantes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al obtener estado de cupón:', error);
|
||||||
|
alert('No se pudo determinar el estado del cupón. Intenta nuevamente.');
|
||||||
|
estadoReserva.cuponeado = false;
|
||||||
|
estadoReserva.descuento = 0;
|
||||||
|
estadoReserva.reservasRestantes = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FUNCIONES DE WHATSAPP Y GUARDADO ---
|
||||||
|
|
||||||
|
async function solicitarCodigoWhatsApp() {
|
||||||
|
const nombre = document.getElementById('cliente-nombre').value.trim();
|
||||||
|
const telefono = document.getElementById('cliente-telefono').value.trim();
|
||||||
|
|
||||||
|
const btnSolicitar = document.getElementById('btn-solicitar-reserva');
|
||||||
|
btnSolicitar.disabled = true;
|
||||||
|
btnSolicitar.textContent = 'Enviando código...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Enviar_Codigo_WSP.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ telefono: telefono, nombre: nombre })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
codigoCorrecto = data.codigo;
|
||||||
|
|
||||||
|
// Ocultar botones de Confirmar/Volver y mostrar campo de validación
|
||||||
|
document.querySelector('.botones-reserva').style.display = 'none';
|
||||||
|
document.getElementById('lbl-telefono').textContent = telefono;
|
||||||
|
document.getElementById('seccion-verificacion').style.display = 'block';
|
||||||
|
|
||||||
|
console.log('Código enviado a Selenium:', data.codigo);
|
||||||
|
} else {
|
||||||
|
alert('Error al enviar el mensaje de WhatsApp.');
|
||||||
|
btnSolicitar.disabled = false;
|
||||||
|
btnSolicitar.textContent = 'Confirmar Reserva';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión con el servidor.');
|
||||||
|
btnSolicitar.disabled = false;
|
||||||
|
btnSolicitar.textContent = 'Confirmar Reserva';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function guardarReservaFinal() {
|
||||||
|
const tipo = estadoReserva.tipoCancha;
|
||||||
|
const datos = {
|
||||||
|
nombre: document.getElementById('cliente-nombre').value.trim(),
|
||||||
|
telefono: document.getElementById('cliente-telefono').value.trim(),
|
||||||
|
dni: document.getElementById('cliente-dni').value.trim(),
|
||||||
|
fecha: estadoReserva.fecha,
|
||||||
|
horario: estadoReserva.horario.split(' - ')[0],
|
||||||
|
tipo: tipo === 'futbol' ? 'F' : 'P',
|
||||||
|
numeroCancha: estadoReserva.numeroCancha,
|
||||||
|
monto: datoscCanchas[tipo].precio,
|
||||||
|
incluirQuincho: estadoReserva.quincho,
|
||||||
|
montoQuincho: estadoReserva.quincho ? datoscCanchas.quincho.precio : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Guardar_Reserva.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(datos)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
alert('¡Reserva confirmada exitosamente!');
|
||||||
|
|
||||||
|
// ADAPTACIÓN: Ocultamos la sección de reserva en vez de refrescar la pantalla
|
||||||
|
document.getElementById('seccion-verificacion').style.display = 'none';
|
||||||
|
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||||
|
|
||||||
|
// Cargamos el ID retornado por tu servidor en el input oculto del formulario
|
||||||
|
// Nota: Tu php/Guardar_Reserva.php debe retornar un campo id_reserva (ej: result.id_reserva)
|
||||||
|
const idNuevaReserva = result.id_reserva || result.id || 0;
|
||||||
|
document.getElementById('comprobante-id-reserva').value = idNuevaReserva;
|
||||||
|
|
||||||
|
// Hacemos visible la ventana de carga para la imagen
|
||||||
|
document.getElementById('seccion-comprobante').style.display = 'block';
|
||||||
|
} else {
|
||||||
|
alert('Error al guardar: ' + result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Ocurrió un error crítico al intentar guardar la reserva.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FUNCIÓN ASÍNCRONA PARA SUBIR EL COMPROBANTE DE PAGO ---
|
||||||
|
async function enviarComprobantePago() {
|
||||||
|
const idReserva = document.getElementById('comprobante-id-reserva').value;
|
||||||
|
const fileInput = document.getElementById('input-file-comprobante');
|
||||||
|
const btnSubir = document.querySelector('#form-comprobante button[type="submit"]');
|
||||||
|
|
||||||
|
if (fileInput.files.length === 0) {
|
||||||
|
alert("Por favor, selecciona una foto de tu comprobante.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSubir.disabled = true;
|
||||||
|
btnSubir.textContent = "Subiendo archivo...";
|
||||||
|
|
||||||
|
// Construimos el FormData binario
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id_reserva', idReserva);
|
||||||
|
formData.append('comprobante', fileInput.files[0]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Subir_Comprobante.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData // El navegador setea automáticamente el multipart/form-data
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert("¡Comprobante enviado con éxito! El administrador verificará su pago en la brevedad.");
|
||||||
|
location.reload(); // Ahora sí, reiniciamos la vista limpia del complejo
|
||||||
|
} else {
|
||||||
|
alert("Error: " + data.error);
|
||||||
|
btnSubir.disabled = false;
|
||||||
|
btnSubir.textContent = "Enviar Comprobante";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al subir el comprobante:", error);
|
||||||
|
alert("Error de conexión al procesar el archivo.");
|
||||||
|
btnSubir.disabled = false;
|
||||||
|
btnSubir.textContent = "Enviar Comprobante";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar cuando el DOM esté listo
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await cargarDatosCanchas();
|
||||||
|
inicializarReserva();
|
||||||
|
});
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const fechaInput = document.getElementById('fecha-resumen');
|
||||||
|
const totalVerInput = document.getElementById('total-ver');
|
||||||
|
const tablaVentas = document.getElementById('tabla-ver-ventas');
|
||||||
|
const tablaDetalle = document.getElementById('tabla-detalle-venta');
|
||||||
|
const detalleContenedor = document.getElementById('detalle-venta-contenedor');
|
||||||
|
const tablaPrincipal = document.getElementById('tabla-principal-contenedor');
|
||||||
|
|
||||||
|
// --- Estado de la aplicación ---
|
||||||
|
let state = {
|
||||||
|
vistaActual: 'principal', // 'principal' o 'detalle'
|
||||||
|
ventaSeleccionada: null,
|
||||||
|
ventasDelDia: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Funciones de utilidad ---
|
||||||
|
function formatMoney(v) {
|
||||||
|
return '$' + Number(v).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateString) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleString('es-AR');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Función para cambiar vistas ---
|
||||||
|
function mostrarVistaPrincipal() {
|
||||||
|
state.vistaActual = 'principal';
|
||||||
|
state.ventaSeleccionada = null;
|
||||||
|
if (tablaPrincipal) tablaPrincipal.style.display = 'block';
|
||||||
|
if (detalleContenedor) detalleContenedor.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarVistaDetalle(idVenta) {
|
||||||
|
state.vistaActual = 'detalle';
|
||||||
|
state.ventaSeleccionada = idVenta;
|
||||||
|
if (tablaPrincipal) tablaPrincipal.style.display = 'none';
|
||||||
|
if (detalleContenedor) detalleContenedor.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Carga de Ventas ---
|
||||||
|
function cargarVentasActuales() {
|
||||||
|
const fechaSeleccionada = fechaInput.value;
|
||||||
|
if (fechaSeleccionada) {
|
||||||
|
console.log("Actualizando vista de ventas para:", fechaSeleccionada);
|
||||||
|
fetchVentas({ start: fechaSeleccionada, end: fechaSeleccionada });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchVentas(params) {
|
||||||
|
const query = new URLSearchParams(params).toString();
|
||||||
|
fetch(`../php/Obtener_Ventas.php?${query}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
state.ventasDelDia = data || [];
|
||||||
|
renderTablaVentas(data);
|
||||||
|
calcularTotalDia(data);
|
||||||
|
console.log('Ventas cargadas:', data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error al cargar ventas:', err);
|
||||||
|
if (tablaVentas) {
|
||||||
|
tablaVentas.querySelector('tbody').innerHTML =
|
||||||
|
'<tr><td colspan="4" style="text-align: center; color: red;">Error al cargar ventas</td></tr>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTablaVentas(ventas) {
|
||||||
|
if (!tablaVentas) return;
|
||||||
|
|
||||||
|
const tbody = tablaVentas.querySelector('tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (!ventas || ventas.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">No hay ventas en el período seleccionado</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ventas.forEach(venta => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${venta.ID_Venta || '-'}</td>
|
||||||
|
<td>${venta.fecha_hora ? formatDate(venta.fecha_hora) : '-'}</td>
|
||||||
|
<td>${formatMoney(venta.monto_total)}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-ver-detalle" onclick="window.verDetalleVenta(${venta.ID_Venta})" title="Ver detalle">Ver Productos</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcularTotalDia(ventas) {
|
||||||
|
let total = 0;
|
||||||
|
if (ventas && Array.isArray(ventas) && ventas.length > 0) {
|
||||||
|
total = ventas.reduce((sum, v) => sum + Number(v.monto_total || 0), 0);
|
||||||
|
}
|
||||||
|
if (totalVerInput) totalVerInput.value = formatMoney(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Funcionalidad de Detalle ---
|
||||||
|
function cargarDetalleVenta(idVenta) {
|
||||||
|
console.log('Cargando detalle para venta:', idVenta);
|
||||||
|
|
||||||
|
fetch(`../php/Obtener_Ventas.php?id_venta=${idVenta}`)
|
||||||
|
.then(res => {
|
||||||
|
console.log('Respuesta status:', res.status);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP Error: ${res.status}`);
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Detalle cargado:', data);
|
||||||
|
|
||||||
|
// Verificar si hay error en la respuesta
|
||||||
|
if (data.error) {
|
||||||
|
console.error('Error del servidor:', data.error);
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la respuesta es un array vacío, mostrar mensaje
|
||||||
|
if (Array.isArray(data) && data.length === 0) {
|
||||||
|
console.warn('No hay productos para esta venta');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDetalleVenta(data);
|
||||||
|
mostrarVistaDetalle(idVenta);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error completo:', err);
|
||||||
|
alert('❌ Error al cargar el detalle de la venta: ' + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetalleVenta(data) {
|
||||||
|
if (!tablaDetalle) return;
|
||||||
|
|
||||||
|
// Asumimos que data contiene los productos de la venta
|
||||||
|
const productos = Array.isArray(data) ? data : (data.productos || []);
|
||||||
|
|
||||||
|
let totalVenta = 0;
|
||||||
|
const tbody = tablaDetalle.querySelector('tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (productos.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">No hay productos en esta venta</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
productos.forEach(prod => {
|
||||||
|
const cantidad = Number(prod.cantidad || 0);
|
||||||
|
const precio = Number(prod.precio_unitario || prod.precio || 0);
|
||||||
|
const subtotal = cantidad * precio;
|
||||||
|
totalVenta += subtotal;
|
||||||
|
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${prod.nombre || prod.descripcion || '-'}</td>
|
||||||
|
<td style="text-align: center;">${cantidad}</td>
|
||||||
|
<td style="text-align: right;">${formatMoney(precio)}</td>
|
||||||
|
<td style="text-align: right;">${formatMoney(subtotal)}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Agregar fila de total
|
||||||
|
const rowTotal = document.createElement('tr');
|
||||||
|
rowTotal.style.fontWeight = 'bold';
|
||||||
|
rowTotal.style.borderTop = '2px solid #333';
|
||||||
|
rowTotal.innerHTML = `
|
||||||
|
<td colspan="3" style="text-align: right;">TOTAL GENERAL:</td>
|
||||||
|
<td style="text-align: right;">${formatMoney(totalVenta)}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(rowTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Exponemos función global para el onclick ---
|
||||||
|
window.verDetalleVenta = function(idVenta) {
|
||||||
|
cargarDetalleVenta(idVenta);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Botón Volver ---
|
||||||
|
const btnVolver = document.getElementById('btn-volver-ventas');
|
||||||
|
if (btnVolver) {
|
||||||
|
btnVolver.addEventListener('click', mostrarVistaPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Inicialización ---
|
||||||
|
const hoyStr = new Date().toISOString().slice(0, 10);
|
||||||
|
if (fechaInput) {
|
||||||
|
fechaInput.value = hoyStr;
|
||||||
|
cargarVentasActuales();
|
||||||
|
fechaInput.addEventListener('change', () => {
|
||||||
|
mostrarVistaPrincipal();
|
||||||
|
cargarVentasActuales();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", () => {
|
||||||
|
if (window.location.hash === "#ver_ventas") {
|
||||||
|
mostrarVistaPrincipal();
|
||||||
|
cargarVentasActuales();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
let gastosActuales = {};
|
||||||
|
let columnasGastos = []; // Columnas dinámicas de gastos
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
cargarEstructuraGastos();
|
||||||
|
setupFormListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupFormListeners() {
|
||||||
|
// Formulario para agregar nuevo tipo de gasto
|
||||||
|
const formAgregarTipo = document.getElementById('form-agregar-tipo-gasto');
|
||||||
|
if (formAgregarTipo) {
|
||||||
|
formAgregarTipo.addEventListener('submit', handleAgregarTipoGasto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== CARGAR ESTRUCTURA DINÁMICA ==========
|
||||||
|
|
||||||
|
async function cargarEstructuraGastos() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Obtener_Gastos.php');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
columnasGastos = data.columnas || [];
|
||||||
|
gastosActuales = data.gasto || {};
|
||||||
|
|
||||||
|
construirFormularioDinamico();
|
||||||
|
} else {
|
||||||
|
console.log('No hay gastos registrados:', data.message);
|
||||||
|
columnasGastos = [];
|
||||||
|
construirFormularioDinamico();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cargar gastos:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== CONSTRUIR FORMULARIO DINÁMICO ==========
|
||||||
|
|
||||||
|
function construirFormularioDinamico() {
|
||||||
|
// Solo mostrar tabla de gastos
|
||||||
|
mostrarTablaGastos();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== AGREGAR NUEVO TIPO DE GASTO ==========
|
||||||
|
|
||||||
|
async function handleAgregarTipoGasto(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const nombre = document.getElementById('nombre-nuevo-gasto').value.trim();
|
||||||
|
const monto = parseFloat(document.getElementById('monto-nuevo-gasto').value);
|
||||||
|
|
||||||
|
if (!nombre || monto < 0) {
|
||||||
|
alert('Por favor completa los datos correctamente');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar nombre (solo letras y espacios)
|
||||||
|
if (!/^[a-zA-Z\s]+$/.test(nombre)) {
|
||||||
|
alert('El nombre del gasto solo puede contener letras y espacios');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Registrar_Gastos.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nuevo_gasto: nombre,
|
||||||
|
monto: monto
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('Nuevo tipo de gasto agregado correctamente');
|
||||||
|
document.getElementById('form-agregar-tipo-gasto').reset();
|
||||||
|
cargarEstructuraGastos();
|
||||||
|
} else {
|
||||||
|
alert('Error al agregar gasto: ' + data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== MOSTRAR TABLA DE GASTOS ==========
|
||||||
|
|
||||||
|
function mostrarTablaGastos() {
|
||||||
|
const tabla = document.getElementById('tabla-gastos');
|
||||||
|
if (!tabla) return;
|
||||||
|
|
||||||
|
const tbody = tabla.querySelector('tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (columnasGastos.length === 0) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const td = document.createElement('td');
|
||||||
|
td.colSpan = 3;
|
||||||
|
td.textContent = 'No hay gastos configurados';
|
||||||
|
td.style.textAlign = 'center';
|
||||||
|
td.style.color = '#999';
|
||||||
|
tr.appendChild(td);
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
columnasGastos.forEach(columna => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const monto = gastosActuales[columna] || 0;
|
||||||
|
const descripcionFormato = columna.charAt(0).toUpperCase() + columna.slice(1);
|
||||||
|
|
||||||
|
// Descripción
|
||||||
|
const tdDesc = document.createElement('td');
|
||||||
|
tdDesc.textContent = descripcionFormato;
|
||||||
|
tr.appendChild(tdDesc);
|
||||||
|
|
||||||
|
// Monto
|
||||||
|
const tdMonto = document.createElement('td');
|
||||||
|
tdMonto.textContent = `$${parseFloat(monto).toFixed(2)}`;
|
||||||
|
tr.appendChild(tdMonto);
|
||||||
|
|
||||||
|
// Acciones
|
||||||
|
const tdAcciones = document.createElement('td');
|
||||||
|
tdAcciones.style.display = 'flex';
|
||||||
|
tdAcciones.style.gap = '10px';
|
||||||
|
|
||||||
|
// Botón Editar
|
||||||
|
const btnEditar = document.createElement('button');
|
||||||
|
btnEditar.type = 'button';
|
||||||
|
btnEditar.className = 'btn-editar-gasto';
|
||||||
|
btnEditar.textContent = 'Editar';
|
||||||
|
btnEditar.addEventListener('click', () => activarEdicionGasto(btnEditar, columna));
|
||||||
|
tdAcciones.appendChild(btnEditar);
|
||||||
|
|
||||||
|
// Botón Eliminar
|
||||||
|
const btnEliminar = document.createElement('button');
|
||||||
|
btnEliminar.type = 'button';
|
||||||
|
btnEliminar.className = 'btn-eliminar-gasto';
|
||||||
|
btnEliminar.textContent = 'Eliminar';
|
||||||
|
btnEliminar.addEventListener('click', () => abrirModalEliminar(columna));
|
||||||
|
tdAcciones.appendChild(btnEliminar);
|
||||||
|
|
||||||
|
tr.appendChild(tdAcciones);
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== EDITAR GASTO ==========
|
||||||
|
|
||||||
|
function activarEdicionGasto(btnEditar, columnaActual) {
|
||||||
|
const fila = btnEditar.closest('tr');
|
||||||
|
const celdas = fila.querySelectorAll('td');
|
||||||
|
const descripcionActual = celdas[0].textContent;
|
||||||
|
const montoActual = parseFloat(celdas[1].textContent.replace('$', ''));
|
||||||
|
|
||||||
|
// Convertir descripción a input
|
||||||
|
celdas[0].innerHTML = `<input type="text" value="${columnaActual}" class="input-descripcion-gasto">`;
|
||||||
|
|
||||||
|
// Convertir monto a input
|
||||||
|
celdas[1].innerHTML = `<input type="number" value="${montoActual}" step="0.01" min="0" class="input-monto-gasto">`;
|
||||||
|
|
||||||
|
// Crear botones Guardar y Cancelar
|
||||||
|
const btnGuardar = document.createElement('button');
|
||||||
|
btnGuardar.type = 'button';
|
||||||
|
btnGuardar.textContent = 'Guardar';
|
||||||
|
btnGuardar.className = 'btn-guardar';
|
||||||
|
btnGuardar.addEventListener('click', () => guardarEdicionGasto(fila, columnaActual, montoActual, descripcionActual));
|
||||||
|
|
||||||
|
const btnCancelar = document.createElement('button');
|
||||||
|
btnCancelar.type = 'button';
|
||||||
|
btnCancelar.textContent = 'Cancelar';
|
||||||
|
btnCancelar.className = 'btn-cancelar';
|
||||||
|
btnCancelar.addEventListener('click', () => cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual));
|
||||||
|
|
||||||
|
// Reemplazar botones en la celda de acciones
|
||||||
|
celdas[2].innerHTML = '';
|
||||||
|
celdas[2].appendChild(btnGuardar);
|
||||||
|
celdas[2].appendChild(btnCancelar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual) {
|
||||||
|
const celdas = fila.querySelectorAll('td');
|
||||||
|
|
||||||
|
// Restaurar descripción
|
||||||
|
celdas[0].textContent = descripcionActual;
|
||||||
|
|
||||||
|
// Restaurar monto
|
||||||
|
celdas[1].textContent = `$${parseFloat(montoActual).toFixed(2)}`;
|
||||||
|
|
||||||
|
// Restaurar botones Editar y Eliminar
|
||||||
|
const btnEditar = document.createElement('button');
|
||||||
|
btnEditar.type = 'button';
|
||||||
|
btnEditar.className = 'btn-editar-gasto';
|
||||||
|
btnEditar.textContent = 'Editar';
|
||||||
|
btnEditar.addEventListener('click', () => activarEdicionGasto(btnEditar, columnaActual));
|
||||||
|
|
||||||
|
const btnEliminar = document.createElement('button');
|
||||||
|
btnEliminar.type = 'button';
|
||||||
|
btnEliminar.className = 'btn-eliminar-gasto';
|
||||||
|
btnEliminar.textContent = 'Eliminar';
|
||||||
|
btnEliminar.addEventListener('click', () => abrirModalEliminar(columnaActual));
|
||||||
|
|
||||||
|
celdas[2].innerHTML = '';
|
||||||
|
celdas[2].appendChild(btnEditar);
|
||||||
|
celdas[2].appendChild(btnEliminar);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function guardarEdicionGasto(fila, columnaActual, montoActual, descripcionActual) {
|
||||||
|
const nuevoNombre = fila.querySelector('.input-descripcion-gasto').value.trim();
|
||||||
|
const nuevoMonto = parseFloat(fila.querySelector('.input-monto-gasto').value);
|
||||||
|
|
||||||
|
if (!nuevoNombre) {
|
||||||
|
alert('El nombre no puede estar vacío');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(nuevoMonto) || nuevoMonto < 0) {
|
||||||
|
alert('El monto debe ser un número válido y mayor o igual a 0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Editar_Gasto.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
columna_actual: columnaActual,
|
||||||
|
columna_nueva: nuevoNombre,
|
||||||
|
monto: nuevoMonto
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('Gasto editado correctamente');
|
||||||
|
cargarEstructuraGastos();
|
||||||
|
} else {
|
||||||
|
alert('Error al editar gasto: ' + data.message);
|
||||||
|
cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión');
|
||||||
|
cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== ELIMINAR GASTO ==========
|
||||||
|
|
||||||
|
function abrirModalEliminar(columna) {
|
||||||
|
const confirmar = confirm(`¿Deseas eliminar el gasto "${columna}"? Esta acción no se puede deshacer.`);
|
||||||
|
|
||||||
|
if (confirmar) {
|
||||||
|
eliminarGasto(columna);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function eliminarGasto(columna) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Eliminar_Gasto.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
columna: columna
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert('Gasto eliminado correctamente');
|
||||||
|
cargarEstructuraGastos();
|
||||||
|
} else {
|
||||||
|
alert('Error al eliminar gasto: ' + data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
let tablaProductosInitDone = false;
|
||||||
|
|
||||||
|
function initTablaProductos() {
|
||||||
|
if (tablaProductosInitDone) return;
|
||||||
|
tablaProductosInitDone = true;
|
||||||
|
|
||||||
|
const form = document.getElementById("form-producto");
|
||||||
|
const tablaBody = document.querySelector("#tabla-productos tbody");
|
||||||
|
|
||||||
|
form.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const datos = new FormData(form);
|
||||||
|
const producto = {
|
||||||
|
descripcion: datos.get("descripcion"),
|
||||||
|
precio_venta: parseFloat(datos.get("precio_venta")),
|
||||||
|
precio_compra: parseFloat(datos.get("precio_compra")),
|
||||||
|
cantidad: parseInt(datos.get("cantidad")),
|
||||||
|
};
|
||||||
|
|
||||||
|
const esEdicion = !!form.dataset.editando;
|
||||||
|
if (esEdicion) {
|
||||||
|
producto.id = form.dataset.editando;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = esEdicion
|
||||||
|
? "../php/Editar_Producto.php"
|
||||||
|
: "../php/Agregar_Producto.php";
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(producto),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((msg) => {
|
||||||
|
alert(msg);
|
||||||
|
form.reset();
|
||||||
|
delete form.dataset.editando;
|
||||||
|
cargarProductos();
|
||||||
|
verificarStockBajoMenu();
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al guardar producto:", err));
|
||||||
|
});
|
||||||
|
|
||||||
|
verificarStockBajoMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stock limit helpers (stored in localStorage)
|
||||||
|
function getStockLimit() {
|
||||||
|
const val = localStorage.getItem('stock_limit_alert');
|
||||||
|
const n = parseInt(val, 10);
|
||||||
|
return isNaN(n) ? 10 : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStockLimit(limit) {
|
||||||
|
localStorage.setItem('stock_limit_alert', String(parseInt(limit, 10) || 0));
|
||||||
|
verificarStockBajoMenu();
|
||||||
|
if (typeof cargarProductos === 'function') cargarProductos();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", initTablaProductos);
|
||||||
|
if (document.readyState !== "loading") {
|
||||||
|
initTablaProductos();
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowStockIconHTML = `<i class="fa-solid fa-triangle-exclamation fa-beat low-stock-alert" style="color: rgb(255, 212, 59); margin-left: 0.35rem;" aria-hidden="true" title="Stock bajo"></i>`;
|
||||||
|
|
||||||
|
function actualizarAlertaStockEnMenu(tieneStockBajo) {
|
||||||
|
const linkProductos = document.querySelector('.sidebar__link[href="#productos"]');
|
||||||
|
if (!linkProductos) return;
|
||||||
|
|
||||||
|
const alertaExistente = linkProductos.querySelector('.low-stock-alert');
|
||||||
|
if (tieneStockBajo) {
|
||||||
|
if (!alertaExistente) {
|
||||||
|
linkProductos.insertAdjacentHTML('beforeend', lowStockIconHTML);
|
||||||
|
}
|
||||||
|
} else if (alertaExistente) {
|
||||||
|
alertaExistente.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function verificarStockBajoMenu() {
|
||||||
|
const limit = getStockLimit();
|
||||||
|
fetch("../php/Obtener_Productos.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((productos) => {
|
||||||
|
const tieneStockBajo = productos.some((p) => parseInt(p.Stock_Disponible, 10) < limit);
|
||||||
|
actualizarAlertaStockEnMenu(tieneStockBajo);
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Error al consultar stock bajo:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarProductos() {
|
||||||
|
fetch("../php/Obtener_Productos.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((productos) => {
|
||||||
|
const tablaBody = document.querySelector("#tabla-productos tbody");
|
||||||
|
tablaBody.innerHTML = "";
|
||||||
|
const limit = getStockLimit();
|
||||||
|
const tieneStockBajo = productos.some((p) => parseInt(p.Stock_Disponible, 10) < limit);
|
||||||
|
|
||||||
|
productos.forEach((p) => {
|
||||||
|
const stockBajo = parseInt(p.Stock_Disponible, 10) < limit;
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${p.ID_Producto}</td>
|
||||||
|
<td>${p.Descripcion}</td>
|
||||||
|
<td>$${parseFloat(p.Precio_Venta).toFixed(2)}</td>
|
||||||
|
<td>$${parseFloat(p.Precio_Compra).toFixed(2)}</td>
|
||||||
|
<td>${p.Stock_Disponible}${stockBajo ? lowStockIconHTML : ''}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-editar" data-id="${
|
||||||
|
p.ID_Producto
|
||||||
|
}">Editar</button>
|
||||||
|
<button class="btn-eliminar" data-id="${
|
||||||
|
p.ID_Producto
|
||||||
|
}">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tablaBody.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
actualizarAlertaStockEnMenu(tieneStockBajo);
|
||||||
|
|
||||||
|
document.querySelectorAll(".btn-editar").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => activarEdicionProducto(btn));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".btn-eliminar").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => eliminarProducto(btn.dataset.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init stock limit UI
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const input = document.getElementById('stock-limit-input');
|
||||||
|
const btn = document.getElementById('save-stock-limit');
|
||||||
|
if (input) {
|
||||||
|
input.value = getStockLimit();
|
||||||
|
}
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const v = document.getElementById('stock-limit-input').value;
|
||||||
|
setStockLimit(parseInt(v, 10) || 0);
|
||||||
|
alert('Límite de stock guardado: ' + (parseInt(v, 10) || 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function eliminarProducto(id) {
|
||||||
|
if (!confirm("¿Estás seguro que querés eliminar este producto?")) return;
|
||||||
|
|
||||||
|
fetch(`../php/Eliminar_Producto.php?id=${id}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
alert(data.message);
|
||||||
|
if (data.success) {
|
||||||
|
cargarProductos();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error:", err);
|
||||||
|
alert("Error al eliminar el producto");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function activarEdicionProducto(btnEditar) {
|
||||||
|
const fila = btnEditar.closest("tr");
|
||||||
|
const celdas = fila.querySelectorAll("td");
|
||||||
|
const idProducto = btnEditar.dataset.id;
|
||||||
|
|
||||||
|
const descripcionActual = celdas[1].textContent;
|
||||||
|
const precioVentaActual = parseFloat(celdas[2].textContent.replace('$', ''));
|
||||||
|
const precioCompraActual = parseFloat(celdas[3].textContent.replace('$', ''));
|
||||||
|
const stockActual = parseInt(celdas[4].textContent);
|
||||||
|
|
||||||
|
celdas[1].innerHTML = `<input type="text" value="${descripcionActual}" class="input-descripcion">`;
|
||||||
|
celdas[2].innerHTML = `<input type="number" value="${precioVentaActual}" class="input-precio-venta">`;
|
||||||
|
celdas[3].innerHTML = `<input type="number" value="${precioCompraActual}" class="input-precio-compra">`;
|
||||||
|
celdas[4].innerHTML = `<input type="number" value="${stockActual}" class="input-stock">`;
|
||||||
|
|
||||||
|
const btnGuardar = document.createElement("button");
|
||||||
|
btnGuardar.textContent = "Guardar";
|
||||||
|
btnGuardar.classList.add("btn-guardar");
|
||||||
|
btnGuardar.addEventListener("click", () => guardarEdicionProducto(fila, idProducto));
|
||||||
|
|
||||||
|
const btnCancelar = document.createElement("button");
|
||||||
|
btnCancelar.textContent = "Cancelar";
|
||||||
|
btnCancelar.classList.add("btn-cancelar");
|
||||||
|
btnCancelar.addEventListener("click", () =>
|
||||||
|
cancelarEdicionProducto(fila, descripcionActual, precioVentaActual, precioCompraActual, stockActual, idProducto)
|
||||||
|
);
|
||||||
|
|
||||||
|
const celdaBotones = celdas[5];
|
||||||
|
celdaBotones.innerHTML = "";
|
||||||
|
celdaBotones.appendChild(btnGuardar);
|
||||||
|
celdaBotones.appendChild(btnCancelar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelarEdicionProducto(fila, descripcion, venta, compra, stock, id) {
|
||||||
|
const celdas = fila.querySelectorAll("td");
|
||||||
|
celdas[1].textContent = descripcion;
|
||||||
|
celdas[2].textContent = `$${parseFloat(venta).toFixed(2)}`;
|
||||||
|
celdas[3].textContent = `$${parseFloat(compra).toFixed(2)}`;
|
||||||
|
celdas[4].textContent = stock;
|
||||||
|
|
||||||
|
const btnEditar = document.createElement("button");
|
||||||
|
btnEditar.textContent = "Editar";
|
||||||
|
btnEditar.classList.add("btn-editar");
|
||||||
|
btnEditar.dataset.id = id;
|
||||||
|
btnEditar.addEventListener("click", () => activarEdicionProducto(btnEditar));
|
||||||
|
|
||||||
|
const btnEliminar = document.createElement("button");
|
||||||
|
btnEliminar.textContent = "Eliminar";
|
||||||
|
btnEliminar.classList.add("btn-eliminar");
|
||||||
|
btnEliminar.dataset.id = id;
|
||||||
|
btnEliminar.addEventListener("click", () => eliminarProducto(id));
|
||||||
|
|
||||||
|
celdas[5].innerHTML = "";
|
||||||
|
celdas[5].appendChild(btnEditar);
|
||||||
|
celdas[5].appendChild(btnEliminar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function guardarEdicionProducto(fila, idProducto) {
|
||||||
|
const descripcion = fila.querySelector(".input-descripcion").value.trim();
|
||||||
|
const precioVenta = parseFloat(fila.querySelector(".input-precio-venta").value);
|
||||||
|
const precioCompra = parseFloat(fila.querySelector(".input-precio-compra").value);
|
||||||
|
const stock = parseInt(fila.querySelector(".input-stock").value);
|
||||||
|
|
||||||
|
if (!descripcion || isNaN(precioVenta) || isNaN(precioCompra) || isNaN(stock)) {
|
||||||
|
alert("Por favor, complete todos los campos correctamente.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("../php/Editar_Producto.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: idProducto,
|
||||||
|
descripcion,
|
||||||
|
precio_venta: precioVenta,
|
||||||
|
precio_compra: precioCompra,
|
||||||
|
cantidad: stock
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
cargarProductos();
|
||||||
|
} else {
|
||||||
|
alert("Error al guardar producto: " + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error:", err);
|
||||||
|
alert("Ocurrió un error al editar el producto.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar Stock sin recargar la pagina
|
||||||
|
window.addEventListener("hashchange", () => {
|
||||||
|
if (window.location.hash === "#productos") {
|
||||||
|
// Esperar un poco para que el DOM actualice la sección visible
|
||||||
|
setTimeout(() => {
|
||||||
|
const tabla = document.querySelector("#tabla-productos");
|
||||||
|
if (tabla) {
|
||||||
|
cargarProductos();
|
||||||
|
} else {
|
||||||
|
console.warn("Tabla de productos no encontrada.");
|
||||||
|
}
|
||||||
|
}, 100); // pequeño retardo para esperar que el DOM muestre la sección
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.cargarProductos = cargarProductos;
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const form = document.getElementById("form-proveedor");
|
||||||
|
const tablaBody = document.querySelector("#tabla-proveedores tbody");
|
||||||
|
|
||||||
|
form.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const nombre = document.querySelector(".nombre-proveedor").value;
|
||||||
|
const telefono = document.querySelector(".telefono-proveedor").value;
|
||||||
|
|
||||||
|
const proveedor = {
|
||||||
|
id: form.dataset.editando || null,
|
||||||
|
nombre,
|
||||||
|
telefono,
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = proveedor.id
|
||||||
|
? "../php/Editar_Proveedor.php"
|
||||||
|
: "../php/Guardar_Proveedor.php";
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(proveedor),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
alert(data.message || "Proveedor guardado");
|
||||||
|
form.reset();
|
||||||
|
delete form.dataset.editando;
|
||||||
|
cargarProveedores();
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (data.error || "Error desconocido"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cargarProveedores();
|
||||||
|
|
||||||
|
// Delegación de eventos para la tabla
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (e.target.classList.contains("btn-cancelar")) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const fila = e.target.closest("tr");
|
||||||
|
if (fila) {
|
||||||
|
cargarProveedores();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function cargarProveedores() {
|
||||||
|
fetch("../php/Obtener_Proveedores.php")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((proveedores) => {
|
||||||
|
const tablaBody = document.querySelector("#tabla-proveedores tbody");
|
||||||
|
tablaBody.innerHTML = "";
|
||||||
|
|
||||||
|
proveedores.forEach((p) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.setAttribute("data-id", p.ID_Proveedor);
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${p.ID_Proveedor}</td>
|
||||||
|
<td>${p.Nombre}</td>
|
||||||
|
<td>${p.Telefono}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-ver-productos" data-id="${p.ID_Proveedor}">Productos</button>
|
||||||
|
<button class="btn-editar-proveedor" data-id="${p.ID_Proveedor}">Editar</button>
|
||||||
|
<button class="btn-eliminar-proveedor" data-id="${p.ID_Proveedor}">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tablaBody.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("#tabla-proveedores .btn-editar-proveedor").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => activarModoEdicion(btn));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("#tabla-proveedores .btn-eliminar-proveedor").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => eliminarProveedor(btn.dataset.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".btn-ver-productos").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => cargarProductosDelProveedor(btn.dataset.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editarProveedor(id) {
|
||||||
|
fetch(`../php/Buscar_Proveedor_Editar.php?id=${id}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log("Respuesta del servidor:", data);
|
||||||
|
if (!data || !data.ID_Proveedor) {
|
||||||
|
alert("Proveedor no encontrado");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.getElementById("form-proveedor");
|
||||||
|
document.querySelector(".nombre-proveedor").value = data.Nombre;
|
||||||
|
document.querySelector(".telefono-proveedor").value = data.Telefono;
|
||||||
|
form.dataset.editando = data.ID_Proveedor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarProveedor(id) {
|
||||||
|
if (!confirm("¿Estás seguro que querés eliminar este proveedor?")) return;
|
||||||
|
|
||||||
|
fetch("../php/Eliminar_Proveedor.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id: parseInt(id, 10) }),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((text) => {
|
||||||
|
if (!text) {
|
||||||
|
throw new Error("Respuesta vacía del servidor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Respuesta de Eliminar_Proveedor no es JSON:", text);
|
||||||
|
throw new Error("Respuesta inválida del servidor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
alert(data.message || "Proveedor eliminado");
|
||||||
|
cargarProveedores();
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (data.error || data.message || "Error desconocido"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error al eliminar proveedor:", error);
|
||||||
|
alert(error.message || "Error de conexión al eliminar proveedor.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarProductosDelProveedor(idProveedor) {
|
||||||
|
fetch(`../php/Obtener_Productos_Proveedor.php?id=${idProveedor}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((productos) => {
|
||||||
|
const contenedor = document.getElementById("productos-proveedor");
|
||||||
|
const cuerpo = document.getElementById("productos-proveedor-body");
|
||||||
|
cuerpo.innerHTML = "";
|
||||||
|
|
||||||
|
productos.forEach((prod) => {
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.setAttribute("data-id", idProveedor);
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${prod.ID_Producto}</td>
|
||||||
|
<td>${prod.Descripcion}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-eliminar-producto" data-id-producto="${prod.ID_Producto}" data-id-proveedor="${idProveedor}">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
cuerpo.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
contenedor.style.display = "block";
|
||||||
|
|
||||||
|
document.querySelectorAll(".btn-eliminar-producto").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const idProd = btn.dataset.idProducto;
|
||||||
|
const idProv = btn.dataset.idProveedor;
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
`../php/Eliminar_Producto_Proveedor.php?id_proveedor=${idProv}&id_producto=${idProd}`
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
cargarProductosDelProveedor(idProv);
|
||||||
|
} else {
|
||||||
|
alert("Error: " + data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar producto al proveedor
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const formAgregarProducto = document.getElementById("form-agregar-producto");
|
||||||
|
|
||||||
|
formAgregarProducto.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const idProveedor = document.getElementById("id-proveedor-producto").value;
|
||||||
|
const idProducto = document.getElementById("nuevo-id-producto").value;
|
||||||
|
|
||||||
|
fetch("../php/Agregar_Producto_Proveedor.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
id_proveedor: parseInt(idProveedor),
|
||||||
|
id_producto: parseInt(idProducto),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
cargarProductosDelProveedor(idProveedor);
|
||||||
|
document.getElementById("nuevo-id-producto").value = "";
|
||||||
|
} else {
|
||||||
|
alert("Error: " + data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
if (e.target.classList.contains("btn-ver-productos")) {
|
||||||
|
const idProveedor = e.target.dataset.id;
|
||||||
|
|
||||||
|
document.getElementById("id-proveedor-producto").value = idProveedor;
|
||||||
|
cargarProductosDelProveedor(idProveedor);
|
||||||
|
|
||||||
|
// Mostrar tabla productos
|
||||||
|
document.getElementById("productos-proveedor").style.display = "block";
|
||||||
|
document.getElementById("tabla-proveedores").style.display = "none";
|
||||||
|
document.getElementById("titulo-proveedores").style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("cerrar-productos").addEventListener("click", () => {
|
||||||
|
document.getElementById("productos-proveedor").style.display = "none";
|
||||||
|
document.getElementById("tabla-proveedores").style.display = "table";
|
||||||
|
document.getElementById("titulo-proveedores").style.display = "block";
|
||||||
|
});
|
||||||
|
|
||||||
|
function activarModoEdicion(btnEditar) {
|
||||||
|
const fila = btnEditar.closest("tr");
|
||||||
|
const celdas = fila.querySelectorAll("td");
|
||||||
|
|
||||||
|
// Obtener ID desde el botón
|
||||||
|
const idProveedor = btnEditar.getAttribute("data-id");
|
||||||
|
|
||||||
|
if (!idProveedor) {
|
||||||
|
console.error("ID DE PROVEEDOR NO ENCONTRADO");
|
||||||
|
alert("ID de proveedor no encontrado.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener valores actuales ANTES de cambiar a inputs
|
||||||
|
const nombreActual = celdas[1].textContent.trim();
|
||||||
|
const telefonoActual = celdas[2].textContent.trim();
|
||||||
|
|
||||||
|
// Reemplazar con inputs
|
||||||
|
celdas[1].innerHTML = `<input type="text" value="${nombreActual}" class="input-nombre">`;
|
||||||
|
celdas[2].innerHTML = `<input type="text" value="${telefonoActual}" class="input-telefono">`;
|
||||||
|
|
||||||
|
// Botón Guardar
|
||||||
|
const btnGuardar = document.createElement("button");
|
||||||
|
btnGuardar.type = "button";
|
||||||
|
btnGuardar.textContent = "Guardar";
|
||||||
|
btnGuardar.className = "btn-guardar";
|
||||||
|
btnGuardar.setAttribute("data-id", idProveedor);
|
||||||
|
btnGuardar.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
guardarEdicion(fila, idProveedor);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Botón Cancelar
|
||||||
|
const btnCancelar = document.createElement("button");
|
||||||
|
btnCancelar.type = "button";
|
||||||
|
btnCancelar.textContent = "Cancelar";
|
||||||
|
btnCancelar.className = "btn-cancelar";
|
||||||
|
btnCancelar.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// Simplemente recargar la tabla cancela todos los cambios
|
||||||
|
cargarProveedores();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reemplazar celda de botones
|
||||||
|
const celdaBotones = celdas[3];
|
||||||
|
celdaBotones.innerHTML = "";
|
||||||
|
celdaBotones.appendChild(btnGuardar);
|
||||||
|
celdaBotones.appendChild(btnCancelar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function guardarEdicion(fila, idProveedor) {
|
||||||
|
const celdas = fila.querySelectorAll("td");
|
||||||
|
const nuevoNombre = fila.querySelector(".input-nombre").value.trim();
|
||||||
|
const nuevoTelefono = fila.querySelector(".input-telefono").value.trim();
|
||||||
|
|
||||||
|
if (nuevoNombre === "" || nuevoTelefono === "") {
|
||||||
|
alert("Por favor, complete todos los campos.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Datos enviados:", {
|
||||||
|
id: idProveedor,
|
||||||
|
nombre: nuevoNombre,
|
||||||
|
telefono: nuevoTelefono,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enviar los nuevos datos al servidor para actualizarlos en la base de datos
|
||||||
|
fetch("../php/Editar_Proveedor.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: idProveedor,
|
||||||
|
nombre: nuevoNombre,
|
||||||
|
telefono: nuevoTelefono,
|
||||||
|
}),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
alert("Proveedor actualizado correctamente");
|
||||||
|
cargarProveedores();
|
||||||
|
} else {
|
||||||
|
alert("Error al actualizar el proveedor.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error al actualizar el proveedor:", error);
|
||||||
|
alert("Ocurrió un error al actualizar el proveedor.");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,673 @@
|
|||||||
|
// Variables globales para mantener el auto-refresh
|
||||||
|
let filtrosActuales = {};
|
||||||
|
let intervaloActualizacion = null;
|
||||||
|
|
||||||
|
// Datos de las canchas (similar a Reservar_Turno.js)
|
||||||
|
let datosCanchas = { futbol: null, padel: null, quincho: null };
|
||||||
|
|
||||||
|
// Cargar datos de las canchas
|
||||||
|
async function cargarDatosCanchastabla() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('../php/Obtener_Canchas.php');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const canchas = Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: (data && Array.isArray(data.canchas) ? data.canchas : []);
|
||||||
|
|
||||||
|
if (!canchas.length) return;
|
||||||
|
|
||||||
|
canchas.forEach(cancha => {
|
||||||
|
const rawTipo = String(cancha.Tipo ?? cancha.tipo ?? '').trim().toUpperCase();
|
||||||
|
const tipo = rawTipo === 'F' || rawTipo === 'FUTBOL' ? 'F'
|
||||||
|
: rawTipo === 'P' || rawTipo === 'PADEL' ? 'P'
|
||||||
|
: rawTipo === 'Q' || rawTipo === 'QUINCHO' ? 'Q'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!tipo) return;
|
||||||
|
|
||||||
|
const duracion = parseInt(cancha.Duracion ?? cancha.duracion, 10) || 0;
|
||||||
|
const apertura = cancha.Hora_Apertura ?? cancha.apertura ?? null;
|
||||||
|
const cierre = cancha.Hora_Cierre ?? cancha.cierre ?? null;
|
||||||
|
|
||||||
|
if (tipo === 'F') {
|
||||||
|
datosCanchas.futbol = { duracion, apertura, cierre };
|
||||||
|
} else if (tipo === 'P') {
|
||||||
|
datosCanchas.padel = { duracion, apertura, cierre };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cargar datos de canchas:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
// Cargar datos de canchas
|
||||||
|
await cargarDatosCanchastabla();
|
||||||
|
|
||||||
|
// Establecer la fecha de hoy por defecto
|
||||||
|
const hoy = new Date().toISOString().split('T')[0];
|
||||||
|
const inputFecha = document.querySelector(".filtro-formulario input[name='fecha']");
|
||||||
|
if (inputFecha) {
|
||||||
|
inputFecha.value = hoy;
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarReservas({ fecha: hoy }); // Cargar reservas del día actual
|
||||||
|
iniciarActualizacionAutomatica(); // Inicia el auto-refresh
|
||||||
|
|
||||||
|
const formFiltro = document.querySelector(".filtro-formulario");
|
||||||
|
const btnReestablecer = document.getElementById("btn-reestablecer-filtros");
|
||||||
|
|
||||||
|
// Asegurar estilos para reprogramar (inyección en head si el css no está presente)
|
||||||
|
ensureReprogramStyles();
|
||||||
|
|
||||||
|
// Agregar event listeners para búsqueda dinámica
|
||||||
|
if (formFiltro) {
|
||||||
|
const inputs = formFiltro.querySelectorAll("input, select");
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
aplicarFiltrosDinamicos();
|
||||||
|
});
|
||||||
|
input.addEventListener("input", () => {
|
||||||
|
aplicarFiltrosDinamicos();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listener para botón reestablecer
|
||||||
|
if (btnReestablecer) {
|
||||||
|
btnReestablecer.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
reestablecerFiltros();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function aplicarFiltrosDinamicos() {
|
||||||
|
const formFiltro = document.querySelector(".filtro-formulario");
|
||||||
|
const formData = new FormData(formFiltro);
|
||||||
|
const filtros = {
|
||||||
|
fecha: formData.get("fecha"),
|
||||||
|
nombre: formData.get("nombre"),
|
||||||
|
dni: formData.get("dni"),
|
||||||
|
tipo: formData.get("tipo_cancha")
|
||||||
|
};
|
||||||
|
|
||||||
|
cargarReservas(filtros);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reestablecerFiltros() {
|
||||||
|
const formFiltro = document.querySelector(".filtro-formulario");
|
||||||
|
|
||||||
|
// Limpiar todos los inputs
|
||||||
|
const inputs = formFiltro.querySelectorAll("input, select");
|
||||||
|
inputs.forEach(input => {
|
||||||
|
if (input.name === "fecha") {
|
||||||
|
// Establecer fecha de hoy
|
||||||
|
input.value = new Date().toISOString().split('T')[0];
|
||||||
|
} else {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cargar reservas del día actual
|
||||||
|
const hoy = new Date().toISOString().split('T')[0];
|
||||||
|
cargarReservas({ fecha: hoy });
|
||||||
|
}
|
||||||
|
|
||||||
|
function cargarReservas(filtros = {}) {
|
||||||
|
// Guardar los filtros actuales para el auto-refresh (solo los que tienen valor)
|
||||||
|
const filtrosConValor = {};
|
||||||
|
for (const [key, value] of Object.entries(filtros)) {
|
||||||
|
if (value) {
|
||||||
|
filtrosConValor[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(filtrosConValor).length > 0) {
|
||||||
|
filtrosActuales = filtrosConValor;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = "../php/Obtener_Reservas.php?";
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
// Usar los filtros guardados si no se proporcionan nuevos
|
||||||
|
const filtrosAUsar = Object.keys(filtrosConValor).length > 0 ? filtrosConValor : filtrosActuales;
|
||||||
|
|
||||||
|
// Solo agregamos parámetros si tienen valor
|
||||||
|
if (filtrosAUsar.fecha) params.append("fecha", filtrosAUsar.fecha);
|
||||||
|
if (filtrosAUsar.nombre) params.append("nombre", filtrosAUsar.nombre);
|
||||||
|
if (filtrosAUsar.dni) params.append("dni", filtrosAUsar.dni);
|
||||||
|
if (filtrosAUsar.tipo) params.append("tipo", filtrosAUsar.tipo);
|
||||||
|
|
||||||
|
fetch(url + params.toString())
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(reservas => {
|
||||||
|
const tablaBody = document.querySelector("#tabla-reservas tbody");
|
||||||
|
tablaBody.innerHTML = "";
|
||||||
|
|
||||||
|
if (reservas.length === 0) {
|
||||||
|
tablaBody.innerHTML = "<tr><td colspan='9'>No se encontraron resultados.</td></tr>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reservas.forEach(r => {
|
||||||
|
const tipoTexto = r.Tipo === 'F' ? 'Fútbol' : (r.Tipo === 'P' ? 'Pádel' : (r.Tipo === 'Q' ? 'Quincho' : 'Otro'));
|
||||||
|
|
||||||
|
// Convertir estado numérico a texto
|
||||||
|
let estadoTexto = '';
|
||||||
|
let estadoValor = parseInt(r.Estado);
|
||||||
|
|
||||||
|
if (estadoValor === 0) {
|
||||||
|
estadoTexto = 'No Pagado';
|
||||||
|
} else if (estadoValor === 1) {
|
||||||
|
estadoTexto = 'Pagado';
|
||||||
|
} else if (estadoValor === 2) {
|
||||||
|
estadoTexto = 'Cancelado';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si la reserva es del pasado
|
||||||
|
const fechaReserva = new Date(r.Fecha_Hora);
|
||||||
|
const hoy = new Date();
|
||||||
|
hoy.setHours(0, 0, 0, 0);
|
||||||
|
fechaReserva.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const esDelPasado = fechaReserva < hoy;
|
||||||
|
const disabledAttr = esDelPasado ? 'disabled' : '';
|
||||||
|
const disabledClass = esDelPasado ? 'disabled' : '';
|
||||||
|
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.setAttribute("data-id-reserva", r.ID_Reserva);
|
||||||
|
fila.setAttribute("data-tipo-cancha", r.Tipo);
|
||||||
|
fila.setAttribute("data-numero-cancha", r.Numero);
|
||||||
|
fila.setAttribute("data-fecha-reserva", r.Fecha_Hora);
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${r.Nombre}</td>
|
||||||
|
<td>${r.DNI}</td>
|
||||||
|
<td>${r.Telefono}</td>
|
||||||
|
<td>${new Date(r.Fecha_Hora).toLocaleString()}</td>
|
||||||
|
<td>${tipoTexto}</td>
|
||||||
|
<td>${r.Numero}</td>
|
||||||
|
<td>${parseFloat(r.Descuento_Cupon) > 0 ? (parseFloat(r.Descuento_Cupon) * 100).toFixed(0) + '%' : '0%'}</td>
|
||||||
|
<td>$${parseFloat(r.Monto).toFixed(2)}</td>
|
||||||
|
<td>
|
||||||
|
<div class="estado-dropdown">
|
||||||
|
<button class="estado-btn ${disabledClass}" data-estado="${estadoValor}" ${disabledAttr} title="${esDelPasado ? 'No se pueden modificar reservas pasadas' : ''}">
|
||||||
|
<span class="estado-texto">${estadoTexto}</span>
|
||||||
|
<span class="estado-flecha">▼</span>
|
||||||
|
</button>
|
||||||
|
<div class="estado-opciones" style="display: none;">
|
||||||
|
<div class="estado-opcion" data-valor="0">No Pagado</div>
|
||||||
|
<div class="estado-opcion" data-valor="1">Pagado</div>
|
||||||
|
<div class="estado-opcion" data-valor="2">Cancelado</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="reprogramar-btn ${disabledClass}" ${disabledAttr} title="Reprogramar reserva">Reprogramar</button>
|
||||||
|
<div class="reprogramar-container" style="display:none;"></div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tablaBody.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Agregar event listeners al dropdown
|
||||||
|
agregarListenersDropdown();
|
||||||
|
agregarListenersReprogramar();
|
||||||
|
})
|
||||||
|
.catch(err => console.error("Error en fetch:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function agregarListenersDropdown() {
|
||||||
|
const botonesEstado = document.querySelectorAll(".estado-btn");
|
||||||
|
|
||||||
|
botonesEstado.forEach(btn => {
|
||||||
|
// Marcar la opción activa al inicio
|
||||||
|
const estadoActual = btn.getAttribute("data-estado");
|
||||||
|
const dropdown = btn.closest(".estado-dropdown");
|
||||||
|
const opciones = dropdown.querySelectorAll(".estado-opcion");
|
||||||
|
|
||||||
|
opciones.forEach(op => {
|
||||||
|
if (op.getAttribute("data-valor") === estadoActual) {
|
||||||
|
op.classList.add("activo");
|
||||||
|
} else {
|
||||||
|
op.classList.remove("activo");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Si el botón está deshabilitado, no hacer nada
|
||||||
|
if (btn.hasAttribute("disabled") || btn.classList.contains("disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opcionesDiv = dropdown.querySelector(".estado-opciones");
|
||||||
|
|
||||||
|
// Cerrar otros dropdowns abiertos
|
||||||
|
document.querySelectorAll(".estado-opciones").forEach(op => {
|
||||||
|
if (op !== opcionesDiv) op.style.display = "none";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle del dropdown actual
|
||||||
|
const estaAbierto = opcionesDiv.style.display === "block";
|
||||||
|
opcionesDiv.style.display = estaAbierto ? "none" : "block";
|
||||||
|
btn.setAttribute("aria-expanded", !estaAbierto);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click en opciones
|
||||||
|
const opcionesEstado = document.querySelectorAll(".estado-opcion");
|
||||||
|
opcionesEstado.forEach(opcion => {
|
||||||
|
opcion.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const dropdown = opcion.closest(".estado-dropdown");
|
||||||
|
const btn = dropdown.querySelector(".estado-btn");
|
||||||
|
const textoSpan = btn.querySelector(".estado-texto");
|
||||||
|
const opcionesDiv = dropdown.querySelector(".estado-opciones");
|
||||||
|
|
||||||
|
const nuevoValor = opcion.getAttribute("data-valor");
|
||||||
|
const textoOpcion = opcion.textContent;
|
||||||
|
|
||||||
|
// Remover clase activo de todas las opciones
|
||||||
|
dropdown.querySelectorAll(".estado-opcion").forEach(op => {
|
||||||
|
op.classList.remove("activo");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Agregar clase activo a la opción seleccionada
|
||||||
|
opcion.classList.add("activo");
|
||||||
|
|
||||||
|
// Actualizar el botón
|
||||||
|
textoSpan.textContent = textoOpcion;
|
||||||
|
btn.setAttribute("data-estado", nuevoValor);
|
||||||
|
|
||||||
|
// Ocultar dropdown
|
||||||
|
opcionesDiv.style.display = "none";
|
||||||
|
btn.setAttribute("aria-expanded", false);
|
||||||
|
|
||||||
|
// Guardar cambio en BD
|
||||||
|
const fila = opcion.closest("tr");
|
||||||
|
const idReserva = fila.getAttribute("data-id-reserva");
|
||||||
|
actualizarEstadoReserva(idReserva, parseInt(nuevoValor));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cerrar dropdown si se hace clic fuera
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (!e.target.closest(".estado-dropdown")) {
|
||||||
|
document.querySelectorAll(".estado-opciones").forEach(op => {
|
||||||
|
op.style.display = "none";
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".estado-btn").forEach(btn => {
|
||||||
|
btn.setAttribute("aria-expanded", false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarEstadoReserva(idReserva, nuevoEstado) {
|
||||||
|
fetch("../php/Actualizar_Estado_Reserva.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id_reserva: idReserva, estado: nuevoEstado })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log("Estado actualizado correctamente");
|
||||||
|
} else {
|
||||||
|
alert("Error al actualizar: " + data.error);
|
||||||
|
// Recargar para mostrar el estado anterior
|
||||||
|
cargarReservas();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error:", err);
|
||||||
|
alert("Error al actualizar el estado");
|
||||||
|
cargarReservas();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para iniciar la actualización automática
|
||||||
|
function iniciarActualizacionAutomatica() {
|
||||||
|
// Actualizar cada 5 segundos (5000 milisegundos)
|
||||||
|
intervaloActualizacion = setInterval(() => {
|
||||||
|
cargarReservas();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para detener la actualización automática (opcional, si es necesario)
|
||||||
|
function detenerActualizacionAutomatica() {
|
||||||
|
if (intervaloActualizacion) {
|
||||||
|
clearInterval(intervaloActualizacion);
|
||||||
|
intervaloActualizacion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listeners y modal para reprogramar
|
||||||
|
function agregarListenersReprogramar() {
|
||||||
|
const botones = document.querySelectorAll('.reprogramar-btn');
|
||||||
|
botones.forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return;
|
||||||
|
const fila = btn.closest('tr');
|
||||||
|
const idReserva = fila.getAttribute('data-id-reserva');
|
||||||
|
crearModalReprogramar(idReserva, fila);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function crearModalReprogramar(idReserva, fila) {
|
||||||
|
// Obtener datos de la reserva
|
||||||
|
const tipoCancha = fila.getAttribute('data-tipo-cancha'); // 'F' o 'P'
|
||||||
|
const fechaActualReserva = fila.getAttribute('data-fecha-reserva');
|
||||||
|
|
||||||
|
// Crear overlay
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'reprogramar-overlay';
|
||||||
|
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'reprogramar-modal';
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<h3 class="modal-title">Elige un día</h3>
|
||||||
|
<div class="dias-grid"></div>
|
||||||
|
<h3 class="modal-title">Selecciona horario</h3>
|
||||||
|
<div class="horarios-grid"></div>
|
||||||
|
<h3 class="modal-title">Selecciona cancha</h3>
|
||||||
|
<div class="canchas-grid"></div>
|
||||||
|
<div class="reprogramar-actions">
|
||||||
|
<button class="btn-cancelar">Cancelar</button>
|
||||||
|
<button class="btn-confirmar" disabled>Confirmar</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
overlay.appendChild(modal);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Generar los próximos 7 días
|
||||||
|
const diasGrid = modal.querySelector('.dias-grid');
|
||||||
|
const hoy = new Date();
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const dia = new Date(hoy);
|
||||||
|
dia.setDate(hoy.getDate() + i);
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'dia-btn';
|
||||||
|
const options = { weekday: 'short', day: 'numeric', month: 'numeric' };
|
||||||
|
btn.textContent = `${dia.toLocaleDateString(undefined, { weekday: 'short' })}\n${dia.getDate()}/${dia.getMonth()+1}`;
|
||||||
|
btn.dataset.iso = dia.toISOString().split('T')[0];
|
||||||
|
diasGrid.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
let diaSeleccionado = null;
|
||||||
|
let horaSeleccionada = null;
|
||||||
|
let canchaSeleccionada = null;
|
||||||
|
|
||||||
|
// Handlers selección de día
|
||||||
|
modal.querySelectorAll('.dia-btn').forEach(d => {
|
||||||
|
d.addEventListener('click', async () => {
|
||||||
|
modal.querySelectorAll('.dia-btn').forEach(x => x.classList.remove('selected'));
|
||||||
|
d.classList.add('selected');
|
||||||
|
diaSeleccionado = d.dataset.iso;
|
||||||
|
|
||||||
|
// Generar horarios cuando se selecciona un día
|
||||||
|
await generarHorariosReprogramar(modal, tipoCancha, diaSeleccionado);
|
||||||
|
|
||||||
|
// Limpiar selección de horario y cancha
|
||||||
|
horaSeleccionada = null;
|
||||||
|
canchaSeleccionada = null;
|
||||||
|
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delegation para horarios (se generan dinámicamente)
|
||||||
|
modal.addEventListener('click', async (e) => {
|
||||||
|
if (e.target.classList.contains('horario-btn') && !e.target.disabled) {
|
||||||
|
modal.querySelectorAll('.horario-btn').forEach(x => x.classList.remove('selected'));
|
||||||
|
e.target.classList.add('selected');
|
||||||
|
horaSeleccionada = e.target.dataset.hora;
|
||||||
|
canchaSeleccionada = null;
|
||||||
|
|
||||||
|
// Generar selección de canchas
|
||||||
|
await generarSeleccionCanchasReprogramar(modal, tipoCancha, diaSeleccionado, horaSeleccionada);
|
||||||
|
|
||||||
|
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delegation para canchas
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('cancha-btn') && !e.target.disabled) {
|
||||||
|
modal.querySelectorAll('.cancha-btn').forEach(x => x.classList.remove('selected'));
|
||||||
|
e.target.classList.add('selected');
|
||||||
|
canchaSeleccionada = e.target.dataset.numero;
|
||||||
|
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancelar
|
||||||
|
modal.querySelector('.btn-cancelar').addEventListener('click', () => {
|
||||||
|
closeModalReprogramar(overlay);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirmar
|
||||||
|
modal.querySelector('.btn-confirmar').addEventListener('click', () => {
|
||||||
|
if (!diaSeleccionado || !horaSeleccionada || !canchaSeleccionada) return;
|
||||||
|
const fechaHora = diaSeleccionado + ' ' + horaSeleccionada;
|
||||||
|
reprogramarReserva(idReserva, fechaHora, overlay);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cerrar al hacer click fuera del modal
|
||||||
|
overlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === overlay) closeModalReprogramar(overlay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar horarios para reprogramación
|
||||||
|
async function generarHorariosReprogramar(modal, tipoCancha, fechaSeleccionada) {
|
||||||
|
const horariosGrid = modal.querySelector('.horarios-grid');
|
||||||
|
horariosGrid.innerHTML = '';
|
||||||
|
|
||||||
|
const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel';
|
||||||
|
const datos = datosCanchas[tipoCode];
|
||||||
|
|
||||||
|
if (!datos || !datos.duracion || !datos.apertura || !datos.cierre) {
|
||||||
|
horariosGrid.innerHTML = '<p>Error: No se pudieron cargar los datos de las canchas</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const duracion = datos.duracion;
|
||||||
|
const [aperturaH, aperturaM] = datos.apertura.split(':').map(Number);
|
||||||
|
const [cierreH, cierreM] = datos.cierre.split(':').map(Number);
|
||||||
|
const aperturaTotalMin = aperturaH * 60 + aperturaM;
|
||||||
|
const cierreTotalMin = cierreH * 60 + cierreM;
|
||||||
|
|
||||||
|
const ahora = new Date();
|
||||||
|
const [selYear, selMonth, selDay] = fechaSeleccionada.split('-').map(Number);
|
||||||
|
const fechaSelDate = new Date(selYear, selMonth - 1, selDay);
|
||||||
|
const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate());
|
||||||
|
const esHoy = fechaSelDate.getTime() === hoy.getTime();
|
||||||
|
const minutoActual = ahora.getHours() * 60 + ahora.getMinutes();
|
||||||
|
|
||||||
|
let horaActualMin = aperturaTotalMin;
|
||||||
|
|
||||||
|
while (horaActualMin + duracion <= cierreTotalMin) {
|
||||||
|
const horas = Math.floor(horaActualMin / 60);
|
||||||
|
const minutos = horaActualMin % 60;
|
||||||
|
const horaFin = Math.floor((horaActualMin + duracion) / 60);
|
||||||
|
const minutosFin = (horaActualMin + duracion) % 60;
|
||||||
|
|
||||||
|
const inicio = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`;
|
||||||
|
const fin = `${String(horaFin).padStart(2, '0')}:${String(minutosFin).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
const inicioMin = horas * 60 + minutos;
|
||||||
|
const esPasado = fechaSelDate < hoy || (esHoy && inicioMin < minutoActual);
|
||||||
|
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'horario-btn';
|
||||||
|
btn.textContent = `${inicio}\n-\n${fin}`;
|
||||||
|
btn.dataset.hora = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}:00`;
|
||||||
|
|
||||||
|
// Verificar disponibilidad
|
||||||
|
let disponibles = 0;
|
||||||
|
if (!esPasado) {
|
||||||
|
disponibles = await obtenerDisponibilidadCanchaReprogramar(
|
||||||
|
fechaSeleccionada,
|
||||||
|
inicio,
|
||||||
|
duracion,
|
||||||
|
tipoCancha
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desactivar si es pasado o no hay canchas disponibles
|
||||||
|
if (esPasado || disponibles === 0) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.classList.add('no-disponible');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
horariosGrid.appendChild(btn);
|
||||||
|
horaActualMin += duracion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpiar grid de canchas
|
||||||
|
modal.querySelector('.canchas-grid').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener disponibilidad de cancha para reprogramación
|
||||||
|
async function obtenerDisponibilidadCanchaReprogramar(fecha, horario, duracion, tipo) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`../php/Obtener_Disponibilidad_Cancha.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo}`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success ? data.disponibles : 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error obteniendo disponibilidad:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar selección de canchas disponibles
|
||||||
|
async function generarSeleccionCanchasReprogramar(modal, tipoCancha, fecha, horario) {
|
||||||
|
const canchasGrid = modal.querySelector('.canchas-grid');
|
||||||
|
canchasGrid.innerHTML = '';
|
||||||
|
|
||||||
|
const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel';
|
||||||
|
const datos = datosCanchas[tipoCode];
|
||||||
|
|
||||||
|
if (!datos || !datos.duracion) {
|
||||||
|
canchasGrid.innerHTML = '<p>Error: No se pudieron cargar los datos</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`../php/Obtener_Canchas_Numeros.php?fecha=${fecha}&horario=${horario}&duracion=${datos.duracion}&tipo=${tipoCancha}`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.canchas) {
|
||||||
|
data.canchas.forEach(cancha => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'cancha-btn';
|
||||||
|
btn.textContent = `Cancha ${cancha.numero}`;
|
||||||
|
btn.dataset.numero = cancha.numero;
|
||||||
|
|
||||||
|
if (!cancha.disponible) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.classList.add('no-disponible');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
btn.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
canchasGrid.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generando selección de canchas:', error);
|
||||||
|
canchasGrid.innerHTML = '<p>Error al cargar canchas disponibles</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualizarEstadoBotonConfirm(modal, dia, hora, cancha) {
|
||||||
|
const btn = modal.querySelector('.btn-confirmar');
|
||||||
|
if (dia && hora && cancha) {
|
||||||
|
btn.disabled = false;
|
||||||
|
} else {
|
||||||
|
btn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModalReprogramar(overlay) {
|
||||||
|
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reprogramarReserva(idReserva, fechaHora, overlay) {
|
||||||
|
fetch('../php/Actualizar_Horario_Reserva.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id_reserva: idReserva, fecha_hora: fechaHora })
|
||||||
|
})
|
||||||
|
.then(async res => {
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
const text = await res.text();
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Respuesta no JSON: ' + text.trim().replace(/\s+/g, ' '));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
closeModalReprogramar(overlay);
|
||||||
|
cargarReservas();
|
||||||
|
alert('Reserva reprogramada correctamente');
|
||||||
|
} else {
|
||||||
|
alert('Error al reprogramar: ' + (data.error || 'Error desconocido'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error reprogramando:', err);
|
||||||
|
alert('Error al reprogramar la reserva: ' + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inyecta estilos mínimos si no están presentes en el CSS cargado
|
||||||
|
function ensureReprogramStyles() {
|
||||||
|
if (document.getElementById('reprogramar-styles')) return;
|
||||||
|
const css = `
|
||||||
|
.reprogramar-btn{margin-left:8px;padding:6px 8px;border-radius:6px;background:rgba(30,125,30,0.12);border:1px solid rgba(30,125,30,0.2);color:#d9f2d9;cursor:pointer;font-weight:600}
|
||||||
|
.reprogramar-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;padding:20px}
|
||||||
|
.reprogramar-modal{width:min(980px,96%);max-height:90vh;overflow:auto;background:#111;border-radius:12px;padding:18px;border:1px solid rgba(30,125,30,0.18)}
|
||||||
|
.dias-grid{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:14px}.dia-btn{width:110px;height:70px;border-radius:10px;padding:8px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line;display:flex;flex-direction:column;align-items:center;justify-content:center}
|
||||||
|
.dia-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
|
||||||
|
.horarios-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.horario-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line}
|
||||||
|
.horario-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
|
||||||
|
.horario-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important}
|
||||||
|
.canchas-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.cancha-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer}
|
||||||
|
.cancha-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
|
||||||
|
.cancha-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important}
|
||||||
|
.reprogramar-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.reprogramar-actions .btn-cancelar,.reprogramar-actions .btn-confirmar{padding:10px 14px;border-radius:8px;font-weight:700;border:2px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.03);color:#fff}
|
||||||
|
.reprogramar-actions .btn-confirmar:disabled{opacity:0.5;cursor:not-allowed}
|
||||||
|
`;
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'reprogramar-styles';
|
||||||
|
style.appendChild(document.createTextNode(css));
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegación: por si los listeners directos no se añadieron (tabla re-renderizada)
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest && e.target.closest('.reprogramar-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return;
|
||||||
|
// evitar crear múltiples overlays
|
||||||
|
if (document.querySelector('.reprogramar-overlay')) return;
|
||||||
|
const fila = btn.closest('tr');
|
||||||
|
const idReserva = fila ? fila.getAttribute('data-id-reserva') : null;
|
||||||
|
if (idReserva) crearModalReprogramar(idReserva, fila);
|
||||||
|
});
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
const productos = [];
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const btnMas = document.querySelector(".btn-mas");
|
||||||
|
const btnRegistrar = document.querySelector("#btn-registrar-venta");
|
||||||
|
const codigoInput = document.querySelector(
|
||||||
|
'input[placeholder="Código del Producto"]'
|
||||||
|
);
|
||||||
|
const nombreInput = document.querySelector(
|
||||||
|
'input[placeholder="Nombre del Producto"]'
|
||||||
|
);
|
||||||
|
const cantidadInput = document.querySelector(
|
||||||
|
'input[placeholder="Cantidad"]'
|
||||||
|
);
|
||||||
|
|
||||||
|
const buscarProducto = (terminoBusqueda) => {
|
||||||
|
return fetch(
|
||||||
|
`../php/Buscar_Producto_Ventas.php?codigo=${encodeURIComponent(terminoBusqueda)}`
|
||||||
|
).then((response) => response.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
const completarProductoDesdeInputs = (event) => {
|
||||||
|
// Identificamos cuál de los dos inputs disparó la acción
|
||||||
|
const inputActual = event ? event.target : null;
|
||||||
|
|
||||||
|
// 1. Si el usuario vació el input en el que está trabajando, limpiamos ambos y salimos
|
||||||
|
if (inputActual && inputActual.value.trim() === "") {
|
||||||
|
codigoInput.value = "";
|
||||||
|
nombreInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codigo = codigoInput.value.trim();
|
||||||
|
const nombre = nombreInput.value.trim();
|
||||||
|
|
||||||
|
// 2. Buscamos priorizando el input que el usuario acaba de modificar
|
||||||
|
let terminoBusqueda = "";
|
||||||
|
if (inputActual === codigoInput) {
|
||||||
|
terminoBusqueda = codigo;
|
||||||
|
} else if (inputActual === nombreInput) {
|
||||||
|
terminoBusqueda = nombre;
|
||||||
|
} else {
|
||||||
|
terminoBusqueda = codigo || nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si por alguna razón ambos están vacíos, aseguramos la limpieza
|
||||||
|
if (!terminoBusqueda) {
|
||||||
|
codigoInput.value = "";
|
||||||
|
nombreInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buscarProducto(terminoBusqueda)
|
||||||
|
.then((data) => {
|
||||||
|
if (!data || !data.ID_Producto) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codigoInput.value = data.ID_Producto;
|
||||||
|
nombreInput.value = data.Descripcion;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error al autocompletar el producto:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
[codigoInput, nombreInput].forEach((input) => {
|
||||||
|
// Cuando sale del campo (hace clic afuera)
|
||||||
|
input.addEventListener("blur", completarProductoDesdeInputs);
|
||||||
|
|
||||||
|
// Cuando presiona Enter
|
||||||
|
input.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
completarProductoDesdeInputs(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// NUEVO: Cuando borra el contenido en tiempo real
|
||||||
|
input.addEventListener("input", (event) => {
|
||||||
|
if (event.target.value.trim() === "") {
|
||||||
|
codigoInput.value = "";
|
||||||
|
nombreInput.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
btnMas.addEventListener("click", () => {
|
||||||
|
const codigo = codigoInput.value.trim();
|
||||||
|
const nombre = nombreInput.value.trim();
|
||||||
|
const cantidad = parseInt(cantidadInput.value);
|
||||||
|
|
||||||
|
if (!codigo && !nombre) {
|
||||||
|
alert("Debe ingresar el Código o el Nombre del producto.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(cantidad) || cantidad <= 0) {
|
||||||
|
alert("Ingrese una cantidad válida mayor a 0.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const terminoBusqueda = codigo || nombre;
|
||||||
|
|
||||||
|
buscarProducto(terminoBusqueda)
|
||||||
|
.then((data) => {
|
||||||
|
console.log("Producto obtenido:", data);
|
||||||
|
if (!data || !data.ID_Producto) {
|
||||||
|
alert("Producto no encontrado.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stockDisponible = parseInt(data.Stock_Disponible);
|
||||||
|
|
||||||
|
// Calculamos cuánto de este producto ya tenemos en la lista actual
|
||||||
|
const cantidadEnLista = productos
|
||||||
|
.filter((p) => p.id === data.ID_Producto)
|
||||||
|
.reduce((acc, p) => acc + p.cantidad, 0);
|
||||||
|
|
||||||
|
const cantidadTotalIntentada = cantidad + cantidadEnLista;
|
||||||
|
|
||||||
|
if (cantidadTotalIntentada > stockDisponible) {
|
||||||
|
alert(`Stock insuficiente.
|
||||||
|
Disponible: ${stockDisponible} unidades.
|
||||||
|
En lista: ${cantidadEnLista} unidades.
|
||||||
|
No puedes agregar ${cantidad} más.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const precio = parseFloat(data.Precio_Venta);
|
||||||
|
const subtotal = precio * cantidad;
|
||||||
|
|
||||||
|
productos.push({
|
||||||
|
id: data.ID_Producto,
|
||||||
|
descripcion: data.Descripcion,
|
||||||
|
precio: precio,
|
||||||
|
cantidad: cantidad,
|
||||||
|
subtotal: subtotal,
|
||||||
|
stockMaximo: stockDisponible,
|
||||||
|
});
|
||||||
|
|
||||||
|
actualizarTabla();
|
||||||
|
codigoInput.value = "";
|
||||||
|
nombreInput.value = "";
|
||||||
|
cantidadInput.value = "";
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error al buscar el producto:", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
btnRegistrar.addEventListener("click", () => {
|
||||||
|
if (productos.length === 0) {
|
||||||
|
alert("No hay productos agregados.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("../php/Registrar_Venta.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(productos),
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((msg) => {
|
||||||
|
alert(msg);
|
||||||
|
productos.length = 0;
|
||||||
|
actualizarTabla();
|
||||||
|
if (typeof window.verificarStockBajoMenu === 'function') {
|
||||||
|
window.verificarStockBajoMenu();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error al registrar la venta:", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function actualizarTabla() {
|
||||||
|
const tbody = document.querySelector("#tabla-ventas tbody");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
productos.forEach((p, index) => {
|
||||||
|
total += p.subtotal;
|
||||||
|
|
||||||
|
const fila = document.createElement("tr");
|
||||||
|
fila.innerHTML = `
|
||||||
|
<td>${p.id}</td>
|
||||||
|
<td>${p.descripcion}</td>
|
||||||
|
<td>$${p.precio.toFixed(2)}</td>
|
||||||
|
<td>${p.cantidad}</td>
|
||||||
|
<td>$${p.subtotal.toFixed(2)}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-eliminar" onclick="eliminarProductoVenta(${index})">Eliminar</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(fila);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("total").value = `$${total.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarProductoVenta(index) {
|
||||||
|
productos.splice(index, 1);
|
||||||
|
actualizarTabla();
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
include 'conexion.php';
|
||||||
|
|
||||||
|
// Leer el JSON enviado por JS
|
||||||
|
$json = file_get_contents('php://input');
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
die("No se recibieron datos.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$tipo = $data['tipo'];
|
||||||
|
$precio = $data['precio'];
|
||||||
|
$duracion = $data['duracion'];
|
||||||
|
$cantidad = $data['cantidad'];
|
||||||
|
$res_cupon = $data['reservas_cupon'];
|
||||||
|
$descuento = floatval(str_replace(',', '.', (string)($data['descuento'] ?? 0)));
|
||||||
|
if ($descuento > 1) {
|
||||||
|
$descuento = $descuento / 100;
|
||||||
|
}
|
||||||
|
$descuento = round($descuento, 4);
|
||||||
|
$faltas = $data['faltas'];
|
||||||
|
$duracion_cupon = $data['duracion_cupon'];
|
||||||
|
|
||||||
|
// Evitar inyección SQL básica
|
||||||
|
$tipo = mysqli_real_escape_string($conexion, $tipo);
|
||||||
|
|
||||||
|
$sql = "UPDATE canchas SET
|
||||||
|
Precio = '$precio',
|
||||||
|
Duracion = '$duracion',
|
||||||
|
Cant_Canchas = '$cantidad',
|
||||||
|
Cant_Reservas_Cupon = '$res_cupon',
|
||||||
|
Descuento_Cupon = '$descuento',
|
||||||
|
Cant_Faltas = '$faltas',
|
||||||
|
Duracion_Cupon = '$duracion_cupon'
|
||||||
|
WHERE Tipo = '$tipo'";
|
||||||
|
|
||||||
|
if (mysqli_query($conexion, $sql)) {
|
||||||
|
echo "Configuración de " . $tipo . " actualizada correctamente.";
|
||||||
|
} else {
|
||||||
|
echo "Error al actualizar: " . mysqli_error($conexion);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
include 'conexion.php';
|
||||||
|
|
||||||
|
// Leer el JSON enviado
|
||||||
|
$json = file_get_contents('php://input');
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
|
||||||
|
if (!$data || !isset($data['id_reserva']) || !isset($data['estado'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datos incompletos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_reserva = intval($data['id_reserva']);
|
||||||
|
$estado = intval($data['estado']);
|
||||||
|
|
||||||
|
// Validar que el estado sea 0, 1 o 2
|
||||||
|
if (!in_array($estado, [0, 1, 2])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Estado inválido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar la reserva
|
||||||
|
$sql = "UPDATE reservas SET Estado = $estado WHERE ID_Reserva = $id_reserva";
|
||||||
|
|
||||||
|
if (mysqli_query($conexion, $sql)) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Estado actualizado correctamente']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => mysqli_error($conexion)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conexion);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
// Validar datos
|
||||||
|
if (!isset($data['gastos']) || empty($data['gastos'])) {
|
||||||
|
throw new Exception("No hay datos de gastos para guardar");
|
||||||
|
}
|
||||||
|
|
||||||
|
$gastos = $data['gastos'];
|
||||||
|
$fecha_hoy = date('Y-m-d');
|
||||||
|
|
||||||
|
// Buscar el registro actual (donde Periodo_Hasta IS NULL)
|
||||||
|
$sql_get_actual = "SELECT ID_Gasto FROM gastos WHERE Periodo_Hasta IS NULL ORDER BY Periodo_Desde DESC LIMIT 1";
|
||||||
|
$result_actual = $conexion->query($sql_get_actual);
|
||||||
|
|
||||||
|
if ($result_actual && $result_actual->num_rows > 0) {
|
||||||
|
$row_actual = $result_actual->fetch_assoc();
|
||||||
|
$id_gasto_actual = $row_actual['ID_Gasto'];
|
||||||
|
|
||||||
|
// Actualizar el Periodo_Hasta del registro actual con la fecha actual
|
||||||
|
$sql_update = "UPDATE gastos SET Periodo_Hasta = ? WHERE ID_Gasto = ?";
|
||||||
|
$stmt_update = $conexion->prepare($sql_update);
|
||||||
|
|
||||||
|
if (!$stmt_update) {
|
||||||
|
throw new Exception("Error preparando UPDATE: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_update->bind_param('si', $fecha_hoy, $id_gasto_actual);
|
||||||
|
|
||||||
|
if (!$stmt_update->execute()) {
|
||||||
|
throw new Exception("Error actualizando período anterior: " . $stmt_update->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_update->close();
|
||||||
|
|
||||||
|
// Obtener todos los valores del registro que acaba de cerrarse
|
||||||
|
$sql_get_values = "SELECT * FROM gastos WHERE ID_Gasto = ?";
|
||||||
|
$stmt_get_values = $conexion->prepare($sql_get_values);
|
||||||
|
$stmt_get_values->bind_param('i', $id_gasto_actual);
|
||||||
|
$stmt_get_values->execute();
|
||||||
|
$result_values = $stmt_get_values->get_result();
|
||||||
|
$valores_anteriores = $result_values->fetch_assoc();
|
||||||
|
$stmt_get_values->close();
|
||||||
|
} else {
|
||||||
|
$valores_anteriores = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir INSERT dinámico
|
||||||
|
$columnas = ['Periodo_Desde', 'Periodo_Hasta'];
|
||||||
|
$valores = [$fecha_hoy, null];
|
||||||
|
$placeholders = ['?', '?'];
|
||||||
|
$tipos = 'ss';
|
||||||
|
|
||||||
|
// Agregar columnas de gastos del request
|
||||||
|
foreach ($gastos as $col => $val) {
|
||||||
|
$columnas[] = $col;
|
||||||
|
$valores[] = floatval($val);
|
||||||
|
$placeholders[] = '?';
|
||||||
|
$tipos .= 'd';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar columnas restantes del registro anterior (si existen)
|
||||||
|
if (!empty($valores_anteriores)) {
|
||||||
|
foreach ($valores_anteriores as $col => $val) {
|
||||||
|
if (!in_array($col, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta']) && !isset($gastos[$col])) {
|
||||||
|
$columnas[] = $col;
|
||||||
|
$valores[] = floatval($val);
|
||||||
|
$placeholders[] = '?';
|
||||||
|
$tipos .= 'd';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_insert = "INSERT INTO gastos (" . implode(', ', $columnas) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||||
|
$stmt_insert = $conexion->prepare($sql_insert);
|
||||||
|
|
||||||
|
if (!$stmt_insert) {
|
||||||
|
throw new Exception("Error preparando INSERT: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_insert->bind_param($tipos, ...$valores);
|
||||||
|
|
||||||
|
if (!$stmt_insert->execute()) {
|
||||||
|
throw new Exception("Error insertando registro: " . $stmt_insert->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nuevo_id = $stmt_insert->insert_id;
|
||||||
|
$stmt_insert->close();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Gastos guardados correctamente',
|
||||||
|
'id_gasto' => $nuevo_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
include 'conexion.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
$json = trim(file_get_contents('php://input'));
|
||||||
|
if ($json === '') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Cuerpo vacío o no se envió JSON']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
if (!is_array($data) || json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$rawPreview = substr($json, 0, 200);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'JSON inválido en la solicitud',
|
||||||
|
'detalle' => json_last_error_msg(),
|
||||||
|
'raw' => $rawPreview,
|
||||||
|
'raw_hex' => bin2hex(substr($json, 0, 50))
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($data['id_reserva']) || !isset($data['fecha_hora'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datos incompletos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_reserva = intval($data['id_reserva']);
|
||||||
|
$fecha_hora = $conexion->real_escape_string($data['fecha_hora']);
|
||||||
|
|
||||||
|
// Validación básica de formato YYYY-MM-DD HH:MM:SS
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $fecha_hora)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Formato de fecha_hora inválido. Use YYYY-MM-DD HH:MM:SS']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE reservas SET Fecha_Hora = '$fecha_hora' WHERE ID_Reserva = $id_reserva";
|
||||||
|
|
||||||
|
if (mysqli_query($conexion, $sql)) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Horario actualizado correctamente']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => mysqli_error($conexion)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conexion);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
include 'conexion.php';
|
||||||
|
$datos = json_decode(file_get_contents("php://input"), true);
|
||||||
|
|
||||||
|
$error = false;
|
||||||
|
foreach ($datos as $h) {
|
||||||
|
$dia = $h['dia'];
|
||||||
|
$ape = $h['ape'];
|
||||||
|
$cie = $h['cie'];
|
||||||
|
|
||||||
|
$sql = "UPDATE horarios SET Hora_Apertura = '$ape', Hora_Cierre = '$cie' WHERE Dia = '$dia'";
|
||||||
|
if (!mysqli_query($conexion, $sql)) {
|
||||||
|
$error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $error ? "Error al actualizar algunos horarios" : "Horarios actualizados correctamente";
|
||||||
|
?>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
mysqli_report(MYSQLI_REPORT_OFF);
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No hay conexión con la base de datos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($data) || !isset($data['id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Faltan datos requeridos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idOrden = intval($data['id']);
|
||||||
|
$estado = isset($data['estado']) ? intval($data['estado']) : (isset($data['aprobada']) ? intval($data['aprobada']) : 0);
|
||||||
|
|
||||||
|
if (!in_array($estado, [0, 1, 2, 3], true)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Estado no válido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tableHasColumn($conexion, $table, $column)
|
||||||
|
{
|
||||||
|
$stmt = $conexion->prepare(
|
||||||
|
"SELECT COUNT(*) AS count FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||||
|
);
|
||||||
|
if (!$stmt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$stmt->bind_param('ss', $table, $column);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
$row = $result ? $result->fetch_assoc() : null;
|
||||||
|
$stmt->close();
|
||||||
|
return $row && intval($row['count']) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasEstado = tableHasColumn($conexion, 'ordenes_compra', 'Estado');
|
||||||
|
if (!$hasEstado) {
|
||||||
|
$alterQuery = "ALTER TABLE ordenes_compra ADD COLUMN Estado TINYINT(1) DEFAULT 0";
|
||||||
|
if (!$conexion->query($alterQuery)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudo corregir la estructura de la tabla: ' . $conexion->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$hasEstado = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasAprobada = tableHasColumn($conexion, 'ordenes_compra', 'Aprobada');
|
||||||
|
if (!$hasAprobada) {
|
||||||
|
$alterQuery = "ALTER TABLE ordenes_compra ADD COLUMN Aprobada TINYINT(1) DEFAULT 0";
|
||||||
|
if (!$conexion->query($alterQuery)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudo corregir la estructura de la tabla: ' . $conexion->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$hasAprobada = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aprobada = in_array($estado, [1, 3], true) ? 1 : 0;
|
||||||
|
|
||||||
|
$conexion->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($hasEstado && $hasAprobada) {
|
||||||
|
$stmt = $conexion->prepare("UPDATE ordenes_compra SET Estado = ?, Aprobada = ? WHERE ID_Orden = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception('Error en la consulta SQL: ' . $conexion->error);
|
||||||
|
}
|
||||||
|
$stmt->bind_param('iii', $estado, $aprobada, $idOrden);
|
||||||
|
} elseif ($hasEstado) {
|
||||||
|
$stmt = $conexion->prepare("UPDATE ordenes_compra SET Estado = ? WHERE ID_Orden = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception('Error en la consulta SQL: ' . $conexion->error);
|
||||||
|
}
|
||||||
|
$stmt->bind_param('ii', $estado, $idOrden);
|
||||||
|
} else {
|
||||||
|
$stmt = $conexion->prepare("UPDATE ordenes_compra SET Aprobada = ? WHERE ID_Orden = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception('Error en la consulta SQL: ' . $conexion->error);
|
||||||
|
}
|
||||||
|
$stmt->bind_param('ii', $aprobada, $idOrden);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('No se pudo actualizar el estado: ' . $stmt->error);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
if ($estado === 3) {
|
||||||
|
$productosStmt = $conexion->prepare("SELECT ID_Producto, Cantidad FROM ordenes_productos WHERE ID_Orden = ?");
|
||||||
|
if (!$productosStmt) {
|
||||||
|
throw new Exception('No se pudieron obtener los productos de la orden.');
|
||||||
|
}
|
||||||
|
$productosStmt->bind_param('i', $idOrden);
|
||||||
|
if (!$productosStmt->execute()) {
|
||||||
|
$productosStmt->close();
|
||||||
|
throw new Exception('No se pudieron leer los productos de la orden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$productosResult = $productosStmt->get_result();
|
||||||
|
$stockStmt = $conexion->prepare("UPDATE productos SET Stock_Disponible = Stock_Disponible + ? WHERE ID_Producto = ?");
|
||||||
|
if (!$stockStmt) {
|
||||||
|
$productosStmt->close();
|
||||||
|
throw new Exception('No se pudo preparar la actualización de stock.');
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($producto = $productosResult->fetch_assoc()) {
|
||||||
|
$cantidad = intval($producto['Cantidad']);
|
||||||
|
$idProducto = intval($producto['ID_Producto']);
|
||||||
|
|
||||||
|
if ($cantidad > 0 && $idProducto > 0) {
|
||||||
|
$stockStmt->bind_param('ii', $cantidad, $idProducto);
|
||||||
|
if (!$stockStmt->execute()) {
|
||||||
|
$stockStmt->close();
|
||||||
|
$productosStmt->close();
|
||||||
|
throw new Exception('No se pudo actualizar el stock de los productos.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stockStmt->close();
|
||||||
|
$productosStmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->commit();
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Estado de la orden actualizado correctamente.']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$conexion->rollback();
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['id_usuario'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Sesión no iniciada']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No se recibieron datos']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$id = isset($input['id']) ? intval($input['id']) : 0;
|
||||||
|
$usuario = trim($input['usuario'] ?? '');
|
||||||
|
$correo = trim($input['correo'] ?? '');
|
||||||
|
$telefono = trim($input['telefono'] ?? '');
|
||||||
|
|
||||||
|
// Validar campos obligatorios (solo usuario es obligatorio)
|
||||||
|
if ($id <= 0 || empty($usuario)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Usuario es obligatorio']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que el usuario logueado sea el que intenta modificar su perfil
|
||||||
|
if ($id !== intval($_SESSION['id_usuario'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No permitido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar solo usuario, correo y teléfono
|
||||||
|
$query = "UPDATE usuarios SET Usuario = ?, Correo = ?, Telefono = ? WHERE ID_Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('sssi', $usuario, $correo, $telefono, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Perfil actualizado']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No hay conexión con la base de datos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($data['id']) || !isset($data['productos']) || !is_array($data['productos'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Faltan datos requeridos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idOrden = intval($data['id']);
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("UPDATE ordenes_productos SET Cantidad = ? WHERE ID_Orden = ? AND ID_Producto = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error en la consulta SQL.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data['productos'] as $producto) {
|
||||||
|
$idProducto = intval($producto['id']);
|
||||||
|
$cantidad = intval($producto['cantidad']);
|
||||||
|
|
||||||
|
if ($idProducto <= 0 || $cantidad <= 0) {
|
||||||
|
$stmt->close();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datos de productos inválidos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('iii', $cantidad, $idOrden, $idProducto);
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
$stmt->close();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudieron guardar los cambios.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Cantidades actualizadas correctamente.']);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] != "POST") {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||||
|
$usuario = isset($_POST['usuario']) ? trim($_POST['usuario']) : '';
|
||||||
|
$contraseña = isset($_POST['contraseña']) ? trim($_POST['contraseña']) : '';
|
||||||
|
$confirmar_contraseña = isset($_POST['confirmar_contraseña']) ? trim($_POST['confirmar_contraseña']) : '';
|
||||||
|
$rol = isset($_POST['rol']) ? trim($_POST['rol']) : '';
|
||||||
|
$nombre = isset($_POST['nombre']) ? trim($_POST['nombre']) : '';
|
||||||
|
$apellido = isset($_POST['apellido']) ? trim($_POST['apellido']) : '';
|
||||||
|
$dni = isset($_POST['dni']) ? trim($_POST['dni']) : '';
|
||||||
|
$correo = isset($_POST['correo']) ? trim($_POST['correo']) : '';
|
||||||
|
$telefono = isset($_POST['telefono']) ? trim($_POST['telefono']) : '';
|
||||||
|
|
||||||
|
if ($id <= 0 || empty($usuario) || empty($contraseña) || !in_array($rol, ['admin', 'cantina', 'cancha']) || empty($nombre) || empty($apellido) || empty($dni)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($contraseña) < 6) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La contraseña debe tener al menos 6 carácteres']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($contraseña !== $confirmar_contraseña) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Las contraseñas no coinciden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "UPDATE usuarios SET Usuario = ?, Contraseña = ?, Rol = ?, Nombre = ?, Apellido = ?, DNI = ?, Correo = ?, Telefono = ? WHERE ID_Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('ssssssssi', $usuario, $contraseña, $rol, $nombre, $apellido, $dni, $correo, $telefono, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Usuario actualizado']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$descripcion = $data['descripcion'];
|
||||||
|
$precio_venta = $data['precio_venta'];
|
||||||
|
$precio_compra = $data['precio_compra'];
|
||||||
|
$cantidad = $data['cantidad'];
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO productos (Descripcion, Precio_Venta, Precio_Compra, Stock_Disponible) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("sddi", $descripcion, $precio_venta, $precio_compra, $cantidad);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
echo "Producto agregado correctamente.";
|
||||||
|
?>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
$idProveedor = $data['id_proveedor'] ?? 0;
|
||||||
|
$idProducto = $data['id_producto'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO proveedores_productos (ID_Proveedor, ID_Producto) VALUES (?, ?)");
|
||||||
|
$stmt->bind_param("ii", $idProveedor, $idProducto);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => $stmt->error]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_GET['codigo'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["error" => "Código no especificado"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$codigo = $_GET['codigo'];
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => "Error de conexión"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("SELECT ID_Producto, Descripcion FROM productos WHERE ID_Producto = ?");
|
||||||
|
$stmt->bind_param("i", $codigo);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$producto = $result->fetch_assoc();
|
||||||
|
echo json_encode($producto ?: []);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if (!isset($_GET['codigo']) || empty($_GET['codigo'])) {
|
||||||
|
echo json_encode(null);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$busqueda = trim($_GET['codigo']);
|
||||||
|
$terminoLike = "%" . $busqueda . "%";
|
||||||
|
|
||||||
|
// Detectamos si la búsqueda es un número (ID) o texto (descripción)
|
||||||
|
$esNumero = is_numeric($busqueda);
|
||||||
|
|
||||||
|
if ($esNumero) {
|
||||||
|
// Búsqueda por ID exacto
|
||||||
|
$sql = "SELECT ID_Producto, Descripcion, Precio_Venta, Stock_Disponible
|
||||||
|
FROM productos
|
||||||
|
WHERE ID_Producto = ?
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare($sql);
|
||||||
|
$busquedaInt = intval($busqueda);
|
||||||
|
$stmt->bind_param("i", $busquedaInt);
|
||||||
|
} else {
|
||||||
|
// Búsqueda por descripción (si contiene el texto)
|
||||||
|
$sql = "SELECT ID_Producto, Descripcion, Precio_Venta, Stock_Disponible
|
||||||
|
FROM productos
|
||||||
|
WHERE Descripcion LIKE ?
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare($sql);
|
||||||
|
$stmt->bind_param("s", $terminoLike);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
$producto = $result->fetch_assoc();
|
||||||
|
|
||||||
|
echo json_encode($producto);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
$codigo = $_GET['codigo'];
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("SELECT Nombre, Telefono FROM proveedores WHERE ID_Proveedor = ?");
|
||||||
|
$stmt->bind_param("i", $codigo);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
echo json_encode($result->fetch_assoc());
|
||||||
|
?>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
$codigo = $_GET['id'];
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("SELECT ID_Proveedor, Nombre, Telefono FROM proveedores WHERE ID_Proveedor = ?");
|
||||||
|
$stmt->bind_param("i", $codigo);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
echo json_encode($result->fetch_assoc());
|
||||||
|
?>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['id_usuario'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Sesión no iniciada']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No se recibieron datos']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$id = isset($input['id']) ? intval($input['id']) : 0;
|
||||||
|
$contrasenia_actual = $input['contrasenia_actual'] ?? '';
|
||||||
|
$contrasenia_nueva = $input['contrasenia_nueva'] ?? '';
|
||||||
|
|
||||||
|
// Validar campos obligatorios
|
||||||
|
if ($id <= 0 || empty($contrasenia_actual) || empty($contrasenia_nueva)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Todos los campos son obligatorios']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que el usuario logueado sea el que intenta modificar su contraseña
|
||||||
|
if ($id !== intval($_SESSION['id_usuario'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No permitido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener la contraseña actual del usuario
|
||||||
|
$query = "SELECT Contraseña FROM usuarios WHERE ID_Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('i', $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
$usuario = $result->fetch_assoc();
|
||||||
|
|
||||||
|
if (!$usuario) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Usuario no encontrado']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que la contraseña actual sea correcta
|
||||||
|
// En este sistema, las contraseñas se guardan en texto plano
|
||||||
|
if ($contrasenia_actual === $usuario['Contraseña']) {
|
||||||
|
// La contraseña actual es correcta
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La contraseña actual es incorrecta']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar longitud mínima de la nueva contraseña
|
||||||
|
if (strlen($contrasenia_nueva) < 6) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La contraseña debe tener al menos 6 caracteres']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar la nueva contraseña en texto plano (compatible con el sistema actual)
|
||||||
|
$contrasenia_nueva_guardada = $contrasenia_nueva;
|
||||||
|
|
||||||
|
// Actualizar la contraseña
|
||||||
|
$update_query = "UPDATE usuarios SET Contraseña = ? WHERE ID_Usuario = ?";
|
||||||
|
$update_stmt = $conexion->prepare($update_query);
|
||||||
|
|
||||||
|
if (!$update_stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$update_stmt->bind_param('si', $contrasenia_nueva_guardada, $id);
|
||||||
|
|
||||||
|
if ($update_stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Contraseña actualizada exitosamente']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar: ' . $update_stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$update_stmt->close();
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['rol'])) {
|
||||||
|
header("Location: ../html/Login.html");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
$rol = $_SESSION['rol']; // admin, cancha, cantina
|
||||||
|
$usuario = $_SESSION['usuario'] ?? '';
|
||||||
|
$usuarioId = $_SESSION['id_usuario'] ?? null;
|
||||||
|
include __DIR__ . '/../html/Cantina.html';
|
||||||
|
?>
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['columna_actual']) || !isset($data['columna_nueva']) || !isset($data['monto'])) {
|
||||||
|
throw new Exception("Datos incompletos");
|
||||||
|
}
|
||||||
|
|
||||||
|
$columna_actual = trim($data['columna_actual']);
|
||||||
|
$columna_nueva = trim($data['columna_nueva']);
|
||||||
|
$monto = floatval($data['monto']);
|
||||||
|
$fecha_hoy = date('Y-m-d'); // Necesitamos la fecha de hoy para los periodos
|
||||||
|
|
||||||
|
// Validar datos
|
||||||
|
if (empty($columna_actual) || empty($columna_nueva) || $monto < 0) {
|
||||||
|
throw new Exception("Datos inválidos");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir nuevo nombre a formato de columna
|
||||||
|
$nombre_columna = strtolower(str_replace(' ', '_', $columna_nueva));
|
||||||
|
|
||||||
|
// Validar que sea un nombre válido para columna SQL
|
||||||
|
if (!preg_match('/^[a-z_][a-z0-9_]*$/', $nombre_columna)) {
|
||||||
|
throw new Exception("Nombre de gasto inválido");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el nombre cambió, renombrar la columna primero
|
||||||
|
if (strtolower(str_replace(' ', '_', $columna_actual)) !== $nombre_columna) {
|
||||||
|
$columna_actual_formatted = strtolower(str_replace(' ', '_', $columna_actual));
|
||||||
|
|
||||||
|
// Verificar que la columna actual existe
|
||||||
|
$sql_check = "SHOW COLUMNS FROM gastos LIKE '" . $conexion->real_escape_string($columna_actual_formatted) . "'";
|
||||||
|
$result_check = $conexion->query($sql_check);
|
||||||
|
|
||||||
|
if ($result_check === false || $result_check->num_rows === 0) {
|
||||||
|
throw new Exception("La columna de gasto no existe: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renombrar columna (Nota: esto cambia el nombre también en los registros históricos)
|
||||||
|
$sql_rename = "ALTER TABLE gastos CHANGE COLUMN `$columna_actual_formatted` `$nombre_columna` DECIMAL(10,2)";
|
||||||
|
|
||||||
|
if (!$conexion->query($sql_rename)) {
|
||||||
|
throw new Exception("Error al renombrar columna: " . $conexion->error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$nombre_columna = strtolower(str_replace(' ', '_', $columna_actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener el registro actual COMPLETO
|
||||||
|
$sql_get_actual = "SELECT * FROM gastos WHERE Periodo_Hasta IS NULL ORDER BY Periodo_Desde DESC LIMIT 1";
|
||||||
|
$result_actual = $conexion->query($sql_get_actual);
|
||||||
|
|
||||||
|
if ($result_actual && $result_actual->num_rows > 0) {
|
||||||
|
$valores_anteriores = $result_actual->fetch_assoc();
|
||||||
|
$id_gasto_actual = $valores_anteriores['ID_Gasto'];
|
||||||
|
|
||||||
|
// 1. Cerrar el periodo actual poniendo la fecha de hoy
|
||||||
|
$sql_update = "UPDATE gastos SET Periodo_Hasta = ? WHERE ID_Gasto = ?";
|
||||||
|
$stmt_update = $conexion->prepare($sql_update);
|
||||||
|
$stmt_update->bind_param('si', $fecha_hoy, $id_gasto_actual);
|
||||||
|
|
||||||
|
if (!$stmt_update->execute()) {
|
||||||
|
throw new Exception("Error cerrando periodo anterior: " . $stmt_update->error);
|
||||||
|
}
|
||||||
|
$stmt_update->close();
|
||||||
|
|
||||||
|
// 2. Preparar el nuevo registro (INSERT)
|
||||||
|
$columnas = ['Periodo_Desde', 'Periodo_Hasta'];
|
||||||
|
$valores = [$fecha_hoy, null];
|
||||||
|
$placeholders = ['?', '?'];
|
||||||
|
$tipos = 'ss';
|
||||||
|
|
||||||
|
// Recorrer los valores del registro que acabamos de cerrar
|
||||||
|
foreach ($valores_anteriores as $col => $val) {
|
||||||
|
// Ignorar las columnas de control
|
||||||
|
if (in_array($col, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnas[] = "`$col`"; // Usamos backticks por seguridad en los nombres
|
||||||
|
|
||||||
|
// Si es la columna que estamos editando, guardamos el NUEVO monto. Si no, arrastramos el viejo.
|
||||||
|
if ($col === $nombre_columna) {
|
||||||
|
$valores[] = $monto;
|
||||||
|
} else {
|
||||||
|
$valores[] = floatval($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders[] = '?';
|
||||||
|
$tipos .= 'd';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Insertar la nueva "versión" de los gastos
|
||||||
|
$sql_insert = "INSERT INTO gastos (" . implode(', ', $columnas) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||||
|
$stmt_insert = $conexion->prepare($sql_insert);
|
||||||
|
|
||||||
|
if (!$stmt_insert) {
|
||||||
|
throw new Exception("Error preparando INSERT: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_insert->bind_param($tipos, ...$valores);
|
||||||
|
|
||||||
|
if (!$stmt_insert->execute()) {
|
||||||
|
throw new Exception("Error insertando el nuevo periodo: " . $stmt_insert->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_insert->close();
|
||||||
|
} else {
|
||||||
|
throw new Exception("No hay un periodo de gastos activo para editar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Gasto editado y nuevo periodo registrado correctamente'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
|
||||||
|
if (!$data || !isset($data["id"])) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Datos inválidos"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$id = $conexion->real_escape_string($data["id"]);
|
||||||
|
$descripcion = $conexion->real_escape_string($data["descripcion"]);
|
||||||
|
$precio_venta = floatval($data["precio_venta"]);
|
||||||
|
$precio_compra = floatval($data["precio_compra"]);
|
||||||
|
$cantidad = intval($data["cantidad"]);
|
||||||
|
|
||||||
|
$query = "UPDATE productos SET Descripcion=?, Precio_Venta=?, Precio_Compra=?, Stock_Disponible=? WHERE ID_Producto=?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
$stmt->bind_param("sddii", $descripcion, $precio_venta, $precio_compra, $cantidad, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(["success" => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(["success" => false, "error" => $conexion->error]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set("display_errors", 1);
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"error" => "Error de conexión: " . $conexion->connect_error
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = file_get_contents("php://input");
|
||||||
|
// file_put_contents("log_debug.txt", "RAW:\n" . $raw . "\n", FILE_APPEND); <-- línea comentada / eliminada
|
||||||
|
|
||||||
|
$data = json_decode($raw, true);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"error" => "JSON inválido",
|
||||||
|
"raw" => $raw
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($data['id']) ? (int)$data['id'] : null;
|
||||||
|
$nombre = $data['nombre'] ?? null;
|
||||||
|
$telefono = $data['telefono'] ?? null;
|
||||||
|
|
||||||
|
if (!$id || !$nombre || !$telefono) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Datos incompletos"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("UPDATE proveedores SET Nombre = ?, Telefono = ? WHERE ID_Proveedor = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(["success" => false, "error" => "Error en prepare(): " . $conexion->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ssi", $nombre, $telefono, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(["success" => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(["success" => false, "error" => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['columna'])) {
|
||||||
|
throw new Exception("Columna no especificada");
|
||||||
|
}
|
||||||
|
|
||||||
|
$columna = trim($data['columna']);
|
||||||
|
|
||||||
|
// Validar que sea un nombre válido
|
||||||
|
if (empty($columna)) {
|
||||||
|
throw new Exception("Columna inválida");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir nombre a formato de columna
|
||||||
|
$nombre_columna = strtolower(str_replace(' ', '_', $columna));
|
||||||
|
|
||||||
|
// Verificar que la columna existe
|
||||||
|
$sql_check = "SHOW COLUMNS FROM gastos LIKE '" . $conexion->real_escape_string($nombre_columna) . "'";
|
||||||
|
$result_check = $conexion->query($sql_check);
|
||||||
|
|
||||||
|
if ($result_check === false || $result_check->num_rows === 0) {
|
||||||
|
throw new Exception("La columna de gasto no existe:");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminar la columna
|
||||||
|
$sql_drop = "ALTER TABLE gastos DROP COLUMN `$nombre_columna`";
|
||||||
|
|
||||||
|
if (!$conexion->query($sql_drop)) {
|
||||||
|
throw new Exception("Error al eliminar columna: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Gasto eliminado correctamente'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID de producto no especificado.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_GET['id']);
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Verificar si la columna Estado existe
|
||||||
|
$checkColumn = $conexion->query("SHOW COLUMNS FROM productos WHERE Field='Estado'");
|
||||||
|
$columnaExists = $checkColumn && $checkColumn->num_rows > 0;
|
||||||
|
|
||||||
|
if ($columnaExists) {
|
||||||
|
// Soft delete: marcar el producto como inactivo
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=0");
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("UPDATE productos SET Estado = 0 WHERE ID_Producto = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=1");
|
||||||
|
|
||||||
|
if ($stmt->affected_rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Producto eliminado correctamente.']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar el producto o no existe.']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: verificar si la columna Activo existe
|
||||||
|
$checkColumnActivo = $conexion->query("SHOW COLUMNS FROM productos WHERE Field='Activo'");
|
||||||
|
$activoExists = $checkColumnActivo && $checkColumnActivo->num_rows > 0;
|
||||||
|
|
||||||
|
if ($activoExists) {
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=0");
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("UPDATE productos SET Activo = 0 WHERE ID_Producto = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=1");
|
||||||
|
|
||||||
|
if ($stmt->affected_rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Producto eliminado correctamente.']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar el producto o no existe.']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=0");
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM productos WHERE ID_Producto = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$conexion->query("SET FOREIGN_KEY_CHECKS=1");
|
||||||
|
|
||||||
|
if ($stmt->affected_rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Producto eliminado correctamente.']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar el producto o no existe.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$idProveedor = $_GET['id_proveedor'] ?? 0;
|
||||||
|
$idProducto = $_GET['id_producto'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM proveedores_productos WHERE ID_Proveedor = ? AND ID_Producto = ?");
|
||||||
|
$stmt->bind_param("ii", $idProveedor, $idProducto);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => $stmt->error]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(0);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
$id = 0;
|
||||||
|
|
||||||
|
if ($rawInput) {
|
||||||
|
$data = json_decode($rawInput, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && isset($data['id'])) {
|
||||||
|
$id = intval($data['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$id && isset($_POST['id'])) {
|
||||||
|
$id = intval($_POST['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$id && isset($_GET['id'])) {
|
||||||
|
$id = intval($_GET['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error de conexión']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID no proporcionado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM ordenes_productos WHERE ID_Orden IN (SELECT ID_Orden FROM ordenes_compra WHERE ID_Proveedor = ?)");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM ordenes_compra WHERE ID_Proveedor = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM proveedores_productos WHERE ID_Proveedor = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("DELETE FROM proveedores WHERE ID_Proveedor = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->affected_rows === 0) {
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->rollback();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Proveedor no encontrado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->commit();
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Proveedor eliminado']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$conexion->rollback();
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error al eliminar proveedor: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Respuesta por defecto
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] != "POST") {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener id del usuario a eliminar
|
||||||
|
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID inválido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevenir la eliminación del usuario actual (si es necesario)
|
||||||
|
if (isset($_SESSION['id_usuario']) && $_SESSION['id_usuario'] == $id) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No puedes eliminar tu propia cuenta']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener el ID del primer usuario registrado para protegerlo
|
||||||
|
$primerUsuarioQuery = "SELECT MIN(ID_Usuario) AS primer_id FROM usuarios";
|
||||||
|
$primerUsuarioStmt = $conexion->prepare($primerUsuarioQuery);
|
||||||
|
$primerUsuarioStmt->execute();
|
||||||
|
$primerUsuarioResult = $primerUsuarioStmt->get_result();
|
||||||
|
$primerUsuarioId = (int) $primerUsuarioResult->fetch_assoc()['primer_id'];
|
||||||
|
$primerUsuarioStmt->close();
|
||||||
|
|
||||||
|
if ($primerUsuarioId > 0 && $id === $primerUsuarioId) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'El administrador supremo no puede ser eliminado']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminar el usuario
|
||||||
|
$query = "DELETE FROM usuarios WHERE ID_Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
if ($stmt->affected_rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Usuario eliminado exitosamente']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'El usuario no existe']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar el usuario']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
set_time_limit(120);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['telefono']) || empty($data['telefono'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Falta el teléfono']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Limpiamos el número y le agregamos el 549 de Argentina si no lo tiene
|
||||||
|
$telefono = preg_replace('/[^0-9]/', '', $data['telefono']);
|
||||||
|
if (substr($telefono, 0, 3) !== '549') {
|
||||||
|
$telefono = '549' . ltrim($telefono, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nombre = $data['nombre'];
|
||||||
|
|
||||||
|
// 2. Generamos el código
|
||||||
|
$codigoGenerado = (string) rand(1000, 9999);
|
||||||
|
$mensaje = "Hola $nombre, tu código de verificación para Complejo Cap1tan es: *$codigoGenerado*";
|
||||||
|
|
||||||
|
// 3. Ejecutamos Python en SEGUNDO PLANO (Asíncrono para Windows)
|
||||||
|
$ruta_script = "C:\\xampp\\htdocs\\TP-Taller\\ProyectoTaller\\python\\Enviar_WSP.py";
|
||||||
|
|
||||||
|
// Armamos el comando base
|
||||||
|
$comando_python = escapeshellcmd("python \"$ruta_script\" \"$telefono\" \"$mensaje\"");
|
||||||
|
|
||||||
|
// "start /B" inicia el proceso sin abrir una nueva ventana de CMD.
|
||||||
|
// "1> NUL 2>&1" tira a la basura los mensajes de consola de Python para que PHP no se quede esperando a leerlos.
|
||||||
|
// popen() y pclose() abren el proceso y lo cierran al instante en PHP, dejando a Python trabajando solo.
|
||||||
|
pclose(popen("start /B " . $comando_python . " 1> NUL 2>&1", "r"));
|
||||||
|
|
||||||
|
// 4. Devolvemos la respuesta INMEDIATAMENTE al JavaScript
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'codigo' => $codigoGenerado,
|
||||||
|
'debug_python' => 'Ejecutando en segundo plano... (No se captura salida)'
|
||||||
|
]);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. OBTENER LAS COLUMNAS DINÁMICAMENTE
|
||||||
|
// Le preguntamos a la base de datos qué gastos existen actualmente
|
||||||
|
$sql_columnas = "SHOW COLUMNS FROM gastos";
|
||||||
|
$result_columnas = $conexion->query($sql_columnas);
|
||||||
|
|
||||||
|
if (!$result_columnas) {
|
||||||
|
throw new Exception("Error al obtener las columnas de gastos: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnas_suma = [];
|
||||||
|
while ($row = $result_columnas->fetch_assoc()) {
|
||||||
|
$col = $row['Field'];
|
||||||
|
// Ignoramos las columnas de control de historial
|
||||||
|
if (!in_array($col, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta'])) {
|
||||||
|
// Usamos COALESCE por si algún valor es NULL, para que la suma matemática no falle
|
||||||
|
$columnas_suma[] = "COALESCE(g.`$col`, 0)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Armamos el fragmento de la suma: "COALESCE(g.sueldo,0) + COALESCE(g.luz,0)..."
|
||||||
|
$suma_gastos_sql = empty($columnas_suma) ? "0" : implode(' + ', $columnas_suma);
|
||||||
|
|
||||||
|
// 2. CONSTRUIR LA CONSULTA PRINCIPAL
|
||||||
|
// Usamos LAST_DAY() para asegurar que tomamos la versión de gastos de fin de mes
|
||||||
|
// y evitamos duplicar los ingresos si hubo cambios a mitad de mes.
|
||||||
|
$sql_reporte = "
|
||||||
|
SELECT
|
||||||
|
ingresos.Mes,
|
||||||
|
COALESCE(ingresos.Ingreso_Bruto, 0) AS Ingreso_Bruto,
|
||||||
|
COALESCE(ingresos.Costo, 0) AS Costo,
|
||||||
|
COALESCE(ingresos.Ganancia_Bruta, 0) AS Ganancia_Bruta,
|
||||||
|
($suma_gastos_sql) AS Gastos_Totales,
|
||||||
|
COALESCE(ingresos.Ganancia_Bruta, 0) - ($suma_gastos_sql) AS Ganancia_Neta
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(v.Fecha, '%Y-%m') AS Mes,
|
||||||
|
LAST_DAY(MAX(v.Fecha)) AS Fin_De_Mes,
|
||||||
|
SUM(p.Precio_Venta * vp.Cantidad) AS Ingreso_Bruto,
|
||||||
|
SUM(p.Precio_Compra * vp.Cantidad) AS Costo,
|
||||||
|
SUM((p.Precio_Venta - p.Precio_Compra) * vp.Cantidad) AS Ganancia_Bruta
|
||||||
|
FROM ventas v
|
||||||
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
GROUP BY Mes
|
||||||
|
) ingresos
|
||||||
|
LEFT JOIN gastos g
|
||||||
|
ON g.Periodo_Desde <= ingresos.Fin_De_Mes
|
||||||
|
AND (g.Periodo_Hasta IS NULL OR g.Periodo_Hasta > ingresos.Fin_De_Mes)
|
||||||
|
ORDER BY ingresos.Mes DESC;
|
||||||
|
";
|
||||||
|
|
||||||
|
// 3. EJECUTAR LA CONSULTA
|
||||||
|
$result_reporte = $conexion->query($sql_reporte);
|
||||||
|
|
||||||
|
if (!$result_reporte) {
|
||||||
|
throw new Exception("Error al generar el reporte: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$reporte = [];
|
||||||
|
while ($row = $result_reporte->fetch_assoc()) {
|
||||||
|
// Convertimos los valores numéricos de strings a floats para el JSON
|
||||||
|
$reporte[] = [
|
||||||
|
'Mes' => $row['Mes'],
|
||||||
|
'Ingreso_Bruto' => floatval($row['Ingreso_Bruto']),
|
||||||
|
'Costo' => floatval($row['Costo']),
|
||||||
|
'Ganancia_Bruta' => floatval($row['Ganancia_Bruta']),
|
||||||
|
'Gastos_Totales' => floatval($row['Gastos_Totales']),
|
||||||
|
'Ganancia_Neta' => floatval($row['Ganancia_Neta'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $reporte
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error de conexión']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
$nombre = $data['nombre'] ?? '';
|
||||||
|
$telefono = $data['telefono'] ?? '';
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO proveedores (Nombre, Telefono) VALUES (?, ?)");
|
||||||
|
$stmt->bind_param("ss", $nombre, $telefono);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => $conexion->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
|
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Obtener datos del POST
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Datos no recibidos'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nombre = isset($data['nombre']) ? trim($data['nombre']) : '';
|
||||||
|
$telefono = isset($data['telefono']) ? trim($data['telefono']) : '';
|
||||||
|
$dni = isset($data['dni']) ? trim($data['dni']) : '';
|
||||||
|
$fecha = isset($data['fecha']) ? $data['fecha'] : '';
|
||||||
|
$horario = isset($data['horario']) ? $data['horario'] : '';
|
||||||
|
$tipo = isset($data['tipo']) ? $data['tipo'] : '';
|
||||||
|
$numeroCancha = isset($data['numeroCancha']) ? intval($data['numeroCancha']) : 0;
|
||||||
|
$monto = isset($data['monto']) ? floatval($data['monto']) : 0;
|
||||||
|
$incluirQuincho = isset($data['incluirQuincho']) ? $data['incluirQuincho'] : false;
|
||||||
|
$montoQuincho = isset($data['montoQuincho']) ? floatval($data['montoQuincho']) : 0;
|
||||||
|
$idCancha = isset($data['idCancha']) ? intval($data['idCancha']) : null;
|
||||||
|
|
||||||
|
// Validar datos requeridos
|
||||||
|
if (empty($nombre) || empty($telefono) || empty($dni) || empty($fecha) || empty($horario) || empty($tipo) || $numeroCancha === 0 || $monto === 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Datos incompletos'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Escapar datos para evitar SQL injection
|
||||||
|
$nombre = mysqli_real_escape_string($conexion, $nombre);
|
||||||
|
$telefono = mysqli_real_escape_string($conexion, $telefono);
|
||||||
|
$dni = mysqli_real_escape_string($conexion, $dni);
|
||||||
|
$fecha = mysqli_real_escape_string($conexion, $fecha);
|
||||||
|
$horario = mysqli_real_escape_string($conexion, $horario);
|
||||||
|
$tipo = mysqli_real_escape_string($conexion, $tipo);
|
||||||
|
|
||||||
|
// Buscar o crear cliente
|
||||||
|
$sqlBuscarCliente = "SELECT ID_Cliente FROM clientes WHERE DNI = '$dni'";
|
||||||
|
$resBuscar = mysqli_query($conexion, $sqlBuscarCliente);
|
||||||
|
|
||||||
|
if (!$resBuscar) {
|
||||||
|
throw new Exception("Error al buscar cliente: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cliente = mysqli_fetch_assoc($resBuscar);
|
||||||
|
|
||||||
|
if ($cliente) {
|
||||||
|
// Cliente existe, actualizar datos
|
||||||
|
$idCliente = $cliente['ID_Cliente'];
|
||||||
|
$sqlActualizarCliente = "UPDATE clientes SET Nombre = '$nombre', Telefono = '$telefono' WHERE ID_Cliente = $idCliente";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sqlActualizarCliente)) {
|
||||||
|
throw new Exception("Error al actualizar cliente: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Crear nuevo cliente
|
||||||
|
$sqlCrearCliente = "INSERT INTO clientes (Nombre, Telefono, DNI) VALUES ('$nombre', '$telefono', '$dni')";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sqlCrearCliente)) {
|
||||||
|
throw new Exception("Error al crear cliente: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$idCliente = mysqli_insert_id($conexion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener los datos de la cancha usada para calcular el cupón
|
||||||
|
$sqlObtenerCancha = "SELECT ID_Cancha, Cant_Reservas_Cupon, Descuento_Cupon, Cant_Faltas, Duracion_Cupon FROM canchas WHERE Tipo = '$tipo' LIMIT 1";
|
||||||
|
$resCancha = mysqli_query($conexion, $sqlObtenerCancha);
|
||||||
|
|
||||||
|
if (!$resCancha) {
|
||||||
|
throw new Exception("Error al obtener cancha: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancha = mysqli_fetch_assoc($resCancha);
|
||||||
|
if (!$cancha) {
|
||||||
|
throw new Exception("No existe cancha del tipo especificado");
|
||||||
|
}
|
||||||
|
|
||||||
|
$idCancha = $cancha['ID_Cancha'];
|
||||||
|
$umbral = intval($cancha['Cant_Reservas_Cupon']);
|
||||||
|
$descuentoCupon = floatval($cancha['Descuento_Cupon']);
|
||||||
|
$maxFaltas = intval($cancha['Cant_Faltas']);
|
||||||
|
$duracionCupon = intval($cancha['Duracion_Cupon']);
|
||||||
|
|
||||||
|
// Buscar fila de cupón existente para el cliente y tipo de cancha
|
||||||
|
$sqlBuscarCupon = "SELECT ID_Cupon, Cantidad_Reservas, Cantidad_Faltas, Ultima_Fecha, Descuento FROM cupones WHERE ID_Cliente = $idCliente AND Tipo = '$tipo' LIMIT 1";
|
||||||
|
$resCupon = mysqli_query($conexion, $sqlBuscarCupon);
|
||||||
|
|
||||||
|
if (!$resCupon) {
|
||||||
|
throw new Exception("Error al buscar cupón: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cupon = mysqli_fetch_assoc($resCupon);
|
||||||
|
|
||||||
|
if (!$cupon) {
|
||||||
|
$sqlCrearCupon = "INSERT INTO cupones (Cantidad_Reservas, Cantidad_Faltas, Tipo, Ultima_Fecha, Descuento, ID_Cliente) VALUES (0, 0, '$tipo', NULL, 0, $idCliente)";
|
||||||
|
if (!mysqli_query($conexion, $sqlCrearCupon)) {
|
||||||
|
throw new Exception("Error al crear cupón: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cupon = [
|
||||||
|
'ID_Cupon' => mysqli_insert_id($conexion),
|
||||||
|
'Cantidad_Reservas' => 0,
|
||||||
|
'Cantidad_Faltas' => 0,
|
||||||
|
'Ultima_Fecha' => null,
|
||||||
|
'Descuento' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cantidadReservasCupon = intval($cupon['Cantidad_Reservas']);
|
||||||
|
$cantidadFaltasCupon = intval($cupon['Cantidad_Faltas']);
|
||||||
|
$ultimaFechaCupon = $cupon['Ultima_Fecha'];
|
||||||
|
|
||||||
|
// Caducidad del cupón
|
||||||
|
if ($duracionCupon > 0 && !empty($ultimaFechaCupon)) {
|
||||||
|
$fechaUltima = new DateTime($ultimaFechaCupon);
|
||||||
|
$fechaUltima->modify("+{$duracionCupon} days");
|
||||||
|
$fechaActual = new DateTime();
|
||||||
|
|
||||||
|
if ($fechaActual > $fechaUltima) {
|
||||||
|
$cantidadReservasCupon = 0;
|
||||||
|
$cantidadFaltasCupon = 0;
|
||||||
|
$ultimaFechaCupon = null;
|
||||||
|
$sqlResetCupon = "UPDATE cupones SET Cantidad_Reservas = 0, Cantidad_Faltas = 0, Ultima_Fecha = NULL, Descuento = 0 WHERE ID_Cupon = {$cupon['ID_Cupon']}";
|
||||||
|
if (!mysqli_query($conexion, $sqlResetCupon)) {
|
||||||
|
throw new Exception("Error al resetear cupón vencido: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la cantidad de faltas supera el máximo permitido, reiniciamos el conteo de reservas
|
||||||
|
if ($maxFaltas > 0 && $cantidadFaltasCupon > $maxFaltas) {
|
||||||
|
$cantidadReservasCupon = 0;
|
||||||
|
$cantidadFaltasCupon = 0;
|
||||||
|
$ultimaFechaCupon = null;
|
||||||
|
$sqlResetCupon = "UPDATE cupones SET Cantidad_Reservas = 0, Cantidad_Faltas = 0, Ultima_Fecha = NULL, Descuento = 0 WHERE ID_Cupon = {$cupon['ID_Cupon']}";
|
||||||
|
if (!mysqli_query($conexion, $sqlResetCupon)) {
|
||||||
|
throw new Exception("Error al resetear cupón por faltas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir horario a formato DATETIME
|
||||||
|
$fechaHora = $fecha . ' ' . $horario . ':00';
|
||||||
|
|
||||||
|
$descuentoAplicado = 0;
|
||||||
|
$montoFinal = $monto;
|
||||||
|
$valorCupon = $descuentoCupon;
|
||||||
|
|
||||||
|
if ($umbral > 0 && ($cantidadReservasCupon + 1) >= $umbral) {
|
||||||
|
$descuentoAplicado = $valorCupon;
|
||||||
|
$montoFinal = round($monto * (1 - $descuentoAplicado), 2);
|
||||||
|
$cantidadReservasCupon = 0;
|
||||||
|
$cantidadFaltasCupon = 0;
|
||||||
|
$ultimaFechaCupon = date('Y-m-d H:i:s');
|
||||||
|
} else {
|
||||||
|
$cantidadReservasCupon += 1;
|
||||||
|
$ultimaFechaCupon = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlActualizarCupon = "UPDATE cupones SET Cantidad_Reservas = $cantidadReservasCupon, Cantidad_Faltas = $cantidadFaltasCupon, Ultima_Fecha = " . ($ultimaFechaCupon ? "'$ultimaFechaCupon'" : "NULL") . ", Descuento = $descuentoAplicado WHERE ID_Cupon = {$cupon['ID_Cupon']}";
|
||||||
|
if (!mysqli_query($conexion, $sqlActualizarCupon)) {
|
||||||
|
throw new Exception("Error al actualizar cupón: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener ID del usuario desde la sesión
|
||||||
|
$id_usuario = isset($_SESSION['id_usuario']) ? intval($_SESSION['id_usuario']) : null;
|
||||||
|
|
||||||
|
// Insertar reserva de cancha
|
||||||
|
$sqlInsertarReserva = "INSERT INTO reservas (Fecha_Hora, Monto, Numero, Estado, ID_Cliente, ID_Cancha, Descuento, ID_Usuario)
|
||||||
|
VALUES ('$fechaHora', $montoFinal, $numeroCancha, 0, $idCliente, $idCancha, $descuentoAplicado, $id_usuario)";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sqlInsertarReserva)) {
|
||||||
|
throw new Exception("Error al insertar reserva: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$idReserva = mysqli_insert_id($conexion);
|
||||||
|
|
||||||
|
// Si incluye quincho, insertar reserva de quincho
|
||||||
|
if ($incluirQuincho && $montoQuincho > 0) {
|
||||||
|
// Obtener ID_Cancha para quincho
|
||||||
|
$sqlObtenerQuincho = "SELECT ID_Cancha FROM canchas WHERE Tipo = 'Q'";
|
||||||
|
$resQuincho = mysqli_query($conexion, $sqlObtenerQuincho);
|
||||||
|
|
||||||
|
if (!$resQuincho) {
|
||||||
|
throw new Exception("Error al obtener quincho: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$quincho = mysqli_fetch_assoc($resQuincho);
|
||||||
|
if ($quincho) {
|
||||||
|
$idQuincho = $quincho['ID_Cancha'];
|
||||||
|
|
||||||
|
// El quincho se reserva por el día completo, entonces el número es 1 (hay un solo quincho)
|
||||||
|
$sqlInsertarQuincho = "INSERT INTO reservas (Fecha_Hora, Monto, Numero, Estado, ID_Cliente, ID_Cancha, Descuento, ID_Usuario)
|
||||||
|
VALUES ('$fechaHora', $montoQuincho, 1, 0, $idCliente, $idQuincho, 0, $id_usuario)";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sqlInsertarQuincho)) {
|
||||||
|
throw new Exception("Error al insertar reserva de quincho: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Reserva confirmada exitosamente',
|
||||||
|
'id_reserva' => $idReserva,
|
||||||
|
'id_cliente' => $idCliente
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Respuesta por defecto
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] != "POST") {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error de conexión a la base de datos: ' . $conexion->connect_error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener datos del formulario
|
||||||
|
$usuario = isset($_POST['usuario']) ? trim($_POST['usuario']) : '';
|
||||||
|
$contraseña = isset($_POST['contraseña']) ? trim($_POST['contraseña']) : '';
|
||||||
|
$confirmar_contraseña = isset($_POST['confirmar_contraseña']) ? trim($_POST['confirmar_contraseña']) : '';
|
||||||
|
$rol = isset($_POST['rol']) ? trim($_POST['rol']) : '';
|
||||||
|
$nombre = isset($_POST['nombre']) ? trim($_POST['nombre']) : '';
|
||||||
|
$apellido = isset($_POST['apellido']) ? trim($_POST['apellido']) : '';
|
||||||
|
$dni = isset($_POST['dni']) ? trim($_POST['dni']) : '';
|
||||||
|
$correo = isset($_POST['correo']) ? trim($_POST['correo']) : '';
|
||||||
|
$telefono = isset($_POST['telefono']) ? trim($_POST['telefono']) : '';
|
||||||
|
|
||||||
|
// El primer usuario registrado será el administrador supremo del sistema
|
||||||
|
$contadorQuery = "SELECT COUNT(*) AS total FROM usuarios";
|
||||||
|
$contadorStmt = $conexion->prepare($contadorQuery);
|
||||||
|
$contadorStmt->execute();
|
||||||
|
$contadorResult = $contadorStmt->get_result();
|
||||||
|
$primerUsuario = $contadorResult->fetch_assoc()['total'] == 0;
|
||||||
|
$contadorStmt->close();
|
||||||
|
|
||||||
|
if ($primerUsuario) {
|
||||||
|
$rol = 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log para debugging
|
||||||
|
error_log("Datos recibidos - Usuario: $usuario, Rol: $rol, Nombre: $nombre");
|
||||||
|
|
||||||
|
// Validar que los campos requeridos estén presentes
|
||||||
|
if (empty($usuario) || empty($contraseña) || empty($confirmar_contraseña) || empty($rol) || empty($nombre) || empty($apellido) || empty($dni)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Todos los campos requeridos deben estar completos']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($contraseña) < 6) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La contraseña debe tener al menos 6 carácteres']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($contraseña !== $confirmar_contraseña) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Las contraseñas no coinciden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que el rol sea válido
|
||||||
|
if (!in_array($rol, ['admin', 'cantina', 'cancha'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Rol inválido']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si el usuario ya existe
|
||||||
|
$query = "SELECT ID_Usuario FROM usuarios WHERE Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en la consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("s", $usuario);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'El usuario ya existe']);
|
||||||
|
$stmt->close();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Insertar el nuevo usuario
|
||||||
|
$query = "INSERT INTO usuarios (Usuario, Contraseña, Rol, Nombre, Apellido, DNI, Correo, Telefono)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error en la consulta: ' . $conexion->error]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ssssssss", $usuario, $contraseña, $rol, $nombre, $apellido, $dni, $correo, $telefono);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Usuario registrado exitosamente', 'id' => $stmt->insert_id]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al registrar el usuario: ' . $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
die("Error de conexión: " . $conexion->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$usuario = $_POST['usuario'];
|
||||||
|
$clave = $_POST['clave'];
|
||||||
|
|
||||||
|
$query = "SELECT * FROM usuarios WHERE Usuario=? AND Contraseña=?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
$stmt->bind_param("ss", $usuario, $clave);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 1) {
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$_SESSION['id_usuario'] = $row['ID_Usuario'] ?? null;
|
||||||
|
$_SESSION['usuario'] = $row['Usuario'];
|
||||||
|
$_SESSION['rol'] = $row['Rol']; // admin, cancha o cantina
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Login exitoso'
|
||||||
|
]);
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Usuario o contraseña incorrectos'
|
||||||
|
]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
if (ini_get('session.use_cookies')) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
setcookie(session_name(), '', time() - 42000,
|
||||||
|
$params['path'], $params['domain'],
|
||||||
|
$params['secure'], $params['httponly']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verificar y agregar las columnas necesarias a la tabla gastos
|
||||||
|
$columnas_necesarias = [
|
||||||
|
'Sueldo' => 'DECIMAL(10,2) DEFAULT 0',
|
||||||
|
'Luz' => 'DECIMAL(10,2) DEFAULT 0',
|
||||||
|
'Agua' => 'DECIMAL(10,2) DEFAULT 0',
|
||||||
|
'Impuestos' => 'DECIMAL(10,2) DEFAULT 0'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($columnas_necesarias as $nombre_columna => $tipo_columna) {
|
||||||
|
// Verificar si la columna existe
|
||||||
|
$result = $conexion->query("SHOW COLUMNS FROM gastos LIKE '$nombre_columna'");
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
// Si no existe, agregarla
|
||||||
|
$sql = "ALTER TABLE gastos ADD COLUMN `$nombre_columna` $tipo_columna";
|
||||||
|
if ($conexion->query($sql)) {
|
||||||
|
echo "✓ Columna '$nombre_columna' agregada correctamente\n";
|
||||||
|
} else {
|
||||||
|
throw new Exception("Error al agregar columna '$nombre_columna': " . $conexion->error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "✓ Columna '$nombre_columna' ya existe\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que haya al menos un registro de gastos
|
||||||
|
$result = $conexion->query("SELECT COUNT(*) as count FROM gastos");
|
||||||
|
$count = $result->fetch_assoc()['count'];
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
// Insertar un registro de gasto por defecto si no existe
|
||||||
|
$sql = "INSERT INTO gastos (Periodo_Desde, Periodo_Hasta, Sueldo, Luz, Agua, Impuestos)
|
||||||
|
VALUES (CURDATE(), NULL, 0, 0, 0, 0)";
|
||||||
|
if ($conexion->query($sql)) {
|
||||||
|
echo "✓ Registro de gasto inicial creado\n";
|
||||||
|
} else {
|
||||||
|
throw new Exception("Error al crear registro inicial: " . $conexion->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n✓ Migración de gastos completada exitosamente\n";
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Migración completada']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Verificar si la columna ya existe
|
||||||
|
$result = $conexion->query("SHOW COLUMNS FROM ventas_productos LIKE 'Precio_Unitario'");
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
// Si no existe, agregarla
|
||||||
|
$sql = "ALTER TABLE ventas_productos ADD COLUMN Precio_Unitario DECIMAL(10, 2) DEFAULT 0.00";
|
||||||
|
|
||||||
|
if ($conexion->query($sql) === TRUE) {
|
||||||
|
// Actualizar con los precios de los productos existentes
|
||||||
|
$update_sql = "
|
||||||
|
UPDATE ventas_productos vp
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
SET vp.Precio_Unitario = p.Precio
|
||||||
|
WHERE vp.Precio_Unitario = 0
|
||||||
|
";
|
||||||
|
|
||||||
|
if ($conexion->query($update_sql) === TRUE) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Columna Precio_Unitario agregada y actualizada correctamente'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al actualizar precios: ' . $conexion->error
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al agregar columna: ' . $conexion->error
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'La columna Precio_Unitario ya existe'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT c.ID_Cancha, c.Tipo, c.Precio, c.Duracion, c.Cant_Canchas,
|
||||||
|
c.Cant_Reservas_Cupon, c.Descuento_Cupon, c.Cant_Faltas, c.Duracion_Cupon,
|
||||||
|
h.Hora_Apertura, h.Hora_Cierre
|
||||||
|
FROM canchas c
|
||||||
|
LEFT JOIN horarios h ON h.Dia = 'Lunes a Viernes'
|
||||||
|
WHERE c.Tipo IN ('F', 'P', 'Q')";
|
||||||
|
|
||||||
|
$res = mysqli_query($conexion, $sql);
|
||||||
|
|
||||||
|
if (!$res) {
|
||||||
|
throw new Exception("Error en la consulta de canchas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$canchas = mysqli_fetch_all($res, MYSQLI_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode($canchas);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$fecha = isset($_GET['fecha']) ? $_GET['fecha'] : '';
|
||||||
|
$horario_inicio = isset($_GET['horario']) ? $_GET['horario'] : '';
|
||||||
|
$duracion = isset($_GET['duracion']) ? $_GET['duracion'] : 60;
|
||||||
|
$tipo = isset($_GET['tipo']) ? $_GET['tipo'] : '';
|
||||||
|
|
||||||
|
if (empty($fecha) || empty($horario_inicio) || empty($tipo)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Parámetros incompletos'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Obtener la cantidad total de canchas del tipo especificado
|
||||||
|
$t = mysqli_real_escape_string($conexion, $tipo);
|
||||||
|
$sqlCanchas = "SELECT Cant_Canchas as cantidad_canchas, Duracion as duracion_cancha
|
||||||
|
FROM canchas
|
||||||
|
WHERE Tipo = '$t'";
|
||||||
|
|
||||||
|
$resCanchas = mysqli_query($conexion, $sqlCanchas);
|
||||||
|
|
||||||
|
if (!$resCanchas) {
|
||||||
|
throw new Exception("Error al obtener información de canchas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancha = mysqli_fetch_assoc($resCanchas);
|
||||||
|
$cantidadCanchas = $cancha['cantidad_canchas'];
|
||||||
|
$duracionCancha = $cancha['duracion_cancha'];
|
||||||
|
|
||||||
|
// Convertir horario
|
||||||
|
$horaInicioArr = explode(':', $horario_inicio);
|
||||||
|
$horaInicioMin = intval($horaInicioArr[0]) * 60 + intval($horaInicioArr[1]);
|
||||||
|
$horaFinMin = $horaInicioMin + intval($duracion);
|
||||||
|
|
||||||
|
$horaInicio = $horario_inicio . ':00';
|
||||||
|
$horaFin = sprintf("%02d:%02d", intval($horaFinMin / 60), intval($horaFinMin % 60)) . ':00';
|
||||||
|
|
||||||
|
// Obtener las canchas reservadas para este horario
|
||||||
|
$f = mysqli_real_escape_string($conexion, $fecha);
|
||||||
|
$sqlReservadasPorNumero = "SELECT r.Numero as numero_cancha
|
||||||
|
FROM reservas r
|
||||||
|
INNER JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
||||||
|
WHERE c.Tipo = '$t'
|
||||||
|
AND DATE(r.Fecha_Hora) = '$f'
|
||||||
|
AND r.Estado != 2
|
||||||
|
AND (
|
||||||
|
(TIME(r.Fecha_Hora) < '$horaFin' AND ADDTIME(TIME(r.Fecha_Hora), SEC_TO_TIME($duracionCancha * 60)) > '$horaInicio')
|
||||||
|
)
|
||||||
|
GROUP BY r.Numero";
|
||||||
|
|
||||||
|
$resReservadasPorNumero = mysqli_query($conexion, $sqlReservadasPorNumero);
|
||||||
|
|
||||||
|
if (!$resReservadasPorNumero) {
|
||||||
|
throw new Exception("Error al obtener canchas reservadas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$canchasReservadas = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($resReservadasPorNumero)) {
|
||||||
|
$canchasReservadas[] = intval($row['numero_cancha']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir lista de todas las canchas con su disponibilidad
|
||||||
|
$canchas = [];
|
||||||
|
for ($i = 1; $i <= $cantidadCanchas; $i++) {
|
||||||
|
$canchas[] = [
|
||||||
|
'numero' => $i,
|
||||||
|
'disponible' => !in_array($i, $canchasReservadas)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'cantidad_total' => $cantidadCanchas,
|
||||||
|
'canchas' => $canchas,
|
||||||
|
'tipo' => $tipo
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$dni = isset($_GET['dni']) ? trim($_GET['dni']) : '';
|
||||||
|
$tipo = isset($_GET['tipo']) ? trim($_GET['tipo']) : '';
|
||||||
|
|
||||||
|
if (empty($dni) || empty($tipo)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'DNI y tipo de cancha son requeridos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tipoDb = '';
|
||||||
|
if ($tipo === 'futbol' || $tipo === 'F') {
|
||||||
|
$tipoDb = 'F';
|
||||||
|
} elseif ($tipo === 'padel' || $tipo === 'P') {
|
||||||
|
$tipoDb = 'P';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($tipoDb)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Tipo de cancha inválido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dni = mysqli_real_escape_string($conexion, $dni);
|
||||||
|
|
||||||
|
$sqlCliente = "SELECT ID_Cliente FROM clientes WHERE DNI = '$dni' LIMIT 1";
|
||||||
|
$resCliente = mysqli_query($conexion, $sqlCliente);
|
||||||
|
if (!$resCliente) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al buscar cliente: ' . mysqli_error($conexion)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cliente = mysqli_fetch_assoc($resCliente);
|
||||||
|
if (!$cliente) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'clienteEncontrado' => false,
|
||||||
|
'cantidadReservas' => 0,
|
||||||
|
'cantidadFaltas' => 0,
|
||||||
|
'umbral' => 0,
|
||||||
|
'descuentoCanchas' => 0,
|
||||||
|
'duracionCupon' => 0,
|
||||||
|
'ultimaFecha' => null,
|
||||||
|
'aplicarDescuento' => false,
|
||||||
|
'reservasRestantes' => null
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idCliente = intval($cliente['ID_Cliente']);
|
||||||
|
|
||||||
|
$sqlCancha = "SELECT Cant_Reservas_Cupon, Descuento_Cupon, Cant_Faltas, Duracion_Cupon FROM canchas WHERE Tipo = '$tipoDb' LIMIT 1";
|
||||||
|
$resCancha = mysqli_query($conexion, $sqlCancha);
|
||||||
|
if (!$resCancha) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al obtener datos de la cancha: ' . mysqli_error($conexion)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancha = mysqli_fetch_assoc($resCancha);
|
||||||
|
if (!$cancha) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No se encontró el tipo de cancha']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$umbral = intval($cancha['Cant_Reservas_Cupon']);
|
||||||
|
$descuentoCanchas = floatval($cancha['Descuento_Cupon']);
|
||||||
|
$duracionCupon = intval($cancha['Duracion_Cupon']);
|
||||||
|
$maxFaltas = intval($cancha['Cant_Faltas']);
|
||||||
|
|
||||||
|
$sqlCupon = "SELECT ID_Cupon, Cantidad_Reservas, Cantidad_Faltas, Ultima_Fecha FROM cupones WHERE ID_Cliente = $idCliente AND Tipo = '$tipoDb' LIMIT 1";
|
||||||
|
$resCupon = mysqli_query($conexion, $sqlCupon);
|
||||||
|
if (!$resCupon) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al buscar cupón: ' . mysqli_error($conexion)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cupon = mysqli_fetch_assoc($resCupon);
|
||||||
|
$cantidadReservas = 0;
|
||||||
|
$cantidadFaltas = 0;
|
||||||
|
$ultimaFecha = null;
|
||||||
|
$vencido = false;
|
||||||
|
|
||||||
|
if ($cupon) {
|
||||||
|
$cantidadReservas = intval($cupon['Cantidad_Reservas']);
|
||||||
|
$cantidadFaltas = intval($cupon['Cantidad_Faltas']);
|
||||||
|
$ultimaFecha = $cupon['Ultima_Fecha'];
|
||||||
|
|
||||||
|
if ($duracionCupon > 0 && !empty($ultimaFecha)) {
|
||||||
|
$fechaUltima = new DateTime($ultimaFecha);
|
||||||
|
$fechaUltima->modify("+{$duracionCupon} days");
|
||||||
|
$fechaActual = new DateTime();
|
||||||
|
|
||||||
|
if ($fechaActual > $fechaUltima) {
|
||||||
|
$cantidadReservas = 0;
|
||||||
|
$cantidadFaltas = 0;
|
||||||
|
$ultimaFecha = null;
|
||||||
|
$vencido = true;
|
||||||
|
$sqlResetCupon = "UPDATE cupones SET Cantidad_Reservas = 0, Cantidad_Faltas = 0, Ultima_Fecha = NULL, Descuento = 0 WHERE ID_Cupon = {$cupon['ID_Cupon']}";
|
||||||
|
mysqli_query($conexion, $sqlResetCupon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($maxFaltas > 0 && $cantidadFaltas > $maxFaltas) {
|
||||||
|
$cantidadReservas = 0;
|
||||||
|
$cantidadFaltas = 0;
|
||||||
|
$ultimaFecha = null;
|
||||||
|
$sqlResetCupon = "UPDATE cupones SET Cantidad_Reservas = 0, Cantidad_Faltas = 0, Ultima_Fecha = NULL, Descuento = 0 WHERE ID_Cupon = {$cupon['ID_Cupon']}";
|
||||||
|
mysqli_query($conexion, $sqlResetCupon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$aplicarDescuento = false;
|
||||||
|
$reservasRestantes = null;
|
||||||
|
if ($umbral > 0) {
|
||||||
|
if (($cantidadReservas + 1) >= $umbral) {
|
||||||
|
$aplicarDescuento = true;
|
||||||
|
$reservasRestantes = 0;
|
||||||
|
} else {
|
||||||
|
$reservasRestantes = $umbral - $cantidadReservas - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'clienteEncontrado' => true,
|
||||||
|
'cantidadReservas' => $cantidadReservas,
|
||||||
|
'cantidadFaltas' => $cantidadFaltas,
|
||||||
|
'umbral' => $umbral,
|
||||||
|
'descuentoCanchas' => $descuentoCanchas,
|
||||||
|
'duracionCupon' => $duracionCupon,
|
||||||
|
'ultimaFecha' => $ultimaFecha,
|
||||||
|
'aplicarDescuento' => $aplicarDescuento,
|
||||||
|
'reservasRestantes' => $reservasRestantes,
|
||||||
|
'vencido' => $vencido
|
||||||
|
]);
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$fecha = isset($_GET['fecha']) ? $_GET['fecha'] : '';
|
||||||
|
$horario_inicio = isset($_GET['horario']) ? $_GET['horario'] : '';
|
||||||
|
$duracion = isset($_GET['duracion']) ? $_GET['duracion'] : 60; // duracion en minutos
|
||||||
|
$tipo = isset($_GET['tipo']) ? $_GET['tipo'] : ''; // 'F' para futbol, 'P' para padel
|
||||||
|
|
||||||
|
if (empty($fecha) || empty($horario_inicio) || empty($tipo)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Parámetros incompletos'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Obtener la cantidad total de canchas del tipo especificado y su duración
|
||||||
|
$t = mysqli_real_escape_string($conexion, $tipo);
|
||||||
|
$sqlCanchas = "SELECT Cant_Canchas as cantidad_canchas, Duracion as duracion_cancha
|
||||||
|
FROM canchas
|
||||||
|
WHERE Tipo = '$t'";
|
||||||
|
|
||||||
|
$resCanchas = mysqli_query($conexion, $sqlCanchas);
|
||||||
|
|
||||||
|
if (!$resCanchas) {
|
||||||
|
throw new Exception("Error al obtener información de canchas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancha = mysqli_fetch_assoc($resCanchas);
|
||||||
|
$cantidadCanchas = $cancha['cantidad_canchas'];
|
||||||
|
$duracionCancha = $cancha['duracion_cancha'];
|
||||||
|
|
||||||
|
// Convertir horario a minutos para comparación
|
||||||
|
$horaInicioArr = explode(':', $horario_inicio);
|
||||||
|
$horaInicioMin = intval($horaInicioArr[0]) * 60 + intval($horaInicioArr[1]);
|
||||||
|
$horaFinMin = $horaInicioMin + intval($duracion);
|
||||||
|
|
||||||
|
// Convertir a formato HH:MM:SS para la query
|
||||||
|
$horaInicio = $horario_inicio . ':00';
|
||||||
|
$horaFin = sprintf("%02d:%02d", intval($horaFinMin / 60), intval($horaFinMin % 60)) . ':00';
|
||||||
|
|
||||||
|
// Obtener la cantidad de reservas que se superponen con el horario seleccionado
|
||||||
|
$f = mysqli_real_escape_string($conexion, $fecha);
|
||||||
|
$sqlReservas = "SELECT COUNT(*) as reservas_solapadas
|
||||||
|
FROM reservas r
|
||||||
|
INNER JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
||||||
|
WHERE c.Tipo = '$t'
|
||||||
|
AND DATE(r.Fecha_Hora) = '$f'
|
||||||
|
AND r.Estado != 2
|
||||||
|
AND (
|
||||||
|
(TIME(r.Fecha_Hora) < '$horaFin' AND ADDTIME(TIME(r.Fecha_Hora), SEC_TO_TIME($duracionCancha * 60)) > '$horaInicio')
|
||||||
|
)";
|
||||||
|
|
||||||
|
$resReservas = mysqli_query($conexion, $sqlReservas);
|
||||||
|
|
||||||
|
if (!$resReservas) {
|
||||||
|
throw new Exception("Error al contar reservas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$reservas = mysqli_fetch_assoc($resReservas);
|
||||||
|
$reservasSolapadas = $reservas['reservas_solapadas'];
|
||||||
|
|
||||||
|
// Calcular disponibilidad
|
||||||
|
$disponibles = $cantidadCanchas - $reservasSolapadas;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'cantidad_total' => $cantidadCanchas,
|
||||||
|
'reservas' => $reservasSolapadas,
|
||||||
|
'disponibles' => max(0, $disponibles),
|
||||||
|
'horario' => $horaInicio,
|
||||||
|
'tipo' => $tipo
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$fecha = isset($_GET['fecha']) ? $_GET['fecha'] : '';
|
||||||
|
|
||||||
|
if (empty($fecha)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Fecha no proporcionada'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Obtener la cantidad total de quinchos disponibles
|
||||||
|
$sqlCanchas = "SELECT Cant_Canchas as cantidad_quinchos
|
||||||
|
FROM canchas
|
||||||
|
WHERE Tipo = 'Q'";
|
||||||
|
|
||||||
|
$resCanchas = mysqli_query($conexion, $sqlCanchas);
|
||||||
|
|
||||||
|
if (!$resCanchas) {
|
||||||
|
throw new Exception("Error al obtener información del quincho: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancha = mysqli_fetch_assoc($resCanchas);
|
||||||
|
$cantidadQuinchos = $cancha['cantidad_quinchos'];
|
||||||
|
|
||||||
|
// Obtener la cantidad de reservas de quincho para esa fecha
|
||||||
|
$f = mysqli_real_escape_string($conexion, $fecha);
|
||||||
|
$sqlReservas = "SELECT COUNT(*) as reservas_quincho
|
||||||
|
FROM reservas r
|
||||||
|
INNER JOIN canchas c ON r.ID_Cancha = c.ID_Cancha
|
||||||
|
WHERE c.Tipo = 'Q'
|
||||||
|
AND DATE(r.Fecha_Hora) = '$f'
|
||||||
|
AND r.Estado != 2";
|
||||||
|
|
||||||
|
$resReservas = mysqli_query($conexion, $sqlReservas);
|
||||||
|
|
||||||
|
if (!$resReservas) {
|
||||||
|
throw new Exception("Error al contar reservas: " . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
$reservas = mysqli_fetch_assoc($resReservas);
|
||||||
|
$reservasQuincho = $reservas['reservas_quincho'];
|
||||||
|
|
||||||
|
// Calcular disponibilidad
|
||||||
|
$disponibles = $cantidadQuinchos - $reservasQuincho;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'cantidad_total' => $cantidadQuinchos,
|
||||||
|
'reservas' => $reservasQuincho,
|
||||||
|
'disponibles' => max(0, $disponibles)
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Obtener columnas dinámicas (excluyendo ID_Gasto, Periodo_Desde, Periodo_Hasta)
|
||||||
|
$sql_columns = "SHOW COLUMNS FROM gastos";
|
||||||
|
$result_columns = $conexion->query($sql_columns);
|
||||||
|
|
||||||
|
$columnas = [];
|
||||||
|
while ($row = $result_columns->fetch_assoc()) {
|
||||||
|
$col = $row['Field'];
|
||||||
|
if (!in_array($col, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta'])) {
|
||||||
|
$columnas[] = $col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener el registro actual (donde Periodo_Hasta IS NULL)
|
||||||
|
$sql_actual = "SELECT * FROM gastos WHERE Periodo_Hasta IS NULL ORDER BY Periodo_Desde DESC LIMIT 1";
|
||||||
|
$result_actual = $conexion->query($sql_actual);
|
||||||
|
|
||||||
|
$gasto_actual = null;
|
||||||
|
if ($result_actual && $result_actual->num_rows > 0) {
|
||||||
|
$gasto_actual = $result_actual->fetch_assoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'columnas' => $columnas,
|
||||||
|
'gasto' => $gasto_actual,
|
||||||
|
'message' => $gasto_actual ? 'Datos cargados' : 'No hay gastos registrados'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
include 'conexion.php';
|
||||||
|
$res = mysqli_query($conexion, "SELECT * FROM horarios");
|
||||||
|
$horarios = mysqli_fetch_all($res, MYSQLI_ASSOC);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($horarios);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT oc.ID_Orden, DATE(oc.Fecha) AS Fecha, oc.ID_Proveedor, p.Nombre AS Proveedor, p.Telefono, oc.Estado
|
||||||
|
FROM ordenes_compra oc
|
||||||
|
LEFT JOIN proveedores p ON oc.ID_Proveedor = p.ID_Proveedor
|
||||||
|
ORDER BY oc.Fecha DESC";
|
||||||
|
|
||||||
|
$result = $conexion->query($sql);
|
||||||
|
$ordenes = [];
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
while ($fila = $result->fetch_assoc()) {
|
||||||
|
$ordenes[] = $fila;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($ordenes);
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Verificar si la columna Estado existe
|
||||||
|
$checkColumn = $conexion->query("SHOW COLUMNS FROM productos WHERE Field='Estado'");
|
||||||
|
$columnaExists = $checkColumn && $checkColumn->num_rows > 0;
|
||||||
|
|
||||||
|
if ($columnaExists) {
|
||||||
|
// Solo mostrar productos activos
|
||||||
|
$result = $conexion->query("SELECT ID_Producto, Descripcion, Precio_Venta, Precio_Compra, Stock_Disponible FROM productos WHERE Estado = 1 ORDER BY ID_Producto DESC");
|
||||||
|
} else {
|
||||||
|
// Fallback: verificar si la columna Activo existe
|
||||||
|
$checkColumnActivo = $conexion->query("SHOW COLUMNS FROM productos WHERE Field='Activo'");
|
||||||
|
$activoExists = $checkColumnActivo && $checkColumnActivo->num_rows > 0;
|
||||||
|
|
||||||
|
if ($activoExists) {
|
||||||
|
$result = $conexion->query("SELECT ID_Producto, Descripcion, Precio_Venta, Precio_Compra, Stock_Disponible FROM productos WHERE Activo = 1 ORDER BY ID_Producto DESC");
|
||||||
|
} else {
|
||||||
|
$result = $conexion->query("SELECT ID_Producto, Descripcion, Precio_Venta, Precio_Compra, Stock_Disponible FROM productos ORDER BY ID_Producto DESC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$productos = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$productos[] = $row;
|
||||||
|
}
|
||||||
|
echo json_encode($productos);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_orden = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||||
|
|
||||||
|
if ($id_orden <= 0) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT op.ID_Producto, op.Cantidad, p.Descripcion
|
||||||
|
FROM ordenes_productos op
|
||||||
|
INNER JOIN productos p ON op.ID_Producto = p.ID_Producto
|
||||||
|
WHERE op.ID_Orden = ?";
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id_orden);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$productos = [];
|
||||||
|
while ($fila = $result->fetch_assoc()) {
|
||||||
|
$productos[] = $fila;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($productos);
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$idProveedor = $_GET['id'] ?? 0;
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("
|
||||||
|
SELECT p.ID_Producto, p.Descripcion
|
||||||
|
FROM productos p
|
||||||
|
INNER JOIN proveedores_productos pp ON p.ID_Producto = pp.ID_Producto
|
||||||
|
WHERE pp.ID_Proveedor = ?
|
||||||
|
");
|
||||||
|
$stmt->bind_param("i", $idProveedor);
|
||||||
|
$stmt->execute();
|
||||||
|
$resultado = $stmt->get_result();
|
||||||
|
|
||||||
|
$productos = [];
|
||||||
|
while ($fila = $resultado->fetch_assoc()) {
|
||||||
|
$productos[] = $fila;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($productos);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultado = $conexion->query("SELECT ID_Proveedor, Nombre, Telefono FROM proveedores");
|
||||||
|
$proveedores = [];
|
||||||
|
|
||||||
|
while ($fila = $resultado->fetch_assoc()) {
|
||||||
|
$proveedores[] = $fila;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($proveedores);
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$fecha = isset($_GET['fecha']) ? $_GET['fecha'] : '';
|
||||||
|
$nombre = isset($_GET['nombre']) ? $_GET['nombre'] : '';
|
||||||
|
$dni = isset($_GET['dni']) ? $_GET['dni'] : '';
|
||||||
|
$tipo = isset($_GET['tipo']) ? $_GET['tipo'] : '';
|
||||||
|
|
||||||
|
$sql = "SELECT r.ID_Reserva, c.Nombre, c.DNI, c.Telefono, r.Fecha_Hora, ca.Tipo, IFNULL(r.Descuento, 0) as Descuento_Cupon, r.Monto, r.Numero, r.Estado
|
||||||
|
FROM reservas r
|
||||||
|
INNER JOIN clientes c ON r.ID_Cliente = c.ID_Cliente
|
||||||
|
LEFT JOIN canchas ca ON r.ID_Cancha = ca.ID_Cancha
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
//Filtro de Fecha
|
||||||
|
if (!empty($fecha)) {
|
||||||
|
$f = mysqli_real_escape_string($conexion, $fecha);
|
||||||
|
$sql .= " AND DATE(r.Fecha_Hora) = '$f'";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Filtro de Nombre
|
||||||
|
if (!empty($nombre)) {
|
||||||
|
$n = mysqli_real_escape_string($conexion, $nombre);
|
||||||
|
$sql .= " AND c.Nombre LIKE '%$n%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro de DNI
|
||||||
|
if (!empty($dni)) {
|
||||||
|
$d = mysqli_real_escape_string($conexion, $dni);
|
||||||
|
$sql .= " AND c.DNI LIKE '%$d%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Filtro de Tipo
|
||||||
|
if (!empty($tipo)) {
|
||||||
|
if ($tipo === 'futbol') {
|
||||||
|
$sql .= " AND ca.Tipo = 'F'";
|
||||||
|
} elseif ($tipo === 'padel') {
|
||||||
|
$sql .= " AND ca.Tipo = 'P'";
|
||||||
|
} elseif ($tipo === 'quincho') {
|
||||||
|
$sql .= " AND ca.Tipo = 'Q'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY r.Fecha_Hora DESC";
|
||||||
|
|
||||||
|
$resultado = mysqli_query($conexion, $sql);
|
||||||
|
$reservas = [];
|
||||||
|
|
||||||
|
if (!$resultado) {
|
||||||
|
die(json_encode(["error" => mysqli_error($conexion)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($resultado) {
|
||||||
|
while ($fila = mysqli_fetch_assoc($resultado)) {
|
||||||
|
$reservas[] = $fila;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($reservas);
|
||||||
|
?>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Respuesta por defecto
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Obtener todos los usuarios
|
||||||
|
$query = "SELECT ID_Usuario, Usuario, Rol, Nombre, Apellido, DNI, Correo, Telefono FROM usuarios ORDER BY Nombre ASC";
|
||||||
|
$result = $conexion->query($query);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$usuarios = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$usuarios[] = $row;
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true, 'usuarios' => $usuarios]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al obtener usuarios: ' . $conexion->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$start = isset($_GET['start']) ? $_GET['start'] : null;
|
||||||
|
$end = isset($_GET['end']) ? $_GET['end'] : null;
|
||||||
|
$id_venta = isset($_GET['id_venta']) ? intval($_GET['id_venta']) : null;
|
||||||
|
$days = isset($_GET['days']) ? intval($_GET['days']) : null;
|
||||||
|
|
||||||
|
// Si se solicita una venta específica, devolver sus productos
|
||||||
|
if ($id_venta !== null) {
|
||||||
|
$stmt = $conexion->prepare("
|
||||||
|
SELECT
|
||||||
|
vp.ID_Producto,
|
||||||
|
p.Descripcion as nombre,
|
||||||
|
vp.Cantidad,
|
||||||
|
COALESCE(p.Precio_Venta, 0) as precio_unitario,
|
||||||
|
(vp.Cantidad * COALESCE(p.Precio_Venta, 0)) as subtotal
|
||||||
|
FROM ventas_productos vp
|
||||||
|
LEFT JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
WHERE vp.ID_Venta = ?
|
||||||
|
ORDER BY vp.ID_Producto
|
||||||
|
");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Error en la consulta preparada: ' . $conexion->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("i", $id_venta);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->error) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Error al ejecutar: ' . $stmt->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$productos = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$productos[] = [
|
||||||
|
'ID_Producto' => $row['ID_Producto'],
|
||||||
|
'nombre' => $row['nombre'],
|
||||||
|
'cantidad' => intval($row['Cantidad']),
|
||||||
|
'precio_unitario' => floatval($row['precio_unitario']),
|
||||||
|
'subtotal' => floatval($row['subtotal'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($productos);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// De lo contrario, devolver las ventas individuales del período
|
||||||
|
if ($days !== null) {
|
||||||
|
$end = date('Y-m-d');
|
||||||
|
$start = date('Y-m-d', strtotime("-{$days} days", strtotime($end)));
|
||||||
|
} else {
|
||||||
|
if (!$start && !$end) {
|
||||||
|
$start = date('Y-m-d');
|
||||||
|
$end = $start;
|
||||||
|
} elseif ($start && !$end) {
|
||||||
|
$end = $start;
|
||||||
|
} elseif (!$start && $end) {
|
||||||
|
$start = $end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDateTime = $start . ' 00:00:00';
|
||||||
|
$endDateTime = $end . ' 23:59:59';
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare("
|
||||||
|
SELECT ID_Venta, Fecha, Monto
|
||||||
|
FROM ventas
|
||||||
|
WHERE Fecha BETWEEN ? AND ?
|
||||||
|
ORDER BY Fecha DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Error en la consulta preparada: ' . $conexion->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ss", $startDateTime, $endDateTime);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->error) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Error al ejecutar: ' . $stmt->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$ventas = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$ventas[] = [
|
||||||
|
'ID_Venta' => intval($row['ID_Venta']),
|
||||||
|
'fecha_hora' => $row['Fecha'],
|
||||||
|
'monto_total' => floatval($row['Monto'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($ventas);
|
||||||
|
?>
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once 'conexion.php';
|
||||||
|
require_once 'config_mail.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['correo']) || empty($data['correo'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Por favor ingresa un correo']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$correo = mysqli_real_escape_string($conexion, trim($data['correo']));
|
||||||
|
|
||||||
|
// Verificar si el correo existe en la base de datos
|
||||||
|
$sql = "SELECT ID_Usuario, Usuario, Nombre FROM usuarios WHERE Correo = '$correo'";
|
||||||
|
$resultado = mysqli_query($conexion, $sql);
|
||||||
|
|
||||||
|
if (!$resultado) {
|
||||||
|
throw new Exception('Error en la consulta: ' . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysqli_num_rows($resultado) === 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'El correo no está registrado en el sistema']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usuario = mysqli_fetch_assoc($resultado);
|
||||||
|
$id_usuario = $usuario['ID_Usuario'];
|
||||||
|
$nombre_usuario = $usuario['Nombre'];
|
||||||
|
|
||||||
|
// Generar código de 6 dígitos
|
||||||
|
$codigo = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
// Guardar el código con fecha de expiración (15 minutos)
|
||||||
|
$fecha_expiracion = date('Y-m-d H:i:s', strtotime('+15 minutes'));
|
||||||
|
$sql_update = "UPDATE usuarios SET Codigo_Recuperacion = '$codigo', Fecha_Exp_Codigo = '$fecha_expiracion' WHERE ID_Usuario = $id_usuario";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sql_update)) {
|
||||||
|
throw new Exception('Error al actualizar: ' . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir el mensaje de email
|
||||||
|
$asunto = 'Código de Recuperación - Complejo Cap1tan';
|
||||||
|
$mensaje = "Hola $nombre_usuario,\n\n";
|
||||||
|
$mensaje .= "Hemos recibido una solicitud para recuperar tu contraseña en Complejo Cap1tan.\n\n";
|
||||||
|
$mensaje .= "Tu código de verificación es: " . $codigo . "\n\n";
|
||||||
|
$mensaje .= "Este código expira en 15 minutos.\n\n";
|
||||||
|
$mensaje .= "Si no realizaste esta solicitud, por favor ignora este correo.\n\n";
|
||||||
|
$mensaje .= "Saludos,\nEquipo de Complejo Cap1tan";
|
||||||
|
|
||||||
|
// Enviar email usando Brevo
|
||||||
|
$correo_enviado = enviarEmailBrevo($correo, $asunto, $mensaje, false);
|
||||||
|
|
||||||
|
// Respuesta exitosa (el código se guardó en la BD de todas formas)
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Se ha enviado un código de verificación a tu correo. Por favor revisa tu bandeja de entrada.',
|
||||||
|
'test_codigo' => $codigo // Cambiar a false en producción
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['correo']) || !isset($data['codigo']) || !isset($data['nueva_contrasena'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Faltan datos requeridos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$correo = mysqli_real_escape_string($conexion, trim($data['correo']));
|
||||||
|
$codigo = mysqli_real_escape_string($conexion, trim($data['codigo']));
|
||||||
|
$nueva_contrasena = mysqli_real_escape_string($conexion, $data['nueva_contrasena']);
|
||||||
|
|
||||||
|
// Validar que la contraseña tenga al menos 6 caracteres
|
||||||
|
if (strlen($nueva_contrasena) < 6) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La contraseña debe tener al menos 6 caracteres']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar el código y que no haya expirado
|
||||||
|
$sql = "SELECT ID_Usuario, Codigo_Recuperacion, Fecha_Exp_Codigo FROM usuarios WHERE Correo = '$correo'";
|
||||||
|
$resultado = mysqli_query($conexion, $sql);
|
||||||
|
|
||||||
|
if (!$resultado) {
|
||||||
|
error_log('Error en SELECT: ' . mysqli_error($conexion));
|
||||||
|
throw new Exception('Error en la consulta: ' . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysqli_num_rows($resultado) === 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Correo no encontrado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usuario = mysqli_fetch_assoc($resultado);
|
||||||
|
$id_usuario = $usuario['ID_Usuario'];
|
||||||
|
|
||||||
|
// Verificar si el código es correcto
|
||||||
|
if ($usuario['Codigo_Recuperacion'] === null || $usuario['Codigo_Recuperacion'] === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No hay código generado. Solicita uno nuevo']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($usuario['Codigo_Recuperacion'] !== $codigo) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Código incorrecto']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si el código ha expirado
|
||||||
|
if ($usuario['Fecha_Exp_Codigo'] === null) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error: fecha de expiración no registrada']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fecha_actual = new DateTime();
|
||||||
|
$fecha_exp = new DateTime($usuario['Fecha_Exp_Codigo']);
|
||||||
|
|
||||||
|
if ($fecha_actual > $fecha_exp) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'El código ha expirado. Solicita uno nuevo']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar la contraseña - IMPORTANTE: el nombre de la columna es "Contraseña" con acento
|
||||||
|
$sql_update = "UPDATE usuarios SET `Contraseña` = '$nueva_contrasena', `Codigo_Recuperacion` = NULL, `Fecha_Exp_Codigo` = NULL WHERE ID_Usuario = $id_usuario";
|
||||||
|
|
||||||
|
if (!mysqli_query($conexion, $sql_update)) {
|
||||||
|
error_log('Error en UPDATE: ' . mysqli_error($conexion) . ' | SQL: ' . $sql_update);
|
||||||
|
throw new Exception('Error al actualizar: ' . mysqli_error($conexion));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que se actualizó correctamente
|
||||||
|
if (mysqli_affected_rows($conexion) === 0) {
|
||||||
|
error_log('Warning: UPDATE ejecutado pero no afectó filas');
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No se pudo actualizar la contraseña']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Contraseña actualizada correctamente']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('Exception en Recuperar_Contrasena_Verificar: ' . $e->getMessage());
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($data['nuevo_gasto']) || !isset($data['monto'])) {
|
||||||
|
throw new Exception("Datos incompletos");
|
||||||
|
}
|
||||||
|
|
||||||
|
$nuevo_gasto = trim($data['nuevo_gasto']);
|
||||||
|
$monto = floatval($data['monto']);
|
||||||
|
|
||||||
|
// Validar nombre del gasto
|
||||||
|
if (empty($nuevo_gasto) || $monto < 0) {
|
||||||
|
throw new Exception("Datos inválidos");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir nombre a formato de columna (minúsculas, sin espacios, con guion bajo)
|
||||||
|
$nombre_columna = strtolower(str_replace(' ', '_', $nuevo_gasto));
|
||||||
|
|
||||||
|
// Validar que sea un nombre válido para columna SQL
|
||||||
|
if (!preg_match('/^[a-z_][a-z0-9_]*$/', $nombre_columna)) {
|
||||||
|
throw new Exception("Nombre de gasto inválido");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si la columna ya existe (sin prepared statement porque SHOW COLUMNS no lo soporta bien)
|
||||||
|
$sql_check = "SHOW COLUMNS FROM gastos LIKE '" . $conexion->real_escape_string($nombre_columna) . "'";
|
||||||
|
$result_check = $conexion->query($sql_check);
|
||||||
|
|
||||||
|
if ($result_check === false) {
|
||||||
|
throw new Exception("Error verificando columna: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result_check->num_rows === 0) {
|
||||||
|
// Columna no existe, agregar con ALTER TABLE
|
||||||
|
$sql_alter = "ALTER TABLE gastos ADD COLUMN `$nombre_columna` DECIMAL(10,2) DEFAULT NULL";
|
||||||
|
|
||||||
|
if (!$conexion->query($sql_alter)) {
|
||||||
|
throw new Exception("Error al agregar columna: " . $conexion->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener el ID del registro anterior (donde Periodo_Hasta IS NULL)
|
||||||
|
$sql_get_id = "SELECT ID_Gasto FROM gastos WHERE Periodo_Hasta IS NULL ORDER BY Periodo_Desde DESC LIMIT 1";
|
||||||
|
$result_id = $conexion->query($sql_get_id);
|
||||||
|
|
||||||
|
$fecha_hoy = date('Y-m-d');
|
||||||
|
$id_anterior = null;
|
||||||
|
|
||||||
|
if ($result_id && $result_id->num_rows > 0) {
|
||||||
|
$row = $result_id->fetch_assoc();
|
||||||
|
$id_anterior = $row['ID_Gasto'];
|
||||||
|
|
||||||
|
// Cerrar el período anterior
|
||||||
|
$sql_update = "UPDATE gastos SET Periodo_Hasta = ? WHERE ID_Gasto = ?";
|
||||||
|
$stmt_update = $conexion->prepare($sql_update);
|
||||||
|
$stmt_update->bind_param('si', $fecha_hoy, $id_anterior);
|
||||||
|
|
||||||
|
if (!$stmt_update->execute()) {
|
||||||
|
throw new Exception("Error actualizando período anterior: " . $stmt_update->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_update->close();
|
||||||
|
|
||||||
|
// Obtener todos los valores del registro anterior para reutilizarlos
|
||||||
|
$sql_get_values = "SELECT * FROM gastos WHERE ID_Gasto = ?";
|
||||||
|
$stmt_get_values = $conexion->prepare($sql_get_values);
|
||||||
|
$stmt_get_values->bind_param('i', $id_anterior);
|
||||||
|
$stmt_get_values->execute();
|
||||||
|
$result_values = $stmt_get_values->get_result();
|
||||||
|
$valores_anteriores = $result_values->fetch_assoc();
|
||||||
|
$stmt_get_values->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir INSERT dinámico
|
||||||
|
$columnas = ['Periodo_Desde', 'Periodo_Hasta', $nombre_columna];
|
||||||
|
$valores = [date('Y-m-d'), null, $monto];
|
||||||
|
$placeholders = ['?', '?', '?'];
|
||||||
|
$tipos = 'ssd';
|
||||||
|
|
||||||
|
// Si hay datos anteriores, agregar todas las columnas existentes
|
||||||
|
if (!empty($valores_anteriores)) {
|
||||||
|
foreach ($valores_anteriores as $col => $val) {
|
||||||
|
if (!in_array($col, ['ID_Gasto', 'Periodo_Desde', 'Periodo_Hasta', $nombre_columna])) {
|
||||||
|
$columnas[] = $col;
|
||||||
|
$valores[] = $val;
|
||||||
|
$placeholders[] = '?';
|
||||||
|
$tipos .= 'd';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_insert = "INSERT INTO gastos (" . implode(', ', $columnas) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||||
|
$stmt_insert = $conexion->prepare($sql_insert);
|
||||||
|
|
||||||
|
if (!$stmt_insert) {
|
||||||
|
throw new Exception("Error preparando INSERT: " . $conexion->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt_insert->bind_param($tipos, ...$valores);
|
||||||
|
|
||||||
|
if (!$stmt_insert->execute()) {
|
||||||
|
throw new Exception("Error insertando registro: " . $stmt_insert->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nuevo_id = $stmt_insert->insert_id;
|
||||||
|
$stmt_insert->close();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Gasto agregado correctamente',
|
||||||
|
'id_gasto' => $nuevo_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Insertar orden
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO ordenes_compra (Fecha, ID_Proveedor) VALUES (NOW(), ?)");
|
||||||
|
$stmt->bind_param("i", $data["proveedor"]);
|
||||||
|
$stmt->execute();
|
||||||
|
$idOrden = $stmt->insert_id;
|
||||||
|
|
||||||
|
// Insertar productos de la orden
|
||||||
|
foreach ($data["productos"] as $p) {
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO ordenes_productos (ID_Orden, ID_Producto, Cantidad) VALUES (?, ?, ?)");
|
||||||
|
$stmt->bind_param("iii", $idOrden, $p["id"], $p["cantidad"]);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Orden registrada correctamente.";
|
||||||
|
?>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'conexion.php';
|
||||||
|
$productos = json_decode(file_get_contents("php://input"), true);
|
||||||
|
|
||||||
|
// Calcular monto total
|
||||||
|
$monto = 0;
|
||||||
|
foreach ($productos as $p) {
|
||||||
|
$monto += $p['subtotal'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener ID del usuario desde la sesión
|
||||||
|
$id_usuario = isset($_SESSION['id_usuario']) ? intval($_SESSION['id_usuario']) : null;
|
||||||
|
|
||||||
|
// Insertar venta
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO ventas (Fecha, Monto, ID_Usuario) VALUES (NOW(), ?, ?)");
|
||||||
|
$stmt->bind_param("di", $monto, $id_usuario);
|
||||||
|
$stmt->execute();
|
||||||
|
$id_venta = $stmt->insert_id;
|
||||||
|
|
||||||
|
// Insertar productos vendidos
|
||||||
|
foreach ($productos as $p) {
|
||||||
|
$stmt = $conexion->prepare("INSERT INTO ventas_productos (ID_Venta, ID_Producto, Cantidad) VALUES (?, ?, ?)");
|
||||||
|
$stmt->bind_param("iii", $id_venta, $p['id'], $p['cantidad']);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
// Descontar stock
|
||||||
|
//$stmt = $conexion->prepare("UPDATE productos SET Stock_Disponible = Stock_Disponible - ? WHERE ID_Producto = ?");
|
||||||
|
//$stmt->bind_param("ii", $p['cantidad'], $p['id']);
|
||||||
|
//$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Venta registrada correctamente.";
|
||||||
|
?>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
die("Error de conexión: " . $conexion->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$usuario = $_POST['usuario'];
|
||||||
|
$clave = $_POST['clave'];
|
||||||
|
$nombre = $_POST['nombre'];
|
||||||
|
$apellido = $_POST['apellido'];
|
||||||
|
$dni = $_POST['dni'];
|
||||||
|
$telefono = $_POST['telefono'];
|
||||||
|
$correo = $_POST['correo'];
|
||||||
|
$rol = $_POST['rol'];
|
||||||
|
|
||||||
|
$sql = "INSERT INTO usuarios (DNI, Usuario, Contraseña, Rol, Nombre, Apellido, Correo, Telefono)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
|
$stmt = $conexion->prepare($sql);
|
||||||
|
$stmt->bind_param("isssssss", $dni, $usuario, $clave, $rol, $nombre, $apellido, $correo, $telefono);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo "Registro exitoso.";
|
||||||
|
} else {
|
||||||
|
echo "Error: " . $stmt->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['id_usuario'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Sesión no iniciada']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
$id = intval($_SESSION['id_usuario']);
|
||||||
|
$query = "SELECT ID_Usuario, Usuario, Rol, Nombre, Apellido, DNI, Correo, Telefono FROM usuarios WHERE ID_Usuario = ?";
|
||||||
|
$stmt = $conexion->prepare($query);
|
||||||
|
$stmt->bind_param('i', $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result && $row = $result->fetch_assoc()) {
|
||||||
|
echo json_encode(['success' => true, 'usuario' => $row]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Usuario no encontrado']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Método no permitido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idReserva = isset($_POST['id_reserva']) ? intval($_POST['id_reserva']) : 0;
|
||||||
|
|
||||||
|
if ($idReserva === 0 || !isset($_FILES['comprobante'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Faltan datos obligatorios.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['comprobante'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png'];
|
||||||
|
|
||||||
|
// Validar extensión
|
||||||
|
if (!in_array($ext, $allowed)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Formato no permitido. Solo JPG, JPEG o PNG.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Definir rutas
|
||||||
|
$nombreArchivo = "reserva_" . $idReserva . "_" . time() . "." . $ext;
|
||||||
|
$carpetaDestino = "C:\\xampp\\htdocs\\TP-Taller\\ProyectoTaller\\uploads\\comprobantes\\";
|
||||||
|
$rutaCompleta = $carpetaDestino . $nombreArchivo;
|
||||||
|
|
||||||
|
// Mover el archivo al servidor
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $rutaCompleta)) {
|
||||||
|
|
||||||
|
// Actualizar la base de datos con el nombre del archivo
|
||||||
|
$stmt = $conexion->prepare("UPDATE reservas SET Comprobante = ? WHERE ID_Reserva = ?");
|
||||||
|
$stmt->bind_param('si', $nombreArchivo, $idReserva);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->affected_rows > 0) {
|
||||||
|
|
||||||
|
// --- NOTIFICACIÓN AL ADMINISTRADOR DE CANCHAS ---
|
||||||
|
// 1. Buscamos el teléfono del usuario con rol 'cancha'
|
||||||
|
$resUser = $conexion->query("SELECT Telefono FROM usuarios WHERE Rol = 'cancha' LIMIT 1");
|
||||||
|
if ($resUser && $resUser->num_rows > 0) {
|
||||||
|
$admin = $resUser->fetch_assoc();
|
||||||
|
$telefonoAdmin = $admin['Telefono']; // Ej: "549343..."
|
||||||
|
|
||||||
|
// 2. Limpiamos el formato del teléfono del administrador
|
||||||
|
$telefonoLimpio = preg_replace('/[^0-9]/', '', $telefonoAdmin);
|
||||||
|
|
||||||
|
// 3. Armamos el mensaje con un link directo para que el admin vea la foto desde su celular
|
||||||
|
// Nota: Cuando pases el sistema a producción, reemplaza 'localhost' por tu IP pública o dominio
|
||||||
|
$urlFoto = "http://localhost/TP-Taller/ProyectoTaller/uploads/comprobantes/" . $nombreArchivo;
|
||||||
|
$mensaje = "Alerta: Se ha subido un comprobante para la Reserva #$idReserva. Ver foto aquí: $urlFoto";
|
||||||
|
|
||||||
|
// 4. Llamamos al bot de Python de forma asíncrona para no trabar la pantalla del cliente
|
||||||
|
$scriptPython = "C:\\xampp\\htdocs\\TP-Taller\\ProyectoTaller\\python\\Enviar_WSP.py";
|
||||||
|
$comando = "start /B python \"$scriptPython\" $telefonoLimpio \"$mensaje\"";
|
||||||
|
pclose(popen($comando, "r"));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Comprobante subido y administrador notificado.']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se encontró la reserva para actualizar.']);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudo guardar el archivo en el servidor.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conexion->close();
|
||||||
|
?>
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
require_once 'conexion.php';
|
||||||
|
|
||||||
|
// Rutas base de tu proyecto
|
||||||
|
$rutaProyecto = "C:\\xampp\\htdocs\\TP-Taller\\ProyectoTaller\\";
|
||||||
|
$carpetaDestino = $rutaProyecto . "backups\\";
|
||||||
|
$mysqldump = "C:\\xampp\\mysql\\bin\\mysqldump.exe";
|
||||||
|
$diasDeRetencion = 30;
|
||||||
|
|
||||||
|
// --- GENERACIÓN DEL BACKUP SQL ---
|
||||||
|
$fecha = date('Y-m-d_H-i-s');
|
||||||
|
$nombreArchivoSql = "bd_cap1tan_" . $fecha . ".sql";
|
||||||
|
$rutaSql = $carpetaDestino . $nombreArchivoSql;
|
||||||
|
|
||||||
|
$comando = "$mysqldump -h $host -u $user $db > \"$rutaSql\" 2>&1";
|
||||||
|
exec($comando, $salida, $resultado);
|
||||||
|
|
||||||
|
if ($resultado !== 0) {
|
||||||
|
echo "<h2 style='color: red;'>Error al generar la base de datos. Abortando.</h2>";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EMPAQUETADO ZIP ---
|
||||||
|
$nombreArchivoZip = "backup_completo_" . $fecha . ".zip";
|
||||||
|
$rutaZip = $carpetaDestino . $nombreArchivoZip;
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
|
||||||
|
if ($zip->open($rutaZip, ZipArchive::CREATE) === TRUE) {
|
||||||
|
|
||||||
|
// Guardamos la base de datos dentro del ZIP
|
||||||
|
$zip->addFile($rutaSql, $nombreArchivoSql);
|
||||||
|
|
||||||
|
// Guardamos el archivo de conexión
|
||||||
|
$rutaConexion = $rutaProyecto . "php\\conexion.php";
|
||||||
|
if (file_exists($rutaConexion)) {
|
||||||
|
$zip->addFile($rutaConexion, "php/conexion.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardamos el bot de Python
|
||||||
|
$rutaBot = $rutaProyecto . "python\\Enviar_WSP.py";
|
||||||
|
if (file_exists($rutaBot)) {
|
||||||
|
$zip->addFile($rutaBot, "python/Enviar_WSP.py");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardamos la carpeta uploads completa
|
||||||
|
$rutaUploads = $rutaProyecto . "uploads\\";
|
||||||
|
|
||||||
|
// Verificamos si la carpeta existe antes de intentar abrirla
|
||||||
|
if (is_dir($rutaUploads)) {
|
||||||
|
// El sabueso que busca todos los archivos dentro de la carpeta
|
||||||
|
$archivos = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($rutaUploads),
|
||||||
|
RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($archivos as $nombre => $archivo) {
|
||||||
|
// Saltamos las carpetas vacías o los directorios del sistema (. y ..)
|
||||||
|
if (!$archivo->isDir()) {
|
||||||
|
// Obtenemos la ruta real del archivo en Windows
|
||||||
|
$rutaReal = $archivo->getRealPath();
|
||||||
|
|
||||||
|
// Calculamos la ruta relativa para que adentro del ZIP quede ordenado en "uploads/comprobantes/foto.jpg"
|
||||||
|
$rutaRelativa = "uploads/" . substr($rutaReal, strlen($rutaUploads));
|
||||||
|
|
||||||
|
// Reemplazamos las barras invertidas de Windows por barras normales para el ZIP
|
||||||
|
$rutaRelativa = str_replace('\\', '/', $rutaRelativa);
|
||||||
|
|
||||||
|
// Añadimos el archivo al ZIP
|
||||||
|
$zip->addFile($rutaReal, $rutaRelativa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
// Borramos el archivo .sql suelto
|
||||||
|
unlink($rutaSql);
|
||||||
|
|
||||||
|
echo "<h2 style='color: green;'>¡Éxito! Backup COMPLETO generado correctamente.</h2>";
|
||||||
|
echo "<p>Ruta: <b>" . $rutaZip . "</b></p>";
|
||||||
|
} else {
|
||||||
|
echo "<h2 style='color: red;'>Error al intentar crear el archivo ZIP.</h2>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- AUTO-LIMPIEZA DE BACKUPS VIEJOS ---
|
||||||
|
$archivosZip = glob($carpetaDestino . "*.zip");
|
||||||
|
$fechaActual = time();
|
||||||
|
$borrados = 0;
|
||||||
|
|
||||||
|
foreach ($archivosZip as $archivo) {
|
||||||
|
if (is_file($archivo)) {
|
||||||
|
$diasDeAntiguedad = ($fechaActual - filemtime($archivo)) / (60 * 60 * 24);
|
||||||
|
if ($diasDeAntiguedad >= $diasDeRetencion) {
|
||||||
|
unlink($archivo);
|
||||||
|
$borrados++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<hr>";
|
||||||
|
if ($borrados > 0) {
|
||||||
|
echo "<p>Limpieza completada: Se eliminaron $borrados backup(s) viejo(s).</p>";
|
||||||
|
} else {
|
||||||
|
echo "<p>Mantenimiento al día: Ningún backup supera los $diasDeRetencion días.</p>";
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
$host = 'localhost';
|
||||||
|
$user = 'root';
|
||||||
|
$password = 'admin';
|
||||||
|
$db = 'complejo_cap1tan';
|
||||||
|
|
||||||
|
$conexion = new mysqli($host, $user, $password, $db);
|
||||||
|
|
||||||
|
if ($conexion->connect_error) {
|
||||||
|
die('Error de conexión: ' . $conexion->connect_error);
|
||||||
|
}
|
||||||
|
$conexion->set_charset('utf8');
|
||||||
|
?>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Configuración de Brevo SMTP
|
||||||
|
* Este archivo contiene las credenciales para enviar emails vía Brevo
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Credenciales de Brevo
|
||||||
|
define('BREVO_API_KEY', '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');
|
||||||
|
define('BREVO_API_URL', 'https://api.brevo.com/v3/smtp/email');
|
||||||
|
define('BREVO_FROM_EMAIL', 'lumicaela2.987@gmail.com');
|
||||||
|
define('BREVO_FROM_NAME', 'Complejo Cap1tan');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función para enviar emails usando API REST de Brevo
|
||||||
|
* Esta es más confiable que SMTP directo
|
||||||
|
*/
|
||||||
|
function enviarEmailBrevo($para, $asunto, $mensaje, $esHTML = false) {
|
||||||
|
try {
|
||||||
|
// Preparar el payload
|
||||||
|
$payload = [
|
||||||
|
'sender' => [
|
||||||
|
'name' => BREVO_FROM_NAME,
|
||||||
|
'email' => BREVO_FROM_EMAIL
|
||||||
|
],
|
||||||
|
'to' => [
|
||||||
|
[
|
||||||
|
'email' => $para,
|
||||||
|
'name' => $para
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'subject' => $asunto,
|
||||||
|
'htmlContent' => $esHTML ? $mensaje : nl2br(htmlspecialchars($mensaje)),
|
||||||
|
'textContent' => $mensaje
|
||||||
|
];
|
||||||
|
|
||||||
|
// Preparar headers
|
||||||
|
$headers = [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'api-key: ' . BREVO_API_KEY,
|
||||||
|
'Accept: application/json'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Inicializar cURL
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, BREVO_API_URL);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
|
||||||
|
// Ejecutar
|
||||||
|
$respuesta = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Verificar respuesta
|
||||||
|
if ($error) {
|
||||||
|
error_log('Brevo API Error: ' . $error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code >= 200 && $http_code < 300) {
|
||||||
|
error_log('Email enviado exitosamente a ' . $para);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
error_log('Brevo API HTTP ' . $http_code . ': ' . $respuesta);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('Brevo Exception: ' . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función fallback usando mail() nativa
|
||||||
|
*/
|
||||||
|
function enviarEmailFallback($para, $asunto, $mensaje) {
|
||||||
|
$headers = "From: " . BREVO_FROM_NAME . " <" . BREVO_FROM_EMAIL . ">\r\n";
|
||||||
|
$headers .= "Reply-To: " . BREVO_FROM_EMAIL . "\r\n";
|
||||||
|
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||||
|
|
||||||
|
return @mail($para, $asunto, $mensaje, $headers);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from urllib.parse import quote
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
# Recibimos los datos
|
||||||
|
telefono = sys.argv[1]
|
||||||
|
mensaje = sys.argv[2]
|
||||||
|
|
||||||
|
chrome_options = Options()
|
||||||
|
chrome_options.add_argument("user-data-dir=C:\\whatsapp_sesion")
|
||||||
|
# Activa el modo invisible
|
||||||
|
chrome_options.add_argument("--headless=new")
|
||||||
|
# Le damos un tamaño de pantalla virtual grande
|
||||||
|
chrome_options.add_argument("--window-size=1920,1080")
|
||||||
|
|
||||||
|
driver = webdriver.Chrome(options=chrome_options)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mensaje_encoded = quote(mensaje)
|
||||||
|
url = f"https://web.whatsapp.com/send?phone={telefono}&text={mensaje_encoded}"
|
||||||
|
|
||||||
|
driver.get(url)
|
||||||
|
wait = WebDriverWait(driver, 40)
|
||||||
|
|
||||||
|
# 1. Esperamos a que cargue el panel principal del chat
|
||||||
|
wait.until(EC.presence_of_element_located((By.XPATH, '//div[@id="main"]')))
|
||||||
|
|
||||||
|
# 2. Le damos 3 segundos para que WhatsApp termine de procesar el texto en la caja
|
||||||
|
time.sleep(15)
|
||||||
|
|
||||||
|
# 3. Buscamos la caja de texto directamente (está en el footer del chat)
|
||||||
|
caja_texto = wait.until(EC.presence_of_element_located((By.XPATH, '//footer//div[@contenteditable="true"]')))
|
||||||
|
|
||||||
|
# 4. Le enviamos la tecla "Enter" directamente a esa caja de texto
|
||||||
|
caja_texto.send_keys(Keys.ENTER)
|
||||||
|
|
||||||
|
# Esperamos 4 segundos para que aparezca el tilde gris antes de cerrar
|
||||||
|
time.sleep(7)
|
||||||
|
print("Mensaje enviado con éxito")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al enviar: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
SELECT SUM(p.Precio_Venta * vp.Cantidad) AS Ingreso_Bruto_Total
|
||||||
|
FROM ventas_productos vp
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto;
|
||||||
|
|
||||||
|
SELECT SUM(p.Precio_Compra * vp.Cantidad) AS Costo_Total
|
||||||
|
FROM ventas_productos vp
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto;
|
||||||
|
|
||||||
|
SELECT SUM((p.Precio_Venta - p.Precio_Compra) * vp.Cantidad) AS Ganancia_Bruta
|
||||||
|
FROM ventas_productos vp
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
v.Fecha,
|
||||||
|
SUM(p.Precio_Venta * vp.Cantidad) AS Ingreso_Bruto,
|
||||||
|
SUM(p.Precio_Compra * vp.Cantidad) AS Costo,
|
||||||
|
SUM((p.Precio_Venta - p.Precio_Compra) * vp.Cantidad) AS Ganancia_Bruta
|
||||||
|
FROM ventas v
|
||||||
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
GROUP BY v.Fecha
|
||||||
|
ORDER BY v.Fecha DESC;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(v.Fecha, '%Y-%m') AS Mes,
|
||||||
|
SUM(p.Precio_Venta * vp.Cantidad) AS Ingreso_Bruto,
|
||||||
|
SUM(p.Precio_Compra * vp.Cantidad) AS Costo,
|
||||||
|
SUM((p.Precio_Venta - p.Precio_Compra) * vp.Cantidad) AS Ganancia_Bruta
|
||||||
|
FROM ventas v
|
||||||
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
GROUP BY Mes
|
||||||
|
ORDER BY Mes DESC;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
g.Periodo_Desde,
|
||||||
|
g.Periodo_Hasta,
|
||||||
|
COALESCE(ingresos.Mes, DATE_FORMAT(g.Periodo_Desde, '%Y-%m')) AS Mes,
|
||||||
|
COALESCE(ingresos.Ingreso_Bruto, 0) AS Ingreso_Bruto,
|
||||||
|
COALESCE(ingresos.Costo, 0) AS Costo,
|
||||||
|
COALESCE(ingresos.Ganancia_Bruta, 0) AS Ganancia_Bruta,
|
||||||
|
(g.Sueldo + g.Luz + g.Agua + g.Impuestos) AS Gastos_Totales,
|
||||||
|
COALESCE(ingresos.Ganancia_Bruta, 0) - (g.Sueldo + g.Luz + g.Agua + g.Impuestos) AS Ganancia_Neta
|
||||||
|
FROM gastos g
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(v.Fecha, '%Y-%m') AS Mes,
|
||||||
|
MIN(v.Fecha) AS Periodo_Desde,
|
||||||
|
MAX(v.Fecha) AS Periodo_Hasta,
|
||||||
|
SUM(p.Precio_Venta * vp.Cantidad) AS Ingreso_Bruto,
|
||||||
|
SUM(p.Precio_Compra * vp.Cantidad) AS Costo,
|
||||||
|
SUM((p.Precio_Venta - p.Precio_Compra) * vp.Cantidad) AS Ganancia_Bruta
|
||||||
|
FROM ventas v
|
||||||
|
JOIN ventas_productos vp ON v.ID_Venta = vp.ID_Venta
|
||||||
|
JOIN productos p ON vp.ID_Producto = p.ID_Producto
|
||||||
|
GROUP BY Mes
|
||||||
|
) ingresos
|
||||||
|
ON g.Periodo_Desde <= ingresos.Periodo_Hasta
|
||||||
|
AND (g.Periodo_Hasta IS NULL OR g.Periodo_Hasta >= ingresos.Periodo_Desde)
|
||||||
|
ORDER BY ingresos.Mes DESC, g.Periodo_Desde;
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
INSERT INTO proveedores (ID_Proveedor, Telefono, Nombre) VALUES
|
||||||
|
(1, '+54-11-1111-1111', 'AG Export'),
|
||||||
|
(2, '+54-11-2222-2222', 'Gastro Service'),
|
||||||
|
(3, '+54-11-3333-3333', 'Freixenet'),
|
||||||
|
(4, '+54-11-4444-4444', 'Supper Foods'),
|
||||||
|
(5, '+54-11-5555-5555', 'Bebidas Buenos Aires');
|
||||||
|
|
||||||
|
INSERT INTO usuarios (ID_Usuario, DNI, Usuario, `Contraseña`, Rol, Nombre, Apellido, Correo, Telefono) VALUES
|
||||||
|
(1, '11111111', 'juanp', 'admin', 'admin', 'Juan', 'Pérez', 'juanp@example.com', '+54-9-351-111-111'),
|
||||||
|
(2, '22222222', 'mariag', 'cancha', 'cancha', 'María', 'Gómez', 'mariag@example.com', '+54-9-351-222-222'),
|
||||||
|
(3, '33333333', 'carlosl', 'cantina', 'cantina', 'Carlos', 'López', 'carlosl@example.com', '+54-9-351-333-333'),
|
||||||
|
(4, '44444444', 'laurac', 'cantina', 'cantina', 'Laura', 'Castaño', 'laurac@example.com', '+54-9-351-444-555'),
|
||||||
|
(5, '55555555', 'robert', 'admin', 'admin', 'Roberto', 'Molina', 'robert@example.com', '+54-9-351-666-777');
|
||||||
|
|
||||||
|
INSERT INTO productos (ID_Producto, Precio_Venta, Precio_Compra, Descripcion, Stock_Disponible) VALUES
|
||||||
|
(1, 2000.00, 1500.00, 'Coca-Cola 500ml', 100),
|
||||||
|
(2, 2500.00, 2000.00, 'Pritty 3l', 50),
|
||||||
|
(3, 1600.00, 1100.00, 'Quilmes Clasica Lata', 80),
|
||||||
|
(4, 1200.00, 1000.00, 'Corona Extra Rubia Lata', 10),
|
||||||
|
(5, 1500.00, 1200.00, 'Pepsi 500ml', 200),
|
||||||
|
(6, 500.00, 250.00, 'Sprite 250ml', 90),
|
||||||
|
(7, 1800.00, 1200.00, 'Fanta 600ml', 180),
|
||||||
|
(8, 1000.00, 600.00, 'Agua Mineral 600ml', 220),
|
||||||
|
(9, 1700.00, 1300.00, 'Heineken Lata', 70),
|
||||||
|
(10, 1400.00, 900.00, 'Schweppes Tonica 500ml', 130),
|
||||||
|
(11, 1900.00, 1100.00, 'Agua Tónica 330ml', 140),
|
||||||
|
(12, 1700.00, 1000.00, 'Jugo de Naranja 500ml', 90),
|
||||||
|
(13, 1300.00, 700.00, 'Alfajor Doble', 75),
|
||||||
|
(14, 2800.00, 1700.00, 'Hamburguesa con Queso', 55),
|
||||||
|
(15, 900.00, 500.00, 'Empanada de Carne', 120);
|
||||||
|
|
||||||
|
INSERT INTO proveedores_productos (ID_Proveedor, ID_Producto) VALUES
|
||||||
|
(1, 1),
|
||||||
|
(1, 2),
|
||||||
|
(1, 5),
|
||||||
|
(2, 3),
|
||||||
|
(2, 6),
|
||||||
|
(2, 12),
|
||||||
|
(3, 4),
|
||||||
|
(3, 9),
|
||||||
|
(3, 13),
|
||||||
|
(4, 10),
|
||||||
|
(4, 11),
|
||||||
|
(4, 14),
|
||||||
|
(5, 7),
|
||||||
|
(5, 8),
|
||||||
|
(5, 15);
|
||||||
|
|
||||||
|
INSERT INTO clientes (ID_Cliente, DNI, Nombre, Telefono) VALUES
|
||||||
|
(1, '44444444', 'Lucas Díaz', '+54-9-351-444-444'),
|
||||||
|
(2, '55555555', 'Carla Romero', '+54-9-351-555-555'),
|
||||||
|
(3, '66666666', 'Diego Suárez', '+54-9-351-666-666'),
|
||||||
|
(4, '77777777', 'Matías Suárez', '+54-9-351-777-777'),
|
||||||
|
(5, '88888888', 'Sofía Martínez', '+54-9-351-888-888'),
|
||||||
|
(6, '99999999', 'Josefa Aguirre', '+54-9-351-999-999'),
|
||||||
|
(7, '10101010', 'Valeria Ríos', '+54-9-351-101-010'),
|
||||||
|
(8, '12121212', 'Marcos Alcántara', '+54-9-351-121-212'),
|
||||||
|
(9, '13131313', 'Nadia Funes', '+54-9-351-131-313'),
|
||||||
|
(10, '14141414', 'Hernán Ortiz', '+54-9-351-141-414'),
|
||||||
|
(11, '15151515', 'Jimena Paredes', '+54-9-351-151-515'),
|
||||||
|
(12, '16161616', 'Pablo Duarte', '+54-9-351-161-616');
|
||||||
|
|
||||||
|
INSERT INTO cupones (ID_Cupon, Cantidad_Reservas, Cantidad_Faltas, Tipo, Ultima_Fecha, Descuento, ID_Cliente) VALUES
|
||||||
|
(1, 5, 0, 'F', '2026-06-01 10:00:00', 0.10, 1),
|
||||||
|
(2, 3, 1, 'P', '2026-06-05 12:00:00', 0.05, 2),
|
||||||
|
(3, 1, 0, 'F', '2026-06-08 17:00:00', 0.08, 3),
|
||||||
|
(4, 4, 0, 'P', '2026-06-10 14:00:00', 0.12, 4),
|
||||||
|
(5, 2, 0, 'F', '2026-06-12 09:00:00', 0.07, 5),
|
||||||
|
(6, 6, 1, 'P', '2026-06-15 20:00:00', 0.15, 6),
|
||||||
|
(7, 5, 0, 'F', '2026-06-18 19:00:00', 0.10, 7),
|
||||||
|
(8, 4, 0, 'P', '2026-06-20 16:30:00', 0.12, 8);
|
||||||
|
|
||||||
|
INSERT INTO canchas (ID_Cancha, Tipo, Precio, Duracion, Cant_Canchas, Cant_Reservas_Cupon, Descuento_Cupon, Cant_Faltas, Duracion_Cupon) VALUES
|
||||||
|
(1, 'F', 3000.00, 60, 2, 5, 0.25, 2, 7),
|
||||||
|
(2, 'P', 2500.00, 60, 2, 5, 0.20, 2, 7),
|
||||||
|
(3, 'Q', 1500.00, 120, 1, 0, 0.00, 0, 0);
|
||||||
|
|
||||||
|
INSERT INTO horarios (Dia, Hora_Apertura, Hora_Cierre) VALUES
|
||||||
|
('Lunes a Viernes', '08:00:00', '23:00:00'),
|
||||||
|
('Sábado', '09:00:00', '22:00:00'),
|
||||||
|
('Domingo', '09:00:00', '22:00:00');
|
||||||
|
|
||||||
|
INSERT INTO gastos (Periodo_Desde, Periodo_Hasta, Sueldo, Luz, Agua, Impuestos) VALUES
|
||||||
|
('2026-04-01', '2026-04-30', 250000, 32000, 12000, 10500),
|
||||||
|
('2026-05-01', '2026-05-31', 270000, 35000, 13000, 11000),
|
||||||
|
('2026-06-01', '2026-06-29', 285000, 38000, 14000, 11500);
|
||||||
|
|
||||||
|
INSERT INTO alertas_stock (ID_Producto, Mensaje) VALUES
|
||||||
|
(4, 'Stock reducido a 9 unidades.'),
|
||||||
|
(9, 'Stock reducido a 7 unidades.'),
|
||||||
|
(13, 'Stock bajo para producto de almacén.'),
|
||||||
|
(15, 'Stock crítico en empanadas. Reponer pronto.');
|
||||||
|
|
||||||
|
INSERT INTO ventas (ID_Venta, Fecha, Monto, ID_Usuario) VALUES
|
||||||
|
(1, '2026-06-01 10:15:00', 12500.00, 2),
|
||||||
|
(2, '2026-06-02 12:30:00', 8700.00, 2),
|
||||||
|
(3, '2026-06-04 15:45:00', 15400.00, 2),
|
||||||
|
(4, '2026-06-06 13:20:00', 10250.00, 2),
|
||||||
|
(5, '2026-06-08 17:05:00', 13600.00, 2),
|
||||||
|
(6, '2026-06-10 19:10:00', 9400.00, 2),
|
||||||
|
(7, '2026-06-12 09:35:00', 11200.00, 1),
|
||||||
|
(8, '2026-06-14 16:00:00', 7600.00, 2),
|
||||||
|
(9, '2026-06-16 22:15:00', 13300.00, 1),
|
||||||
|
(10, '2026-06-18 18:45:00', 11850.00, 1),
|
||||||
|
(11, '2026-06-20 14:00:00', 9800.00, 2),
|
||||||
|
(12, '2026-06-22 20:30:00', 14700.00, 2),
|
||||||
|
(13, '2026-06-24 11:20:00', 8200.00, 2),
|
||||||
|
(14, '2026-06-25 13:30:00', 7500.00, 2),
|
||||||
|
(15, '2026-06-26 17:40:00', 16600.00, 1),
|
||||||
|
(16, '2026-06-27 18:25:00', 13200.00, 2),
|
||||||
|
(17, '2026-06-28 16:50:00', 10150.00, 2),
|
||||||
|
(18, '2026-06-29 11:05:00', 8900.00, 1),
|
||||||
|
(19, '2026-06-30 12:40:00', 9400.00, 5),
|
||||||
|
(20, '2026-06-30 18:15:00', 12000.00, 4),
|
||||||
|
(21, '2026-06-30 20:30:00', 13200.00, 4),
|
||||||
|
(22, '2026-06-30 21:15:00', 8700.00, 5);
|
||||||
|
|
||||||
|
INSERT INTO ordenes_compra (ID_Orden, ID_Proveedor, ID_Usuario, Fecha) VALUES
|
||||||
|
(1, 1, 2, '2026-06-01 09:00:00'),
|
||||||
|
(2, 2, 2, '2026-06-03 10:30:00'),
|
||||||
|
(3, 3, 2, '2026-06-05 08:45:00'),
|
||||||
|
(4, 1, 2, '2026-06-07 09:15:00'),
|
||||||
|
(5, 2, 2, '2026-06-09 11:00:00'),
|
||||||
|
(6, 3, 2, '2026-06-11 08:30:00'),
|
||||||
|
(7, 1, 2, '2026-06-13 12:00:00'),
|
||||||
|
(8, 2, 2, '2026-06-15 09:45:00'),
|
||||||
|
(9, 3, 2, '2026-06-17 10:20:00'),
|
||||||
|
(10, 1, 2, '2026-06-19 12:15:00'),
|
||||||
|
(11, 4, 2, '2026-06-21 09:05:00'),
|
||||||
|
(12, 5, 4, '2026-06-23 10:15:00'),
|
||||||
|
(13, 4, 4, '2026-06-24 08:50:00'),
|
||||||
|
(14, 5, 2, '2026-06-25 11:30:00');
|
||||||
|
|
||||||
|
INSERT INTO reservas (ID_Reserva, Fecha_Hora, Monto, Numero, Estado, ID_Cliente, ID_Cancha, ID_Usuario) VALUES
|
||||||
|
(1, '2026-06-01 18:00:00', 3000.00, 1, 1, 1, 1, 3),
|
||||||
|
(2, '2026-06-02 19:00:00', 2500.00, 1, 1, 2, 2, 3),
|
||||||
|
(3, '2026-06-03 20:00:00', 3000.00, 2, 1, 3, 1, 3),
|
||||||
|
(4, '2026-06-04 17:00:00', 2500.00, 2, 1, 1, 2, 3),
|
||||||
|
(5, '2026-06-05 17:00:00', 1500.00, 1, 1, 2, 3, 3),
|
||||||
|
(6, '2026-06-06 16:30:00', 3000.00, 1, 1, 1, 1, 1),
|
||||||
|
(7, '2026-06-07 18:30:00', 2500.00, 2, 1, 3, 2, 3),
|
||||||
|
(8, '2026-06-08 17:30:00', 3000.00, 1, 1, 2, 1, 3),
|
||||||
|
(9, '2026-06-09 17:30:00', 1500.00, 1, 1, 3, 3, 3),
|
||||||
|
(10, '2026-06-10 19:30:00', 2500.00, 1, 1, 1, 2, 1),
|
||||||
|
(11, '2026-06-11 18:00:00', 3000.00, 2, 1, 2, 1, 3),
|
||||||
|
(12, '2026-06-12 21:00:00', 1500.00, 1, 1, 3, 3, 1),
|
||||||
|
(13, '2026-06-13 16:00:00', 2500.00, 1, 1, 1, 2, 3),
|
||||||
|
(14, '2026-06-14 20:30:00', 3000.00, 2, 1, 2, 1, 3),
|
||||||
|
(15, '2026-06-15 18:45:00', 1500.00, 1, 1, 3, 3, 3),
|
||||||
|
(16, '2026-06-16 17:00:00', 2800.00, 1, 2, 4, 2, 3),
|
||||||
|
(17, '2026-06-17 19:15:00', 2200.00, 2, 1, 5, 1, 3),
|
||||||
|
(18, '2026-06-18 15:30:00', 3200.00, 1, 1, 6, 1, 3),
|
||||||
|
(19, '2026-06-20 18:45:00', 3000.00, 2, 1, 4, 2, 3),
|
||||||
|
(20, '2026-06-22 16:20:00', 1700.00, 1, 1, 5, 2, 3),
|
||||||
|
(21, '2026-06-23 19:00:00', 1500.00, 1, 1, 8, 3, 3),
|
||||||
|
(22, '2026-06-24 20:00:00', 1500.00, 1, 0, 9, 3, 3),
|
||||||
|
(23, '2026-06-25 19:30:00', 1500.00, 1, 1, 10, 3, 1),
|
||||||
|
(24, '2026-06-26 21:00:00', 1500.00, 1, 2, 11, 3, 1);
|
||||||
|
|
||||||
|
INSERT INTO ordenes_productos (ID_Orden, ID_Producto, Cantidad) VALUES
|
||||||
|
(1, 1, 50),
|
||||||
|
(1, 4, 10),
|
||||||
|
(2, 2, 30),
|
||||||
|
(2, 5, 100),
|
||||||
|
(3, 3, 60),
|
||||||
|
(3, 6, 80),
|
||||||
|
(4, 1, 40),
|
||||||
|
(4, 3, 35),
|
||||||
|
(5, 2, 25),
|
||||||
|
(5, 6, 50),
|
||||||
|
(6, 4, 18),
|
||||||
|
(6, 5, 70),
|
||||||
|
(7, 1, 30),
|
||||||
|
(7, 2, 20),
|
||||||
|
(8, 3, 45),
|
||||||
|
(8, 6, 60),
|
||||||
|
(9, 7, 60),
|
||||||
|
(9, 8, 40),
|
||||||
|
(10, 9, 20),
|
||||||
|
(10, 10, 30),
|
||||||
|
(11, 11, 20),
|
||||||
|
(11, 13, 25),
|
||||||
|
(12, 12, 30),
|
||||||
|
(12, 14, 15),
|
||||||
|
(13, 1, 40),
|
||||||
|
(13, 15, 50),
|
||||||
|
(14, 5, 40),
|
||||||
|
(14, 8, 70);
|
||||||
|
|
||||||
|
INSERT INTO ventas_productos (ID_Venta, ID_Producto, Cantidad) VALUES
|
||||||
|
(1, 1, 12),
|
||||||
|
(1, 2, 5),
|
||||||
|
(2, 3, 8),
|
||||||
|
(2, 5, 10),
|
||||||
|
(3, 4, 5),
|
||||||
|
(3, 6, 7),
|
||||||
|
(4, 1, 8),
|
||||||
|
(4, 3, 7),
|
||||||
|
(5, 2, 6),
|
||||||
|
(5, 5, 14),
|
||||||
|
(6, 4, 3),
|
||||||
|
(6, 6, 10),
|
||||||
|
(7, 1, 10),
|
||||||
|
(7, 2, 4),
|
||||||
|
(8, 3, 9),
|
||||||
|
(8, 5, 11),
|
||||||
|
(9, 6, 15),
|
||||||
|
(9, 7, 8),
|
||||||
|
(10, 2, 7),
|
||||||
|
(10, 4, 2),
|
||||||
|
(11, 3, 12),
|
||||||
|
(11, 5, 9),
|
||||||
|
(12, 1, 11),
|
||||||
|
(12, 6, 13),
|
||||||
|
(13, 7, 10),
|
||||||
|
(13, 8, 6),
|
||||||
|
(14, 9, 8),
|
||||||
|
(14, 5, 7),
|
||||||
|
(15, 10, 6),
|
||||||
|
(15, 1, 4),
|
||||||
|
(16, 2, 8),
|
||||||
|
(16, 4, 9),
|
||||||
|
(17, 3, 4),
|
||||||
|
(17, 9, 5),
|
||||||
|
(18, 5, 10),
|
||||||
|
(18, 8, 7),
|
||||||
|
(19, 11, 10),
|
||||||
|
(19, 13, 5),
|
||||||
|
(20, 12, 12),
|
||||||
|
(20, 14, 6),
|
||||||
|
(21, 15, 15),
|
||||||
|
(21, 8, 10),
|
||||||
|
(22, 1, 9),
|
||||||
|
(22, 11, 8);
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
CREATE TABLE proveedores (
|
||||||
|
ID_Proveedor INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Telefono VARCHAR(20),
|
||||||
|
Nombre VARCHAR(20)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE usuarios (
|
||||||
|
ID_Usuario INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Usuario VARCHAR(50) NOT NULL,
|
||||||
|
`Contraseña` VARCHAR(100) NOT NULL,
|
||||||
|
Rol ENUM('admin', 'cancha', 'cantina') NOT NULL,
|
||||||
|
Nombre VARCHAR(50),
|
||||||
|
Apellido VARCHAR(50),
|
||||||
|
DNI VARCHAR(10),
|
||||||
|
Correo VARCHAR(100),
|
||||||
|
Telefono VARCHAR(20)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ventas (
|
||||||
|
ID_Venta INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
Monto DECIMAL(10,2) NULL,
|
||||||
|
ID_Usuario INT,
|
||||||
|
CONSTRAINT fk_venta_usuario FOREIGN KEY (ID_Usuario) REFERENCES usuarios(ID_Usuario)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ordenes_compra (
|
||||||
|
ID_Orden INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
ID_Proveedor INT,
|
||||||
|
ID_Usuario INT,
|
||||||
|
Fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
Estado INT DEFAULT 0, -- 0=Pendiente, 1=Aprobada, 2=Denegada, 3=Confirmada
|
||||||
|
CONSTRAINT fk_oc_proveedor FOREIGN KEY (ID_Proveedor) REFERENCES proveedores(ID_Proveedor),
|
||||||
|
CONSTRAINT fk_oc_usuario FOREIGN KEY (ID_Usuario) REFERENCES usuarios(ID_Usuario)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE productos (
|
||||||
|
ID_Producto INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Precio_Venta DECIMAL(10,2),
|
||||||
|
Precio_Compra DECIMAL(10,2),
|
||||||
|
Descripcion VARCHAR(100),
|
||||||
|
Stock_Disponible INT,
|
||||||
|
Estado INT DEFAULT TRUE -- 0=Inactivo, 1=Activo
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE clientes (
|
||||||
|
ID_Cliente INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
DNI VARCHAR(10),
|
||||||
|
Nombre VARCHAR(50),
|
||||||
|
Telefono VARCHAR(20)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE gastos (
|
||||||
|
ID_Gasto INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Periodo_Desde DATE NOT NULL,
|
||||||
|
Periodo_Hasta DATE DEFAULT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ordenes_productos (
|
||||||
|
ID_Orden INT NOT NULL,
|
||||||
|
ID_Producto INT NOT NULL,
|
||||||
|
Cantidad INT,
|
||||||
|
PRIMARY KEY (ID_Orden, ID_Producto),
|
||||||
|
CONSTRAINT fk_op_orden FOREIGN KEY (ID_Orden) REFERENCES ordenes_compra(ID_Orden),
|
||||||
|
CONSTRAINT fk_op_producto FOREIGN KEY (ID_Producto) REFERENCES productos(ID_Producto)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ventas_productos (
|
||||||
|
ID_Venta INT NOT NULL,
|
||||||
|
ID_Producto INT NOT NULL,
|
||||||
|
Cantidad INT,
|
||||||
|
PRIMARY KEY (ID_Venta, ID_Producto),
|
||||||
|
CONSTRAINT fk_vp_venta FOREIGN KEY (ID_Venta) REFERENCES ventas(ID_Venta),
|
||||||
|
CONSTRAINT fk_vp_producto FOREIGN KEY (ID_Producto) REFERENCES productos(ID_Producto)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE proveedores_productos (
|
||||||
|
ID_Proveedor INT NOT NULL,
|
||||||
|
ID_Producto INT NOT NULL,
|
||||||
|
PRIMARY KEY (ID_Proveedor, ID_Producto),
|
||||||
|
CONSTRAINT fk_pp_proveedor FOREIGN KEY (ID_Proveedor) REFERENCES proveedores(ID_Proveedor),
|
||||||
|
CONSTRAINT fk_pp_producto FOREIGN KEY (ID_Producto) REFERENCES productos(ID_Producto)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE alertas_stock (
|
||||||
|
ID_Alerta INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
ID_Producto INT,
|
||||||
|
Mensaje VARCHAR(255),
|
||||||
|
Fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_alerta_producto FOREIGN KEY (ID_Producto) REFERENCES productos(ID_Producto)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cupones (
|
||||||
|
ID_Cupon INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Cantidad_Reservas INT,
|
||||||
|
Cantidad_Faltas INT,
|
||||||
|
Tipo VARCHAR(1),
|
||||||
|
Ultima_Fecha DATETIME,
|
||||||
|
Descuento DECIMAL(4,2),
|
||||||
|
ID_Cliente INT,
|
||||||
|
CONSTRAINT fk_cupon_cliente FOREIGN KEY (ID_Cliente) REFERENCES clientes(ID_Cliente)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE canchas (
|
||||||
|
ID_Cancha INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Tipo VARCHAR(1), -- F=Futbol, P=Padel, Q=Quincho
|
||||||
|
Precio DECIMAL(10,2),
|
||||||
|
Duracion INT, -- en minutos
|
||||||
|
Cant_Canchas INT,
|
||||||
|
Cant_Reservas_Cupon INT,
|
||||||
|
Descuento_Cupon DECIMAL(4,2),
|
||||||
|
Cant_Faltas INT,
|
||||||
|
Duracion_Cupon INT -- en dias
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE reservas (
|
||||||
|
ID_Reserva INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Fecha_Hora DATETIME,
|
||||||
|
Monto DECIMAL(10,2),
|
||||||
|
Numero INT,
|
||||||
|
Estado INT, -- 0=No Pagado, 1=Pagado, 2=Cancelado
|
||||||
|
Descuento DECIMAL(4,2),
|
||||||
|
Comprobante VARCHAR(255),
|
||||||
|
ID_Cliente INT,
|
||||||
|
ID_Cancha INT,
|
||||||
|
ID_Usuario INT,
|
||||||
|
CONSTRAINT fk_reserva_cliente FOREIGN KEY (ID_Cliente) REFERENCES clientes(ID_Cliente),
|
||||||
|
CONSTRAINT fk_reserva_cancha FOREIGN KEY (ID_Cancha) REFERENCES canchas(ID_Cancha),
|
||||||
|
CONSTRAINT fk_reserva_usuario FOREIGN KEY (ID_Usuario) REFERENCES usuarios(ID_Usuario)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE horarios (
|
||||||
|
ID_Horario INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Dia ENUM('Lunes a Viernes', 'Sábado', 'Domingo'),
|
||||||
|
Hora_Apertura TIME,
|
||||||
|
Hora_Cierre TIME
|
||||||
|
);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
SHOW TRIGGERS FROM complejo_cap1tan;
|
||||||
|
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER alerta_stock_bajo
|
||||||
|
AFTER UPDATE ON productos FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.Stock_Disponible < 10 AND OLD.Stock_Disponible >= 10 THEN
|
||||||
|
INSERT INTO alertas_stock (ID_Producto, Mensaje)
|
||||||
|
VALUES (NEW.ID_Producto, CONCAT('Stock reducido a ', NEW.Stock_Disponible, ' unidades.'));
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
// DELIMITER ;
|
||||||
|
|
||||||
|
-- Setear un producto por debajo de 10
|
||||||
|
-- UPDATE productos SET Stock_Disponible = 100 WHERE ID_Producto = 1;
|
||||||
|
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER actualizar_stock_venta
|
||||||
|
AFTER INSERT ON ventas_productos FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE productos
|
||||||
|
SET Stock_Disponible = Stock_Disponible - NEW.Cantidad
|
||||||
|
WHERE ID_Producto = NEW.ID_Producto;
|
||||||
|
END;
|
||||||
|
// DELIMITER ;
|
||||||
|
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER actualizar_stock_orden_compra
|
||||||
|
AFTER UPDATE ON ordenes_compra FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
-- Si la orden cambió a estado aprobado y antes no lo estaba
|
||||||
|
IF NEW.Estado = TRUE AND OLD.Estado = FALSE THEN
|
||||||
|
INSERT INTO alertas_stock (ID_Producto, Mensaje)
|
||||||
|
SELECT op.ID_Producto, CONCAT('Orden de compra aprobada. Se sumaron ', op.Cantidad, ' unidades al stock.')
|
||||||
|
FROM ordenes_productos op
|
||||||
|
WHERE op.ID_Orden = NEW.ID_Orden;
|
||||||
|
|
||||||
|
-- Aumentar el stock
|
||||||
|
UPDATE productos p
|
||||||
|
JOIN ordenes_productos op ON p.ID_Producto = op.ID_Producto
|
||||||
|
SET p.Stock_Disponible = p.Stock_Disponible + op.Cantidad
|
||||||
|
WHERE op.ID_Orden = NEW.ID_Orden;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
// DELIMITER ;
|
||||||
Reference in New Issue
Block a user