1/** 2 * Sistema de Autenticación con Firebase Authentication 3 */ 4 5import { initializeApp, getApps } from 'https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js'; 6import { 7 getAuth, 8 signInWithEmailAndPassword, 9 signOut, 10 onAuthStateChanged, 11 sendPasswordResetEmail, 12 createUserWithEmailAndPassword, 13 updateProfile 14} from 'https://www.gstatic.com/firebasejs/12.6.0/firebase-auth.js'; 15import { 16 getFirestore, 17 doc, getDoc, setDoc, collection, getDocs, updateDoc 18} from 'https://www.gstatic.com/firebasejs/12.6.0/firebase-firestore.js'; 19 20// Configuración Firebase â proyecto correcto 21const firebaseConfig = { 22 apiKey: "AIzaSyB9-4eJ0P5K6jmNW0huxcs7ISyx0lCCjcw", 23 authDomain: "ail-lab-sistema-cristales.firebaseapp.com", 24 projectId: "ail-lab-sistema-cristales", 25 storageBucket: "ail-lab-sistema-cristales.firebasestorage.app", 26 messagingSenderId: "788992440656", 27 appId: "1:788992440656:web:2584c08358cd256cdb9455" 28}; 29 30// Inicializar Firebase solo si no está ya inicializado 31const app = getApps().length === 0 32 ? initializeApp(firebaseConfig) 33 : getApps()[0]; 34 35const auth = getAuth(app); 36const db = getFirestore(app); 37 38// Exponer globalmente para otros módulos 39window.db = db; 40window.auth = auth; 41 42class FirebaseAuthSystem { 43 normalizeRole(rawRole) { 44 const role = String(rawRole || '') 45 .trim() 46 .toLowerCase() 47 .normalize('NFD') 48 .replace(/[\u0300-\u036f]/g, '') 49 .replace(/\s+/g, '_'); 50 if (role === 'encargao') return 'encargado'; 51 if (role === 'perfil_losada' || role === 'perfil_jefe_optica_losada') { 52 return 'perfil_jefe_optica_losada'; 53 } 54 if (role === 'perfil_encargado_optica_losada') { 55 return 'perfil_encargado_optica_losada'; 56 } 57 return role || 'optica'; 58 } 59 60 constructor() { 61 this.currentUser = null; 62 this.authReady = false; 63 this.authReadyCallbacks = []; 64 65 onAuthStateChanged(auth, async (firebaseUser) => { 66 if (firebaseUser) { 67 const userData = await this.loadUserData(firebaseUser.uid); 68 const resolvedRole = this.normalizeRole( 69 userData ? (userData.role || userData.rol || 'optica') : 'optica' 70 ); 71 this.currentUser = { 72 uid: firebaseUser.uid, 73 email: firebaseUser.email, 74 username: firebaseUser.email, 75 name: userData ? userData.name : firebaseUser.email, 76 role: resolvedRole, 77 rol: resolvedRole, 78 empresaId: userData ? (userData.empresaId || null) : null, 79 sucursalId: userData ? (userData.sucursalId || null) : null, 80 clientId: userData ? userData.clientId : null, 81 losadaEncargadoCompraNormal: userData?.losadaEncargadoCompraNormal === true, 82 permissions: userData ? userData.permissions : ['view_orders', 'create_orders', 'edit_own_orders'] 83 }; 84 } else { 85 this.currentUser = null; 86 } 87 88 this.authReady = true; 89 this.authReadyCallbacks.forEach(cb => cb(this.currentUser)); 90 this.authReadyCallbacks = []; 91 }); 92 } 93 94 onReady(callback) { 95 if (this.authReady) { 96 callback(this.currentUser); 97 } else { 98 this.authReadyCallbacks.push(callback); 99 } 100 } 101 102 async login(email, password) { 103 try { 104 console.log('ð Intentando login con:', email); 105 const userCredential = await signInWithEmailAndPassword(auth, email, password); 106 const firebaseUser = userCredential.user; 107 console.log('â Firebase Auth OK, UID:', firebaseUser.uid); 108 109 const userData = await this.loadUserData(firebaseUser.uid); 110 console.log('ð Datos Firestore:', userData); 111 112 if (!userData) { 113 await signOut(auth); 114 throw new Error('Usuario no configurado. Contacte al administrador.'); 115 } 116 117 const resolvedRole = this.normalizeRole(userData.role || userData.rol || 'optica'); 118 this.currentUser = { 119 uid: firebaseUser.uid, 120 email: firebaseUser.email, 121 username: firebaseUser.email, 122 name: userData.name || firebaseUser.email, 123 role: resolvedRole, 124 rol: resolvedRole, 125 empresaId: userData.empresaId || null, 126 sucursalId: userData.sucursalId || null, 127 clientId: userData.clientId || null, 128 losadaEncargadoCompraNormal: userData.losadaEncargadoCompraNormal === true, 129 permissions: userData.permissions || ['view_orders', 'create_orders', 'edit_own_orders'] 130 }; 131 132 console.log('â Login completo:', this.currentUser); 133 return this.currentUser; 134 135 } catch (error) { 136 console.error('â Error login completo:', error); 137 if (error.code) { 138 throw new Error(this.translateError(error.code)); 139 } 140 throw error; 141 } 142 } 143 144 async logout() { 145 await signOut(auth); 146 this.currentUser = null; 147 } 148 149 isAuthenticated() { 150 return this.currentUser !== null; 151 } 152 153 getCurrentUser() { 154 return this.currentUser; 155 } 156 157 isAdmin() { 158 return this.currentUser && this.currentUser.role === 'admin'; 159 } 160 161 isOptica() { 162 return this.currentUser && [ 163 'optica', 164 'encargado', 165 'jefe_empresa', 166 'jefe_sucursal', 167 'perfil_jefe_optica_losada', 168 'perfil_encargado_optica_losada' 169 ].includes(this.currentUser.role); 170 } 171 172 async loadUserData(uid) { 173 try { 174 const docRef = doc(db, 'usuarios', uid); 175 const docSnap = await getDoc(docRef); 176 if (docSnap.exists()) { 177 console.log('â Documento encontrado en Firestore'); 178 return docSnap.data(); 179 } else { 180 console.warn('â ï¸ No existe documento para UID:', uid); 181 return null; 182 } 183 } catch (error) { 184 console.error('â Error cargando datos usuario:', error); 185 return null; 186 } 187 } 188 189 async createUser(email, password, userData) { 190 if (!this.isAdmin()) throw new Error('Solo el administrador puede crear usuarios'); 191 192 const rolesValidos = [ 193 'admin', 194 'jefe_empresa', 195 'jefe_sucursal', 196 'encargado', 197 'encargao', 198 'optica', 199 'perfil_jefe_optica_losada', 200 'perfil_losada', 201 'perfil_encargado_optica_losada', 202 'perfil_encargado_optica_losada_compra' 203 ]; 204 const formRole = userData.role || userData.rol || 'optica'; 205 if ( 206 !rolesValidos.includes(formRole) && 207 !rolesValidos.includes(this.normalizeRole(formRole)) 208 ) { 209 throw new Error('rol-invalido'); 210 } 211 212 // Validar asociaciones según rol 213 const losadaCompraNormal = 214 formRole === 'perfil_encargado_optica_losada_compra' || userData.losadaEncargadoCompraNormal === true; 215 const rol = this.normalizeRole( 216 formRole === 'perfil_encargado_optica_losada_compra' ? 'perfil_encargado_optica_losada' : formRole 217 ); 218 if ((rol === 'encargado' || rol === 'perfil_encargado_optica_losada') && !userData.sucursalId) { 219 throw new Error('sucursal-requerida'); 220 } 221 if ( 222 (rol === 'jefe_empresa' || rol === 'jefe_sucursal' || rol === 'perfil_encargado_optica_losada') && 223 !userData.empresaId 224 ) { 225 throw new Error('empresa-requerida'); 226 } 227 228 try { 229 // IMPORTANT: no usar el auth principal para crear usuarios, 230 // porque cambiarÃa la sesión del admin. Se usa un auth secundario. 231 const existingSecondary = getApps().find(a => a.name === 'secondary-auth'); 232 const secondaryApp = existingSecondary || initializeApp(firebaseConfig, 'secondary-auth'); 233 const secondaryAuth = getAuth(secondaryApp); 234 235 const userCredential = await createUserWithEmailAndPassword(secondaryAuth, email, password); 236 const newUser = userCredential.user; 237 await updateProfile(newUser, { displayName: userData.name }); 238 239 const permissions = rol === 'admin' 240 ? ['view_all_orders', 'create_orders', 'edit_all_orders', 'delete_orders', 'manage_users', 'view_reports', 'manage_inventory', 'manage_settings'] 241 : (rol === 'jefe_empresa' || rol === 'jefe_sucursal') 242 ? ['view_company_orders', 'create_orders', 'manage_credit'] 243 : ['view_orders', 'create_orders', 'edit_own_orders']; 244 245 await setDoc(doc(db, 'usuarios', newUser.uid), { 246 uid: newUser.uid, 247 email: email, 248 name: userData.name, 249 role: rol, 250 rol: rol, 251 empresaId: userData.empresaId || null, 252 sucursalId: userData.sucursalId || null, 253 clientId: userData.clientId || null, 254 losadaEncargadoCompraNormal: 255 rol === 'perfil_encargado_optica_losada' ? losadaCompraNormal : false, 256 phone: userData.phone || '', 257 address: userData.address || '', 258 permissions: permissions, 259 createdAt: new Date().toISOString(), 260 active: true 261 }); 262 263 // Mantener limpia la sesión del auth secundario 264 await signOut(secondaryAuth); 265 266 return { success: true, uid: newUser.uid, email }; 267 } catch (error) { 268 throw new Error(this.translateError(error.code)); 269 } 270 } 271 272 async getAllUsers() { 273 if (!this.isAdmin()) throw new Error('Acceso denegado'); 274 try { 275 const snap = await getDocs(collection(db, 'usuarios')); 276 return snap.docs.map(d => ({ id: d.id, ...d.data() })); 277 } catch (error) { 278 console.error('Error obteniendo usuarios:', error); 279 return []; 280 } 281 } 282 283 async updateUser(uid, updates) { 284 if (!this.isAdmin()) throw new Error('Acceso denegado'); 285 await updateDoc(doc(db, 'usuarios', uid), { 286 ...updates, 287 updatedAt: new Date().toISOString() 288 }); 289 } 290 291 /** 292 * Compatibilidad: actualiza el usuario actual (perfil propio) 293 */ 294 async updateCurrentUser(updates) { 295 if (!this.currentUser?.uid) { 296 throw new Error('No hay sesión activa'); 297 } 298 299 const allowedFields = ['name', 'phone', 'address']; 300 const safeUpdates = {}; 301 for (const key of allowedFields) { 302 if (key in updates) safeUpdates[key] = updates[key]; 303 } 304 305 await updateDoc(doc(db, 'usuarios', this.currentUser.uid), { 306 ...safeUpdates, 307 updatedAt: new Date().toISOString() 308 }); 309 310 if (safeUpdates.name && auth.currentUser) { 311 try { 312 await updateProfile(auth.currentUser, { displayName: safeUpdates.name }); 313 } catch (_) { 314 // No bloquear por fallo de displayName 315 } 316 } 317
318 this.currentUser = { ...this.currentUser, ...safeUpdates }; 319 return this.currentUser; 320 } 321 322 /** 323 * Compatibilidad: eliminar usuario (no soportado desde frontend) 324 * En producción esto debe hacerse con Admin SDK / Cloud Functions. 325 */ 326 deleteUser() { 327 throw new Error('Eliminar usuarios requiere backend (Admin SDK). Use "Desactivar" en su lugar.'); 328 } 329 330 async deactivateUser(uid) { 331 return await this.updateUser(uid, { active: false }); 332 } 333 334 async sendPasswordReset(email) { 335 try { 336 await sendPasswordResetEmail(auth, email); 337 return { success: true }; 338 } catch (error) { 339 throw new Error(this.translateError(error.code)); 340 } 341 } 342 343 translateError(code) { 344 const errors = { 345 'auth/user-not-found': 'No existe una cuenta con este correo', 346 'auth/wrong-password': 'Contraseña incorrecta', 347 'auth/invalid-email': 'Correo electrónico inválido', 348 'auth/user-disabled': 'Esta cuenta ha sido desactivada', 349 'auth/too-many-requests': 'Demasiados intentos. Intente más tarde', 350 'auth/email-already-in-use': 'Este correo ya está registrado', 351 'auth/weak-password': 'La contraseña debe tener al menos 6 caracteres', 352 'auth/network-request-failed': 'Error de conexión. Verifique su internet', 353 'auth/invalid-credential': 'Correo o contraseña incorrectos' 354 }; 355 return errors[code] || 'Error de autenticación. Intente nuevamente'; 356 } 357} 358 359// Instancia global 360window.firebaseAuthSystem = new FirebaseAuthSystem(); 361window.authSystem = window.firebaseAuthSystem; 362 363export default window.firebaseAuthSystem;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.