import 'dart:convert'; import 'package:intl/intl.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import '../models/models.dart'; class RepoService { static int dbVersion = 13; static String dbName = 'olivamiapos01.db'; static const String id = Basico.identificadorWeb; static const String idLocal = Basico.identificadorLocal; static Database? _db; final Map contexto = { 'Categoria': CategoriaProducto(), 'Producto': Producto(), 'Pedido': Pedido(productos: []), 'PedidoProducto': PedidoProducto(), 'Gasto': Gasto(), 'Deposito': Deposito(), 'CorteCaja': CorteCaja(), }; Future get db async { if (_db != null) return _db; _db = await databaseInit(); return _db; } Future databaseInit() async { String dir = (await getApplicationDocumentsDirectory()).path; String path = join(dir, dbName); return await openDatabase( path, version: dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade, ); } _onCreate(Database db, int version) async { contexto.forEach((String nombre, dynamic modelo) async { Map model = json.decode(json.encode(modelo.toJson())); String sql = "CREATE TABLE ${modelo.runtimeType.toString()} (id INTEGER PRIMARY KEY AUTOINCREMENT"; model.forEach((String key, dynamic value) { if (key != "id") { String tipo = value.runtimeType.toString(); String sqlType = ""; if (equals(tipo, 'int')) { sqlType = "INTEGER"; } else if (equals(tipo, 'double')) { sqlType = "REAL"; } else if (equals(tipo, 'bool')) { sqlType = "BOOLEAN"; } else { sqlType = "TEXT"; } sql += ", $key $sqlType"; } }); sql += ")"; await db.execute(sql); await db.execute(''' CREATE TABLE PedidoProductoTopping ( id INTEGER PRIMARY KEY AUTOINCREMENT, idPedidoProducto INTEGER, idTopping INTEGER, FOREIGN KEY (idPedidoProducto) REFERENCES PedidoProducto(id), FOREIGN KEY (idTopping) REFERENCES Producto(id) ) '''); if (modelo.runtimeType.toString() == 'Pedido') { await db.execute( 'ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0'); } await db.execute(''' CREATE TABLE Descuento ( id INTEGER PRIMARY KEY AUTOINCREMENT, porcentaje INTEGER ) '''); await db.insert('Descuento', {'porcentaje': 0}); await db.insert('Descuento', {'porcentaje': 5}); await db.insert('Descuento', {'porcentaje': 10}); await db.insert('Descuento', {'porcentaje': 15}); await db.insert('Descuento', {'porcentaje': 20}); await db.insert('Descuento', {'porcentaje': 25}); await db.insert('Descuento', {'porcentaje': 30}); }); await db.execute(''' CREATE TABLE ProductoTopping ( id INTEGER PRIMARY KEY AUTOINCREMENT, idProducto INTEGER, idTopping INTEGER, FOREIGN KEY (idProducto) REFERENCES Producto(id), FOREIGN KEY (idTopping) REFERENCES Producto(id) ) '''); await db.execute(''' CREATE TABLE Variable ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT, clave TEXT, descripcion TEXT, activo BOOLEAN, idLocal INTEGER, eliminado TEXT ) '''); } void _onUpgrade(Database db, int oldVersion, int newVersion) async { while (oldVersion < newVersion) { switch (oldVersion) { case 1: await db.execute( "ALTER TABLE CategoriaProducto ADD COLUMN esToping INTEGER DEFAULT 0"); await db.execute( "ALTER TABLE CategoriaProducto ADD COLUMN descripcion TEXT DEFAULT ''"); await db.execute( "ALTER TABLE CategoriaProducto ADD COLUMN maximo INTEGER"); break; case 2: await db.execute(''' CREATE TABLE ProductoTopping ( id INTEGER PRIMARY KEY AUTOINCREMENT, idProducto INTEGER, idTopping INTEGER, FOREIGN KEY (idProducto) REFERENCES Producto(id), FOREIGN KEY (idTopping) REFERENCES Producto(id) ) '''); break; case 3: await db.execute(''' CREATE TABLE PedidoProductoTopping ( id INTEGER PRIMARY KEY AUTOINCREMENT, idPedidoProducto INTEGER, idTopping INTEGER, FOREIGN KEY (idPedidoProducto) REFERENCES PedidoProducto(id), FOREIGN KEY (idTopping) REFERENCES Producto(id) ) '''); break; case 4: await db.execute(''' ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0 '''); break; case 5: await db.execute(''' CREATE TABLE IF NOT EXISTS Descuento ( id INTEGER PRIMARY KEY AUTOINCREMENT, porcentaje INTEGER ) '''); await db.insert('Descuento', {'porcentaje': 0}); await db.insert('Descuento', {'porcentaje': 5}); await db.insert('Descuento', {'porcentaje': 10}); await db.insert('Descuento', {'porcentaje': 15}); await db.insert('Descuento', {'porcentaje': 20}); await db.insert('Descuento', {'porcentaje': 25}); await db.insert('Descuento', {'porcentaje': 30}); break; case 6: await db.execute(''' ALTER TABLE Pedido ADD COLUMN tipoPago TEXT DEFAULT ''; '''); await db.execute(''' ALTER TABLE Pedido ADD COLUMN cantEfectivo REAL DEFAULT 0; '''); await db.execute(''' ALTER TABLE Pedido ADD COLUMN cantTarjeta REAL DEFAULT 0; '''); await db.execute(''' ALTER TABLE Pedido ADD COLUMN cantTransferencia REAL DEFAULT 0; '''); break; case 7: await db.execute(''' CREATE TABLE Variable ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT, clave TEXT, descripcion TEXT, activo BOOLEAN, idLocal INTEGER, eliminado TEXT ) '''); break; case 9: await db.execute(''' ALTER TABLE Pedido ADD COLUMN idWeb INTEGER; '''); await db.execute(''' ALTER TABLE Pedido ADD COLUMN sincronizado TEXT; '''); await db.execute(''' ALTER TABLE PedidoProducto ADD COLUMN idWeb INTEGER; '''); await db.execute(''' ALTER TABLE PedidoProducto ADD COLUMN sincronizado TEXT; '''); break; case 10: await db.execute(''' update Pedido set sincronizado = null, peticion = strftime('%Y-%m-%dT%H:%M:%S', datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00')) WHERE strftime('%Y-%m-%dT%H:%M:%S', datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00')) is not null '''); break; case 12: await db.execute(''' ALTER TABLE CategoriaProducto ADD COLUMN creado TEXT; '''); await db.execute(''' ALTER TABLE CategoriaProducto ADD COLUMN modificado TEXT; '''); await db.execute(''' ALTER TABLE Producto ADD COLUMN creado TEXT; '''); await db.execute(''' ALTER TABLE Producto ADD COLUMN modificado TEXT; '''); await db.execute(''' ALTER TABLE Producto ADD COLUMN activo INTEGER; '''); break; } oldVersion++; } } Future guardar(T model) async { try { print("Guardando modelo en la base de datos: ${model.runtimeType}"); // Comprobar si es un modelo que obtiene las fechas del servidor (Producto o CategoriaProducto) if (model is Producto || model is CategoriaProducto) { // No hacer nada, las fechas vienen del servidor print("Usando fechas del servidor para ${model.runtimeType}"); } else if (model is Basico) { // Generar las fechas localmente para otros modelos asignarFechasLocalmente(model); } // Convertir el modelo a JSON para la base de datos String modelo = json.encode(model, toEncodable: toEncodable); print("Modelo convertido a JSON: $modelo"); Map modelMap = json.decode(modelo); var dbClient = await db; String nombreTabla = model.runtimeType.toString(); int? id = modelMap['id']; if (id == null || id == 0) { modelMap.remove('id'); } List existing = id != null && id > 0 ? await dbClient!.query(nombreTabla, where: 'id = ?', whereArgs: [id]) : []; if (existing.isNotEmpty) { print("Actualizando registro existente con ID: $id"); await dbClient!.update( nombreTabla, modelMap, where: 'id = ?', whereArgs: [id], ); } else { print("Insertando nuevo registro en la tabla $nombreTabla"); id = await dbClient!.insert(nombreTabla, modelMap); } return id!; } catch (e) { print('Error al guardar en dynamic: $e'); return 0; } } void asignarFechasLocalmente(Basico model) { DateTime ahora = DateTime.now(); if (model.creado == null) { model.creado = ahora.toUtc(); } model.modificado = ahora.toUtc(); } Future _guardarToppings( Database db, int idProducto, List? topings) async { await db.delete('ProductoTopping', where: 'idProducto = ?', whereArgs: [idProducto]); if (topings != null) { for (var topping in topings) { await db.insert('ProductoTopping', { 'idProducto': idProducto, 'idTopping': topping.id, }); } } } Future guardarLocal(T model) async { var dbClient = await db; String nombreTabla = model.runtimeType.toString(); String modelo = json.encode(model, toEncodable: toEncodable); Map modelMap = json.decode(modelo); if (nombreTabla == "PedidoProductoTopping") { modelMap.remove('idLocal'); modelMap.remove('eliminado'); } if (modelMap['id'] == null || modelMap['id'] == 0) { modelMap.remove('id'); } int resultado; int? identificadorLocal = modelMap[Basico.identificadorLocal]; if (identificadorLocal != null && identificadorLocal > 0) { resultado = await dbClient!.update( nombreTabla, modelMap, where: "$idLocal = ?", whereArgs: [identificadorLocal], ); } else { resultado = await dbClient!.insert(nombreTabla, modelMap); var rawQuery = await dbClient.rawQuery("SELECT last_insert_rowid() as id"); if (rawQuery.isNotEmpty) { resultado = int.parse(rawQuery.first["id"].toString()); modelMap[Basico.identificadorLocal] = resultado; } } if (model is Pedido) { model.id = resultado; } if (model is Producto) { await _guardarToppings(dbClient!, model.id!, model.topings); } return resultado; } dynamic toEncodable(dynamic item) { print("Serializando objeto: $item"); if (item is Pedido) { return item.toJson(); } else if (item is PedidoProducto) { return item.toJson(); } else if (item is PedidoProductoTopping) { return item.toJson(); } else if (item is Producto) { return item.toJson(); } else if (item is CategoriaProducto) { return item.toJson(); } else if (item is Variable) { return item.toJson(); } throw UnsupportedError( 'Type not supported for serialization: ${item.runtimeType}'); } Future> obtenerTodos({String orderBy = 'id DESC'}) async { var db = await this.db; String tableName = T.toString(); var result = await db! .query(tableName, where: 'eliminado IS NULL', orderBy: orderBy); return result.map((map) => fromMap(map)).toList(); } T fromMap(Map map) { switch (T) { case Pedido: return Pedido.fromJson(map) as T; case Producto: return Producto.fromJson(map) as T; default: throw Exception('Tipo no soportado'); } } Future obtenerPorId(int id) async { Database? dbClient = await db; List maps = await dbClient!.query('Pedido', where: 'id = ?', whereArgs: [id]); if (maps.isNotEmpty) { return Pedido.fromJson(Map.from(maps.first)); } return null; } Future> obtenerPorIdPedido(int idPedido) async { Database? dbClient = await db; List maps = await dbClient! .query('PedidoProducto', where: 'idPedido = ?', whereArgs: [idPedido]); return maps .map((map) => PedidoProducto.fromJson(Map.from(map))) .toList(); } Future> obtenerDepositosPorIdCorteCaja(int idCorteCaja) async { var dbClient = await db; List> maps = await dbClient!.query( 'Deposito', where: 'idCorteCaja = ?', whereArgs: [idCorteCaja], ); return maps.map((map) => Deposito.fromJson(map)).toList(); } Future> obtenerGastosPorIdCorteCaja(int idCorteCaja) async { var dbClient = await db; List> maps = await dbClient!.query( 'Gasto', where: 'idCorteCaja = ?', whereArgs: [idCorteCaja], ); return maps.map((map) => Gasto.fromJson(map)).toList(); } Future contarPedidos() async { Database? dbClient = await db; var result = await dbClient!.rawQuery('SELECT COUNT(*) FROM Pedido'); return Sqflite.firstIntValue(result) ?? 0; } Future> obtenerPedidosPaginados(int limit, int offset) async { Database? dbClient = await db; List> result = await dbClient! .query('Pedido', limit: limit, offset: offset, orderBy: 'id DESC'); return result.map((map) => Pedido.fromJson(map)).toList(); } Future obtenerProductoPorId(int idProducto) async { Database? dbClient = await db; List maps = await dbClient! .query('Producto', where: 'id = ?', whereArgs: [idProducto]); if (maps.isNotEmpty) { return Producto.fromJson(Map.from(maps.first)); } return null; } Future> obtenerToppingsPorProducto(int idProducto) async { var dbClient = await db; var result = await dbClient!.query( 'ProductoTopping', where: 'idProducto = ?', whereArgs: [idProducto], ); return result.map((map) => map['idTopping'] as int).toList(); } Future> obtenerToppingsPorPedidoProducto( int idPedidoProducto) async { var dbClient = await db; List maps = await dbClient!.query('PedidoProductoTopping', where: 'idPedidoProducto = ?', whereArgs: [idPedidoProducto]); if (maps.isNotEmpty) { return maps .map((map) => PedidoProductoTopping.fromJson(Map.from(map))) .toList(); } return []; } Future obtenerProximoFolio() async { var dbClient = await db; var result = await dbClient!.rawQuery( 'SELECT MAX(CAST(folio AS INTEGER)) as last_folio FROM Pedido'); if (result.isNotEmpty && result.first["last_folio"] != null) { return int.tryParse(result.first["last_folio"].toString())! + 1; } return 1; } Future> buscarPorFolio(String folio) async { var dbClient = await db; List> maps = await dbClient!.query( 'Pedido', where: 'folio LIKE ?', whereArgs: ['%$folio%'], ); return maps.map((map) => fromMap(map)).toList(); } Future> buscarPorFecha( DateTime startDate, DateTime endDate) async { var dbClient = await db; String startDateString = startDate.toIso8601String(); String endDateString = endDate.toIso8601String(); print( 'Ejecutando consulta: SELECT * FROM Pedido WHERE peticion BETWEEN $startDateString AND $endDateString'); List> maps = await dbClient!.rawQuery(''' SELECT * FROM Pedido WHERE peticion BETWEEN ? AND ? ''', [startDateString, endDateString]); print('Resultado de la consulta: ${maps.length} pedidos encontrados.'); return maps.map((map) => Pedido.fromJson(map)).toList(); } Future> buscarPorFechaCorte( DateTime startDate, DateTime endDate) async { var dbClient = await db; String startDateString = DateFormat('dd-MM-yyyy').format(startDate); String endDateString = DateFormat('dd-MM-yyyy 23:59:59').format(endDate); List> maps = await dbClient!.query( 'CorteCaja', where: 'fecha BETWEEN ? AND ?', whereArgs: [startDateString, endDateString], ); return maps.map((map) => CorteCaja.fromJson(map)).toList(); } Future eliminar(int id) async { var dbClient = await db; String tableName = T.toString(); await dbClient!.delete(tableName, where: 'id = ?', whereArgs: [id]); } Future> obtenerTodosDescuentos() async { var dbClient = await db; var result = await dbClient!.query('Descuento', orderBy: 'porcentaje ASC'); return result.map((map) => Descuento.fromJson(map)).toList(); } Future guardarDescuento(Descuento descuento) async { var dbClient = await db; if (descuento.id != null && descuento.id! > 0) { return await dbClient!.update( 'Descuento', descuento.toJson(), where: 'id = ?', whereArgs: [descuento.id], ); } else { return await dbClient!.insert('Descuento', descuento.toJson()); } } Future eliminarDescuento(int id) async { var dbClient = await db; return await dbClient! .delete('Descuento', where: 'id = ?', whereArgs: [id]); } Future obtenerPorNombre(String nombre) async { var dbClient = await db; List> maps = await dbClient!.query( 'Variable', where: 'nombre = ?', whereArgs: [nombre], ); if (maps.isNotEmpty) { return Variable.fromJson(Map.from(maps.first)); } return null; } Future> obtenerPedidosOrdenadosPorFecha() async { var dbClient = await db; String orderBy = "datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12)) ASC"; List> result = await dbClient!.query( 'Pedido', where: 'sincronizado IS NULL', orderBy: orderBy, ); return result.map((map) => Pedido.fromJson(map)).toList(); } Future sincronizarCategorias( List categoriasApi) async { var db = await RepoService().db; var categoriasLocalesQuery = await db!.query('CategoriaProducto'); List categoriasLocales = categoriasLocalesQuery .map((e) => CategoriaProducto.fromJson(e)) .toList(); for (var categoriaApi in categoriasApi) { var categoriaLocal = categoriasLocales.firstWhere( (categoria) => categoria.id == categoriaApi.id, orElse: () => CategoriaProducto(), ); if (categoriaLocal.id != 0 && categoriaApi.modificado != null && (categoriaLocal.modificado == null || categoriaApi.modificado!.isAfter(categoriaLocal.modificado!))) { await RepoService().guardar(categoriaApi); } else if (categoriaLocal.id == 0) { await RepoService().guardar(categoriaApi); } } } Future sincronizarProductos(List productosApi) async { var db = await RepoService().db; var productosLocalesQuery = await db!.query('Producto'); List productosLocales = productosLocalesQuery.map((e) => Producto.fromJson(e)).toList(); for (var productoApi in productosApi) { var productoLocal = productosLocales.firstWhere( (producto) => producto.id == productoApi.id, orElse: () => Producto(), ); if (productoLocal.id != 0 && productoApi.modificado != null && productoApi.modificado!.isAfter(productoLocal.modificado!)) { await RepoService().guardar(productoApi); } else if (productoLocal.id == 0) { await RepoService().guardar(productoApi); } } } }