Mejoras integrales: visitas, POS, WACRM, reportes, temas y módulos legacy

- Módulo Visitas completo (auto desde agenda, insumos con descargo de
  inventario, fotos antes/después y documentos, receta imprimible)
- Punto de Venta (catálogo + ticket sticky, cobro con cambio, pago con
  puntos monedero, ticket imprimible)
- WACRM: leads automáticos desde WhatsApp, asignación de conversaciones
  y leads a agentes, conversión lead→paciente, ficha del paciente en chat
- Pacientes: completitud de expediente, alertas clínicas, historial
  unificado con detalle, foto, WhatsApp, estado de cuenta, filtros
  rápidos (VIP/recientes/médico), documentos (expediente escaneado + galería)
- Agenda: vistas por médico y por hora, filtros rápidos (libres, primera
  vez, check-in, no-show), modal de acciones, bloqueos por médico,
  drag&drop para mover citas
- Reportes: 18 pestañas (diario, cortes, ingresos, inventario, adeudos,
  comisiones, pagos, devoluciones, top clientes, horas, paquetes,
  vendedores, concentrado, recomendaciones, KPIs) con exportación Excel
- Temas: nuevo tema Clásico (look legacy AdminLTE) con submenús tipo
  treeview, selector de tema; accesos rápidos personalizables con 3
  presentaciones; búsqueda global; notificaciones reales
- Configuración: secciones (clínica, usuarios con permisos por sección,
  recetas, catálogos de diagnósticos y procedimientos)
- Inventario: alertas de caducidad y sugerencia de compra, cron diario
  que descuenta artículos caducados, compras/bajas
- Consultas Médicas, página Expedientes, importadores delta
  (citas/visitas legacy idempotentes), depuración de duplicados
- Infra: tema Tailwind conectado (@config), gzip en nginx, secuencias
  Odoo corregidas (noupdate, company_id), rollback en validaciones
This commit is contained in:
2026-08-13 23:30:08 +00:00
parent a718592291
commit 01f6007e30
109 changed files with 19067 additions and 931 deletions

View File

@@ -18,7 +18,7 @@ import {
badgeForSaleState,
} from '../components/ui';
import { odooApi, type Sale, type SaleLine, type Service, type Patient, type Doctor } from '../services/odoo';
import { downloadCsv } from '../lib/utils';
import { exportToExcel } from '../lib/exporter';
interface LineForm {
service_id: string;
@@ -246,19 +246,40 @@ const Ventas: FC = () => {
}
};
const exportSales = () => {
const rows: (string | number)[][] = [
['Referencia', 'Paciente', 'Fecha', 'Subtotal', 'Descuento', 'Impuesto', 'Total', 'Pagado', 'Por cobrar', 'Estado', 'Devuelta', 'Monto devuelto'],
];
sales.forEach((s) =>
rows.push([
s.name, s.patient, s.date, s.subtotal, s.discount, s.tax, s.total,
s.amount_paid, s.amount_due, s.state, s.refunded ? 'Sí' : 'No', s.refund_amount || 0,
])
);
rows.push(['', '', 'Totales', '', '', '', sales.reduce((a, s) => a + s.total, 0), sales.reduce((a, s) => a + s.amount_paid, 0), sales.reduce((a, s) => a + s.amount_due, 0), '', '', sales.reduce((a, s) => a + (s.refund_amount || 0), 0)]);
downloadCsv(`ventas-skeen-${new Date().toISOString().split('T')[0]}.csv`, rows);
toast.success(`CSV descargado (${sales.length} ventas de la vista actual)`);
const exportSales = async () => {
try {
await exportToExcel({
filename: `ventas-skeen-${new Date().toISOString().split('T')[0]}`,
sheetName: 'Ventas',
title: 'Ventas SKEEN',
subtitle: `${sales.length} ventas de la vista actual`,
columns: [
{ header: 'Referencia', key: 'name' },
{ header: 'Paciente', key: 'patient' },
{ header: 'Fecha', key: 'date', format: 'date' },
{ header: 'Subtotal', key: 'subtotal', format: 'currency' },
{ header: 'Descuento', key: 'discount', format: 'currency' },
{ header: 'Impuesto', key: 'tax', format: 'currency' },
{ header: 'Total', key: 'total', format: 'currency' },
{ header: 'Pagado', key: 'amount_paid', format: 'currency' },
{ header: 'Por cobrar', key: 'amount_due', format: 'currency' },
{ header: 'Estado', key: 'state' },
{ header: 'Devuelta', key: 'devuelta' },
{ header: 'Monto devuelto', key: 'refund_amount', format: 'currency' },
],
rows: sales.map((s) => ({ ...s, devuelta: s.refunded ? 'Sí' : 'No', refund_amount: s.refund_amount || 0 })),
totals: {
name: '', total: sales.reduce((a, s) => a + s.total, 0),
amount_paid: sales.reduce((a, s) => a + s.amount_paid, 0),
amount_due: sales.reduce((a, s) => a + s.amount_due, 0),
refund_amount: sales.reduce((a, s) => a + (s.refund_amount || 0), 0),
},
});
toast.success(`Excel descargado (${sales.length} ventas de la vista actual)`);
} catch (err) {
toast.error('Error al exportar');
console.error(err);
}
};
const patientOptions = [