Initial commit: SKEEN Derma Experts - Sistema Integral de Gestión Clínica
- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
This commit is contained in:
748
frontend/src/services/odoo.ts
Normal file
748
frontend/src/services/odoo.ts
Normal file
@@ -0,0 +1,748 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const ODOO_BASE = import.meta.env.VITE_API_BASE_URL || '/api/odoo';
|
||||
|
||||
// ============================================================
|
||||
// Auth: adjuntar token y manejar 401 globalmente
|
||||
// ============================================================
|
||||
axios.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('skeen_token');
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as Record<string, string>).Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(res) => res,
|
||||
(error) => {
|
||||
if (error?.response?.status === 401) {
|
||||
const onLogin = window.location.pathname === '/login';
|
||||
const isLoginCall = String(error.config?.url || '').includes('/auth/login');
|
||||
if (!onLogin && !isLoginCall) {
|
||||
localStorage.removeItem('skeen_token');
|
||||
localStorage.removeItem('skeen_user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Tipos
|
||||
// ============================================================
|
||||
export interface DashboardStats {
|
||||
appointments_today: number;
|
||||
appointments_month: number;
|
||||
new_patients_month: number;
|
||||
total_patients: number;
|
||||
revenue_month: number;
|
||||
pending_payments: number;
|
||||
total_wallet_points: number;
|
||||
sales_today: number;
|
||||
}
|
||||
|
||||
export interface Appointment {
|
||||
id: number;
|
||||
reference: string;
|
||||
patient_id: number;
|
||||
patient: string;
|
||||
phone: string;
|
||||
service_id: number;
|
||||
service: string;
|
||||
service_category?: string;
|
||||
date: string;
|
||||
time: string;
|
||||
state: string;
|
||||
payment_state: string;
|
||||
price: number;
|
||||
doctor_id?: number;
|
||||
doctor?: string;
|
||||
branch?: string;
|
||||
medium?: string;
|
||||
notes?: string;
|
||||
package_finished?: boolean;
|
||||
package_finished_date?: string | null;
|
||||
}
|
||||
|
||||
export interface Patient {
|
||||
id: number;
|
||||
patient_id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
birth_date: string | null;
|
||||
age?: number;
|
||||
gender?: string | false;
|
||||
blood_type?: string | false;
|
||||
last_visit: string | null;
|
||||
total_visits: number;
|
||||
wallet_points: number;
|
||||
total_spent: number;
|
||||
source: string;
|
||||
is_vip: boolean;
|
||||
primary_doctor_id?: number;
|
||||
primary_doctor?: string;
|
||||
// Datos personales extendidos
|
||||
birthplace?: string;
|
||||
occupation?: string;
|
||||
marital_status?: string;
|
||||
emergency_contact?: string;
|
||||
emergency_phone?: string;
|
||||
home_phone?: string;
|
||||
mobile?: string;
|
||||
address_notes?: string;
|
||||
referred_by?: string;
|
||||
patient_comments?: string;
|
||||
internal_notes?: string;
|
||||
// Historia clínica
|
||||
is_pregnant?: boolean;
|
||||
is_breastfeeding?: boolean;
|
||||
uses_contraceptives?: boolean;
|
||||
children_count?: number;
|
||||
kidney_problems?: boolean;
|
||||
back_pain?: boolean;
|
||||
heart_disease?: boolean;
|
||||
respiratory_problems?: boolean;
|
||||
blood_pressure?: boolean;
|
||||
diabetes?: boolean;
|
||||
thyroid?: boolean;
|
||||
colitis?: boolean;
|
||||
constipation?: boolean;
|
||||
liver_problems?: boolean;
|
||||
surgeries?: boolean;
|
||||
surgeries_notes?: string;
|
||||
varicose_veins?: boolean;
|
||||
migraine?: boolean;
|
||||
faints_with_needles?: boolean;
|
||||
medical_notes?: string;
|
||||
// Legacy medical fields
|
||||
allergies?: string;
|
||||
medical_history?: string;
|
||||
current_medication?: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
category: string;
|
||||
price: number;
|
||||
package_price?: number;
|
||||
package_sessions?: number;
|
||||
package_notes?: string;
|
||||
duration_min: number;
|
||||
description: string;
|
||||
color: string;
|
||||
service_group: string;
|
||||
is_favorite: boolean;
|
||||
}
|
||||
|
||||
export interface Doctor {
|
||||
id: number;
|
||||
name: string;
|
||||
job_title: string;
|
||||
work_phone: string;
|
||||
work_email: string;
|
||||
commission_pct: number;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
name: string;
|
||||
list_price: number;
|
||||
default_code: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Wallet {
|
||||
id: number;
|
||||
patient_id: number;
|
||||
patient: string;
|
||||
phone: string;
|
||||
points: number;
|
||||
equivalent_mxn: number;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: number;
|
||||
name: string;
|
||||
patient: string;
|
||||
patient_id: number;
|
||||
amount: number;
|
||||
payment_method: string;
|
||||
state: string;
|
||||
payment_url?: string;
|
||||
provider_reference?: string;
|
||||
create_date?: string;
|
||||
}
|
||||
|
||||
export interface SaleLine {
|
||||
id?: number;
|
||||
service_id: number;
|
||||
service?: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
price_unit: number;
|
||||
subtotal: number;
|
||||
is_prescribed?: boolean;
|
||||
prescribed_by_id?: number | null;
|
||||
prescribed_by?: string | null;
|
||||
}
|
||||
|
||||
export interface CommissionRow {
|
||||
doctor_id: number;
|
||||
doctor: string;
|
||||
job_title: string;
|
||||
commission_pct: number;
|
||||
base: number;
|
||||
commission: number;
|
||||
items: number;
|
||||
sales: number;
|
||||
}
|
||||
|
||||
export interface Sale {
|
||||
id: number;
|
||||
name: string;
|
||||
patient_id: number;
|
||||
patient: string;
|
||||
date: string;
|
||||
subtotal: number;
|
||||
discount: number;
|
||||
tax: number;
|
||||
total: number;
|
||||
amount_paid: number;
|
||||
amount_due: number;
|
||||
state: string;
|
||||
lines: SaleLine[];
|
||||
refunded?: boolean;
|
||||
refund_amount?: number;
|
||||
refund_reason?: string;
|
||||
refunded_at?: string | null;
|
||||
}
|
||||
|
||||
export interface CashClosing {
|
||||
id: number;
|
||||
name: string;
|
||||
date: string;
|
||||
user: string | false;
|
||||
opening_cash: number;
|
||||
total_cash: number;
|
||||
total_card: number;
|
||||
total_transfer: number;
|
||||
total_other: number;
|
||||
total_sales: number;
|
||||
closing_cash: number;
|
||||
difference: number;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export interface ChartPoint {
|
||||
name: string;
|
||||
appointments: number;
|
||||
revenue: number;
|
||||
patients: number;
|
||||
}
|
||||
|
||||
export interface Birthday {
|
||||
id: number;
|
||||
name: string;
|
||||
phone: string;
|
||||
birth_date: string;
|
||||
occurs_on: string;
|
||||
turning_age: number;
|
||||
last_visit: string | null;
|
||||
total_spent: number;
|
||||
amount_due: number;
|
||||
is_vip: boolean;
|
||||
}
|
||||
|
||||
export type InventoryKind = 'producto' | 'consumible';
|
||||
export type InventoryLevel = 'out' | 'critical' | 'low' | 'optimal';
|
||||
export type InventoryMoveType = 'compra' | 'venta' | 'baja' | 'ajuste';
|
||||
|
||||
export interface InventoryItem {
|
||||
id: number;
|
||||
name: string;
|
||||
kind: InventoryKind;
|
||||
sku: string;
|
||||
category: string;
|
||||
unit: string;
|
||||
qty: number;
|
||||
qty_optimal: number;
|
||||
qty_min: number;
|
||||
cost: number;
|
||||
inventory_value: number;
|
||||
expiry_date: string | null;
|
||||
last_count_date: string | null;
|
||||
active: boolean;
|
||||
notes: string;
|
||||
stock_level: InventoryLevel;
|
||||
}
|
||||
|
||||
export interface InventoryMove {
|
||||
id: number;
|
||||
item_id: number;
|
||||
type: InventoryMoveType;
|
||||
qty: number;
|
||||
before_qty: number;
|
||||
after_qty: number;
|
||||
date: string | null;
|
||||
reference: string;
|
||||
notes: string;
|
||||
}
|
||||
|
||||
export interface InventorySummary {
|
||||
levels: Record<InventoryLevel, number>;
|
||||
total_value: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// WACRM
|
||||
export interface WacrmConversation {
|
||||
id: number;
|
||||
external_id: string;
|
||||
contact_id?: string;
|
||||
contact_name: string;
|
||||
contact_phone: string;
|
||||
status: string;
|
||||
assigned_agent: string;
|
||||
assigned_agent_id: string;
|
||||
last_message_text: string;
|
||||
last_message_at: string | null;
|
||||
unread_count: number;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface WacrmMessage {
|
||||
id: number;
|
||||
external_id: string;
|
||||
conversation_id: string;
|
||||
contact_name: string;
|
||||
contact_phone: string;
|
||||
sender_type: 'customer' | 'agent' | 'bot';
|
||||
content_type: string;
|
||||
content_text: string;
|
||||
media_url: string;
|
||||
template_name: string;
|
||||
status: string;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface WacrmStage {
|
||||
id: number;
|
||||
external_id: string;
|
||||
name: string;
|
||||
position: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface WacrmPipeline {
|
||||
id: number;
|
||||
external_id: string;
|
||||
name: string;
|
||||
stages: WacrmStage[];
|
||||
}
|
||||
|
||||
export interface WacrmLead {
|
||||
id: number;
|
||||
external_id: string;
|
||||
title: string;
|
||||
contact_id?: string;
|
||||
contact_name: string;
|
||||
contact_phone: string;
|
||||
pipeline: string;
|
||||
pipeline_id?: number;
|
||||
stage: string;
|
||||
stage_id?: number;
|
||||
stage_color: string;
|
||||
value: number;
|
||||
currency: string;
|
||||
status: 'open' | 'won' | 'lost';
|
||||
expected_close_date: string | null;
|
||||
notes: string;
|
||||
assigned_to: string;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export type FrontendRole = 'admin' | 'recepcion' | 'medico' | 'lectura';
|
||||
|
||||
export interface FrontendUser {
|
||||
id: number;
|
||||
login: string;
|
||||
name: string;
|
||||
role: FrontendRole;
|
||||
must_change_password?: boolean;
|
||||
last_login?: string | null;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
export interface Paginated<T> {
|
||||
status: string;
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
items: T;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API
|
||||
// ============================================================
|
||||
export const odooApi = {
|
||||
// Auth (frontend React)
|
||||
async login(login: string, password: string): Promise<{ status: string; token: string; user: FrontendUser; message?: string }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/auth/login`, { login, password });
|
||||
return data;
|
||||
},
|
||||
|
||||
async me(): Promise<{ status: string; user: FrontendUser }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/auth/me`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async logout(): Promise<{ status: string }> {
|
||||
try {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/auth/logout`);
|
||||
return data;
|
||||
} catch {
|
||||
return { status: 'success' };
|
||||
}
|
||||
},
|
||||
|
||||
async listFrontendUsers(): Promise<{ status: string; users: FrontendUser[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/auth/users`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async createFrontendUser(payload: { login: string; name: string; password: string; role: FrontendRole }): Promise<{ status: string; user: FrontendUser; message?: string }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/auth/users`, payload);
|
||||
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 }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/auth/users/${id}`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Health
|
||||
async healthCheck(): Promise<unknown> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/health`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Dashboard
|
||||
async getDashboard(): Promise<{ status: string; stats: DashboardStats }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/dashboard`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWeeklyChart(): Promise<{ status: string; data: ChartPoint[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/weekly_chart`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Citas
|
||||
async getAppointments(params?: Record<string, string | number>): Promise<{ status: string; appointments: Appointment[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/appointments`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createAppointment(appointment: Partial<Appointment> & { sessions?: number }): Promise<{ status: string; appointment: Appointment; appointments?: Appointment[]; sessions?: number }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/appointments`, appointment);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateAppointment(id: number, appointment: Partial<Appointment>): Promise<{ status: string; appointment: Appointment }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/appointments/${id}`, appointment);
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteAppointment(id: number): Promise<{ status: string; message: string }> {
|
||||
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/appointments/${id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateAppointmentStatus(id: number, action: string, reason?: string): Promise<{ status: string; appointment: Appointment }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/appointments/${id}/status`, { action, reason });
|
||||
return data;
|
||||
},
|
||||
|
||||
async packageFinished(id: number, finished = true): Promise<{ status: string; appointment: Appointment }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/appointments/${id}/package_finished`, { finished });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getAvailableSlots(date: string, servicio_id: number): Promise<{ status: string; slots: { time: number; time_str: string }[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/appointments/slots`, { params: { date, servicio_id } });
|
||||
return data;
|
||||
},
|
||||
|
||||
// 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 }> {
|
||||
const p = typeof params === 'string' ? { search: params } : params || {};
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients`, { params: p });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createPatient(patient: Partial<Patient>): Promise<{ status: string; patient: Patient }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/patients`, patient);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updatePatient(id: number, patient: Partial<Patient>): Promise<{ status: string; patient: Patient }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/patients/${id}`, patient);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getPatientHistory(id: number): Promise<{ status: string; appointments: Appointment[]; sales: Sale[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/patients/${id}/history`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Servicios
|
||||
async getServices(): Promise<{ status: string; services: Service[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/services`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async createService(service: Partial<Service>): Promise<{ status: string; service: Service }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/services`, service);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateService(id: number, service: Partial<Service>): Promise<{ status: string; service: Service }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/services/${id}`, service);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Médicos
|
||||
async getDoctors(): Promise<{ status: string; doctors: Doctor[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/doctors`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateDoctor(id: number, payload: { commission_pct: number }): Promise<{ status: string; doctor: Doctor }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/doctors/${id}`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Comisiones
|
||||
async getCommissions(start?: string, end?: string): Promise<{ status: string; start: string; end: string; total_base: number; total_commission: number; commissions: CommissionRow[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/commissions`, { params: { start, end } });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Productos
|
||||
async getProducts(): Promise<{ status: string; products: Product[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/products`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Monederos
|
||||
async getWallets(params?: string | { phone?: string; search?: string; page?: number; page_size?: number }): Promise<{ status: string; wallets: Wallet[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const p = typeof params === 'string' ? { phone: params } : params || {};
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wallets`, { params: p });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWalletByPhone(phone: string): Promise<{ status: string; wallet: { points: number; equivalent_mxn: number; history: unknown[] } }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wallets/${phone}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async walletTransaction(payload: { phone: string; type: 'accrual' | 'redemption'; points: number; description?: string }): Promise<{ status: string; wallet: Wallet }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wallets/transaction`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Pagos
|
||||
async getPayments(state?: string): Promise<{ status: string; payments: Payment[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/payments`, { params: state ? { state } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createPayment(payment: Partial<Payment>): Promise<{ status: string; payment: Payment }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/payments`, payment);
|
||||
return data;
|
||||
},
|
||||
|
||||
async confirmPayment(id: number): Promise<{ status: string; payment: Payment }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/payments/${id}/confirm`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Ventas
|
||||
async getSales(params?: Record<string, string | number>): Promise<{ status: string; sales: Sale[]; total?: number; page?: number; page_size?: number; total_pages?: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/sales`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createSale(sale: Omit<Partial<Sale>, 'lines'> & { lines: Omit<SaleLine, 'subtotal'>[] }): Promise<{ status: string; sale: Sale }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sales`, sale);
|
||||
return data;
|
||||
},
|
||||
|
||||
async paySale(id: number, payload: { payment_method: string; amount?: number }): Promise<{ status: string; sale: Sale; payment: Payment }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sales/${id}/pay`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async refundSale(id: number, payload: { amount?: number; reason?: string }): Promise<{ status: string; sale: Sale; result: { refund_amount: number; points_reversed: number; new_state: string; new_amount_paid: number } }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sales/${id}/refund`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// Cortes de caja
|
||||
async getCashClosings(date?: string): Promise<{ status: string; cash_closings: CashClosing[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/cash_closings`, { params: date ? { date } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createCashClosing(payload: { date?: string; opening_cash: number; notes?: string }): Promise<{ status: string; cash_closing: CashClosing }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/cash_closings`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async closeCashClosing(id: number, closing_cash: number): Promise<{ status: string; cash_closing: CashClosing }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/cash_closings/${id}/close`, { closing_cash });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Reportes
|
||||
async getSalesReport(start?: string, end?: string): Promise<{ status: string; total_sales: number; total_paid: number; total_due: number; count: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/sales`, { params: { start, end } });
|
||||
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;
|
||||
},
|
||||
|
||||
async getTopServices(start?: string, end?: string, limit = 8): Promise<{ status: string; start: string; end: string; services: { service_id: number; service: string; qty: number; revenue: number }[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/top_services`, { params: { start, end, limit } });
|
||||
return data;
|
||||
},
|
||||
|
||||
async getCashReport(date?: string): Promise<{ status: string; total: number; by_method: Record<string, number> }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/cash`, { params: { date } });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Tipo de cambio
|
||||
async getExchangeRate(): Promise<{ status: string; current: { rate: number; date: string; source: string } | null; history: { id: number; date: string; rate: number; source: string }[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/exchange_rate`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async setExchangeRate(rate: number, source = 'manual'): Promise<{ status: string; exchange_rate: { id: number; date: string; rate: number; source: string } }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/exchange_rate`, { rate, source });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Meta de ventas mensual
|
||||
async getSalesGoal(): Promise<{ status: string; goal: number; current: number; pct: number; start: string; end: string; count: number }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/sales_goal`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async setSalesGoal(goal: number): Promise<{ status: string; goal: number }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sales_goal`, { goal });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Cumpleañeros
|
||||
async getBirthdays(period: 'today' | 'week' | 'month' = 'month'): Promise<{ status: string; period: string; start: string; end: string; total: number; birthdays: Birthday[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/reports/birthdays`, { params: { period } });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Inventario / Consumibles
|
||||
async getInventory(params?: { kind?: InventoryKind; level?: InventoryLevel; search?: string }): Promise<{ status: string; items: InventoryItem[]; summary: InventorySummary }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/inventory/items`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async createInventoryItem(item: Partial<InventoryItem>): Promise<{ status: string; item: InventoryItem }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/inventory/items`, item);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateInventoryItem(id: number, item: Partial<InventoryItem>): Promise<{ status: string; item: InventoryItem }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/inventory/items/${id}`, item);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getInventoryMoves(id: number): Promise<{ status: string; moves: InventoryMove[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/inventory/items/${id}/moves`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async createInventoryMove(id: number, payload: { type: InventoryMoveType; qty: number; reference?: string; notes?: string }): Promise<{ status: string; move: InventoryMove; item: InventoryItem }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/inventory/items/${id}/moves`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// WACRM Proxy
|
||||
// ============================================================
|
||||
async syncWacrm(): Promise<{ status: string; result: Record<string, number | string | null> }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/sync`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmMembers(): Promise<{ status: string; members: { 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;
|
||||
},
|
||||
|
||||
async assignWacrmConversation(external_id: string, assigned_agent_id: string | null): Promise<{ status: string; assigned_agent_id?: string }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/conversations/${external_id}/assign`, {
|
||||
assigned_agent_id,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmConversations(search?: string): Promise<{ status: string; conversations: WacrmConversation[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/conversations`, { params: search ? { search } : {} });
|
||||
return data;
|
||||
},
|
||||
|
||||
async sendWacrmMessage(payload: { conversation_id: string; text: string; assigned_agent_id: string }): Promise<{ status: string; message: string }> {
|
||||
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/wacrm/messages`, payload);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmMessages(search?: string, conversation_id?: string): Promise<{ status: string; messages: WacrmMessage[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/messages`, {
|
||||
params: { ...(search ? { search } : {}), ...(conversation_id ? { conversation_id } : {}) },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmPipelines(): Promise<{ status: string; pipelines: WacrmPipeline[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/pipelines`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async getWacrmLeads(search?: string, status?: string, pipeline_id?: string): Promise<{ status: string; leads: WacrmLead[] }> {
|
||||
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/wacrm/leads`, {
|
||||
params: { ...(search ? { search } : {}), ...(status ? { status } : {}), ...(pipeline_id ? { pipeline_id } : {}) },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateWacrmLeadStatus(id: number, payload: { status?: 'open' | 'won' | 'lost'; stage_id?: string }): Promise<{ status: string; lead: WacrmLead }> {
|
||||
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/wacrm/leads/${id}/status`, payload);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user