# Análisis de Mocks de Staff App vs. Schema de Data Connect

Este documento analiza las estructuras de datos "mock" encontradas en el código de la aplicación de Staff (Flutter) y las compara con los schemas GraphQL de Data Connect. El objetivo es identificar discrepancias y proponer cambios para alinear el backend con las necesidades del frontend.

---

## 1. Conversaciones y Mensajes (`messages_screen.dart`)

El mock `_conversations` representa la lista de chats de un usuario. Se compara con las entidades `Conversation` y `Message` del schema.

### Estructura del Mock

```dart
// Estructura de un elemento en la lista `_conversations`
{
  'sender_id': String,
  'sender_name': String,
  'lastMessage': String,
  'lastTime': DateTime,
  'unread': int,
  'messages': [
    {
      'content': String,
      'sender_id': String,
    }
  ]
}
```

### Entidades de Data Connect

**`dataconnect/schema/conversation.gql`**
```graphql
type Conversation @table(name: "conversations") {
  id: UUID!
  participants: String! // Se espera un array de IDs de usuario en formato JSON string
  conversationType: ConversationType!
  // ... otros campos
}
```

**`dataconnect/schema/message.gql`**
```graphql
type Message @table(name: "messages") {
  id: UUID!
  conversationId: UUID!
  senderName: String!
  content: String!
  readBy: String // Se espera un array de IDs de usuario que han leído el mensaje
  // ... otros campos
}
```

### Análisis y Discrepancias

1.  **Modelo de Datos**: El mock está diseñado para una vista de "bandeja de entrada", donde cada item es una conversación y muestra un resumen (último mensaje, hora, contador de no leídos). El schema de Data Connect, en cambio, normaliza los datos en dos tablas: `conversations` y `messages`, lo cual es correcto desde el punto de vista de la base de datos.

2.  **Campos de Resumen Faltantes**: La entidad `Conversation` no tiene campos para `lastMessage`, `lastTime` (o `updatedDate` que podría servir para ordenar, pero no es lo mismo que la fecha del último mensaje), ni un contador `unread`. Para construir la pantalla de la app, el cliente tendría que:
    *   Obtener todas las conversaciones.
    *   Para CADA conversación, obtener el último mensaje para mostrar `lastMessage` y `lastTime`.
    *   Para CADA conversación, contar los mensajes donde el `readBy` no incluya al usuario actual para saber el `unread`.
    Esto es muy ineficiente (problema N+1).

3.  **Identificación de Participantes**:
    *   El mock tiene `sender_id` y `sender_name`, lo que sugiere una conversación 1-a-1.
    *   La entidad `Conversation` tiene un campo `participants` (un JSON string de un array de IDs), que es más flexible y soporta chats grupales. El frontend deberá adaptar su lógica para manejar una lista de participantes en lugar de un único `sender`.

### Recomendaciones

Para optimizar la carga de datos y alinear el backend con las necesidades de la UI, recomiendo **desnormalizar** la entidad `Conversation` añadiendo campos de resumen.

**Archivo a modificar**: `dataconnect/schema/conversation.gql`

**Cambios sugeridos**:

```graphql
# EN: dataconnect/schema/conversation.gql

type Conversation @table(name: "conversations") {
  id: UUID! @default(expr: "uuidV4()")
  participants: String!          # participants (jsonb -> String, required array of strings)
  conversationType: ConversationType!
  relatedTo: UUID!              # related_to (generic FK as string)
  status: ConversationStatus
  createdDate: Timestamp @default(expr: "request.time")
  updatedDate: Timestamp @default(expr: "request.time")
  createdBy: String @default(expr: "auth.uid")

  # --- CAMPOS AÑADIDOS PARA RESUMEN ---
  lastMessage: String           # Contenido del último mensaje
  lastMessageSenderId: String   # ID del remitente del último mensaje
  lastMessageTimestamp: Timestamp # Fecha y hora del último mensaje
}
```

**Consideraciones Adicionales**:

*   **Contador de no leídos (`unread`)**: Añadir un campo `unreadCount` a `Conversation` es complejo porque su valor depende de *quién* está mirando la conversación. Una mejor estrategia sería manejar esto en el lado del cliente o con una query más específica si el rendimiento lo permite. Por ahora, los campos de resumen del último mensaje son la mejora más crítica.
*   **Lógica de Actualización**: Será necesario implementar una lógica (probablemente en una Cloud Function o en la mutación que crea mensajes) para que cada vez que se añada un `Message` nuevo, se actualicen los campos `lastMessage`, `lastMessageSenderId` y `lastMessageTimestamp` en la `Conversation` padre. Data Connect por sí solo no manejará esta lógica de desnormalización.
*   **Alineación en la App**: La app de Flutter deberá cambiar su mock y su lógica para usar `participants` en lugar de `sender_id` y `sender_name`. El nombre del otro participante en un chat 1-a-1 se puede obtener buscando el ID en `participants` que no sea el del usuario actual.

