📄 Informe de Construcción de Servicios de Lectura Genéricos
I. Objetivo de la Arquitectura
Crear una capa de acceso a datos modular, reutilizable y eficiente, separando la lógica de consulta estática (get), la lógica de tiempo real (onSnapshot), y la lógica de escalabilidad (Paginación).
II. Resumen de Responsabilidades
| Servicio | Responsabilidad Primaria | Ciclo de Vida | Enfoque de Costo |
|---|---|---|---|
| ReadService | Lecturas Únicas, Estáticas y Consultas Offline. | Singleton (vive siempre). | Bajo Costo. Lecturas contadas. |
| ListenerService | Conexiones en Tiempo Real y Suscripciones. | Finito (se destruye con el componente). | Alto Costo. Requiere limpieza (ngOnDestroy). |
| CollectionService | Gestión de Conjuntos Grandes (Paginación). | Singleton (vive siempre). | Eficiente. Controla el número de documentos leídos. |
III. Diseño e Implementación Detallada
1. 📞 ReadService (Lectura Única)
| Característica | Detalle de Implementación |
|---|---|
| Declaración | @Injectable({ providedIn: 'root' }) |
| Dependencia | Inyectar AngularFirestore (o Firestore si usas la API modular). |
| Método Clave | `getDocument<T>(path: string, source: 'default' |
| Lógica | Utiliza la función doc(path).get() de Firestore. El parámetro source permite implementar la Lectura en Caché (forzando la lectura local). |
| Requerimiento | No debe usar Observables, sino Promises, ya que la conexión termina después de la lectura. |
Ejemplo de Uso:
// En un componente
this.readService.getDocument<Mascota>('viajes/ID123')
.then(data => console.log(data));
2. 👂 ListenerService (Tiempo Real)
| Característica | Detalle de Implementación |
|---|---|
| Declaración | @Injectable() (Debe ser provisto en un componente o módulo específico). |
| Dependencia | Inyectar AngularFirestore. |
| Método Clave | listenCollection<T>(path: string, queryFn?: any): Observable<T[]> |
| Lógica | Utiliza collection(path).snapshotChanges() para obtener un Observable. Esto facilita la desuscripción. |
| Requerimiento Crítico |
El servicio debe devolver un Observable. El componente que lo usa (PromocionesComponent) es el responsable final de la desuscripción al destruirse, o el desarrollador debe usar operadores de RxJS como takeUntil o take(1). La nueva característica DestroyRef (si está disponible en tu versión de Angular) es el método moderno preferido. |
Ejemplo de Uso:
// En PromocionesComponent.ts
this.listenerService.listenCollection<Promocion>('promociones')
.pipe(takeUntil(this.destroy$)) // O takeUntil(this.destroyRef.destroyed$)
.subscribe(promos => this.promociones = promos);
3. ⚙️ CollectionService (Paginación y Escalas)
| Característica | Detalle de Implementación |
|---|---|
| Declaración | @Injectable({ providedIn: 'root' }) |
| Dependencia | Inyectar AngularFirestore. |
| Método Clave | getPaginatedCollection<T>(path: string, limit: number, startAfterDoc?: any) |
| Lógica | Utiliza las cláusulas .limit(N) y .startAfter(doc) de Firestore. Debe devolver los datos de la página y el último documento leído (el cursor) para poder pedir la siguiente página. |
| Requerimiento | Se deben usar Promises (.get()) para cada página cargada. Debe ser genérico para soportar diferentes tipos de ordenación (orderBy). |
Ejemplo de Uso:
// Para cargar la primera página de negocios
const primerPagina = await this.collectionService.getPaginatedCollection('negocios', 10);
this.cursor = primerPagina.nextCursor;
IV. Recomendaciones de Código y Mantenimiento
* Tipado Genérico (<T>): Asegúrate de que todos los métodos usen tipos genéricos (<T>) para que estos servicios puedan manejar cualquier tipo de objeto (Mascota, Viaje, Promoción, Negocio, etc.) sin cambiar el código interno.
* Manejo de Errores: Incluye bloques try...catch en los métodos basados en Promises (ReadService, CollectionService) para manejar errores de red o permisos de Firestore.
* Abstracción de Queries: Usa un objeto de configuración (QueryOptions) para pasar where(), orderBy(), y limit() a los métodos, manteniendo el código del servicio limpio.
*