// portal-app.jsx — Main app: Login, navigation, Home, Perfil, Operator mode const { useState, useMemo } = React; // ── Lookup helpers ───────────────────────────────────────────────────────────── function findPatient(query) { const q = query.trim().toLowerCase(); return (window.PATIENTS || []).find(p => p.dni.replace(/-/g,'').includes(q.replace(/-/g,'')) || p.whatsapp.replace(/[^0-9]/g,'').includes(q.replace(/[^0-9]/g,'')) || p.nombre.toLowerCase().includes(q) ); } // ── LoginScreen ─────────────────────────────────────────────────────────────── function LoginScreen({ onLogin, onOperatorMode }) { const C = window.C; const [query, setQuery] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const handleSearch = () => { if (!query.trim()) return; setLoading(true); setError(''); setTimeout(() => { const patient = findPatient(query); setLoading(false); if (patient) { onLogin(patient); } else { setError('No encontramos ningún socio con ese DNI o número. ¿Estás inscrito en Club Santos?'); } }, 600); }; return (
{/* Header */}
S o s
Club Santos
Tu salud,
en tus manos
Monitorea tus vitales, sigue tus programas de canje y lleva el control de tus medicamentos.
{/* Card */}
Buscar mi cuenta
Ingresa tu DNI, WhatsApp o número de tarjeta Club Santos
{ setQuery(e.target.value); setError(''); }} onKeyDown={e => e.key === 'Enter' && handleSearch()} placeholder="0801-XXXX-XXXXX o +504 XXXX XXXX" style={{ flex: 1, padding: '12px 16px', borderRadius: 12, border: `1.5px solid ${error ? C.danger : C.border}`, fontFamily: 'inherit', fontSize: 14, color: C.text1, background: C.bg, outline: 'none' }} />
{error &&
{error}
}
Cuentas demo: { setQuery('0801-1972-04521'); }}>María García · setQuery('0801-1979-07765')}>Carlos Ortiz · setQuery('0804-1963-08834')}>Juan Hernández
); } // ── HomeTab ─────────────────────────────────────────────────────────────────── function HomeTab({ patient, enrollments, onTabChange }) { const C = window.C; const myEnr = enrollments.filter(e => e.customerId === patient.id); const readyCanjes = myEnr.filter(e => e.rewardsPending > 0); const inProgress = myEnr.filter(e => e.rewardsPending === 0 && (e.purchasesLogged + e.purchasesPending) > 0); const tierGoals = { BRONCE: 5000, PLATA: 15000, ORO: 40000, JADE: 99999, PLATINO: 99999 }; const goal = tierGoals[patient.tier] || 15000; const tierCol = C.tiers[patient.tier]; return (
{/* Welcome card */}
Bienvenida de nuevo
{patient.nombre.split(' ')[0]} {patient.nombre.split(' ')[1]}
{patient.puntos.toLocaleString()}
puntos
L. {patient.gastoAnual.toLocaleString()}
gasto anual
{patient.transacciones}
compras
{/* Tier progress */}
Progreso de nivel
L. {Math.max(goal - patient.gastoAnual, 0).toLocaleString()} para el siguiente
{/* Canje summary */} {readyCanjes.length > 0 && ( )} {inProgress.length > 0 && ( )} {/* Quick links */} ACCESOS RÁPIDOS
{[ { icon: 'Rx', label: 'Mis canjes', sub: `${myEnr.length} programas`, tab: 'canjes', color: C.primary }, { icon: '▦', label: 'Mis vitales', sub: 'Glucose, presión…', tab: 'vitales', color: '#E8743A' }, { icon: '●', label: 'Mi perfil', sub: 'Datos y medicación', tab: 'perfil', color: C.info }, { icon: '✆', label: 'WhatsApp', sub: 'Farmacia Santos', tab: 'wa', color: '#25D366' }, ].map(item => ( ))}
{/* Contact */}
Farmacia Santos, Copán Ruinas
Calle La Independencia · 8am–8pm diario
Llamar
); } // ── PerfilTab ───────────────────────────────────────────────────────────────── function PerfilTab({ patient, onLogout }) { const C = window.C; return (
{/* Profile header */}
{patient.iniciales}
{patient.nombre}
DATOS PERSONALES
{[ { label: 'DNI / Cédula', value: patient.dni }, { label: 'WhatsApp', value: patient.whatsapp }, { label: 'Email', value: patient.email }, { label: 'Edad', value: `${patient.edad} años` }, { label: 'Dirección', value: patient.direccion }, { label: 'Sucursal preferida', value: patient.sucursal.split('—')[0].trim() }, ].map((f, i, arr) => (
{f.label}
{f.value}
))}
{(patient.condiciones?.length > 0 || patient.alergias?.length > 0) && ( <> HISTORIAL CLÍNICO
{patient.condiciones?.length > 0 && (
CONDICIONES CRÓNICAS
{patient.condiciones.map((c, i) => ( {c} ))}
)} {patient.alergias?.length > 0 && (
! ALERGIAS
{patient.alergias.map((a, i) => ( {a} ))}
)} {patient.doctor &&
Médico tratante: {patient.doctor}
} {patient.seguro &&
Seguro médico: {patient.seguro}
}
)}
); } // ── OperatorLogin ───────────────────────────────────────────────────────────── function OperatorLogin({ onAccess, onBack }) { const C = window.C; const [pin, setPin] = useState(''); const [err, setErr] = useState(false); const check = () => { if (pin === '1234' || pin === 'santos') { onAccess(); } else { setErr(true); setTimeout(() => setErr(false), 1400); } }; return (
*
Modo Operador
Ingresa la contraseña de farmacia
setPin(e.target.value)} onKeyDown={e => e.key === 'Enter' && check()} placeholder="Contraseña" style={{ width: '100%', padding: '14px 18px', borderRadius: 14, border: `2px solid ${err ? C.danger : C.border}`, fontFamily: 'inherit', fontSize: 18, textAlign: 'center', letterSpacing: 4, marginBottom: 12, background: C.surface, color: C.text1, outline: 'none', transition: 'border-color .2s' }} /> {err &&
Contraseña incorrecta. Usa: 1234
}
Demo PIN: 1234
); } // ── App ──────────────────────────────────────────────────────────────────────── function App() { const C = window.C; const { CanjesTab, OperatorQueue, VitalesTab } = window; const [patient, setPatient] = useState(null); const [tab, setTab] = useState('home'); const [enrollments, setEnrollments] = useState(window.INITIAL_ENROLLMENTS || []); const [submissions, setSubmissions] = useState(window.INITIAL_SUBMISSIONS || []); const [vitals, setVitals] = useState(window.VITALS_DATA || {}); const [mode, setMode] = useState('patient'); // 'patient' | 'operator-login' | 'operator' // ── Canje actions ─────────────────────────────────────────────────────────── const handleAddEnrollment = (customerId, prog) => { const newEnr = { id: `e-${Date.now()}`, customerId, programId: prog.id, purchasesLogged: 0, purchasesPending: 0, rewardsEarned: 0, rewardsPending: 0, sinceDate: new Date().toISOString().split('T')[0] }; setEnrollments(prev => [...prev, newEnr]); }; const handleRegisterPurchase = (enrollmentId, date) => { const enr = enrollments.find(e => e.id === enrollmentId); if (!enr) return; const prog = (window.LAB_PROGRAMS || []).find(p => p.id === enr.programId); const newSub = { id: `s-${Date.now()}`, enrollmentId, customerId: enr.customerId, programId: enr.programId, labId: prog?.labId || '', date, submittedToPortal: false }; setSubmissions(prev => [...prev, newSub]); setEnrollments(prev => prev.map(e => e.id === enrollmentId ? { ...e, purchasesPending: e.purchasesPending + 1 } : e)); }; const handleMarkSubmitted = (subId) => { const sub = submissions.find(s => s.id === subId); if (!sub) return; const prog = (window.LAB_PROGRAMS || []).find(p => p.id === sub.programId); setSubmissions(prev => prev.map(s => s.id === subId ? { ...s, submittedToPortal: true } : s)); setEnrollments(prev => prev.map(e => { if (e.id !== sub.enrollmentId) return e; const newLogged = e.purchasesLogged + 1; const newPending = Math.max(e.purchasesPending - 1, 0); const thresholdHit = prog && newLogged >= prog.buyQty; return { ...e, purchasesLogged: thresholdHit ? 0 : newLogged, purchasesPending: newPending, rewardsEarned: e.rewardsEarned + (thresholdHit ? 1 : 0), rewardsPending: e.rewardsPending + (thresholdHit ? 1 : 0), }; })); }; const handleLogVital = (patientId, key, entry) => { setVitals(prev => ({ ...prev, [patientId]: { ...prev[patientId], [key]: [...(prev[patientId]?.[key] || []), entry] }, })); }; const pendingCount = submissions.filter(s => !s.submittedToPortal).length; // ── Operator mode ─────────────────────────────────────────────────────────── if (mode === 'operator-login') { return setMode('operator')} onBack={() => setMode('patient')} />; } if (mode === 'operator') { return (
{/* Operator header */}
Modo Operador
Lista de Registro — Portales
{pendingCount} envío{pendingCount !== 1 ? 's' : ''} pendiente{pendingCount !== 1 ? 's' : ''} — Copán Ruinas · {new Date().toLocaleDateString('es-HN', { weekday: 'long', day: 'numeric', month: 'long' })}
); } // ── Patient not logged in ─────────────────────────────────────────────────── if (!patient) { return { setPatient(p); setTab('home'); }} onOperatorMode={() => setMode('operator-login')} />; } // ── Patient portal ────────────────────────────────────────────────────────── const NAV = [ { id: 'home', icon: '⌂', label: 'Inicio' }, { id: 'canjes', icon: '✦', label: 'Canjes' }, { id: 'vitales', icon: '▦', label: 'Vitales' }, { id: 'perfil', icon: '●', label: 'Perfil' }, ]; const myEnr = enrollments.filter(e => e.customerId === patient.id); const readyCount = myEnr.filter(e => e.rewardsPending > 0).length; return (
{/* Brand header */}
S o s
Club Santos
{patient.nombre.split(' ')[0]}
{patient.sucursal.split('—')[0].trim()}
{patient.iniciales}
{/* Content */}
{tab === 'home' && } {tab === 'canjes' && } {tab === 'vitales' && } {tab === 'perfil' && { setPatient(null); setTab('home'); }} />}
{/* Bottom nav */}
{NAV.map(n => { const active = tab === n.id; const badge = n.id === 'canjes' && readyCount > 0; return ( ); })}
); } ReactDOM.createRoot(document.getElementById('root')).render();