fase 2.2: impuestos configurables por instancia (tasa por servicio/articulo, calculo por linea, desglose en POS/tickets/estado de cuenta, reporte de impuestos, permiso ventas.impuestos) con defaults apagados

This commit is contained in:
2026-10-11 08:04:11 +00:00
parent 28a690aa0f
commit 358b85afcd
22 changed files with 1105 additions and 30 deletions

View File

@@ -0,0 +1,54 @@
import { useEffect, useState } from 'react';
import { odooApi, type ClinicSettings, CLINIC_SETTINGS_DEFAULT, IMPUESTOS_DEFAULT } from '../services/odoo';
// Ajustes de la clínica compartidos por la sesión (nombre, moneda e impuestos).
// Se leen una sola vez del servidor y se avisa a quien los use cuando cambian.
// Es la fuente que decide si los impuestos están activos en cada pantalla.
const normalizar = (settings?: Partial<ClinicSettings> | null): ClinicSettings => ({
...CLINIC_SETTINGS_DEFAULT,
...(settings || {}),
impuestos: { ...IMPUESTOS_DEFAULT, ...(settings?.impuestos || {}) },
});
let actual: ClinicSettings = normalizar(null);
let peticion: Promise<ClinicSettings> | null = null;
const oyentes = new Set<(s: ClinicSettings) => void>();
const publicar = (s: ClinicSettings) => {
actual = s;
oyentes.forEach((f) => f(s));
};
export const cargarClinicSettings = (): Promise<ClinicSettings> => {
if (peticion) return peticion;
peticion = odooApi.getClinicSettings()
.then((res) => {
if (res.status === 'success' && res.settings) publicar(normalizar(res.settings));
return actual;
})
.catch(() => actual)
.finally(() => { peticion = null; });
return peticion;
};
/** Fuerza la recarga (tras guardar en Configuración). */
export const recargarClinicSettings = (): Promise<ClinicSettings> => {
peticion = null;
return cargarClinicSettings();
};
/** Registra los ajustes ya cargados/guardados sin volver a pedirlos. */
export const publicarClinicSettings = (settings: Partial<ClinicSettings>) =>
publicar(normalizar(settings));
export const useClinicSettings = (): ClinicSettings => {
const [s, setS] = useState(actual);
useEffect(() => {
const f = (v: ClinicSettings) => setS(v);
oyentes.add(f);
cargarClinicSettings().then(setS);
return () => { oyentes.delete(f); };
}, []);
return s;
};

View File

@@ -9,7 +9,7 @@ export const PERM_KEYS: string[] = [
'pacientes.ver', 'pacientes.expediente', 'pacientes.crear', 'pacientes.editar',
'pacientes.eliminar', 'pacientes.exportar', 'pacientes.saldos',
'visitas.ver', 'visitas.crear', 'visitas.editar', 'visitas.eliminar', 'visitas.archivos',
'ventas.vender', 'ventas.cobrar', 'ventas.descuentos', 'ventas.cancelar',
'ventas.vender', 'ventas.cobrar', 'ventas.descuentos', 'ventas.impuestos', 'ventas.cancelar',
'ventas.devolver', 'ventas.reimprimir',
'inventario.ver', 'inventario.ajustar', 'inventario.mover', 'inventario.costos',
'servicios.ver', 'servicios.editar',
@@ -46,6 +46,7 @@ const DEFAULT_PERM_ROLES: Record<string, string[]> = {
'visitas.ver': ALL, 'visitas.crear': W, 'visitas.editar': W,
'visitas.eliminar': W, 'visitas.archivos': W,
'ventas.vender': MONEY, 'ventas.cobrar': WN, 'ventas.descuentos': MONEY,
'ventas.impuestos': MONEY,
'ventas.cancelar': AG, 'ventas.devolver': MONEY, 'ventas.reimprimir': ALL,
'inventario.ver': ALL, 'inventario.ajustar': WN, 'inventario.mover': WN,
'inventario.costos': WN,

View File

@@ -68,6 +68,7 @@ export const FALLBACK_CONFIG: TicketConfig = {
subtotal: true,
descuento: true,
puntos_canjeados: true,
impuesto: true,
total: true,
total_usd: true,
metodo_pago: true,