From 3988e328eb95b2cacdca66ce0ac21664d3dc8f52 Mon Sep 17 00:00:00 2001 From: Horacio Daniel Ros Date: Wed, 22 Jul 2026 16:57:16 -0300 Subject: [PATCH] feat: add public visibility configuration flow for organizations and services --- .../AssistantHome/AssistantHome.tsx | 10 ++ .../components/StepEngine/StepEngine.tsx | 137 +++++++++++++++++- 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx index 4e6a4c3..392ef6b 100644 --- a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx +++ b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx @@ -15,6 +15,7 @@ import EventRepeatIcon from "@mui/icons-material/EventRepeat"; import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; import ColorLensIcon from '@mui/icons-material/ColorLens'; import PinDropIcon from '@mui/icons-material/PinDrop'; +import ReviewsIcon from '@mui/icons-material/Reviews'; import API from "@services/Api.Service"; @@ -65,6 +66,15 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [ keywords: ["telefono", "contacto", "email", "whatsapp", "redes", "celular"], icon: AddCircleOutlineIcon }, + { + id: "public-visibility", + flowId: "public-visibility", + title: "Visibilidad Pública", + description: "Configura opiniones, puntuaciones y profesionales visibles para tus clientes.", + category: "Negocio", + keywords: ["opiniones", "puntuaciones", "calificaciones", "profesionales", "visibilidad", "reseñas", "publico"], + icon: ReviewsIcon + }, { 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 2916e3a..2c7ca34 100644 --- a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx +++ b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx @@ -100,6 +100,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm const [previewServices, setPreviewServices] = useState([]); + // State for Public Visibility Flow + const [selectedOrganization, setSelectedOrganization] = useState(null); + const [publicVisibilityServices, setPublicVisibilityServices] = useState([]); + const [orgShowPublicScores, setOrgShowPublicScores] = useState(true); + const [orgShowPublicOpinions, setOrgShowPublicOpinions] = useState(true); + const [orgShowPublicProfessionals, setOrgShowPublicProfessionals] = useState(true); + const [servicesShowPublicScores, setServicesShowPublicScores] = useState(true); + const [servicesShowPublicOpinions, setServicesShowPublicOpinions] = useState(true); + // State for standalone update schedule flow const [scheduleScope, setScheduleScope] = useState<"me" | "specific" | "all" | null>(null); const [targetEmployeeId, setTargetEmployeeId] = useState(null); @@ -138,6 +147,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm const loadOrganizationContext = (org: any) => { setCreatedCompanyId(org.id || org._id); + setSelectedOrganization(org); setOrgName(org.name || ""); setCategoryId(org.categoryId || null); setOrgDescription(org.description || ""); @@ -156,6 +166,9 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm setHeatMapFraction(org.heatMapFraction || 60); setAppointmentTime(org.appointmentTime || 24); setCancellationTime(org.cancellationTime || 12); + setOrgShowPublicScores(org.showPublicScores !== undefined ? org.showPublicScores : true); + setOrgShowPublicOpinions(org.showPublicOpinions !== undefined ? org.showPublicOpinions : true); + setOrgShowPublicProfessionals(org.showPublicProfessionals !== undefined ? org.showPublicProfessionals : true); if (org.headerColor) setHeaderColor(org.headerColor); if (org.headerFontColor) setHeaderFontColor(org.headerFontColor); @@ -237,6 +250,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm }).catch(console.error); } else if (flowId === "new-collaborator") { setCurrentStepIndex(1); + } else if (flowId === "public-visibility") { + setCurrentStepIndex(0); + API.post("services/get-by-company", { companyId: org.id || org._id }) + .then(res => { + const services = res || []; + setPublicVisibilityServices(services); + setServicesShowPublicScores(services.every(service => service.showPublicScores !== false)); + setServicesShowPublicOpinions(services.every(service => service.showPublicOpinions !== false)); + }).catch(console.error); } else if (flowId === "setup-collaborator") { setCurrentStepIndex(0); if (initialEmployeeId) { @@ -264,7 +286,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm }; React.useEffect(() => { - if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") { + if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || 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 => { @@ -285,7 +307,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm .finally(() => setIsResuming(false)); } - if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || (flowId === "onboarding-org" && action === "new-org")) { + if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || (flowId === "onboarding-org" && action === "new-org")) { setCurrentStepIndex(0); } }, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]); @@ -3257,6 +3279,116 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm return null; }; + const isSelectedOrganizationPaidPlan = () => { + if (!createdCompanyId) return false; + return (SessionInfo.organizationSubscriptions?.[createdCompanyId]?.plan?.price || 0) > 0; + }; + + const handleSavePublicVisibility = async () => { + if (!createdCompanyId || isCreating) return; + + setIsCreating(true); + try { + await API.post("companies/update", { + id: createdCompanyId, + showPublicScores: orgShowPublicScores, + showPublicOpinions: orgShowPublicOpinions, + showPublicProfessionals: orgShowPublicProfessionals, + sessionUser: SessionInfo.userId + }); + + await Promise.all(publicVisibilityServices.map(service => + API.post("services/update", { + id: service.id || service._id, + companyId: createdCompanyId, + name: service.name, + description: service.description, + originalPrice: service.originalPrice, + limit: service.limit, + length: service.length, + color: service.color, + fontColor: service.fontColor, + fontShadowColor: service.fontShadowColor, + sessionUser: SessionInfo.userId, + showPublicScores: servicesShowPublicScores, + showPublicOpinions: servicesShowPublicOpinions + }) + )); + + setIsSuccess(true); + } catch (error: any) { + console.error("Error actualizando visibilidad pública:", error); + alert(error?.message || "Hubo un error al guardar la visibilidad pública."); + } finally { + setIsCreating(false); + } + }; + + const renderVisibilityCheckbox = (label: string, description: string, checked: boolean, onChange: (checked: boolean) => void) => ( + + ); + + const renderPublicVisibilityFlow = () => { + if (!isSelectedOrganizationPaidPlan()) { + return ( + router.push(createdCompanyId ? `/landing/upgrade-plan?org=${createdCompanyId}` : "/landing/pricing")} + nextLabel="Ver planes" + > +
+ Estás configurando {selectedOrganization?.name ? {selectedOrganization.name} : "esta organización"}. Para cambiar qué opiniones, puntuaciones y profesionales se muestran públicamente, el dueño de esta organización necesita un plan pago. +
+
+ ); + } + + return ( + +
+
+

Organización

+ {renderVisibilityCheckbox("Mostrar puntuación pública", "Permite que los clientes vean la puntuación promedio de la organización.", orgShowPublicScores, setOrgShowPublicScores)} + {renderVisibilityCheckbox("Mostrar opiniones públicas", "Permite que los clientes vean las opiniones recibidas por la organización.", orgShowPublicOpinions, setOrgShowPublicOpinions)} + {renderVisibilityCheckbox("Mostrar profesionales públicos", "Permite que los clientes vean la lista de profesionales de la organización.", orgShowPublicProfessionals, setOrgShowPublicProfessionals)} +
+ +
+

Servicios

+ {renderVisibilityCheckbox("Mostrar puntuación pública de servicios", "Aplica este valor a todos los servicios de la organización.", servicesShowPublicScores, setServicesShowPublicScores)} + {renderVisibilityCheckbox("Mostrar opiniones públicas de servicios", "Aplica este valor a todos los servicios de la organización.", servicesShowPublicOpinions, setServicesShowPublicOpinions)} +
+
+
+ ); + }; + const renderFlow = () => { if (allOrganizations.length > 1 && !createdCompanyId && action !== "new-org") { return ( @@ -3294,6 +3426,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm if (flowId === "setup-collaborator") return renderSetupCollaboratorFlow(); if (flowId === "new-collaborator") return renderNewCollaboratorFlow(); + if (flowId === "public-visibility") return renderPublicVisibilityFlow(); if (flowId === "disable-schedule") return renderDisableScheduleFlow(); if (flowId === "override-schedule") return renderOverrideScheduleFlow(); if (flowId === "update-schedule") return renderUpdateScheduleFlow();