334 lines
15 KiB
Markdown
334 lines
15 KiB
Markdown
# 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<String>, // Podría ser 'hubLocation' o 'eventLocation'
|
|
'max_distance_miles': double, // No existe en Staff
|
|
'skills': List<String>, // No existe en Staff, ¿quizás 'track' o 'position'?
|
|
'industries': List<String>, // 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.
|