Skip to main content

sesion-workflow-clinic

Mental health practice management platform with AI-powered scheduling, WhatsApp automation, AFIP billing, and video consultations for Argentine clinics

インストールへ移動

ソース情報

リポジトリ
reason-machines/devtools-skills
ソースの最終更新活動
2026年7月12日 11:58
検出された SKILL.md の言語
英語
スター
4
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
sesion-workflow-clinic
description
Mental health practice management platform with AI-powered scheduling, WhatsApp automation, AFIP billing, and video consultations for Argentine clinics
triggers
["how do I integrate Sesión into my psychology clinic","set up WhatsApp automation for patient appointments","configure AFIP electronic invoicing in Sesión","implement Claude AI for clinical note summarization","create appointment scheduling with Sesión","build video consultation feature with Sesión","configure Mercado Pago payment integration","set up patient journey pipeline in Sesión"]
# Sesión Workflow Clinic > Skill by [ara.so](https://ara.so) — Devtools Skills collection. Sesión is a comprehensive mental health practice orchestration platform designed for psychology clinics in Argentina. It unifies appointment scheduling, automated WhatsApp messaging, AFIP-compliant billing, secure video consultations, and AI-powered clinical assistance using Claude Opus 4.6 and Sonnet 4.6. ## Tech Stack - **Frontend**: SvelteKit 5 + Svelte 5, TailwindCSS - **Backend**: NestJS, Prisma ORM - **Database**: PostgreSQL (relational), Redis (caching), Elasticsearch (search) - **AI**: Anthropic Claude Opus 4.6 & Sonnet 4.6 - **Video**: LiveKit (WebRTC) - **Messaging**: Baileys (WhatsApp automation) - **Payments**: Stripe, Mercado Pago - **Language**: TypeScript ## Installation ### Prerequisites ```bash # Required dependencies node >= 18.0.0 postgresql >= 14 redis >= 6.2 docker >= 20.10 (optional but recommended) ``` ### Clone and Install ```bash git clone https://github.com/fahad-hamid/psique-workflow-clinic.git cd psique-workflow-clinic # Install dependencies npm install # Setup environment variables cp .env.example .env ``` ### Environment Configuration ```bash # .env DATABASE_URL="postgresql://user:password@localhost:5432/sesion" REDIS_URL="redis://localhost:6379" # AI Configuration ANTHROPIC_API_KEY=your_anthropic_key_here CLAUDE_OPUS_MODEL="claude-opus-4.6" CLAUDE_SONNET_MODEL="claude-sonnet-4.6" # WhatsApp Automation WHATSAPP_SESSION_PATH="./whatsapp-session" WHATSAPP_WEBHOOK_SECRET=your_webhook_secret # Video Consultations LIVEKIT_API_KEY=your_livekit_key LIVEKIT_API_SECRET=your_livekit_secret LIVEKIT_WS_URL="wss://your-livekit-instance.com" # Payment Processing STRIPE_SECRET_KEY=your_stripe_key MERCADOPAGO_ACCESS_TOKEN=your_mercadopago_token # AFIP Integration (Argentina Tax Authority) AFIP_CUIT=your_cuit_number AFIP_CERT_PATH="./certs/afip-cert.pem" AFIP_KEY_PATH="./certs/afip-key.pem" ``` ### Database Setup ```bash # Run Prisma migrations npx prisma migrate dev # Generate Prisma client npx prisma generate # Seed initial data npm run db:seed ``` ### Start Development Server ```bash # Start backend (NestJS) npm run dev:api # Start frontend (SvelteKit) npm run dev:web # Start all services with Docker Compose docker-compose up -d ``` ## Core Modules ### 1. Appointment Scheduling **Prisma Schema (appointments)**: ```typescript // prisma/schema.prisma model Appointment { id String @id @default(cuid()) patientId String practitionerId String startTime DateTime endTime DateTime sessionType SessionType status AppointmentStatus @default(SCHEDULED) roomId String? notes String? patient Patient @relation(fields: [patientId], references: [id]) practitioner Practitioner @relation(fields: [practitionerId], references: [id]) room Room? @relation(fields: [roomId], references: [id]) @@index([patientId, startTime]) @@index([practitionerId, startTime]) } enum SessionType { PRESENCIAL VIRTUAL EVALUACION PAREJA FAMILIAR } enum AppointmentStatus { SCHEDULED CONFIRMED IN_PROGRESS COMPLETED CANCELLED NO_SHOW } ``` **Creating Appointments (NestJS Service)**: ```typescript // src/appointments/appointments.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { WhatsAppService } from '../whatsapp/whatsapp.service'; @Injectable() export class AppointmentsService { constructor( private prisma: PrismaService, private whatsapp: WhatsAppService, ) {} async createAppointment(data: CreateAppointmentDto) { // Check for scheduling conflicts const conflicts = await this.prisma.appointment.findMany({ where: { practitionerId: data.practitionerId, status: { not: 'CANCELLED' }, OR: [ { AND: [ { startTime: { lte: data.startTime } }, { endTime: { gt: data.startTime } }, ], }, { AND: [ { startTime: { lt: data.endTime } }, { endTime: { gte: data.endTime } }, ], }, ], }, }); if (conflicts.length > 0) { throw new Error('Scheduling conflict detected'); } const appointment = await this.prisma.appointment.create({ data: { ...data, status: 'SCHEDULED', }, include: { patient: true, practitioner: true, }, }); // Send WhatsApp confirmation await this.whatsapp.sendAppointmentConfirmation(appointment); return appointment; } async getAvailableSlots( practitionerId: string, date: Date, duration: number = 45, ) { const startOfDay = new Date(date); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(date); endOfDay.setHours(23, 59, 59, 999); const existingAppointments = await this.prisma.appointment.findMany({ where: { practitionerId, startTime: { gte: startOfDay, lte: endOfDay }, status: { not: 'CANCELLED' }, }, orderBy: { startTime: 'asc' }, }); // Algorithm to find available slots const workingHours = { start: 9, end: 20 }; // 9 AM to 8 PM const slots: Date[] = []; let currentTime = new Date(date); currentTime.setHours(workingHours.start, 0, 0, 0); const endTime = new Date(date); endTime.setHours(workingHours.end, 0, 0, 0); while (currentTime < endTime) { const slotEnd = new Date(currentTime.getTime() + duration * 60000); const hasConflict = existingAppointments.some(apt => { return currentTime < apt.endTime && slotEnd > apt.startTime; }); if (!hasConflict) { slots.push(new Date(currentTime)); } currentTime = new Date(currentTime.getTime() + 15 * 60000); // 15-min increments } return slots; } } ``` **SvelteKit Appointment Scheduler**: ```svelte <!-- src/routes/appointments/+page.svelte --> <script lang="ts"> import { onMount } from 'svelte'; import type { Appointment } from '$lib/types'; let selectedDate = $state(new Date()); let selectedPractitioner = $state(''); let availableSlots = $state<Date[]>([]); let loading = $state(false); async function loadAvailableSlots() { loading = true; try { const response = await fetch('/api/appointments/available-slots', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ practitionerId: selectedPractitioner, date: selectedDate.toISOString(), duration: 45, }), }); availableSlots = await response.json(); } finally { loading = false; } } async function bookAppointment(slot: Date) { const response = await fetch('/api/appointments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ practitionerId: selectedPractitioner, patientId: $page.data.currentPatient.id, startTime: slot, endTime: new Date(slot.getTime() + 45 * 60000), sessionType: 'VIRTUAL', }), }); if (response.ok) { alert('Cita agendada correctamente'); loadAvailableSlots(); } } $effect(() => { if (selectedPractitioner && selectedDate) { loadAvailableSlots(); } }); </script> <div class="appointment-scheduler"> <h2 class="text-2xl font-bold mb-4">Agendar Sesión</h2> <input type="date" bind:value={selectedDate} class="border rounded px-4 py-2 mb-4" /> {#if loading} <p class="text-gray-500">Cargando horarios disponibles...</p> {:else if availableSlots.length === 0} <p class="text-gray-500">No hay horarios disponibles para esta fecha</p> {:else} <div class="grid grid-cols-4 gap-2"> {#each availableSlots as slot} <button onclick={() => bookAppointment(slot)} class="border border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white rounded px-4 py-2 transition" > {slot.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })} </button> {/each} </div> {/if} </div> ``` ### 2. WhatsApp Automation **WhatsApp Service with Baileys**: ```typescript // src/whatsapp/whatsapp.service.ts import { Injectable } from '@nestjs/common'; import makeWASocket, { useMultiFileAuthState, DisconnectReason, MessageType, } from '@whiskeysockets/baileys'; import { Boom } from '@hapi/boom'; @Injectable() export class WhatsAppService { private socket: any; private sessionPath = process.env.WHATSAPP_SESSION_PATH || './wa-session'; async initialize() { const { state, saveCreds } = await useMultiFileAuthState(this.sessionPath); 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.initialize(); // Reconnect } } }); this.socket.ev.on('messages.upsert', async ({ messages }) => { await this.handleIncomingMessages(messages); }); } async sendAppointmentConfirmation(appointment: any) { const patient = appointment.patient; const message = `Hola ${patient.firstName}! 👋\n\n` + `Tu sesión con ${appointment.practitioner.name} ha sido confirmada:\n\n` + `📅 Fecha: ${appointment.startTime.toLocaleDateString('es-AR')}\n` + `🕐 Hora: ${appointment.startTime.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })}\n` + `💻 Modalidad: ${appointment.sessionType}\n\n` + `Por favor responde *SI* para confirmar tu asistencia.`; await this.socket.sendMessage( `${patient.phoneNumber}@s.whatsapp.net`, { text: message }, ); } async sendReminder24Hours(appointment: any) { const patient = appointment.patient; const message = `Recordatorio: Tu sesión es mañana a las ${appointment.startTime.toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })} 📆`; await this.socket.sendMessage( `${patient.phoneNumber}@s.whatsapp.net`, { text: message }, ); } private async handleIncomingMessages(messages: any[]) { for (const msg of messages) { if (msg.key.fromMe) continue; const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text; const from = msg.key.remoteJid; // Crisis keywords detection const crisisKeywords = ['suicidio', 'morir', 'acabar con todo', 'no puedo más']; if (crisisKeywords.some(keyword => text?.toLowerCase().includes(keyword))) { await this.escalateCrisis(from, text); } // Confirmation parsing if (text?.toLowerCase() === 'si' || text?.toLowerCase() === 'sí') { await this.handleConfirmation(from); } } } private async escalateCrisis(phoneNumber: string, message: string) { // Notify practitioner immediately // Log to crisis management system console.error(`CRISIS ALERT from ${phoneNumber}: ${message}`); // Trigger emergency notification workflow }
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る