sat_scraping 0.0.9 copy "sat_scraping: ^0.0.9" to clipboard
sat_scraping: ^0.0.9 copied to clipboard

Herramienta que permite obtener información fiscal de un contribuyente. Ayuda a automatizar dicho proceso y puede ser utilizado en aplicaciones de negocios o financieras.

example/lib/main.dart

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:sat_scraping/sat_scraping.dart';
import 'package:share_plus/share_plus.dart';

import 'scanner/scanner.dart';
import 'services/history_service.dart';
import 'services/theme_service.dart';
import 'theme/app_theme.dart';
import 'utils/permissions.dart';
import 'widgets/empty_state.dart';
import 'widgets/error_banner.dart';
import 'widgets/history_list.dart';
import 'widgets/info_sat.dart';
import 'widgets/info_sat_dialog.dart';

/// Punto de entrada de la aplicación.
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ThemeService.instance.init();
  runApp(const MyApp());
}

/// Widget raíz de la aplicación Info SAT.
///
/// Configura los temas claro y oscuro con [AppTheme] y establece
/// [ThemeMode.system] para adaptarse automáticamente a las preferencias
/// del usuario.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: ThemeService.instance.themeModeNotifier,
      builder: (context, themeMode, _) {
        return MaterialApp(
          title: 'Escáner de constancia de situación fiscal',
          debugShowCheckedModeBanner: false,
          theme: AppTheme.light,
          darkTheme: AppTheme.dark,
          themeMode: themeMode,
          home: const MyHomePage(title: 'Información Fiscal'),
        );
      },
    );
  }
}