---

## 2. Perfil de Staff y Leaderboard (`leaderboard_screen.dart`)

El mock `_profiles` se usa para mostrar la tabla de clasificación y parece ser un subconjunto de un perfil de trabajador (staff). Se compara con las entidades `Staff` y `User`.

### Estructura del Mock

```dart
// Estructura de un elemento en la lista `_profiles`
{
  'id': String,            // ID del perfil del trabajador (Staff)
  'name': String,
  'photo_url': String?,
  'xp': int,               // Puntos de experiencia
  'level': String,         // Nivel de gamificación, ej: 'Krower III'
  'user_id': String,       // ID de la entidad User (auth)
}
```

### Entidades de Data Connect

**`dataconnect/schema/staff.gql`**
```graphql
type Staff @table(name: "staffs") {
  id: UUID!
  employeeName: String!
  # ... muchos otros campos relacionados con el trabajo
}
```

**`dataconnect/schema/user.gql`**
```graphql
type User @table(name: "users") {
  id: String! // Uid de Firebase Auth
  email: String!
  fullName: String!
  # ...
}
```

### Análisis y Discrepancias

1.  **Relación `User` <-> `Staff` Faltante**: El mock contiene `id` (para el perfil `Staff`) y `user_id` (para la entidad `User`), lo cual es una arquitectura correcta. Sin embargo, la entidad `Staff` en el schema **no tiene un campo `userId`** para vincularlo con la tabla `User`. Esta es una relación fundamental que debe existir.

2.  **Campos de Gamificación (`xp`, `level`)**: Estos campos son el núcleo de la funcionalidad del leaderboard, pero están completamente ausentes en la entidad `Staff`.

3.  **Foto de Perfil (`photo_url`)**: Un campo para la URL de la foto de perfil es estándar en cualquier sistema con perfiles de usuario y falta en el schema de `Staff`.

4.  **Convención de Nombres**: El mock usa `photo_url` y `user_id` (snake_case), mientras que la convención estándar de GraphQL y de los SDK generados por Data Connect es `camelCase`. La aplicación cliente debería usar `photoUrl` y `userId` para evitar inconsistencias.

### Recomendaciones

Para soportar la funcionalidad del leaderboard y establecer una arquitectura de datos robusta, se deben hacer los siguientes cambios.

**Archivo a modificar**: `dataconnect/schema/staff.gql`

**Cambios sugeridos**:

```graphql
# EN: dataconnect/schema/staff.gql

type Staff @table(name: "staffs") {
  id: UUID! @default(expr: "uuidV4()")
  userId: String!                # --- AÑADIDO: Vínculo a la tabla User (FK a User.id)
  employeeName: String! @col(name: "employee_name")
  # ... (campos existentes)

  # --- CAMPOS AÑADIDOS PARA PERFIL Y GAMIFICACIÓN ---
  photoUrl: String               # URL de la foto de perfil
  xp: Int @default(expr: "0")    # Puntos de experiencia, con valor inicial de 0
  level: String                  # Nivel de gamificación (ej: "Beginner", "Krower I")

  # ... (resto de campos existentes)
  createdDate: Timestamp @default(expr: "request.time")
  updatedDate: Timestamp @default(expr: "request.time")
  createdBy: String @default(expr: "auth.uid")
}
```

**Consideraciones Adicionales**:

*   **`userId` debe ser único**: Se debería añadir una constraint a nivel de base de datos para asegurar que no pueda haber dos perfiles de `Staff` con el mismo `userId`.
*   **Alineación en la App**: Recomendar al equipo de Flutter que estandarice el uso de `camelCase` en las claves de sus `Map` para que coincidan con los nombres de campo de GraphQL (`photoUrl`, `userId`, `employeeName`).
*   **`User.fullName` vs `Staff.employeeName`**: El sistema debe decidir cuál es la fuente de verdad para el nombre del usuario. Generalmente, `User.fullName` es el nombre legal/de registro, y `Staff.employeeName` podría ser un alias o nombre profesional. Para el leaderboard, `employeeName` parece más apropiado.

