UX Pacientes: drawer de ficha clicable global, cumpleanos en dashboard/agenda, badges VIP/alergias/adeudo en ficha y POS

This commit is contained in:
2026-09-01 22:26:09 +00:00
parent 7261da3ab1
commit 2710d9d92d
9 changed files with 492 additions and 20 deletions

View File

@@ -5,6 +5,7 @@ import {
Search, Plus, Minus, X, Store, AlertTriangle, ShoppingBag, Star, Printer, Wallet, Trash2, Package,
} from 'lucide-react';
import Layout from '../components/Layout';
import { usePatientDrawer } from '../components/PatientDrawer';
import {
Card,
Button,
@@ -44,6 +45,7 @@ interface TicketLine {
}
const Pos: FC = () => {
const { openPatient } = usePatientDrawer();
// Catálogo
const [catMode, setCatMode] = useState<'servicios' | 'articulos'>('servicios');
const [services, setServices] = useState<Service[]>([]);
@@ -57,6 +59,7 @@ const Pos: FC = () => {
const [lines, setLines] = useState<TicketLine[]>([]);
const [discount, setDiscount] = useState('');
const [paciente, setPaciente] = useState<Patient | null>(null);
const [pacienteAdeudo, setPacienteAdeudo] = useState(0);
const [pacienteQuery, setPacienteQuery] = useState('');
const [pacienteResults, setPacienteResults] = useState<Patient[]>([]);
const [altaRapida, setAltaRapida] = useState(false);
@@ -310,28 +313,71 @@ const Pos: FC = () => {
/>
);
// Al seleccionar paciente: traer su adeudo para el badge de alerta en caja
const selectPaciente = (p: Patient) => {
setPaciente(p);
setPacienteResults([]);
setPacienteAdeudo(0);
odooApi.getPatientHistory(p.id)
.then((res) => { if (res.status === 'success') setPacienteAdeudo(res.estado_cuenta?.adeudo ?? 0); })
.catch(() => {});
};
const nuevaVenta = () => {
setResultado(null);
setLines([]);
setDiscount('');
setPaciente(null);
setPacienteAdeudo(0);
setPacienteQuery('');
setDoctorId('');
};
// ---- Bloques del ticket (desktop sticky y modal móvil) ----
const pacienteBlock = paciente ? (
<div className="flex items-center justify-between p-2.5 bg-theme-bg rounded-xl">
<div className="min-w-0">
<p className="text-sm font-medium text-theme-heading truncate">{paciente.name}</p>
<p className="text-xs text-theme-muted flex items-center gap-1.5">
{paciente.phone}
{puntosPaciente > 0 && (
<span className="inline-flex items-center gap-0.5"><Wallet size={11} /> {puntosPaciente} pts</span>
)}
</p>
<div className="p-2.5 bg-theme-bg rounded-xl space-y-1.5">
<div className="flex items-center justify-between">
<div className="min-w-0">
<button
type="button"
onClick={() => openPatient(paciente.id)}
className="text-sm font-medium text-theme-heading truncate hover:underline hover:text-theme-accent"
title="Abrir ficha del paciente"
>
{paciente.name}
</button>
<p className="text-xs text-theme-muted flex items-center gap-1.5">
{paciente.phone}
{puntosPaciente > 0 && (
<span className="inline-flex items-center gap-0.5"><Wallet size={11} /> {puntosPaciente} pts</span>
)}
</p>
</div>
<Button variant="ghost" size="sm" onClick={() => { setPaciente(null); setPacienteAdeudo(0); setPacienteQuery(''); }}>Cambiar</Button>
</div>
<Button variant="ghost" size="sm" onClick={() => { setPaciente(null); setPacienteQuery(''); }}>Cambiar</Button>
{/* Alertas para caja antes de cobrar: VIP / Alergias / Adeudo */}
{(paciente.is_vip || paciente.allergies || pacienteAdeudo > 0) && (
<div className="flex flex-wrap gap-1.5">
{paciente.is_vip && (
<span className="inline-flex items-center gap-1 rounded-full border border-theme-accent bg-theme-accent-bg px-2 py-0.5 text-[11px] font-medium text-theme-heading">
<Star size={11} /> VIP
</span>
)}
{paciente.allergies && (
<span
className="inline-flex items-center gap-1 rounded-full border border-rose-300 bg-rose-50 px-2 py-0.5 text-[11px] font-medium text-rose-700"
title={`Alergias: ${paciente.allergies}`}
>
<AlertTriangle size={11} /> Alergias
</span>
)}
{pacienteAdeudo > 0 && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700">
Adeudo {fmtMoney(pacienteAdeudo)}
</span>
)}
</div>
)}
</div>
) : altaRapida ? (
<div className="p-2.5 bg-theme-bg rounded-xl space-y-2">
@@ -357,7 +403,7 @@ const Pos: FC = () => {
<button
key={p.id}
type="button"
onClick={() => { setPaciente(p); setPacienteResults([]); }}
onClick={() => selectPaciente(p)}
className="w-full text-left px-3 py-2 text-sm hover:bg-theme-bg transition"
>
<span className="font-medium text-theme-heading">{p.name}</span>