import { useEffect, useState } from 'react'; import { odooApi } from '../services/odoo'; import { useAuth } from './auth'; // Configuración del ticket (diseño + datos de la clínica + logo). // // Vive en el servidor (ir.config_parameter) para que todas las cajas impriman // igual. Antes los datos de la clínica se guardaban en el localStorage de cada // navegador (clave skeen_clinic_settings), así que cada equipo imprimía una // dirección distinta. Este módulo cachea la configuración en memoria, la // comparte con quien la pida y deja el localStorage como respaldo de arranque. export interface TicketClinica { nombre: string; direccion: string; telefono: string; email: string; sitio: string; rfc: string; sucursal: string; tiene_logo: boolean; } export interface TicketLayout { ancho: '58mm' | '80mm'; fuente: 'monoespaciada' | 'sans'; letra: 'compacta' | 'normal' | 'grande'; separador: 'guiones' | 'solida' | 'ninguna'; mayusculas: boolean; bloques: string[]; secciones: Record; textos: { encabezado: string; pie: string; politica: string }; } export interface TicketConfig { layout: TicketLayout; clinica: TicketClinica; logo: string; } const CACHE_KEY = 'skeen_ticket_config'; // Respaldo por si el API no responde (sin sesión, red caída): el último config // leído, o el formato histórico del ticket si nunca se leyó nada. export const FALLBACK_CONFIG: TicketConfig = { layout: { ancho: '80mm', fuente: 'monoespaciada', letra: 'normal', separador: 'guiones', mayusculas: true, bloques: ['encabezado', 'cliente', 'conceptos', 'totales', 'pago', 'monedero', 'pie'], secciones: { nombre_clinica: true, direccion: true, telefono_clinica: true, sucursal: true, rfc: false, mensaje_encabezado: true, folio: true, fecha: true, cajero: true, paciente: true, telefono_paciente: false, doctor: true, conceptos: true, detalle_conceptos: true, subtotal: true, descuento: true, puntos_canjeados: true, impuesto: true, total: true, total_usd: true, metodo_pago: true, recibido: true, cambio: true, pagado: true, saldo_pendiente: true, puntos_ganados: true, monedero: true, notas: true, politica: false, mensaje_pie: true, }, textos: { encabezado: '', pie: '¡Gracias por su visita!', politica: 'Conserve este ticket. No tiene validez fiscal.', }, }, clinica: { // Neutro a propósito: cada clínica configura su perfil en // Configuración › Tickets. Si `nombre` queda vacío se usa el nombre del // brand. Antes traía los datos de SKEEN y se filtraban a otras instancias. nombre: '', direccion: '', telefono: '', email: '', sitio: '', rfc: '', sucursal: '', tiene_logo: false, }, logo: '', }; const clonar = (v: T): T => JSON.parse(JSON.stringify(v)) as T; const leerLocal = (): TicketConfig | null => { try { const raw = localStorage.getItem(CACHE_KEY); if (!raw) return null; const parsed = JSON.parse(raw) as TicketConfig; if (!parsed?.layout?.secciones || !parsed?.clinica) return null; return parsed; } catch { return null; } }; let actual: TicketConfig = leerLocal() || clonar(FALLBACK_CONFIG); const oyentes = new Set<(c: TicketConfig) => void>(); let peticion: Promise | null = null; let yaCargo = false; const publicar = (c: TicketConfig) => { actual = c; try { localStorage.setItem(CACHE_KEY, JSON.stringify(c)); } catch { // localStorage lleno o bloqueado: seguimos en memoria } oyentes.forEach((f) => f(c)); }; /** Fuerza la recarga (tras guardar en Configuración). */ export const recargarTicketConfig = async (): Promise => { peticion = null; yaCargo = false; return cargarTicketConfig(); }; export const cargarTicketConfig = (): Promise => { if (peticion) return peticion; if (yaCargo) return Promise.resolve(actual); peticion = odooApi.getTicketConfig() .then((res) => { if (res.status === 'success' && res.layout && res.clinica) { publicar({ layout: res.layout as TicketConfig['layout'], clinica: res.clinica as TicketConfig['clinica'], logo: res.logo || '', }); } return actual; }) .catch(() => actual) .finally(() => { peticion = null; yaCargo = true; }); return peticion; }; /** Config actual dentro de React; se refresca sola cuando cambia. */ export const useTicketConfig = (): TicketConfig => { const [cfg, setCfg] = useState(actual); useEffect(() => { const f = (c: TicketConfig) => setCfg(clonar(c)); oyentes.add(f); cargarTicketConfig().then((c) => setCfg(clonar(c))); return () => { oyentes.delete(f); }; }, []); return cfg; }; /** Versión no-React para componentes que solo leen (impresión puntual). */ export const ticketConfigActual = (): TicketConfig => actual; /** * Datos de la clínica para los documentos impresos (ticket, recibo, estado de * cuenta, receta, corte, conteo). Sale de la configuración del servidor; si * nunca se configuró, cae al skeen_clinic_settings de este navegador y de ahí * al nombre del brand (SKEEN por omisión, o la clínica white-label). */ export const useClinicaPrint = () => { const { brand } = useAuth(); const c = useTicketConfig().clinica; const local = leerLocal()?.clinica; const mix = (campo: keyof TicketClinica, porDefecto = '') => String(c[campo] || local?.[campo] || porDefecto || ''); return { nombre: mix('nombre', brand.nombre), direccion: mix('direccion', 'Playas de Rosarito, Baja California'), telefono: mix('telefono'), email: mix('email'), sitio: mix('sitio'), rfc: mix('rfc'), sucursal: mix('sucursal'), }; };