---

## 3. Hojas de Tiempo (Timesheets) y Pagos (`time_card_screen.dart`)

El mock `_timesheets` representa los registros de horas trabajadas que son la base para el cálculo de pagos. Esto es un concepto distinto al de un `Shift` (turno programado).

### Estructura del Mock

```dart
// Estructura de un elemento en la lista `_timesheets`
{
  'id': String,
  'shift_id': String,
  'date': String,
  'actual_start': String,  // Hora de entrada real
  'actual_end': String,    // Hora de salida real
  'total_hours': double,
  'hourly_rate': double,
  'total_pay': double,
  'status': String,        // ej: 'pending', 'approved', 'paid'
  'shift_title': String,   // Dato desnormalizado
  'client_name': String,   // Dato desnormalizado
  'location': String,      // Dato desnormalizado
}
```

### Entidad de Data Connect (`shift.gql`)

```graphql
// dataconnect/schema/shift.gql
type Shift @table(name: "shifts") {
  id: UUID!
  shiftName: String!
  startDate: Timestamp! // Hora de inicio programada
  endDate: Timestamp    // Hora de fin programada
  assignedStaff: String
  # ...
}
```

### Análisis y Discrepancias

1.  **Entidad Faltante: `Timesheet`**: La entidad `Shift` representa un evento *programado*. El mock `_timesheets`, sin embargo, describe el trabajo *realizado*. Es una práctica estándar separar estos dos conceptos. Se necesita una nueva entidad, que podríamos llamar `Timesheet` o `WorkLog`, que registre las horas reales y su estado de aprobación y pago.

2.  **Campos del Mock**: El mock contiene todos los campos necesarios para la nueva entidad:
    *   Relación con el turno: `shift_id`.
    *   Tiempos reales: `actual_start`, `actual_end`.
    *   Cálculos de pago: `total_hours`, `hourly_rate`, `total_pay`.
    *   Ciclo de vida: `status`.

3.  **Datos Desnormalizados**: El mock incluye `shift_title`, `client_name` y `location` para facilitar su visualización en la app. En el backend, estos datos no deben duplicarse en la tabla `Timesheet`. La nueva entidad solo debe contener el `shiftId`, y los datos relacionados se obtendrán mediante una query de GraphQL que una las entidades `Timesheet`, `Shift`, `Event`, `Client`, etc.

### Recomendaciones

La recomendación principal es **crear una nueva entidad `Timesheet`**.

**1. Crear nuevo archivo**: `dataconnect/schema/timesheet.gql`

**2. Añadir el siguiente schema**:

```graphql
# NUEVO ARCHIVO: dataconnect/schema/timesheet.gql

enum TimesheetStatus {
  PENDING   # El trabajador lo ha enviado, pendiente de aprobación
  APPROVED  # Aprobado por un manager, listo para ser pagado
  DISPUTED  # El trabajador o manager ha marcado una discrepancia
  PAID      # Incluido en una nómina y pagado
}

type Timesheet @table(name: "timesheets") {
  id: UUID! @default(expr: "uuidV4()")
  shiftId: UUID!      # FK a la tabla Shift
  staffId: UUID!      # FK a la tabla Staff
  
  actualStart: Timestamp # Hora de entrada real registrada
  actualEnd: Timestamp   # Hora de salida real registrada

  totalHours: Float   # Horas totales calculadas
  hourlyRate: Float   # Tarifa por hora (puede copiarla del turno por si cambia)
  totalPay: Float     # Pago total calculado (totalHours * hourlyRate)

  status: TimesheetStatus! @default(expr: "'PENDING'")

  notes: String       # Notas del trabajador o del manager
  
  createdDate: Timestamp @default(expr: "request.time")
  updatedDate: Timestamp @default(expr: "request.time")
  createdBy: String @default(expr: "auth.uid")
}
```

**Consideraciones Adicionales**:

*   **Relación**: La entidad `Timesheet` tendría una relación de uno-a-uno (o uno-a-muchos, si un turno puede tener varios registros de tiempo) con la entidad `Shift`.
*   **Automatización**: La lógica para calcular `totalHours` y `totalPay` podría vivir en el backend (una Cloud Function que se dispara al crear/modificar un `Timesheet`) para asegurar consistencia.
*   **Alineación en la App**: La app debe ser actualizada para hacer una query a `timesheets` y, a través de GraphQL, pedir los campos relacionados del turno (`shift { shiftName event { client { name } } }`).

---
