Avance de la aplicacion

This commit is contained in:
2026-05-22 20:43:49 -06:00
parent 37e83a8226
commit 458af32fcf
13 changed files with 1918 additions and 463 deletions

View File

@@ -1,7 +1,6 @@
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/models.dart';
import '../models/route_model.dart';
class DbHelper {
static Database? _db;
@@ -12,47 +11,75 @@ class DbHelper {
}
static Future<Database> _initDb() async {
final path = join(await getDatabasesPath(), 'celaya_v2.db');
final path = join(await getDatabasesPath(), 'celaya_v3.db');
return openDatabase(path, version: 1, onCreate: _onCreate);
}
static Future<void> _onCreate(Database db, int v) async {
// Usuarios
await db.execute('''CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL,
email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, rol TEXT NOT NULL)''');
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL, email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL, rol TEXT NOT NULL)''');
// Domicilios (User → Domicilio → Colonia/Zona → Ruta)
await db.execute('''CREATE TABLE domicilios(
id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL,
calle TEXT NOT NULL, colonia TEXT NOT NULL, route_id TEXT NOT NULL,
horario_estimado TEXT NOT NULL, is_primary INTEGER DEFAULT 1)''');
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL, alias TEXT DEFAULT 'Casa',
calle TEXT NOT NULL, colonia TEXT NOT NULL,
route_id TEXT NOT NULL, horario_estimado TEXT NOT NULL,
is_primary INTEGER DEFAULT 0)''');
await db.execute('''CREATE TABLE asignaciones(
id INTEGER PRIMARY KEY AUTOINCREMENT, conductor_id INTEGER NOT NULL,
route_id TEXT NOT NULL, dia_semana TEXT NOT NULL, turno TEXT NOT NULL)''');
// Definiciones de rutas creadas por admin
await db.execute('''CREATE TABLE route_definitions(
id INTEGER PRIMARY KEY AUTOINCREMENT,
route_id TEXT UNIQUE NOT NULL, nombre TEXT NOT NULL,
dias TEXT NOT NULL, hora_inicio TEXT NOT NULL,
hora_fin TEXT NOT NULL, turno TEXT NOT NULL,
colonias TEXT NOT NULL, activa INTEGER DEFAULT 1)''');
// Estado de rutas
await db.execute('''CREATE TABLE route_status(
route_id TEXT PRIMARY KEY, status TEXT NOT NULL,
mensaje TEXT, updated_at TEXT)''');
// Asignaciones conductor
await db.execute('''CREATE TABLE asignaciones(
id INTEGER PRIMARY KEY AUTOINCREMENT,
conductor_id INTEGER NOT NULL, route_id TEXT NOT NULL,
dia_semana TEXT NOT NULL, turno TEXT NOT NULL)''');
// Alertas del sistema
await db.execute('''CREATE TABLE alertas(
id INTEGER PRIMARY KEY AUTOINCREMENT, tipo TEXT NOT NULL,
route_id TEXT NOT NULL, mensaje TEXT NOT NULL,
fecha TEXT NOT NULL, resuelta INTEGER DEFAULT 0)''');
id INTEGER PRIMARY KEY AUTOINCREMENT,
tipo TEXT NOT NULL, route_id TEXT NOT NULL,
mensaje TEXT NOT NULL, fecha TEXT NOT NULL,
resuelta INTEGER DEFAULT 0)''');
// Reportes ciudadanos
await db.execute('''CREATE TABLE reportes(
id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL,
tipo TEXT NOT NULL, descripcion TEXT NOT NULL, colonia TEXT NOT NULL,
route_id TEXT, fecha TEXT NOT NULL, estado TEXT DEFAULT 'PENDIENTE',
calificacion INTEGER DEFAULT 5)''');
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL, tipo TEXT NOT NULL,
descripcion TEXT NOT NULL, colonia TEXT NOT NULL,
route_id TEXT, fecha TEXT NOT NULL,
estado TEXT DEFAULT 'PENDIENTE', calificacion INTEGER DEFAULT 5)''');
// Seed: admin y conductor demo
// RESEÑAS del servicio
await db.execute('''CREATE TABLE reviews(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL, colonia TEXT NOT NULL,
route_id TEXT NOT NULL, estrellas INTEGER NOT NULL,
comentario TEXT NOT NULL, fecha TEXT NOT NULL,
nombre_usuario TEXT DEFAULT 'Ciudadano')''');
// Seed: admin y conductor
await db.insert('users', {'nombre':'Administrador','email':'admin@celaya.gob.mx',
'password':'admin123','rol':'ADMINISTRADOR'});
await db.insert('users', {'nombre':'Juan Conductor','email':'conductor@celaya.gob.mx',
'password':'conductor123','rol':'CONDUCTOR'});
}
// ── USERS ──────────────────────────────────────────────────────────────
// ── USERS ────────────────────────────────────────────────────────────────
static Future<int> insertUser(UserModel u) async =>
(await database).insert('users', u.toMap(), conflictAlgorithm: ConflictAlgorithm.abort);
@@ -71,49 +98,71 @@ class DbHelper {
return res.map((m) => UserModel.fromMap(m)).toList();
}
// ── DOMICILIOS ─────────────────────────────────────────────────────────
static Future<int> insertDomicilio(DomicilioModel d) async =>
(await database).insert('domicilios', d.toMap());
// ── DOMICILIOS ───────────────────────────────────────────────────────────
static Future<int> insertDomicilio(DomicilioModel d) async {
final db = await database;
// Si es el primero del usuario, marcarlo como primario
final existing = await db.query('domicilios', where:'user_id=?', whereArgs:[d.userId]);
final isPrimary = existing.isEmpty ? 1 : (d.isPrimary ? 1 : 0);
return db.insert('domicilios', {...d.toMap(), 'is_primary': isPrimary});
}
static Future<List<DomicilioModel>> getDomiciliosByUser(int userId) async {
final res = await (await database).query('domicilios',
where:'user_id=?', whereArgs:[userId], orderBy:'is_primary DESC, id ASC');
return res.map((m) => DomicilioModel.fromMap(m)).toList();
}
static Future<DomicilioModel?> getPrimaryDomicilio(int userId) async {
final res = await (await database).query('domicilios',
final db = await database;
var res = await db.query('domicilios',
where:'user_id=? AND is_primary=1', whereArgs:[userId]);
if (res.isEmpty) {
res = await db.query('domicilios', where:'user_id=?', whereArgs:[userId], limit:1);
}
return res.isEmpty ? null : DomicilioModel.fromMap(res.first);
}
// ── ASIGNACIONES ───────────────────────────────────────────────────────
static Future<void> upsertAsignacion(AssignmentModel a) async {
static Future<void> setPrimaryDomicilio(int domId, int userId) async {
final db = await database;
final ex = await db.query('asignaciones',
where:'conductor_id=? AND dia_semana=?',
whereArgs:[a.conductorId, a.diaSemana]);
if (ex.isEmpty) {
await db.insert('asignaciones', a.toMap());
} else {
await db.update('asignaciones', {'route_id':a.routeId,'turno':a.turno},
where:'conductor_id=? AND dia_semana=?',
whereArgs:[a.conductorId, a.diaSemana]);
}
await db.update('domicilios', {'is_primary':0}, where:'user_id=?', whereArgs:[userId]);
await db.update('domicilios', {'is_primary':1}, where:'id=?', whereArgs:[domId]);
}
static Future<List<AssignmentModel>> getAsignacionesByConductor(int conductorId) async {
final res = await (await database).query('asignaciones',
where:'conductor_id=?', whereArgs:[conductorId]);
return res.map((m) => AssignmentModel.fromMap(m)).toList();
static Future<void> deleteDomicilio(int id) async =>
(await database).delete('domicilios', where:'id=?', whereArgs:[id]);
static Future<List<DomicilioModel>> getDomiciliosByRoute(String routeId) async {
final res = await (await database).query('domicilios',
where:'route_id=?', whereArgs:[routeId]);
return res.map((m) => DomicilioModel.fromMap(m)).toList();
}
static Future<List<AssignmentModel>> getAllAsignaciones() async {
final res = await (await database).query('asignaciones');
return res.map((m) => AssignmentModel.fromMap(m)).toList();
// ── ROUTE DEFINITIONS ────────────────────────────────────────────────────
static Future<int> insertRouteDefinition(RouteDefinitionModel r) async =>
(await database).insert('route_definitions', r.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
static Future<List<RouteDefinitionModel>> getAllRouteDefinitions() async {
final res = await (await database).query('route_definitions', orderBy:'route_id ASC');
return res.map((m) => RouteDefinitionModel.fromMap(m)).toList();
}
// ── ROUTE STATUS ───────────────────────────────────────────────────────
static Future<void> upsertRouteStatus(RouteStatusModel s) async {
final db = await database;
await db.insert('route_status', s.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
static Future<RouteDefinitionModel?> getRouteDefinitionById(String routeId) async {
final res = await (await database).query('route_definitions',
where:'route_id=?', whereArgs:[routeId]);
return res.isEmpty ? null : RouteDefinitionModel.fromMap(res.first);
}
static Future<void> updateRouteDefinition(RouteDefinitionModel r) async =>
(await database).update('route_definitions', r.toMap(),
where:'route_id=?', whereArgs:[r.routeId]);
// ── ROUTE STATUS ─────────────────────────────────────────────────────────
static Future<void> upsertRouteStatus(RouteStatusModel s) async =>
(await database).insert('route_status', s.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
static Future<RouteStatusModel?> getRouteStatus(String routeId) async {
final res = await (await database).query('route_status',
where:'route_id=?', whereArgs:[routeId]);
@@ -125,11 +174,35 @@ class DbHelper {
return res.map((m) => RouteStatusModel.fromMap(m)).toList();
}
// ── ALERTAS ────────────────────────────────────────────────────────────
// ── ASIGNACIONES ─────────────────────────────────────────────────────────
static Future<void> upsertAsignacion(AssignmentModel a) async {
final db = await database;
final ex = await db.query('asignaciones',
where:'conductor_id=? AND dia_semana=?', whereArgs:[a.conductorId, a.diaSemana]);
if (ex.isEmpty) {
await db.insert('asignaciones', a.toMap());
} else {
await db.update('asignaciones', {'route_id':a.routeId,'turno':a.turno},
where:'conductor_id=? AND dia_semana=?', whereArgs:[a.conductorId, a.diaSemana]);
}
}
static Future<List<AssignmentModel>> getAsignacionesByConductor(int id) async {
final res = await (await database).query('asignaciones',
where:'conductor_id=?', whereArgs:[id]);
return res.map((m) => AssignmentModel.fromMap(m)).toList();
}
static Future<List<AssignmentModel>> getAllAsignaciones() async {
final res = await (await database).query('asignaciones');
return res.map((m) => AssignmentModel.fromMap(m)).toList();
}
// ── ALERTAS ──────────────────────────────────────────────────────────────
static Future<int> insertAlerta(AlertaModel a) async =>
(await database).insert('alertas', a.toMap());
static Future<List<AlertaModel>> getAlertas({bool soloNoResueltas = false}) async {
static Future<List<AlertaModel>> getAlertas({bool soloNoResueltas=false}) async {
final db = await database;
final res = soloNoResueltas
? await db.query('alertas', where:'resuelta=0', orderBy:'fecha DESC')
@@ -137,49 +210,70 @@ class DbHelper {
return res.map((m) => AlertaModel.fromMap(m)).toList();
}
static Future<List<AlertaModel>> getIncidentesConductor() async {
final res = await (await database).query('alertas',
where:"tipo LIKE 'INCIDENTE_%'", orderBy:'fecha DESC');
return res.map((m) => AlertaModel.fromMap(m)).toList();
}
static Future<void> resolverAlerta(int id) async =>
(await database).update('alertas', {'resuelta':1}, where:'id=?', whereArgs:[id]);
// ── REPORTES ───────────────────────────────────────────────────────────
// ── REPORTES ─────────────────────────────────────────────────────────────
static Future<int> insertReporte(ReporteModel r) async =>
(await database).insert('reportes', r.toMap());
static Future<List<ReporteModel>> getAllReportes() async {
final res = await (await database).query('reportes', orderBy:'fecha DESC');
return res.map((m) => ReporteModel.fromMap(m)).toList();
}
static Future<List<ReporteModel>> getReportesByUser(int userId) async {
final res = await (await database).query('reportes',
where:'user_id=?', whereArgs:[userId], orderBy:'fecha DESC');
return res.map((m) => ReporteModel.fromMap(m)).toList();
}
static Future<List<ReporteModel>> getAllReportes() async {
final res = await (await database).query('reportes', orderBy:'fecha DESC');
return res.map((m) => ReporteModel.fromMap(m)).toList();
static Future<List<Map<String, dynamic>>> getReportesConUsuario() async {
final db = await database;
return db.rawQuery('''
SELECT r.*, u.nombre as user_nombre, u.email as user_email
FROM reportes r LEFT JOIN users u ON r.user_id = u.id
ORDER BY r.fecha DESC''');
}
static Future<void> updateReporteEstado(int id, String estado) async =>
(await database).update('reportes', {'estado':estado}, where:'id=?', whereArgs:[id]);
// ── REPORTES CON INFO DE USUARIO ──────────────────────────────────────
static Future<List<Map<String, dynamic>>> getReportesConUsuario() async {
// ── REVIEWS ──────────────────────────────────────────────────────────────
static Future<int> insertReview(ReviewModel r) async =>
(await database).insert('reviews', r.toMap());
static Future<List<ReviewModel>> getAllReviews() async {
final res = await (await database).query('reviews', orderBy:'fecha DESC');
return res.map((m) => ReviewModel.fromMap(m)).toList();
}
static Future<bool> hasReviewedRoute(int userId, String routeId) async {
// Verifica si el usuario ya calificó esta ruta hoy
final today = DateTime.now().toIso8601String().substring(0, 10);
final res = await (await database).query('reviews',
where:"user_id=? AND route_id=? AND fecha LIKE '$today%'",
whereArgs:[userId, routeId]);
return res.isNotEmpty;
}
// Promedio por colonia para el admin
static Future<List<Map<String, dynamic>>> getReviewSummaryByColonia() async {
final db = await database;
return db.rawQuery('''
SELECT r.*, u.nombre as user_nombre, u.email as user_email
FROM reportes r
LEFT JOIN users u ON r.user_id = u.id
ORDER BY r.fecha DESC
''');
}
// ── INCIDENTES CONDUCTOR ───────────────────────────────────────────────
static Future<List<AlertaModel>> getIncidentesConductor() async {
final res = await (await database).query('alertas',
where: "tipo LIKE 'INCIDENTE_%'", orderBy: 'fecha DESC');
return res.map((m) => AlertaModel.fromMap(m)).toList();
}
// ── DOMICILIOS POR RUTA ────────────────────────────────────────────────
static Future<List<DomicilioModel>> getDomiciliosByRoute(String routeId) async {
final res = await (await database).query('domicilios',
where: 'route_id = ?', whereArgs: [routeId]);
return res.map((m) => DomicilioModel.fromMap(m)).toList();
SELECT colonia, route_id,
AVG(estrellas) as promedio,
COUNT(*) as total,
MIN(estrellas) as min_est,
MAX(estrellas) as max_est
FROM reviews
GROUP BY colonia
ORDER BY promedio ASC''');
}
}