// Yomee — Profile Screen ("Mi perfil") // Entered from the avatar in the Radiografía header. Covers RF-033 → RF-038: // • Mi información (RF-033) // • Notificaciones (RF-034) // • Privacidad y seguridad (RF-035) // • Mi suscripción (RF-036) // • Términos y privacidad (RF-037) // • Cerrar sesión (RF-038) // // Uses the project's existing design tokens (var(--yo-*) + Onest/Geist Mono). // Profile chip colors map to the 4 personas already defined elsewhere // (Totalero / Optimizador / Escalador / Reiniciador). // // All sub-screens (suscripción, info, notificaciones, privacidad, legales) // live in this same file as small inner views, switched via local state. // They share a single PageShell with a sticky back-header. /* global React, RadioIcon, useYomeeUser, BottomSheetModal, TERMS_BODY, PRIVACY_BODY, AyudaScreen */ const { useState: useStatePF, useEffect: useEffectPF } = React; // Body background — matches Radiografía body so the transition feels seamless. const PF_BG = "rgb(238, 235, 230)"; const PF_FG = "var(--yo-ink)"; const PF_FG_MUTED = "#6b6b6b"; const PF_FG_SOFT = "#9aa0a6"; const PF_LINE = "rgba(20,32,28,0.08)"; const PF_BRAND = "var(--yo-forest-700)"; // #19553e — primary brand action const PF_DANGER = "var(--yo-red-strong)"; // #dc4046 — destructive const PF_IVORY = "#fbf7ee"; // upgrade / privacy banner // Profile chip colors — match RADIO_HEADER from RadiografiaScreen.jsx const PROFILE_CHIP = { totalero: { bg: "#1a6b3f", label: "Totalero" }, optimizador: { bg: "#185fa5", label: "Optimizador" }, escalador: { bg: "#7c2d12", label: "Escalador" }, reiniciador: { bg: "#4c1d95", label: "Reiniciador" }, }; // Subscription tiers per persona (mocked). const TIER_BY_PERSONA = { ana: { tier: "Free", price: null, cta: "Mejora tu plan" }, luis: { tier: "Plus", price: "$99 MXN/mes", cta: null }, carlos: { tier: "Free", price: null, cta: "Mejora tu plan" }, sofia: { tier: "Pro", price: "$199 MXN/mes", cta: null }, }; // ──────────────────────────────────────────────────────────────────── // Reusable building blocks (scoped to this file so they don't collide) // ──────────────────────────────────────────────────────────────────── function PFAvatar({ initials, size = 64, fontSize = 22, bg }) { return ( ); } function PFCard({ children, style }) { return (
{children}
); } // Section row — icon + title (+ optional subtitle) + chevron. function PFRow({ icon, title, subtitle, subtitleColor, onClick, last, danger }) { const [pressed, setPressed] = useStatePF(false); return ( ); } // Sticky header with back arrow + title. function PFHeader({ title, onBack }) { return (

