Files
prosappweb/lib/providers/professional_form_provider.dart
T
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

145 lines
5.2 KiB
Dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:prosapp_web_app/models/location_preferences.dart';
import 'package:prosapp_web_app/models/payment_method_entity.dart';
import 'package:prosapp_web_app/models/profesional.dart';
import 'package:prosapp_web_app/models/schedules.dart';
import 'package:prosapp_web_app/services/api_service.dart';
import 'package:prosapp_web_app/services/notifications_service.dart';
class ProfessionalFormProvider with ChangeNotifier {
Profesional? profesional;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
GlobalKey<FormState> profileFormKey = GlobalKey<FormState>();
final _api = ApiService.instance;
copyProfesionalWith({
String? id,
String? identification,
String? rethusCode,
String? address,
String? aditionalAddress,
String? profession,
bool ratePreferences = false,
String? rate,
LocationPreferences? locationPreferences,
String? bannerPicture,
String? identificationPicture,
String? certificatePicture,
double? latitude,
double? longitude,
List<String>? specializations,
List<String>? specializationsPictures,
Schedules? schedules,
PaymentMethodEntity? paymentMethods,
}) {
profesional = Profesional(
id: id ?? profesional!.id,
identification: identification ?? profesional!.identification,
rethusCode: rethusCode ?? profesional!.rethusCode,
rethusValidated: profesional!.rethusValidated,
address: address ?? profesional!.address,
aditionalAddress: aditionalAddress ?? profesional!.aditionalAddress,
profession: profession ?? profesional!.profession,
ratePreferences: ratePreferences,
rate: rate ?? profesional!.rate,
locationPreferences: locationPreferences ?? profesional!.locationPreferences,
bannerPicture: bannerPicture ?? profesional!.bannerPicture,
identificationPicture: identificationPicture ?? profesional!.identificationPicture,
certificatePicture: certificatePicture ?? profesional!.certificatePicture,
latitude: latitude ?? profesional!.latitude,
longitude: longitude ?? profesional!.longitude,
specializations: specializations ?? profesional!.specializations,
specializationsPictures: specializationsPictures ?? profesional!.specializationsPictures,
schedules: schedules ?? profesional!.schedules,
paymentMethods: paymentMethods ?? profesional!.paymentMethods,
);
notifyListeners();
}
bool _validForm() => formKey.currentState!.validate();
bool _validProfileForm() => profileFormKey.currentState!.validate();
void clear() {
profesional = null;
notifyListeners();
}
setProfesional(Profesional p) {
profesional = p;
notifyListeners();
}
Future<bool> updateProfesionalInfo(String userId) async {
if (!_validForm()) return false;
try {
await _api.patch('/professionals/me', profesional!.toDocument());
} catch (e) {
NotificationsService.showSnackbar('Error al guardar: $e');
return false;
}
NotificationsService.showSnackbar('Información actualizada');
return true;
}
Future<void> submitForReview() async {
await _api.patch('/professionals/me', profesional!.toDocument());
NotificationsService.showSnackbar('Solicitud enviada a revisión');
}
Future<bool> updateProfesionalProfileInfo(String userId) async {
if (!_validProfileForm()) return false;
try {
await _api.patch('/professionals/me', profesional!.toDocument());
} catch (e) {
NotificationsService.showSnackbar('Error al guardar: $e');
return false;
}
try {
final data = await _api.get('/professionals/me');
profesional = Profesional.fromDocument(data as Map<String, dynamic>);
notifyListeners();
} catch (_) {}
NotificationsService.showSnackbar('Información actualizada');
return true;
}
Future<bool> updateProfesionalProfileScheduleInfo(String userId) async {
try {
await _api.patch('/professionals/me/schedules', {
'schedules': profesional!.schedules.toSchedulesArray(),
});
} catch (e) {
NotificationsService.showSnackbar('Error al guardar: $e');
return false;
}
NotificationsService.showSnackbar('Horario actualizado');
return true;
}
Future<Profesional> uploadPdfIdentification(Uint8List fileBytes, String userId) async {
final url = await _api.upload(fileBytes, '${userId}_cedula.pdf');
if (url != null) copyProfesionalWith(identificationPicture: url);
notifyListeners();
return profesional!;
}
Future<Profesional> uploadPdfCertificate(Uint8List fileBytes, String userId) async {
final url = await _api.upload(fileBytes, '${userId}_certificado.pdf');
if (url != null) copyProfesionalWith(certificatePicture: url);
notifyListeners();
return profesional!;
}
Future<Profesional> uploadPdfSpecializations(List<Uint8List> filesBytes, String userId) async {
List<String> urls = [];
for (int i = 0; i < filesBytes.length; i++) {
final url = await _api.upload(filesBytes[i], '${userId}_especializacion_$i.pdf');
if (url != null) urls.add(url);
}
copyProfesionalWith(specializationsPictures: urls);
notifyListeners();
return profesional!;
}
}