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에서 보기