fase 1 productizacion: capa de configuracion (glosario multi-giro, sucursales y categorias configurables, ajustes de clinica por instancia, colores de marca, horarios, URLs de pago dinamicas) + script de provisioning de instancia nueva con datos demo + frontend consumiendo la config

This commit is contained in:
2026-10-11 05:50:18 +00:00
parent 28118ce732
commit f92018f88e
50 changed files with 4314 additions and 210 deletions

View File

@@ -905,6 +905,9 @@ export interface ClinicBrand {
logo_claro: string; // fondos oscuros (login SKEEN, sidebar)
logo_oscuro: string; // fondos claros (tema HomeNest)
pie: string; // línea del pie (login / documentos)
url_publica?: string; // URL pública de la instancia (opcional)
// Colores de marca (opcional). Vacíos = se usa el color del tema activo.
colores?: { primario?: string; acento?: string };
}
export const BRAND_DEFAULT: ClinicBrand = {
@@ -914,6 +917,80 @@ export const BRAND_DEFAULT: ClinicBrand = {
pie: 'SKEEN Derma Experts · Playas de Rosarito, B.C.',
};
// ---- Glosario / terminología white-label ----
export const GLOSSARY_KEYS = [
'patient_singular', 'patient_plural',
'appointment_singular', 'appointment_plural',
'visit_singular', 'visit_plural',
'record_singular', 'record_plural',
'doctor_singular', 'doctor_plural',
'staff_singular', 'staff_plural',
'branch_singular', 'branch_plural',
'room_singular', 'room_plural',
'service_singular', 'service_plural',
'package_singular', 'package_plural',
'prescription_singular', 'prescription_plural',
'treatment_singular', 'treatment_plural',
] as const;
export type GlossaryKey = typeof GLOSSARY_KEYS[number];
export type GlossaryTerms = Record<GlossaryKey, string>;
export interface GlossaryPack { code: string; name: string; }
// ---- Sucursales ----
export interface Sucursal {
id: number;
code: string;
name: string;
sequence: number;
active: boolean;
is_default: boolean;
address: string;
phone: string;
notes: string;
}
// ---- Categorías de servicio ----
export interface ServiceCategory {
id: number;
code: string;
name: string;
sequence: number;
active: boolean;
is_system: boolean;
}
// ---- Ajustes de la clínica ----
export interface ClinicSettings {
nombre: string;
telefono: string;
email: string;
direccion: string;
web: string;
rfc: string;
horario_apertura: string;
horario_cierre: string;
zona_horaria: string;
url_publica: string;
moneda_base: string;
nota_ticket: string;
}
export const CLINIC_SETTINGS_DEFAULT: ClinicSettings = {
nombre: BRAND_DEFAULT.nombre,
telefono: '',
email: '',
direccion: '',
web: '',
rfc: '',
horario_apertura: '08:00',
horario_cierre: '19:00',
zona_horaria: 'America/Tijuana',
url_publica: '',
moneda_base: 'MXN',
nota_ticket: '',
};
export interface FrontendUser {
id: number;
login: string;
@@ -1397,6 +1474,79 @@ export const odooApi = {
return data;
},
// ---- Glosario / terminología ----
async getGlosario(): Promise<{ status: string; pack: string; terms: GlossaryTerms; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/glosario`);
return data;
},
async getGlosarioPacks(): Promise<{ status: string; packs: GlossaryPack[]; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/glosario/packs`);
return data;
},
async putGlosario(payload: { pack?: string; overrides?: Partial<GlossaryTerms>; motivo?: string }): Promise<{ status: string; pack: string; terms: GlossaryTerms; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/glosario`, payload);
return data;
},
// ---- Sucursales ----
async getSucursales(opts?: { includeInactive?: boolean }): Promise<{ status: string; sucursales: Sucursal[]; total: number; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/sucursales`, {
params: opts?.includeInactive ? { include_inactive: 1 } : undefined,
});
return data;
},
async createSucursal(payload: Partial<Sucursal>): Promise<{ status: string; sucursal: Sucursal; message?: string }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/sucursales`, payload);
return data;
},
async updateSucursal(id: number, payload: Partial<Sucursal>): Promise<{ status: string; sucursal: Sucursal; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/sucursales/${id}`, payload);
return data;
},
async deleteSucursal(id: number): Promise<{ status: string; sucursal: Sucursal; en_uso: number; message: string }> {
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/sucursales/${id}`);
return data;
},
// ---- Categorías de servicio ----
async getServiceCategories(opts?: { includeInactive?: boolean }): Promise<{ status: string; categories: ServiceCategory[]; total: number; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/service-categories`, {
params: opts?.includeInactive ? { include_inactive: 1 } : undefined,
});
return data;
},
async createServiceCategory(payload: { name: string; code?: string; sequence?: number; active?: boolean }): Promise<{ status: string; category: ServiceCategory; message?: string }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/service-categories`, payload);
return data;
},
async updateServiceCategory(id: number, payload: { name?: string; code?: string; sequence?: number; active?: boolean }): Promise<{ status: string; category: ServiceCategory; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/service-categories/${id}`, payload);
return data;
},
async deleteServiceCategory(id: number): Promise<{ status: string; category?: ServiceCategory; en_uso?: number; message?: string }> {
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/service-categories/${id}`);
return data;
},
// ---- Ajustes de la clínica ----
async getClinicSettings(): Promise<{ status: string; settings: ClinicSettings; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/clinic-settings`);
return data;
},
async putClinicSettings(settings: Partial<ClinicSettings> & { motivo?: string }): Promise<{ status: string; settings: ClinicSettings; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/clinic-settings`, settings);
return data;
},
async updateRolePermissions(matrix: Record<string, FrontendRole[]>): Promise<{ status: string; matrix: Record<string, FrontendRole[]>; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/roles/permissions`, { matrix });
return data;
@@ -1430,7 +1580,7 @@ export const odooApi = {
return data;
},
async createQuickAppointment(payload: { name: string; phone: string; date: string; time: number; doctor_id?: number; notes?: string; duration?: number }): Promise<{ status: string; appointment: Appointment; patient: Patient; patient_created: boolean; message?: string }> {
async createQuickAppointment(payload: { name: string; phone: string; date: string; time: number; doctor_id?: number; notes?: string; duration?: number; branch?: string }): Promise<{ status: string; appointment: Appointment; patient: Patient; patient_created: boolean; message?: string }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/appointments/quick`, payload);
return data;
},