Agrego frontend app

This commit is contained in:
Pablo
2026-08-22 19:08:19 -03:00
parent 824e092b29
commit 2d0797e627
254 changed files with 37231 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:gimnasio_soma/core/config/app_env.dart';
import 'package:gimnasio_soma/core/config/env_banner.dart';
import 'package:gimnasio_soma/core/config/supabase_config.dart';
import 'package:gimnasio_soma/core/router/app_router.dart';
import 'package:gimnasio_soma/core/services/soma_logger.dart';
import 'package:gimnasio_soma/core/theme/soma_theme.dart';
/// Si se compiló para prod y no vino marcada por `soma_pro_updater`, relanza
/// a través de él en vez de arrancar directo. Evita que alguien se quede en
/// una versión vieja por abrir el .exe de la app sin pasar por el updater.
/// Se salta en dev porque ahí no existe `soma_pro_updater` al lado.
Future<bool> _redirectToUpdaterIfNeeded(List<String> args) async {
if (!AppEnv.isProd || args.contains('--from-updater')) return false;
// En Windows/Linux esta app vive en una subcarpeta `app/` al lado del
// updater (evita que sus carpetas `data/` se pisen); en macOS son
// hermanos porque cada `.app` ya es autocontenido.
final exeDir = File(Platform.resolvedExecutable).parent;
// En macOS el exe vive en .app/Contents/MacOS/ — subimos 3 niveles para llegar
// a la carpeta que contiene ambos .app bundles (donde está soma_pro_updater.app).
// En Windows/Linux la app vive en app/ — subimos 1 nivel para llegar al updater.
final updaterDir = Platform.isMacOS ? exeDir.parent.parent.parent : exeDir.parent;
final sep = Platform.pathSeparator;
final updaterName = Platform.isMacOS ? 'soma_pro_updater.app' : 'soma_pro_updater.exe';
final updaterPath = '${updaterDir.path}$sep$updaterName';
final exists =
Platform.isMacOS ? Directory(updaterPath).existsSync() : File(updaterPath).existsSync();
if (!exists) return false;
// Freno de rebote: si ya redirigimos hace poco, el updater ya nos relanzó
// pero este proceso arrancó de nuevo sin `--from-updater` (en macOS, `open`
// sin `-n` puede reactivar una instancia existente en vez de crear una
// nueva, y ahí se pierde el `--args`). En vez de rebotar para siempre,
// cortamos el ciclo y arrancamos directo.
final bounceFile = File('${updaterDir.path}$sep.soma_redirect_bounce');
final nowMs = DateTime.now().millisecondsSinceEpoch;
if (bounceFile.existsSync()) {
final lastMs = int.tryParse(bounceFile.readAsStringSync().trim()) ?? 0;
if (nowMs - lastMs < 8000) {
try {
bounceFile.deleteSync();
} catch (_) {}
return false;
}
}
bounceFile.writeAsStringSync('$nowMs');
if (Platform.isMacOS) {
// `-n` fuerza una instancia nueva del updater en vez de reactivar una
// existente, así el `open` de abajo siempre entrega los args frescos.
await Process.start('open', ['-n', updaterPath], mode: ProcessStartMode.detached);
} else {
await Process.start(updaterPath, const [], mode: ProcessStartMode.detached);
}
// Mismo respiro que del lado del updater: si este proceso sale demasiado
// rápido, macOS puede no llegar a transferirle el foco al proceso recién
// lanzado y su ventana queda sin renderizar el primer frame.
await Future.delayed(const Duration(milliseconds: 600));
return true;
}
void main(List<String> args) async {
if (await _redirectToUpdaterIfNeeded(args)) {
exit(0);
}
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
final log = SomaLogger.instance;
log.detectPlatform();
log.info('App', 'Iniciando SOMA PRO...');
// Config de entorno horneada al compilar (--dart-define-from-file=config/<env>.json).
// Corta el arranque con un mensaje claro si se compiló sin pasar el archivo.
AppEnv.assertConfigured();
log.info('App', 'Entorno: ${AppEnv.env}');
try {
await SupabaseConfig.initialize();
log.info('App', 'Supabase inicializado');
} catch (e) {
log.error('App', 'Error inicializando Supabase', detail: e.toString());
}
// Captura errores de Flutter (widgets, rendering, etc.)
FlutterError.onError = (details) {
log.error('Flutter', details.exceptionAsString(),
detail: details.stack?.toString().split('\n').take(5).join('\n'));
};
runApp(const ProviderScope(child: MainApp()));
}, (error, stack) {
// Captura errores no manejados fuera del framework
SomaLogger.instance.error('Zone', error.toString(),
detail: stack.toString().split('\n').take(5).join('\n'));
});
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
title: 'SOMA PRO',
debugShowCheckedModeBanner: false,
theme: SomaTheme.dark,
routerConfig: router,
// Cinta "DEV" en cualquier build que no sea prod (ver EnvBanner).
builder: (context, child) =>
EnvBanner(child: child ?? const SizedBox.shrink()),
);
}
}