Corte de caja asistido (esperado vs contado, diferencias, PDF) y fotos antes/despues con comparacion en expediente

This commit is contained in:
2026-09-08 07:27:57 +00:00
parent e8690dd74e
commit 4f9a16103f
7 changed files with 672 additions and 81 deletions

View File

@@ -0,0 +1,115 @@
import type { FC } from 'react';
import { Printer } from 'lucide-react';
import { Button, Modal } from './ui';
import type { CashClosing } from '../services/odoo';
// Corte de caja imprimible. Sigue el patrón de impresión existente
// (RecetaPrint / EstadoCuentaPrint / TicketPrint): contenido en un
// div.print-ticket + window.print(); el CSS de @media print en index.css
// oculta el resto de la página.
interface ClinicSettings {
name: string;
phone: string;
address: string;
}
const clinicSettings = (): ClinicSettings => {
try {
const saved = localStorage.getItem('skeen_clinic_settings');
if (saved) return JSON.parse(saved);
} catch {
// ignore
}
return { name: 'SKEEN Derma Experts', phone: '', address: '' };
};
const money = (v: number) =>
'$' + (v ?? 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
export const CortePrint: FC<{ corte: CashClosing | null; onClose: () => void }> = ({ corte, onClose }) => {
if (!corte) return null;
const clinic = clinicSettings();
const fila = (label: string, valor: number) => (
<tr>
<td className="py-1 pr-2">{label}</td>
<td className="py-1 text-right">{money(valor)}</td>
</tr>
);
const filaDiff = (label: string, esperado: number, contado: number, diff: number) => (
<tr>
<td className="py-1 pr-2">{label}</td>
<td className="py-1 text-right">{money(esperado)}</td>
<td className="py-1 text-right">{money(contado)}</td>
<td className={'py-1 text-right font-semibold ' + (diff < 0 ? 'text-rose-700' : diff > 0 ? 'text-emerald-700' : '')}>
{diff > 0 ? '+' : ''}{money(diff)}
</td>
</tr>
);
return (
<Modal isOpen={!!corte} onClose={onClose} title={`Corte de caja ${corte.name}`} maxWidth="lg">
<div className="print-ticket bg-white text-[#222] p-5 rounded-xl border border-theme-border text-[13px] leading-snug">
<div className="text-center mb-3">
<p className="font-bold text-[15px]">{clinic.name}</p>
{clinic.address && <p className="text-[11px] text-[#555]">{clinic.address}</p>}
{clinic.phone && <p className="text-[11px] text-[#555]">{clinic.phone}</p>}
<p className="font-semibold mt-1">CORTE DE CAJA {corte.name}</p>
<p className="text-[11px] text-[#555]">Fecha: {corte.date} · Cerró: {corte.user || '—'}</p>
</div>
<table className="w-full text-[12px]">
<thead>
<tr className="border-b border-[#ccc] text-[10px] uppercase text-[#666]">
<th className="text-left py-1">Método</th>
<th className="text-right py-1">Esperado</th>
<th className="text-right py-1">Contado</th>
<th className="text-right py-1">Diferencia</th>
</tr>
</thead>
<tbody className="border-b border-[#ccc]">
{filaDiff('Efectivo', corte.opening_cash + corte.total_cash, corte.counted_cash, corte.diff_cash)}
{filaDiff('Tarjeta', corte.total_card, corte.counted_card, corte.diff_card)}
{filaDiff('Transferencia', corte.total_transfer, corte.counted_transfer, corte.diff_transfer)}
{filaDiff('Otros', corte.total_other, corte.counted_other, corte.diff_other)}
</tbody>
<tbody>
{fila('Ventas totales del día', corte.total_sales)}
{fila('Efectivo esperado (apertura + cobros)', corte.opening_cash + corte.total_cash)}
{fila('Efectivo de cierre registrado', corte.closing_cash)}
</tbody>
<tbody className="border-t border-[#ccc]">
<tr className="font-bold">
<td className="py-1.5 pr-2">Diferencia total del corte</td>
<td className={'py-1.5 text-right ' + (corte.difference < 0 ? 'text-rose-700' : corte.difference > 0 ? 'text-emerald-700' : '')}>
{corte.difference > 0 ? '+' : ''}{money(corte.difference)}
</td>
</tr>
</tbody>
</table>
{corte.notes && (
<div className="mt-3 text-[12px]">
<p className="font-semibold">Notas:</p>
<p className="whitespace-pre-wrap">{corte.notes}</p>
</div>
)}
<div className="mt-6 flex justify-between text-[11px] text-[#555]">
<span>_____________________<br />Entregó (cajero)</span>
<span>_____________________<br />Recibió (administración)</span>
</div>
<p className="mt-4 text-center text-[10px] text-[#888]">Impreso el {new Date().toLocaleString('es-MX')}</p>
</div>
<div className="flex justify-end mt-3">
<Button onClick={() => window.print()}>
<Printer size={14} className="mr-1.5" />
Imprimir
</Button>
</div>
</Modal>
);
};