Without notifyListeners() after isLoading=true, the Consumer would
briefly show stale data (or 'service not found') from a previous
navigation before the API call completes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After converting ServiceView to StatefulWidget, the build() method and
helper callbacks still referenced 'type' as a bare name instead of
widget.type, causing compilation failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When professional accepts 'both' modes, add office/delivery toggle so
the patient can explicitly choose instead of defaulting to delivery.
Reset _bookAsDelivery when a professional is selected; office-only
professionals still force office, delivery-only force delivery.
- Fix 'sin servicio' on ServiceView: getServiceForUser was calling
/users/:professionalId using the professionals-table UUID (not user UUID),
returning 404 and setting service=null. Use the embedded professionals.users
data from findById instead.
- Convert ServiceView to StatefulWidget; fetch in initState to avoid
re-fetching on every parent rebuild.
- Remove client-side FCM notification from _requestService; backend
create() now notifies the professional directly with the server key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Service.fromJson: lat/lng con Decimal de Prisma llegaban como string y
el cast 'as num?' lanzaba TypeError silencioso → lista de servicios vacía.
Mismo patrón que average_score, fix: double.tryParse(?.toString())
- service_status: agregar enumToStringService que mapea el enum de Flutter
al string que espera el DTO del backend ('pending','accepted', etc.).
changeServiceStatus enviaba el índice entero → @IsEnum rechazaba → el
profesional no podía aceptar ni rechazar solicitudes
- maps_service: agregar MapsService.reverseGeocode que usa el Geocoder del
Maps JS SDK ya cargado, sin requerir la Geocoding API habilitada por separado
- dashboard_view: _reverseGeocode usa el JS Geocoder en vez del endpoint HTTP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- day: usar formato ISO 8601 "yyyy-MM-dd" — @IsDateString() rechazaba
el formato de DateTime.toString() que usa espacio en vez de T
- location_preference: enviar string "office"/"delivery" con la clave
correcta — se enviaba 'location' con un entero (enum index)
- Construir el payload directamente en vez de usar Service.toDocument()
que no coincide con CreateServiceDto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- calendar_view: separar carga del profesional (1 vez) de la carga de
servicios (por cada cambio de día), eliminando re-fetches innecesarios
- calendar_view: usar DateTime local en vez de UTC para evitar desfase
de fecha en zonas horarias (Colombia UTC-5)
- calendar_view: excluir explícitamente cancelled/denied en _isOccupied
y refactorizar los 4 tipos de card en un único método _slotCard
- dashboard_view: usar slotDurationMinutes del profesional en range1Hour2
en vez de 2 horas hardcodeadas, respetando la duración real del slot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agrega getters patientLat/patientLng al ProfessionalsProvider
- Calcula distancia Haversine entre la ubicación del paciente y el consultorio
del profesional, mostrándola en azul (ej: "1.3 km" o "800 m") en la card
- Muestra la dirección del consultorio (icono de tienda) en ambos layouts:
horizontal (compact) y vertical (grid)
- Distancia solo visible cuando el paciente tiene coordenadas registradas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Muestra marcadores azules en el mapa para profesionales con coordenadas
reales y modalidad presencial (office/both), filtrados por ciudad del paciente.
Tocar un marcador navega directamente al calendario del profesional.
- Agrega tabla de coordenadas hardcodeadas para 30 ciudades colombianas como
fallback inmediato en _centerOnUserCity, evitando que el mapa cargue en
Medellín cuando la Geocoding API no está habilitada en el proyecto GCP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_initMaps ahora geocodifica la ciudad del perfil del usuario (_centerOnUserCity)
antes de mostrar el mapa. Así el mapa arranca en Bucaramanga si el usuario
es de Bucaramanga, y el autocomplete ya tiene coordenadas correctas desde
el primer keystroke, sin esperar GPS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Default _mapCenter era Medellín (nuevo fallback neutro) pero se usaba como
bias de coordenadas en el autocomplete antes de que GPS/geocoding terminara.
Ahora:
- _mapPositioned=false hasta que GPS o _centerOnUserCity actualice el centro
- Autocomplete omite parámetro location hasta que _mapPositioned=true
- Mientras tanto, la ciudad del usuario se agrega al query de texto para biasear
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _requestService: omite validación de dirección si locationPreferences=office
- Usa la dirección y coordenadas del consultorio del profesional en ese caso
- Botón y warnings de ciudad deshabilitados solo en modo domicilio/ambos
- Tarjeta inferior muestra dirección del consultorio cuando es office-only
- location_preference del servicio refleja correctamente office vs delivery
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prisma serializa Decimal como string JSON ("0"), no como número.
'(doc['average_score'] as num?)' lanzaba TypeError en Dart cuando el valor
era "0" (string), el catch lo tragaba y professionals quedaba vacío.
Cambiado a double.tryParse que maneja null, string y número.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _mapCenter default cambiado de Bogotá a coordenadas más neutras
- Cuando GPS no está disponible/denegado, _centerOnUserCity() geocodifica
la ciudad del perfil del usuario y centra el mapa ahí
- El autocomplete usa _mapCenter como bias → resultados ahora corresponden
a la ciudad del usuario, no a Bogotá hardcodeado
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clear button (✕) now resets _cityMismatch and _detectedCity so banner and CTA unblock
- _citiesMatch: replace bidirectional contains with equality + word-prefix check to prevent 'Cali' matching 'Calima'
- ProfessionalsProvider: track _currentUserId to reset state on user change (cross-user leak)
- ProfessionalsProvider: store _lastSearch so setLocationContext preserves active search term
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Professionals filtered by user city + proximity (lat/lng) to selected address
- ProfessionalsProvider: setLocationContext(city, lat, lng) updates filter and reloads
- Dashboard: detect city mismatch after reverse geocode, block booking if wrong city
- Dashboard: autocomplete biased toward user's city
- Dashboard: setLocationContext called before navigating to professionals list
- Dashboard: keyboard no longer shifts map layout on tablet (MediaQuery viewInsets=0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getProfessionals() now accepts optional search param sent to ?search=
- updateCity only triggers the initial load (city no longer used as filter)
- Search field debounces 450ms before firing API call
- TextEditingController added so clear button also resets the field
- Client-side filtering removed (API now handles it and returns top 7)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
updateCity skipped getProfessionals() when both _city and the incoming
city were null (null != null = false). Added _initialized flag so the
first call always triggers the load regardless of city value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove fp.clear() from profile initState: if profesional is already
loaded (e.g. navigating from schedule page) the form renders instantly
without a spinner; if null the Consumer already shows the spinner.
- Add .catchError() so the spinner always resolves even on API failure.
- Remove redundant GET /professionals/me reload inside updateProfesionalProfileInfo:
data is already correct from copyProfesionalWith, so the save button
no longer waits for an extra round-trip after the PATCH.
- Wrap save button in try/finally so _saving always resets to false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /services/professional returns all statuses including cancelled.
After unblocking (self_booked → cancelled), _isOccupied matched the
cancelled service because it only excluded selfBooked.
Exclude cancelled and denied from _isOccupied so unblocked slots
correctly revert to Disponible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Service.fromJson stored day as the raw Prisma JSON string
"2024-01-15T00:00:00.000Z", but _isSelfBooked/_isOccupied/_isBlocked
compared it against "2024-01-15" (toIso8601String().split('T').first),
so the match always failed and every slot appeared as Disponible.
Normalize day to the date-only portion in fromJson so all comparisons
work correctly across ProfessionalCalendarView and CalendarView.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
isProModeActive was in-memory only — a browser refresh reset it to
false even when the URL was a professional route.
Fix: read/write 'isProModeActive' from SharedPreferences in
ProfessionalProvider (constructor, toggleProMode, setIsProModeActive,
logout). Also clear the flag in AuthProvider.isAuthenticated() when the
user's proState is not active, so a stale value can't trap a non-pro user
in professional mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CalendarView now treats self_booked services as unavailable:
- _isBlocked() filters slots with selfBooked status from the range list
- _isHora1Ocupada() excludes selfBooked so they don't show as Ocupado
- Blocked slots render as grey "No disponible" with block icon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ServicesProvider: add blockSlot() calling POST /services/block and
unblockSlot() calling PATCH /services/:id/status with cancelled
- ProfessionalCalendarView:
- Fix _services always-null bug: getServicesForProfessional() returns
void; now reads from sp.services after it resolves
- Differentiate self_booked (blocked) vs regular occupied slots
- Show amber 'Bloqueado' state with lock icon for self_booked services
- Show lock button on available slots → confirm dialog → blockSlot()
- Show unlock button on blocked slots → confirm dialog → unblockSlot()
- Reload calendar after block/unblock so UI reflects new state
- Header stats now show separate counts: ocupadas / bloqueadas / libres
- _ActionButton widget for reusable tap-target icon buttons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical fixes for the scheduling flow:
- ScheduleEntity: add toScheduleDto() with snake_case keys, day_of_week,
and HH:MM zero-padded format required by the backend DTO @Matches validator
- Schedules: add toSchedulesArray() converting the day-keyed object to the
array format expected by PATCH /professionals/me/schedules
- ProfessionalFormProvider: updateProfesionalProfileScheduleInfo now calls
the correct endpoint (/professionals/me/schedules) instead of /professionals/me
which was stripping the schedules field via ValidationPipe whitelist
- Service.formatTimeOfDay: zero-pad hours and minutes so POST /services passes
the @Matches(/^\d{2}:\d{2}$/) DTO validation
- ProfessionalProvider: add getProfessionalById() calling /professionals/:id
(public endpoint) to load any professional's data, not the viewer's own
- CalendarServicesProvider: add getPublicServicesForProfessional() calling
/services/public-calendar/:id so the user calendar shows the target
professional's booked slots, not the viewer's own services
- CalendarView: use getProfessionalById + getPublicServicesForProfessional
so the calendar correctly reflects the selected professional's schedule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a timeout, a hanging backend request left the schedule page showing
an infinite spinner forever. Timeout ensures any hung request fails fast and
the spinner resolves. The catchError fallback in schedule_view handles the
edge case where getProfessional rejects unexpectedly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When toggling off jornada continua, range1Hour2 defaults to 12:00 and
range2Hour1 defaults to 14:00 if not already set. Existing values are
preserved so the user can still edit them freely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prisma serializes Decimal fields (rate, latitude, longitude) as JSON
strings, causing 'as num?' casts to throw in fromDocument(). The catch
block in getProfessional() then returned an empty Profesional, so saving
overwrote all existing data with empty strings.
Also added try-catch in all patch API calls to surface errors to user
instead of silently failing with a stuck loading button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a day is toggled on and has no hours set yet, automatically
apply continuous-day defaults (08:00 - 18:00) so the user can
save immediately without having to pick times manually.
Also clear fp.profesional in initState to ensure fresh data loads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use GET /professionals/me (JWT-authenticated) instead of /professionals/{uid}
to avoid silent failures when the user_id lookup fails
- After save, re-fetch from server so fp.profesional reflects confirmed server state
on the next page navigation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix typo aditional_address -> additional_address in toDocument() and fromDocument()
- Clear ProfessionalFormProvider before fetch in initState() so TextFormField initialValue always applies to fresh data
- Add clear() to ProfessionalFormProvider; call it on logout to avoid leaking data between users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After logout or on app start with no/invalid token, the browser URL
might be #/dashboard or #/auth/login. Using addPostFrameCallback to
navigate to phoneLoginRoute once the navigator is ready ensures the
user always lands on the phone login screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter's PWA service worker caches the entire app shell aggressively,
causing users to see old versions even after deploys. Disabling it lets
nginx cache headers take effect. Also unregisters any existing service
worker already installed on user devices.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- professional_calendar_view: styled TableCalendar with app colors,
day header with date block + stats pills (occupied/available),
time slot cards with accent strip + status icon, clean empty state
- services_requests_view: card list with status-colored accent strips,
avatar initials fallback, date/time row, colored status badges with
icons for all ServiceStatus values, proper empty state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- professional_profile_view: gradient header card with avatar overlay,
sectioned cards (service modality, address, rate, payment chips, schedule),
animated payment method chips, full-width save button with loading state
- sidebar: teal-dark background in pro mode vs navy in user mode,
animated mode indicator pill, muted toggle button in pro mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stop swallowing DELETE errors silently; show snackbar instead
- After successful reset, reload professional from server and notify providers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add missing api_service.dart import (without it the file fails to compile)
- Initialize _loadingSettings = false so button renders before async settings load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Usuario model: proRejectedAt parsed from professionals.updated_at
- _StatusCard: shows countdown or retry button based on rejection_wait_days setting
- Retry clears the rejected record via DELETE /professionals/me
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows document type locked to 'Cédula de ciudadanía' and adds required
number input field above the photo upload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
context.read doesn't subscribe to changes, so the form stayed visible
after submitForReview even though AuthProvider was updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Profesional.empty() factory for users with no professional record
- initState: catch error from getProfessional and show empty form instead of infinite spinner
- Backend upsert: create professional record if none exists (no longer throws 404)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Display: only prepend + if number doesn't already start with +
URL: strip + and spaces so WhatsApp API receives digits only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Profile view: gradient hero banner with overlapping avatar, status badges (phone verified, email, pro state), gender field, city from flat API endpoint, manual validation
- nginx: JS files use no-cache/must-revalidate instead of 1y immutable to prevent stale deploys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Solo se puede seleccionar de la lista de ciudades registradas
- GPS detecta ciudad → si está en la lista: preselecciona y muestra banner verde
- GPS detecta ciudad → si NO está en lista: banner amarillo con mensaje de no disponibilidad
- No hay campo de texto libre, solo selección de lista
- Botón muestra "Continuar con [Ciudad]" solo cuando hay selección
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Carga ciudades de GET /locations/cities/all
- Detecta ciudad via GPS → Nominatim
- Muestra ciudad GPS detectada en banner verde
- Preselecciona automáticamente en la lista si coincide
- Buscador en tiempo real con filtro
- Ciudad GPS sin seleccionar muestra badge "GPS"
- Botón deshabilitado hasta seleccionar una ciudad
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Eliminado Flutter Form widget, validación manual por campo
- submitForReview() en provider sin depender de formKey
- Errores inline visibles (cédula obligatoria, profesión obligatoria)
- Loading spinner en el botón mientras envía
- try/catch con snackbar de error si falla la API
- Orden de campos: foto cédula, profesión, RETHUS (opcional), especializaciones (opcional)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Grid responsivo (1-4 columnas según ancho)
- Tarjeta con header gradiente, avatar centrado superpuesto y badge de score
- Modo compacto (lista horizontal) en pantallas pequeñas
- Buscador en tiempo real por nombre, profesión o ciudad
- Chips de métodos de pago (Datáfono, Nequi, Transferencia)
- Dark mode completo, avatar con inicial como fallback
- Estado vacío con ícono y mensaje contextual
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- logo.dart: path correcto assets/prosapp-logo.png + errorBuilder
- services_view.dart: rediseño con tarjetas, dark mode, avatar con inicial, badges de estado
- services_history_view.dart: mismo rediseño + badges completado/cancelado/rechazado
- request_professional_view.dart: secciones con cards, botones de upload con estado visual,
estados pending/rejected mejorados, dark mode completo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Navbar: reemplaza hoverColor deprecated por IconButton.styleFrom(overlayColor)
- Support: rediseño completo con tarjetas, secciones y manejo correcto de URLs vacías
- Políticas/Términos: carga desde GET /settings/policies y muestra en dialog in-app
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- professional_form_provider: agregar rethusValidated al constructor Profesional
- professional_provider: agregar rethusCode y rethusValidated al fallback del constructor
- profile_view: quitar await de refreshUser() que retorna void
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PhoneLoginView y LoginView: form envuelto en Card con sombra (blanca en claro, oscura en noche)
- Títulos: Color(0xFF0F172A) en claro, blanco en noche - siempre visible
- Subtítulos: Color(0xFF64748B) en claro (contraste 4.5:1+) vs Colors.grey anterior
- Inputs: fillColor, textColor y borderColor explícitos por tema
- AuthLayout desktop: fondo usa scaffoldBackgroundColor del tema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AuthLayout desktop: fondo usa scaffoldBackgroundColor en vez de Colors.white fijo
- PhoneLoginView y LoginView: todos los textos, inputs y bordes usan colores tema-aware
(titulos, subtítulos, fillColor, borderColor se adaptan a modo día/noche)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Navbar: hoverColor tenue por tema (azul/blanco) en vez del gris por defecto de Flutter web
- SettingsProvider: lazy:false + timeout 10s para evitar spinner gris permanente en soporte
- SupportView: muestra contenido con defaults inmediatamente sin bloquear en isLoading
- Modelo Profesional: campos rethus_code y rethus_validated
- ProfessionalFormProvider: soporte para rethus_code en copyProfesionalWith
- RequestProfessionalView: campo RETHUS opcional en formulario + fix color dropdown profesión
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProfileView vuelve a StatefulWidget para inicializar ProfileFormProvider
- Quitar import custom_inputs.dart sin usar
- DropdownButtonFormField con ValueKey para forzar rebuild cuando cargan ciudades
- Normalizacion de acentos en ciudad (cucuta == cucuta con/sin tilde)
- _EmailCard siempre muestra el formulario (no requiere expandir)
- Eliminado campo email read-only del formulario principal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Setting: lee support_title/description/days/hours/email/phone
- SettingsProvider: fallback a Setting vacio si no hay datos en DB
- SupportView: guard para settings null
- Avatar: ui-avatars.com como imagen por defecto (con nombre)
- Perfil: seccion correo como ExpansionTile (acordeon)
paso 1: email+contrasena+confirmar → Enviar codigo
paso 2: ingresar codigo de 6 digitos → Verificar (verde)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Detectar usuario nuevo (nombre == telefono) y redirigir
a pantalla setup-name antes del dashboard
- SetupNameView: bienvenida con campo de nombre obligatorio
- AuthProvider.updateName() para guardar via PATCH /users/me
- NavbarAvatar: iniciales con color por inicial del nombre
en lugar de no-image.jpg cuando no hay foto
- Perfil: mismo avatar de iniciales en la pantalla de perfil
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WhiteCard usa Theme.cardColor en lugar de Colors.white fijo
- Dropdown ciudad: normaliza valor guardado vs lista (evita mismatch)
- Muestra spinner mientras cargan las ciudades
- Texto de items adapta color segun el tema (isDark)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Navbar: convertir a StatefulWidget, un solo nivel de build,
usa context.watch en lugar de Consumer anidados
- Iconos sol/luna y campana siempre visibles con color explicito
- Dashboard: barra busqueda, sugerencias y tarjeta inferior
ahora respetan isDark (cardBg, textColor, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Quitar AnimatedSwitcher/RotationTransition que ocultaba los iconos
- Icono sol/luna y campana con InkWell simple y colores explicitos
- Fondo navbar animado segun tema (isDark)
- DashboardLayout usa scaffoldBackgroundColor del tema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Icono sol/luna con animacion de rotacion en navbar
- Navbar cambia color segun el tema (animado)
- Quitar toggle del sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CitiesProvider.isCityAvailable() y findMatchingCity() con comparacion flexible
- Al mover el mapa extrae la ciudad del geocodificado inverso
- Si la ciudad no esta en la lista muestra aviso amarillo y bloquea solicitud
- Boton de solicitar deshabilitado cuando ciudad no esta disponible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ThemeProvider con persistencia en SharedPreferences
- Toggle animado en sidebar (sol/luna con switch visual)
- Temas light y dark completos en MaterialApp
- Campana en navbar con badge rojo y panel inferior de notificaciones
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Quitar replaceAll(' ', '_') que rompia la query
- Pasar coordenadas actuales del mapa como location bias
- Soportar formatted_address y description en respuesta
- Manejar results o predictions como clave raiz
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- hide ServiceStatus de geolocator para evitar conflicto con modelo propio
- usar _selectedHour! en getFormatTime (ya esta guardado por null-check previo)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mapa ocupa toda la pantalla, pin central fijo, barra de busqueda
flotante con autocomplete, tarjeta inferior para profesional/fecha.
Al mover el mapa hace geocodificacion inversa y actualiza la direccion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MapsService: obtiene la key via API e inyecta el script Maps JS en runtime
- LocationPickerDialog: muestra error descriptivo si la key no esta configurada
- index.html: eliminado script estatico de Maps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LocationPickerDialog: detecta ubicación del dispositivo, pin draggable,
reverse geocoding para obtener dirección y ciudad
- Dashboard: campo de dirección abre el mapa al tocar
- index.html: carga Maps JS API (key pendiente de configurar)
- pubspec: google_maps_flutter + geolocator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Logo: network image in white card + tagline
- Sidebar: deep navy gradient (#0D1B3E) with blue accent active states
- MenuItem: left accent bar indicator, cleaner hover, better contrast
- Mode toggle: gradient button matching brand colors
- TextSeparator: uppercase label with divider line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main.dart builder already wraps unauthenticated routes in AuthLayout.
Wrapping again in handlers caused double login screen on all viewports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add full-screen gradient splash (blue #42A4EF → #1565C0) with logo
and spinner shown while Flutter engine loads
- Fade out splash on flutter-first-frame event (smooth 300ms transition)
- Remove unused Firebase and reCAPTCHA scripts that slowed initial load
- Set body background-color to match splash so no white flash at any point
- Update title/description to ProsApp branding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Put logo inside white container on gradient so colors are correct
- Remove BlendMode.srcATop which was making logo invisible on gradient
- Simplify auth_layout mobile/desktop branches
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Service.fromJson: string status/location enums, ISO8601 time parsing, additional_address field name
- _loadServices: unwrap {data:[...]} paginated response; extract embedded user from response instead of extra API calls
- calendar_services_provider: same response unwrap; fallback user when not embedded
- Add ProState import where needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace FaIcon(FontAwesomeIcons.google) with plain Text widget.
Remove font_awesome_flutter from pubspec — package extends final
class IconData which is forbidden in Dart 3.12/Flutter 3.44.
Fix currentUser.value → currentUser (was Rx<UserModel?>, now UserModel?).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update SDK constraint to >=3.0.0 <4.0.0 (Flutter stable uses Dart 3.x)
- Remove flutter_email_sender (no web support) — replace with mailto: via url_launcher
- support.dart: unified _sendEmail works on both web and mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>