Skip to main content

psique-workflow-clinic

Sesión - Mental health practice management platform with AI orchestration, WhatsApp automation, AFIP billing, and video consultations for Argentine psychology clinics

跳到安装

来源信息

仓库
reason-machines/devtools-skills
最近来源活动
2026年7月12日 15:26
检测到的 SKILL.md 语言
英语
星标
4
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
psique-workflow-clinic
description
Sesión - Mental health practice management platform with AI orchestration, WhatsApp automation, AFIP billing, and video consultations for Argentine psychology clinics
triggers
["how do I set up Sesión for a psychology clinic","configure WhatsApp automation for patient reminders","integrate AFIP electronic invoicing in Sesión","set up Claude AI for clinical note summarization","implement video consultation workflow","configure patient journey pipeline stages","automate appointment scheduling with conflict detection","integrate Mercado Pago payment processing"]
# Sesión Workflow Clinic > Skill by [ara.so](https://ara.so) — Devtools Skills collection. Sesión is a comprehensive SaaS platform for psychology clinics in Argentina that unifies appointment scheduling, WhatsApp automation, AFIP-compliant billing, secure video consultations, and AI-powered clinical assistance using Claude Opus 4.6 and Sonnet 4.6. ## Architecture Overview Sesión uses a microservices architecture with: - **Frontend**: SvelteKit 5 with TypeScript and TailwindCSS - **Backend**: NestJS microservices - **Database**: PostgreSQL with Prisma ORM - **Cache**: Redis for sessions and real-time presence - **Search**: Elasticsearch for patient records - **Storage**: MinIO for encrypted documents - **Messaging**: Apache Kafka for event-driven workflows - **WhatsApp**: Baileys library for WhatsApp Business integration - **Video**: LiveKit for WebRTC video consultations - **AI**: Anthropic Claude Opus 4.6 and Sonnet 4.6 - **Payments**: Stripe and Mercado Pago integration ## Installation ### Prerequisites ```bash # Required versions node --version # v20+ npm --version # v10+ docker --version # v24+ ``` ### Clone and Install ```bash git clone https://github.com/fahad-hamid/psique-workflow-clinic.git cd psique-workflow-clinic npm install ``` ### Environment Configuration Create `.env` file in project root: ```env # Database DATABASE_URL="postgresql://user:password@localhost:5432/sesion" # Redis REDIS_URL="redis://localhost:6379" # Anthropic AI ANTHROPIC_API_KEY="sk-ant-..." CLAUDE_OPUS_MODEL="claude-opus-4.6" CLAUDE_SONNET_MODEL="claude-sonnet-4.6" # WhatsApp (Baileys) WHATSAPP_SESSION_PATH="./whatsapp-sessions" WHATSAPP_WEBHOOK_SECRET="your-webhook-secret" # LiveKit Video LIVEKIT_API_KEY="your-livekit-api-key" LIVEKIT_API_SECRET="your-livekit-api-secret" LIVEKIT_URL="wss://your-livekit-server.com" # AFIP (Argentina Tax Authority) AFIP_CUIT="20123456789" AFIP_CERTIFICATE_PATH="./certificates/afip-cert.pem" AFIP_PRIVATE_KEY_PATH="./certificates/afip-key.pem" AFIP_ENVIRONMENT="production" # or "homologacion" for testing # Payment Gateways MERCADOPAGO_ACCESS_TOKEN="APP_USR-..." STRIPE_SECRET_KEY="sk_live_..." STRIPE_WEBHOOK_SECRET="whsec_..." # Application JWT_SECRET="your-jwt-secret" FRONTEND_URL="https://app.sesion.com.ar" BACKEND_URL="https://api.sesion.com.ar" ``` ### Database Setup ```bash # Generate Prisma client npx prisma generate # Run migrations npx prisma migrate deploy # Seed initial data (optional) npx prisma db seed ``` ### Start Services ```bash # Development mode with hot reload npm run dev # Production build npm run build npm run start # Docker Compose (all services) docker-compose up -d ``` ## Core Module Usage ### 1. Appointment Scheduling Engine **Schema Definition** (Prisma): ```prisma model Appointment { id String @id @default(cuid()) patientId String practitionerId String sessionType SessionType startTime DateTime endTime DateTime status AppointmentStatus roomId String? isVirtual Boolean @default(false) notes String? patient Patient @relation(fields: [patientId], references: [id]) practitioner Practitioner @relation(fields: [practitionerId], references: [id]) room Room? @relation(fields: [roomId], references: [id]) @@index([practitionerId, startTime]) @@index([patientId]) } enum SessionType { INDIVIDUAL COUPLES FAMILY EVALUATION FOLLOWUP } enum AppointmentStatus { SCHEDULED CONFIRMED CANCELLED COMPLETED NO_SHOW } ``` **Creating Appointments with Conflict Detection**: ```typescript // services/appointment.service.ts import { Injectable, ConflictException } from '@nestjs/common'; import { PrismaService } from './prisma.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; @Injectable() export class AppointmentService { constructor( private prisma: PrismaService, private eventEmitter: EventEmitter2 ) {} async createAppointment(data: CreateAppointmentDto) { // Check for scheduling conflicts const conflicts = await this.findConflicts( data.practitionerId, data.startTime, data.endTime, data.roomId ); if (conflicts.length > 0) { throw new ConflictException({ message: 'Scheduling conflict detected', conflicts: conflicts, suggestions: await this.suggestAlternatives(data) }); } const appointment = await this.prisma.appointment.create({ data: { patientId: data.patientId, practitionerId: data.practitionerId, sessionType: data.sessionType, startTime: data.startTime, endTime: data.endTime, isVirtual: data.isVirtual, roomId: data.roomId, status: 'SCHEDULED' }, include: { patient: true, practitioner: true } }); // Emit event for WhatsApp automation this.eventEmitter.emit('appointment.created', appointment); return appointment; } private async findConflicts( practitionerId: string, startTime: Date, endTime: Date, roomId?: string ) { return this.prisma.appointment.findMany({ where: { AND: [ { OR: [ { practitionerId }, { roomId: roomId || undefined } ] }, { status: { in: ['SCHEDULED', 'CONFIRMED'] } }, { OR: [ { AND: [ { startTime: { lte: startTime } }, { endTime: { gt: startTime } } ] }, { AND: [ { startTime: { lt: endTime } }, { endTime: { gte: endTime } } ] }, { AND: [ { startTime: { gte: startTime } }, { endTime: { lte: endTime } } ] } ] } ] } }); } private async suggestAlternatives(data: CreateAppointmentDto) { const duration = data.endTime.getTime() - data.startTime.getTime(); const searchStart = new Date(data.startTime); searchStart.setDate(searchStart.getDate() - 1); const searchEnd = new Date(data.startTime); searchEnd.setDate(searchEnd.getDate() + 7); // Find free slots in the next week const allSlots = await this.generateTimeSlots( data.practitionerId, searchStart, searchEnd, duration ); return allSlots.slice(0, 5); } } ``` ### 2. WhatsApp Automation with Baileys **WhatsApp Service Implementation**: ```typescript // services/whatsapp.service.ts import makeWASocket, { DisconnectReason, useMultiFileAuthState, WAMessage } from '@whiskeysockets/baileys'; import { Boom } from '@hapi/boom'; import { OnEvent } from '@nestjs/event-emitter'; @Injectable() export class WhatsAppService { private sock: any; private qrCode: string; async initialize() { const { state, saveCreds } = await useMultiFileAuthState( process.env.WHATSAPP_SESSION_PATH ); this.sock = makeWASocket({ auth: state, printQRInTerminal: true }); this.sock.ev.on('connection.update', (update) => { const { connection, lastDisconnect, qr } = update; if (qr) { this.qrCode = qr; } if (connection === 'close') { const shouldReconnect = (lastDisconnect?.error as Boom)?.output ?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) { this.initialize(); } } }); this.sock.ev.on('creds.update', saveCreds); this.sock.ev.on('messages.upsert', this.handleIncomingMessage.bind(this)); } @OnEvent('appointment.created') async sendAppointmentConfirmation(appointment: any) { const patient = appointment.patient; const practitioner = appointment.practitioner; const message = this.formatConfirmationMessage(appointment); await this.sendMessage(patient.phoneNumber, message, { buttons: [ { buttonId: `confirm:${appointment.id}`, buttonText: { displayText: 'Confirmar ✅' } }, { buttonId: `cancel:${appointment.id}`, buttonText: { displayText: 'Cancelar ❌' } } ] }); } @OnEvent('appointment.reminder.24h') async send24HourReminder(appointment: any) { const message = ` Hola ${appointment.patient.firstName}! 👋 Recordatorio: tenés turno mañana a las ${this.formatTime(appointment.startTime)} con ${appointment.practitioner.fullName}. ${appointment.isVirtual ? '📹 Sesión virtual - El link se enviará 15 minutos antes.' : '📍 Dirección: ' + appointment.practitioner.address} ¿Confirmás tu asistencia? `.trim(); await this.sendMessage(appointment.patient.phoneNumber, message); } private async sendMessage( phoneNumber: string, text: string, options?: any ) { const jid = `${phoneNumber}@s.whatsapp.net`; if (options?.buttons) { await this.sock.sendMessage(jid, { text, footer: 'Sesión - Tu agenda inteligente', buttons: options.buttons, headerType: 1 }); } else { await this.sock.sendMessage(jid, { text }); } } private async handleIncomingMessage(m: { messages: WAMessage[] }) { const message = m.messages[0]; if (!message.message) return; const text = message.message.conversation || message.message.extendedTextMessage?.text || ''; // Crisis detection keywords const crisisKeywords = [ 'suicidio', 'quitarme la vida', 'no puedo más', 'voy a hacerme daño', 'crisis', 'emergencia' ]; const isCrisis = crisisKeywords.some(keyword => text.toLowerCase().includes(keyword) ); if (isCrisis) { await this.escalateCrisis(message); } // Button response handling if (message.message.buttonsResponseMessage) { const buttonId = message.message.buttonsResponseMessage.selectedButtonId; await this.handleButtonResponse(buttonId, message); } } private formatTime(date: Date): string { return new Intl.DateTimeFormat('es-AR', { hour: '2-digit', minute: '2-digit', hour12: false }).format(date); } } ``` ### 3. AFIP Electronic Invoicing **Invoice Generation Service**: ```typescript // services/afip-billing.service.ts import { Injectable } from '@nestjs/common'; import { readFileSync } from 'fs'; import { Afip } from '@afipsdk/afip.js'; @Injectable() export class AfipBillingService { private afip: Afip; constructor() { this.afip = new Afip({ CUIT: process.env.AFIP_CUIT, cert: readFileSync(process.env.AFIP_CERTIFICATE_PATH, 'utf8'), key: readFileSync(process.env.AFIP_PRIVATE_KEY_PATH, 'utf8'), production: process.env.AFIP_ENVIRONMENT === 'production' }); } async generateInvoice(sessionData: GenerateInvoiceDto) { // Determine invoice type based on practitioner's fiscal category const invoiceType = this.determineInvoiceType(sessionData.practitionerFiscalCategory); const lastVoucher = await this.afip.ElectronicBilling.getLastVoucher( sessionData.salePoint, invoiceType ); const invoiceNumber = lastVoucher + 1; const invoiceData = { 'CantReg': 1, 'PtoVta': sessionData.salePoint, 'CbteTipo': invoiceType, 'Concepto': 2, // Services 'DocTipo': 96, // DNI 'DocNro': sessionData.patientDni, 'CbteDesde': invoiceNumber, 'CbteHasta': invoiceNumber, 'CbteFch': this.formatAfipDate(new Date()), 'FchServDesde': this.formatAfipDate(sessionData.sessionDate), 'FchServHasta': this.formatAfipDate(sessionData.sessionDate), 'FchVtoPago': this.formatAfipDate(sessionData.dueDate), 'ImpTotal': sessionData.amount, 'ImpTotConc': 0, 'ImpNeto': sessionData.amount, 'ImpOpEx': 0, 'ImpIVA': 0, 'ImpTrib': 0, 'MonId': 'PES', 'MonCotiz': 1 }; const result = await this.afip.ElectronicBilling.createVoucher(invoiceData); // Store invoice in database const invoice = await this.prisma.invoice.create({ data: { cae: result.CAE, caeExpirationDate: result.CAEFchVto, invoiceNumber: invoiceNumber, invoiceType: invoiceType, salePoint: sessionData.salePoint, amount: sessionData.amount, patientId: sessionData.patientId, sessionId: sessionData.sessionId, afipResponse: result } });
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看