import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Download, BarChart3 } from 'lucide-react'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, } from 'recharts'; import Layout from '../components/Layout'; import DailyReport from '../components/reportes/DailyReport'; import CortesReport from '../components/reportes/CortesReport'; import IngresosReport from '../components/reportes/IngresosReport'; import InventarioReport from '../components/reportes/InventarioReport'; import AdeudosReport from '../components/reportes/AdeudosReport'; import ComisionesReport from '../components/reportes/ComisionesReport'; import PagosServiciosReport from '../components/reportes/PagosServiciosReport'; import PagosClientesReport from '../components/reportes/PagosClientesReport'; import DevolucionesReport from '../components/reportes/DevolucionesReport'; import TopClientesReport from '../components/reportes/TopClientesReport'; import ExportarReport from '../components/reportes/ExportarReport'; import HorasAgendaReport from '../components/reportes/HorasAgendaReport'; import PaquetesReport from '../components/reportes/PaquetesReport'; import VendedoresReport from '../components/reportes/VendedoresReport'; import ConcentradoReport from '../components/reportes/ConcentradoReport'; import RecomendacionesReport from '../components/reportes/RecomendacionesReport'; import KpisReport from '../components/reportes/KpisReport'; import { Card, Button, Input, PageHeader, Skeleton, EmptyState, toast } from '../components/ui'; import { odooApi } from '../services/odoo'; import type { CommissionRow } from '../services/odoo'; import { exportToExcel } from '../lib/exporter'; const COLORS = ['#57534e', '#a8a29e', '#d6d3d1', '#78716c', '#f59e0b', '#10b981']; const TABS = [ { key: 'general', label: 'General' }, { key: 'diario', label: 'Movimientos diarios' }, { key: 'cortes', label: 'Cortes de caja' }, { key: 'ingresos', label: 'Ingresos' }, { key: 'inventario', label: 'Inventario' }, { key: 'adeudos', label: 'Adeudos' }, { key: 'comisiones', label: 'Comisiones' }, { key: 'pagos-servicios', label: 'Pagos · Servicios' }, { key: 'pagos-clientes', label: 'Pagos · Clientes' }, { key: 'devoluciones', label: 'Devoluciones' }, { key: 'top-clientes', label: 'Top clientes' }, { key: 'exportar', label: 'Exportar' }, { key: 'horas-agenda', label: 'Horas agenda' }, { key: 'paquetes', label: 'Paquetes' }, { key: 'vendedores', label: 'Vendedores' }, { key: 'concentrado', label: 'Concentrado' }, { key: 'recomendaciones', label: 'Recomendaciones' }, { key: 'kpis', label: 'KPIs' }, ] as const; interface SalesReport { total_sales: number; total_paid: number; total_due: number; count: number; } interface AppointmentsReport { total: number; by_state: Record; } interface CashReport { total: number; by_method: Record; } const Reportes: FC = () => { const [searchParams, setSearchParams] = useSearchParams(); const tab = searchParams.get('tab') || 'general'; const setTab = (t: string) => setSearchParams({ tab: t }); const [start, setStart] = useState(() => { const d = new Date(); d.setDate(1); return d.toISOString().split('T')[0]; }); const [end, setEnd] = useState(() => new Date().toISOString().split('T')[0]); const [today, setToday] = useState(() => new Date().toISOString().split('T')[0]); const [salesReport, setSalesReport] = useState(null); const [appointmentsReport, setAppointmentsReport] = useState(null); const [cashReport, setCashReport] = useState(null); const [commissions, setCommissions] = useState([]); const [commTotals, setCommTotals] = useState<{ base: number; commission: number }>({ base: 0, commission: 0 }); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (tab !== 'general') return; const load = async () => { try { setLoading(true); setError(null); const [salesRes, aptRes, cashRes, commRes] = await Promise.all([ odooApi.getSalesReport(start, end), odooApi.getAppointmentsReport(start, end), odooApi.getCashReport(today), odooApi.getCommissions(start, end), ]); if (salesRes.status === 'success') setSalesReport({ total_sales: salesRes.total_sales, total_paid: salesRes.total_paid, total_due: salesRes.total_due, count: salesRes.count, }); if (aptRes.status === 'success') setAppointmentsReport({ total: aptRes.total, by_state: aptRes.by_state }); if (cashRes.status === 'success') setCashReport({ total: cashRes.total, by_method: cashRes.by_method }); if (commRes.status === 'success') { setCommissions(commRes.commissions || []); setCommTotals({ base: commRes.total_base || 0, commission: commRes.total_commission || 0 }); } } catch (err) { setError('Error al cargar reportes'); toast.error('Error al cargar reportes'); console.error(err); } finally { setLoading(false); } }; load(); }, [start, end, today, tab]); const aptData = appointmentsReport ? Object.entries(appointmentsReport.by_state).map(([name, value]) => ({ name, value })) : []; const cashData = cashReport ? Object.entries(cashReport.by_method).map(([name, value]) => ({ name, value })) : []; const [exporting, setExporting] = useState(false); const exportExcel = async () => { try { setExporting(true); const rows: Record[] = []; if (salesReport) { rows.push({ seccion: 'Ventas', concepto: 'Total', cantidad: null, monto: salesReport.total_sales }); rows.push({ seccion: 'Ventas', concepto: 'Pagado', cantidad: null, monto: salesReport.total_paid }); rows.push({ seccion: 'Ventas', concepto: 'Por cobrar', cantidad: null, monto: salesReport.total_due }); rows.push({ seccion: 'Ventas', concepto: 'Cantidad', cantidad: salesReport.count, monto: null }); } aptData.forEach((row) => rows.push({ seccion: 'Citas por estado', concepto: row.name, cantidad: row.value, monto: null })); cashData.forEach((row) => rows.push({ seccion: 'Efectivo por método', concepto: row.name, cantidad: null, monto: row.value })); commissions.forEach((c) => rows.push({ seccion: 'Comisiones', concepto: c.doctor, cantidad: c.items, monto: c.commission })); await exportToExcel({ filename: `reporte-skeen-${start}_${end}`, sheetName: 'General', title: 'Reporte general SKEEN', subtitle: `Rango ${start} a ${end} · efectivo del ${today}`, columns: [ { header: 'Sección', key: 'seccion' }, { header: 'Concepto', key: 'concepto' }, { header: 'Cantidad', key: 'cantidad', format: 'number' }, { header: 'Monto', key: 'monto', format: 'currency' }, ], rows, totals: { seccion: '', concepto: 'Comisión total', monto: commTotals.commission }, }); toast.success('Excel descargado'); } catch (err) { toast.error('Error al exportar'); console.error(err); } finally { setExporting(false); } }; return ( {tab === 'general' && ( )} {/* Tabs de reportes */}
{TABS.map((t) => ( ))}
{tab === 'diario' ? ( ) : tab === 'cortes' ? ( ) : tab === 'ingresos' ? ( ) : tab === 'inventario' ? ( ) : tab === 'adeudos' ? ( ) : tab === 'comisiones' ? ( ) : tab === 'pagos-servicios' ? ( ) : tab === 'pagos-clientes' ? ( ) : tab === 'devoluciones' ? ( ) : tab === 'top-clientes' ? ( ) : tab === 'exportar' ? ( ) : tab === 'horas-agenda' ? ( ) : tab === 'paquetes' ? ( ) : tab === 'vendedores' ? ( ) : tab === 'concentrado' ? ( ) : tab === 'recomendaciones' ? ( ) : tab === 'kpis' ? ( ) : ( <>
setStart(e.target.value)} className="sm:max-w-[180px]" /> setEnd(e.target.value)} className="sm:max-w-[180px]" /> setToday(e.target.value)} className="sm:max-w-[180px]" />
{error &&

{error}

} {loading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : salesReport ? (

Ventas

${salesReport.total_sales}

Pagado

${salesReport.total_paid}

Por cobrar

${salesReport.total_due}

Cantidad

{salesReport.count}

) : null}

Citas por estado

{loading ? ( ) : aptData.length === 0 ? ( } /> ) : ( )}

Efectivo por método

{loading ? ( ) : cashData.length === 0 ? ( } /> ) : ( {cashData.map((_, index) => ( ))} )}

Comisiones por médico

Comisión = % del médico sobre la venta de artículos recetados por él.

Base recetada: ${commTotals.base.toLocaleString('es-MX')} {' · '}Comisión total: ${commTotals.commission.toLocaleString('es-MX')}
{loading ? ( ) : commissions.length === 0 ? (

No hay ventas de artículos recetados en el periodo. Marca las líneas como “Artículo recetado” al crear la venta y asigna el % en Médicos.

) : (
{commissions.map((c) => ( ))}
Médico % Comisión Artículos Ventas Base recetada Comisión

{c.doctor}

{c.job_title || '—'}

{c.commission_pct}% {c.items} {c.sales} ${c.base.toLocaleString('es-MX')} ${c.commission.toLocaleString('es-MX')}
)}
)}
); }; export default Reportes;