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>
- 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>
- 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>