# Diccionario de Datos - Sistema de Gestión de Servicios de Transporte

**Fecha de creación:** 17 de noviembre de 2025  
**Versión:** 1.0  
**Propósito:** Fuente única de verdad para la estructura de datos del sistema

---

## 📋 Índice
1. [Servicios](#servicios)
2. [Detalles](#detalles)
3. [Clientes](#clientes)
4. [Conductores](#conductores)
5. [Vehículos](#vehículos)
6. [Estados](#estados)
7. [Tiempos](#tiempos)
8. [Direcciones](#direcciones)
9. [Costos](#costos)
10. [Configuraciones](#configuraciones)

---

## 🚗 SERVICIOS (services)

**Tabla principal que representa un viaje/servicio solicitado**

| Campo | Tipo | Null | Default | Descripción | Relaciones |
|-------|------|------|---------|-------------|------------|
| `id` | serial | NO | AUTO | Identificador único del servicio | PK |
| `client_phone` | varchar(255) | NO | - | Teléfono del cliente | FK → clients.phone |
| `id_driver` | integer | NO | 1 | ID del conductor asignado | FK → drivers.id |
| `id_detail` | integer | NO | - | ID de los detalles del servicio | FK → details.id |
| `id_time` | integer | NO | - | ID del registro de tiempos | FK → times.id |
| `id_status` | integer | NO | 1 | ID del estado actual | FK → statuses.id |
| `id_os` | integer | NO | - | ID del sistema operativo origen | FK → os.id |
| `id_origin` | integer | NO | 1 | ID de la plataforma origen | FK → origin_platforms.id |
| `external_trip_id` | integer | YES | NULL | ID del viaje en sistema externo | - |
| `created_at` | timestamp | YES | - | Fecha de creación | - |
| `updated_at` | timestamp | YES | - | Fecha de última actualización | - |

**Índices:**
- `idx_services_id_driver` (id_driver)
- `idx_services_id_status` (id_status)
- `idx_services_driver_status` (id_driver, id_status)

**Valores especiales:**
- `id_driver = 1`: Conductor "No Disponible" (servicio sin asignar)

**Estados del servicio:**
1. N/A (Sin asignar)
2. Aceptado (Conductor aceptó)
3. Cancelado
4. Terminado
5. N/A Reservado (Programado/Reservado)
6. En espera (Asignado pero no aceptado)

---

## 📝 DETALLES (details)

**Información detallada del viaje: origen, destino, costo, características**

| Campo | Tipo | Null | Default | Descripción | Uso |
|-------|------|------|---------|-------------|-----|
| `id` | serial | NO | AUTO | Identificador único | PK |
| `origin` | varchar(255) | NO | - | Dirección de origen | Entrada de usuario |
| `destination` | varchar(255) | NO | - | Dirección de destino | Entrada de usuario |
| `notes` | varchar(255) | NO | - | Referencias/notas del viaje | Entrada de usuario |
| `origin_latitude` | varchar(255) | NO | - | Latitud del origen | Geolocalización |
| `origin_longitude` | varchar(255) | NO | - | Longitud del origen | Geolocalización |
| `destination_latitude` | varchar(255) | NO | - | Latitud del destino | Geolocalización |
| `destination_longitude` | varchar(255) | NO | - | Longitud del destino | Geolocalización |
| `polyline` | varchar(1500) | NO | - | Polilínea de la ruta (Google Maps) | Visualización de ruta |
| `distance` | varchar(255) | NO | - | Distancia calculada | Cálculo de costo |
| `cost` | varchar(255) | NO | - | Costo del servicio | Facturación |
| `code` | varchar(255) | NO | - | Código único del servicio | Identificación rápida |
| `calification` | integer | NO | 0 | Calificación del servicio (0-5) | Feedback |
| `flag_executive` | boolean | NO | false | **¿Es servicio ejecutivo?** | Determina tipo de vehículo |
| `flag_scheduled` | boolean | NO | false | **¿Es servicio programado?** | **CRÍTICO: Indica si fue creado como reservado/programado** |
| `flag_visible` | boolean | NO | true | **¿Visible en dashboard principal?** | **CRÍTICO: true=visible, false=oculto** |
| `created_at` | timestamp | YES | - | Fecha de creación | - |
| `updated_at` | timestamp | YES | - | Fecha de última actualización | - |

**⚠️ FLAGS CRÍTICOS:**

### `flag_scheduled` (Programado)
- **`true`**: Servicio creado como programado/reservado
- **`false`**: Servicio inmediato/normal
- **Cuándo se marca `true`**: Cuando se crea un servicio con fecha/hora futura (campo `reservar` en request)
- **Efecto en UI**: Chip negro "N/A" con tooltip "Servicio N/A Programado"

### `flag_visible` (Visibilidad)
- **`true`**: Aparece en dashboard principal "Servicios en Tiempo Real"
- **`false`**: Oculto del dashboard, aparece en "Servicios Reservados"
- **Cuándo se marca `false`**: Cuando usuario oculta un servicio programado manualmente
- **Efecto**: Define qué endpoint lo muestra (`getByNoTerminados` vs `getByNaReservado`)

### `flag_executive` (Tipo de servicio)
- **`true`**: Servicio ejecutivo (requiere vehículo ejecutivo)
- **`false`**: Servicio regular
- **Efecto**: Determina tarifa y tipo de vehículo asignado

**Valores por defecto en creación:**
```javascript
// Servicio programado
flag_scheduled = true
flag_visible = true
status = 5 (N/A Reservado)

// Servicio normal
flag_scheduled = false
flag_visible = true
status = 1 (N/A)
```

---

## 👤 CLIENTES (clients)

**Información de los usuarios que solicitan servicios**

| Campo | Tipo | Null | Default | Descripción |
|-------|------|------|---------|-------------|
| `phone` | varchar(255) | NO | - | Teléfono (PK) |
| `name` | varchar(255) | NO | - | Nombre del cliente |
| `email` | varchar(255) | NO | - | Correo electrónico |
| `device` | varchar(255) | NO | - | Token del dispositivo (FCM) |
| `os` | integer | NO | - | Sistema operativo (1=iOS, 2=Android, 3=Web) |
| `id_user` | integer | YES | NULL | ID de usuario si está registrado |
| `created_at` | timestamp | YES | - | Fecha de registro |
| `updated_at` | timestamp | YES | - | Última actualización |

**Valores especiales:**
- `device = "NO DEVICE"`: Cliente sin app móvil (servicio web/telefónico)
- `os = 3`: Servicio creado desde web/dashboard

---

## 🚕 CONDUCTORES (drivers)

**Información de los operadores de vehículos**

| Campo | Tipo | Null | Default | Descripción | Estados |
|-------|------|------|---------|-------------|---------|
| `id` | serial | NO | AUTO | Identificador único | PK |
| `id_user` | integer | NO | - | Usuario asociado | FK → users.id |
| `latitude` | varchar(255) | NO | - | Última latitud GPS | Tracking |
| `longitude` | varchar(255) | NO | - | Última longitud GPS | Tracking |
| `device` | varchar(255) | NO | - | Token FCM del dispositivo | Notificaciones |
| `license` | varchar(255) | NO | - | Número de licencia | Documentación |
| `card` | varchar(255) | NO | - | Número de tarjetón | Documentación |
| `expiration_card` | date | NO | - | Vencimiento tarjetón | Validación |
| `expiration_license` | date | NO | - | Vencimiento licencia | Validación |
| `flag_status` | boolean | NO | false | **GPS activo/conectado** | Tracking |
| `flag_active` | boolean | NO | false | **Disponible para servicios** | Asignación |
| `flag_service` | boolean | NO | false | **En servicio actualmente** | Estado operativo |
| `flag_panic` | boolean | NO | false | **Botón de pánico activado** | Emergencia |
| `id_vehicle` | integer | NO | - | Vehículo asignado | FK → vehicles.id |
| `image` | varchar(255) | YES | NULL | URL foto de perfil | - |
| `canceled` | boolean | NO | false | **Conductor dado de baja** | Estado cuenta |
| `policy` | varchar(255) | YES | NULL | Aseguradora de póliza | Documentación |
| `magazine` | varchar(255) | YES | NULL | Periodo de revista | Documentación |
| `expiration_policy` | date | YES | NULL | Vencimiento póliza | Validación |
| `expiration_magazine` | date | YES | NULL | Vencimiento revista | Validación |
| `flag_documents` | boolean | NO | false | **Documentos completos** | Validación |
| `created_at` | timestamp | YES | - | Fecha de registro | - |
| `updated_at` | timestamp | YES | - | Última actualización | - |

**Índices:**
- `idx_drivers_flag_active_canceled` (flag_active, canceled)

**Driver especial ID=1:**
```json
{
  "id": 1,
  "name": "Driver No Disponible",
  "id_vehicle": 47,
  "canceled": true
}
```
- **Propósito**: Placeholder para servicios sin asignar
- **Vehículo 47**: number_gral = "S" (causa la "S" en UI)

**Estados operativos:**
- `flag_active=true, flag_service=false`: Disponible para asignar
- `flag_active=true, flag_service=true`: En servicio
- `flag_active=false`: No disponible
- `canceled=true`: Conductor dado de baja

---

## 🚙 VEHÍCULOS (vehicles)

**Información de las unidades de transporte**

| Campo | Tipo | Null | Default | Descripción |
|-------|------|------|---------|-------------|
| `id` | serial | NO | AUTO | Identificador único |
| `economic` | varchar(255) | NO | - | Número económico |
| `number_gral` | varchar(255) | NO | - | **Número de unidad (mostrado en UI)** |
| `brand` | varchar(255) | NO | - | Marca del vehículo |
| `model` | varchar(255) | NO | - | Modelo |
| `plates` | varchar(255) | NO | - | Placas |
| `year` | integer | NO | - | Año del vehículo |
| `passengers` | integer | NO | - | Capacidad de pasajeros |
| `color` | varchar(255) | NO | - | Color (hex code) |
| `owner` | varchar(255) | NO | - | Propietario |
| `permission` | varchar(255) | NO | - | Permiso SCT |
| `expiration_permission` | date | NO | - | Vencimiento permiso |
| `id_type` | integer | NO | - | Tipo de vehículo | FK → type_vehicles.id |
| `canceled` | boolean | NO | false | Vehículo dado de baja |
| `created_at` | timestamp | YES | - | Fecha de registro |
| `updated_at` | timestamp | YES | - | Última actualización |

**Índices:**
- `idx_vehicles_id_type` (id_type)

**Vehículo especial ID=47:**
```json
{
  "id": 47,
  "economic": "00",
  "number_gral": "S",  ← Causa la "S" en servicios sin asignar
  "canceled": true
}
```

**Tipos de vehículo:**
1. Regular (economía)
2. Ejecutivo (premium)

---

## 📊 ESTADOS (statuses)

**Estados posibles de un servicio**

| ID | Label | Descripción | Flujo |
|----|-------|-------------|-------|
| 1 | N/A | Sin asignar (normal) | Inicial → Asignado |
| 2 | Aceptado | Conductor aceptó | En curso → Terminado |
| 3 | Cancelado | Servicio cancelado | Estado final |
| 4 | Terminado | Servicio completado | Estado final |
| 5 | N/A Reservado | Programado sin asignar | Inicial programado → Asignado |
| 6 | En espera | Asignado pero no aceptado | Transitorio |

**Flujos normales:**

**Servicio inmediato:**
```
1 (N/A) → 6 (En espera) → 2 (Aceptado) → 4 (Terminado)
                    ↓
                3 (Cancelado)
```

**Servicio programado:**
```
5 (N/A Reservado) → 6 (En espera) → 2 (Aceptado) → 4 (Terminado)
                              ↓
                          3 (Cancelado)
```

**Lógica en UI:**
- `status.label.includes('N/A')`: Cualquier estado sin asignar (1 o 5)
- `status.label == 'Aceptado'`: Chip verde
- `status.label == 'En espera'`: Chip ámbar
- `status.label.includes('N/A') && flag_scheduled`: Chip negro "N/A"
- `status.label.includes('N/A') && !flag_scheduled`: Chip azul-gris "N/A"

---

## ⏱️ TIEMPOS (times)

**Registro de timestamps del ciclo de vida del servicio**

| Campo | Tipo | Null | Default | Descripción | Momento |
|-------|------|------|---------|-------------|---------|
| `id` | serial | NO | AUTO | Identificador único | - |
| `time_request` | timestamp | NO | - | **Hora solicitada/programada** | Al crear servicio |
| `time_attention` | timestamp | YES | NULL | Hora de atención | Cuando conductor atiende |
| `time_assignment` | timestamp | YES | NULL | **Hora de asignación** | Cuando se asigna conductor |
| `time_finish` | timestamp | YES | NULL | **Hora de finalización** | Cuando termina servicio |
| `created_at` | timestamp | YES | - | Fecha de registro | - |
| `updated_at` | timestamp | YES | - | Última actualización | - |

**Índices:**
- `idx_times_time_request` (time_request)

**Uso:**
- `time_request`: Para servicios programados, fecha/hora futura. Para inmediatos, fecha/hora actual
- `time_assignment - time_request`: Tiempo de respuesta
- `time_finish - time_assignment`: Duración del servicio

---

## 📍 DIRECCIONES (addresses)

**Direcciones guardadas de clientes**

| Campo | Tipo | Null | Default | Descripción |
|-------|------|------|---------|-------------|
| `id` | serial | NO | AUTO | Identificador único |
| `address` | varchar(255) | NO | - | Dirección completa |
| `label` | varchar(255) | NO | - | Etiqueta (Casa, Trabajo, Servicio) |
| `latitude` | varchar(255) | NO | '0.0' | Latitud |
| `longitude` | varchar(255) | NO | '0.0' | Longitud |
| `flag_favorite` | boolean | NO | false | Marcado como favorito |
| `client_phone` | varchar(255) | NO | - | Teléfono del cliente | FK → clients.phone |
| `created_at` | timestamp | YES | - | Fecha de creación |
| `updated_at` | timestamp | YES | - | Última actualización |

**Valores especiales:**
- `label = "Servicio"`: Dirección usada en un servicio (se crea automáticamente)

---

## 💰 COSTOS (costs)

**Tabla de tarifas por distancia**

| Campo | Tipo | Null | Default | Descripción |
|-------|------|------|---------|-------------|
| `id` | serial | NO | AUTO | Identificador único |
| `min` | integer | NO | - | Distancia mínima (km) |
| `max` | integer | NO | - | Distancia máxima (km) |
| `value` | double | NO | 0 | Tarifa regular |
| `value_discount` | double | NO | 0 | Tarifa con descuento |
| `is_executive` | boolean | NO | false | Es tarifa ejecutiva |
| `is_nightly` | boolean | NO | false | Es tarifa nocturna |
| `created_at` | timestamp | YES | - | Fecha de creación |
| `updated_at` | timestamp | YES | - | Última actualización |

**Lógica de cálculo:**
```javascript
// 1. Calcular distancia del servicio
// 2. Buscar en costs donde: distance >= min && distance <= max
// 3. Filtrar por is_executive según flag_executive del detail
// 4. Aplicar value o value_discount según corresponda
```

---

## ⚙️ CONFIGURACIONES (configurations)

**Configuración global del sistema**

| Campo | Tipo | Default | Descripción |
|-------|------|---------|-------------|
| `id` | serial | AUTO | Identificador único |
| `version_android` | numeric(6,3) | 0 | Versión mínima Android |
| `version_ios` | numeric(6,3) | 0 | Versión mínima iOS |
| `enabled_android` | boolean | true | Android habilitado |
| `enabled_ios` | boolean | true | iOS habilitado |
| `passive_mode_time` | integer | 1 | Segundos modo pasivo |
| `passive_mode_mts` | integer | 1 | Metros modo pasivo |
| `active_mode_time` | integer | 1 | Segundos modo activo |
| `active_mode_mts` | integer | 1 | Metros modo activo |
| `panic_mode_time` | integer | 1 | Segundos modo pánico |
| `panic_mode_mts` | integer | 1 | Metros modo pánico |

---

## 🔗 RELACIONES CLAVE

### Servicio completo (Service with relations):
```javascript
Service {
  id, client_phone, id_driver, id_detail, id_time, id_status,
  
  detail: {
    origin, destination, cost, flag_executive, flag_scheduled, flag_visible
  },
  
  time: {
    time_request, time_assignment, time_finish
  },
  
  status: {
    label  // "N/A", "N/A Reservado", "Aceptado", etc
  },
  
  driver: {
    name, device,
    vehicle: {
      number_gral,  // Número mostrado en UI
      type_vehicle: { label }  // "Regular" o "Ejecutivo"
    }
  },
  
  client: {
    name, phone
  }
}
```

---

## 📱 ENDPOINTS PRINCIPALES

### Servicios activos (Dashboard principal):
**GET** `/api-web/services/v2/noTerminados`
- Filtro: `id_status != 4` AND `flag_visible = true`
- Incluye: N/A (1), N/A Reservado (5), En espera (6), Aceptado (2)

### Servicios programados ocultos:
**GET** `/api-web/services/v2/na/reservado`
- Filtro: `id_status = 5` AND `flag_visible = false`
- Solo servicios programados que usuario ocultó manualmente

### Crear servicio:
**POST** `/api-web/services/v2/store`
```javascript
{
  origin, destination, notes, cost,
  flag_executive: boolean,
  reservar: boolean,  // Si true → flag_scheduled=true, status=5
  time_request: timestamp,  // Fecha/hora del servicio
  client_phone, client_name
}
```

### Ocultar servicio programado:
**PATCH** `/api-web/services/v2/hide`
```javascript
{
  id_service: number,
  flag_visible: false,  // Lo oculta del dashboard principal
  id_status: 5  // Mantiene estado N/A Reservado
}
```

---

## 🔒 SEGURIDAD Y PROTECCIONES

### Campos Protegidos en Actualización

**CRÍTICO**: El endpoint `PATCH /api-web/services/v2/update/{id}` tiene protecciones para prevenir modificaciones accidentales:

#### Campos que NO se pueden modificar directamente:
```php
// ❌ BLOQUEADOS en actualización general:
- id_status (solo se cambia en flujos específicos)
- flag_scheduled (permanente, se establece en creación)
- id_driver (solo en asignación explícita)
```

#### Campos que SÍ se pueden modificar:
```php
// ✅ PERMITIDOS:
- origin, destination, notes (detail)
- cost, flag_executive (detail)  
- flag_visible (detail) - Solo este flag
- time_request (time)
```

#### Flujos específicos para cambiar id_status:
```
1. Asignación de conductor: id_status → 6 (En espera)
   Endpoint: GET /api-web/services/v2/{id}/driver/{id}/asignar

2. Cancelación: id_status → 3 (Cancelado)
   Endpoint: PATCH /api-web/services/v2/cancel/{id}
   
3. Finalización: id_status → 4 (Terminado)
   Backend: Método específico FinishService

4. Aceptación (mobile): id_status → 2 (Aceptado)
   Endpoint mobile: Controladores específicos
```

### Bug Prevenido: Conversión Programado → Normal

**Problema detectado en producción**:
```javascript
// ❌ BUG (17 Nov 2025):
// Al hacer "Mostrar" en servicio reservado, cambiaba id_status de 5 a 1
await this.hideService({  // ← Acción incorrecta
    id_service: 311523,
    id_status: 1,  // ← Convertía programado a normal
    flag_visible: true
});
```

**Solución implementada**:
```javascript
// ✅ CORRECCIÓN:
await this.showService({  // ← Acción correcta
    id_service: 311523
    // Solo cambia flag_visible, mantiene id_status = 5
});
```

**Protección en backend**:
- Método `update()` ya NO usa `fill($request->all())`
- Solo actualiza campos explícitamente permitidos
- Tests automáticos validan que no se puede cambiar id_status accidentalmente

### Tests de Regresión

**Archivo**: `tests/Feature/Web/V2/ScheduledServicesTest.php`

**Cobertura**: 9 tests con 83+ assertions

**Tests críticos**:
- `test_show_scheduled_service_does_not_change_status` ⭐
- `test_cannot_accidentally_convert_scheduled_to_normal` ⭐

Ver: `/tests/Feature/Web/V2/README_SCHEDULED_SERVICES.md`

---

## 🎨 LÓGICA DE UI - CHIPS DE UNIDAD

### Colores de chips según estado:

```javascript
// Verde - Servicio en curso
status.label == 'Aceptado' 
→ 'green darken-1' 
→ Muestra: vehicle.number_gral (ej: "TX 20")

// Ámbar - Asignado esperando aceptación
status.label == 'En espera'
→ 'amber darken-1'
→ Muestra: vehicle.number_gral

// Negro - Programado sin asignar
status.label.includes('N/A') && flag_scheduled == true
→ 'grey darken-3'
→ Muestra: "N/A"
→ Tooltip: "Servicio N/A Programado"

// Azul-gris - Normal sin asignar
status.label.includes('N/A') && flag_scheduled == false
→ 'blue-grey lighten-1'
→ Muestra: "N/A"
→ Tooltip: "Servicio no asignado"
```

---

## ⚠️ CASOS ESPECIALES Y PROBLEMAS CONOCIDOS

### Problema de la "S":
**Causa:** Vehículo ID 47 del Driver ID 1 tiene `number_gral = "S"`
**Aparece cuando:** Servicio tiene status "N/A Reservado" pero código usaba comparación exacta `==` en lugar de `.includes()`
**Solución:** Usar `status.label.includes('N/A')` para detectar ambos tipos

### Driver ID 1 (No Disponible):
- **Propósito:** Placeholder para servicios sin asignar
- **Problema:** Su vehículo tiene datos placeholder ("S", "SS", etc)
- **Solución UI:** Nunca mostrar `driver.vehicle.number_gral` cuando status incluye "N/A"

### Flag_visible vs Flag_scheduled:
**Semántica actualizada (17-Nov-2025):**
- `flag_scheduled`: Indica que el servicio debe ser habilitado automáticamente por el cronjob
- `flag_visible`: Estado temporal (oculto en "Reservados" o visible en dashboard)

**⚡ REGLA DE NEGOCIO CRÍTICA:**
> **Todo servicio con `flag_visible=false` DEBE tener `flag_scheduled=true`**
> 
> Cuando un servicio se oculta (manualmente o desde creación), automáticamente se marca como programado para que el cronjob `service:scheduled` lo habilite según su `time_request`.

**Combinaciones válidas:**
```
flag_scheduled=true  + flag_visible=true  + id_status=1 → Programado ya habilitado (después del cronjob)
flag_scheduled=true  + flag_visible=false + id_status=5 → Programado oculto esperando cronjob
flag_scheduled=false + flag_visible=true  + id_status=1 → Normal visible (creado sin programación)
flag_scheduled=false + flag_visible=false → ❌ NO VÁLIDO - violación de regla de negocio
```

**Flujo automático del cronjob:**
```
1. Cronjob ejecuta cada X minutos: php artisan service:scheduled
2. Busca servicios con: id_status=5 AND flag_scheduled=true AND time_request <= now()+30min
3. Los convierte: id_status=5 → id_status=1 (N/A Reservado → N/A normal)
4. Los habilita: flag_visible=false → flag_visible=true
5. Mantiene: flag_scheduled=true (característica permanente)
```

---

## 🔄 FLUJO COMPLETO DE SERVICIO PROGRAMADO

### **Caso 1: Servicio programado desde creación**

1. **Creación:**
   ```javascript
   POST /api-web/services/v2/store
   {
     reservar: true,
     time_request: "2025-11-21 14:55:00"
   }
   
   → detail.flag_scheduled = true
   → detail.flag_visible = true
   → service.id_status = 5 (N/A Reservado)
   → service.id_driver = 1 (No Disponible)
   ```

2. **Visualización inicial:**
   - Aparece en dashboard principal (`getByNoTerminados`)
   - Chip negro "N/A"
   - Tooltip: "Servicio N/A Programado"

3. **Acción de ocultar (opcional):**
   ```javascript
   PATCH /api-web/services/v2/hide
   { id_service, flag_visible: false }
   
   → detail.flag_visible = false
   → Ya no aparece en dashboard principal
   → Aparece en "Servicios Reservados" (últimos 30 días)
   ```

4. **Cronjob automático (service:scheduled):**
   ```bash
   # Se ejecuta cada X minutos (configurado en cron)
   php artisan service:scheduled
   
   # Cuando time_request <= now() + 30 minutos:
   → service.id_status = 5 → 1 (N/A Reservado → N/A normal)
   → detail.flag_visible = false → true
   → detail.flag_scheduled = true (sin cambios)
   → Log: "Servicio programado habilitado automáticamente"
   ```

5. **Resultado final:**
   - Aparece automáticamente en dashboard
   - Listo para asignación de driver
   - `flag_scheduled` permanece `true` (histórico)

---

### **Caso 2: Servicio normal que se oculta manualmente**

1. **Creación normal:**
   ```javascript
   POST /api-web/services/v2/store
   {
     reservar: false,  // ← Servicio inmediato
     time_request: now()
   }
   
   → detail.flag_scheduled = false
   → detail.flag_visible = true
   → service.id_status = 1 (N/A normal)
   ```

2. **Usuario decide ocultarlo:**
   ```javascript
   PATCH /api-web/services/v2/hide
   { id_service, flag_visible: false }
   
   → detail.flag_visible = false
   → detail.flag_scheduled = false → true  // ⚡ CONVERSIÓN AUTOMÁTICA
   → service.id_status = 1 → 5  // Convertido a "N/A Reservado"
   ```

3. **Comportamiento:**
   - Se mueve a "Servicios Reservados"
   - El cronjob lo habilitará según su `time_request`
   - Si `time_request` ya pasó, se habilita en el próximo ciclo del cronjob

---

### **Caso 3: Mostrar servicio reservado manualmente**

1. **Usuario hace click en "Mostrar":**
   ```javascript
   PATCH /api-web/services/v2/update/:id
   { flag_visible: true }
   
   → detail.flag_visible = true
   → service.id_status = 5 → 1  // Lo hace disponible inmediatamente
   → detail.flag_scheduled = true (sin cambios)
   
   // Lista filtrada por:
   // - id_status = 5 (N/A Reservado)
   // - flag_visible = false
   // - created_at >= 30 días atrás
   ```

4. **Asignación de conductor:**
   ```javascript
   POST /api-web/services/v2/assign
   { id_service, id_driver }
   
   → service.id_driver = [conductor real]
   → service.id_status = 6 (En espera)
   → Notificación push al conductor
   ```

5. **Aceptación:**
   ```javascript
   → service.id_status = 2 (Aceptado)
   → time.time_assignment = NOW()
   → driver.flag_service = true
   ```

6. **Finalización:**
   ```javascript
   POST /api-web/services/v2/finish
   { id_service }
   
   → service.id_status = 4 (Terminado)
   → time.time_finish = NOW()
   → driver.flag_service = false
   → detail.flag_visible = false (oculto automáticamente)
   ```

---

## 📚 REFERENCIAS DE CÓDIGO

### Backend - Creación de servicio:
- `app/Repositories/Web/Services/ServicesRepository.php::store()`
- `app/Http/Controllers/ServiceController.php::store()`
- Líneas críticas: 101-111

### Frontend - Visualización de chips:
- `resources/assets/js/components/dashboard/components/table-component.vue`
- Template: `v-slot:item.driver.vehicle.number_gral`
- Líneas: 105-127

### Store - Acciones:
- `resources/assets/js/store/modules/services.js`
- Actions: `newService`, `FinishService`, `hideService`, `assignDriver`

---

## 👥 GESTIÓN DE USUARIOS Y CONDUCTORES

### Comando: make:user-account

**Uso**: Crear usuarios web o conductores móviles

```bash
# Crear usuario web (admin/viewer/operative)
php artisan make:user-account admin@empresa.com
> Enter user name: Administrator
> Select user type: admin

# Crear conductor móvil (con registro en tabla drivers)
php artisan make:user-account conductor@empresa.com
> Enter user name: Juan Conductor
> Select user type: driver
> Enter driver phone number: 4611234567
> Enter license number: LIC123456
> Enter card number: CARD789012
```

**⚠️ CRÍTICO - Creación de Drivers**:

Cuando se crea un `driver`, el comando genera **automáticamente**:

1. **Registro en `users`**:
   ```php
   {
     name: "Juan Conductor",
     email: "conductor@empresa.com",
     password: md5("conductor@empresa.com"),
     phone: "4611234567",
     id_role: 4,  // Driver role
     status: true
   }
   ```

2. **Registro en `drivers`** (NUEVO):
   ```php
   {
     id_user: [id del usuario creado],
     latitude: "0.0",
     longitude: "0.0",
     device: "NO_DEVICE",
     license: "LIC123456",
     card: "CARD789012",
     expiration_card: [1 año adelante],
     expiration_license: [1 año adelante],
     flag_status: false,    // GPS inactivo
     flag_active: false,    // No disponible
     flag_service: false,   // Sin servicio
     flag_panic: false,
     id_vehicle: [vehículo disponible o ID 1],
     canceled: false,
     flag_documents: false
   }
   ```

**Verificar creación correcta**:
```sql
-- Verificar que driver tiene ambos registros
SELECT u.id, u.name, u.email, u.id_role, 
       d.id as driver_id, d.license, d.card, d.id_vehicle
FROM users u
JOIN drivers d ON u.id = d.id_user
WHERE u.email = 'conductor@empresa.com';
```

**⚡ ANTES DE ESTE FIX** (19-Nov-2025):
- ❌ Solo se creaba registro en `users`
- ❌ Faltaba registro en `drivers`
- ❌ Conductor no podía hacer login en app móvil
- ❌ Error: "Driver not found"

**✅ DESPUÉS DE ESTE FIX**:
- ✅ Se crean ambos registros automáticamente
- ✅ Relación `users.id` → `drivers.id_user` correcta
- ✅ Conductor puede hacer login móvil inmediatamente
- ✅ Todos los campos obligatorios completados

---

## 🔧 COMANDOS ÚTILES

### Verificar datos:
```sql
-- Servicios programados visibles
SELECT s.id, st.label, d.flag_scheduled, d.flag_visible
FROM services s
JOIN details d ON s.id_detail = d.id
JOIN statuses st ON s.id_status = st.id
WHERE s.id_status = 5 AND d.flag_visible = true;

-- Servicios programados ocultos
SELECT s.id, st.label, d.flag_scheduled, d.flag_visible
FROM services s
JOIN details d ON s.id_detail = d.id
JOIN statuses st ON s.id_status = st.id
WHERE s.id_status = 5 AND d.flag_visible = false;
```

---

## 🛠️ SCRIPTS DE MANTENIMIENTO

### Auditoría de Consistencia
Verificar que no hay datos inconsistentes en la BD:
```bash
PGPASSWORD=2662 psql -U postgres -d traffic_dev -f scripts/audit_data_consistency.sql
```

Este script verifica:
- Servicios reservados (id_status=5) con `flag_visible=true` o `flag_scheduled=false`
- Servicios normales (id_status=1) con `flag_scheduled=true`
- Servicios cancelados que eran programados

### Corrección Automática de Inconsistencias
Corregir inconsistencias encontradas:
```bash
PGPASSWORD=2662 psql -U postgres -d traffic_dev -f scripts/fix_data_inconsistencies.sql
```

**⚠️ IMPORTANTE**: Este script usa transacciones. Después de ejecutarlo:
- Revisar los cambios propuestos
- Si todo está correcto: ejecutar `COMMIT;`
- Si hay errores: ejecutar `ROLLBACK;`

### Casos de Inconsistencia Documentados

**Caso 1: Servicio 311523 (17-Nov-2025 23:00)**
- **Problema**: `flag_visible=true` y `flag_scheduled=false` en servicio con `id_status=5`
- **Síntoma**: Al mostrar servicio 311505 se habilitaba el 311523 incorrecto
- **Causa**: Bug anterior en `showService()` que llamaba `hideService` con `id_status=1`
- **Solución**: Corrección manual + triple fix (API/store/componente)
- **Prevención**: Tests en `ScheduledServicesTest.php` líneas 420-445

**Caso 2: Servicios 311607, 311606 (17-Nov-2025 23:10)**
- **Problema**: Servicios reservados con `flag_visible=true`
- **Síntoma**: Aparecían en lista de reservados cuando no deberían
- **Causa**: Misma que Caso 1 - bug showService anterior
- **Solución**: Corrección automática con script `fix_data_inconsistencies.sql`
- **Prevención**: Protecciones en `ServicesRepository.php` líneas 160-210

**Caso 3: Servicio 311626 (17-Nov-2025 23:15)**
- **Problema**: Frontend intentaba mostrar servicio inexistente → Error 404
- **Síntoma**: Click en "Mostrar" retornaba 404
- **Causa**: ID nunca existió en BD, probablemente error de sincronización frontend
- **Solución**: Hard refresh (Cmd+Shift+R) para actualizar lista de servicios
- **Prevención**: Validación de existencia antes de intentar actualizar

---

## 📜 HISTORIAL DE CAMBIOS

### 2025-11-17 (00:00) - REFACTORIZACIÓN COMPLETA DE REGLAS DE NEGOCIO
- 🔄 **CAMBIO CRÍTICO**: Actualizada semántica de `flag_scheduled`
- ✅ Nueva regla: Al ocultar servicio normal → se convierte en programado automáticamente
- ✅ Nueva regla: Al mostrar servicio reservado → se habilita inmediatamente (id_status 5→1)
- ✅ Cronjob actualizado: Solo procesa servicios con `flag_scheduled=true`
- ✅ Agregado logging mejorado en cronjob
- ✅ Creado `ScheduledServiceBusinessRulesTest.php` (6 tests, 16 assertions)
- ✅ Documentación completa en `docs/SCHEDULED_SERVICES_BUSINESS_RULES.md`
- ⚠️ **IMPORTANTE**: Servicios ocultos ahora siempre son programados (flag_scheduled=true)

### 2025-11-18 (10:53) - COMANDOS DE CONTROL Y LIBERACIÓN DE CONDUCTORES
- ✅ Implementado sistema completo de comandos de control
- ✅ Creada tabla `service_control_logs` para auditoría
- ✅ Ejecutada limpieza masiva: 953 servicios N/A + 88 En Espera + 1 Reservado = **1042 servicios**
- ✅ Liberados **88 conductores** trabados en estado "En espera"
- ✅ Corregido bug en `CancelStaleWaitingCommand`: maneja servicios sin `time_assignment`
- ✅ Corregido `ServiceControlTrait.cancelService()`: **ahora libera conductores automáticamente**
- ⚠️ **DESCUBIERTO**: 88 servicios en estado 6 (En espera) sin `time_assignment` (inconsistencia de datos)
- 🔧 **CORRECCIÓN CRÍTICA**: `cancelService()` ahora actualiza `drivers.flag_service=false` dentro de la transacción

### 2025-11-17 (23:30)
- ✅ Agregados scripts de auditoría y corrección automática
- ✅ Documentados 3 casos de inconsistencia reales con sus soluciones
- ✅ Corregidos 3 servicios con datos corruptos (311523, 311607, 311606)
- ✅ Agregada sección de scripts de mantenimiento

### 2025-11-19 (12:00) - CORRECCIÓN CRÍTICA: COHERENCIA SISTEMA HÍBRIDO DE ROLES
- 🔧 **BUG CRÍTICO CORREGIDO**: `make:user-account` asignaba `id_role=5` (client) a todos los usuarios web
- ✅ Implementado mapeo automático de tipos de usuario → `id_role` correcto
- ✅ Sistema ahora mantiene coherencia entre `users.id_role` (legacy) y `assigned_roles` (Bouncer)
- ✅ Creado script de migración: `database/migrations/fix_user_roles_coherence.sql`
- ✅ Creado test de coherencia: `tests/test_roles_coherence.sh` (5/5 tests pasando)
- ✅ Actualizada documentación: `database/seeds/README_ROLES.md`
- ⚠️ **ACCIÓN REQUERIDA EN PRODUCCIÓN**: Ejecutar script de migración para corregir usuarios existentes

**Mapeo Correcto (19-Nov-2025 en adelante)**:
- root → id_role=7 + bouncer:root
- admin → id_role=1 + bouncer:admin
- operative → id_role=3 + bouncer:operative
- viewer → id_role=8 + bouncer:viewer
- driver → id_role=4 (sin Bouncer)

**Problema Histórico (antes del 19-Nov-2025)**:
- ❌ root → id_role=5 (client) + bouncer:root **← INCOHERENTE**
- ❌ admin → id_role=5 (client) + bouncer:admin **← INCOHERENTE**
- ❌ operative → id_role=5 (client) + bouncer:operative **← INCOHERENTE**
- ❌ viewer → id_role=5 (client) + bouncer:viewer **← INCOHERENTE**

### 2025-11-17 (inicial)
- ✅ Documentación inicial completa del sistema
- ✅ Agregadas secciones de seguridad y protecciones
- ✅ Documentados flujos completos de servicios programados
- ✅ Agregadas referencias a tests y ejemplos

---

**Última actualización:** 17 de noviembre de 2025 (00:00) - Refactorización completa  
**Mantenido por:** Equipo de desarrollo AMX  
**Contacto:** albertogranados@aliomexico.com

---

## 📖 Documentación Adicional

- **Reglas de negocio completas**: `docs/SCHEDULED_SERVICES_BUSINESS_RULES.md`
- **Tests del sistema**: `tests/Feature/Web/V2/ScheduledServicesTest.php`
- **Tests de reglas**: `tests/Unit/ScheduledServiceBusinessRulesTest.php`
- **Scripts de mantenimiento**: `scripts/audit_data_consistency.sql`, `scripts/fix_data_inconsistencies.sql`
