diff --git a/txclient/src/app/admin/(organization-profile)/org/profile/[id]/wap/page.tsx b/txclient/src/app/admin/(organization-profile)/org/profile/[id]/wap/page.tsx index 6120353..9c02405 100644 --- a/txclient/src/app/admin/(organization-profile)/org/profile/[id]/wap/page.tsx +++ b/txclient/src/app/admin/(organization-profile)/org/profile/[id]/wap/page.tsx @@ -85,6 +85,10 @@ export default function OrganizationEdit() { return; } + if (wapView?.botSession === "ok") { + return; + } + wapServerBotGetQR({ companyId: id, sessionUser: SessionInfo.userId, diff --git a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx index 24c2798..6a46422 100644 --- a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx +++ b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx @@ -17,6 +17,7 @@ import ColorLensIcon from '@mui/icons-material/ColorLens'; import PinDropIcon from '@mui/icons-material/PinDrop'; import ReviewsIcon from '@mui/icons-material/Reviews'; import NotificationsIcon from '@mui/icons-material/Notifications'; +import WhatsAppIcon from '@mui/icons-material/WhatsApp'; import API from "@services/Api.Service"; @@ -81,10 +82,19 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [ flowId: "notifications", title: "Notificaciones", description: "Configura canales de notificación, recordatorios y horario silencioso para la organización o un cliente.", - category: "Negocio", + category: "Comunicación", keywords: ["notificaciones", "canales", "whatsapp", "email", "sistema", "recordatorio", "silencioso", "cliente"], icon: NotificationsIcon }, + { + id: "whatsapp-bot", + flowId: "whatsapp-bot", + title: "WhatsApp", + description: "Configura tu bot de WhatsApp para enviar notificaciones automáticas a tus clientes.", + category: "Comunicación", + keywords: ["whatsapp", "bot", "notificaciones", "wap", "mensajes", "automatico", "qr"], + icon: WhatsAppIcon + }, { id: "new-service", flowId: "onboarding-service&action=new-service", diff --git a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx index ce9991a..e5f83c1 100644 --- a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx +++ b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx @@ -26,6 +26,7 @@ import Switch from "@components/Switch/Switch"; import TextTime from "@components/TextTime/TextTime"; import { NotificationChannel, ReminderRule } from "@models/NotificationPreferences.model"; import { OrganizationClientView } from "@models/Clients.model"; +import WhatsAppIcon from "@mui/icons-material/WhatsApp"; interface StepEngineProps { flowId: string; @@ -149,6 +150,13 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm const [isUpgradeRequired, setIsUpgradeRequired] = useState(false); const [inviteFlowNotice, setInviteFlowNotice] = useState(""); + // State for WhatsApp Bot Flow + const [wapView, setWapView] = useState(null); + const [wapAction, setWapAction] = useState(null); + const [wapPhase, setWapPhase] = useState("intro"); + const [wapQr, setWapQr] = useState(null); + const [wapError, setWapError] = useState(null); + // State for Setup Collaborator Flow const [collaboratorToSetup, setCollaboratorToSetup] = useState(null); const [setupMissingServices, setSetupMissingServices] = useState(false); @@ -319,11 +327,16 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm setNotifClients(res?.data || []); }) .catch(err => console.error("Error cargando clientes de la organización:", err)); + } else if (flowId === "whatsapp-bot") { + setWapPhase("intro"); + setWapView(null); + setWapQr(null); + setWapError(null); } }; React.useEffect(() => { - if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") { + if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") { setIsResuming(true); API.post("companies/get-by-user", { sessionUser: SessionInfo.userId }) .then(response => { @@ -344,7 +357,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 === "notifications" || (flowId === "onboarding-org" && action === "new-org")) { + if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId === "whatsapp-bot" || (flowId === "onboarding-org" && action === "new-org")) { setCurrentStepIndex(0); } }, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]); @@ -638,6 +651,543 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm } }; + // --- WhatsApp Bot Flow Helpers --- + + const getImageFromBase64 = (base64: string): string => { + const base64Data = base64.includes("base64,") ? base64.split(",")[1] : base64; + const byteCharacters = atob(base64Data); + const byteArray = new Uint8Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteArray[i] = byteCharacters.charCodeAt(i); + } + const blob = new Blob([byteArray], { type: "image/png" }); + return URL.createObjectURL(blob); + }; + + const loadWapView = async () => { + if (!createdCompanyId) return; + setWapAction("load"); + setWapError(null); + try { + const view = await API.post("views/wap-server-view", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + setWapView(view); + + const hasPlan = SessionInfo.organizationSubscriptions?.[createdCompanyId]?.plan?.wapNotifications; + if (!hasPlan) { + setWapPhase("plan-required"); + } else if (!view?.serverId) { + setWapPhase("assign-server"); + } else if (!view?.exists) { + setWapPhase("create-bot"); + } else if (view?.runningState && !String(view.runningState).includes("Up")) { + setWapPhase("start-bot"); + } else if (view?.runningState && String(view.runningState).includes("Up") && view?.botSession !== "ok") { + setWapPhase("scan-qr"); + try { + const qrData = await API.post("wapserver/qr", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + setWapQr(qrData); + } catch { + setWapQr(null); + } + } else if (view?.runningState && String(view.runningState).includes("Up") && view?.botSession === "ok") { + setWapPhase("bot-ready"); + } + } catch (err: any) { + console.error("Error loading WAP view:", err); + setWapError(err?.message || "Error al verificar el estado del bot."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapAssignServer = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("assign"); + setWapError(null); + try { + await API.post("wapserver/assign", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 2000)); + await loadWapView(); + } catch (err: any) { + console.error("Error assigning server:", err); + setWapError(err?.message || "Error al asignar el servidor."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapCreateBot = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("create"); + setWapError(null); + try { + await API.post("wapserver/create", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 5000)); + await loadWapView(); + } catch (err: any) { + console.error("Error creating bot:", err); + setWapError(err?.message || "Error al crear el bot."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapStartBot = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("start"); + setWapError(null); + try { + await API.post("wapserver/start", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 3000)); + await loadWapView(); + } catch (err: any) { + console.error("Error starting bot:", err); + setWapError(err?.message || "Error al iniciar el bot."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapVerifyQr = async () => { + if (!createdCompanyId) return; + setWapAction("verify"); + try { + const view = await API.post("views/wap-server-view", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + setWapView(view); + if (view?.botSession === "ok") { + setIsSuccess(true); + } else { + setWapError("Aún no se detectó la conexión. Asegurate de haber escaneado el QR y volvé a intentar."); + } + } catch (err: any) { + setWapError(err?.message || "Error al verificar la conexión."); + } finally { + setWapAction(null); + } + }; + + const handleWapStopBot = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("stop"); + setWapError(null); + try { + await API.post("wapserver/stop", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 2000)); + await loadWapView(); + } catch (err: any) { + console.error("Error stopping bot:", err); + setWapError(err?.message || "Error al detener el bot."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapDeleteBot = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("delete"); + setWapError(null); + try { + await API.post("wapserver/delete", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 3000)); + await loadWapView(); + } catch (err: any) { + console.error("Error deleting bot:", err); + setWapError(err?.message || "Error al eliminar el bot."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const handleWapQuitServer = async () => { + if (!createdCompanyId || wapAction) return; + setWapAction("quit"); + setWapError(null); + try { + await API.post("wapserver/quit", { + companyId: createdCompanyId, + sessionUser: SessionInfo.userId + }); + await new Promise(resolve => setTimeout(resolve, 2000)); + await loadWapView(); + } catch (err: any) { + console.error("Error quitting server:", err); + setWapError(err?.message || "Error al desasignar el servidor."); + setWapPhase("error"); + } finally { + setWapAction(null); + } + }; + + const renderWhatsAppBotFlow = () => { + return ( + <> + {/* Intro */} + { + loadWapView(); + }} + nextLabel="Verificar estado" + topAccessory={ + + } + > + {null} + + + {/* Plan required */} + { + router.push(`/landing/upgrade-plan?org=${createdCompanyId}`); + }} + nextLabel="Ver planes" + topAccessory={ +
📦
+ } + > +
+ Actualizá tu plan para desbloquear notificaciones por WhatsApp y mejorar la comunicación con tus clientes. +
+
+ + {/* Assign server */} + 🖥️ + } + > + {wapAction === "assign" && ( +
+
+ Asignando servidor... +
+ )} + + + {/* Create bot */} + 🤖
+ } + > +
+ {wapAction === "create" && ( +
+
+ Creando bot... +
+ )} + +
+ + + {/* Start bot */} + ▶️
+ } + > +
+ {wapAction === "start" && ( +
+
+ Iniciando bot... +
+ )} + +
+ + + {/* Scan QR */} + 📱
+ } + > +
+ {wapQr ? ( +
+ Escaneá el código QR +
+ ) : ( +
+ Cargando QR... +
+ )} +
+ Abrí WhatsApp en tu teléfono → ConfiguraciónDispositivos vinculadosVincular dispositivo +
+ {wapError && ( +
+ {wapError} +
+ )} + +
+
+ + {/* Bot ready */} + setIsSuccess(true)} + nextLabel="Finalizar" + topAccessory={ +
+ } + > +
+
+ Sesión de WhatsApp activa — Bot operativo +
+ +
+
+ + {/* Error */} + { + setWapPhase("intro"); + setWapError(null); + }} + nextLabel="Reintentar" + topAccessory={ +
⚠️
+ } + > +
+ Si el problema persiste, contactá al soporte de TurnosXpress. +
+
+ + + + ); + }; + const renderOnboardingOrgFlow = () => { return ( <> @@ -3945,6 +4495,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm if (flowId === "new-collaborator") return renderNewCollaboratorFlow(); if (flowId === "public-visibility") return renderPublicVisibilityFlow(); if (flowId === "notifications") return renderNotificationsFlow(); + if (flowId === "whatsapp-bot") return renderWhatsAppBotFlow(); if (flowId === "disable-schedule") return renderDisableScheduleFlow(); if (flowId === "override-schedule") return renderOverrideScheduleFlow(); if (flowId === "update-schedule") return renderUpdateScheduleFlow(); @@ -3964,6 +4515,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm if (flowId === "new-collaborator") return "El colaborador fue invitado exitosamente."; 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 (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."; };