{title}

); } function PageShell({ title, onBack, children }) { return (
{children}
); } // ──────────────────────────────────────────────────────────────────── // Inline icons — kept tiny and consistent (24px stroke, 1.6 weight) // We don't reuse RadioIcon here because the section icons are distinct // from the dashboard's categorical iconography. // ──────────────────────────────────────────────────────────────────── function PFIcon({ name }) { const props = { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", }; switch (name) { case "star": return ( ); case "user": return ( ); case "bell": return ( ); case "shield": return ( ); case "help": return ( ); case "doc": return ( ); case "trash": return ( ); case "external": return ( ); case "logout": return ( ); default: return null; } } // ──────────────────────────────────────────────────────────────────── // Identity hero card — used in the main /perfil view // ──────────────────────────────────────────────────────────────────── function IdentityCard({ user, profileKey, onEdit }) { const chip = PROFILE_CHIP[profileKey] || PROFILE_CHIP.totalero; return (
{user.name}
{user.email}
{chip.label}
Tu perfil se actualiza conforme avanzas. Cada estado de cuenta nuevo refina tu radiografía.
); } // ──────────────────────────────────────────────────────────────────── // Logout confirmation modal // ──────────────────────────────────────────────────────────────────── function LogoutModal({ onCancel, onConfirm }) { return (
¿Cerrar sesión?
Tendrás que volver a iniciar sesión la próxima vez.
); } // ──────────────────────────────────────────────────────────────────── // MAIN PROFILE VIEW // ──────────────────────────────────────────────────────────────────── function MainProfileView({ user, profileKey, tier, notifsOn, onNav, onLogout }) { return (
onNav("info")} /> {/* SECTIONS LIST */} } title="Mi suscripción" subtitle={ tier.tier === "Free" ? "Plan Free · Mejora tu plan" : `Plan ${tier.tier} · ${tier.price}` } subtitleColor={tier.tier === "Free" ? PF_BRAND : PF_FG_MUTED} onClick={() => onNav("suscripcion")} /> } title="Mi información" subtitle="Nombre, email, ciudad" onClick={() => onNav("info")} /> } title="Notificaciones" subtitle={notifsOn ? "Activadas" : "Desactivadas"} onClick={() => onNav("notificaciones")} /> } title="Privacidad y seguridad" subtitle="Tus datos están protegidos" onClick={() => onNav("privacidad")} /> } title="Centro de ayuda" subtitle="Preguntas frecuentes y soporte" onClick={() => onNav("ayuda")} last /> {/* LOGOUT */}
{/* VERSION FOOTER */}
yomee.ai · v1.0.0 · Hecho con 🦔 en México
); } function LogoutButton({ onClick }) { const [pressed, setPressed] = useStatePF(false); return ( ); } // ──────────────────────────────────────────────────────────────────── // SUB-VIEW: Mi suscripción (RF-036) // ──────────────────────────────────────────────────────────────────── const TIER_BENEFITS = { Free: [ "1 estado de cuenta al mes", "Radiografía básica", "Detección de oportunidades", ], Plus: [ "Hasta 5 estados de cuenta al mes", "Radiografía completa multibanco", "Recomendaciones personalizadas", "Recordatorios de pago", ], Pro: [ "Estados de cuenta ilimitados", "Plan de consolidación con Pocopin", "Análisis de patrones avanzados", "Soporte prioritario", ], }; const NEXT_TIER = { Free: "Plus", Plus: "Pro", Pro: null }; const TIER_PRICE = { Free: "Gratis", Plus: "$99 MXN/mes", Pro: "$199 MXN/mes" }; function SuscripcionView({ tier, onBack }) { const next = NEXT_TIER[tier.tier]; const benefits = TIER_BENEFITS[tier.tier] || []; const nextBenefits = next ? TIER_BENEFITS[next] || [] : []; return ( {/* Current tier card */}
Tu plan actual
{tier.tier} · {TIER_PRICE[tier.tier]}
{/* Upgrade card (ivory) */} {next && (
Sigue creciendo · {next}
Lo que sumas con {next}
{TIER_PRICE[next]} · Cancela cuando quieras
)} {/* Payment history */}
Historial de pagos
{tier.tier === "Free" ? (
Aún no tienes pagos. Tu plan {tier.tier} no tiene cargo.
) : ( [ { date: "01 Oct 2025", amount: tier.price }, { date: "01 Sep 2025", amount: tier.price }, { date: "01 Ago 2025", amount: tier.price }, ].map((row, i, arr) => (
{row.date} {row.amount}
)) )}
); } // ──────────────────────────────────────────────────────────────────── // SUB-VIEW: Mi información (RF-033) // ──────────────────────────────────────────────────────────────────── function InfoView({ user, onBack }) { const [name, setName] = useStatePF(user.name || ""); const [city, setCity] = useStatePF("CDMX"); const [dob, setDob] = useStatePF("12 / 03 / 1992"); return (
); } function Field({ label, value, onChange, readOnly, last }) { return (
{label}
onChange && onChange(e.target.value)} style={{ marginTop: 4, width: "100%", background: "transparent", border: "none", padding: 0, fontFamily: "var(--font-body)", fontSize: 15, fontWeight: 400, color: readOnly ? PF_FG_MUTED : PF_FG, letterSpacing: "0.005em", outline: "none", }} />
); } // ──────────────────────────────────────────────────────────────────── // SUB-VIEW: Notificaciones (RF-034) // ──────────────────────────────────────────────────────────────────── const NOTIF_OPTIONS = [ { key: "carga", title: "Recordatorio de carga mensual", desc: "Avisos para subir tu estado de cuenta cada mes.", def: true, }, { key: "oportunidades", title: "Oportunidades de mercado", desc: "Cuando detectamos algo que puede ayudarte a ahorrar.", def: true, }, { key: "novedades", title: "Novedades de yomee", desc: "Nuevas funciones y mejoras en la app.", def: false, }, { key: "pagos", title: "Recordatorios de pago próximo", desc: "Antes de la fecha límite para que no se te olvide.", def: true, }, ]; function NotificacionesView({ values, setValues, onBack }) { return ( {NOTIF_OPTIONS.map((opt, i, arr) => ( setValues({ ...values, [opt.key]: v })} last={i === arr.length - 1} /> ))}
Puedes cambiar tus preferencias en cualquier momento. No usamos tus datos para anuncios.
); } function ToggleRow({ title, desc, value, onChange, last }) { return (
{title}
{desc}
); } // ──────────────────────────────────────────────────────────────────── // SUB-VIEW: Privacidad y seguridad (RF-035) // ──────────────────────────────────────────────────────────────────── function PrivacidadView({ onBack, onOpenLegal, onDeleteAccount }) { return ( {/* Ivory banner */}
Tus estados de cuenta originales se eliminan en cuanto procesamos los datos (NIST 800-88). Solo guardamos los números, nunca el documento.
{/* 3 rows: Términos, Aviso de privacidad, Eliminar cuenta */} } title="Términos y condiciones" onClick={() => onOpenLegal("terms")} /> } title="Aviso de privacidad" onClick={() => onOpenLegal("privacy")} /> } title="Eliminar cuenta" danger onClick={onDeleteAccount} last />
); } // ──────────────────────────────────────────────────────────────────── // SUB-VIEW: Términos y aviso de privacidad (RF-037) // ──────────────────────────────────────────────────────────────────── function LegalesView({ onBack }) { return ( } title="Términos de uso" subtitle="Condiciones del servicio" onClick={() => {}} /> } title="Aviso de privacidad LFPDPPP" subtitle="Cómo tratamos tus datos personales" onClick={() => {}} /> } title="Licencias" subtitle="Software de terceros" onClick={() => {}} last /> ); } // ──────────────────────────────────────────────────────────────────── // Generic placeholder sub-view (Centro de ayuda + privacy children) // ──────────────────────────────────────────────────────────────────── function PlaceholderView({ title, body, onBack }) { return (
{body}
); } // ──────────────────────────────────────────────────────────────────── // Top-level ProfileScreen — owns the in-section route + logout modal // ──────────────────────────────────────────────────────────────────── function ProfileScreen({ onBack, onLoggedOut }) { // Active persona drives identity + chip color + tier. const yomeeUser = (window.useYomeeUser && window.useYomeeUser()) || {}; const personaKey = yomeeUser.personaKey || "ana"; const profileKey = yomeeUser.profile || "totalero"; const tier = TIER_BY_PERSONA[personaKey] || TIER_BY_PERSONA.ana; // In-section sub-route (lives inside ProfileScreen so the parent route // stays "perfil" and the back button on the main view returns to the // dashboard, not to a stale sub-view). const [sub, setSub] = useStatePF("main"); // Legal modal opened from Privacidad: "terms" | "privacy" | null. // Reuses the same BottomSheetModal + TERMS_BODY / PRIVACY_BODY shipped // from Shared.jsx and used by sign-up/sign-in. const [legal, setLegal] = useStatePF(null); const [showLogout, setShowLogout] = useStatePF(false); // Notification toggles — local state, persisted only for the session. const [notifs, setNotifs] = useStatePF(() => { const init = {}; NOTIF_OPTIONS.forEach((o) => (init[o.key] = o.def)); return init; }); const notifsOn = Object.values(notifs).some(Boolean); // Keep the inner route consistent if persona switches mid-flow. useEffectPF(() => { setSub("main"); setLegal(null); setShowLogout(false); }, [personaKey]); // "Eliminar cuenta" — provisional: opens yomee.ai in a new tab. function handleDeleteAccount() { window.open("https://www.yomee.ai", "_blank"); } function handleLogoutConfirm() { setShowLogout(false); if (onLoggedOut) onLoggedOut(); } return (
{sub === "main" && ( setSub(k)} onLogout={() => setShowLogout(true)} /> )} {sub === "suscripcion" && ( setSub("main")} /> )} {sub === "info" && ( setSub("main")} /> )} {sub === "notificaciones" && ( setSub("main")} /> )} {sub === "privacidad" && ( setSub("main")} onOpenLegal={(k) => setLegal(k)} onDeleteAccount={handleDeleteAccount} /> )} {sub === "ayuda" && ( setSub("main")} /> )} {/* Reused legal modals — same components/copy as sign-up/sign-in. */} setLegal(null)} > {TERMS_BODY} setLegal(null)} > {PRIVACY_BODY} {showLogout && ( setShowLogout(false)} onConfirm={handleLogoutConfirm} /> )}
); } window.ProfileScreen = ProfileScreen;