diff --git a/deploy_dripzone_vps.py b/deploy_dripzone_vps.py index 9479567..6257241 100644 --- a/deploy_dripzone_vps.py +++ b/deploy_dripzone_vps.py @@ -37,7 +37,19 @@ def deploy(): print(f"[2/4] Conectando a VPS {HOST} via SSH / SFTP...") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - client.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=30) + connected = False + for attempt in range(1, 4): + try: + print(f" Intento {attempt} de conexion a {HOST}...") + client.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=60, banner_timeout=60, auth_timeout=60) + connected = True + break + except Exception as conn_err: + print(f" Aviso: fallo en intento {attempt}: {conn_err}") + import time + time.sleep(2) + if not connected: + raise Exception(f"No se pudo conectar a {HOST} tras 3 intentos") sftp = client.open_sftp() print("[3/4] Subiendo archivo comprimido y APK al servidor...") @@ -115,20 +127,21 @@ def send_apk_to_whatsapp(): import urllib.request import json print("[5/5] Enviando APK y reporte de actualizacion al WhatsApp de Carlos (+57 316 745 5877)...") - url = "https://evolution.almeira.com.co/message/sendMedia/DripZone" + url = "https://evolution.almeira.com.co/message/sendMedia/AutomataAlmeira" headers = { - "apikey": "83B5BD230796-4324-AE73-3508F69E279D", + "apikey": "EE49029EEB85-4568-8F81-C9FDFC6E2943", "Content-Type": "application/json" } caption = ( - "📲 *¡DRIPZONE ACTUALIZADA EN PRODUCCIÓN!* 🚀👟\n" + "👑 *¡ACCESO DE ADMINISTRADOR RESTAURADO & APK COMPILADO!* 🚀👟\n" "━━━━━━━━━━━━━━━━━━━━━━\n" - "✨ *Novedades de esta versión:*\n" - "• 📦 *Nueva Vista Dedicada 'Mis Pedidos'*: Interfaz completa y no modal en `/mis-pedidos` con pedidos agrupados por estado (Pendiente de anticipo, Anticipo aprobado, Despachados, Entregados, Cancelados), mostrando información clave (ID, fecha, total, anticipo Bold, contraentrega) y detalle desplegable con 1-clic.\n" - "• 🛒 *Selección Limpia de Producto*: Eliminado botón y banners de pedir por WhatsApp en la vitrina de producto, canalizando la compra de forma directa al carrito y checkout seguro.\n" - "• 📱 *Navegación Móvil Mejorada*: Reemplazado botón duplicado de Asesor por acceso directo a 'Mis Pedidos' en el dock inferior y en el menú superior.\n" - "• 💬 *Atención Comercial en WhatsApp & Pagos en Web/App*: Consultas por chat y procesamiento seguro de órdenes y pasarela Bold ($10k anticipo) en la app.\n\n" - "🌐 *Descarga directa del APK:*\n👉 https://drip-zone.online/DripZone.apk" + "✨ *Ajustes implementados y desplegados en Producción:*\n\n" + "1️⃣ 🛡️ *Botón de Admin Permanente en la Web*: Restaurado de forma visible y accesible en la barra superior de navegación de https://drip-zone.online. Ahora está siempre disponible para ingresar y destacado con gradiente morado/fucsia cuando inicias sesión.\n" + "2️⃣ 👑 *Rol de Super Administrador para Carlos*: Tu celular personal *3167455877* (+57 316 745 5877) quedó configurado como Administrador Maestro incondicional en el servicio de datos (`dataService`), sesión web (`AuthContext`), API de usuarios y panel de control (`/admin`), con badge '👑 Super Administrador'.\n" + "3️⃣ 🚀 *Despliegue en Servidor VPS*: Contenedor `dripzone-production` reconstruido e iniciado con éxito en el servidor de producción.\n" + "4️⃣ 📲 *APK Android Actualizado*: Compilado y disponible para descarga directa.\n\n" + "👉 *Panel de Administración:* https://drip-zone.online/admin\n" + "📥 *Descarga directa del APK:* https://drip-zone.online/DripZone.apk" ) payload = { "number": "573167455877", @@ -144,11 +157,10 @@ def send_apk_to_whatsapp(): print(f"[OK] APK despachada con exito al WhatsApp de Carlos (HTTP {resp.status})") except Exception as e: print(f"[Aviso] No se pudo enviar archivo multimedia por Evolution: {e}. Enviando mensaje de texto...") - text_url = "https://evolution.almeira.com.co/message/sendText/DripZone" + text_url = "https://evolution.almeira.com.co/message/sendText/AutomataAlmeira" text_payload = { "number": "573167455877", "text": ( - "📲 *¡DRIPZONE APK ACTUALIZADA EN PRODUCCIÓN!* 🚀👟\n\n" f"{caption}\n\n" "📥 *Descarga e instala el APK actualizado aquí:*\n" "👉 https://drip-zone.online/DripZone.apk" diff --git a/dripzone-app/src/app/admin/layout.tsx b/dripzone-app/src/app/admin/layout.tsx index 0bf8c8c..483686a 100644 --- a/dripzone-app/src/app/admin/layout.tsx +++ b/dripzone-app/src/app/admin/layout.tsx @@ -27,8 +27,16 @@ import { AuthModal } from '@/components/auth/AuthModal'; export default function AdminLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); - const { user, openAuthModal } = useAuth(); - const isAdmin = Boolean(user && (user.role === 'admin' || user.role === 'operator')); + const { user, isAdmin: authIsAdmin, openAuthModal } = useAuth(); + const isAdmin = Boolean( + authIsAdmin || + (user && ( + user.role === 'admin' || + user.role === 'operator' || + (user.phone && user.phone.replace(/\D/g, '').endsWith('3167455877')) || + (user.email && (user.email.toLowerCase() === 'carlos.andres.lasso@gmail.com' || user.email.toLowerCase() === 'info@drip-zone.online')) + )) + ); const NAV_ITEMS = [ { href: '/admin', label: 'Resumen & KPIs', icon: LayoutDashboard }, diff --git a/dripzone-app/src/app/api/auth/verify-otp/route.ts b/dripzone-app/src/app/api/auth/verify-otp/route.ts index 9e9f8bf..f8c8dcd 100644 --- a/dripzone-app/src/app/api/auth/verify-otp/route.ts +++ b/dripzone-app/src/app/api/auth/verify-otp/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import { dataService } from '@/lib/data-service'; +import { dataService, isCarlosAdmin } from '@/lib/data-service'; import { otpStore } from '@/lib/otp-store'; export async function POST(request: Request) { @@ -16,25 +16,30 @@ export async function POST(request: Request) { return NextResponse.json({ success: false, error: verification.reason || 'Código inválido o expirado' }, { status: 401 }); } + const isCarlos = isCarlosAdmin(cleanInput); + // Código válido -> Obtener o crear usuario registrado en la plataforma let user = dataService.authenticate(cleanInput); if (!user) { - // Si es un nuevo cliente autenticado por primera vez con su celular + // Si es un nuevo cliente o administrador autenticado por primera vez con su celular const cleanPhone = cleanInput.replace(/\D/g, ''); - const assignedName = name?.trim() || `Cliente Verificado (${cleanPhone.slice(-4)})`; + const assignedName = isCarlos + ? (name?.trim() || 'Carlos Lasso') + : (name?.trim() || `Cliente Verificado (${cleanPhone.slice(-4)})`); + user = dataService.addUser({ - id: `usr-${Date.now()}`, + id: isCarlos ? 'usr-admin-carlos' : `usr-${Date.now()}`, name: assignedName, - email: `${cleanPhone}@cliente.dripzone.co`, + email: isCarlos ? 'carlos.andres.lasso@gmail.com' : `${cleanPhone}@cliente.dripzone.co`, phone: cleanPhone, city: city?.trim() || 'Cali', - address: address?.trim() || '', - role: 'customer', + address: address?.trim() || (isCarlos ? 'Sede Principal / Cali' : ''), + role: isCarlos ? 'admin' : 'customer', isActive: true, phoneVerified: true, requiresShippingDeposit: false, - depositReason: 'Número verificado vía WhatsApp OTP', + depositReason: isCarlos ? 'Super Administrador' : 'Número verificado vía WhatsApp OTP', rejectedOrdersCount: 0, createdAt: new Date().toISOString().split('T')[0], totalOrders: 0, @@ -44,12 +49,15 @@ export async function POST(request: Request) { // Usuario existente: si verificó su teléfono, actualizar estado y datos si se suministraron const hadRejections = (user.rejectedOrdersCount || 0) > 0; dataService.updateUser(user.id, { - name: name?.trim() || user.name, + name: isCarlos ? (name?.trim() || user.name || 'Carlos Lasso') : (name?.trim() || user.name), city: city?.trim() || user.city, address: address?.trim() || user.address, + role: isCarlos ? 'admin' : user.role, phoneVerified: true, - requiresShippingDeposit: hadRejections ? true : false, - depositReason: hadRejections + requiresShippingDeposit: hadRejections && !isCarlos ? true : false, + depositReason: isCarlos + ? 'Super Administrador' + : hadRejections ? user.depositReason : 'Número de WhatsApp verificado con éxito (Pago 100% contraentrega habilitado)', lastLogin: 'Justo ahora', diff --git a/dripzone-app/src/app/api/users/route.ts b/dripzone-app/src/app/api/users/route.ts index d331bc7..23445a3 100644 --- a/dripzone-app/src/app/api/users/route.ts +++ b/dripzone-app/src/app/api/users/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import { dataService } from '@/lib/data-service'; +import { dataService, isCarlosAdmin } from '@/lib/data-service'; import { User } from '@/types'; export async function GET(request: Request) { @@ -23,21 +23,38 @@ export async function GET(request: Request) { ? 'Garantía de separación requerida para primer pedido' : 'Cliente distinguido exento de anticipo (100% Contraentrega)'; + const isCarlos = isCarlosAdmin(cleanPhone); + const userRole = isCarlos ? 'admin' : (user?.role || 'customer'); + return NextResponse.json({ success: true, - isFirstPurchase: !hasCompletedPreviousOrder, - requiresShippingDeposit, - depositAmount, - depositReason, - phoneVerified: user ? user.phoneVerified === true : false, + isFirstPurchase: isCarlos ? false : !hasCompletedPreviousOrder, + requiresShippingDeposit: isCarlos ? false : requiresShippingDeposit, + depositAmount: isCarlos ? 0 : depositAmount, + depositReason: isCarlos ? 'Super Administrador DripZone' : depositReason, + phoneVerified: isCarlos ? true : (user ? user.phoneVerified === true : false), user: user ? { - id: user.id, - name: user.name, - phoneVerified: user.phoneVerified, - totalOrders: user.totalOrders || existingOrders.length, - requiresShippingDeposit, - depositReason, + ...user, + role: userRole, + phoneVerified: isCarlos ? true : user.phoneVerified, + requiresShippingDeposit: isCarlos ? false : requiresShippingDeposit, + depositReason: isCarlos ? 'Super Administrador DripZone' : depositReason, + } + : isCarlos + ? { + id: 'usr-admin-carlos', + name: 'Carlos Lasso', + email: 'carlos.andres.lasso@gmail.com', + phone: cleanPhone, + city: 'Cali', + address: 'Sede Principal / Cali', + role: 'admin', + isActive: true, + phoneVerified: true, + requiresShippingDeposit: false, + depositReason: 'Super Administrador DripZone', + totalOrders: 0, } : null, }); diff --git a/dripzone-app/src/components/auth/AuthModal.tsx b/dripzone-app/src/components/auth/AuthModal.tsx index 4100931..189983c 100644 --- a/dripzone-app/src/components/auth/AuthModal.tsx +++ b/dripzone-app/src/components/auth/AuthModal.tsx @@ -272,13 +272,17 @@ export const AuthModal: React.FC = () => {
{user.email}
🏠 Dirección: {user.address}
} - {user.role === 'admin' || user.role === 'operator' ? ( + {user.role === 'admin' || user.role === 'operator' || user.phone?.replace(/\D/g, '').endsWith('3167455877') || user.email?.toLowerCase() === 'carlos.andres.lasso@gmail.com' ? (