Initial commit: SIBU 2.0 MISSION

This commit is contained in:
2026-02-21 09:53:31 -05:00
commit 0c7aa53c8b
400 changed files with 67708 additions and 0 deletions

View File

@ -0,0 +1,203 @@
class BusStopModel {
final String id;
final String name;
final double lat;
final double lng;
final String? city;
final String? address;
final String? parentId;
final String? side;
final String stopType;
final bool hasShelter;
final bool hasSeating;
final bool isAccessible;
final DateTime? createdAt;
final DateTime? updatedAt;
// Route-specific fields (from route_stops junction table)
final int? stopOrder;
final int? travelTimeMinutes;
final bool? isPickupPoint;
final bool? isDropoffPoint;
BusStopModel({
required this.id,
required this.name,
required this.lat,
required this.lng,
this.city,
this.address,
this.parentId,
this.side,
this.stopType = 'regular',
this.hasShelter = false,
this.hasSeating = false,
this.isAccessible = false,
this.createdAt,
this.updatedAt,
this.stopOrder,
this.travelTimeMinutes,
this.isPickupPoint,
this.isDropoffPoint,
});
factory BusStopModel.fromJson(Map<String, dynamic> json) {
return BusStopModel(
id: json['id']?.toString() ?? '',
name: json['name']?.toString() ?? '',
lat: double.tryParse(json['lat']?.toString() ?? '0') ?? 0.0,
lng: double.tryParse(json['lng']?.toString() ?? '0') ?? 0.0,
city: json['city']?.toString(),
address: json['address']?.toString(),
parentId: json['parent_id']?.toString(),
side: json['side']?.toString(),
stopType: json['stop_type']?.toString() ?? 'regular',
hasShelter: json['has_shelter'] == true,
hasSeating: json['has_seating'] == true,
isAccessible: json['is_accessible'] == true,
createdAt:
json['created_at'] != null
? DateTime.tryParse(json['created_at'].toString())
: null,
updatedAt:
json['updated_at'] != null
? DateTime.tryParse(json['updated_at'].toString())
: null,
stopOrder:
json['stop_order'] != null
? int.tryParse(json['stop_order'].toString())
: null,
travelTimeMinutes:
json['travel_time_minutes'] != null
? int.tryParse(json['travel_time_minutes'].toString())
: null,
isPickupPoint: json['is_pickup_point'] == true,
isDropoffPoint: json['is_dropoff_point'] == true,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'lat': lat,
'lng': lng,
'city': city,
'address': address,
'parent_id': parentId,
'side': side,
'stop_type': stopType,
'has_shelter': hasShelter,
'has_seating': hasSeating,
'is_accessible': isAccessible,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
'stop_order': stopOrder,
'travel_time_minutes': travelTimeMinutes,
'is_pickup_point': isPickupPoint,
'is_dropoff_point': isDropoffPoint,
};
}
// Helper getters
String get displayName => name;
String get fullAddress {
if (address != null && address!.isNotEmpty) {
return city != null ? '$address, $city' : address!;
}
return city ?? 'Ubicación desconocida';
}
String get stopTypeDisplay {
switch (stopType) {
case 'terminal':
return 'Terminal';
case 'express_only':
return 'Solo Express';
case 'regular':
default:
return 'Parada Regular';
}
}
List<String> get amenities {
List<String> amenityList = [];
if (hasShelter) amenityList.add('Refugio');
if (hasSeating) amenityList.add('Asientos');
if (isAccessible) amenityList.add('Accesible');
return amenityList;
}
String get amenitiesText {
final amenityList = amenities;
if (amenityList.isEmpty) return 'Sin servicios especiales';
return amenityList.join(', ');
}
bool get isTerminal => stopType == 'terminal';
bool get isExpressOnly => stopType == 'express_only';
String get travelTimeText {
if (travelTimeMinutes != null && travelTimeMinutes! > 0) {
return '${travelTimeMinutes} min';
}
return 'N/A';
}
BusStopModel copyWith({
String? id,
String? name,
double? lat,
double? lng,
String? city,
String? address,
String? parentId,
String? side,
String? stopType,
bool? hasShelter,
bool? hasSeating,
bool? isAccessible,
DateTime? createdAt,
DateTime? updatedAt,
int? stopOrder,
int? travelTimeMinutes,
bool? isPickupPoint,
bool? isDropoffPoint,
}) {
return BusStopModel(
id: id ?? this.id,
name: name ?? this.name,
lat: lat ?? this.lat,
lng: lng ?? this.lng,
city: city ?? this.city,
address: address ?? this.address,
parentId: parentId ?? this.parentId,
side: side ?? this.side,
stopType: stopType ?? this.stopType,
hasShelter: hasShelter ?? this.hasShelter,
hasSeating: hasSeating ?? this.hasSeating,
isAccessible: isAccessible ?? this.isAccessible,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
stopOrder: stopOrder ?? this.stopOrder,
travelTimeMinutes: travelTimeMinutes ?? this.travelTimeMinutes,
isPickupPoint: isPickupPoint ?? this.isPickupPoint,
isDropoffPoint: isDropoffPoint ?? this.isDropoffPoint,
);
}
@override
String toString() {
return 'BusStopModel(id: $id, name: $name, city: $city, stopType: $stopType)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is BusStopModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
}

View File

@ -0,0 +1,113 @@
class CouponModel {
final String id;
final String businessName;
final String title;
final String description;
final DateTime? validUntil;
final String? imageUrl;
final String category;
final bool isActive;
final DateTime createdAt;
CouponModel({
required this.id,
required this.businessName,
required this.title,
required this.description,
this.validUntil,
this.imageUrl,
required this.category,
required this.isActive,
required this.createdAt,
});
factory CouponModel.fromMap(Map<String, dynamic> map) {
return CouponModel(
id: map['id'] as String,
businessName: map['business_name'] as String,
title: map['title'] as String,
description: map['description'] as String? ?? '',
validUntil: map['valid_until'] != null
? DateTime.parse(map['valid_until'])
: null,
imageUrl: map['image_url'] as String?,
category: map['category'] as String,
isActive: map['is_active'] as bool? ?? true,
createdAt: DateTime.parse(map['created_at']),
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'business_name': businessName,
'title': title,
'description': description,
'valid_until': validUntil?.toIso8601String(),
'image_url': imageUrl,
'category': category,
'is_active': isActive,
'created_at': createdAt.toIso8601String(),
};
}
bool get isExpired {
if (validUntil == null) return false;
return DateTime.now().isAfter(validUntil!);
}
bool get isExpiringSoon {
if (validUntil == null) return false;
final now = DateTime.now();
final difference = validUntil!.difference(now).inDays;
return difference <= 3 && difference >= 0;
}
String get categoryDisplayName {
switch (category.toLowerCase()) {
case 'restaurantes':
return 'Restaurantes';
case 'tiendas':
return 'Tiendas';
case 'servicios':
return 'Servicios';
case 'entretenimiento':
return 'Entretenimiento';
case 'salud':
return 'Salud';
case 'belleza':
return 'Belleza';
default:
return 'Otros';
}
}
String get validUntilFormatted {
if (validUntil == null) return 'Sin fecha de vencimiento';
return '${validUntil!.day.toString().padLeft(2, '0')}/${validUntil!.month.toString().padLeft(2, '0')}/${validUntil!.year}';
}
CouponModel copyWith({
String? id,
String? businessName,
String? title,
String? description,
DateTime? validUntil,
String? imageUrl,
String? category,
bool? isActive,
DateTime? createdAt,
}) {
return CouponModel(
id: id ?? this.id,
businessName: businessName ?? this.businessName,
title: title ?? this.title,
description: description ?? this.description,
validUntil: validUntil ?? this.validUntil,
imageUrl: imageUrl ?? this.imageUrl,
category: category ?? this.category,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
);
}
}

View File

@ -0,0 +1,154 @@
class RouteModel {
final String id;
final String name;
final String? description;
final String color;
final String direction;
final String? originCity;
final String? destinationCity;
final double? distanceKm;
final int? estimatedDurationMinutes;
final String status;
final DateTime? createdAt;
final DateTime? updatedAt;
RouteModel({
required this.id,
required this.name,
this.description,
required this.color,
required this.direction,
this.originCity,
this.destinationCity,
this.distanceKm,
this.estimatedDurationMinutes,
this.status = 'active',
this.createdAt,
this.updatedAt,
});
factory RouteModel.fromJson(Map<String, dynamic> json) {
return RouteModel(
id: json['id']?.toString() ?? '',
name: json['name']?.toString() ?? '',
description: json['description']?.toString(),
color: json['color']?.toString() ?? '#FEE715',
direction: json['direction']?.toString() ?? 'outbound',
originCity: json['origin_city']?.toString(),
destinationCity: json['destination_city']?.toString(),
distanceKm: json['distance_km'] != null
? double.tryParse(json['distance_km'].toString())
: null,
estimatedDurationMinutes: json['estimated_duration_minutes'] != null
? int.tryParse(json['estimated_duration_minutes'].toString())
: null,
status: json['status']?.toString() ?? 'active',
createdAt: json['created_at'] != null
? DateTime.tryParse(json['created_at'].toString())
: null,
updatedAt: json['updated_at'] != null
? DateTime.tryParse(json['updated_at'].toString())
: null,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'color': color,
'direction': direction,
'origin_city': originCity,
'destination_city': destinationCity,
'distance_km': distanceKm,
'estimated_duration_minutes': estimatedDurationMinutes,
'status': status,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
};
}
// Helper getters
String get displayName {
if (name.isNotEmpty) return name;
final od = [originCity, destinationCity]
.where((e) => e != null && e.trim().isNotEmpty)
.map((e) => e!.trim())
.join(' ');
return od.isNotEmpty ? od : 'Route';
}
String get routeDescription {
if (description != null && description!.isNotEmpty) {
return description!;
}
return 'Ruta $displayName';
}
String get durationText {
if (estimatedDurationMinutes != null) {
if (estimatedDurationMinutes! >= 60) {
final hours = estimatedDurationMinutes! ~/ 60;
final minutes = estimatedDurationMinutes! % 60;
return minutes > 0 ? '${hours}h ${minutes}min' : '${hours}h';
}
return '${estimatedDurationMinutes}min';
}
return 'N/A';
}
String get distanceText {
if (distanceKm != null) {
return '${distanceKm!.toStringAsFixed(1)} km';
}
return 'N/A';
}
bool get isActive => status == 'active';
RouteModel copyWith({
String? id,
String? name,
String? description,
String? color,
String? direction,
String? originCity,
String? destinationCity,
double? distanceKm,
int? estimatedDurationMinutes,
String? status,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return RouteModel(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
color: color ?? this.color,
direction: direction ?? this.direction,
originCity: originCity ?? this.originCity,
destinationCity: destinationCity ?? this.destinationCity,
distanceKm: distanceKm ?? this.distanceKm,
estimatedDurationMinutes:
estimatedDurationMinutes ?? this.estimatedDurationMinutes,
status: status ?? this.status,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
String toString() {
return 'RouteModel(id: $id, name: $name, direction: $direction, status: $status)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is RouteModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
}

View File

@ -0,0 +1,72 @@
class RouteStopModel {
final String id;
final String routeId;
final String stopId;
final int stopOrder;
final int? travelTimeMinutes;
final bool isPickupPoint;
final bool isDropoffPoint;
final DateTime createdAt;
// Populated from joined data
final String? stopName;
final double? latitude;
final double? longitude;
final String? city;
RouteStopModel({
required this.id,
required this.routeId,
required this.stopId,
required this.stopOrder,
this.travelTimeMinutes,
required this.isPickupPoint,
required this.isDropoffPoint,
required this.createdAt,
this.stopName,
this.latitude,
this.longitude,
this.city,
});
factory RouteStopModel.fromJson(Map<String, dynamic> json) {
return RouteStopModel(
id: json['id'] as String,
routeId: json['route_id'] as String,
stopId: json['stop_id'] as String,
stopOrder: json['stop_order'] as int,
travelTimeMinutes: json['travel_time_minutes'] as int?,
isPickupPoint: json['is_pickup_point'] as bool,
isDropoffPoint: json['is_dropoff_point'] as bool,
createdAt: DateTime.parse(json['created_at'] as String),
stopName: json['stop_name'] as String?,
latitude: json['latitude']?.toDouble(),
longitude: json['longitude']?.toDouble(),
city: json['city'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'route_id': routeId,
'stop_id': stopId,
'stop_order': stopOrder,
'travel_time_minutes': travelTimeMinutes,
'is_pickup_point': isPickupPoint,
'is_dropoff_point': isDropoffPoint,
'created_at': createdAt.toIso8601String(),
if (stopName != null) 'stop_name': stopName,
if (latitude != null) 'latitude': latitude,
if (longitude != null) 'longitude': longitude,
if (city != null) 'city': city,
};
}
String get operationType {
if (isPickupPoint && isDropoffPoint) return 'Subida/Bajada';
if (isPickupPoint) return 'Solo Subida';
if (isDropoffPoint) return 'Solo Bajada';
return 'Sin servicio';
}
}

View File

@ -0,0 +1,140 @@
import 'package:flutter/foundation.dart';
/// Model representing a taxi service with contact and location information
@immutable
class TaxiModel {
final String id;
final String name;
final String phone;
final String corregimiento;
final String shift;
final bool isActive;
final DateTime createdAt;
final DateTime updatedAt;
const TaxiModel({
required this.id,
required this.name,
required this.phone,
required this.corregimiento,
required this.shift,
required this.isActive,
required this.createdAt,
required this.updatedAt,
});
/// Create TaxiModel from Supabase JSON response
factory TaxiModel.fromJson(Map<String, dynamic> json) {
return TaxiModel(
id: json['id'] as String,
name: json['name'] as String,
phone: json['phone'] as String,
corregimiento: json['corregimiento'] as String,
shift: json['shift'] as String,
isActive: json['is_active'] as bool? ?? true,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}
/// Convert TaxiModel to JSON for Supabase operations
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'phone': phone,
'corregimiento': corregimiento,
'shift': shift,
'is_active': isActive,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}
/// Create a copy with modified properties
TaxiModel copyWith({
String? id,
String? name,
String? phone,
String? corregimiento,
String? shift,
bool? isActive,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return TaxiModel(
id: id ?? this.id,
name: name ?? this.name,
phone: phone ?? this.phone,
corregimiento: corregimiento ?? this.corregimiento,
shift: shift ?? this.shift,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TaxiModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
@override
String toString() {
return 'TaxiModel(id: $id, name: $name, phone: $phone, corregimiento: $corregimiento, shift: $shift, isActive: $isActive)';
}
}
/// Model representing a user's favorite taxi
@immutable
class FavoriteTaxiModel {
final String id;
final String userId;
final String taxiId;
final DateTime createdAt;
const FavoriteTaxiModel({
required this.id,
required this.userId,
required this.taxiId,
required this.createdAt,
});
/// Create FavoriteTaxiModel from Supabase JSON response
factory FavoriteTaxiModel.fromJson(Map<String, dynamic> json) {
return FavoriteTaxiModel(
id: json['id'] as String,
userId: json['user_id'] as String,
taxiId: json['taxi_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
);
}
/// Convert FavoriteTaxiModel to JSON for Supabase operations
Map<String, dynamic> toJson() {
return {
'id': id,
'user_id': userId,
'taxi_id': taxiId,
'created_at': createdAt.toIso8601String(),
};
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is FavoriteTaxiModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
@override
String toString() {
return 'FavoriteTaxiModel(id: $id, userId: $userId, taxiId: $taxiId)';
}
}