session_start();
date_default_timezone_set('America/Lima');
define('BASE_PATH', __DIR__);
if (file_exists(BASE_PATH . '/config.php')) {
require BASE_PATH . '/config.php';
}
if (!defined('APP_NAME')) define('APP_NAME', 'JAMA Store');
if (!defined('APP_URL')) define('APP_URL', '');
if (!defined('ADMIN_EMAIL')) define('ADMIN_EMAIL', 'admin@jama.pe');
if (!defined('ADMIN_PASSWORD')) define('ADMIN_PASSWORD', 'jhoan14');
if (!defined('RESET_ADMIN_PASSWORD')) define('RESET_ADMIN_PASSWORD', false);
if (!defined('YAPE_PHONE')) define('YAPE_PHONE', '+51 906264890');
if (!defined('YAPE_RECIPIENT')) define('YAPE_RECIPIENT', 'JULIO MIN*');
if (!defined('CONTACT_PHONE')) define('CONTACT_PHONE', 'REDES SOCIALES');
if (!defined('CONTACT_EMAIL')) define('CONTACT_EMAIL', 'ventas@jama.pe');
if (!defined('STORE_CITY')) define('STORE_CITY', 'Peru');
if (!defined('GOOGLE_CLIENT_ID')) define('GOOGLE_CLIENT_ID', '');
if (!defined('GOOGLE_CLIENT_SECRET')) define('GOOGLE_CLIENT_SECRET', '');
if (!defined('GOOGLE_REDIRECT_URI')) define('GOOGLE_REDIRECT_URI', '');
if (!defined('DATA_DIR')) define('DATA_DIR', BASE_PATH . '/data');
if (!defined('DB_PATH')) define('DB_PATH', DATA_DIR . '/jama_store.sqlite');
if (!defined('UPLOAD_PRODUCTS_DIR')) define('UPLOAD_PRODUCTS_DIR', BASE_PATH . '/uploads/products');
if (!defined('UPLOAD_SOCIAL_DIR')) define('UPLOAD_SOCIAL_DIR', BASE_PATH . '/uploads/social');
if (!defined('RECEIPTS_DIR')) define('RECEIPTS_DIR', BASE_PATH . '/private/receipts');
foreach (array(DATA_DIR, UPLOAD_PRODUCTS_DIR, UPLOAD_SOCIAL_DIR, RECEIPTS_DIR) as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
function e($value) {
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
function money($value) {
return 'S/ ' . number_format((float)$value, 2);
}
function now_text() {
return date('Y-m-d H:i:s');
}
function url($route = 'inicio', $params = array()) {
if ($route && $route !== 'inicio') {
$params = array('r' => $route) + $params;
}
return 'index.php' . ($params ? '?' . http_build_query($params) : '');
}
function redirect_to($route = 'inicio', $params = array()) {
header('Location: ' . url($route, $params));
exit;
}
function csrf_token() {
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(24));
}
return $_SESSION['csrf'];
}
function csrf_field() {
return ' ';
}
function check_csrf() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return;
}
$token = isset($_POST['csrf']) ? $_POST['csrf'] : '';
if (!$token || empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $token)) {
http_response_code(403);
echo 'Solicitud no valida. Vuelve atras y recarga la pagina.';
exit;
}
}
function current_route() {
if (!empty($_GET['r'])) {
return trim($_GET['r'], '/');
}
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$base = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
if ($base !== '/' && strpos($path, $base) === 0) {
$path = substr($path, strlen($base));
}
$path = trim($path, '/');
if ($path === '' || $path === 'index.php') {
return 'inicio';
}
$map = array(
'login/google' => 'google-login',
'login/google/callback' => 'google-callback',
'admin/pedidos' => 'admin-pedidos',
'admin/productos' => 'admin-productos',
'admin/clientes' => 'admin-clientes',
'admin/promociones' => 'admin-promociones',
'admin/redes' => 'admin-redes',
'admin/backup' => 'admin-backup',
'admin/boleta' => 'admin-boleta'
);
return isset($map[$path]) ? $map[$path] : $path;
}
function db() {
static $pdo = null;
if ($pdo) {
return $pdo;
}
if (!extension_loaded('pdo_sqlite')) {
http_response_code(500);
echo '
Falta activar SQLite en PHP ';
echo 'Activa la extension pdo_sqlite en XAMPP o en tu hosting cPanel.
';
exit;
}
$pdo = new PDO('sqlite:' . DB_PATH);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
init_db($pdo);
return $pdo;
}
function run_sql($sql, $params = array()) {
$stmt = db()->prepare($sql);
$stmt->execute($params);
return $stmt;
}
function one($sql, $params = array()) {
$stmt = run_sql($sql, $params);
$row = $stmt->fetch();
return $row ? $row : null;
}
function all_rows($sql, $params = array()) {
$stmt = run_sql($sql, $params);
return $stmt->fetchAll();
}
function init_db($pdo) {
static $done = false;
if ($done) {
return;
}
$done = true;
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'client',
google_id TEXT,
auth_provider TEXT NOT NULL DEFAULT 'email',
promo_opt_in INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category TEXT NOT NULL,
price REAL NOT NULL,
promo_price REAL,
stock INTEGER NOT NULL DEFAULT 0,
sizes TEXT,
description TEXT,
image TEXT,
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS cart_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
size TEXT,
qty INTEGER NOT NULL DEFAULT 1,
updated_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
customer_name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT NOT NULL,
address TEXT NOT NULL,
shipping_agency TEXT NOT NULL DEFAULT 'SHALOM',
payment_method TEXT NOT NULL DEFAULT 'Yape',
yape_phone TEXT NOT NULL,
yape_recipient TEXT NOT NULL,
yape_name TEXT,
yape_operation TEXT,
receipt_file TEXT,
total REAL NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'Pendiente',
payment_status TEXT NOT NULL DEFAULT 'En revision',
admin_hidden INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS order_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER,
product_name TEXT NOT NULL,
price REAL NOT NULL,
quantity INTEGER NOT NULL,
size TEXT,
image TEXT
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS promotions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
message TEXT NOT NULL,
discount TEXT,
recipients INTEGER NOT NULL DEFAULT 0,
sent_at TEXT,
created_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS social_links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
url TEXT NOT NULL,
icon TEXT,
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin_settings (
setting_key TEXT PRIMARY KEY,
setting_value TEXT
)");
$admin = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$admin->execute(array(strtolower(ADMIN_EMAIL)));
$adminRow = $admin->fetch();
if (!$adminRow) {
$stmt = $pdo->prepare("INSERT INTO users (name, email, password_hash, role, promo_opt_in, created_at) VALUES (?, ?, ?, 'admin', 0, ?)");
$stmt->execute(array('Administrador JAMA', strtolower(ADMIN_EMAIL), password_hash(ADMIN_PASSWORD, PASSWORD_DEFAULT), now_text()));
} elseif (RESET_ADMIN_PASSWORD) {
$stmt = $pdo->prepare("UPDATE users SET password_hash = ?, role = 'admin' WHERE email = ?");
$stmt->execute(array(password_hash(ADMIN_PASSWORD, PASSWORD_DEFAULT), strtolower(ADMIN_EMAIL)));
}
$count = $pdo->query("SELECT COUNT(*) AS total FROM products")->fetch();
if ((int)$count['total'] === 0) {
$products = array(
array('Polo JAMA Signature', 'Polos', 59, 49, 18, 'S,M,L,XL', 'Polo urbano de algodon, corte comodo y detalle JAMA al frente.'),
array('Jogger JAMA Street', 'Pantalones', 89, 79, 10, 'S,M,L,XL', 'Jogger urbano para diario, suave y con estilo callejero.'),
array('Gorra JAMA Urban', 'Accesorios', 39, null, 25, 'Unica', 'Gorra moderna para completar el outfit JAMA.'),
array('Perfume JAMA Noir', 'Perfumes', 89, 79, 15, '50ml,100ml', 'Aroma elegante, intenso y urbano. Ideal para noche y salidas.'),
array('Perfume JAMA Fresh', 'Perfumes', 75, null, 20, '50ml,100ml,Unisex', 'Fragancia fresca y limpia para uso diario.'),
array('Perfume JAMA Gold', 'Perfumes', 99, 89, 10, '100ml,Unisex', 'Fragancia premium dulce con fondo amaderado.')
);
$stmt = $pdo->prepare("INSERT INTO products (name, category, price, promo_price, stock, sizes, description, image, active, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, '', 1, ?)");
foreach ($products as $p) {
$stmt->execute(array($p[0], $p[1], $p[2], $p[3], $p[4], $p[5], $p[6], now_text()));
}
}
}
function admin_setting($key, $default = '') {
$row = one("SELECT setting_value FROM admin_settings WHERE setting_key = ?", array($key));
return $row ? $row['setting_value'] : $default;
}
function set_admin_setting($key, $value) {
run_sql("INSERT OR REPLACE INTO admin_settings (setting_key, setting_value) VALUES (?, ?)", array($key, $value));
}
function current_user() {
if (empty($_SESSION['user_id'])) {
return null;
}
return one("SELECT * FROM users WHERE id = ?", array((int)$_SESSION['user_id']));
}
function is_admin() {
$user = current_user();
return $user && $user['role'] === 'admin';
}
function require_user() {
if (!current_user()) {
redirect_to('login', array('err' => 'Inicia sesion para continuar.'));
}
}
function require_admin() {
if (!is_admin()) {
redirect_to('admin-login', array('err' => 'Ingresa como administrador.'));
}
}
function login_user($user) {
$_SESSION['user_id'] = (int)$user['id'];
merge_session_cart_to_user((int)$user['id']);
}
function cart_key($productId, $size) {
return (int)$productId . '|' . trim((string)$size);
}
function parse_cart_key($key) {
$parts = explode('|', $key, 2);
return array((int)$parts[0], isset($parts[1]) ? $parts[1] : '');
}
function session_cart() {
return isset($_SESSION['cart']) && is_array($_SESSION['cart']) ? $_SESSION['cart'] : array();
}
function save_session_cart($cart) {
$_SESSION['cart'] = $cart;
}
function user_cart($userId) {
$cart = array();
$rows = all_rows("SELECT product_id, size, qty FROM cart_items WHERE user_id = ?", array($userId));
foreach ($rows as $row) {
$cart[cart_key($row['product_id'], $row['size'])] = (int)$row['qty'];
}
return $cart;
}
function save_user_cart($userId, $cart) {
run_sql("DELETE FROM cart_items WHERE user_id = ?", array($userId));
foreach ($cart as $key => $qty) {
list($productId, $size) = parse_cart_key($key);
if ($productId > 0 && $qty > 0) {
run_sql("INSERT INTO cart_items (user_id, product_id, size, qty, updated_at) VALUES (?, ?, ?, ?, ?)", array($userId, $productId, $size, (int)$qty, now_text()));
}
}
}
function merge_session_cart_to_user($userId) {
$session = session_cart();
if (!$session) {
return;
}
$cart = user_cart($userId);
foreach ($session as $key => $qty) {
$cart[$key] = isset($cart[$key]) ? $cart[$key] + (int)$qty : (int)$qty;
}
save_user_cart($userId, $cart);
save_session_cart(array());
}
function get_cart() {
$user = current_user();
if ($user && $user['role'] === 'client') {
return user_cart((int)$user['id']);
}
return session_cart();
}
function save_cart($cart) {
$user = current_user();
if ($user && $user['role'] === 'client') {
save_user_cart((int)$user['id'], $cart);
} else {
save_session_cart($cart);
}
}
function cart_count() {
$total = 0;
foreach (get_cart() as $qty) {
$total += (int)$qty;
}
return $total;
}
function product_price($product) {
if ($product['promo_price'] !== null && $product['promo_price'] !== '') {
return (float)$product['promo_price'];
}
return (float)$product['price'];
}
function image_tag($path, $class = '', $alt = '') {
if (!$path) {
return '';
}
return ' ';
}
function product_media($product, $small = false) {
if ($small) {
if (!$product['image']) {
return 'JAMA ';
}
return ' ';
}
return image_tag($product['image'], '', $product['name']);
}
function product_card($product) {
$price = product_price($product);
$old = ($product['promo_price'] !== null && $product['promo_price'] !== '') ? '' . money($product['price']) . ' ' : '';
$sizes = '';
foreach (array_filter(array_map('trim', explode(',', (string)$product['sizes']))) as $size) {
$sizes .= '' . e($size) . ' ';
}
return '
' . product_media($product) . '
' . e($product['category']) . '
' . e($product['name']) . '
' . $old . money($price) . '
' . e($product['description']) . '
Disponible: ' . (int)$product['stock'] . ' ' . $sizes . '
Ver prenda
';
}
function social_links_html() {
$rows = all_rows("SELECT * FROM social_links WHERE active = 1 ORDER BY created_at DESC");
if (!$rows) {
return 'redes @.
';
}
$html = '';
foreach ($rows as $row) {
$icon = $row['icon'] ? '
' : '
' . e(substr($row['name'], 0, 1)) . ' ';
$html .= '
' . $icon . e($row['name']) . ' ';
}
return $html . '
';
}
function notice_html() {
if (!empty($_GET['ok'])) {
return '' . e($_GET['ok']) . '
';
}
if (!empty($_GET['err'])) {
return '' . e($_GET['err']) . '
';
}
return '';
}
function shipping_badge() {
return ' SHALOM ';
}
function page($title, $content, $active = '') {
$user = current_user();
$userLinks = '';
if ($user) {
if ($user['role'] === 'admin') {
$userLinks .= 'Panel admin ';
$userLinks .= 'Salir (Administrador) ';
} else {
$userLinks .= 'Mis pedidos ';
$userLinks .= 'Salir ';
}
} else {
$userLinks .= 'Ingresar ';
$userLinks .= 'Crear cuenta ';
}
$pageTitle = $title . ' - ' . APP_NAME;
$seoHead = '';
if ($active === 'inicio') {
$pageTitle = 'JAMA Store Perú | Ropa Urbana y Perfumes';
$seoHead = '
';
}
echo '
' . e($pageTitle) . '
' . $seoHead . '
' . notice_html() . $content . '
';
}
function admin_page($title, $content, $active = 'dashboard') {
require_admin();
$links = array(
'dashboard' => array('Panel', 'admin'),
'pedidos' => array('Pedidos', 'admin-pedidos'),
'productos' => array('Productos', 'admin-productos'),
'clientes' => array('Clientes', 'admin-clientes'),
'redes' => array('Redes', 'admin-redes'),
'promos' => array('Promociones', 'admin-promociones'),
'backup' => array('Backup', 'admin-backup')
);
$nav = '';
foreach ($links as $key => $item) {
$nav .= '' . e($item[0]) . ' ';
}
echo '
' . e($title) . ' - Admin JAMA
' . notice_html() . $content . '
';
}
function page_home() {
$products = all_rows("SELECT * FROM products WHERE active = 1 ORDER BY created_at DESC LIMIT 6");
$cards = '';
foreach ($products as $p) {
$cards .= product_card($p);
}
$promo = one("SELECT * FROM promotions ORDER BY created_at DESC LIMIT 1");
$promoHtml = $promo ? '' : '';
$content = '
JAMA Store 24/7
JAMA Store Perú - Ropa urbana y perfumes
Compra prendas y perfumes JAMA, paga con Yape y envia por SHALOM. Cada cliente tiene su cuenta, carrito y pedidos guardados.
JAMA Store Moda, perfumes y promociones
Novedades Moda urbana, perfumes y promociones para encontrar tu propio estilo.
' . $cards . '
' . $promoHtml . '
';
page('Inicio', $content, 'inicio');
}
function page_catalog($onlyPerfumes = false) {
if ($onlyPerfumes) {
$rows = all_rows("SELECT * FROM products WHERE active = 1 AND category = 'Perfumes' ORDER BY created_at DESC");
$title = 'Perfumes JAMA';
$active = 'perfumes';
$desc = 'Fragancias para sumar a tu tienda con el mismo carrito.';
} else {
$rows = all_rows("SELECT * FROM products WHERE active = 1 AND category != 'Perfumes' ORDER BY created_at DESC");
$title = 'Catalogo JAMA';
$active = 'catalogo';
$desc = 'Ropa, accesorios y prendas urbanas.';
}
$cards = '';
foreach ($rows as $p) {
$cards .= product_card($p);
}
if (!$cards) {
$cards = 'Aun no hay productos activos.
';
}
$content = ' ' . e($active) . '' . e($title) . ' ' . e($desc) . '
' . $cards . '
';
page($title, $content, $active);
}
function page_product() {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$product = one("SELECT * FROM products WHERE id = ? AND active = 1", array($id));
if (!$product) {
page('No encontrado', 'Producto no encontrado.
');
return;
}
$sizes = '';
foreach (array_filter(array_map('trim', explode(',', (string)$product['sizes']))) as $size) {
$sizes .= '' . e($size) . ' ';
}
if (!$sizes) {
$sizes = 'Unica ';
}
$old = ($product['promo_price'] !== null && $product['promo_price'] !== '') ? '' . money($product['price']) . ' ' : '';
$content = '
' . product_media($product) . '
' . e($product['category']) . '
' . e($product['name']) . '
' . $old . money(product_price($product)) . '
' . e($product['description']) . '
';
page($product['name'], $content, $product['category'] === 'Perfumes' ? 'perfumes' : 'catalogo');
}
function cart_lines() {
$lines = array();
$total = 0;
foreach (get_cart() as $key => $qty) {
list($productId, $size) = parse_cart_key($key);
$product = one("SELECT * FROM products WHERE id = ?", array($productId));
if (!$product) {
continue;
}
$price = product_price($product);
$subtotal = $price * (int)$qty;
$total += $subtotal;
$lines[] = array('key' => $key, 'product' => $product, 'size' => $size, 'qty' => (int)$qty, 'price' => $price, 'subtotal' => $subtotal);
}
return array($lines, $total);
}
function page_cart() {
list($lines, $total) = cart_lines();
$rows = '';
foreach ($lines as $line) {
$p = $line['product'];
$rows .= '
' . product_media($p, true) . '
' . e($p['name']) . ' Variante: ' . e($line['size']) . '
' . money($line['price']) . '
' . money($line['subtotal']) . '
';
}
if (!$rows) {
$rows = 'Tu carrito esta vacio. ';
}
$user = current_user();
if ($user && $user['role'] === 'client' && $lines) {
$checkout = '';
} elseif ($lines) {
$checkout = 'Inicia sesion para comprar Asi tu pedido queda guardado con tu correo y lo puedes ver en Mis pedidos.
Ingresar Crear cuenta ';
} else {
$checkout = 'Total Carrito ' . money(0) . '
';
}
$content = ' CarritoFinaliza tu compra Prenda Precio Cantidad Subtotal ' . $rows . '
' . $checkout . '
';
page('Carrito', $content);
}
function handle_add_to_cart() {
check_csrf();
$productId = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0;
$qty = max(1, isset($_POST['qty']) ? (int)$_POST['qty'] : 1);
$size = trim(isset($_POST['size']) ? $_POST['size'] : '');
$product = one("SELECT * FROM products WHERE id = ? AND active = 1", array($productId));
if (!$product) {
redirect_to('catalogo', array('err' => 'Producto no encontrado.'));
}
$key = cart_key($productId, $size);
$cart = get_cart();
$cart[$key] = isset($cart[$key]) ? $cart[$key] + $qty : $qty;
save_cart($cart);
redirect_to('carrito', array('ok' => 'Prenda agregada al carrito.'));
}
function handle_cart_update() {
check_csrf();
$key = isset($_POST['key']) ? $_POST['key'] : '';
$qty = isset($_POST['qty']) ? (int)$_POST['qty'] : 0;
$cart = get_cart();
if (isset($cart[$key])) {
if ($qty <= 0) {
unset($cart[$key]);
} else {
$cart[$key] = $qty;
}
}
save_cart($cart);
redirect_to('carrito', array('ok' => 'Carrito actualizado.'));
}
function handle_cart_remove() {
check_csrf();
$key = isset($_POST['key']) ? $_POST['key'] : '';
$cart = get_cart();
if (isset($cart[$key])) {
unset($cart[$key]);
save_cart($cart);
}
redirect_to('carrito', array('ok' => 'Prenda eliminada del carrito.'));
}
function relative_path($absolute) {
return str_replace('\\', '/', ltrim(str_replace(BASE_PATH, '', $absolute), '/'));
}
function upload_file($field, $dir, $prefix, $allowed) {
if (empty($_FILES[$field]) || $_FILES[$field]['error'] === UPLOAD_ERR_NO_FILE) {
return '';
}
if ($_FILES[$field]['error'] !== UPLOAD_ERR_OK) {
return '';
}
$ext = strtolower(pathinfo($_FILES[$field]['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
return '';
}
$name = $prefix . '-' . bin2hex(random_bytes(8)) . '.' . $ext;
$target = rtrim($dir, '/\\') . '/' . $name;
if (!move_uploaded_file($_FILES[$field]['tmp_name'], $target)) {
return '';
}
return relative_path($target);
}
function handle_create_order() {
check_csrf();
require_user();
$user = current_user();
if ($user['role'] !== 'client') {
redirect_to('carrito', array('err' => 'Usa una cuenta de cliente para comprar.'));
}
list($lines, $total) = cart_lines();
if (!$lines) {
redirect_to('carrito', array('err' => 'Tu carrito esta vacio.'));
}
$phone = trim(isset($_POST['phone']) ? $_POST['phone'] : '');
$address = trim(isset($_POST['address']) ? $_POST['address'] : '');
$agency = trim(isset($_POST['shipping_agency']) ? $_POST['shipping_agency'] : 'SHALOM');
$yapeName = trim(isset($_POST['yape_name']) ? $_POST['yape_name'] : '');
$operation = trim(isset($_POST['yape_operation']) ? $_POST['yape_operation'] : '');
$receipt = upload_file('receipt', RECEIPTS_DIR, 'comprobante', array('jpg', 'jpeg', 'png', 'webp', 'pdf'));
if (!$phone || !$address || !$yapeName || !$receipt) {
redirect_to('carrito', array('err' => 'Completa direccion, telefono, nombre Yape y comprobante.'));
}
run_sql("INSERT INTO orders (user_id, customer_name, email, phone, address, shipping_agency, yape_phone, yape_recipient, yape_name, yape_operation, receipt_file, total, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", array($user['id'], $user['name'], $user['email'], $phone, $address, $agency ?: 'SHALOM', YAPE_PHONE, YAPE_RECIPIENT, $yapeName, $operation, $receipt, $total, now_text()));
$orderId = (int)db()->lastInsertId();
foreach ($lines as $line) {
$p = $line['product'];
run_sql("INSERT INTO order_items (order_id, product_id, product_name, price, quantity, size, image) VALUES (?, ?, ?, ?, ?, ?, ?)", array($orderId, $p['id'], $p['name'], $line['price'], $line['qty'], $line['size'], $p['image']));
run_sql("UPDATE products SET stock = MAX(stock - ?, 0) WHERE id = ?", array($line['qty'], $p['id']));
}
save_cart(array());
redirect_to('pedido-exito', array('id' => $orderId));
}
function page_order_success() {
require_user();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$order = one("SELECT * FROM orders WHERE id = ? AND user_id = ?", array($id, current_user()['id']));
if (!$order) {
redirect_to('mis-pedidos');
}
$content = ' Pedido recibidoGracias por tu compra Tu pedido #' . (int)$order['id'] . ' ya aparece en tu cuenta y en el panel admin. El pago queda en revision hasta validar el Yape.
Ver mis pedidos Total
' . money($order['total']) . ' ' . e($order['payment_status']) . ' ';
page('Pedido recibido', $content);
}
function page_my_orders() {
require_user();
$user = current_user();
$orders = all_rows("SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC", array($user['id']));
$rows = '';
foreach ($orders as $o) {
$rows .= '#' . (int)$o['id'] . ' ' . e($o['created_at']) . ' ' . money($o['total']) . ' ' . e($o['status']) . ' ' . e($o['payment_status']) . ' Ver ';
}
if (!$rows) {
$rows = 'Aun no hiciste pedidos. ';
}
$content = ' Mi cuentaPedidos realizados Pedido Fecha Total Envio Pago ' . $rows . '
';
page('Mis pedidos', $content);
}
function page_customer_order() {
require_user();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$user = current_user();
$order = one("SELECT * FROM orders WHERE id = ? AND user_id = ?", array($id, $user['id']));
if (!$order) {
redirect_to('mis-pedidos');
}
$items = all_rows("SELECT * FROM order_items WHERE order_id = ?", array($id));
$rows = '';
foreach ($items as $it) {
$variant = trim((string)$it['size']) !== '' ? e($it['size']) : 'Única';
$rows .= '
' . image_tag($it['image'], 'mini-img', $it['product_name']) . '
' . e($it['product_name']) . ' Variante: ' . $variant . '
' . (int)$it['quantity'] . '
' . money($it['price']) . '
' . money($it['price'] * $it['quantity']) . '
';
}
if (!$rows) {
$rows = 'Este pedido no tiene productos. ';
}
$operationText = trim((string)$order['yape_operation']) !== '' ? e($order['yape_operation']) : 'No registrado';
$yapeNameText = trim((string)$order['yape_name']) !== '' ? e($order['yape_name']) : 'No registrado';
$content = '
Pedido #' . (int)$order['id'] . '
Detalle del pedido
' . e($order['status']) . ' ' . e($order['payment_status']) . '
TU COMPRA Productos del pedido
' . count($items) . ' producto(s)
Producto Cant. Precio Subtotal ' . $rows . '
⌖
DIRECCIÓN DE ENTREGA ' . e($order['address']) . '
↗
AGENCIA DE ENVÍO ' . shipping_badge() . '
Y
DATOS DEL YAPE Pago registrado NOMBRE ' . $yapeNameText . '
OPERACIÓN ' . $operationText . '
TOTAL DEL PEDIDO ' . money($order['total']) . '
';
page('Pedido #' . $id, $content);
}
function google_configured() {
return GOOGLE_CLIENT_ID !== '' && GOOGLE_CLIENT_SECRET !== '';
}
function app_url() {
if (APP_URL !== '') {
return rtrim(APP_URL, '/');
}
$https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
$scheme = $https ? 'https' : 'http';
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
$base = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
return rtrim($scheme . '://' . $host . ($base === '/' ? '' : $base), '/');
}
function google_redirect_uri() {
return GOOGLE_REDIRECT_URI !== '' ? GOOGLE_REDIRECT_URI : app_url() . '/index.php?r=google-callback';
}
function google_button() {
if (!google_configured()) {
return 'Google Login esta listo, pero falta poner GOOGLE_CLIENT_ID y GOOGLE_CLIENT_SECRET en config.php.
';
}
return 'G Continuar con Google ';
}
function page_login($admin = false) {
$action = $admin ? 'admin-login' : 'login';
$extra = $admin ? '' : 'o
' . google_button() . 'No tienes cuenta? Crea una aqui
';
$content = ' ';
page($admin ? 'Admin' : 'Ingresar', $content);
}
function page_register() {
$content = ' ';
page('Registro', $content);
}
function handle_login($admin = false) {
check_csrf();
$email = strtolower(trim(isset($_POST['email']) ? $_POST['email'] : ''));
$password = isset($_POST['password']) ? $_POST['password'] : '';
$user = one("SELECT * FROM users WHERE email = ?", array($email));
if (!$user || !password_verify($password, $user['password_hash'])) {
redirect_to($admin ? 'admin-login' : 'login', array('err' => 'Correo o contrasena incorrectos.'));
}
if ($admin && $user['role'] !== 'admin') {
redirect_to('admin-login', array('err' => 'Esta cuenta no es admin.'));
}
login_user($user);
redirect_to($user['role'] === 'admin' ? 'admin' : 'inicio', array('ok' => 'Sesion iniciada.'));
}
function handle_register() {
check_csrf();
$name = trim(isset($_POST['name']) ? $_POST['name'] : '');
$email = strtolower(trim(isset($_POST['email']) ? $_POST['email'] : ''));
$password = isset($_POST['password']) ? $_POST['password'] : '';
if (!$name || !$email || strlen($password) < 4) {
redirect_to('registro', array('err' => 'Completa tus datos.'));
}
try {
run_sql("INSERT INTO users (name, email, password_hash, role, promo_opt_in, created_at) VALUES (?, ?, ?, 'client', ?, ?)", array($name, $email, password_hash($password, PASSWORD_DEFAULT), isset($_POST['promo_opt_in']) ? 1 : 0, now_text()));
} catch (Exception $e) {
redirect_to('registro', array('err' => 'Ese correo ya esta registrado.'));
}
$user = one("SELECT * FROM users WHERE email = ?", array($email));
login_user($user);
redirect_to('inicio', array('ok' => 'Cuenta creada.'));
}
function handle_logout() {
session_destroy();
redirect_to('inicio', array('ok' => 'Sesion cerrada.'));
}
function handle_google_login() {
if (!google_configured()) {
redirect_to('login', array('err' => 'Falta configurar Google Login en config.php.'));
}
$_SESSION['google_state'] = bin2hex(random_bytes(16));
$params = array(
'client_id' => GOOGLE_CLIENT_ID,
'redirect_uri' => google_redirect_uri(),
'response_type' => 'code',
'scope' => 'openid email profile',
'state' => $_SESSION['google_state'],
'access_type' => 'online',
'prompt' => 'select_account'
);
header('Location: https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query($params));
exit;
}
function curl_json($url, $postFields = null, $bearer = '') {
if (!function_exists('curl_init')) {
return null;
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($postFields !== null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
} elseif ($bearer) {
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . $bearer));
}
$body = curl_exec($ch);
curl_close($ch);
return $body ? json_decode($body, true) : null;
}
function handle_google_callback() {
if (!google_configured()) {
redirect_to('login', array('err' => 'Google Login no esta configurado.'));
}
if (!empty($_GET['error'])) {
redirect_to('login', array('err' => 'Google cancelo el acceso.'));
}
$state = isset($_GET['state']) ? $_GET['state'] : '';
if (!$state || empty($_SESSION['google_state']) || !hash_equals($_SESSION['google_state'], $state)) {
redirect_to('login', array('err' => 'Estado de Google no valido.'));
}
$code = isset($_GET['code']) ? $_GET['code'] : '';
$token = curl_json('https://oauth2.googleapis.com/token', array(
'code' => $code,
'client_id' => GOOGLE_CLIENT_ID,
'client_secret' => GOOGLE_CLIENT_SECRET,
'redirect_uri' => google_redirect_uri(),
'grant_type' => 'authorization_code'
));
if (!$token || empty($token['access_token'])) {
redirect_to('login', array('err' => 'No se pudo conectar con Google.'));
}
$info = curl_json('https://www.googleapis.com/oauth2/v3/userinfo', null, $token['access_token']);
if (!$info || empty($info['email'])) {
redirect_to('login', array('err' => 'Google no devolvio el correo.'));
}
$email = strtolower($info['email']);
$googleId = isset($info['sub']) ? $info['sub'] : '';
$name = isset($info['name']) ? $info['name'] : $email;
$user = one("SELECT * FROM users WHERE google_id = ? AND google_id != ''", array($googleId));
if (!$user) {
$user = one("SELECT * FROM users WHERE email = ?", array($email));
if ($user) {
run_sql("UPDATE users SET google_id = ?, auth_provider = 'google' WHERE id = ?", array($googleId, $user['id']));
} else {
run_sql("INSERT INTO users (name, email, password_hash, role, google_id, auth_provider, promo_opt_in, created_at) VALUES (?, ?, ?, 'client', ?, 'google', 1, ?)", array($name, $email, password_hash(bin2hex(random_bytes(18)), PASSWORD_DEFAULT), $googleId, now_text()));
}
$user = one("SELECT * FROM users WHERE email = ?", array($email));
}
login_user($user);
redirect_to('inicio', array('ok' => 'Entraste con Google.'));
}
function admin_dashboard() {
$orders = one("SELECT COUNT(*) AS total FROM orders");
$clients = one("SELECT COUNT(*) AS total FROM users WHERE role = 'client'");
$products = one("SELECT COUNT(*) AS total FROM products WHERE active = 1");
$pending = one("SELECT COUNT(*) AS total FROM orders WHERE payment_status != 'Aprobado' AND status != 'Cancelado'");
$resetAt = admin_setting('earnings_reset_at', '');
$sql = "SELECT COALESCE(SUM(total),0) AS total FROM orders WHERE payment_status = 'Aprobado' AND status != 'Cancelado'";
$params = array();
if ($resetAt !== '') {
$sql .= " AND created_at >= ?";
$params[] = $resetAt;
}
$earnings = one($sql, $params);
$resetLabel = $resetAt !== '' ? 'Desde: ' . $resetAt : 'Desde el inicio';
$content = '
Pedidos ' . (int)$orders['total'] . '
Ganancia ' . money($earnings['total']) . ' ' . e($resetLabel) . '
Clientes ' . (int)$clients['total'] . '
Pagos pendientes ' . (int)$pending['total'] . '
Ganancia del panel Este contador suma pedidos con pago aprobado. Reiniciarlo no borra pedidos ni ventas guardadas.
Acumulado ' . money($earnings['total']) . '
';
admin_page('Panel', $content, 'dashboard');
}
function handle_admin_earnings_reset() {
check_csrf();
require_admin();
set_admin_setting('earnings_reset_at', now_text());
redirect_to('admin', array('ok' => 'Ganancia reiniciada a S/ 0.00. Tus pedidos siguen guardados.'));
}
function admin_order_items_summary($orderId) {
$items = all_rows("SELECT * FROM order_items WHERE order_id = ?", array($orderId));
$html = '';
foreach ($items as $it) {
$html .= '' . image_tag($it['image'], 'mini-img tiny', $it['product_name']) . '
' . e($it['product_name']) . ' ' . e($it['size']) . ' x ' . (int)$it['quantity'] . '
';
}
return $html;
}
function admin_orders() {
$orders = all_rows("SELECT * FROM orders WHERE admin_hidden = 0 ORDER BY created_at DESC");
$rows = '';
foreach ($orders as $o) {
$remove = '';
if (in_array($o['status'], array('Entregado','Cancelado'), true)) {
$remove = '';
}
$rows .= '#' . (int)$o['id'] . '' . e($o['created_at']) . ' ' . e($o['customer_name']) . '' . e($o['email']) . ' ' . admin_order_items_summary($o['id']) . ' ' . e($o['address']) . ' ' . shipping_badge() . ' ' . money($o['total']) . '' . e($o['payment_status']) . ' ' . e($o['status']) . ' Ver ' . $remove . ' ';
}
if (!$rows) {
$rows = 'No hay pedidos visibles. ';
}
$content = '
Pedidos Pedido Cliente Prendas Envio Total/Pago Estado ' . $rows . '
';
admin_page('Pedidos', $content, 'pedidos');
}
function admin_order_detail() {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$order = one("SELECT * FROM orders WHERE id = ?", array($id));
if (!$order) {
redirect_to('admin-pedidos', array('err' => 'Pedido no encontrado.'));
}
$items = all_rows("SELECT * FROM order_items WHERE order_id = ?", array($id));
$rows = '';
foreach ($items as $it) {
$rows .= '' . image_tag($it['image'], 'mini-img', $it['product_name']) . '
' . e($it['product_name']) . ' Talla/variante: ' . e($it['size']) . '
' . (int)$it['quantity'] . ' ' . money($it['price']) . ' ' . money($it['price'] * $it['quantity']) . ' ';
}
$receipt = $order['receipt_file'] ? 'Ver comprobante Yape ' : 'Sin comprobante ';
$invoice = $order['payment_status'] === 'Aprobado' ? 'Generar boleta PDF ' : 'Aprueba el pago para generar boleta. ';
$delete = in_array($order['status'], array('Entregado', 'Cancelado'), true) ? '' : '';
$content = ' Pedido #' . (int)$order['id'] . '
Detalle del pedido Volver
Prenda Cant. Precio Subtotal ' . $rows . '
Cliente y envio ' . e($order['customer_name']) . ' ' . e($order['email']) . ' ' . e($order['phone']) . '
Direccion: ' . e($order['address']) . '
Agencia: ' . shipping_badge() . '
Yape Numero: ' . e($order['yape_phone']) . ' Destinatario: ' . e($order['yape_recipient']) . ' Nombre Yape: ' . e($order['yape_name']) . ' Operacion: ' . e($order['yape_operation']) . '
' . $receipt . '
Boleta ' . $invoice . '
Total ' . money($order['total']) . '
';
admin_page('Pedido #' . $id, $content, 'pedidos');
}
function options($values, $selected) {
$html = '';
foreach ($values as $value) {
$html .= '' . e($value) . ' ';
}
return $html;
}
function handle_admin_order_status() {
check_csrf();
require_admin();
$id = (int)$_POST['id'];
$status = trim($_POST['status']);
$payment = trim($_POST['payment_status']);
run_sql("UPDATE orders SET status = ?, payment_status = ? WHERE id = ?", array($status, $payment, $id));
redirect_to('admin-pedido', array('id' => $id, 'ok' => 'Pedido actualizado.'));
}
function handle_admin_order_delete() {
check_csrf();
require_admin();
$id = (int)$_POST['id'];
$order = one("SELECT * FROM orders WHERE id = ?", array($id));
if (!$order || !in_array($order['status'], array('Entregado', 'Cancelado'), true)) {
redirect_to('admin-pedidos', array('err' => 'Solo puedes quitar pedidos entregados o cancelados.'));
}
run_sql("UPDATE orders SET admin_hidden = 1 WHERE id = ?", array($id));
redirect_to('admin-pedidos', array('ok' => 'Pedido quitado del panel. El cliente aun lo conserva en su cuenta.'));
}
function admin_products() {
$rows = all_rows("SELECT * FROM products ORDER BY active DESC, created_at DESC");
$html = '';
foreach ($rows as $p) {
$action = $p['active'] ? '' : '';
$html .= '' . product_media($p, true) . ' ' . e($p['name']) . ' ' . e($p['category']) . ' ' . money($p['price']) . ' ' . ($p['promo_price'] ? money($p['promo_price']) : '-') . ' ' . (int)$p['stock'] . ' ' . e($p['sizes']) . ' Editar ' . $action . ' ';
}
if (!$html) {
$html = 'No hay productos. ';
}
$content = 'Foto Producto Precio Promo Stock Tallas ' . $html . '
';
admin_page('Productos', $content, 'productos');
}
function admin_product_form() {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$p = $id ? one("SELECT * FROM products WHERE id = ?", array($id)) : null;
$action = $p ? 'admin-producto-guardar' : 'admin-producto-crear';
$image = $p && $p['image'] ? 'Foto actual ' : '';
$content = '' . ($p ? 'Editar producto' : 'Agregar producto') . ' Volver
';
admin_page('Producto', $content, 'productos');
}
function admin_products_bulk_form() {
require_admin();
$categories = array('Polos', 'Camisas', 'Poleras', 'Pantalones', 'Shorts', 'Accesorios', 'Zapatillas', 'Perfumes', 'Otros');
$rows = '';
for ($i = 0; $i < 20; $i++) {
$number = $i + 1;
$options = '';
foreach ($categories as $category) {
$options .= '' . e($category) . ' ';
}
$rows .= '';
}
$content = 'Agregar hasta 20 productos Completa solo los productos que quieras agregar. Los espacios vacios se ignoran. Usa la categoria Perfumes para que aparezcan en la seccion Perfumes; las demas categorias aparecen en Catalogo JAMA.
Volver
' . csrf_field() . '
' . $rows . '
Guardar productos
';
admin_page('Agregar productos', $content, 'productos');
}
function upload_file_array($field, $index, $dir, $prefix, $allowed) {
if (empty($_FILES[$field]) || !isset($_FILES[$field]['error'][$index]) || $_FILES[$field]['error'][$index] === UPLOAD_ERR_NO_FILE) {
return '';
}
if ($_FILES[$field]['error'][$index] !== UPLOAD_ERR_OK) {
return '';
}
$originalName = isset($_FILES[$field]['name'][$index]) ? $_FILES[$field]['name'][$index] : '';
$tmpName = isset($_FILES[$field]['tmp_name'][$index]) ? $_FILES[$field]['tmp_name'][$index] : '';
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!$tmpName || !in_array($ext, $allowed, true)) {
return '';
}
$name = $prefix . '-' . bin2hex(random_bytes(8)) . '.' . $ext;
$target = rtrim($dir, '/\\') . '/' . $name;
if (!move_uploaded_file($tmpName, $target)) {
return '';
}
return relative_path($target);
}
function handle_products_bulk_save() {
check_csrf();
require_admin();
$names = isset($_POST['name']) && is_array($_POST['name']) ? $_POST['name'] : array();
$categories = isset($_POST['category']) && is_array($_POST['category']) ? $_POST['category'] : array();
$prices = isset($_POST['price']) && is_array($_POST['price']) ? $_POST['price'] : array();
$promoPrices = isset($_POST['promo_price']) && is_array($_POST['promo_price']) ? $_POST['promo_price'] : array();
$stocks = isset($_POST['stock']) && is_array($_POST['stock']) ? $_POST['stock'] : array();
$sizes = isset($_POST['sizes']) && is_array($_POST['sizes']) ? $_POST['sizes'] : array();
$descriptions = isset($_POST['description']) && is_array($_POST['description']) ? $_POST['description'] : array();
$created = 0;
$skipped = 0;
$max = min(20, count($names));
for ($i = 0; $i < $max; $i++) {
$name = trim(isset($names[$i]) ? $names[$i] : '');
$priceText = trim(isset($prices[$i]) ? (string)$prices[$i] : '');
if ($name === '') {
continue;
}
if ($priceText === '' || !is_numeric($priceText)) {
$skipped++;
continue;
}
$category = trim(isset($categories[$i]) ? $categories[$i] : 'Polos');
if ($category === '') {
$category = 'Polos';
}
$price = max(0, (float)$priceText);
$promoText = trim(isset($promoPrices[$i]) ? (string)$promoPrices[$i] : '');
$promoPrice = ($promoText === '' || !is_numeric($promoText)) ? null : max(0, (float)$promoText);
$stock = max(0, (int)(isset($stocks[$i]) ? $stocks[$i] : 0));
$sizeText = trim(isset($sizes[$i]) ? $sizes[$i] : '');
$description = trim(isset($descriptions[$i]) ? $descriptions[$i] : '');
$image = upload_file_array('images', $i, UPLOAD_PRODUCTS_DIR, 'producto', array('jpg','jpeg','png','webp'));
run_sql(
"INSERT INTO products (name, category, price, promo_price, stock, sizes, description, image, active, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?)",
array($name, $category, $price, $promoPrice, $stock, $sizeText, $description, $image, now_text())
);
$created++;
}
if ($created === 0) {
redirect_to('admin-productos-masivo', array('err' => 'No se agregaron productos. Completa al menos nombre y precio.'));
}
$message = $created . ' producto(s) agregado(s) correctamente.';
if ($skipped > 0) {
$message .= ' ' . $skipped . ' fila(s) se omitieron por no tener precio valido.';
}
redirect_to('admin-productos', array('ok' => $message));
}
function handle_product_save($create = false) {
check_csrf();
require_admin();
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$current = $id ? one("SELECT * FROM products WHERE id = ?", array($id)) : null;
$image = upload_file('image', UPLOAD_PRODUCTS_DIR, 'producto', array('jpg','jpeg','png','webp'));
if (!$image && $current) {
$image = $current['image'];
}
$data = array(
trim($_POST['name']),
trim($_POST['category']),
(float)$_POST['price'],
$_POST['promo_price'] === '' ? null : (float)$_POST['promo_price'],
(int)$_POST['stock'],
trim($_POST['sizes']),
trim($_POST['description']),
$image
);
if ($create) {
run_sql("INSERT INTO products (name, category, price, promo_price, stock, sizes, description, image, active, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?)", array_merge($data, array(now_text())));
} else {
$data[] = $id;
run_sql("UPDATE products SET name = ?, category = ?, price = ?, promo_price = ?, stock = ?, sizes = ?, description = ?, image = ? WHERE id = ?", $data);
}
redirect_to('admin-productos', array('ok' => 'Producto guardado.'));
}
function handle_product_active($active) {
check_csrf();
require_admin();
$id = (int)$_POST['id'];
run_sql("UPDATE products SET active = ? WHERE id = ?", array($active ? 1 : 0, $id));
redirect_to('admin-productos', array('ok' => $active ? 'Producto restaurado.' : 'Producto ocultado.'));
}
function admin_clients() {
$rows = all_rows("SELECT u.*, COUNT(o.id) AS orders_count, COALESCE(SUM(o.total),0) AS spent FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.role = 'client' GROUP BY u.id ORDER BY u.created_at DESC");
$html = '';
foreach ($rows as $u) {
$html .= '' . e($u['name']) . ' ' . e($u['email']) . ' ' . e($u['auth_provider']) . ' ' . (int)$u['orders_count'] . ' ' . money($u['spent']) . ' ' . ($u['promo_opt_in'] ? 'Si' : 'No') . ' ';
}
if (!$html) {
$html = 'Aun no hay clientes. ';
}
$content = '
Clientes Cliente Registro Pedidos Total Promos ' . $html . '
';
admin_page('Clientes', $content, 'clientes');
}
function admin_promotions() {
$recipientCount = one("SELECT COUNT(*) AS total FROM users WHERE role = 'client' AND promo_opt_in = 1");
$promos = all_rows("SELECT * FROM promotions ORDER BY created_at DESC");
$history = '';
foreach ($promos as $p) {
$discount = trim((string)$p['discount']) !== '' ? $p['discount'] : 'PROMO';
$history .= '
' . e($discount) . '
' . e($p['title']) . ' ' . e($p['message']) . '
' . (int)$p['recipients'] . ' clientes' . e($p['created_at']) . '
';
}
if (!$history) {
$history = 'Aun no hay promociones.
';
}
$content = '
';
admin_page('Promociones', $content, 'promos');
}
function handle_promotion_create() {
check_csrf();
require_admin();
$title = trim($_POST['title']);
$message = trim($_POST['message']);
$discount = trim(isset($_POST['discount']) ? $_POST['discount'] : '');
$clients = all_rows("SELECT email FROM users WHERE role = 'client' AND promo_opt_in = 1");
foreach ($clients as $client) {
@mail($client['email'], $title, $message, 'From: ' . CONTACT_EMAIL);
}
run_sql("INSERT INTO promotions (title, message, discount, recipients, sent_at, created_at) VALUES (?, ?, ?, ?, ?, ?)", array($title, $message, $discount, count($clients), now_text(), now_text()));
redirect_to('admin-promociones', array('ok' => 'Promocion guardada.'));
}
function page_promos() {
$rows = all_rows("SELECT * FROM promotions ORDER BY created_at DESC LIMIT 12");
$html = '';
foreach ($rows as $p) {
$discount = trim((string)$p['discount']) !== '' ? $p['discount'] : 'PROMO';
$html .= '
' . e($p['title']) . ' ' . e($p['message']) . '
';
}
if (!$html) {
$html = 'Aun no hay promociones publicadas.
';
}
$content = '
';
page('Promos', $content, 'promos');
}
function admin_socials() {
$rows = all_rows("SELECT * FROM social_links ORDER BY active DESC, created_at DESC");
$html = '';
foreach ($rows as $s) {
$icon = $s['icon'] ? ' ' : '' . e(substr($s['name'], 0, 1)) . ' ';
$html .= '' . $icon . ' ' . e($s['name']) . ' ' . e($s['url']) . ' ' . csrf_field() . 'Eliminar ';
}
if (!$html) {
$html = 'Aun no agregaste redes. ';
}
$content = '
Redes sociales ';
admin_page('Redes', $content, 'redes');
}
function normalize_social_url($url) {
$url = trim($url);
if ($url && !preg_match('/^https?:\/\//i', $url)) {
$url = 'https://' . $url;
}
return $url;
}
function handle_social_create() {
check_csrf();
require_admin();
$icon = upload_file('icon', UPLOAD_SOCIAL_DIR, 'red', array('jpg','jpeg','png','webp'));
run_sql("INSERT INTO social_links (name, url, icon, active, created_at) VALUES (?, ?, ?, 1, ?)", array(trim($_POST['name']), normalize_social_url($_POST['url']), $icon, now_text()));
redirect_to('admin-redes', array('ok' => 'Red social agregada.'));
}
function handle_social_delete() {
check_csrf();
require_admin();
run_sql("DELETE FROM social_links WHERE id = ?", array((int)$_POST['id']));
redirect_to('admin-redes', array('ok' => 'Red social eliminada.'));
}
function serve_receipt() {
require_user();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$order = one("SELECT * FROM orders WHERE id = ?", array($id));
$user = current_user();
if (!$order || (!$user || ($user['role'] !== 'admin' && (int)$order['user_id'] !== (int)$user['id']))) {
http_response_code(403);
echo 'No autorizado.';
exit;
}
$file = BASE_PATH . '/' . $order['receipt_file'];
if (!is_file($file)) {
http_response_code(404);
echo 'Comprobante no encontrado.';
exit;
}
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$types = array('jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'pdf' => 'application/pdf');
header('Content-Type: ' . (isset($types[$ext]) ? $types[$ext] : 'application/octet-stream'));
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
function pdf_escape($text) {
$text = (string)$text;
if (function_exists('iconv')) {
$converted = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
if ($converted !== false) {
$text = $converted;
}
}
return str_replace(array('\\', '(', ')', "\r", "\n"), array('\\\\', '\\(', '\\)', ' ', ' '), $text);
}
function pdf_short($text, $max = 36) {
$text = trim((string)$text);
if (strlen($text) <= $max) {
return $text;
}
return substr($text, 0, max(0, $max - 3)) . '...';
}
function pdf_text_cmd($x, $y, $size, $text, $font = 'F1', $rgb = array(0.12,0.12,0.14)) {
return sprintf("BT /%s %.2f Tf %.3f %.3f %.3f rg %.2f %.2f Td (%s) Tj ET\n", $font, $size, $rgb[0], $rgb[1], $rgb[2], $x, $y, pdf_escape($text));
}
function jama_invoice_pdf($order, $items) {
$c = '';
// Fondo y cabecera premium.
$c .= "0.075 0.075 0.090 rg 0 760 595 82 re f\n";
$c .= "0.780 0.050 0.080 rg 0 754 595 6 re f\n";
$c .= pdf_text_cmd(38, 805, 24, 'JAMA STORE', 'F2', array(1,1,1));
$c .= pdf_text_cmd(39, 784, 10, 'Ropa urbana y perfumes | Peru', 'F1', array(0.82,0.82,0.84));
$c .= pdf_text_cmd(420, 806, 12, 'COMPROBANTE', 'F2', array(1,1,1));
$c .= pdf_text_cmd(420, 787, 10, 'Pedido #' . str_pad($order['id'], 6, '0', STR_PAD_LEFT), 'F1', array(0.88,0.88,0.90));
// Datos generales.
$c .= "0.965 0.965 0.972 rg 32 650 531 82 re f\n";
$c .= pdf_text_cmd(46, 710, 9, 'CLIENTE', 'F2', array(0.45,0.45,0.48));
$c .= pdf_text_cmd(46, 692, 12, pdf_short($order['customer_name'], 42), 'F2');
$c .= pdf_text_cmd(46, 675, 9, pdf_short($order['email'], 52), 'F1', array(0.32,0.32,0.35));
$c .= pdf_text_cmd(310, 710, 9, 'FECHA / PAGO', 'F2', array(0.45,0.45,0.48));
$c .= pdf_text_cmd(310, 692, 10, $order['created_at'], 'F1');
$c .= pdf_text_cmd(310, 675, 9, 'Yape: ' . pdf_short($order['yape_name'], 28), 'F1', array(0.32,0.32,0.35));
$c .= pdf_text_cmd(46, 635, 9, 'ENVIO', 'F2', array(0.45,0.45,0.48));
$c .= pdf_text_cmd(46, 619, 9, pdf_short($order['address'], 74), 'F1');
$c .= pdf_text_cmd(46, 603, 9, 'Agencia: ' . $order['shipping_agency'] . ' | Telefono: ' . $order['phone'], 'F1', array(0.32,0.32,0.35));
// Tabla.
$tableTop = 565;
$c .= "0.075 0.075 0.090 rg 32 " . ($tableTop - 4) . " 531 28 re f\n";
$c .= pdf_text_cmd(42, $tableTop + 5, 9, 'PRODUCTO', 'F2', array(1,1,1));
$c .= pdf_text_cmd(305, $tableTop + 5, 9, 'VARIANTE', 'F2', array(1,1,1));
$c .= pdf_text_cmd(400, $tableTop + 5, 9, 'CANT.', 'F2', array(1,1,1));
$c .= pdf_text_cmd(450, $tableTop + 5, 9, 'PRECIO', 'F2', array(1,1,1));
$c .= pdf_text_cmd(510, $tableTop + 5, 9, 'TOTAL', 'F2', array(1,1,1));
$y = $tableTop - 27;
$maxRows = 16;
$shown = 0;
foreach ($items as $it) {
if ($shown >= $maxRows) break;
if ($shown % 2 === 1) {
$c .= sprintf("0.975 0.975 0.980 rg 32 %.2f 531 24 re f\n", $y - 7);
}
$c .= pdf_text_cmd(42, $y, 9, pdf_short($it['product_name'], 36), 'F1');
$c .= pdf_text_cmd(305, $y, 8.5, pdf_short($it['size'], 14), 'F1');
$c .= pdf_text_cmd(408, $y, 9, (string)(int)$it['quantity'], 'F1');
$c .= pdf_text_cmd(450, $y, 8.5, money($it['price']), 'F1');
$c .= pdf_text_cmd(510, $y, 8.5, money($it['price'] * $it['quantity']), 'F2');
$c .= sprintf("0.88 0.88 0.90 RG 32 %.2f m 563 %.2f l S\n", $y - 10, $y - 10);
$y -= 27;
$shown++;
}
if (count($items) > $maxRows) {
$c .= pdf_text_cmd(42, $y, 8.5, '... y ' . (count($items) - $maxRows) . ' producto(s) mas', 'F1', array(0.40,0.40,0.43));
$y -= 24;
}
// Total y pie.
$totalBoxY = max(82, $y - 52);
$c .= sprintf("0.965 0.965 0.972 rg 335 %.2f 228 56 re f\n", $totalBoxY);
$c .= pdf_text_cmd(350, $totalBoxY + 34, 9, 'TOTAL PAGADO', 'F2', array(0.45,0.45,0.48));
$c .= pdf_text_cmd(444, $totalBoxY + 20, 19, money($order['total']), 'F2', array(0.72,0.04,0.07));
$c .= pdf_text_cmd(38, 50, 9, 'Gracias por comprar en JAMA Store.', 'F2');
$c .= pdf_text_cmd(38, 34, 7.5, 'Comprobante interno de pedido. No reemplaza un comprobante electronico emitido ante SUNAT.', 'F1', array(0.46,0.46,0.48));
$objects = array();
$objects[] = "1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n";
$objects[] = "2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n";
$objects[] = "3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> /Contents 4 0 R >> endobj\n";
$objects[] = "4 0 obj << /Length " . strlen($c) . " >> stream\n" . $c . "\nendstream\nendobj\n";
$objects[] = "5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj\n";
$objects[] = "6 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >> endobj\n";
$pdf = "%PDF-1.4\n";
$offsets = array(0);
foreach ($objects as $obj) {
$offsets[] = strlen($pdf);
$pdf .= $obj;
}
$xref = strlen($pdf);
$pdf .= "xref\n0 " . (count($objects) + 1) . "\n0000000000 65535 f \n";
for ($i = 1; $i <= count($objects); $i++) {
$pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
}
$pdf .= "trailer << /Size " . (count($objects) + 1) . " /Root 1 0 R >>\nstartxref\n" . $xref . "\n%%EOF";
return $pdf;
}
function admin_invoice_pdf() {
require_admin();
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$order = one("SELECT * FROM orders WHERE id = ?", array($id));
if (!$order || $order['payment_status'] !== 'Aprobado') {
redirect_to('admin-pedidos', array('err' => 'Primero aprueba el pago.'));
}
$items = all_rows("SELECT * FROM order_items WHERE order_id = ?", array($id));
$pdf = jama_invoice_pdf($order, $items);
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="comprobante-jama-' . (int)$order['id'] . '.pdf"');
header('Content-Length: ' . strlen($pdf));
echo $pdf;
exit;
}
function admin_backup() {
require_admin();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="jama-store-backup-' . date('Ymd-His') . '.sqlite"');
header('Content-Length: ' . filesize(DB_PATH));
readfile(DB_PATH);
exit;
}
function legal_page($title) {
$content = ' JAMA Store' . e($title) . ' Acceso y Navegación: El uso de nuestra plataforma es libre y gratuito. Al navegar en el sitio, el usuario se compromete a proporcionar información verídica y mantener un uso adecuado de las funcionalidades disponibles.
Disponibilidad de Stock: Las prendas y perfumes mostrados están sujetos a disponibilidad de stock. En caso de agotarse un producto finalizado el pedido, nos comunicaremos de inmediato para coordinar el cambio o la devolución del monto abonado.
Contacto: ' . e(CONTACT_EMAIL) . ' - ' . e(CONTACT_PHONE) . '
';
page($title, $content);
}
$route = current_route();
try {
switch ($route) {
case 'inicio': page_home(); break;
case 'catalogo': page_catalog(false); break;
case 'perfumes': page_catalog(true); break;
case 'producto': page_product(); break;
case 'promos': page_promos(); break;
case 'carrito': page_cart(); break;
case 'agregar': handle_add_to_cart(); break;
case 'carrito-actualizar': handle_cart_update(); break;
case 'carrito-eliminar': handle_cart_remove(); break;
case 'pedido-crear': handle_create_order(); break;
case 'pedido-exito': page_order_success(); break;
case 'mis-pedidos': page_my_orders(); break;
case 'pedido': page_customer_order(); break;
case 'login': $_SERVER['REQUEST_METHOD'] === 'POST' ? handle_login(false) : page_login(false); break;
case 'registro': $_SERVER['REQUEST_METHOD'] === 'POST' ? handle_register() : page_register(); break;
case 'logout': handle_logout(); break;
case 'google-login': handle_google_login(); break;
case 'google-callback': handle_google_callback(); break;
case 'admin-login': $_SERVER['REQUEST_METHOD'] === 'POST' ? handle_login(true) : page_login(true); break;
case 'admin': admin_dashboard(); break;
case 'admin-pedidos': admin_orders(); break;
case 'admin-pedido': admin_order_detail(); break;
case 'admin-pedido-estado': handle_admin_order_status(); break;
case 'admin-pedido-eliminar': handle_admin_order_delete(); break;
case 'admin-ganancia-reiniciar': handle_admin_earnings_reset(); break;
case 'admin-productos': admin_products(); break;
case 'admin-productos-masivo': admin_products_bulk_form(); break;
case 'admin-productos-masivo-guardar': handle_products_bulk_save(); break;
case 'admin-producto-form': admin_product_form(); break;
case 'admin-producto-crear': handle_product_save(true); break;
case 'admin-producto-guardar': handle_product_save(false); break;
case 'admin-producto-eliminar': handle_product_active(false); break;
case 'admin-producto-restaurar': handle_product_active(true); break;
case 'admin-clientes': admin_clients(); break;
case 'admin-promociones': admin_promotions(); break;
case 'admin-promo-crear': handle_promotion_create(); break;
case 'admin-redes': admin_socials(); break;
case 'admin-redes-crear': handle_social_create(); break;
case 'admin-redes-eliminar': handle_social_delete(); break;
case 'admin-boleta': admin_invoice_pdf(); break;
case 'admin-backup': admin_backup(); break;
case 'comprobante': serve_receipt(); break;
case 'configuracion-cookies': legal_page('Configuracion de las cookies'); break;
case 'terminos-y-condiciones': legal_page('Terminos y Condiciones'); break;
case 'terminos-promociones': legal_page('Terminos y Condiciones Promociones'); break;
case 'politica-privacidad': legal_page('Politica de Privacidad'); break;
case 'seleccionar-pais': legal_page('Seleccionar Pais'); break;
case 'uso-del-sitio': legal_page('Uso del Sitio'); break;
default:
http_response_code(404);
page('No encontrado', 'Pagina no encontrada.
');
}
} catch (Exception $e) {
http_response_code(500);
page('Error', 'Error interno Revisa que PHP tenga SQLite activo y que las carpetas data, uploads y private tengan permiso de escritura.
' . e($e->getMessage()) . ' ');
}
?>