asdadsasd PK -\ԟ), , api.phpnu [ false, 'message' => $e->getMessage() ]); } /** * Manejar petición de pedidos */ function handleOrdersRequest($api, $cache) { $page = intval($_GET['page'] ?? 1); $cache_key = "orders_page_{$page}"; // Intentar obtener del cache $cached_data = $cache->get($cache_key); if ($cached_data !== false) { echo json_encode([ 'success' => true, 'data' => $cached_data, 'cached' => true ]); return; } // Obtener pedidos frescos de la API $orders = $api->getCompletedOrdersByProduct(IGLESIAS_PRODUCT_ID, $page); // Procesar y formatear los pedidos $processed_orders = []; foreach ($orders as $order) { $church_data = extractChurchData($order); $processed_orders[] = [ 'id' => $order['id'], 'number' => $order['number'], 'status' => $order['status'], 'total' => floatval($order['total']), 'date_created' => $order['date_created'], 'church_name' => $church_data['church_name'], 'pastor_name' => $church_data['pastor_name'], 'billing_email' => $order['billing']['email'], 'billing_phone' => $order['billing']['phone'], 'total_churches' => $church_data['total_churches'] ]; } // Guardar en cache $cache->set($cache_key, $processed_orders); echo json_encode([ 'success' => true, 'data' => $processed_orders, 'cached' => false ]); } /** * Manejar petición de detalles de pedido */ function handleOrderDetailsRequest($api, $cache) { $order_id = intval($_GET['id'] ?? 0); if (!$order_id) { throw new Exception('ID de pedido requerido'); } $cache_key = "order_details_{$order_id}"; // Intentar obtener del cache $cached_data = $cache->get($cache_key); if ($cached_data !== false) { echo json_encode([ 'success' => true, 'data' => $cached_data, 'cached' => true ]); return; } // Obtener detalles del pedido $order = $api->getOrderDetails($order_id); $church_data = extractChurchData($order); $participation_details = extractParticipationDetails($order); $order_details = [ 'id' => $order['id'], 'number' => $order['number'], 'status' => $order['status'], 'total' => floatval($order['total']), 'date_created' => $order['date_created'], 'church_name' => $church_data['church_name'], 'pastor_name' => $church_data['pastor_name'], 'billing_email' => $order['billing']['email'], 'billing_phone' => $order['billing']['phone'], 'billing_address' => formatAddress($order['billing']), 'total_churches' => $church_data['total_churches'], 'participation_details' => $participation_details ]; // Guardar en cache $cache->set($cache_key, $order_details); echo json_encode([ 'success' => true, 'data' => $order_details, 'cached' => false ]); } /** * Manejar petición de estadísticas */ function handleStatsRequest($api, $cache) { $cache_key = 'order_stats'; // Intentar obtener del cache $cached_data = $cache->get($cache_key); if ($cached_data !== false) { echo json_encode([ 'success' => true, 'data' => $cached_data, 'cached' => true ]); return; } // Obtener estadísticas frescas $stats = $api->getOrderStats(IGLESIAS_PRODUCT_ID); // Guardar en cache por más tiempo (10 minutos) $cache->set($cache_key, $stats, 600); echo json_encode([ 'success' => true, 'data' => $stats, 'cached' => false ]); } /** * Extraer datos de la iglesia de los metadatos del pedido */ function extractChurchData($order) { $church_name = ''; $pastor_name = ''; $total_churches = 0; foreach ($order['line_items'] as $item) { if ($item['product_id'] == IGLESIAS_PRODUCT_ID) { foreach ($item['meta_data'] as $meta) { switch ($meta['key']) { case 'Nombre de la Iglesia/Ministerio': $church_name = $meta['value']; break; case 'Pastor / Representante': $pastor_name = $meta['value']; break; case 'Número de Iglesias': $total_churches = intval($meta['value']); break; } } break; } } return [ 'church_name' => $church_name, 'pastor_name' => $pastor_name, 'total_churches' => $total_churches ]; } /** * Extraer detalles de participación económica */ function extractParticipationDetails($order) { $details = []; foreach ($order['line_items'] as $item) { if ($item['product_id'] == IGLESIAS_PRODUCT_ID) { foreach ($item['meta_data'] as $meta) { // Buscar metadatos específicos de cada iglesia (formato: "Iglesia 1", "Iglesia 2", etc.) if (preg_match('/^Iglesia (\d+)$/', $meta['key'], $matches)) { $church_number = $matches[1]; $description = $meta['value']; $amount = extractAmountFromString($description); $details[] = [ 'church' => "Iglesia {$church_number}", 'description' => $description, 'amount' => $amount ]; } // También buscar el metadato "Participación Económica" como fallback elseif ($meta['key'] === 'Participación Económica') { $lines = explode("\n", $meta['value']); foreach ($lines as $line) { $line = trim($line); if (!empty($line) && strpos($line, ':') !== false) { $parts = explode(':', $line, 2); $church = trim($parts[0]); $desc = trim($parts[1]); $amount = extractAmountFromString($desc); $details[] = [ 'church' => $church, 'description' => $desc, 'amount' => $amount ]; } } } } break; } } return $details; } /** * Extraer cantidad numérica de una cadena */ function extractAmountFromString($string) { // Debug para ver qué estamos procesando error_log("Extracting amount from: " . $string); // Buscar patrones como "150€", "150.00€", "150 €", "- 150€", etc. if (preg_match('/-\s*(\d+(?:[.,]\d{1,2})?)\s*€/', $string, $matches)) { $amount = floatval(str_replace(',', '.', $matches[1])); error_log("Found amount with dash-euro pattern: " . $amount); return $amount; } // Buscar patrones como "cantidad personalizada - 250€" if (preg_match('/(\d+(?:[.,]\d{1,2})?)\s*€/', $string, $matches)) { $amount = floatval(str_replace(',', '.', $matches[1])); error_log("Found amount with euro pattern: " . $amount); return $amount; } // Buscar solo números con decimales al final de la cadena if (preg_match('/(\d+(?:[.,]\d{1,2})?)$/', trim($string), $matches)) { $amount = floatval(str_replace(',', '.', $matches[1])); error_log("Found amount with number pattern: " . $amount); return $amount; } error_log("No amount found in: " . $string); return 0; } /** * Formatear dirección de facturación */ function formatAddress($billing) { $address_parts = array_filter([ $billing['address_1'], $billing['address_2'], $billing['city'], $billing['state'], $billing['postcode'], $billing['country'] ]); return implode(', ', $address_parts); } /** * Limpiar cache expirado periódicamente */ function cleanupCache($cache) { // Solo limpiar ocasionalmente (10% de probabilidad) if (rand(1, 10) === 1) { $cache->cleanup(); } } // Limpiar cache al final cleanupCache($cache); /** * Función de debug para verificar datos */ function handleDebugRequest($api) { try { // Obtener algunos pedidos para debug $orders = $api->getCompletedOrders(1, 5); $debug_info = [ 'total_orders_found' => count($orders), 'orders_sample' => [] ]; foreach ($orders as $order) { $has_iglesias_product = false; $iglesias_metadata = []; foreach ($order['line_items'] as $item) { if ($item['product_id'] == IGLESIAS_PRODUCT_ID) { $has_iglesias_product = true; foreach ($item['meta_data'] as $meta) { $iglesias_metadata[$meta['key']] = $meta['value']; } break; } } if ($has_iglesias_product) { $debug_info['orders_sample'][] = [ 'id' => $order['id'], 'number' => $order['number'], 'total' => $order['total'], 'status' => $order['status'], 'metadata' => $iglesias_metadata ]; } } echo json_encode([ 'success' => true, 'debug_info' => $debug_info ]); } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => $e->getMessage() ]); } }PK -\F README.mdnu [ # Visor de Pedidos de Iglesias Sistema para visualizar pedidos completados del producto de participación de iglesias a través de la API REST de WordPress/WooCommerce. ## Configuración ### 1. Credenciales de WooCommerce Edita el archivo `config.php` y configura: ```php // URL de tu sitio WordPress define('WP_API_BASE_URL', 'https://tu-sitio-wordpress.com'); // Credenciales de la API REST de WooCommerce define('WC_CONSUMER_KEY', 'ck_tu_consumer_key_aqui'); define('WC_CONSUMER_SECRET', 'cs_tu_consumer_secret_aqui'); ``` ### 2. Generar Credenciales de API en WooCommerce 1. Ve a tu panel de WordPress 2. Navega a **WooCommerce > Configuración > Avanzado > REST API** 3. Haz clic en **Añadir clave** 4. Configura: - Descripción: "Visor de Pedidos Externo" - Usuario: Selecciona un usuario administrador - Permisos: **Lectura** 5. Copia las claves generadas al archivo `config.php` ### 3. Configuración del Servidor - Asegúrate de que PHP esté instalado (versión 7.4 o superior) - Habilita las extensiones: `curl`, `json` - Configura permisos de escritura en la carpeta `cache` (se crea automáticamente) ## Estructura de Archivos ``` external-orders-viewer/ ├── config.php # Configuración principal ├── api_client.php # Cliente para API de WooCommerce ├── api.php # Endpoint de la API local ├── index.html # Página principal ├── assets/ │ ├── style.css # Estilos CSS │ └── script.js # JavaScript principal ├── cache/ # Directorio de cache (se crea automáticamente) └── README.md # Esta documentación ``` ## Funcionalidades ### Características Principales - **Dashboard con Estadísticas**: Visualiza totales de pedidos, iglesias y recaudación - **Lista de Pedidos**: Muestra todos los pedidos completados del producto de iglesias - **Filtros Avanzados**: Buscar por iglesia, pastor, email; filtrar por fecha y cantidad - **Detalles Completos**: Modal con información detallada de cada pedido - **Exportación CSV**: Descarga datos filtrados en formato CSV - **Cache Inteligente**: Sistema de cache para mejorar rendimiento - **Responsive**: Diseño adaptable para móviles y tablets ### Datos Mostrados Para cada pedido se muestra: - Número de pedido - Fecha de creación - Nombre de la iglesia/ministerio - Nombre del pastor/representante - Información de contacto (email, teléfono) - Número de iglesias representadas - Detalles de participación económica - Total del pedido ## Uso 1. **Instalación**: Sube los archivos a tu servidor web 2. **Configuración**: Edita `config.php` con tus credenciales 3. **Acceso**: Navega a `index.html` en tu navegador 4. **Permisos**: Asegúrate de que el directorio tenga permisos de escritura para el cache ### Filtros Disponibles - **Búsqueda de texto**: Busca en nombre de iglesia, pastor o email - **Filtro de fecha**: Hoy, esta semana, este mes, este año - **Filtro de cantidad**: Rangos de importes económicos ## Seguridad - Las credenciales de API solo tienen permisos de **lectura** - No se almacenan datos sensibles en el cache - Todas las peticiones son validadas - Sistema de cache con expiración automática ## Solución de Problemas ### Error "cURL Error" o "HTTP Error 401" - Verifica las credenciales de API en `config.php` - Asegúrate de que la URL base sea correcta - Confirma que las claves de API estén activas ### Error "No se encontraron pedidos" - Verifica que existan pedidos completados para el producto ID 5029 - Comprueba que el producto esté configurado correctamente ### Problemas de Cache - Elimina manualmente la carpeta `cache` para forzar actualización - Verifica permisos de escritura en el directorio ### Error de CORS - Si usas desde un dominio diferente, configura CORS en tu WordPress - Asegúrate de que el servidor permita peticiones desde el origen ## Personalización ### Cambiar ID del Producto Edita `config.php` y modifica: ```php define('IGLESIAS_PRODUCT_ID', 5029); // Cambia por el ID correcto ``` ### Ajustar Cache Modifica la duración del cache en `config.php`: ```php define('CACHE_DURATION', 300); // 5 minutos en segundos ``` ### Personalizar Estilos Edita `assets/style.css` para cambiar la apariencia visual. ## Requisitos del Sistema - **PHP**: 7.4 o superior - **Extensiones**: curl, json, file system access - **WordPress**: 5.0 o superior - **WooCommerce**: 3.0 o superior - **Navegador**: Moderno con soporte para ES6 ## Soporte Para problemas o consultas: 1. Verifica la configuración siguiendo esta documentación 2. Revisa los logs de PHP para errores específicos 3. Confirma que las credenciales de API funcionen correctamentePK -\n , cache/a8489bc680339ef84def3b1b71aa368a.cachenu [ a:2:{s:7:"expires";i:1765354505;s:4:"data";a:4:{s:12:"total_orders";i:12;s:12:"total_amount";d:2002;s:14:"total_churches";i:15;s:17:"average_per_order";d:166.83333333333334;}}PK -\'% % , cache/fd96ab23b60c6e6b67e56886ae090b30.cachenu [ a:2:{s:7:"expires";i:1763637401;s:4:"data";a:12:{s:2:"id";i:5090;s:6:"number";s:4:"5090";s:6:"status";s:9:"completed";s:5:"total";d:200;s:12:"date_created";s:19:"2025-11-18T12:54:37";s:11:"church_name";s:32:"Nueva Sion San Pedro Del Pinatar";s:11:"pastor_name";s:19:"Gustavo Camps Parra";s:13:"billing_email";s:0:"";s:13:"billing_phone";s:0:"";s:15:"billing_address";s:0:"";s:14:"total_churches";i:1;s:21:"participation_details";a:1:{i:0;a:3:{s:6:"church";s:9:"Iglesia 1";s:11:"description";s:27:"hasta 100 miembros - 200€";s:6:"amount";d:200;}}}}PK -\ , cache/8219db266264aaa2e76e0d81848f80b9.cachenu [ a:2:{s:7:"expires";i:1763637480;s:4:"data";a:12:{s:2:"id";i:5054;s:6:"number";s:4:"5054";s:6:"status";s:9:"completed";s:5:"total";d:1;s:12:"date_created";s:19:"2025-11-12T08:02:39";s:11:"church_name";s:6:"church";s:11:"pastor_name";s:15:"ANGEL FERNANDEZ";s:13:"billing_email";s:0:"";s:13:"billing_phone";s:0:"";s:15:"billing_address";s:0:"";s:14:"total_churches";i:3;s:21:"participation_details";a:3:{i:0;a:3:{s:6:"church";s:9:"Iglesia 1";s:11:"description";s:26:"hasta 50 miembros - 150€";s:6:"amount";d:150;}i:1;a:3:{s:6:"church";s:9:"Iglesia 2";s:11:"description";s:27:"hasta 200 miembros - 300€";s:6:"amount";d:300;}i:2;a:3:{s:6:"church";s:9:"Iglesia 3";s:11:"description";s:33:"cantidad personalizada - 445.9€";s:6:"amount";d:445.9;}}}}PK -\!2 2 , cache/b85b9af1226c289e7795064bf72d052e.cachenu [ a:2:{s:7:"expires";i:1765354209;s:4:"data";a:0:{}}PK -\h api_client.phpnu [ consumer_key = WC_CONSUMER_KEY; $this->consumer_secret = WC_CONSUMER_SECRET; $this->base_url = WP_API_BASE_URL . '/wp-json/wc/v3/'; } /** * Realizar petición GET a la API de WooCommerce */ private function makeRequest($endpoint, $params = []) { $url = $this->base_url . $endpoint; // Añadir autenticación como parámetros de query $params['consumer_key'] = $this->consumer_key; $params['consumer_secret'] = $this->consumer_secret; $url .= '?' . http_build_query($params); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_USERAGENT, 'WordPress API Client'); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_error($ch)) { throw new Exception('cURL Error: ' . curl_error($ch)); } curl_close($ch); if ($httpCode !== 200) { throw new Exception("HTTP Error {$httpCode}: " . $response); } return json_decode($response, true); } /** * Obtener pedidos completados con paginación */ public function getCompletedOrders($page = 1, $per_page = ITEMS_PER_PAGE) { $params = [ 'status' => 'completed', 'page' => $page, 'per_page' => $per_page, 'order' => 'desc', 'orderby' => 'date' ]; return $this->makeRequest('orders', $params); } /** * Obtener pedidos completados filtrados por producto específico */ public function getCompletedOrdersByProduct($product_id, $page = 1, $per_page = ITEMS_PER_PAGE) { // Primero obtenemos todos los pedidos completados $orders = $this->getCompletedOrders($page, $per_page); // Filtramos los que contienen el producto específico $filtered_orders = []; foreach ($orders as $order) { foreach ($order['line_items'] as $item) { if ($item['product_id'] == $product_id) { $filtered_orders[] = $order; break; // Solo necesitamos encontrar uno para incluir el pedido } } } return $filtered_orders; } /** * Obtener detalles de un pedido específico */ public function getOrderDetails($order_id) { return $this->makeRequest("orders/{$order_id}"); } /** * Obtener información del producto */ public function getProduct($product_id) { return $this->makeRequest("products/{$product_id}"); } /** * Obtener estadísticas resumidas */ public function getOrderStats($product_id) { try { // Obtener TODOS los pedidos completados, página por página $all_orders = []; $page = 1; $per_page = 100; // Más pedidos por página para ser eficiente do { $orders = $this->getCompletedOrders($page, $per_page); // Filtrar solo los pedidos que contienen el producto específico foreach ($orders as $order) { foreach ($order['line_items'] as $item) { if ($item['product_id'] == $product_id) { $all_orders[] = $order; break; // Solo necesitamos encontrar uno para incluir el pedido } } } $page++; // Continuar solo si obtuvimos el máximo de pedidos (indica que hay más páginas) // Pero también poner un límite de seguridad para evitar loops infinitos } while (count($orders) == $per_page && $page <= 50); $total_orders = count($all_orders); $total_amount = 0; $total_churches = 0; foreach ($all_orders as $order) { $total_amount += floatval($order['total']); // Buscar metadatos de número de iglesias en los line items foreach ($order['line_items'] as $item) { if ($item['product_id'] == $product_id) { foreach ($item['meta_data'] as $meta) { if ($meta['key'] === 'Número de Iglesias') { $total_churches += intval($meta['value']); break; } } break; // Ya encontramos el producto, no necesitamos seguir buscando } } } return [ 'total_orders' => $total_orders, 'total_amount' => $total_amount, 'total_churches' => $total_churches, 'average_per_order' => $total_orders > 0 ? $total_amount / $total_orders : 0 ]; } catch (Exception $e) { error_log("Error getting order stats: " . $e->getMessage()); return [ 'error' => $e->getMessage(), 'total_orders' => 0, 'total_amount' => 0, 'total_churches' => 0, 'average_per_order' => 0 ]; } } } /** * Clase para manejar cache simple en archivos */ class SimpleCache { private $cache_dir; public function __construct($cache_dir = 'cache') { $this->cache_dir = $cache_dir; if (!is_dir($this->cache_dir)) { mkdir($this->cache_dir, 0755, true); } } /** * Guardar datos en cache */ public function set($key, $data, $duration = CACHE_DURATION) { $cache_file = $this->cache_dir . '/' . md5($key) . '.cache'; $cache_data = [ 'expires' => time() + $duration, 'data' => $data ]; file_put_contents($cache_file, serialize($cache_data)); } /** * Obtener datos del cache */ public function get($key) { $cache_file = $this->cache_dir . '/' . md5($key) . '.cache'; if (!file_exists($cache_file)) { return false; } $cache_data = unserialize(file_get_contents($cache_file)); if (time() > $cache_data['expires']) { unlink($cache_file); return false; } return $cache_data['data']; } /** * Limpiar cache expirado */ public function cleanup() { $files = glob($this->cache_dir . '/*.cache'); $cleaned = 0; foreach ($files as $file) { $cache_data = unserialize(file_get_contents($file)); if (time() > $cache_data['expires']) { unlink($file); $cleaned++; } } return $cleaned; } }PK -\ֆ4s s config.phpnu [ Configuración > Avanzado > REST API define('WC_CONSUMER_KEY', 'ck_8596559a16f803a7af96fc26cb1460d76e5def12'); define('WC_CONSUMER_SECRET', 'cs_940416f5ddf502129cc86ae3929da4437da528c5'); // ID del producto específico (iglesias) define('IGLESIAS_PRODUCT_ID', 5029); // Configuración de la base de datos (opcional, si quieres cache local) define('DB_HOST', 'alospiesjsvidawp.mysql.db'); define('DB_NAME', 'alospiesjsvidawp'); define('DB_USER', 'alospiesjsvidawp'); define('DB_PASS', 'Rtvidawp19'); // Configuración general define('ITEMS_PER_PAGE', 20); define('CACHE_DURATION', 300); // 5 minutos en segundosPK -\ް# z z setup.htmlnu [
Configura el sistema para conectar con tu WordPress/WooCommerce
Edita el archivo config.php con las credenciales de tu sitio:
Asegúrate de que tu servidor tenga:
No se encontraron pedidos que coincidan con los filtros aplicados.
Iglesia/Ministerio: ${item.church_name || ''}
Pastor/Representante: ${item.pastor_name || ''}
Número de Iglesias: ${item.total_churches || ''}
Correo de Contacto: ${item.contact_email || ''}
Teléfono de Contacto: ${item.contact_phone || ''}
Total: ${item.total || ''}€
${JSON.stringify(item.meta, null, 2)}
No se pudieron cargar los pedidos.
'; } } catch (error) { ordersContainer.innerHTML = `Error al cargar los pedidos: ${error}
`; } } // Ejecutar al cargar la página window.addEventListener('DOMContentLoaded', loadOrders); PK -\@qw( w( assets/style.cssnu [ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; color: #333; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } /* Header */ .header { background: white; border-radius: 15px; padding: 25px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; } .header h1 { color: #2c3e50; font-size: 2rem; font-weight: 600; } .header h1 i { color: #e74c3c; margin-right: 10px; } .header-actions { display: flex; gap: 10px; } /* Botones */ .btn { padding: 12px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; display: inline-flex; align-items: center; gap: 8px; transition: all 0.3s ease; text-decoration: none; } .btn-primary { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); } .btn-secondary { background: linear-gradient(135deg, #11998e, #38ef7d); color: white; } .btn-secondary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(17, 153, 142, 0.4); } .btn-outline { background: transparent; border: 2px solid #667eea; color: #667eea; } .btn-outline:hover { background: #667eea; color: white; } .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } /* Estadísticas */ .stats-section { margin-bottom: 20px; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } .stat-card { background: white; border-radius: 15px; padding: 25px; display: flex; align-items: center; box-shadow: 0 10px 30px rgba(0,0,0,0.1); transition: transform 0.3s ease; } .stat-card:hover { transform: translateY(-5px); } .stat-icon { background: linear-gradient(135deg, #667eea, #764ba2); color: white; width: 60px; height: 60px; border-radius: 15px; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-right: 20px; } .stat-info h3 { font-size: 2rem; font-weight: 700; color: #2c3e50; margin-bottom: 5px; } .stat-info p { color: #7f8c8d; font-size: 14px; font-weight: 500; } /* Filtros */ .filters-section { background: white; border-radius: 15px; padding: 25px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .filters { display: grid; grid-template-columns: 1fr auto auto; gap: 15px; align-items: center; } .search-input { padding: 12px 20px; border: 2px solid #e9ecef; border-radius: 8px; font-size: 14px; transition: border-color 0.3s ease; } .search-input:focus { outline: none; border-color: #667eea; } .filter-select { padding: 12px 15px; border: 2px solid #e9ecef; border-radius: 8px; background: white; cursor: pointer; font-size: 14px; } /* Loading y Error */ .loading, .error-message { text-align: center; padding: 40px; background: white; border-radius: 15px; margin: 20px 0; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .loading { color: #667eea; font-size: 18px; } .error-message { color: #e74c3c; font-size: 16px; } .error-message i { font-size: 24px; margin-bottom: 10px; display: block; } /* Sección de pedidos */ .orders-section { background: white; border-radius: 15px; padding: 25px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 2px solid #f8f9fa; } .section-header h2 { color: #2c3e50; font-size: 1.5rem; font-weight: 600; } .pagination-info { color: #7f8c8d; font-size: 14px; } /* Grid de pedidos */ .orders-grid { display: grid; gap: 20px; } .order-card { border: 2px solid #f1f3f4; border-radius: 12px; padding: 20px; transition: all 0.3s ease; cursor: pointer; position: relative; overflow: hidden; } .order-card:hover { border-color: #667eea; transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102, 126, 234, 0.2); } .order-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: linear-gradient(135deg, #11998e, #38ef7d); } .order-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; } .order-number { font-weight: 600; color: #2c3e50; font-size: 16px; } .order-status { background: #d4edda; color: #155724; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; } .order-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px; } .order-field { display: flex; flex-direction: column; gap: 2px; } .order-field label { font-size: 12px; color: #7f8c8d; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; } .order-field span { color: #2c3e50; font-weight: 500; } .order-amount { color: #e74c3c !important; font-weight: 700; font-size: 18px; } .order-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 15px; border-top: 1px solid #f1f3f4; } .churches-info { background: #e3f2fd; color: #1565c0; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; } .order-date { color: #7f8c8d; font-size: 12px; } /* Paginación */ .pagination { display: flex; justify-content: center; align-items: center; gap: 20px; margin-top: 20px; } #page-info { color: white; font-weight: 500; background: rgba(255,255,255,0.2); padding: 10px 20px; border-radius: 8px; } /* Modal */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 1000; animation: fadeIn 0.3s ease; } .modal-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 15px; max-width: 800px; width: 90%; max-height: 90%; overflow-y: auto; animation: slideIn 0.3s ease; } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 25px; border-bottom: 2px solid #f8f9fa; } .modal-header h3 { color: #2c3e50; font-size: 1.5rem; font-weight: 600; } .modal-close { background: none; border: none; font-size: 24px; color: #7f8c8d; cursor: pointer; padding: 5px; } .modal-close:hover { color: #e74c3c; } .modal-body { padding: 25px; } .detail-section { margin-bottom: 25px; } .detail-section h4 { color: #2c3e50; font-size: 1.2rem; font-weight: 600; margin-bottom: 15px; padding-bottom: 8px; border-bottom: 2px solid #f8f9fa; } .detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; } .detail-item { background: #f8f9fa; padding: 15px; border-radius: 8px; } .detail-item label { font-size: 12px; color: #7f8c8d; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; display: block; margin-bottom: 5px; } .detail-item span { color: #2c3e50; font-weight: 500; font-size: 14px; } .participation-details { background: #e8f5e8; border: 2px solid #d4edda; border-radius: 10px; padding: 20px; margin-top: 15px; } .participation-details-enhanced { background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 10px; padding: 20px; margin-top: 15px; } .participation-item { display: grid; grid-template-columns: 150px 1fr auto; gap: 15px; align-items: center; padding: 12px 0; border-bottom: 1px solid #dee2e6; } .participation-item:last-child { border-bottom: none; } .participation-item.participation-total { border-top: 2px solid #28a745; margin-top: 10px; padding-top: 15px; font-weight: bold; background: #d4edda; margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; border-radius: 0 0 8px 8px; } .participation-church { font-weight: 600; color: #495057; font-size: 14px; } .participation-desc { color: #6c757d; font-size: 14px; } .participation-amount { font-weight: bold; color: #28a745; font-size: 16px; text-align: right; } /* Animaciones */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { opacity: 0; transform: translate(-50%, -60%); } to { opacity: 1; transform: translate(-50%, -50%); } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .loading i { animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Responsive */ @media (max-width: 768px) { .container { padding: 10px; } .header { flex-direction: column; align-items: stretch; gap: 15px; } .header-actions { justify-content: center; } .filters { grid-template-columns: 1fr; } .order-info { grid-template-columns: 1fr; } .order-footer { flex-direction: column; align-items: flex-start; gap: 10px; } .pagination { flex-direction: column; gap: 10px; } .modal-content { width: 95%; margin: 20px; } }PK -\Q[ [ debug.htmlnu [Total Pedidos
Total Iglesias
Total Recaudado
Promedio por Pedido