PageSourceSearch

https://ail-lab-sistema-cristales.web.app/js/firebase-auth.js

js ail-lab-sistema-cristales.web.app collected 2026-10-03 12:16:04 UTC 13,284 bytes, 363 lines download raw bytes

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.