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

@@ -1,10 +1,26 @@
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Phone, MapPin, Mail, Globe, Save, Building2, ArrowRightLeft } from 'lucide-react';
import Layout from '../components/Layout';
import UsuariosPanel from '../components/UsuariosPanel';
import RecetasPanel from '../components/RecetasPanel';
import CatalogoPanel from '../components/CatalogoPanel';
import { Card, Button, Input, PageHeader, toast } from '../components/ui';
import { odooApi } from '../services/odoo';
const TABS = [
{ key: 'clinica', label: 'Clínica' },
{ key: 'usuarios', label: 'Usuarios' },
{ 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;
@@ -24,6 +40,13 @@ const defaultSettings: ClinicSettings = {
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<string>(() => {
const sub = searchParams.get('sub');
return sub === 'procedimientos' ? 'procedimientos' : 'diagnosticos';
});
const [settings, setSettings] = useState<ClinicSettings>(defaultSettings);
const [loading, setLoading] = useState(true);
@@ -89,6 +112,55 @@ const Configuracion: FC = () => {
<Layout title="Configuración" subtitle="Ajustes de la clínica">
<PageHeader title="Configuración" subtitle="Información y ajustes de la clínica" />
{/* Tabs de secciones */}
<div className="flex flex-wrap gap-1.5 mb-6">
{TABS.map((t) => (
<button
key={t.key}
type="button"
onClick={() => setTab(t.key)}
className={`px-4 py-2 text-sm font-medium rounded-full transition ${
tab === t.key ? 'bg-theme-accent text-theme-inverse' : 'bg-theme-accent-bg text-theme-heading hover:bg-theme-bg'
}`}
>
{t.label}
</button>
))}
</div>
{tab === 'usuarios' ? (
<Card>
<Card.Body>
<UsuariosPanel />
</Card.Body>
</Card>
) : tab === 'recetas' ? (
<Card>
<Card.Body>
<RecetasPanel />
</Card.Body>
</Card>
) : tab === 'catalogos' ? (
<Card>
<Card.Body>
<div className="flex flex-wrap gap-1.5 mb-4">
{SUBCATALOGOS.map((s) => (
<button
key={s.key}
type="button"
onClick={() => setSubcatalogo(s.key)}
className={`px-3 py-1.5 text-sm font-medium rounded-full transition ${
subcatalogo === s.key ? 'bg-theme-accent text-theme-inverse' : 'bg-theme-accent-bg text-theme-heading hover:bg-theme-bg'
}`}
>
{s.label}
</button>
))}
</div>
<CatalogoPanel kind={subcatalogo as 'diagnosticos' | 'procedimientos'} titulo={subcatalogo === 'diagnosticos' ? 'Diagnósticos' : 'Procedimientos'} />
</Card.Body>
</Card>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
<Card>
<Card.Body>
@@ -234,6 +306,7 @@ const Configuracion: FC = () => {
</Card.Body>
</Card>
</div>
)}
</Layout>
);
};