Skip to main content

sesion-mental-health-clinic-platform

AI-powered practice management platform for psychologists with scheduling, WhatsApp automation, AFIP billing, video calls, and Claude AI integration

Ir para a instalação

Informações da origem

Repositório
reason-machines/devtools-skills
Última atividade na origem
12 de julho de 2026 às 12:52
Idioma detectado do SKILL.md
inglês
Estrelas
4
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
sesion-mental-health-clinic-platform
description
AI-powered practice management platform for psychologists with scheduling, WhatsApp automation, AFIP billing, video calls, and Claude AI integration
triggers
["set up sesion mental health platform","configure sesion for psychology clinic","integrate claude ai with sesion","configure whatsapp automation for clinic","set up afip electronic invoicing","configure livekit video consultations","deploy sesion workflow orchestration","configure mercadopago billing integration"]
# Sesión Mental Health Clinic Platform > Skill by [ara.so](https://ara.so) — Devtools Skills collection. Sesión is a comprehensive SaaS platform for psychology clinics in Argentina that orchestrates appointment scheduling, WhatsApp automation (via Baileys), AFIP-compliant electronic invoicing, secure video consultations (LiveKit), and AI-powered clinical assistance using Claude Opus 4.6 and Sonnet 4.6. ## Architecture Overview Sesión uses a microservices architecture with: - **Backend**: NestJS (TypeScript) with Prisma ORM - **Frontend**: SvelteKit 5 with Tailwind CSS - **Database**: PostgreSQL with Prisma - **AI**: Anthropic Claude (Opus 4.6 for deep analysis, Sonnet 4.6 for real-time) - **Video**: LiveKit WebRTC infrastructure - **Messaging**: Baileys (WhatsApp Web API) - **Payments**: Stripe (international), MercadoPago (Argentina) - **Caching**: Redis for sessions and real-time data ## Installation & Setup ### Prerequisites ```bash # Required versions node >= 20.x postgresql >= 15.x redis >= 7.x ``` ### Clone and Install ```bash git clone https://github.com/fahad-hamid/psique-workflow-clinic.git cd psique-workflow-clinic # Install dependencies for backend cd backend npm install # Install dependencies for frontend cd ../frontend npm install ``` ### Environment Configuration Create `.env` files in both backend and frontend directories: **Backend `.env`:** ```env # Database DATABASE_URL="postgresql://user:password@localhost:5432/sesion_db" # Redis REDIS_URL="redis://localhost:6379" # Anthropic Claude AI ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY}" CLAUDE_OPUS_MODEL="claude-opus-4.6" CLAUDE_SONNET_MODEL="claude-sonnet-4.6" # WhatsApp (Baileys) WHATSAPP_SESSION_DIR="./whatsapp-sessions" WHATSAPP_WEBHOOK_SECRET="${WHATSAPP_WEBHOOK_SECRET}" # LiveKit Video LIVEKIT_API_KEY="${LIVEKIT_API_KEY}" LIVEKIT_API_SECRET="${LIVEKIT_API_SECRET}" LIVEKIT_WS_URL="wss://your-livekit-server.com" # AFIP (Argentina Tax Authority) AFIP_CUIT="${AFIP_CUIT}" AFIP_CERT_PATH="./certs/afip-cert.pem" AFIP_KEY_PATH="./certs/afip-key.pem" AFIP_ENVIRONMENT="testing" # or "production" # Payment Providers STRIPE_SECRET_KEY="${STRIPE_SECRET_KEY}" STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET}" MERCADOPAGO_ACCESS_TOKEN="${MERCADOPAGO_ACCESS_TOKEN}" MERCADOPAGO_PUBLIC_KEY="${MERCADOPAGO_PUBLIC_KEY}" # Application JWT_SECRET="${JWT_SECRET}" APP_URL="http://localhost:3000" API_URL="http://localhost:4000" ``` **Frontend `.env`:** ```env PUBLIC_API_URL="http://localhost:4000" PUBLIC_LIVEKIT_WS_URL="wss://your-livekit-server.com" ``` ### Database Setup ```bash cd backend # Generate Prisma client npx prisma generate # Run migrations npx prisma migrate deploy # Seed initial data (optional) npx prisma db seed ``` ### Start Development Servers ```bash # Terminal 1 - Backend (NestJS) cd backend npm run start:dev # Terminal 2 - Frontend (SvelteKit) cd frontend npm run dev # Terminal 3 - Redis (if not running as service) redis-server ``` ## Core Module Usage ### 1. Appointment Scheduling Module **Creating an appointment with conflict detection:** ```typescript import { Injectable } from '@nestjs/common'; import { PrismaService } from './prisma.service'; import { AppointmentConflictError } from './errors'; @Injectable() export class AppointmentService { constructor(private prisma: PrismaService) {} async createAppointment(data: { patientId: string; practitionerId: string; startTime: Date; duration: number; // minutes type: 'presencial' | 'virtual' | 'evaluacion'; }) { const endTime = new Date(data.startTime.getTime() + data.duration * 60000); // Check for conflicts const conflicts = await this.prisma.appointment.findMany({ where: { practitionerId: data.practitionerId, OR: [ { startTime: { lte: data.startTime }, endTime: { gt: data.startTime }, }, { startTime: { lt: endTime }, endTime: { gte: endTime }, }, ], status: { not: 'cancelled' }, }, }); if (conflicts.length > 0) { throw new AppointmentConflictError( 'Practitioner has conflicting appointments' ); } const appointment = await this.prisma.appointment.create({ data: { ...data, endTime, status: 'scheduled', }, include: { patient: true, practitioner: true, }, }); // Trigger WhatsApp confirmation await this.whatsappService.sendAppointmentConfirmation(appointment); return appointment; } async suggestAlternativeSlots( practitionerId: string, preferredDate: Date, duration: number ) { const startOfDay = new Date(preferredDate); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(preferredDate); endOfDay.setHours(23, 59, 59, 999); const bookedSlots = await this.prisma.appointment.findMany({ where: { practitionerId, startTime: { gte: startOfDay, lte: endOfDay }, status: { not: 'cancelled' }, }, select: { startTime: true, endTime: true }, orderBy: { startTime: 'asc' }, }); // Get practitioner availability rules const availability = await this.prisma.practitionerAvailability.findMany({ where: { practitionerId, dayOfWeek: preferredDate.getDay() }, }); const suggestions: Date[] = []; for (const slot of availability) { const slotStart = new Date(preferredDate); slotStart.setHours(slot.startHour, slot.startMinute, 0, 0); const slotEnd = new Date(preferredDate); slotEnd.setHours(slot.endHour, slot.endMinute, 0, 0); let currentTime = slotStart; while (currentTime.getTime() + duration * 60000 <= slotEnd.getTime()) { const isFree = !bookedSlots.some( (booked) => currentTime >= booked.startTime && currentTime < booked.endTime ); if (isFree) { suggestions.push(new Date(currentTime)); } currentTime = new Date(currentTime.getTime() + 15 * 60000); // 15-min increments } } return suggestions.slice(0, 5); // Return top 5 suggestions } } ``` ### 2. WhatsApp Automation (Baileys Integration) **Setting up WhatsApp session and sending automated reminders:** ```typescript import { Injectable, OnModuleInit } from '@nestjs/common'; import makeWASocket, { DisconnectReason, useMultiFileAuthState, WAMessage, } from '@whiskeysockets/baileys'; import { Boom } from '@hapi/boom'; import * as cron from 'node-cron'; @Injectable() export class WhatsAppService implements OnModuleInit { private socket: ReturnType<typeof makeWASocket>; async onModuleInit() { await this.initializeWhatsApp(); this.scheduleReminders(); } private async initializeWhatsApp() { const { state, saveCreds } = await useMultiFileAuthState( process.env.WHATSAPP_SESSION_DIR || './whatsapp-sessions' ); this.socket = makeWASocket({ auth: state, printQRInTerminal: true, }); this.socket.ev.on('creds.update', saveCreds); this.socket.ev.on('connection.update', (update) => { const { connection, lastDisconnect } = update; if (connection === 'close') { const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) { this.initializeWhatsApp(); } } }); // Handle incoming messages this.socket.ev.on('messages.upsert', async (m) => { await this.handleIncomingMessage(m.messages[0]); }); } async sendAppointmentConfirmation(appointment: { id: string; patient: { name: string; phone: string }; startTime: Date; type: string; }) { const message = `Hola ${appointment.patient.name},\n\n` + `Tu sesión ${appointment.type} está confirmada para:\n` + `📅 ${appointment.startTime.toLocaleDateString('es-AR')}\n` + `🕒 ${appointment.startTime.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })}\n\n` + `Por favor responde *CONFIRMAR* para confirmar tu asistencia.\n\n` + `Sesión - Tu asistente de salud mental`; await this.socket.sendMessage( this.formatPhoneNumber(appointment.patient.phone), { text: message } ); } private scheduleReminders() { // Run every hour cron.schedule('0 * * * *', async () => { const now = new Date(); const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000); const in2Hours = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 24-hour reminders const upcoming24h = await this.prisma.appointment.findMany({ where: { startTime: { gte: new Date(in24Hours.getTime() - 30 * 60 * 1000), // 30-min window lte: new Date(in24Hours.getTime() + 30 * 60 * 1000), }, status: 'scheduled', reminderSent24h: false, }, include: { patient: true }, }); for (const apt of upcoming24h) { await this.sendReminder(apt, '24 horas'); await this.prisma.appointment.update({ where: { id: apt.id }, data: { reminderSent24h: true }, }); } // 2-hour reminders const upcoming2h = await this.prisma.appointment.findMany({ where: { startTime: { gte: new Date(in2Hours.getTime() - 15 * 60 * 1000), lte: new Date(in2Hours.getTime() + 15 * 60 * 1000), }, status: 'scheduled', reminderSent2h: false, }, include: { patient: true }, }); for (const apt of upcoming2h) { await this.sendReminder(apt, '2 horas'); await this.prisma.appointment.update({ where: { id: apt.id }, data: { reminderSent2h: true }, }); } }); } private async sendReminder(appointment: any, timeframe: string) { const message = `🔔 Recordatorio: Tu sesión es en ${timeframe}\n\n` + `📅 ${appointment.startTime.toLocaleDateString('es-AR')}\n` + `🕒 ${appointment.startTime.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })}\n\n` + `Nos vemos pronto!`; await this.socket.sendMessage( this.formatPhoneNumber(appointment.patient.phone), { text: message } ); } private async handleIncomingMessage(message: WAMessage) { if (!message.message || message.key.fromMe) return; const text = message.message.conversation || message.message.extendedTextMessage?.text || ''; const from = message.key.remoteJid; // Crisis detection keywords const crisisKeywords = ['suicidio', 'morir', 'acabar', 'terminar todo', 'crisis']; if (crisisKeywords.some(keyword => text.toLowerCase().includes(keyword))) { await this.handleCrisisEscalation(from, text); return; } // Appointment confirmation parsing if (text.toLowerCase().includes('confirmar')) { await this.handleAppointmentConfirmation(from); } } private async handleCrisisEscalation(phone: string, message: string) { // Alert practitioner immediately const patient = await this.prisma.patient.findUnique({ where: { phone: this.normalizePhoneNumber(phone) }, include: { practitioner: true }, }); if (patient?.practitioner) { // Send urgent notification to practitioner await this.socket.sendMessage( this.formatPhoneNumber(patient.practitioner.phone), { text: `🚨 ALERTA DE CRISIS 🚨\n\nPaciente: ${patient.name}\nMensaje: "${message}"\n\nContactar inmediatamente.`, } ); } // Send supportive auto-response await this.socket.sendMessage(phone, {
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub