Agrego frontend app
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
enum DiaEstado { cerrado, normal, horarioDiferente }
|
||||
|
||||
DiaEstado _parseEstado(String? raw) {
|
||||
switch (raw) {
|
||||
case 'cerrado':
|
||||
return DiaEstado.cerrado;
|
||||
case 'horario_diferente':
|
||||
return DiaEstado.horarioDiferente;
|
||||
default:
|
||||
return DiaEstado.normal;
|
||||
}
|
||||
}
|
||||
|
||||
class TurnoActividad {
|
||||
final int id;
|
||||
final String nombre;
|
||||
final bool libre;
|
||||
|
||||
const TurnoActividad({
|
||||
required this.id,
|
||||
required this.nombre,
|
||||
required this.libre,
|
||||
});
|
||||
|
||||
factory TurnoActividad.fromMap(Map<String, dynamic> map) {
|
||||
return TurnoActividad(
|
||||
id: (map['id'] as num?)?.toInt() ?? 0,
|
||||
nombre: map['nombre'] as String? ?? '',
|
||||
libre: map['libre'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Turno {
|
||||
final String id;
|
||||
final String horaInicio;
|
||||
final String horaFin;
|
||||
final int capacidadMaxima;
|
||||
final int ocupacion;
|
||||
final TurnoActividad actividad;
|
||||
|
||||
const Turno({
|
||||
required this.id,
|
||||
required this.horaInicio,
|
||||
required this.horaFin,
|
||||
required this.capacidadMaxima,
|
||||
required this.ocupacion,
|
||||
required this.actividad,
|
||||
});
|
||||
|
||||
factory Turno.fromMap(Map<String, dynamic> map) {
|
||||
return Turno(
|
||||
id: map['id'] as String? ?? '',
|
||||
horaInicio: map['hora_inicio'] as String? ?? '',
|
||||
horaFin: map['hora_fin'] as String? ?? '',
|
||||
capacidadMaxima: (map['capacidad_maxima'] as num?)?.toInt() ?? 0,
|
||||
ocupacion: (map['ocupacion'] as num?)?.toInt() ?? 0,
|
||||
actividad: TurnoActividad.fromMap(
|
||||
(map['actividad'] as Map?)?.cast<String, dynamic>() ?? const {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int get disponible {
|
||||
final libre = capacidadMaxima - ocupacion;
|
||||
return libre < 0 ? 0 : libre;
|
||||
}
|
||||
|
||||
bool get estaLleno => capacidadMaxima > 0 && disponible == 0;
|
||||
}
|
||||
|
||||
class DiaTurnos {
|
||||
final DateTime fecha;
|
||||
final int diaSemana;
|
||||
final DiaEstado estado;
|
||||
final List<Turno> turnos;
|
||||
|
||||
const DiaTurnos({
|
||||
required this.fecha,
|
||||
required this.diaSemana,
|
||||
required this.estado,
|
||||
required this.turnos,
|
||||
});
|
||||
|
||||
factory DiaTurnos.fromMap(DateTime fecha, Map<String, dynamic> map) {
|
||||
final lista = (map['turnos'] as List?)
|
||||
?.map((e) => Turno.fromMap((e as Map).cast<String, dynamic>()))
|
||||
.toList() ??
|
||||
const <Turno>[];
|
||||
return DiaTurnos(
|
||||
fecha: fecha,
|
||||
diaSemana: (map['dia_semana'] as num?)?.toInt() ?? fecha.weekday,
|
||||
estado: _parseEstado(map['estado'] as String?),
|
||||
turnos: lista,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SemanaTurnos {
|
||||
final DateTime weekStart;
|
||||
final Map<String, DiaTurnos> _byKey;
|
||||
|
||||
const SemanaTurnos._(this.weekStart, this._byKey);
|
||||
|
||||
factory SemanaTurnos.fromResponse(
|
||||
DateTime weekStart,
|
||||
Map<String, dynamic> raw,
|
||||
) {
|
||||
final map = <String, DiaTurnos>{};
|
||||
raw.forEach((dateKey, value) {
|
||||
if (value is! Map) return;
|
||||
final fecha = DateTime.tryParse(dateKey);
|
||||
if (fecha == null) return;
|
||||
final f = DateTime(fecha.year, fecha.month, fecha.day);
|
||||
map[_keyFor(f)] =
|
||||
DiaTurnos.fromMap(f, value.cast<String, dynamic>());
|
||||
});
|
||||
return SemanaTurnos._(weekStart, map);
|
||||
}
|
||||
|
||||
static String _keyFor(DateTime d) =>
|
||||
'${d.year.toString().padLeft(4, '0')}-'
|
||||
'${d.month.toString().padLeft(2, '0')}-'
|
||||
'${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
DiaTurnos? diaPara(DateTime fecha) => _byKey[_keyFor(fecha)];
|
||||
|
||||
bool contieneFecha(DateTime fecha) => _byKey.containsKey(_keyFor(fecha));
|
||||
}
|
||||
|
||||
class InscriptoTurno {
|
||||
final String reservaId;
|
||||
final String clienteId;
|
||||
final String nombre;
|
||||
final String? apellido;
|
||||
final bool cancelada;
|
||||
|
||||
const InscriptoTurno({
|
||||
required this.reservaId,
|
||||
required this.clienteId,
|
||||
required this.nombre,
|
||||
this.apellido,
|
||||
this.cancelada = false,
|
||||
});
|
||||
|
||||
factory InscriptoTurno.fromMap(Map<String, dynamic> map) {
|
||||
return InscriptoTurno(
|
||||
reservaId: map['reserva_id'] as String? ?? '',
|
||||
clienteId: map['cliente_id'] as String? ?? '',
|
||||
nombre: map['nombre'] as String? ?? '',
|
||||
apellido: map['apellido'] as String?,
|
||||
cancelada: map['cancelada'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
if (nombre.isNotEmpty && apellido != null && apellido!.isNotEmpty) {
|
||||
return '$nombre $apellido';
|
||||
}
|
||||
return nombre.isNotEmpty ? nombre : '?';
|
||||
}
|
||||
|
||||
String get initials {
|
||||
if (nombre.isNotEmpty) {
|
||||
if (apellido != null && apellido!.isNotEmpty) {
|
||||
return '${nombre[0]}${apellido![0]}'.toUpperCase();
|
||||
}
|
||||
return nombre[0].toUpperCase();
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
class EstadoCupo {
|
||||
final int usados;
|
||||
final int disponibles;
|
||||
final int limiteTotal;
|
||||
final bool tienePlan;
|
||||
|
||||
const EstadoCupo({
|
||||
required this.usados,
|
||||
required this.disponibles,
|
||||
required this.limiteTotal,
|
||||
required this.tienePlan,
|
||||
});
|
||||
|
||||
factory EstadoCupo.fromMap(Map<String, dynamic> map) {
|
||||
return EstadoCupo(
|
||||
usados: (map['usados'] as num?)?.toInt() ?? 0,
|
||||
disponibles: (map['disponibles'] as num?)?.toInt() ?? 0,
|
||||
limiteTotal: (map['limite_total'] as num?)?.toInt() ?? 0,
|
||||
tienePlan: map['tiene_plan'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:gimnasio_soma/features/turnos/domain/entities/turno.dart';
|
||||
|
||||
abstract class TurnosRepository {
|
||||
/// Carga la semana completa empezando en [weekStart] (lunes recomendado).
|
||||
/// Una sola llamada trae 7 días consecutivos; la JIT del backend
|
||||
/// materializa los turnos faltantes según el horario vigente.
|
||||
Future<SemanaTurnos> obtenerSemana(DateTime weekStart);
|
||||
|
||||
/// Workaround admin para crear un turno suelto fuera del schedule regular.
|
||||
/// El backend lo marca como `es_especial=true`.
|
||||
Future<void> crearTurnoManual({
|
||||
required DateTime fecha,
|
||||
required int actividadId,
|
||||
required String horaInicio,
|
||||
required String horaFin,
|
||||
required int capacidad,
|
||||
});
|
||||
|
||||
/// Lista las reservas del turno (devuelve también canceladas).
|
||||
Future<List<InscriptoTurno>> obtenerInscriptos(String turnoId);
|
||||
|
||||
/// Reserva como admin: bypassa deuda/plan/semana/fecha pasada,
|
||||
/// sólo respeta capacidad del turno.
|
||||
Future<void> reservarAdmin({
|
||||
required String turnoId,
|
||||
required String clienteId,
|
||||
});
|
||||
|
||||
/// Cancela una reserva como admin: bypassa ownership y antelación.
|
||||
Future<void> cancelarReservaAdmin(String reservaId);
|
||||
|
||||
/// Cupo semanal del cliente para la semana que contiene [fecha].
|
||||
Future<EstadoCupo> obtenerEstadoCupo({
|
||||
required String clienteId,
|
||||
required DateTime fecha,
|
||||
});
|
||||
|
||||
/// Mantenimiento: borra turnos vacíos de más de [diasAntiguedad] días.
|
||||
Future<Map<String, dynamic>> limpiarTurnosAntiguos({int diasAntiguedad = 30});
|
||||
}
|
||||
Reference in New Issue
Block a user