/** * Firebase Types * * Tipos e interfaces para la integración de Firebase en valtech-components. * Todos los modelos de Firestore deben extender FirestoreDocument. */ import type { AnalyticsConfig } from './analytics-types'; /** * Configuración de Firebase (valores de firebaseConfig) */ export interface FirebaseConfig { apiKey: string; authDomain: string; projectId: string; storageBucket: string; messagingSenderId: string; appId: string; measurementId?: string; /** Habilitar Firebase Cloud Messaging (default: false) */ enableMessaging?: boolean; /** VAPID key para Firebase Cloud Messaging */ messagingVapidKey?: string; } /** * Configuración de emuladores para desarrollo local */ export interface EmulatorConfig { firestore?: { host: string; port: number; }; auth?: { host: string; port: number; }; storage?: { host: string; port: number; }; } /** * Configuración por defecto de emuladores. * Puertos estándar usados en todos los proyectos frontend. * * @see https://firebase.google.com/docs/emulator-suite */ export declare const DEFAULT_EMULATOR_CONFIG: EmulatorConfig; /** * Identificador de aplicación para namespacing automático en Firestore y Storage. * Permite aislar datos de diferentes apps dentro del mismo proyecto Firebase. * * Cada app consumidora define sus propios valores de AppId en su configuración local. */ export type AppId = string; /** * Configuración completa de Valtech Firebase */ export interface ValtechFirebaseConfig { /** Configuración de Firebase */ firebase: FirebaseConfig; /** Configuración de emuladores (opcional, para desarrollo) */ emulator?: EmulatorConfig; /** Habilitar persistencia offline de Firestore (default: false) */ persistence?: boolean; /** Habilitar Firebase Cloud Messaging (default: false) - requiere Service Worker */ enableMessaging?: boolean; /** VAPID key para Firebase Cloud Messaging */ messagingVapidKey?: string; /** * ID de la aplicación para namespacing automático. * Si se proporciona: * - Firestore: prefija colecciones con `apps/{appId}/` * - Storage: prefija paths con `{appId}/` * Si no se proporciona, los paths quedan sin modificar (backward compatible). */ appId?: AppId; /** * Persiste mensajes FCM en localStorage para debugging. * Solo para desarrollo, permite ver mensajes en DevTools. * Default: false */ debugMessagePersistence?: boolean; /** * Habilitar Firebase Analytics / GA4 (default: false). * Requiere measurementId en firebase config. */ enableAnalytics?: boolean; /** * Configuración detallada de Analytics. * Solo aplica si enableAnalytics es true. */ analyticsConfig?: AnalyticsConfig; } /** * Interface base para todos los documentos de Firestore. * Todos los modelos deben extender esta interface. * * @example * interface User extends FirestoreDocument { * name: string; * email: string; * } */ export interface FirestoreDocument { /** ID del documento (asignado por Firestore) */ id?: string; /** Fecha de creación (manejada automáticamente) */ createdAt?: Date; /** Fecha de última actualización (manejada automáticamente) */ updatedAt?: Date; } /** * Operadores disponibles para cláusulas where */ export type WhereOperator = '==' | '!=' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'array-contains-any' | 'in' | 'not-in'; /** * Cláusula where para filtrar documentos */ export interface WhereClause { /** Campo a filtrar */ field: string; /** Operador de comparación */ operator: WhereOperator; /** Valor a comparar */ value: unknown; } /** * Dirección de ordenamiento */ export type OrderDirection = 'asc' | 'desc'; /** * Cláusula orderBy para ordenar resultados */ export interface OrderByClause { /** Campo por el cual ordenar */ field: string; /** Dirección del ordenamiento */ direction: OrderDirection; } /** * Opciones para queries de Firestore */ export interface QueryOptions { /** Filtros where (AND entre todos) */ where?: WhereClause[]; /** Ordenamiento de resultados */ orderBy?: OrderByClause[]; /** Límite de documentos a retornar */ limit?: number; /** Cursor para paginación: empezar después de este documento */ startAfter?: unknown; /** Cursor para paginación: empezar en este documento */ startAt?: unknown; /** Cursor para paginación: terminar antes de este documento */ endBefore?: unknown; /** Cursor para paginación: terminar en este documento */ endAt?: unknown; } /** * Opciones adicionales para subscripciones real-time */ export interface SubscriptionOptions extends QueryOptions { /** Incluir cambios de metadata (ej: pendingWrites) */ includeMetadataChanges?: boolean; } /** * Resultado de una query paginada */ export interface PaginatedResult { /** Documentos de la página actual */ data: T[]; /** Indica si hay más páginas disponibles */ hasMore: boolean; /** Cursor para la siguiente página (pasar a startAfter) */ lastDoc: unknown; /** Total de documentos (opcional, requiere query adicional) */ total?: number; } /** * Estado de una operación de upload */ export type UploadState = 'running' | 'paused' | 'success' | 'canceled' | 'error'; /** * Progreso de upload de archivo */ export interface UploadProgress { /** Bytes transferidos hasta ahora */ bytesTransferred: number; /** Total de bytes a transferir */ totalBytes: number; /** Porcentaje completado (0-100) */ percentage: number; /** Estado actual del upload */ state: UploadState; } /** * Resultado de un upload completado */ export interface UploadResult { /** URL de descarga del archivo */ downloadUrl: string; /** Ruta completa en Storage */ fullPath: string; /** Nombre del archivo */ name: string; /** Tamaño en bytes */ size: number; /** Tipo MIME del archivo */ contentType: string; /** Metadata personalizada */ metadata: Record; } /** * Metadata para archivos en Storage */ export interface StorageMetadata { /** Tipo MIME del archivo */ contentType?: string; /** Metadata personalizada (key-value) */ customMetadata?: Record; /** Control de caché HTTP */ cacheControl?: string; /** * Skip the appId prefix for shared paths. * Use this for cross-app paths like user avatars: `users/{userId}/avatar.jpg` * @default false */ skipPrefix?: boolean; } /** * Resultado de listar archivos en Storage */ export interface StorageListResult { /** Rutas de los archivos encontrados */ items: string[]; /** Token para la siguiente página (si hay más) */ nextPageToken?: string; } /** * Información de membership en una organización. * Representa el rol y permisos que tiene un usuario en una org específica. */ export interface MembershipInfo { /** ID del rol (ej: 'admin', 'editor', 'viewer') */ roleId: string; /** Nombre del rol para display */ roleName: string; /** Lista de permisos en formato 'resource:action' */ permissions: string[]; } /** * Estructura completa de claims con RBAC. * Estos claims están disponibles en request.auth.token en Firestore Rules. */ export interface RBACClaims { /** Email del usuario */ email: string; /** Nombre completo del usuario */ name: string; /** Si el email está verificado */ verified: boolean; /** * Mapa de organizaciones donde el usuario tiene roles. * Key = orgId, Value = información del rol y permisos en esa org. */ memberships: Record; /** ID de la organización actualmente seleccionada */ activeOrg: string; } /** * Información de una organización para display */ export interface OrganizationInfo { /** ID único de la organización */ id: string; /** Rol del usuario en esta organización */ roleId: string; /** Nombre del rol para display */ roleName: string; /** Permisos del usuario en esta organización */ permissions: string[]; } /** * Información del usuario de Firebase (simplificada) */ export interface FirebaseUser { /** UID único del usuario */ uid: string; /** Email del usuario */ email: string | null; /** Nombre para mostrar */ displayName: string | null; /** URL de foto de perfil */ photoURL: string | null; /** Email verificado */ emailVerified: boolean; /** Usuario anónimo */ isAnonymous: boolean; /** Proveedor de autenticación */ providerId: string; } /** * Estado de la sesión de Firebase */ export interface SessionState { /** Usuario actual (null si no autenticado) */ user: FirebaseUser | null; /** Indica si el usuario está autenticado */ isAuthenticated: boolean; /** Indica si se está cargando el estado de auth */ isLoading: boolean; /** Error de autenticación (si lo hay) */ error: Error | null; } /** * Estado del permiso de notificaciones */ export type NotificationPermission = 'granted' | 'denied' | 'default'; /** * Payload de una notificación push */ export interface NotificationPayload { /** Título de la notificación */ title?: string; /** Cuerpo del mensaje */ body?: string; /** URL de imagen */ image?: string; /** Datos personalizados */ data?: Record; /** ID del mensaje de FCM */ messageId?: string; } /** * Acción de navegación desde una notificación */ export interface NotificationAction { /** Ruta interna de la app (ej: '/orders/123') */ route?: string; /** URL externa (ej: 'https://example.com') */ url?: string; /** Parámetros de query string */ queryParams?: Record; /** Tipo de acción personalizada */ actionType?: string; /** Datos adicionales para la acción */ actionData?: Record; } /** * Resultado de `MessagingService.enable()`. * * Tipo discriminado descriptivo (NO se hace throw): la página consumidora lo * inspecciona para decidir qué toast/UX mostrar. * * `status`: * - `enabled` — permiso otorgado + token FCM obtenido (`token` presente). * - `denied` — el usuario denegó el permiso del navegador. * - `unsupported` — el navegador no soporta FCM (incl. iOS sin standalone). * - `error` — el flujo falló por otra causa (ver `reason`). * - `timeout` — el flujo se colgó >15s. Si `reloaded` es true, la página * está por recargarse (auto-reload, 1ª vez en la sesión); si * es false, ya se consumió el auto-reload y la página debe * mostrar un error sin recargar. */ export interface EnablePushResult { /** Resultado del flujo de activación de push. */ status: 'enabled' | 'denied' | 'unsupported' | 'error' | 'timeout'; /** Token FCM — presente solo cuando `status === 'enabled'`. */ token?: string; /** Detalle legible de la causa — presente en `error` / `timeout`. */ reason?: string; /** * Solo para `status === 'timeout'`: true si se disparó el auto-reload * (la página está por recargarse). False si el auto-reload ya se consumió * esta sesión y la app debe mostrar un error en lugar de recargar. */ reloaded?: boolean; } /** * Evento de click en una notificación */ export interface NotificationClickEvent { /** Payload original de la notificación */ notification: NotificationPayload; /** Acción de navegación extraída */ action: NotificationAction; /** Timestamp del click */ timestamp: Date; } /** * Códigos de error de Firebase */ export type FirebaseErrorCode = 'permission-denied' | 'not-found' | 'already-exists' | 'resource-exhausted' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'unauthenticated'; /** * Error de Firebase tipado */ export interface FirebaseError { /** Código del error */ code: FirebaseErrorCode; /** Mensaje de error (en español) */ message: string; /** Error original de Firebase */ originalError?: unknown; }