# Análisis Comparativo: Mocks de Staff App vs. Schema de Data Connect Este documento detalla el análisis comparativo entre las estructuras de datos mock utilizadas en la aplicación de Staff y las entidades definidas en el schema de Firebase Data Connect. El objetivo es identificar discrepancias, proponer alineaciones y asegurar la consistencia del modelo de datos. --- ## 1. `_calculateDuration()` (de `lib/widgets/shift_card.dart`) - **Estructura Mock:** ```dart { 'hours': int, 'breakTime': String, } ``` - **Entidad Data Connect más cercana:** No aplica. - **Análisis:** - Esta estructura es un tipo de retorno de una función de utilidad en la UI, no representa una entidad persistente. - Calcula valores derivados (`horas`, `breakTime`) a partir de los datos de un turno (`Shift`). - **Recomendación:** - No se requiere ninguna acción. Es correcto que la lógica de la UI maneje este tipo de cálculos derivados. --- ## 2. `createWorkerProfile` y `_profile` (Múltiples archivos) - **Estructura Mock (agregada):** ```dart { 'full_name': String, // Corresponde a 'employeeName' en Staff 'bio': String, // No existe en Staff 'preferred_locations': List, // Podría ser 'hubLocation' o 'eventLocation' 'max_distance_miles': double, // No existe en Staff 'skills': List, // No existe en Staff, ¿quizás 'track' o 'position'? 'industries': List, // No existe en Staff 'level': String, // No existe en Staff 'photo_url': String, // No existe en Staff 'total_shifts': int, // Existe como 'totalShifts' 'average_rating': double, // Existe como 'rating' 'on_time_rate': int, // No existe en Staff 'no_show_count': int, // Existe como 'noShowCount' 'cancellation_count': int, // Existe como 'cancellationCount' 'reliability_score': int, // Existe como 'reliabilityScore' 'phone': String, // Existe como 'phone' } ``` - **Entidad Data Connect más cercana:** `Staff` (`staff.gql`) - **Comparación Campo por Campo:** | Campo Mock | Campo `Staff` (Data Connect) | Coincidencia | Notas | | --- | --- | --- | --- | | `full_name` | `employeeName` | Parcial | El nombre es diferente pero el propósito es el mismo. | | `bio` | `notes` | Parcial | `notes` es un campo genérico, `bio` es más específico. | | `preferred_locations` | `hubLocation`, `eventLocation` | Parcial | El schema tiene campos de texto, el mock una lista. | | `max_distance_miles`| - | No | No existe un campo equivalente. | | `skills` | `track`, `position`, `position2`| Parcial | `skills` es una lista, mientras que el schema tiene campos fijos. | | `industries` | - | No | No existe un campo equivalente. | | `level` | `profileType` | Parcial | `level` es un string, `profileType` es un `Enum`. El propósito es similar. | | `photo_url` | - | No | No existe un campo para la foto. | | `total_shifts` | `totalShifts` | **Directa** | Coincidencia exacta. | | `average_rating` | `rating` | **Directa** | El propósito es el mismo. | | `on_time_rate` | - | No | No existe un campo equivalente. | | `no_show_count` | `noShowCount` | **Directa** | Coincidencia exacta. | | `cancellation_count`| `cancellationCount` | **Directa** | Coincidencia exacta. | | `reliability_score`| `reliabilityScore` | **Directa** | Coincidencia exacta. | | `phone` | `phone`, `contactNumber` | **Directa** | Existen dos campos de teléfono. Alinear a uno. | - **Diferencias y Recomendaciones:** 1. **Nombres de Campos:** Se recomienda que la app **se alinee con los nombres de Data Connect** para mantener la consistencia (e.g., usar `employeeName` en lugar de `full_name`). 2. **Campos Faltantes en `Staff`:** - `photo_url`: **Crítico.** Se debe agregar un campo `photoUrl: String` a la entidad `Staff`. - `bio`: Se puede usar el campo `notes` o agregar un campo `bio: String` si se considera una propiedad fundamental. - `max_distance_miles`: Recomendar agregar `maxTravelDistance: Float`. - `on_time_rate`: Recomendar agregar `onTimeRate: Float`. 3. **Manejo de Listas (skills, industries, preferred_locations):** - **Opción A (Recomendada):** Usar campos de tipo `jsonb` para almacenar estas listas. ```graphql scalar Any # En Staff skills: Any @col(dataType: "jsonb") industries: Any @col(dataType: "jsonb") preferredLocations: Any @col(dataType: "jsonb") ``` - **Opción B:** Crear nuevas entidades y relaciones (e.g., `StaffSkill`, `StaffIndustry`), lo cual es más complejo pero más estructurado. --- ## 3. `_user` (de `worker_profile_screen.dart` y `personal_info_screen.dart`) - **Estructura Mock:** ```dart { 'full_name': String, 'email': String, 'photo_url': String, } ``` - **Entidad Data Connect más cercana:** `User` (`user.gql`) - **Comparación Campo por Campo:** | Campo Mock | Campo `User` (Data Connect) | Coincidencia | Notas | | --- | --- | --- | --- | | `full_name` | `fullName` | **Directa** | Coincidencia de propósito, diferencia de naming. | | `email` | `email` | **Directa** | Coincidencia exacta. | | `photo_url` | - | No | No existe un campo para la foto en `User`. | - **Diferencias y Recomendaciones:** - El mock `_user` parece ser una combinación de datos de la entidad `User` (auth) y `Staff` (perfil). - `photo_url` pertenece al perfil del trabajador, no a su registro de autenticación. Debería estar en la entidad `Staff` (ver punto anterior). - La app debe obtener el `email` y `fullName` de la entidad `User` y el resto de la información de perfil de la entidad `Staff`. --- ## 4. `_contacts` (de `emergency_contact_screen.dart`) - **Estructura Mock:** ```dart { 'name': String, 'phone': String, 'relationship': String } ``` - **Entidad Data Connect más cercana:** No existe. - **Análisis:** - Esta información es específica del perfil de un trabajador. - No justifica una nueva tabla en la base de datos, ya que un contacto de emergencia no es una entidad independiente. - **Recomendación:** - Agregar un campo `emergencyContacts: Any @col(dataType: "jsonb")` a la entidad `Staff`. Este campo almacenaría un array de objetos JSON con la estructura del mock. --- ## 5. `_conversations` y `messages` (de `messages_screen.dart`) - **Estructura Mock:** ```dart // Conversación { 'sender_id': String, 'sender_name': String, 'lastMessage': String, 'lastTime': DateTime, 'unread': int, 'messages': [ ... ] // Lista de mensajes } // Mensaje { 'content': String, 'sender_id': String, } ``` - **Entidades Data Connect más cercanas:** `Conversation` (`conversation.gql`) y `Message` (`message.gql`). - **Análisis y Comparación:** - Data Connect tiene un modelo normalizado con dos entidades separadas: `Conversation` y `Message`, relacionadas por `conversationId`. - El mock presenta un modelo desnormalizado, donde los mensajes están anidados dentro de la conversación. - **Discrepancias:** - `Conversation`: - El mock tiene `sender_id` y `sender_name`, mientras que el schema tiene `participants` (un array de IDs). - El mock tiene `lastMessage`, `lastTime` y `unread`, que son datos derivados. El schema de `Conversation` no los tiene, y deberían calcularse o recuperarse del último mensaje asociado. - `Message`: - El schema tiene `senderName` y `createdBy` (ID), mientras que el mock solo tiene `sender_id`. - **Recomendaciones:** 1. **Alinear la App:** La app debe adaptarse al modelo de Data Connect. - Primero, obtener la lista de `Conversation` para el usuario actual. - Luego, para cada conversación, obtener el último `Message` para mostrar `lastMessage` y `lastTime`. - El conteo de `unread` deberá calcularse en la app o agregar un campo derivado en `Conversation`. 2. **Schema `Message`:** El schema de `Message` está bien, pero podría beneficiarse de un campo `senderId` explícito además de `createdBy` para mayor claridad. --- ## 6. `_timesheets` (de `time_card_screen.dart`) - **Estructura Mock:** ```dart { 'id': String, 'shift_id': String, 'date': String, 'actual_start': String, 'actual_end': String, 'total_hours': double, 'hourly_rate': double, 'total_pay': double, 'status': String, 'shift_title': String, 'client_name': String, 'location': String, } ``` - **Entidad Data Connect más cercana:** `Assignment` (`assignment.gql`) y `Shift` (`shift.gql`). - **Análisis:** - Un `timesheet` parece ser el resultado de un `Assignment` completado. - Combina información de múltiples entidades: el `Assignment` (estado, rol), el `Shift` (fechas, título) y `Staff` (tarifa/rate). - **Recomendaciones:** 1. **Crear una nueva entidad `TimeCard`:** Esta es la opción más limpia para representar una hoja de tiempo. ```graphql type TimeCard @table(name: "time_cards") { id: UUID! @default(expr: "uuidV4()") assignmentId: UUID! staffId: UUID! actualStart: Timestamp! actualEnd: Timestamp! totalHours: Float! hourlyRate: Float! totalPay: Float! status: String # Podría ser un Enum (PENDING, APPROVED, PAID) # ... otros campos relevantes } ``` 2. **Extender `Assignment`:** Agregar los campos `actualStart`, `actualEnd`, `totalHours`, etc., a la entidad `Assignment`. Esto es menos ideal, ya que mezcla la planificación (`scheduledStart`) con la ejecución. 3. **App:** La app deberá construir la vista del `timesheet` combinando datos de `TimeCard` (o `Assignment` extendido), `Shift` y `Order` (para `client_name`). --- ## 7. `_certificates` y `_courses` (Múltiples archivos) - **Estructura Mock (agregada):** ```dart { 'id': String, 'name': String, // o 'title' 'description': String, 'status': String, // 'CURRENT', 'EXPIRED', 'PENDING' 'expiry': String?, 'duration_minutes': int, 'xp_reward': int, 'progress_percent': int, 'completed': bool, } ``` - **Entidad Data Connect más cercana:** `Certification` (`certification.gql`). - **Análisis y Comparación:** - La entidad `Certification` de Data Connect ya cubre los aspectos más importantes: nombre, tipo, estado y fecha de expiración. - Los mocks `_courses` y `_trainings` introducen conceptos de gamificación (`xp_reward`) y seguimiento del progreso (`progress_percent`, `duration_minutes`) que no están en `Certification`. - **Recomendaciones:** 1. **Diferenciar Entidades:** Es probable que se necesiten dos entidades: - `Certification`: Para documentos oficiales y de cumplimiento (la entidad actual funciona bien). - `TrainingCourse`: Una nueva entidad para los cursos de "Krow University". ```graphql type TrainingCourse @table(name: "training_courses") { id: UUID! @default(expr: "uuidV4()") title: String! description: String durationMinutes: Int xpReward: Int # ... otros campos } type StaffTrainingProgress @table(name: "staff_training_progress") { id: UUID! @default(expr: "uuidV4()") staffId: UUID! courseId: UUID! progressPercent: Int completed: Boolean } ``` 2. **App:** La app deberá consumir de `Certification` para la pantalla de "Certificados" y de las nuevas entidades para "Krow University". --- ## 8. `_recentPayments` (de `payments_screen.dart` y `earnings_screen.dart`) - **Estructura Mock:** ```dart { 'date': String, 'amount': double, 'status': String, 'title': String, // Nombre del turno/evento 'location': String, 'workedTime': String, 'hours': int, 'rate': int, } ``` - **Entidad Data Connect más cercana:** `Invoice` (`invoice.gql`). - **Análisis:** - La entidad `Invoice` representa una factura con un monto total y un estado, pero no el detalle de los turnos que la componen. - El mock `_recentPayments` es una vista agregada que combina datos de un pago/factura con detalles del trabajo realizado. - **Recomendaciones:** - El modelo de datos debería incluir una relación entre `Invoice` y los `TimeCard` (o `Assignment`) que la componen. ```graphql # En la nueva entidad TimeCard invoiceId: UUID # Se asigna cuando se incluye en una factura ``` - La app deberá: 1. Obtener los `Invoice` pagados para un `Staff`. 2. Para cada `Invoice`, obtener los `TimeCard` asociados. 3. Con los `TimeCard`, obtener los detalles del `Shift` y `Order` para construir la vista completa que se ve en el mock. --- ## 9. `_recentActivity` (de `clock_in_screen.dart`) - **Estructura Mock:** ```dart { 'date': DateTime, 'start': String, 'end': String, 'hours': String, } ``` - **Entidad Data Connect más cercana:** `ActivityLog` (`activityLog.gql`) - **Análisis:** - La entidad `ActivityLog` está diseñada para registrar eventos genéricos (`EVENT_CREATED`, `STAFF_ASSIGNED`), pero no específicamente los eventos de "clock-in" y "clock-out". - Los datos del mock (`start`, `end`, `hours`) se solapan con la entidad `TimeCard` propuesta anteriormente. - **Recomendaciones:** 1. **Usar `TimeCard`:** Los eventos de `clock-in` y `clock-out` deberían **crear o actualizar** un registro en la entidad `TimeCard`. - `Clock-in` → Crea un `TimeCard` con `actualStart`. - `Clock-out` → Actualiza el `TimeCard` existente con `actualEnd` y calcula `totalHours`. 2. **`ActivityLog` para Notificaciones:** Se puede usar `ActivityLog` para *notificar* al usuario sobre acciones importantes (e.g., "Tu hoja de tiempo ha sido aprobada"), pero no para almacenar los datos crudos del fichaje. 3. La app debería mostrar el historial de `TimeCard` en la pantalla de `clock_in`. --- ## Conclusión General y Pasos a Seguir La validación revela que, si bien hay una base sólida, se necesitan varias actualizaciones en el schema de Data Connect para dar soporte completo a las funcionalidades de la app de Staff. **Acciones recomendadas (en orden de prioridad):** 1. **Actualizar `Staff`:** - Añadir campos faltantes: `photoUrl`, `maxTravelDistance`, `onTimeRate`, `bio`. - Añadir campos `jsonb` para `skills`, `industries`, `preferredLocations` y `emergencyContacts`. - Alinear el naming en la app a `employeeName`. 2. **Crear `TimeCard`:** - Definir e implementar la nueva entidad `TimeCard` para gestionar los fichajes y las horas trabajadas. 3. **Crear Entidades de `Training`:** - Crear `TrainingCourse` y `StaffTrainingProgress` para la funcionalidad de "Krow University". 4. **Alinear la App:** - Refactorizar los servicios de la app para que consuman el SDK de Data Connect según el modelo de datos actualizado (especialmente para conversaciones y pagos). - Ajustar la UI para construir las vistas a partir de múltiples entidades relacionadas en lugar de un único mock desnormalizado.