Fecha: 2026-01-08 Version: 1.0 Status: COMPLETO - Listo para implementar en frontend
El backend ha migrado TODOS los identificadores de UUID (String de 36 caracteres) a BIGINT (Long/Int64).
// ANTES (UUID)
val tenantId: String = "550e8400-e29b-41d4-a716-446655440000"
val greenhouseId: String = "123e4567-e89b-12d3-a456-426614174000"
// AHORA (Long)
val tenantId: Long = 1
val greenhouseId: Long = 5| Aspecto | Cambio |
|---|---|
| Tipo de dato | String (UUID) → Long / Int64 |
| Longitud | 36 caracteres → ~19 dígitos máximo |
| JSON | "id": "uuid-string" → "id": 123 (número) |
| URL paths | /api/v1/greenhouses/uuid-string → /api/v1/greenhouses/123 |
| Tabla | Columnas cambiadas a BIGINT |
|---|---|
tenants |
id |
users |
id, tenant_id |
greenhouses |
id, tenant_id |
devices |
id, tenant_id, greenhouse_id |
sensors |
id, greenhouse_id, tenant_id |
actuators |
id, greenhouse_id, tenant_id |
alerts |
id, greenhouse_id, tenant_id |
sectors |
id, greenhouse_id |
settings |
id, greenhouse_id, tenant_id |
command_history |
id, device_id, user_id |
| Tabla | Columnas cambiadas a BIGINT |
|---|---|
readings |
device_id |
sensor_readings_daily |
greenhouse_id, tenant_id |
sensor_readings_hourly |
greenhouse_id, tenant_id |
sensor_readings_monthly |
greenhouse_id, tenant_id |
greenhouse_daily_summary |
greenhouse_id, tenant_id |
sensor_performance_daily |
sensor_id, greenhouse_id, tenant_id |
// ANTES
data class Tenant(
val id: String, // UUID como String
val name: String,
// ...
)
data class Greenhouse(
val id: String, // UUID
val tenantId: String, // UUID
val name: String,
// ...
)
// AHORA
data class Tenant(
val id: Long, // BIGINT
val name: String,
// ...
)
data class Greenhouse(
val id: Long, // BIGINT
val tenantId: Long, // BIGINT
val name: String,
// ...
)// ANTES
struct Tenant: Codable {
let id: String // UUID
let name: String
}
// AHORA
struct Tenant: Codable {
let id: Int64 // BIGINT
let name: String
}// ANTES
class Tenant {
final String id; // UUID
final String name;
}
// AHORA
class Tenant {
final int id; // BIGINT (int en Dart es int64)
final String name;
}// ANTES - UUID como strings
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenantId": "123e4567-e89b-12d3-a456-426614174000",
"name": "Mi Invernadero"
}
// AHORA - BIGINT como números
{
"id": 1,
"tenantId": 5,
"name": "Mi Invernadero"
}// ANTES
@Serializable
data class Greenhouse(
val id: String,
@SerialName("tenant_id") val tenantId: String
)
// AHORA
@Serializable
data class Greenhouse(
val id: Long,
@SerialName("tenant_id") val tenantId: Long
)// ANTES
val url = "${baseUrl}/api/v1/greenhouses/550e8400-e29b-41d4-a716-446655440000"
val url = "${baseUrl}/api/v1/tenants/123e4567-e89b-12d3-a456-426614174000/greenhouses"
// AHORA
val url = "${baseUrl}/api/v1/greenhouses/1"
val url = "${baseUrl}/api/v1/tenants/5/greenhouses"// ANTES
fun getDevices(tenantId: String, greenhouseId: String)
GET /api/v1/devices?tenantId=uuid-string&greenhouseId=uuid-string
// AHORA
fun getDevices(tenantId: Long, greenhouseId: Long)
GET /api/v1/devices?tenantId=1&greenhouseId=5// ANTES
val tenantId: String = preferences.getString("tenant_id", "")
val greenhouseId: String = preferences.getString("greenhouse_id", "")
// AHORA
val tenantId: Long = preferences.getLong("tenant_id", 0L)
val greenhouseId: Long = preferences.getLong("greenhouse_id", 0L)// ANTES
if (greenhouse.id == selectedGreenhouseId) { ... }
if (tenantId.isNotEmpty()) { ... }
if (greenhouse.id != null && greenhouse.id != "") { ... }
// AHORA
if (greenhouse.id == selectedGreenhouseId) { ... } // Mismo, Long == Long
if (tenantId > 0) { ... } // Validación de BIGINT
if (greenhouse.id != null && greenhouse.id > 0) { ... }// ANTES - Los mensajes incluían UUIDs
data class GreenhouseMessage(
val greenhouseId: String, // UUID
val deviceId: String, // UUID
val timestamp: Instant,
val value: Double
)
// AHORA - Los mensajes incluyen Long
data class GreenhouseMessage(
val greenhouseId: Long, // BIGINT
val deviceId: Long, // BIGINT
val timestamp: Instant,
val value: Double
)src/commonMain/kotlin/models/
├── Tenant.kt // id: String → Long
├── User.kt // id, tenantId: String → Long
├── Greenhouse.kt // id, tenantId: String → Long
├── Device.kt // id, tenantId, greenhouseId: String → Long
├── Sensor.kt // id, greenhouseId, tenantId: String → Long
├── Actuator.kt // id, greenhouseId, tenantId: String → Long
├── Alert.kt // id, greenhouseId, tenantId: String → Long
├── Sector.kt // id, greenhouseId: String → Long
├── Setting.kt // id, greenhouseId, tenantId: String → Long
├── CommandHistory.kt // id, deviceId, userId: String → Long
└── Reading.kt // deviceId: String → Long
src/commonMain/kotlin/data/
├── TenantRepository.kt
├── GreenhouseRepository.kt
├── DeviceRepository.kt
├── SensorRepository.kt
├── AlertRepository.kt
└── ...
Actualizar:
- Firmas de métodos:
fun getById(id: String)→fun getById(id: Long) - URLs de API
- Parseo de respuestas
Actualizar las referencias a IDs en estados y acciones:
// ANTES
data class GreenhouseListState(
val selectedGreenhouseId: String? = null,
val greenhouses: List<Greenhouse> = emptyList()
)
// AHORA
data class GreenhouseListState(
val selectedGreenhouseId: Long? = null,
val greenhouses: List<Greenhouse> = emptyList()
)// ANTES
sealed class Route {
data class GreenhouseDetail(val id: String) : Route()
}
// AHORA
sealed class Route {
data class GreenhouseDetail(val id: Long) : Route()
}Si el frontend almacena IDs localmente (cache, favoritos, etc.), se necesita migrar:
fun migrateLocalStorage() {
val oldTenantId = preferences.getString("tenant_id", null)
if (oldTenantId != null && oldTenantId.contains("-")) {
// Es un UUID antiguo - obtener el nuevo ID del servidor
val newTenantId = api.getTenantByUuid(oldTenantId).id
preferences.putLong("tenant_id", newTenantId)
preferences.remove("tenant_id_string") // Limpiar antiguo si existe
}
}Todos los endpoints que usan IDs ahora esperan/devuelven Long:
GET /api/v1/tenants/{id} // id: Long
POST /api/v1/tenants // body.id: Long (optional for create)
PUT /api/v1/tenants/{id} // id: Long
DELETE /api/v1/tenants/{id} // id: Long
GET /api/v1/greenhouses/{id} // id: Long
GET /api/v1/tenants/{tenantId}/greenhouses // tenantId: Long
POST /api/v1/greenhouses // body.id, body.tenantId: Long
PUT /api/v1/greenhouses/{id} // id: Long
DELETE /api/v1/greenhouses/{id} // id: Long
GET /api/v1/devices/{id} // id: Long
GET /api/v1/greenhouses/{greenhouseId}/devices // greenhouseId: Long
POST /api/v1/devices // body includes Long IDs
(Mismo patrón - todos los IDs son ahora Long)
IMPORTANTE: NO hay compatibilidad hacia atrás. El frontend DEBE actualizarse antes de conectar al backend migrado.
El backend NO acepta UUIDs en ningún formato:
- ❌ Path:
/api/v1/greenhouses/550e8400-e29b-41d4-a716-446655440000 - ❌ Query:
?tenantId=550e8400-e29b-41d4-a716-426655440000 - ❌ Body:
{ "id": "550e8400-e29b-41d4-a716-446655440000" }
Solo acepta Long:
- ✅ Path:
/api/v1/greenhouses/1 - ✅ Query:
?tenantId=5 - ✅ Body:
{ "id": 1 }
- Revisar todos los modelos de datos
- Identificar todos los archivos que usan UUID/String para IDs
- Crear rama de migración
- Cambiar
id: String→id: Longen todos los DTOs - Cambiar
tenantId: String→tenantId: Long - Cambiar
greenhouseId: String→greenhouseId: Long - Cambiar otros IDs relacionales (deviceId, sensorId, etc.)
- Verificar que los parsers JSON manejan números (no strings)
- Actualizar @SerialName si es necesario
- Probar deserialización con respuestas de prueba
- Actualizar construcción de URLs
- Actualizar parámetros de query
- Actualizar body de requests
- Actualizar tipos en estados
- Actualizar comparaciones
- Actualizar navegación
- Migrar SharedPreferences/UserDefaults
- Actualizar cache local si existe
- Limpiar datos antiguos
- Probar login/autenticación
- Probar listado de invernaderos
- Probar detalle de invernadero
- Probar sensores y dispositivos
- Probar alertas
- Probar WebSocket/tiempo real
- Probar casos edge (IDs grandes, etc.)
- Coordinar despliegue con backend
- Publicar nueva versión de app
- Monitorear errores en producción
// ANTES
interface GreenhouseApi {
@GET("api/v1/tenants/{tenantId}/greenhouses")
suspend fun getGreenhouses(
@Path("tenantId") tenantId: String
): List<GreenhouseResponse>
}
data class GreenhouseResponse(
val id: String,
val tenantId: String,
val name: String
)
// AHORA
interface GreenhouseApi {
@GET("api/v1/tenants/{tenantId}/greenhouses")
suspend fun getGreenhouses(
@Path("tenantId") tenantId: Long
): List<GreenhouseResponse>
}
data class GreenhouseResponse(
val id: Long,
val tenantId: Long,
val name: String
)// ANTES
data class CreateAlertRequest(
val greenhouseId: String,
val tenantId: String,
val message: String,
val severity: String
)
// AHORA
data class CreateAlertRequest(
val greenhouseId: Long,
val tenantId: Long,
val message: String,
val severity: String
)// ANTES
fun onMessage(json: String) {
val message = Json.decodeFromString<WebSocketMessage>(json)
val greenhouseId: String = message.greenhouseId // UUID string
updateGreenhouse(greenhouseId)
}
// AHORA
fun onMessage(json: String) {
val message = Json.decodeFromString<WebSocketMessage>(json)
val greenhouseId: Long = message.greenhouseId // BIGINT number
updateGreenhouse(greenhouseId)
}- Rendimiento: BIGINT es ~50% más pequeño que UUID (8 bytes vs 16 bytes)
- Compresión: TimescaleDB comprime BIGINT mucho mejor que UUID
- Velocidad de índices: ~40% más rápido en búsquedas
- Simplicidad: Los números son más fáciles de debuggear y recordar
Sí. Los datos se migraron con nuevos IDs numéricos. No hay correlación directa entre el UUID anterior y el nuevo BIGINT.
El frontend debe obtener los IDs del servidor. No intentes convertir UUIDs locales a Long - simplemente haz un fresh login y obtén los datos actualizados del API.
Debe ser invalidado/limpiado. Los IDs locales antiguos (UUID strings) ya no son válidos.
No. Solo cambiaron los tipos de datos de los IDs. Las rutas, verbos HTTP y estructura de respuesta son los mismos.
Si tienes preguntas sobre la migración, contacta al equipo de backend antes de implementar cambios.
Última actualización: 2026-01-08