fix: port 7 web features and repair the endless-loading screens

Root cause behind most "stuck loading" reports: the backend changed shape
(schedules became an array, location_preferences a string) while the mobile
parser still hard-cast to Map/int. The TypeError was swallowed by a silent
catch that returned null, and screens only handled the success state, so a
parse failure rendered as a permanent spinner. Same class of bug appeared
across service lists via non-null map lookups and a total absence of
request timeouts.

Ported from prosappweb:
- in-app suggestions (POST /suggestions)
- policies/terms from GET /settings/policies
- configurable appointment length (slot_duration_minutes)
- block/unblock calendar slots (POST /services/block)
- GPS city detection on the profile (Nominatim)
- server-side professional search with haversine distance
- retry cooldown after a rejected professional application

Reliability:
- parse schedules array (day_of_week 0=Mon) and string location_preferences
- read times as wall clock, so 08:00 stays 08:00 across timezones
- carry minutes into hours in TimeOfDay.add; a minute-based step used to
  loop forever and freeze the calendar (covered by test/time_slots_test.dart)
- semver update check instead of string equality, which blocked every build
  that did not exactly match the configured version
- request timeouts across all repositories
- surface HTTP >= 400 instead of reporting failed writes as success
- error states with retry instead of an indefinite shimmer

Includes pre-existing uncommitted work from the UI redesign.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-24 16:45:42 -05:00
co-authored by Claude Opus 5
parent 06a89df690
commit 8631e6f729
86 changed files with 5470 additions and 5291 deletions
@@ -19,6 +19,7 @@ class ProfessionalEntity extends Equatable {
final List<String> specializationsPictures;
final Schedules schedules;
final PaymentMethodEntity paymentMethods;
final int slotDurationMinutes;
const ProfessionalEntity({
required this.id,
@@ -38,6 +39,7 @@ class ProfessionalEntity extends Equatable {
required this.specializationsPictures,
required this.schedules,
required this.paymentMethods,
this.slotDurationMinutes = 120,
});
static ProfessionalEntity fromDocument(Map<String, dynamic> doc) {
@@ -60,6 +62,8 @@ class ProfessionalEntity extends Equatable {
List<String>.from(doc['specializations_pictures']),
schedules: Schedules.fromDocument(doc['schedules']),
paymentMethods: PaymentMethodEntity.fromDocument(doc['payment_methods']),
slotDurationMinutes:
(doc['slot_duration_minutes'] as num?)?.toInt() ?? 120,
);
}
@@ -81,6 +85,7 @@ class ProfessionalEntity extends Equatable {
List<String>? specializationsPictures,
Schedules? schedules,
PaymentMethodEntity? paymentMethods,
int? slotDurationMinutes,
}) {
return ProfessionalEntity(
id: id ?? this.id,
@@ -102,6 +107,7 @@ class ProfessionalEntity extends Equatable {
specializationsPictures ?? this.specializationsPictures,
schedules: schedules ?? this.schedules,
paymentMethods: paymentMethods ?? this.paymentMethods,
slotDurationMinutes: slotDurationMinutes ?? this.slotDurationMinutes,
);
}
@@ -124,6 +130,7 @@ class ProfessionalEntity extends Equatable {
'specializations_pictures': specializationsPictures,
'schedules': schedules.toJson(),
'payment_methods': paymentMethods.toDocument(),
'slot_duration_minutes': slotDurationMinutes,
};
}
@@ -145,7 +152,8 @@ class ProfessionalEntity extends Equatable {
specializations,
specializationsPictures,
schedules,
paymentMethods
paymentMethods,
slotDurationMinutes,
];
@override