/// Pantalla principal de la aplicación.
///
/// Maneja tres estados principales:
/// - **Vacío**: Sin datos cargados, muestra instrucciones y acciones
/// - **Cargando**: Consultando la información al SAT
/// - **Con datos**: Muestra la información fiscal en tarjetas premium
///
/// También gestiona el historial de consultas previas y el compartir
/// información.
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  /// Título mostrado en el AppBar.
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _loading = false;
  bool _error = false;
  String _errorMessage = '';
  int _historyCount = 0;

  InfoFiscal _infoFiscal = InfoFiscal.getDefault();

  final _historyService = HistoryService.instance;

  @override
  void initState() {
    super.initState();
    _loadHistoryCount();
  }

  /// Carga el número de registros en el historial para mostrar en el
  /// estado vacío.
  Future<void> _loadHistoryCount() async {
    final count = await _historyService.count;
    if (mounted) {
      setState(() => _historyCount = count);
    }
  }

  /// Comparte un resumen breve de la información fiscal.
  void _shared() {
    String regimenesComplete = '';
    for (final row in _infoFiscal.caracteristicasFiscales) {
      regimenesComplete +=
          'Régimen Fiscal: \t\t${row.codigoRegimen} - ${row.regimenFiscal}\n\n';
    }
    SharePlus.instance.share(
      ShareParams(
        text: 'RFC: \t\t ${_infoFiscal.rfc}\n\n'
            'ID CIF: \t\t ${_infoFiscal.idCif}\n\n'
            'Razón Social: \t\t${_infoFiscal.razonSocial}\n\n'
            'Código Postal: \t\t${_infoFiscal.cp}\n\n'
            '$regimenesComplete',
      ),
    );
  }

  /// Abre el escáner QR y consulta la información fiscal.
  Future<void> _scanQR(BuildContext ctx) async {
    setState(() {
      _loading = true;
      _error = false;
    });

    if (await PermissionUtils.request(Permission.camera, context: ctx) &&
        ctx.mounted) {
      final cameraResult = await Scanner.scanQR(context: ctx);

      if (cameraResult != null) {
        try {
          _infoFiscal = await SatScraping.getInfoFiscal(cameraResult);
          await _historyService.save(_infoFiscal);
          await _loadHistoryCount();
        } catch (e) {
          log('Error: $e');
          _error = true;
          _errorMessage = e.toString();
        }
      }
    }

    if (mounted) {
      setState(() => _loading = false);
    }
  }

  /// Abre el diálogo de ingreso manual y consulta la información fiscal.
  Future<void> _writeData() async {
    setState(() {
      _loading = true;
      _error = false;
    });

    final tmpMap = await showDialog(
      context: context,
      builder: (BuildContext context) => const InfoSatDialog(),
    );

    if (tmpMap == null) {
      setState(() => _loading = false);
      return;
    }

    try {
      _infoFiscal = await SatScraping.getInfoFiscalManual(
        rfc: tmpMap['rfc'] as String,
        idCif: tmpMap['idcif'] as String,
      );
      await _historyService.save(_infoFiscal);
      await _loadHistoryCount();
    } catch (e) {
      log('Error: $e');
      _error = true;
      _errorMessage = e.toString();
    }

    if (mounted) {
      setState(() => _loading = false);
    }
  }

  /// Abre la pantalla de historial.
  void _openHistory() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) => HistoryList(
          onSelect: (info) {
            setState(() {
              _infoFiscal = info;
              _error = false;
            });
            Navigator.of(context).pop();
          },
        ),
      ),
    );
  }

  /// Limpia el estado actual y vuelve al estado vacío.
  void _reset() {
    setState(() {
      _error = false;
      _infoFiscal = InfoFiscal.getDefault();
    });
    _loadHistoryCount();
  }

  bool get _hasData => _infoFiscal.rfc.isNotEmpty;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          // ── Botón de tema ──
          ValueListenableBuilder<ThemeMode>(
            valueListenable: ThemeService.instance.themeModeNotifier,
            builder: (context, mode, _) {
              IconData icon;
              if (mode == ThemeMode.light) {
                icon = Icons.dark_mode_outlined;
              } else if (mode == ThemeMode.dark) {
                icon = Icons.light_mode_outlined;
              } else {
                icon = Icons.brightness_auto_outlined;
              }
              return IconButton(
                onPressed: () => ThemeService.instance.toggleTheme(context),
                icon: Icon(icon),
                tooltip: 'Cambiar tema',
              );
            },
          ),
          // ── Botón de historial ──
          if (_historyCount > 0)
            IconButton(
              onPressed: _openHistory,
              icon: const Icon(Icons.history),
              tooltip: 'Historial',
            ),
          // ── Botón de compartir (solo con datos) ──
          if (_hasData)
            IconButton(
              onPressed: _loading ? null : _shared,
              icon: const Icon(Icons.share_outlined),
              tooltip: 'Compartir información',
            ),
          // ── Botón de limpiar (solo con datos) ──
          if (_hasData)
            IconButton(
              onPressed: _reset,
              icon: const Icon(Icons.refresh),
              tooltip: 'Nueva consulta',
            ),
        ],
      ),
      body: AnimatedSwitcher(
        duration: const Duration(milliseconds: 300),
        switchInCurve: Curves.easeOut,
        switchOutCurve: Curves.easeIn,
        child: _buildBody(),
      ),

      // ── FABs (solo en estado vacío) ──
      floatingActionButton: _hasData
          ? null
          : _loading
              ? null
              : Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    FloatingActionButton.extended(
                      heroTag: 'write',
                      onPressed: _writeData,
                      icon: const Icon(Icons.edit_note),
                      label: const Text('Manual'),
                    ),
                    const SizedBox(height: 12),
                    FloatingActionButton.extended(
                      heroTag: 'scan',
                      onPressed: () async => await _scanQR(context),
                      icon: const Icon(Icons.qr_code_scanner),
                      label: const Text('Escanear'),
                    ),
                  ],
                ),
    );
  }

  /// Construye el cuerpo de la pantalla según el estado actual.
  Widget _buildBody() {
    // Estado de carga
    if (_loading) {
      return const Center(
        key: ValueKey('loading'),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator.adaptive(),
            SizedBox(height: 20),
            Text(
              'Consultando información fiscal...',
              style: TextStyle(fontSize: 16),
            ),
          ],
        ),
      );
    }

    // Estado con datos
    if (_hasData) {
      return ListView(
        key: const ValueKey('data'),
        padding: const EdgeInsets.all(16),
        children: [
          if (_error)
            ErrorBanner(
              message: _errorMessage,
              onDismiss: () => setState(() => _error = false),
            ),
          InfoSat(infoFiscal: _infoFiscal),
          const SizedBox(height: 80), // Espacio para el FAB
        ],
      );
    }

    // Estado vacío (con posible error)
    return Column(
      key: const ValueKey('empty'),
      children: [
        if (_error)
          ErrorBanner(
            message: _errorMessage,
            onRetry: () => _scanQR(context),
            onDismiss: () => setState(() => _error = false),
          ),
        Expanded(
          child: EmptyState(
            onScanQR: () => _scanQR(context),
            onManualInput: _writeData,
            onViewHistory: _historyCount > 0 ? _openHistory : null,
            historyCount: _historyCount,
          ),
        ),
      ],
    );
  }
}
6
likes
160
points
37
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Herramienta que permite obtener información fiscal de un contribuyente. Ayuda a automatizar dicho proceso y puede ser utilizado en aplicaciones de negocios o financieras.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

http, new_beautiful_soup_dart

More

Packages that depend on sat_scraping