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

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 15:26
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
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 } });
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub