feat: implement backend and frontend support for managing reservation period extensions and summaries

This commit is contained in:
2026-07-28 17:10:20 -03:00
parent 68d98e8b40
commit ece3879c8d
9 changed files with 697 additions and 13 deletions
@@ -18,6 +18,49 @@ export type EnableScheduleParams = {
sessionUser: string;
};
export type ReservationPeriodScope = "employee" | "all";
export type ReservationPeriodDurationUnit = "week" | "month";
export type ReservationPeriodDuration = {
unit: ReservationPeriodDurationUnit;
value: number;
};
export type ReservationPeriodsSummaryParams = {
companyId: string;
sessionUser: string;
};
export type ExtendReservationPeriodsParams = {
companyId: string;
scope: ReservationPeriodScope;
employeeId?: string;
duration?: ReservationPeriodDuration;
weeks?: number;
sessionUser: string;
};
export type ReservationPeriodEmployeeSummary = {
employeeId: string;
employeeFullName: string;
employeeEmail: string;
employeeAvatar: string;
employeeUserId: string;
isOpen: boolean;
currentOpenUntil: string | null;
daysRemaining: number | null;
};
export type ReservationPeriodsSummary = {
companyId: string;
status: "missing" | "expired" | "near-ending" | "open";
worstDaysRemaining: number | null;
currentOpenUntil: string | null;
nearEndingThresholdDays: number;
employees: ReservationPeriodEmployeeSummary[];
};
export interface SchedulesEnabledView {
id: string;
employeeId: string;
@@ -8,7 +8,10 @@ import { ApiVoidResult } from "@models/Api.VoidResult.type";
import ApiRequest from "@services/Api.Service";
import {
EnableScheduleParams,
ExtendReservationPeriodsParams,
FindSchedulesEnabledParams,
ReservationPeriodsSummary,
ReservationPeriodsSummaryParams,
SchedulesEnabledView,
} from "@models/SchedulesEnabled.type";
import {
@@ -56,6 +59,18 @@ export const disableSchedule = async (data: EnableScheduleParams): Promise<ApiVo
return ApiRequest.post<ApiVoidResult>("schedules-enabled/disable", data);
};
export const summarizeReservationPeriods = async (
data: ReservationPeriodsSummaryParams
): Promise<ReservationPeriodsSummary> => {
return ApiRequest.post<ReservationPeriodsSummary>("schedules-enabled/reservation-periods/summary", data);
};
export const extendReservationPeriods = async (
data: ExtendReservationPeriodsParams
): Promise<ReservationPeriodsSummary> => {
return ApiRequest.post<ReservationPeriodsSummary>("schedules-enabled/reservation-periods/extend", data);
};
export const uploadHeaderImage = async (data: FormData): Promise<ApiVoidResult> => {
return ApiRequest.postFile<ApiVoidResult>("companies/upload-header", data);
};
@@ -20,6 +20,7 @@ import NotificationsIcon from '@mui/icons-material/Notifications';
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
import QrCode2Icon from '@mui/icons-material/QrCode2';
import API from "@services/Api.Service";
import { ReservationPeriodsSummary } from "@models/SchedulesEnabled.type";
type FlowDefinition = {
@@ -123,6 +124,15 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [
keywords: ["horarios", "disponibilidad", "semana", "apertura", "cierre", "jornada"],
icon: CalendarMonthIcon
},
{
id: "open-reservation-periods",
flowId: "open-reservation-periods",
title: "Extender Reservas",
description: "Abrir más semanas para que tus clientes puedan reservar turnos.",
category: "Horarios",
keywords: ["reservas", "periodo", "agenda", "extender", "habilitar", "semanas"],
icon: EventBusyIcon
},
{
id: "override-schedule",
flowId: "override-schedule",
@@ -165,6 +175,7 @@ export default function AssistantHome() {
const [incompleteOrgs, setIncompleteOrgs] = React.useState<IncompleteOrgData[]>([]);
const [incompleteCollaborators, setIncompleteCollaborators] = React.useState<any[]>([]);
const [reservationPeriodAlerts, setReservationPeriodAlerts] = React.useState<Array<{ org: any; summary: ReservationPeriodsSummary }>>([]);
const [isLoadingFlow, setIsLoadingFlow] = React.useState(true);
const [searchQuery, setSearchQuery] = React.useState("");
@@ -174,9 +185,25 @@ export default function AssistantHome() {
const response = await API.post<any[]>("companies/get-by-user", { sessionUser: SessionInfo.userId });
const orgs = response || [];
const newIncompleteOrgs: IncompleteOrgData[] = [];
const reservationAlerts: Array<{ org: any; summary: ReservationPeriodsSummary }> = [];
if (orgs.length > 0) {
for (const org of orgs) {
if (org.isAdmin) {
try {
const summary = await API.post<ReservationPeriodsSummary>("schedules-enabled/reservation-periods/summary", {
companyId: org.id || org._id,
sessionUser: SessionInfo.userId
});
if (summary && summary.status !== "open") {
reservationAlerts.push({ org, summary });
}
} catch (e) {
console.error("Error checking reservation periods", e);
}
}
if (org.onboardingCompleted) continue;
const isNameMissing = !org.name || org.name.trim() === "";
@@ -239,6 +266,7 @@ export default function AssistantHome() {
}
setIncompleteOrgs(newIncompleteOrgs);
setReservationPeriodAlerts(reservationAlerts);
} else {
setIncompleteOrgs([{
org: null,
@@ -246,6 +274,7 @@ export default function AssistantHome() {
currentStepName: "Detalles del Negocio",
flowToTrigger: "onboarding-org"
}]);
setReservationPeriodAlerts([]);
}
} catch (error) {
console.error("Error fetching organizations:", error);
@@ -255,6 +284,7 @@ export default function AssistantHome() {
currentStepName: "Detalles del Negocio",
flowToTrigger: "onboarding-org"
}]);
setReservationPeriodAlerts([]);
}
try {
@@ -273,7 +303,7 @@ export default function AssistantHome() {
}, [SessionInfo.metrics, SessionInfo.userId, router]);
const isSetupIncomplete = () => {
return incompleteOrgs.length > 0 || incompleteCollaborators.length > 0;
return incompleteOrgs.length > 0 || incompleteCollaborators.length > 0 || reservationPeriodAlerts.length > 0;
};
const handleStartFlow = (flowId: string, companyId?: string) => {
@@ -397,6 +427,46 @@ export default function AssistantHome() {
/>
</div>
))}
{reservationPeriodAlerts.map(({ org, summary }, index) => {
const orgId = org.id || org._id;
const reservationPeriodCopy = summary.status === "missing"
? {
title: "Falta abrir el período de reservas",
description: "Uno o más colaboradores todavía no tienen un período abierto para recibir reservas. Abrilo para que tus clientes puedan sacar turnos.",
}
: summary.status === "expired"
? {
title: "El período de reservas está vencido",
description: "Uno o más colaboradores tienen el período de reservas vencido. Extendelo para que tus clientes puedan volver a reservar turnos.",
}
: {
title: "El período de reservas está por finalizar",
description: "El período abierto para reservas de uno o más colaboradores está por llegar a su fin. Extendelo para que tus clientes puedan seguir reservando turnos.",
};
return (
<div key={`reservation-period-${orgId || index}`} className={style.onboardingCard}>
<div className={style.onboardingContent}>
<div className={style.onboardingIcon}>
<EventBusyIcon fontSize="large" style={{ color: "#fff" }} />
</div>
<div className={style.onboardingTexts} style={{ flex: 1 }}>
<h3>{reservationPeriodCopy.title}</h3>
<p>{reservationPeriodCopy.description}</p>
</div>
</div>
<AssistantButton
text={isLoadingFlow ? "Cargando..." : "Extender período"}
onClick={() => {
if (!isLoadingFlow) {
handleStartFlow("open-reservation-periods", orgId);
}
}}
style={{ width: "100%", marginTop: "20px", height: "48px" }}
/>
</div>
);
})}
</div>
)}
@@ -29,6 +29,7 @@ import { OrganizationClientView } from "@models/Clients.model";
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
import QrCode2Icon from "@mui/icons-material/QrCode2";
import { getSlug } from "@core/app/helpers/Slug";
import { ReservationPeriodDuration, ReservationPeriodsSummary } from "@models/SchedulesEnabled.type";
interface StepEngineProps {
flowId: string;
@@ -143,6 +144,18 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
const [isFullDayDisabled, setIsFullDayDisabled] = useState<boolean>(true);
const [disableStartTime, setDisableStartTime] = useState<string>("");
const [disableEndTime, setDisableEndTime] = useState<string>("");
const [reservationPeriodScope, setReservationPeriodScope] = useState<"employee" | "all" | null>(null);
const reservationPeriodOptions: Array<ReservationPeriodDuration & { label: string }> = [
{ unit: "week", value: 1, label: "1 semana" },
{ unit: "week", value: 2, label: "2 semanas" },
{ unit: "week", value: 3, label: "3 semanas" },
{ unit: "month", value: 1, label: "1 mes" },
{ unit: "month", value: 2, label: "2 meses" },
];
const [reservationPeriodDuration, setReservationPeriodDuration] = useState<ReservationPeriodDuration | null>({ unit: "month", value: 1 });
const [reservationPeriodSummary, setReservationPeriodSummary] = useState<ReservationPeriodsSummary | null>(null);
const [reservationPeriodEmployeesLoading, setReservationPeriodEmployeesLoading] = useState(false);
const [reservationPeriodEmployeesError, setReservationPeriodEmployeesError] = useState<string | null>(null);
// State for New Collaborator Flow
const [inviteEmail, setInviteEmail] = useState("");
@@ -265,18 +278,56 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
}).catch(console.error);
}
}).catch(console.error);
} else if (flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") {
} else if (flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule" || flowId === "open-reservation-periods") {
if (flowId === "open-reservation-periods") {
setCurrentStepIndex(0);
setReservationPeriodScope(null);
setTargetEmployeeId(null);
setReservationPeriodDuration({ unit: "month", value: 1 });
setReservationPeriodSummary(null);
setAllEmployees([]);
setReservationPeriodEmployeesError(null);
setReservationPeriodEmployeesLoading(true);
}
API.post<any[]>("employees/get-by-company", { companyId: org.id || org._id })
.then(empRes => {
if (empRes && empRes.length > 0) {
const availableEmployees = empRes.filter(canManageEmployeeSchedules);
setAllEmployees(availableEmployees);
const me = availableEmployees.find((e: any) => String(getEmployeeUserId(e)) === String(SessionInfo.userId));
if (me) {
setEmployeeId(me.id || me._id || me.employeeId);
const availableEmployees = flowId === "open-reservation-periods"
? (empRes || []).filter((employee: any) => !employee.removed)
: (empRes || []).filter(canManageEmployeeSchedules);
setAllEmployees(availableEmployees);
const me = availableEmployees.find((e: any) => String(getEmployeeUserId(e)) === String(SessionInfo.userId));
if (me) {
setEmployeeId(me.id || me._id || me.employeeId);
}
if (flowId === "open-reservation-periods") {
if (availableEmployees.length === 0) {
setReservationPeriodEmployeesError("No encontramos colaboradores activos para extender el período de reservas.");
return;
}
if (availableEmployees.length === 1) {
setReservationPeriodScope("employee");
setTargetEmployeeId(availableEmployees[0].id || availableEmployees[0]._id || availableEmployees[0].employeeId);
}
}
}).catch(console.error);
}).catch(error => {
console.error(error);
if (flowId === "open-reservation-periods") {
setReservationPeriodEmployeesError("No pudimos cargar los colaboradores. Reintentá antes de extender el período.");
}
}).finally(() => {
if (flowId === "open-reservation-periods") {
setReservationPeriodEmployeesLoading(false);
}
});
if (flowId === "open-reservation-periods") {
API.post<ReservationPeriodsSummary>("schedules-enabled/reservation-periods/summary", {
companyId: org.id || org._id,
sessionUser: SessionInfo.userId
}).then(setReservationPeriodSummary).catch(console.error);
}
} else if (flowId === "new-collaborator") {
setCurrentStepIndex(1);
} else if (flowId === "public-visibility") {
@@ -342,7 +393,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
};
React.useEffect(() => {
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") {
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule" || flowId === "open-reservation-periods") {
setIsResuming(true);
API.post<any[]>("companies/get-by-user", { sessionUser: SessionInfo.userId })
.then(response => {
@@ -363,7 +414,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
.finally(() => setIsResuming(false));
}
if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || (flowId === "onboarding-org" && action === "new-org")) {
if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId === "open-reservation-periods" || (flowId === "onboarding-org" && action === "new-org")) {
setCurrentStepIndex(0);
}
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
@@ -4556,6 +4607,253 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
);
};
const handleExtendReservationPeriods = async () => {
if (!createdCompanyId || !reservationPeriodScope || !reservationPeriodDuration || isCreating) return;
if (reservationPeriodEmployeesLoading || reservationPeriodEmployeesError || allEmployees.length === 0) return;
if (reservationPeriodScope === "employee" && !targetEmployeeId) return;
setIsCreating(true);
try {
await API.post<ReservationPeriodsSummary>("schedules-enabled/reservation-periods/extend", {
companyId: createdCompanyId,
scope: reservationPeriodScope,
employeeId: reservationPeriodScope === "employee" ? targetEmployeeId : undefined,
duration: reservationPeriodDuration,
sessionUser: SessionInfo.userId
});
setIsSuccess(true);
} catch (error: any) {
console.error("Error extendiendo período de reservas:", error);
alert(error?.message || "Hubo un error al extender el período de reservas.");
} finally {
setIsCreating(false);
}
};
const renderReservationPeriodFlow = () => {
if (reservationPeriodEmployeesLoading) {
return (
<QuestionCard
isActive={currentStepIndex === 0}
title="Cargando colaboradores"
description="Estamos buscando los colaboradores disponibles para extender el período de reservas."
disableNext
nextLabel="Cargando..."
>
<div />
</QuestionCard>
);
}
if (reservationPeriodEmployeesError || allEmployees.length === 0) {
return (
<QuestionCard
isActive={currentStepIndex === 0}
title="No pudimos preparar la extensión"
description={reservationPeriodEmployeesError || "No encontramos colaboradores activos para extender el período de reservas."}
disableNext
nextLabel="Extensión bloqueada"
>
<div />
</QuestionCard>
);
}
const needsEmployeeSelection = allEmployees.length > 1 && reservationPeriodScope === "employee";
const selectedEmployee = allEmployees.find((employee: any) => String(employee.id || employee._id || employee.employeeId) === String(targetEmployeeId));
const reservationPeriodEmployees = reservationPeriodSummary?.employees || [];
const getReservationPeriodEmployeeSummary = (employee: any) => {
const employeeKey = employee.id || employee._id || employee.employeeId;
return reservationPeriodEmployees.find(summary => String(summary.employeeId) === String(employeeKey));
};
const getReservationPeriodStatus = (employee: any) => {
const summary = getReservationPeriodEmployeeSummary(employee);
const daysRemaining = summary?.daysRemaining;
const safeDaysRemaining = typeof daysRemaining === "number" ? Math.max(daysRemaining, 0) : 0;
const progress = Math.min(Math.round((safeDaysRemaining / 30) * 100), 100);
const color = !summary?.isOpen || safeDaysRemaining === 0
? "#ef4444"
: safeDaysRemaining <= 7
? "#f59e0b"
: "#22c55e";
let label = "Sin período abierto";
if (summary?.isOpen && typeof daysRemaining === "number") {
if (daysRemaining < 0) {
label = "Vencido";
} else if (daysRemaining === 1) {
label = "1 día restante";
} else {
label = `${daysRemaining} días restantes`;
}
} else if (typeof daysRemaining === "number" && daysRemaining < 0) {
label = "Vencido";
}
return {
label,
color,
progress,
openUntilLabel: summary?.currentOpenUntil ? `Abierto hasta ${dayjs(summary.currentOpenUntil).format("D [de] MMMM")}` : null
};
};
return (
<>
{allEmployees.length > 1 && (
<QuestionCard
isActive={currentStepIndex === 0}
title="Elegí a quién extenderle el período"
description="Podés abrir reservas para todos los colaboradores o para uno específico."
onNext={handleNext}
disableNext={!reservationPeriodScope || (needsEmployeeSelection && !targetEmployeeId)}
nextLabel="Siguiente"
>
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%" }}>
<div
onClick={() => {
setReservationPeriodScope("all");
setTargetEmployeeId(null);
}}
style={{
padding: "18px", borderRadius: "12px", cursor: "pointer",
background: reservationPeriodScope === "all" ? "var(--wine-red)" : "rgba(255,255,255,0.05)",
border: `2px solid ${reservationPeriodScope === "all" ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
color: "white", textAlign: "center", transition: "all 0.2s ease"
}}
>
<h4 style={{ margin: "0 0 5px 0", fontSize: "16px", fontWeight: reservationPeriodScope === "all" ? "bold" : "normal" }}>Todos los colaboradores</h4>
<p style={{ margin: 0, fontSize: "13px", color: "rgba(255,255,255,0.75)" }}>Extiende el período de reservas para todo el equipo.</p>
</div>
<div
onClick={() => setReservationPeriodScope("employee")}
style={{
padding: "18px", borderRadius: "12px", cursor: "pointer",
background: reservationPeriodScope === "employee" ? "var(--wine-red)" : "rgba(255,255,255,0.05)",
border: `2px solid ${reservationPeriodScope === "employee" ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
color: "white", textAlign: "center", transition: "all 0.2s ease"
}}
>
<h4 style={{ margin: "0 0 5px 0", fontSize: "16px", fontWeight: reservationPeriodScope === "employee" ? "bold" : "normal" }}>Un colaborador específico</h4>
<p style={{ margin: 0, fontSize: "13px", color: "rgba(255,255,255,0.75)" }}>Elige una persona del equipo.</p>
</div>
{needsEmployeeSelection && (
<div style={{ display: "flex", flexDirection: "column", gap: "8px", marginTop: "8px" }}>
{allEmployees.map((employee: any) => {
const employeeKey = employee.id || employee._id || employee.employeeId;
const isSelected = String(targetEmployeeId) === String(employeeKey);
const periodStatus = getReservationPeriodStatus(employee);
return (
<div
key={employeeKey}
onClick={() => setTargetEmployeeId(employeeKey)}
style={{
padding: "12px 14px", borderRadius: "10px", cursor: "pointer",
background: isSelected ? "rgba(255,42,127,0.18)" : "rgba(255,255,255,0.04)",
border: `1px solid ${isSelected ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
color: "white"
}}
>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<div style={{ display: "flex", justifyContent: "space-between", gap: "12px", alignItems: "flex-start" }}>
<span style={{ fontWeight: 600 }}>
{employee.fullName || `${employee.firstName || ""} ${employee.lastName || ""}`.trim() || employee.email || "Colaborador"}
</span>
<span style={{ color: periodStatus.color, fontSize: "12px", fontWeight: 700, whiteSpace: "nowrap" }}>
{periodStatus.label}
</span>
</div>
<div style={{ height: "7px", borderRadius: "999px", background: "rgba(255,255,255,0.12)", overflow: "hidden" }}>
<div
style={{
width: `${periodStatus.progress}%`,
minWidth: periodStatus.progress > 0 ? "8px" : 0,
height: "100%",
borderRadius: "999px",
background: periodStatus.color,
transition: "width 0.2s ease"
}}
/>
</div>
{periodStatus.openUntilLabel && (
<span style={{ color: "rgba(255,255,255,0.62)", fontSize: "12px" }}>
{periodStatus.openUntilLabel}
</span>
)}
</div>
</div>
);
})}
</div>
)}
</div>
</QuestionCard>
)}
<QuestionCard
isActive={currentStepIndex === (allEmployees.length > 1 ? 1 : 0)}
title="Elegí cuánto extender"
description="Abriremos un nuevo bloque continuo desde el último día actualmente disponible."
onNext={handleNext}
disableNext={!reservationPeriodDuration}
nextLabel="Siguiente"
>
<div style={{ display: "flex", gap: "10px", flexWrap: "wrap", width: "100%", justifyContent: "center" }}>
{reservationPeriodOptions.map(option => {
const isSelected = reservationPeriodDuration?.unit === option.unit && reservationPeriodDuration.value === option.value;
return (
<div
key={`${option.unit}-${option.value}`}
onClick={() => setReservationPeriodDuration({ unit: option.unit, value: option.value })}
style={{
padding: "12px 24px", borderRadius: "12px", cursor: "pointer",
background: isSelected ? "var(--wine-red)" : "rgba(255,255,255,0.05)",
border: `2px solid ${isSelected ? "var(--wine-red)" : "rgba(255,255,255,0.1)"}`,
color: "white", fontWeight: isSelected ? "bold" : "normal",
transition: "all 0.2s ease", fontSize: "16px"
}}
>
{option.label}
</div>
);
})}
</div>
</QuestionCard>
<QuestionCard
isActive={currentStepIndex === (allEmployees.length > 1 ? 2 : 1)}
title="Confirmar extensión"
description="Revisá la selección antes de abrir el nuevo período de reservas."
onNext={handleExtendReservationPeriods}
disableNext={isCreating || reservationPeriodEmployeesLoading || Boolean(reservationPeriodEmployeesError) || allEmployees.length === 0 || !reservationPeriodScope || !reservationPeriodDuration || (reservationPeriodScope === "employee" && !targetEmployeeId)}
nextLabel={isCreating ? "Extendiendo..." : "Extender período"}
>
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%", maxWidth: "500px", margin: "0 auto", background: "rgba(255,255,255,0.05)", borderRadius: "12px", padding: "18px", border: "1px solid rgba(255,255,255,0.1)", color: "white" }}>
<div>
<div style={{ color: "rgba(255,255,255,0.65)", fontSize: "13px" }}>Organización</div>
<div style={{ fontWeight: 700 }}>{selectedOrganization?.name || orgName}</div>
</div>
<div>
<div style={{ color: "rgba(255,255,255,0.65)", fontSize: "13px" }}>Aplicar a</div>
<div style={{ fontWeight: 700 }}>{reservationPeriodScope === "all" ? "Todos los colaboradores" : (selectedEmployee?.fullName || selectedEmployee?.email || "Colaborador seleccionado")}</div>
</div>
<div>
<div style={{ color: "rgba(255,255,255,0.65)", fontSize: "13px" }}>Duración</div>
<div style={{ fontWeight: 700 }}>{reservationPeriodOptions.find(option => option.unit === reservationPeriodDuration?.unit && option.value === reservationPeriodDuration.value)?.label}</div>
</div>
{reservationPeriodSummary?.worstDaysRemaining !== null && reservationPeriodSummary?.worstDaysRemaining !== undefined && (
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: "13px" }}>
Menor disponibilidad actual: {reservationPeriodSummary.worstDaysRemaining} días restantes.
</div>
)}
</div>
</QuestionCard>
</>
);
};
const renderFlow = () => {
if (allOrganizations.length > 1 && !createdCompanyId && action !== "new-org") {
return (
@@ -4598,6 +4896,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
if (flowId === "notifications") return renderNotificationsFlow();
if (flowId === "whatsapp-bot") return renderWhatsAppBotFlow();
if (flowId === "disable-schedule") return renderDisableScheduleFlow();
if (flowId === "open-reservation-periods") return renderReservationPeriodFlow();
if (flowId === "override-schedule") return renderOverrideScheduleFlow();
if (flowId === "update-schedule") return renderUpdateScheduleFlow();
if (isAddingAnotherService || flowId === "onboarding-service") return renderOnboardingServiceFlow();
@@ -4617,6 +4916,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
if (flowId === "setup-collaborator") return "El colaborador ha sido configurado exitosamente y ya puede comenzar a recibir reservas.";
if (flowId === "notifications") return "La configuración de notificaciones se ha guardado exitosamente.";
if (flowId === "whatsapp-bot") return "Tu bot de WhatsApp está configurado y funcionando. Tus clientes recibirán notificaciones automáticas por WhatsApp.";
if (flowId === "open-reservation-periods") return "El período de reservas fue extendido exitosamente.";
if (action) return "Tus cambios se han guardado exitosamente.";
return "Has configurado exitosamente tu negocio. Todo está listo para que empieces a recibir reservas y gestionar tu agenda como un profesional.";
};