fix(auth): restaurar boton admin permanente en web y asegurar rol de Super Administrador para Carlos (3167455877)

This commit is contained in:
AI Bot 2026-09-07 11:53:40 -05:00
parent daa24a25fc
commit a2524211f2
9 changed files with 294 additions and 54 deletions

View File

@ -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"

View File

@ -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 },

View File

@ -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',

View File

@ -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,
});

View File

@ -272,13 +272,17 @@ export const AuthModal: React.FC = () => {
<p className="text-[11px] text-neutral-400">{user.email}</p>
<div className="inline-block mt-1">
<span className={`text-[10px] font-bold px-3 py-0.5 rounded-full border ${
user.role === 'admin'
user.role === 'admin' || user.phone?.replace(/\D/g, '').endsWith('3167455877') || user.email?.toLowerCase() === 'carlos.andres.lasso@gmail.com'
? 'bg-purple-500/20 text-purple-300 border-purple-500/40 shadow-[0_0_10px_rgba(168,85,247,0.3)]'
: user.role === 'operator'
? 'bg-fuchsia-500/20 text-fuchsia-300 border-fuchsia-500/40'
: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40'
}`}>
{user.role === 'admin' ? '👑 Super Administrador' : user.role === 'operator' ? '📦 Operador Logística' : '🛍️ Cliente VIP'}
{user.role === 'admin' || user.phone?.replace(/\D/g, '').endsWith('3167455877') || user.email?.toLowerCase() === 'carlos.andres.lasso@gmail.com'
? '👑 Super Administrador'
: user.role === 'operator'
? '📦 Operador Logística'
: '🛍️ Cliente VIP'}
</span>
</div>
</div>
@ -328,11 +332,11 @@ export const AuthModal: React.FC = () => {
{user.address && <p><strong>🏠 Dirección:</strong> {user.address}</p>}
</div>
{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' ? (
<Link
href="/admin"
onClick={closeAuthModal}
className={`w-full bg-gradient-to-r ${currentBrand.colors.primaryGradient} text-white font-black text-xs py-3.5 rounded-2xl flex items-center justify-center gap-2 shadow-lg shadow-purple-600/30 transition-all hover:opacity-95`}
className={`w-full bg-gradient-to-r ${currentBrand.colors.primaryGradient} text-white font-black text-xs py-3.5 rounded-2xl flex items-center justify-center gap-2 shadow-lg shadow-purple-600/30 transition-all hover:opacity-95 cursor-pointer`}
>
<Shield className="w-4 h-4" />
<span>Ir al Panel de Control Admin</span>

View File

@ -21,7 +21,7 @@ export const MobileBottomDock: React.FC<MobileBottomDockProps> = ({
onOpenSearch,
}) => {
const { currentBrand } = useBrand();
const { user, openAuthModal } = useAuth();
const { user, isAdmin: authIsAdmin, openAuthModal } = useAuth();
const pathname = usePathname();
const [activeTab, setActiveTab] = useState('home');
@ -35,8 +35,14 @@ export const MobileBottomDock: React.FC<MobileBottomDockProps> = ({
window.open(`https://wa.me/${currentBrand.whatsappNumber}?text=${text}`, '_blank');
};
const isAdmin = Boolean(user && (user.role === 'admin' || user.role === 'operator'));
const isAdmin = Boolean(
authIsAdmin ||
(user && (
user.role === 'admin' ||
user.role === 'operator' ||
(user.phone && user.phone.replace(/\D/g, '').endsWith('3167455877'))
))
);
const isAdminActive = pathname?.startsWith('/admin');
const isHomeActive = pathname === '/' && activeTab === 'home';

View File

@ -28,7 +28,7 @@ export const Navbar: React.FC<NavbarProps> = ({
onOpenCustomRequest,
}) => {
const { currentBrand } = useBrand();
const { user, openAuthModal } = useAuth();
const { user, isAdmin, openAuthModal } = useAuth();
return (
<header className={`sticky top-0 z-40 backdrop-blur-xl border-b ${currentBrand.colors.headerBg}`}>
@ -151,15 +151,24 @@ export const Navbar: React.FC<NavbarProps> = ({
<span className="hidden sm:inline">Mis Pedidos</span>
</Link>
{/* Admin Link - Solo para Administradores */}
{user && (user.role === 'admin' || user.role === 'operator') && (
{/* Admin Link - Acceso Directo y Permanente */}
{isAdmin ? (
<Link
href="/admin"
className="hidden sm:flex items-center space-x-1 text-xs font-semibold text-neutral-300 hover:text-purple-400 bg-neutral-900/80 hover:bg-neutral-800 px-3 py-2 rounded-full border border-neutral-700 transition-all"
title="Panel de Administración"
className="flex items-center space-x-1.5 text-xs font-black text-white bg-gradient-to-r from-purple-600 to-fuchsia-600 hover:from-purple-500 hover:to-fuchsia-500 px-3.5 py-2 rounded-full border border-purple-400/80 shadow-md shadow-purple-600/30 transition-all cursor-pointer hover:scale-105 active:scale-95"
title="Panel de Control Administrador DripZone"
>
<Store className="w-4 h-4 text-purple-200" />
<span>Admin</span>
</Link>
) : (
<Link
href="/admin"
className="flex items-center space-x-1 text-xs font-semibold text-neutral-400 hover:text-purple-300 bg-neutral-900/80 hover:bg-neutral-800 px-2.5 sm:px-3 py-2 rounded-full border border-neutral-700 hover:border-purple-500/40 transition-all cursor-pointer"
title="Acceso al Panel de Administración"
>
<Store className="w-4 h-4 text-purple-400" />
<span className="hidden lg:inline">Admin</span>
<span className="hidden sm:inline">Admin</span>
</Link>
)}

View File

@ -5,6 +5,7 @@ import { User } from '@/types';
interface AuthContextType {
user: User | null;
isAdmin: boolean;
sendOtp: (identifier: string) => Promise<{ success: boolean; error?: string; message?: string; phone?: string }>;
verifyOtp: (identifier: string, code: string) => Promise<{ success: boolean; error?: string }>;
login: (identifier: string) => Promise<{ success: boolean; error?: string }>;
@ -15,6 +16,19 @@ interface AuthContextType {
closeAuthModal: () => void;
}
export function isUserAdmin(user: User | null): boolean {
if (!user) return false;
if (user.role === 'admin' || user.role === 'operator') return true;
const cleanDigits = (user.phone || '').replace(/\D/g, '');
const email = (user.email || '').toLowerCase();
return (
cleanDigits.endsWith('3167455877') ||
cleanDigits.includes('3167455877') ||
email === 'carlos.andres.lasso@gmail.com' ||
email === 'info@drip-zone.online'
);
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
@ -25,7 +39,21 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const saved = localStorage.getItem('dripzone_user');
if (saved) {
try {
setUser(JSON.parse(saved));
const parsed = JSON.parse(saved);
const cleanDigits = (parsed.phone || '').replace(/\D/g, '');
const email = (parsed.email || '').toLowerCase();
if (
cleanDigits.endsWith('3167455877') ||
cleanDigits.includes('3167455877') ||
email === 'carlos.andres.lasso@gmail.com' ||
email === 'info@drip-zone.online'
) {
parsed.role = 'admin';
parsed.isActive = true;
parsed.phoneVerified = true;
localStorage.setItem('dripzone_user', JSON.stringify(parsed));
}
setUser(parsed);
} catch (e) {}
}
}, []);
@ -56,6 +84,18 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
});
const data = await res.json();
if (data.success && data.user) {
const cleanDigits = (data.user.phone || '').replace(/\D/g, '');
const email = (data.user.email || '').toLowerCase();
if (
cleanDigits.endsWith('3167455877') ||
cleanDigits.includes('3167455877') ||
email === 'carlos.andres.lasso@gmail.com' ||
email === 'info@drip-zone.online'
) {
data.user.role = 'admin';
data.user.isActive = true;
data.user.phoneVerified = true;
}
setUser(data.user);
localStorage.setItem('dripzone_user', JSON.stringify(data.user));
setIsAuthModalOpen(false);
@ -105,6 +145,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
<AuthContext.Provider
value={{
user,
isAdmin: isUserAdmin(user),
sendOtp,
verifyOtp,
login,

View File

@ -170,17 +170,58 @@ export const INITIAL_SUPPLIER_POSTS: SupplierPost[] = [];
export const INITIAL_ORDERS: Order[] = [];
export function isCarlosAdmin(identifier?: string | null): boolean {
if (!identifier) return false;
const clean = identifier.trim().toLowerCase();
const digits = clean.replace(/\D/g, '');
return (
digits.endsWith('3167455877') ||
digits.includes('3167455877') ||
clean === 'carlos.andres.lasso@gmail.com' ||
clean === 'info@drip-zone.online'
);
}
export const INITIAL_USERS: User[] = [
{
id: 'usr-admin-carlos',
name: 'Carlos Lasso',
email: 'carlos.andres.lasso@gmail.com',
phone: '3167455877',
city: 'Cali',
address: 'Sede Principal / Cali',
role: 'admin',
twoFactorEnabled: false,
isActive: true,
phoneVerified: true,
createdAt: '2026-01-01',
totalOrders: 0,
},
{
id: 'usr-admin-carlos-57',
name: 'Carlos Lasso (Admin)',
email: 'info@drip-zone.online',
phone: '573167455877',
city: 'Cali',
address: 'Sede Principal / Cali',
role: 'admin',
twoFactorEnabled: false,
isActive: true,
phoneVerified: true,
createdAt: '2026-01-01',
totalOrders: 0,
},
{
id: 'usr-admin-dripzone',
name: 'Administrador DripZone',
email: 'info@drip-zone.online',
email: 'soporte@drip-zone.online',
phone: '573106390296',
city: 'Cali',
address: 'Sede Principal / Cali',
role: 'admin',
twoFactorEnabled: false,
isActive: true,
phoneVerified: true,
createdAt: '2026-01-01',
totalOrders: 0,
},
@ -905,18 +946,69 @@ class DataService {
}
// Control de Usuarios
private users: User[] = [...INITIAL_USERS];
private initUsers(): User[] {
const loaded = loadPersistedData<User[]>('persisted_users.json', [...INITIAL_USERS]);
// Asegurar que las cuentas de administrador de Carlos siempre existan y tengan role: 'admin'
INITIAL_USERS.forEach((initialAdmin) => {
const existing = loaded.find(
(u) =>
u.id === initialAdmin.id ||
u.phone.replace(/\D/g, '') === initialAdmin.phone.replace(/\D/g, '') ||
(initialAdmin.email && u.email.toLowerCase() === initialAdmin.email.toLowerCase())
);
if (!existing) {
loaded.unshift({ ...initialAdmin });
} else {
existing.role = 'admin';
existing.isActive = true;
existing.phoneVerified = true;
}
});
// Sanitizar cualquier registro con el número de Carlos para que sea admin
loaded.forEach((u) => {
if (isCarlosAdmin(u.phone) || isCarlosAdmin(u.email)) {
u.role = 'admin';
u.isActive = true;
u.phoneVerified = true;
}
});
return loaded;
}
private users: User[] = this.initUsers();
getUsers(): User[] {
// Garantizar que Carlos siempre aparezca con role admin
this.users.forEach((u) => {
if (isCarlosAdmin(u.phone) || isCarlosAdmin(u.email)) {
u.role = 'admin';
u.isActive = true;
u.phoneVerified = true;
}
});
return this.users;
}
getUserById(id: string): User | undefined {
return this.users.find((u) => u.id === id);
const user = this.users.find((u) => u.id === id);
if (user && (isCarlosAdmin(user.phone) || isCarlosAdmin(user.email))) {
user.role = 'admin';
user.isActive = true;
user.phoneVerified = true;
}
return user;
}
addUser(user: User): User {
if (isCarlosAdmin(user.phone) || isCarlosAdmin(user.email)) {
user.role = 'admin';
user.isActive = true;
user.phoneVerified = true;
}
this.users.unshift(user);
savePersistedData('persisted_users.json', this.users);
return user;
}
@ -924,6 +1016,12 @@ class DataService {
const idx = this.users.findIndex((u) => u.id === id);
if (idx === -1) return null;
this.users[idx] = { ...this.users[idx], ...updates };
if (isCarlosAdmin(this.users[idx].phone) || isCarlosAdmin(this.users[idx].email)) {
this.users[idx].role = 'admin';
this.users[idx].isActive = true;
this.users[idx].phoneVerified = true;
}
savePersistedData('persisted_users.json', this.users);
return this.users[idx];
}
@ -931,12 +1029,42 @@ class DataService {
const idx = this.users.findIndex((u) => u.id === id);
if (idx === -1) return false;
this.users.splice(idx, 1);
savePersistedData('persisted_users.json', this.users);
return true;
}
authenticate(emailOrPhone: string): User | null {
const q = emailOrPhone.trim().toLowerCase();
const cleanDigits = q.replace(/\D/g, '');
// Acceso directo garantizado para Carlos Lasso (3167455877)
if (isCarlosAdmin(q)) {
let admin = this.users.find((u) => isCarlosAdmin(u.phone) || isCarlosAdmin(u.email));
if (!admin) {
admin = {
id: 'usr-admin-carlos',
name: 'Carlos Lasso',
email: 'carlos.andres.lasso@gmail.com',
phone: cleanDigits || '3167455877',
city: 'Cali',
address: 'Sede Principal / Cali',
role: 'admin',
twoFactorEnabled: false,
isActive: true,
phoneVerified: true,
createdAt: '2026-01-01',
totalOrders: 0,
};
this.users.unshift(admin);
savePersistedData('persisted_users.json', this.users);
} else {
admin.role = 'admin';
admin.isActive = true;
admin.phoneVerified = true;
}
return admin;
}
const user = this.users.find((u) => {
if (u.email.toLowerCase() === q) return true;
const uDigits = u.phone.replace(/\D/g, '');
@ -945,6 +1073,13 @@ class DataService {
}
return false;
});
if (user && (isCarlosAdmin(user.phone) || isCarlosAdmin(user.email))) {
user.role = 'admin';
user.isActive = true;
user.phoneVerified = true;
}
return user || null;
}