Commit Graph
340 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 aa7b890ce4 fix: notify immediately when isLoading=true in service fetch methods
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>
2026-07-22 10:17:04 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ea442aafa7 fix: use widget.type in _ServiceViewState (build error)
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>
2026-07-22 09:41:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 646e44d126 fix: location preference bug and service detail 404
- 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>
2026-07-22 09:33:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 735d8c2ba0 fix: corregir flujo completo de servicio y geocodificación
- 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>
2026-07-22 09:15:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8296b5a3ea fix: corregir payload de solicitud de servicio al backend
- 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>
2026-07-22 08:54:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 462d47d182 fix: mostrar dirección del consultorio en InfoWindow del marcador del mapa
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 08:51:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6c248e1f98 fix: corregir flujo de agendamiento del paciente
- 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>
2026-07-22 08:38:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 387b647abc feat: mostrar dirección y distancia en cards de profesionales
- 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>
2026-07-22 08:32:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5fdc87dad7 feat: marcadores de consultorios en mapa + fix ciudad inicial
- 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>
2026-07-22 08:09:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 66f861601f feat: inicializar mapa en ciudad del usuario antes de GPS
_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>
2026-07-21 21:06:30 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 cfd1c23fe3 fix: autocomplete muestra resultados de Medellín para usuario en Bucaramanga
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>
2026-07-21 21:06:11 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 495502cfc1 fix: no exigir dirección cuando profesional solo atiende en consultorio
- _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>
2026-07-21 21:04:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c0fce73d41 fix: average_score Decimal string cast crash silently empties professionals list
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>
2026-07-21 20:57:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 953196df1a fix: autocomplete muestra Bogotá para usuario en Bucaramanga
- _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>
2026-07-21 20:48:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 54f9cc272e fix: 4 audit findings in professionals location flow
- 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>
2026-07-21 20:47:14 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f26b3d3c03 feat: dashboard city validation + professionals by location
- 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>
2026-07-21 20:36:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e0982995e6 feat: server-side search with debounce for professionals list
- 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>
2026-07-12 20:18:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9da159f287 Fix professionals list never loading when user has no city
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>
2026-07-12 20:14:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4698454cfc Fix stuck loading after save on profile and schedule views
- 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>
2026-07-12 19:44:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fd7b57fd35 fix unblocked slot showing as Ocupado instead of Disponible
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>
2026-07-12 19:26:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 920ae7144d configurable slot duration: UI picker + genRanges stepMinutes
- Fix TimeOfDayExtension.add() to handle minute overflow via total-minutes math
- genRanges() now takes stepMinutes param (default 30, clamp 5-480)
- Profesional model: slotDurationMinutes field (parsed from slot_duration_minutes)
- ProfessionalFormProvider: saves slot duration alongside schedules on Guardar
- schedule_view: _SlotDurationPicker chip selector (15/20/30/45/60/90/120 min)
- ProfessionalCalendarView + CalendarView: pass slotDurationMinutes to genRanges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:25:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 46acad0b55 fix blocked/occupied slot detection always returning false
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>
2026-07-12 19:14:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 577c4737ae persist professional mode across page refreshes
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>
2026-07-12 19:07:20 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1870159dea hide professional-blocked slots from user-facing calendar
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>
2026-07-12 18:56:17 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 52845c59fd feat(calendar): add block/unblock slots for professionals
- 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>
2026-07-12 18:26:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 498481cab0 fix(scheduling): fix schedule save endpoint, time format, and calendar load
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>
2026-07-12 17:52:02 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 360f88777b fix: add 30s timeout to API calls and catchError fallback on schedule load
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>
2026-07-12 17:42:43 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 dbeda22e19 feat: auto-fill 12pm-2pm break when disabling continuous day on schedule
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>
2026-07-06 22:16:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 706b78b737 fix: safe parsing of Prisma Decimal fields to prevent data loss on save
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>
2026-07-06 22:00:29 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7451b7c57b feat: apply default hours (08:00-18:00) when enabling a schedule day
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>
2026-07-02 23:08:23 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ee678b26ff fix: professional profile fetch uses /me endpoint and re-fetches after save
- 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>
2026-07-02 23:05:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b31ff13657 fix: professional profile not loading or saving correctly
- 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>
2026-07-02 21:44:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 732da2e475 fix(auth): always redirect to phone login when unauthenticated
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>
2026-07-02 15:05:24 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e56ec8619e fix(onboarding): redirect to city setup after name step
Was going directly to dashboard, skipping the city selection screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:44:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2d5ff1581b Fix dark/light theme support in redesigned views
Replace hardcoded colors with Theme.of(context) values via BuildContext
extension (_Th): bg, card, onSurface, muted, subtle, divider, shadow,
inputFill, chipBg. Affects: professional_profile_view, professional_
calendar_view, services_requests_view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:45:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 142e3ffafd Redesign calendar and requests views
- 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>
2026-06-29 11:41:34 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 024d2ec5f5 Update mode toggle button: swap_horiz icon + shorter label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:37:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ef8ccb9aee Redesign professional profile UI + sidebar mode indicator
- 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>
2026-06-29 11:34:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9f2612ec70 Add diploma/certificate upload to professional request form (required field)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 10:50:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3e59a1cb4f Fix retry: show error on failure, reload professional data after reset
- 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>
2026-06-29 10:00:43 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 dba8eaa709 Fix retry button not showing: add missing ApiService import and show button immediately
- 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>
2026-06-29 09:49:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 391c32f765 Rejection retry flow with configurable wait days
- 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>
2026-06-28 18:03:20 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9634aa13ff Add cedula number field to professional request form
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>
2026-06-28 17:49:34 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 28511491af Fix pro state not updating after submit: use context.watch for AuthProvider
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>
2026-06-28 17:27:12 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 eb63a3e415 Fix request professional flow for new users
- 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>
2026-06-28 17:20:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f1dc2cb75d Fix double + in WhatsApp support number
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>
2026-06-28 17:18:39 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ad557b8318 Improve profile UI/UX and fix nginx JS cache
- 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>
2026-06-28 17:08:53 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4a42f4a9e1 fix: setup_city solo permite ciudades del backend, mensaje si no disponible
- 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>
2026-06-28 11:41:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 877324517e feat: setup_city_view con lista de ciudades del backend + preselección por GPS
- 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>
2026-06-28 11:38:44 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0711d30f07 feat: 6 mejoras simultáneas en prosappweb
- web/index.html: lang=es (evita traductor) + cache busting con timestamp en flutter_bootstrap.js
- profesional.dart: rate → número, location_preferences → string para coincidir con backend DTO
- location_preferences.dart: funciones locationPrefsToString/locationPrefsFromValue
- auth_provider.dart: _navigateAfterAuth redirige a setup-city si user.city vacío, método updateCity y linkEmailWithOtp
- setup_city_view.dart: nueva vista con GPS + Nominatim para detectar ciudad, campo editable, omitir
- email_view.dart: rediseño completo con flujo OTP (paso 1: email+contraseña → paso 2: código recibido en correo)
- router + dashboard_handlers: ruta /dashboard/setup-city

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 11:34:51 -05:00