mapa con marcador añadido beta

This commit is contained in:
Abraham
2025-07-21 21:09:34 -07:00
parent 7f435fa791
commit dee9e52ae0
6 changed files with 2316 additions and 165 deletions

View File

@@ -0,0 +1,293 @@
# 📘 Estructura de Base de Datos — NETHIVE (Esquema: `nethive`)
---
## 🏢 `empresa`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|-----------------------------------|
| id | UUID | PRIMARY KEY |
| nombre | TEXT | Nombre de la empresa |
| rfc | TEXT | Registro fiscal |
| direccion | TEXT | Dirección |
| telefono | TEXT | Teléfono de contacto |
| email | TEXT | Correo electrónico |
| fecha_creacion | TIMESTAMP | Fecha de creación (default: now) |
| logo_url | TEXT | URL del logo |
| imagen_url | TEXT | URL de imagen principal |
---
## 🏪 `negocio`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|-----------------------------------|
| id | UUID | PRIMARY KEY |
| empresa_id | UUID | FK → empresa.id |
| nombre | TEXT | Nombre del negocio |
| direccion | TEXT | Dirección |
| latitud | DECIMAL(9,6) | Latitud geográfica |
| longitud | DECIMAL(9,6) | Longitud geográfica |
| tipo_local | TEXT | Tipo de local (Sucursal, etc.) |
| fecha_creacion | TIMESTAMP | Default: now() |
| logo_url | TEXT | Logo del negocio |
| imagen_url | TEXT | Imagen del negocio |
---
## 🧾 `categoria_componente`
| Columna | Tipo de dato | Descripción |
|---------|--------------|--------------------------|
| id | SERIAL | PRIMARY KEY |
| nombre | TEXT | Nombre único de categoría|
---
## 📦 `componente`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|------------------------------------|
| id | UUID | PRIMARY KEY |
| negocio_id | UUID | FK → negocio.id |
| categoria_id | INT | FK → categoria_componente.id |
| nombre | TEXT | Nombre del componente |
| descripcion | TEXT | Descripción general |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física (rack, bandeja) |
| imagen_url | TEXT | URL de imagen |
| fecha_registro | TIMESTAMP | Default: now() |
---
## 🔌 `detalle_cable`
| Columna | Tipo de dato |
|----------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo_cable | TEXT |
| color | TEXT |
| tamaño | DECIMAL(5,2) |
| tipo_conector | TEXT |
---
## 📶 `detalle_switch`
| Columna | Tipo de dato |
|----------------------|----------------|
| componente_id | UUID (PK, FK) |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| administrable | BOOLEAN |
| poe | BOOLEAN |
| cantidad_puertos | INT |
| velocidad_puertos | TEXT |
| tipo_puertos | TEXT |
| ubicacion_en_rack | TEXT |
| direccion_ip | TEXT |
| firmware | TEXT |
---
## 🧱 `detalle_patch_panel`
| Columna | Tipo de dato |
|---------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo_conector | TEXT |
| numero_puertos | INT |
| categoria | TEXT |
| tipo_montaje | TEXT |
| numeracion_frontal | BOOLEAN |
| panel_ciego | BOOLEAN |
---
## 🗄 `detalle_rack`
| Columna | Tipo de dato |
|------------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| altura_u | INT |
| profundidad_cm | INT |
| ancho_cm | INT |
| ventilacion_integrada | BOOLEAN |
| puertas_con_llave | BOOLEAN |
| ruedas | BOOLEAN |
| color | TEXT |
---
## 🧰 `detalle_organizador`
| Columna | Tipo de dato |
|--------------|----------------|
| componente_id| UUID (PK, FK) |
| tipo | TEXT |
| material | TEXT |
| tamaño | TEXT |
| color | TEXT |
---
## ⚡ `detalle_ups`
| Columna | Tipo de dato |
|--------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| voltaje_entrada | TEXT |
| voltaje_salida | TEXT |
| capacidad_va | INT |
| autonomia_minutos | INT |
| cantidad_tomas | INT |
| rackeable | BOOLEAN |
---
## 🔐 `detalle_router_firewall`
| Columna | Tipo de dato |
|--------------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| interfaces | TEXT |
| capacidad_routing_gbps | DECIMAL(5,2) |
| direccion_ip | TEXT |
| firmware | TEXT |
| licencias | TEXT |
---
## 🧿 `detalle_equipo_activo`
| Columna | Tipo de dato |
|-------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| especificaciones | TEXT |
| direccion_ip | TEXT |
| firmware | TEXT |
---
## 🧭 `distribucion`
| Columna | Tipo de dato | Descripción |
|--------------|----------------|--------------------------------------|
| id | UUID | PRIMARY KEY |
| negocio_id | UUID | FK → negocio.id |
| tipo | TEXT | 'MDF' o 'IDF' |
| nombre | TEXT | Nombre de la ubicación lógica |
| descripcion | TEXT | Detalles adicionales (opcional) |
---
## 🔗 `conexion_componente`
| Columna | Tipo de dato | Descripción |
|-----------------------|----------------|------------------------------------------|
| id | UUID | PRIMARY KEY |
| componente_origen_id | UUID | FK → componente.id |
| componente_destino_id | UUID | FK → componente.id |
| descripcion | TEXT | Descripción de la conexión (opcional) |
| activo | BOOLEAN | Si la conexión está activa |
---
## 👁️ `vista_negocios_con_coordenadas`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|--------------------------------------------|
| negocio_id | UUID | ID del negocio |
| nombre_negocio | TEXT | Nombre del negocio |
| latitud | DECIMAL | Latitud del negocio |
| longitud | DECIMAL | Longitud del negocio |
| logo_negocio | TEXT | URL del logo del negocio |
| imagen_negocio | TEXT | URL de la imagen del negocio |
| empresa_id | UUID | ID de la empresa |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | URL del logo de la empresa |
| imagen_empresa | TEXT | URL de la imagen de la empresa |
---
## 📋 `vista_inventario_por_negocio`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|---------------------------------------------|
| componente_id | UUID | ID del componente |
| nombre_componente | TEXT | Nombre del componente |
| categoria | TEXT | Categoría del componente |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física del componente |
| imagen_componente | TEXT | Imagen asociada al componente |
| negocio_id | UUID | ID del negocio |
| nombre_negocio | TEXT | Nombre del negocio |
| logo_negocio | TEXT | Logo del negocio |
| imagen_negocio | TEXT | Imagen del negocio |
| empresa_id | UUID | ID de la empresa |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | Logo de la empresa |
| imagen_empresa | TEXT | Imagen de la empresa |
---
## 🧵 `vista_detalle_cables`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|--------------------------------------------|
| componente_id | UUID | ID del componente |
| nombre | TEXT | Nombre del cable |
| tipo_cable | TEXT | Tipo de cable (UTP, fibra, etc.) |
| color | TEXT | Color del cable |
| tamaño | DECIMAL | Longitud del cable |
| tipo_conector | TEXT | Tipo de conector (RJ45, LC, etc.) |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física |
| imagen_componente | TEXT | Imagen del cable |
| nombre_negocio | TEXT | Nombre del negocio |
| logo_negocio | TEXT | Logo del negocio |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | Logo de la empresa |
---
## 📊 `vista_resumen_componentes_activos`
| Columna | Tipo de dato | Descripción |
|------------------|--------------|----------------------------------------------|
| nombre_empresa | TEXT | Nombre de la empresa |
| nombre_negocio | TEXT | Nombre del negocio |
| categoria | TEXT | Categoría del componente |
| cantidad_activos | INTEGER | Cantidad total de componentes activos |
---
## 🔌 `vista_conexiones_por_negocio`
| Columna | Tipo de dato | Descripción |
|-----------------------|--------------|------------------------------------------|
| id | UUID | ID de la conexión |
| componente_origen_id | UUID | Componente origen |
| componente_destino_id | UUID | Componente destino |
| descripcion | TEXT | Descripción de la conexión |
| activo | BOOLEAN | Si la conexión está activa |
"""

View File

@@ -5,8 +5,8 @@ import 'package:nethive_neo/pages/empresa_negocios/widgets/empresa_selector_side
import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_table.dart'; import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_table.dart';
import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_cards_view.dart'; import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_cards_view.dart';
import 'package:nethive_neo/pages/empresa_negocios/widgets/mobile_empresa_selector.dart'; import 'package:nethive_neo/pages/empresa_negocios/widgets/mobile_empresa_selector.dart';
import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_map_view.dart';
import 'package:nethive_neo/theme/theme.dart'; import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/helpers/globals.dart';
class EmpresaNegociosPage extends StatefulWidget { class EmpresaNegociosPage extends StatefulWidget {
const EmpresaNegociosPage({Key? key}) : super(key: key); const EmpresaNegociosPage({Key? key}) : super(key: key);
@@ -55,7 +55,6 @@ class _EmpresaNegociosPageState extends State<EmpresaNegociosPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isLargeScreen = MediaQuery.of(context).size.width > 1200; final isLargeScreen = MediaQuery.of(context).size.width > 1200;
final isMediumScreen = MediaQuery.of(context).size.width > 800;
return Scaffold( return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground, backgroundColor: AppTheme.of(context).primaryBackground,
@@ -589,85 +588,10 @@ class _EmpresaNegociosPageState extends State<EmpresaNegociosPage>
} }
Widget _buildMapView() { Widget _buildMapView() {
return Container( return Consumer<EmpresasNegociosProvider>(
decoration: BoxDecoration( builder: (context, provider, child) {
gradient: LinearGradient( return NegociosMapView(provider: provider);
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.withOpacity(0.1),
Colors.blue.withOpacity(0.3),
AppTheme.of(context).primaryColor.withOpacity(0.2),
],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppTheme.of(context).primaryColor,
width: 2,
),
boxShadow: [
BoxShadow(
color: AppTheme.of(context).primaryColor.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Center(
child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 1500),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: 0.8 + (0.2 * value),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: AppTheme.of(context).modernGradient,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.map,
size: 80,
color: Colors.white,
),
),
const SizedBox(height: 24),
Text(
'Vista de Mapa',
style: TextStyle(
color: AppTheme.of(context).primaryColor,
fontSize: 28,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(15),
),
child: Text(
'Próximamente se implementará el mapa interactivo\ncon las ubicaciones de todas las sucursales',
style: TextStyle(
color: AppTheme.of(context).primaryColor,
fontSize: 16,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
],
),
);
}, },
),
),
); );
} }

View File

@@ -0,0 +1,685 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:go_router/go_router.dart';
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
import 'package:nethive_neo/theme/theme.dart';
class NegociosMapView extends StatefulWidget {
final EmpresasNegociosProvider provider;
const NegociosMapView({
Key? key,
required this.provider,
}) : super(key: key);
@override
State<NegociosMapView> createState() => _NegociosMapViewState();
}
class _NegociosMapViewState extends State<NegociosMapView>
with TickerProviderStateMixin {
final MapController _mapController = MapController();
late AnimationController _markerAnimationController;
late AnimationController _tooltipAnimationController;
late Animation<double> _markerAnimation;
late Animation<double> _tooltipAnimation;
late Animation<Offset> _tooltipSlideAnimation;
String? _hoveredNegocioId;
Offset? _tooltipPosition;
bool _showTooltip = false;
@override
void initState() {
super.initState();
_markerAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_tooltipAnimationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_markerAnimation = Tween<double>(
begin: 1.0,
end: 1.4,
).animate(CurvedAnimation(
parent: _markerAnimationController,
curve: Curves.easeOutBack,
));
_tooltipAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _tooltipAnimationController,
curve: Curves.easeOutCubic,
));
_tooltipSlideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _tooltipAnimationController,
curve: Curves.easeOutCubic,
));
// Centrar el mapa después de que se construya
WidgetsBinding.instance.addPostFrameCallback((_) {
_centerMapOnNegocios();
});
}
@override
void dispose() {
_markerAnimationController.dispose();
_tooltipAnimationController.dispose();
super.dispose();
}
void _showTooltipForNegocio(String negocioId, Offset position) {
setState(() {
_hoveredNegocioId = negocioId;
_tooltipPosition = Offset(
position.dx + 20, // Offset del cursor para evitar interferencia
position.dy - 80, // Arriba del cursor
);
_showTooltip = true;
});
_markerAnimationController.forward();
_tooltipAnimationController.forward();
}
void _hideTooltip() {
_tooltipAnimationController.reverse().then((_) {
if (mounted) {
setState(() {
_hoveredNegocioId = null;
_showTooltip = false;
_tooltipPosition = null;
});
}
});
_markerAnimationController.reverse();
}
void _centerMapOnNegocios() {
if (widget.provider.negocios.isNotEmpty) {
final bounds = _calculateBounds();
_mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.all(50),
maxZoom: 15,
),
);
}
}
LatLngBounds _calculateBounds() {
if (widget.provider.negocios.isEmpty) {
return LatLngBounds(
const LatLng(-90, -180),
const LatLng(90, 180),
);
}
double minLat = widget.provider.negocios.first.latitud;
double maxLat = widget.provider.negocios.first.latitud;
double minLng = widget.provider.negocios.first.longitud;
double maxLng = widget.provider.negocios.first.longitud;
for (final negocio in widget.provider.negocios) {
minLat = minLat < negocio.latitud ? minLat : negocio.latitud;
maxLat = maxLat > negocio.latitud ? maxLat : negocio.latitud;
minLng = minLng < negocio.longitud ? minLng : negocio.longitud;
maxLng = maxLng > negocio.longitud ? maxLng : negocio.longitud;
}
return LatLngBounds(
LatLng(minLat, minLng),
LatLng(maxLat, maxLng),
);
}
@override
Widget build(BuildContext context) {
if (widget.provider.negocios.isEmpty) {
return _buildEmptyMapState();
}
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
// Listener para detectar movimientos del mouse sobre el mapa
MouseRegion(
onExit: (_) => _hideTooltip(),
child: FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter: const LatLng(19.4326, -99.1332),
initialZoom: 10,
minZoom: 3,
maxZoom: 18,
),
children: [
// Capa de tiles del mapa
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.nethive.app',
),
// Capa de marcadores
MarkerLayer(
markers: _buildMarkers(),
),
],
),
),
// Header del mapa con información
Positioned(
top: 20,
left: 20,
right: 20,
child: _buildMapHeader(),
),
// Controles del mapa
Positioned(
bottom: 20,
right: 20,
child: _buildMapControls(),
),
// Tooltip flotante con información del negocio
if (_showTooltip && _tooltipPosition != null)
Positioned(
left: _tooltipPosition!.dx
.clamp(20.0, MediaQuery.of(context).size.width - 280),
top: _tooltipPosition!.dy
.clamp(20.0, MediaQuery.of(context).size.height - 150),
child: _buildAnimatedTooltip(),
),
],
),
),
);
}
List<Marker> _buildMarkers() {
return widget.provider.negocios.map((negocio) {
final isHovered = _hoveredNegocioId == negocio.id;
return Marker(
point: LatLng(negocio.latitud, negocio.longitud),
width: 50,
height: 50,
child: MouseRegion(
onEnter: (event) {
_showTooltipForNegocio(negocio.id, event.position);
},
onHover: (event) {
// Actualizar posición del tooltip sin recalcular todo
if (_hoveredNegocioId == negocio.id) {
setState(() {
_tooltipPosition = Offset(
event.position.dx + 20,
event.position.dy - 80,
);
});
}
},
child: GestureDetector(
onTap: () {
// Navegar a la infraestructura del negocio
context.go('/infrastructure/${negocio.id}');
},
child: AnimatedBuilder(
animation: _markerAnimation,
builder: (context, child) {
return Transform.scale(
scale: isHovered ? _markerAnimation.value : 1.0,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: isHovered
? AppTheme.of(context).primaryGradient
: AppTheme.of(context).modernGradient,
boxShadow: [
BoxShadow(
color: AppTheme.of(context)
.primaryColor
.withOpacity(0.4),
blurRadius: isHovered ? 15 : 8,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: Colors.white,
width: isHovered ? 3 : 2,
),
),
child: Icon(
Icons.store,
color: Colors.white,
size: isHovered ? 22 : 18,
),
),
);
},
),
),
),
);
}).toList();
}
Widget _buildAnimatedTooltip() {
final negocio = widget.provider.negocios.firstWhere(
(n) => n.id == _hoveredNegocioId,
);
return SlideTransition(
position: _tooltipSlideAnimation,
child: FadeTransition(
opacity: _tooltipAnimation,
child: ScaleTransition(
scale: _tooltipAnimation,
child: Container(
width: 260,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: AppTheme.of(context).modernGradient,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 8),
),
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.store,
color: Colors.white,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
negocio.nombre,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Icon(
Icons.location_on,
color: Colors.white.withOpacity(0.8),
size: 16,
),
const SizedBox(width: 6),
Expanded(
child: Text(
negocio.direccion,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.business,
color: Colors.white.withOpacity(0.8),
size: 16,
),
const SizedBox(width: 6),
Text(
negocio.tipoLocal,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 12),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.touch_app,
color: Colors.white,
size: 14,
),
const SizedBox(width: 6),
Text(
'Clic para ver infraestructura',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
),
),
);
}
Widget _buildMapHeader() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: AppTheme.of(context).primaryGradient,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.map,
color: Colors.white,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mapa de Sucursales',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'${widget.provider.negocios.length} ubicaciones de ${widget.provider.empresaSeleccionada?.nombre ?? ""}',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.location_on,
color: Colors.white,
size: 16,
),
const SizedBox(width: 6),
Text(
'OpenStreetMap',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
);
}
Widget _buildMapControls() {
return Column(
children: [
// Botón de centrar mapa
Container(
decoration: BoxDecoration(
gradient: AppTheme.of(context).primaryGradient,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _centerMapOnNegocios,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.center_focus_strong,
color: Colors.white,
size: 24,
),
),
),
),
),
const SizedBox(height: 12),
// Botón de zoom in
Container(
decoration: BoxDecoration(
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
_mapController.move(
_mapController.camera.center,
_mapController.camera.zoom + 1,
);
},
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.add,
color: AppTheme.of(context).primaryColor,
size: 20,
),
),
),
),
),
const SizedBox(height: 8),
// Botón de zoom out
Container(
decoration: BoxDecoration(
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
_mapController.move(
_mapController.camera.center,
_mapController.camera.zoom - 1,
);
},
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.remove,
color: AppTheme.of(context).primaryColor,
size: 20,
),
),
),
),
),
],
);
}
Widget _buildEmptyMapState() {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.withOpacity(0.1),
Colors.blue.withOpacity(0.3),
AppTheme.of(context).primaryColor.withOpacity(0.2),
],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppTheme.of(context).primaryColor,
width: 2,
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: AppTheme.of(context).modernGradient,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.location_off,
size: 60,
color: Colors.white,
),
),
const SizedBox(height: 20),
Text(
'Sin ubicaciones para mostrar',
style: TextStyle(
color: AppTheme.of(context).primaryColor,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
'Selecciona una empresa con sucursales\npara ver sus ubicaciones en el mapa',
textAlign: TextAlign.center,
style: TextStyle(
color: AppTheme.of(context).secondaryText,
fontSize: 16,
),
),
],
),
),
);
}
bool get isHovered => _hoveredNegocioId != null;
}

File diff suppressed because it is too large Load Diff

View File

@@ -193,6 +193,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dart_jsonwebtoken: dart_jsonwebtoken:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -371,6 +379,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -709,6 +725,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.1" version: "0.3.1"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -741,6 +765,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -781,6 +821,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -893,6 +941,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.0" version: "3.8.0"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
postgrest: postgrest:
dependency: transitive dependency: transitive
description: description:
@@ -909,6 +965,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.0" version: "3.4.0"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1202,6 +1266,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1394,6 +1466,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.5.4" version: "5.5.4"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -40,6 +40,10 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
# Mapa interactivo
flutter_map: ^7.0.2
latlong2: ^0.9.1
# Paquetes adicionales del pubspec.yaml que te proporcionaron # Paquetes adicionales del pubspec.yaml que te proporcionaron
animated_toggle_switch: ^0.8.2 animated_toggle_switch: ^0.8.2
another_stepper: ^1.2.2 another_stepper: ^1.2.2