import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Phone, MapPin, Mail, Globe, Save, Building2, ArrowRightLeft, Percent, Check } from 'lucide-react'; import Layout from '../components/Layout'; import UsuariosPanel from '../components/UsuariosPanel'; import RolesPanel from '../components/RolesPanel'; import RecetasPanel from '../components/RecetasPanel'; import CatalogoPanel from '../components/CatalogoPanel'; import { Card, Button, Input, PageHeader, toast } from '../components/ui'; import { odooApi, type Doctor } from '../services/odoo'; const TABS = [ { key: 'clinica', label: 'Clínica' }, { key: 'usuarios', label: 'Usuarios' }, { key: 'roles', label: 'Roles' }, { key: 'recetas', label: 'Recetas' }, { key: 'catalogos', label: 'Catálogos' }, ] as const; const SUBCATALOGOS = [ { key: 'diagnosticos', label: 'Diagnósticos' }, { key: 'procedimientos', label: 'Procedimientos' }, ] as const; interface ClinicSettings { name: string; phone: string; email: string; address: string; website: string; } const defaultSettings: ClinicSettings = { name: 'SKEEN Derma Experts', phone: '+52 (661) 100-2172', email: 'hola@skeen.mx', address: 'Playas de Rosarito, Baja California', website: 'https://skeen.mx', }; const STORAGE_KEY = 'skeen_clinic_settings'; const Configuracion: FC = () => { const [searchParams, setSearchParams] = useSearchParams(); const tab = searchParams.get('tab') || 'clinica'; const setTab = (t: string) => setSearchParams({ tab: t }); const [subcatalogo, setSubcatalogo] = useState(() => { const sub = searchParams.get('sub'); return sub === 'procedimientos' ? 'procedimientos' : 'diagnosticos'; }); const [settings, setSettings] = useState(defaultSettings); const [loading, setLoading] = useState(true); useEffect(() => { try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) setSettings(JSON.parse(saved)); } catch { // ignore } finally { setLoading(false); } }, []); const save = () => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); toast.success('Configuración guardada'); } catch { toast.error('Error al guardar configuración'); } }; const update = (field: keyof ClinicSettings, value: string) => { setSettings((prev) => ({ ...prev, [field]: value })); }; // ---- Tipo de cambio USD/MXN ---- const [tcCurrent, setTcCurrent] = useState<{ rate: number; date: string; source: string } | null>(null); const [tcInput, setTcInput] = useState(''); const [tcSaving, setTcSaving] = useState(false); useEffect(() => { odooApi.getExchangeRate() .then((res) => { if (res.current) { setTcCurrent(res.current); setTcInput(String(res.current.rate)); } }) .catch(() => {/* odoo no disponible, ignorar */}); }, []); const saveTc = async () => { const rate = parseFloat(tcInput); if (!rate || rate <= 0) { toast.error('Ingresa un tipo de cambio válido'); return; } setTcSaving(true); try { const res = await odooApi.setExchangeRate(rate, 'manual'); setTcCurrent(res.exchange_rate); toast.success(`Tipo de cambio actualizado: ${rate.toFixed(2)} MXN/USD`); } catch { toast.error('No se pudo guardar el tipo de cambio'); } finally { setTcSaving(false); } }; // ---- Comisiones de médicos ---- const [commissionDefault, setCommissionDefault] = useState(''); const [commissionDefaultSaving, setCommissionDefaultSaving] = useState(false); const [doctors, setDoctors] = useState([]); const [commissionInputs, setCommissionInputs] = useState>({}); const [commissionSavingId, setCommissionSavingId] = useState(null); const [commissionSavedId, setCommissionSavedId] = useState(null); useEffect(() => { odooApi.getCommissionDefault() .then((res) => setCommissionDefault(String(res.commission_pct ?? 0))) .catch(() => {/* odoo no disponible, ignorar */}); odooApi.getDoctors() .then((res) => { if (res.status === 'success') { setDoctors(res.doctors); setCommissionInputs(Object.fromEntries(res.doctors.map((d) => [d.id, String(d.commission_pct ?? 0)]))); } }) .catch(() => {/* odoo no disponible, ignorar */}); }, []); const validPct = (raw: string): number | null => { const pct = parseFloat(raw); if (Number.isNaN(pct) || pct < 0 || pct > 100) return null; return pct; }; const saveCommissionDefault = async () => { const pct = validPct(commissionDefault); if (pct === null) { toast.error('El porcentaje debe estar entre 0 y 100'); return; } setCommissionDefaultSaving(true); try { await odooApi.setCommissionDefault(pct); toast.success(`Comisión global actualizada: ${pct}%`); } catch { toast.error('No se pudo guardar la comisión global'); } finally { setCommissionDefaultSaving(false); } }; const saveDoctorCommission = async (id: number) => { const pct = validPct(commissionInputs[id] ?? ''); if (pct === null) { toast.error('El porcentaje debe estar entre 0 y 100'); return; } setCommissionSavingId(id); try { await odooApi.setDoctorCommission(id, pct); setDoctors((prev) => prev.map((d) => (d.id === id ? { ...d, commission_pct: pct } : d))); setCommissionSavedId(id); setTimeout(() => setCommissionSavedId((cur) => (cur === id ? null : cur)), 2000); } catch { toast.error('No se pudo guardar la comisión del médico'); } finally { setCommissionSavingId(null); } }; return ( {/* Tabs de secciones */}
{TABS.map((t) => ( ))}
{tab === 'usuarios' ? ( ) : tab === 'roles' ? ( ) : tab === 'recetas' ? ( ) : tab === 'catalogos' ? (
{SUBCATALOGOS.map((s) => ( ))}
) : (

Información de la clínica

Ajustes

{loading ? (

Cargando...

) : (
update('name', e.target.value)} /> update('phone', e.target.value)} /> update('email', e.target.value)} /> update('address', e.target.value)} /> update('website', e.target.value)} />
)}

Tipo de cambio USD / MXN

Se usa para convertir ventas en dólares. Vigente: {tcCurrent ? ( {Number(tcCurrent.rate).toFixed(2)} MXN por 1 USD ({tcCurrent.date}) ) : ( sin definir )}

setTcInput(e.target.value)} placeholder="17.25" />

Comisiones

Porcentaje de comisión sobre las ventas atribuidas a cada médico (visible en su página "Mis ventas").

setCommissionDefault(e.target.value)} placeholder="0" />

Se aplica a médicos sin porcentaje propio (con 0%).

{doctors.map((d) => ( ))} {doctors.length === 0 && ( )}
Médico Tipo % actual Nuevo % Acción
{d.name} {(d.staff_type ?? 'doctor') === 'cosmetologa' ? 'Cosmetóloga' : 'Doctor'} {d.commission_pct ? `${d.commission_pct}%` : '0% (usa el global)'} setCommissionInputs((prev) => ({ ...prev, [d.id]: e.target.value }))} /> {commissionSavedId === d.id ? ( Guardado ) : ( )}
No hay médicos dados de alta.
)}
); }; export default Configuracion;