Añadido Dockerfil docker-compose.yml

This commit is contained in:
2026-05-30 21:47:44 -05:00
parent 40986b0359
commit 5a563b214f
101 changed files with 10207 additions and 950 deletions
Regular → Executable
+122 -22
View File
@@ -1,40 +1,140 @@
import { z } from 'zod';
export const PacienteSchema = z.object({
// ─── Enums matching DB constraints ─────────────────────────────────────
const patientStatusEnum = z.enum(["prospecto", "activo", "inactivo"]);
const slotStatusEnum = z.enum(["available", "confirmed", "cancelled", "completed", "reschedule", "blocked"]);
const blockTypeEnum = z.enum(["surgery", "admin", "vacation", "event", "appointment"]);
const messageDirectionEnum = z.enum(["in", "out"]);
const messageTypeEnum = z.enum(["text", "audio", "image", "document"]);
const conversationStatusEnum = z.enum(["active", "pending_human", "resolved"]);
const channelEnum = z.enum(["whatsapp", "messenger", "instagram", "tiktok", "web", "manual"]);
const templateTypeEnum = z.enum(["confirmation", "reminder", "reschedule", "post_op", "notification", "news", "surgeon_instructions"]);
const notificationTypeEnum = z.enum([
"new_appointment",
"appointment_cancelled",
"human_handoff",
"new_patient",
"appointment_reminder_2h",
"daily_summary",
"appointment_rescheduled",
"appointment_reminder_next_day",
]);
// ─── Domain Schemas ─────────────────────────────────────────────────────
const phoneRegex = /^[+]?[\d\s\-().]{7,30}$/;
export const PatientSchema = z.object({
id: z.string().uuid().optional(),
nombre: z.string().min(2, "El nombre debe tener al menos 2 caracteres"),
telefono: z.string().optional(),
email: z.string().email("Correo inválido").optional(),
notas_clinicas: z.string().optional(),
name: z.string().min(2, "Name must be at least 2 characters").max(100, "Name too long").trim(),
phone: z.string().regex(phoneRegex, "Invalid phone format").max(30).optional().nullable(),
email: z.string().email("Invalid email").max(255).optional().nullable(),
status: patientStatusEnum.default("prospecto"),
});
export const ProcedimientoSchema = z.object({
export const PatientProfileSchema = z.object({
id: z.string().uuid().optional(),
nombre: z.string().min(2, "Procedimiento requerido"),
duracion_minutos: z.number().int().positive("Obligatorio"),
precio: z.number().positive().optional(),
patient_id: z.string().uuid(),
interested_procedures: z.array(z.string().max(100)).default([]),
notes: z.string().max(5000).optional().nullable(),
last_interaction: z.string().datetime().optional().nullable(),
});
export const CitaSchema = z.object({
export const ProcedureSchema = z.object({
id: z.string().uuid().optional(),
paciente_id: z.string().uuid(),
procedimiento_id: z.string().uuid().optional(),
start_time: z.string().datetime(),
end_time: z.string().datetime(),
status: z.enum(["agendada", "completada", "cancelada"]).default("agendada"),
medico_id: z.string().uuid(),
name: z.string().min(2, "Procedure name is required").max(120),
description: z.string().max(2000).optional().nullable(),
duration_min: z.number().int().positive("Duration is required"),
preparation_notes: z.string().max(2000).optional().nullable(),
is_active: z.boolean().default(true),
});
export const InteraccionSchema = z.object({
export const SlotSchema = z.object({
id: z.string().uuid().optional(),
paciente_id: z.string().uuid(),
plataforma: z.enum(["whatsapp", "instagram", "tiktok", "web"]),
request_payload: z.any(),
start_at: z.string().datetime(),
end_at: z.string().datetime(),
block_type: blockTypeEnum,
patient_id: z.string().uuid().optional().nullable(),
procedure_id: z.string().uuid().optional().nullable(),
status: slotStatusEnum.default("available"),
channel: channelEnum.optional().nullable(),
notes: z.string().max(2000).optional().nullable(),
});
export const MessageSchema = z.object({
id: z.string().uuid().optional(),
patient_id: z.string().uuid(),
conversation_id: z.string().uuid().optional().nullable(),
channel: channelEnum,
direction: messageDirectionEnum,
type: messageTypeEnum,
content: z.string().max(4000).optional().nullable(),
audio_url: z.string().url().optional().nullable(),
transcript: z.string().max(4000).optional().nullable(),
created_at: z.string().datetime().optional(),
});
export const ConversationSchema = z.object({
id: z.string().uuid().optional(),
patient_id: z.string().uuid(),
channel: channelEnum,
status: conversationStatusEnum.default("active"),
ai_context: z.record(z.string(), z.unknown()).optional().nullable(),
last_message_at: z.string().datetime().optional(),
});
export const StaffSchema = z.object({
id: z.string().uuid().optional(),
name: z.string().min(2).max(100).trim(),
role: z.enum(["admin", "staff"]),
email: z.string().trim().email().max(255).toLowerCase(),
is_active: z.boolean().default(true),
});
export const StaffNotificationSchema = z.object({
id: z.string().uuid().optional(),
staff_id: z.string().uuid(),
type: notificationTypeEnum,
payload: z.record(z.string(), z.unknown()).default({}),
is_read: z.boolean().default(false),
created_at: z.string().datetime().optional(),
});
const timeRegex = /^([01]\d|2[0-3]):([0-5]\d)$/;
// WorkingHours: day-keyed object matching DB structure { "mon": ["08:00","18:00"], ... }
export const WorkingHoursSchema = z.record(
z.string(),
z.tuple([z.string().regex(timeRegex), z.string().regex(timeRegex)]).nullable()
);
export const FaqItemSchema = z.object({
q: z.string().min(1).max(500),
a: z.string().min(1).max(2000),
});
export const AgentConfigSchema = z.object({
id: z.string().uuid().optional(),
system_prompt: z.string().min(1).max(8000),
working_hours: WorkingHoursSchema,
faq: z.array(FaqItemSchema).default([]),
enabled_channels: z.array(channelEnum).default(["whatsapp", "web"]),
updated_at: z.string().datetime().optional(),
});
export const MessageTemplateSchema = z.object({
id: z.string().uuid().optional(),
name: z.string().min(1).max(120),
type: templateTypeEnum,
content: z.string().min(1).max(4000),
channel: channelEnum.optional().nullable(),
is_active: z.boolean().default(true),
});
export const ChatStatusSchema = z.object({
id: z.string().uuid().optional(),
paciente_id: z.string().uuid(),
patient_id: z.string().uuid(),
ia_activa: z.boolean().default(true),
last_updated: z.string().datetime().optional()
last_updated: z.string().datetime().optional().nullable(),
});