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:
@@ -58,7 +58,10 @@ export interface Appointment {
|
||||
state: string;
|
||||
payment_state: string;
|
||||
price: number;
|
||||
doctor_id?: number;
|
||||
amount_paid?: number;
|
||||
duration?: number;
|
||||
is_first_visit?: boolean;
|
||||
doctor_id?: number | null;
|
||||
doctor?: string;
|
||||
branch?: string;
|
||||
medium?: string;
|
||||
@@ -67,6 +70,64 @@ export interface Appointment {
|
||||
package_finished_date?: string | null;
|
||||
}
|
||||
|
||||
export interface Bloqueo {
|
||||
id: number;
|
||||
doctor_id: number;
|
||||
doctor: string;
|
||||
date: string;
|
||||
all_day: boolean;
|
||||
time_from: number;
|
||||
time_to: number;
|
||||
motivo: string;
|
||||
}
|
||||
|
||||
export type VisitaState = 'en_curso' | 'completada' | 'cancelada';
|
||||
|
||||
export interface VisitaInsumo {
|
||||
id: number;
|
||||
item_id: number;
|
||||
item: string;
|
||||
unit: string;
|
||||
qty: number;
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface VisitaAdjunto {
|
||||
id: number;
|
||||
kind: 'antes' | 'despues' | 'documento';
|
||||
name: string;
|
||||
mimetype: string;
|
||||
notes: string;
|
||||
create_date: string | null;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Visita {
|
||||
id: number;
|
||||
name: string;
|
||||
cita_id: number | null;
|
||||
partner_id: number;
|
||||
patient: string;
|
||||
phone: string;
|
||||
doctor_id: number | null;
|
||||
doctor: string | null;
|
||||
cosmetologa_id: number | null;
|
||||
cosmetologa: string | null;
|
||||
servicio_id: number | null;
|
||||
servicio: string | null;
|
||||
motivo: string;
|
||||
diagnostico: string;
|
||||
tratamiento: string;
|
||||
notas: string;
|
||||
receta_text?: string;
|
||||
state: VisitaState;
|
||||
date_start: string | null;
|
||||
date_end: string | null;
|
||||
insumos: VisitaInsumo[];
|
||||
insumos_descargados: boolean;
|
||||
adjuntos: VisitaAdjunto[];
|
||||
}
|
||||
|
||||
export interface Patient {
|
||||
id: number;
|
||||
patient_id: string;
|
||||
@@ -83,8 +144,12 @@ export interface Patient {
|
||||
total_spent: number;
|
||||
source: string;
|
||||
is_vip: boolean;
|
||||
primary_doctor_id?: number;
|
||||
primary_doctor_id?: number | null;
|
||||
primary_doctor?: string;
|
||||
whatsapp?: string;
|
||||
has_photo?: boolean;
|
||||
photo_url?: string;
|
||||
photo?: string;
|
||||
// Datos personales extendidos
|
||||
birthplace?: string;
|
||||
occupation?: string;
|
||||
@@ -122,6 +187,42 @@ export interface Patient {
|
||||
allergies?: string;
|
||||
medical_history?: string;
|
||||
current_medication?: string;
|
||||
// Completitud de expediente
|
||||
expediente_completion?: number;
|
||||
expediente_missing?: string[];
|
||||
}
|
||||
|
||||
export interface EstadoCuenta {
|
||||
puntos_monedero: number;
|
||||
total_gastado: number;
|
||||
adeudo: number;
|
||||
visitas_total: number;
|
||||
}
|
||||
|
||||
export interface PatientAdjunto {
|
||||
id: number;
|
||||
kind: 'expediente' | 'imagen';
|
||||
name: string;
|
||||
mimetype: string;
|
||||
notes: string;
|
||||
create_date: string | null;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ExpedienteReciente {
|
||||
id: number;
|
||||
patient_id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
age: number;
|
||||
birth_date: string | null;
|
||||
primary_doctor: string | null;
|
||||
last_visit: string | null;
|
||||
internal_notes: string;
|
||||
expediente_count: number;
|
||||
imagenes_count: number;
|
||||
has_photo: boolean;
|
||||
photo_url: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
@@ -147,6 +248,7 @@ export interface Doctor {
|
||||
work_phone: string;
|
||||
work_email: string;
|
||||
commission_pct: number;
|
||||
patient_count?: number;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
@@ -364,6 +466,8 @@ export interface WacrmLead {
|
||||
expected_close_date: string | null;
|
||||
notes: string;
|
||||
assigned_to: string;
|
||||
partner_id?: number | null;
|
||||
conversation_id?: string;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
@@ -377,6 +481,237 @@ export interface FrontendUser {
|
||||
must_change_password?: boolean;
|
||||
last_login?: string | null;
|
||||
active?: boolean;
|
||||
allowed_menus?: string[] | null;
|
||||
}
|
||||
|
||||
export interface Receta {
|
||||
id: number;
|
||||
name: string;
|
||||
categoria: string;
|
||||
contenido: string;
|
||||
}
|
||||
|
||||
export interface Diagnostico {
|
||||
id: number;
|
||||
name: string;
|
||||
categoria: string;
|
||||
descripcion: string;
|
||||
}
|
||||
|
||||
export interface Procedimiento {
|
||||
id: number;
|
||||
name: string;
|
||||
categoria: string;
|
||||
descripcion: string;
|
||||
}
|
||||
|
||||
export interface AdeudoRow {
|
||||
id: number;
|
||||
name: string;
|
||||
phone: string;
|
||||
num_ventas: number;
|
||||
total_adeudo: number;
|
||||
ultima_venta: string | null;
|
||||
}
|
||||
|
||||
export interface ComisionRow {
|
||||
doctor_id: number;
|
||||
doctor: string;
|
||||
job_title: string;
|
||||
items: number;
|
||||
sales: number;
|
||||
citas_done: number;
|
||||
total_vendido: number;
|
||||
commission_pct: number;
|
||||
comision: number;
|
||||
}
|
||||
|
||||
export interface MovimientoRow extends InventoryMove {
|
||||
item: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface CaducidadRow {
|
||||
id: number;
|
||||
name: string;
|
||||
category: string;
|
||||
qty: number;
|
||||
unit: string;
|
||||
expiry_date: string;
|
||||
dias_restantes: number;
|
||||
vencido: boolean;
|
||||
}
|
||||
|
||||
export interface SugerenciaRow {
|
||||
id: number;
|
||||
name: string;
|
||||
category: string;
|
||||
unit: string;
|
||||
qty: number;
|
||||
qty_min: number;
|
||||
qty_optimal: number;
|
||||
sugerido: number;
|
||||
costo_estimado: number;
|
||||
}
|
||||
|
||||
export interface PagoServicioRow {
|
||||
servicio: string;
|
||||
cantidad: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface PagoClienteRow {
|
||||
id: number;
|
||||
name: string;
|
||||
num_pagos: number;
|
||||
total_cobrado: number;
|
||||
ultimo_pago: string | null;
|
||||
}
|
||||
|
||||
export interface DevolucionRow {
|
||||
id: number;
|
||||
folio: string;
|
||||
paciente: string;
|
||||
fecha: string | null;
|
||||
total: number;
|
||||
refund_amount: number;
|
||||
refund_reason: string;
|
||||
refunded_at: string | null;
|
||||
}
|
||||
|
||||
export interface TopClienteRow {
|
||||
id: number;
|
||||
name: string;
|
||||
num_ventas: number;
|
||||
total: number;
|
||||
ticket_promedio: number;
|
||||
}
|
||||
|
||||
export interface DailyPorMedico {
|
||||
medico: string;
|
||||
citas: number;
|
||||
by_state: Record<string, number>;
|
||||
ventas: number;
|
||||
cobros: number;
|
||||
}
|
||||
|
||||
export interface DailyPorRecepcion {
|
||||
usuario: string;
|
||||
ventas_count: number;
|
||||
ventas_total: number;
|
||||
cobros_total: number;
|
||||
}
|
||||
|
||||
export interface HoraAgendaRow {
|
||||
hora: string;
|
||||
total: number;
|
||||
by_state: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface PaqueteRow {
|
||||
id: number;
|
||||
name: string;
|
||||
package_sessions: number;
|
||||
package_price: number;
|
||||
price: number;
|
||||
citas: number;
|
||||
terminados: number;
|
||||
}
|
||||
|
||||
export interface VendedorRow {
|
||||
usuario: string;
|
||||
num_ventas: number;
|
||||
total: number;
|
||||
cobrado: number;
|
||||
}
|
||||
|
||||
export interface ConcentradoReport {
|
||||
start: string;
|
||||
end: string;
|
||||
citas: { total: number; by_state: Record<string, number> };
|
||||
ventas: { count: number; total: number; total_paid: number; total_due: number };
|
||||
pagos: { total: number; count: number; by_method: Record<string, number> };
|
||||
devoluciones: { count: number; total: number };
|
||||
pacientes_nuevos: number;
|
||||
visitas: { total: number; by_state: Record<string, number> };
|
||||
}
|
||||
|
||||
export interface RecomendacionRow {
|
||||
recomendador: string;
|
||||
pacientes: number;
|
||||
}
|
||||
|
||||
export interface MonederoPuntosReport {
|
||||
by_type: Record<string, number>;
|
||||
puntos_activos: number;
|
||||
cuentas: number;
|
||||
equivalente_mxn: number;
|
||||
}
|
||||
|
||||
export interface KpisNoShowMedico {
|
||||
medico: string;
|
||||
citas: number;
|
||||
no_shows: number;
|
||||
tasa: number;
|
||||
}
|
||||
|
||||
export interface KpisOcupacionMedico {
|
||||
medico: string;
|
||||
horas_vendidas: number;
|
||||
horas_disponibles: number;
|
||||
horas_libres: number;
|
||||
ocupacion: number;
|
||||
}
|
||||
|
||||
export interface KpisReport {
|
||||
start: string;
|
||||
end: string;
|
||||
no_show: { tasa: number; total_citas: number; total_no_shows: number; por_medico: KpisNoShowMedico[] };
|
||||
ocupacion: { promedio: number; dias_habiles: number; por_medico: KpisOcupacionMedico[] };
|
||||
leads: { total: number; ganados: number; tasa: number; dias_promedio_ganar: number | null };
|
||||
primera_vez: { primera: number; subsecuentes: number; tasa: number };
|
||||
}
|
||||
|
||||
export interface AppNotification {
|
||||
tipo: string;
|
||||
titulo: string;
|
||||
detalle: string;
|
||||
link: string;
|
||||
severity: 'info' | 'warning' | 'danger';
|
||||
}
|
||||
|
||||
export interface PosCheckoutPayload {
|
||||
partner_id: number;
|
||||
lines: { service_id: number; quantity: number; price_unit: number; description?: string; prescribed_by_id?: number }[];
|
||||
discount?: number;
|
||||
payment_method?: string;
|
||||
amount_received?: number;
|
||||
pay_with_points?: boolean;
|
||||
}
|
||||
|
||||
export interface PosCheckoutResult {
|
||||
status: string;
|
||||
sale: Sale;
|
||||
payment: Payment | null;
|
||||
cambio: number;
|
||||
puntos_usados: number;
|
||||
puntos_ganados: number;
|
||||
wallet_points: number;
|
||||
}
|
||||
|
||||
export interface DailyReport {
|
||||
date: string;
|
||||
citas: { total: number; by_state: Record<string, number> };
|
||||
ventas: { count: number; total: number; total_paid: number; total_due: number };
|
||||
pagos: { total: number; count: number; by_method: Record<string, number> };
|
||||
inventario: { total: number; by_type: Record<string, number> };
|
||||
}
|
||||
|
||||
export interface InventoryReportSummary {
|
||||
total_value: number;
|
||||
count: number;
|
||||
out: number;
|
||||
critical: number;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
@@ -428,11 +763,16 @@ export const odooApi = {
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateFrontendUser(id: number, payload: Partial<{ name: string; role: FrontendRole; active: boolean; password: string; must_change_password: boolean }>): Promise<{ status: string; user: FrontendUser; message?: string }> {
|
||||
async updateFrontendUser(id: number, payload: Partial<{ name: string; role: FrontendRole; active: boolean; password: string; must_change_password: boolean; allowed_menus: string[] | null }>): Promise<{ status: string; user: FrontendUser; message?: string }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/auth/users/${id}`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteFrontendUser(id: number): Promise<{ status: string; message?: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/auth/users/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Health
|
||||
async healthCheck(): Promise<unknown> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/health`);
|
||||
@@ -451,7 +791,7 @@ export const odooApi = {
|
||||
},
|
||||
|
||||
// Citas
|
||||
async getAppointments(params?: Record<string, string | number>): Promise<{ status: string; appointments: Appointment[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
async getAppointments(params?: Record<string, string | number>): Promise<{ status: string; appointments: Appointment[]; bloqueos?: Bloqueo[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/appointments`, { params });
|
||||
return data;
|
||||
},
|
||||
@@ -486,8 +826,81 @@ export const odooApi = {
|
||||
return data;
|
||||
},
|
||||
|
||||
// Bloqueos de agenda
|
||||
async getBloqueos(params?: Record<string, string | number>): Promise<{ status: string; bloqueos: Bloqueo[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/bloqueos`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createBloqueo(payload: { doctor_id: number; date: string; all_day?: boolean; time_from?: number; time_to?: number; motivo?: string }): Promise<{ status: string; bloqueo: Bloqueo }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/bloqueos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteBloqueo(id: number): Promise<{ status: string; message: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/bloqueos/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Visitas clínicas
|
||||
async getVisitas(params?: Record<string, string | number>): Promise<{ status: string; visitas: Visita[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/visitas`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getVisita(id: number): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateVisita(id: number, visita: Partial<Visita>): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}`, visita);
|
||||
return data;
|
||||
},
|
||||
|
||||
async completeVisita(id: number): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}/complete`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async cancelVisita(id: number): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}/cancel`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async addVisitaInsumo(id: number, payload: { item_id: number; qty: number; notes?: string }): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}/insumos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteVisitaInsumo(id: number, lineId: number): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}/insumos/${lineId}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getInventarioItems(search?: string): Promise<{ status: string; items: InventoryItem[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/inventory/items`, { params: search ? { search } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async uploadVisitaAdjunto(id: number, payload: { kind: string; name: string; file_b64: string; mimetype: string; notes?: string }): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/visitas/${id}/adjuntos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteVisitaAdjunto(adjId: number): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/visitas/adjuntos/${adjId}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// URL del binario de un adjunto con el token en query (para <img src>)
|
||||
visitaAdjuntoUrl(url: string): string {
|
||||
const token = localStorage.getItem('skeen_token') || '';
|
||||
return `${ODOO_BASE}${url}?token=${encodeURIComponent(token)}`;
|
||||
},
|
||||
|
||||
// Pacientes
|
||||
async getPatients(params?: string | { search?: string; page?: number; page_size?: number }): Promise<{ status: string; patients: Patient[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
async getPatients(params?: string | { search?: string; page?: number; page_size?: number; incomplete?: number; vip?: number; recent?: number; doctor_id?: number }): Promise<{ status: string; patients: Patient[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const p = typeof params === 'string' ? { search: params } : params || {};
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients`, { params: p });
|
||||
return data;
|
||||
@@ -503,14 +916,131 @@ export const odooApi = {
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPatientHistory(id: number): Promise<{ status: string; appointments: Appointment[]; sales: Sale[] }> {
|
||||
async getPatientHistory(id: number): Promise<{ status: string; appointments: Appointment[]; sales: Sale[]; visitas: Visita[]; estado_cuenta: EstadoCuenta }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients/${id}/history`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// URL de la foto del paciente con el token en query (para <img src>)
|
||||
patientPhotoUrl(url: string): string {
|
||||
const token = localStorage.getItem('skeen_token') || '';
|
||||
return `${ODOO_BASE}${url}?token=${encodeURIComponent(token)}`;
|
||||
},
|
||||
|
||||
// Expedientes recientes y adjuntos de paciente
|
||||
async getExpedientesRecientes(params?: Record<string, string | number>): Promise<{ status: string; expedientes: ExpedienteReciente[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/expedientes-recientes`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPatientAdjuntos(id: number, kind?: string): Promise<{ status: string; adjuntos: PatientAdjunto[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients/${id}/adjuntos`, { params: kind ? { kind } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async uploadPatientAdjunto(id: number, payload: { kind: string; name: string; file_b64: string; mimetype: string; notes?: string }): Promise<{ status: string; adjunto: PatientAdjunto }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/patients/${id}/adjuntos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deletePatientAdjunto(adjId: number): Promise<{ status: string; message: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/patients/adjuntos/${adjId}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// URL del binario de un adjunto de paciente con el token en query (para <img src>)
|
||||
patientAdjuntoUrl(url: string): string {
|
||||
const token = localStorage.getItem('skeen_token') || '';
|
||||
return `${ODOO_BASE}${url}?token=${encodeURIComponent(token)}`;
|
||||
},
|
||||
|
||||
// Recetas (plantillas)
|
||||
async getRecetas(search?: string): Promise<{ status: string; recetas: Receta[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/recetas`, { params: search ? { search } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createReceta(receta: Partial<Receta>): Promise<{ status: string; receta: Receta }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/recetas`, receta);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateReceta(id: number, receta: Partial<Receta>): Promise<{ status: string; receta: Receta }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/recetas/${id}`, receta);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteReceta(id: number): Promise<{ status: string; message?: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/recetas/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Catálogos: diagnósticos y procedimientos
|
||||
async getDiagnosticos(search?: string): Promise<{ status: string; items: Diagnostico[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/diagnosticos`, { params: search ? { search } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createDiagnostico(payload: Partial<Diagnostico>): Promise<{ status: string; item: Diagnostico }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/diagnosticos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateDiagnostico(id: number, payload: Partial<Diagnostico>): Promise<{ status: string; item: Diagnostico }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/diagnosticos/${id}`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteDiagnostico(id: number): Promise<{ status: string; message?: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/diagnosticos/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getProcedimientos(search?: string): Promise<{ status: string; items: Procedimiento[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/procedimientos`, { params: search ? { search } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createProcedimiento(payload: Partial<Procedimiento>): Promise<{ status: string; item: Procedimiento }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/procedimientos`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateProcedimiento(id: number, payload: Partial<Procedimiento>): Promise<{ status: string; item: Procedimiento }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/procedimientos/${id}`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteProcedimiento(id: number): Promise<{ status: string; message?: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/procedimientos/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Consultas médicas directas
|
||||
async createConsulta(payload: { partner_id: number; doctor_id?: number | null; motivo?: string; diagnostico?: string; tratamiento?: string; notas?: string }): Promise<{ status: string; visita: Visita }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/consultas`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Reporte de adeudos (cartera por cobrar)
|
||||
async getAdeudos(): Promise<{ status: string; adeudos: AdeudoRow[]; total_cartera: number; total_pacientes: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/adeudos`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Servicios
|
||||
async getServices(): Promise<{ status: string; services: Service[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/services`);
|
||||
async getServices(params?: { search?: string; category?: string; page?: number; page_size?: number }): Promise<{ status: string; services: Service[]; total?: number; page?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/services`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async globalSearch(q: string): Promise<{
|
||||
status: string;
|
||||
patients: { id: number; name: string; phone: string }[];
|
||||
appointments: { id: number; patient: string; service: string; date: string; time: string }[];
|
||||
services: { id: number; name: string; price: number }[];
|
||||
}> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/search`, { params: { q } });
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -618,11 +1148,119 @@ export const odooApi = {
|
||||
},
|
||||
|
||||
// Reportes
|
||||
async getSalesReport(start?: string, end?: string): Promise<{ status: string; total_sales: number; total_paid: number; total_due: number; count: number }> {
|
||||
async getSalesReport(start?: string, end?: string): Promise<{ status: string; total_sales: number; total_paid: number; total_due: number; count: number; by_day?: { date: string; total: number }[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/sales`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getDailyReport(date?: string, by?: 'medico' | 'recepcion'): Promise<{ status: string } & DailyReport & { por_medico?: DailyPorMedico[]; por_recepcion?: DailyPorRecepcion[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/daily`, {
|
||||
params: { ...(date ? { date } : {}), ...(by ? { by } : {}) },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async getComisiones(start?: string, end?: string): Promise<{ status: string; start: string; end: string; comisiones: ComisionRow[]; total_vendido: number; total_comision: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/comisiones`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPagosServicios(start?: string, end?: string): Promise<{ status: string; total_cobrado: number; total_pagos: number; by_method: Record<string, number>; servicios: PagoServicioRow[]; total_pagado_completo: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/pagos-servicios`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPagosClientes(start?: string, end?: string): Promise<{ status: string; clientes: PagoClienteRow[]; total_cobrado: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/pagos-clientes`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getDevoluciones(start?: string, end?: string): Promise<{ status: string; devoluciones: DevolucionRow[]; total_devuelto: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/devoluciones`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getTopClientes(start?: string, end?: string, limit = 20): Promise<{ status: string; clientes: TopClienteRow[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/top-clientes`, { params: { start, end, limit } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getMovimientos(params?: Record<string, string | number>): Promise<{ status: string; movimientos: MovimientoRow[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/inventario/movimientos`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createCompra(payload: { item_id: number; qty: number; cost?: number; reference?: string; notes?: string }): Promise<{ status: string; move: InventoryMove; item: InventoryItem }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/inventario/compras`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async createBaja(payload: { item_id: number; qty: number; reference?: string; notes?: string }): Promise<{ status: string; move: InventoryMove; item: InventoryItem }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/inventario/bajas`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getInventarioAlertas(): Promise<{ status: string; caducidades: CaducidadRow[]; sugerencia_compra: SugerenciaRow[]; sugerencia_total: { items: number; costo_total: number } }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/inventario/alertas`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getHorasAgenda(start?: string, end?: string): Promise<{ status: string; horas: HoraAgendaRow[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/horas-agenda`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPaquetes(): Promise<{ status: string; paquetes: PaqueteRow[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/paquetes`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getVendedores(start?: string, end?: string): Promise<{ status: string; vendedores: VendedorRow[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/vendedores`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getConcentrado(start?: string, end?: string): Promise<{ status: string } & ConcentradoReport> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/concentrado`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getRecomendaciones(): Promise<{ status: string; recomendaciones: RecomendacionRow[]; total: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/recomendaciones`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getMonederoPuntos(start?: string, end?: string): Promise<{ status: string } & MonederoPuntosReport> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/monedero-puntos`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getKpis(start?: string, end?: string): Promise<{ status: string } & KpisReport> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/kpis`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getNotifications(): Promise<{ status: string; notifications: AppNotification[]; total: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/notifications`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// POS: venta + pago en una sola operación
|
||||
async posCheckout(payload: PosCheckoutPayload): Promise<PosCheckoutResult> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/pos/checkout`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getCashReportRange(start: string, end: string): Promise<{ status: string; total: number; by_method: Record<string, number> }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/cash`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getInventoryReport(): Promise<{ status: string; summary: InventoryReportSummary; items: InventoryItem[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/inventory`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getAppointmentsReport(start?: string, end?: string): Promise<{ status: string; total: number; by_state: Record<string, number> }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/appointments`, { params: { start, end } });
|
||||
return data;
|
||||
@@ -700,7 +1338,7 @@ export const odooApi = {
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmMembers(): Promise<{ status: string; members: { id: string; name: string; email?: string; avatar_url?: string; role: string }[] }> {
|
||||
async getWacrmMembers(): Promise<{ status: string; members: { id: string; profile_id?: string; name: string; email?: string; avatar_url?: string; role: string }[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/members`);
|
||||
return data;
|
||||
},
|
||||
@@ -741,8 +1379,13 @@ export const odooApi = {
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateWacrmLeadStatus(id: number, payload: { status?: 'open' | 'won' | 'lost'; stage_id?: string }): Promise<{ status: string; lead: WacrmLead }> {
|
||||
async updateWacrmLeadStatus(id: number, payload: { status?: 'open' | 'won' | 'lost'; stage_id?: string; assigned_to?: string | null }): Promise<{ status: string; lead: WacrmLead }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/wacrm/leads/${id}/status`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async convertWacrmLead(id: number): Promise<{ status: string; lead: WacrmLead; patient_id: number; created: boolean; message?: string }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/leads/${id}/convert`);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user