Skip to main content

sesion-clinic-workflow

Mental health practice management platform with intelligent scheduling, WhatsApp automation, AFIP billing, video consultations, and Claude AI orchestration for Argentine psychologists

설치로 이동

소스 정보

저장소
reason-machines/devtools-skills
최근 소스 활동
2026년 7월 12일 10:07
감지된 SKILL.md 언어
영어
스타
4
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
sesion-clinic-workflow
description
Mental health practice management platform with intelligent scheduling, WhatsApp automation, AFIP billing, video consultations, and Claude AI orchestration for Argentine psychologists
triggers
["set up Sesión clinic workflow platform","integrate WhatsApp automation for patient appointments","configure AFIP electronic invoicing for psychology practice","implement Claude AI for clinical note summarization","build video consultation with LiveKit integration","create automated appointment reminders via WhatsApp","configure MercadoPago payment processing for sessions","set up multi-practitioner scheduling system"]
# Sesión Clinic Workflow 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, automated WhatsApp messaging via Baileys, AFIP-compliant electronic invoicing, secure LiveKit video consultations, and AI-powered clinical assistance using Claude Opus 4.6 and Sonnet 4.6. Built with NestJS backend, SvelteKit 5 frontend, Prisma ORM, and designed for Argentine healthcare compliance. ## Installation & Setup ### Prerequisites ```bash # Required versions node >= 20.x pnpm >= 8.x postgres >= 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 monorepo pnpm install # Set up environment variables cp .env.example .env ``` ### Core Environment Configuration ```bash # Database DATABASE_URL="postgresql://user:password@localhost:5432/sesion_db" # Redis Cache REDIS_URL="redis://localhost:6379" # Anthropic AI ANTHROPIC_API_KEY="your_anthropic_api_key" 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" # AFIP (Argentine Tax Authority) AFIP_CUIT="your_clinic_cuit" AFIP_CERTIFICATE_PATH="./certs/afip-cert.pem" AFIP_PRIVATE_KEY_PATH="./certs/afip-key.pem" AFIP_PRODUCTION_MODE="false" # Payment Providers MERCADOPAGO_ACCESS_TOKEN="your_mp_access_token" STRIPE_SECRET_KEY="your_stripe_secret_key" # LiveKit Video LIVEKIT_API_KEY="your_livekit_api_key" LIVEKIT_API_SECRET="your_livekit_api_secret" LIVEKIT_WS_URL="wss://your-livekit-server.com" # App Configuration JWT_SECRET="your_jwt_secret" APP_URL="http://localhost:5173" API_URL="http://localhost:3000" ``` ### Database Migration ```bash # Generate Prisma client pnpm prisma generate # Run migrations pnpm prisma migrate deploy # Seed initial data pnpm prisma db seed ``` ### Start Development Servers ```bash # Start backend (NestJS) cd apps/backend pnpm dev # Start frontend (SvelteKit) - in separate terminal cd apps/frontend pnpm dev ``` ## Project Structure ``` psique-workflow-clinic/ ├── apps/ │ ├── backend/ # NestJS API server │ │ ├── src/ │ │ │ ├── agenda/ # Scheduling module │ │ │ ├── whatsapp/ # Baileys integration │ │ │ ├── billing/ # AFIP invoicing │ │ │ ├── video/ # LiveKit module │ │ │ ├── ai/ # Claude orchestration │ │ │ └── patients/ # Patient management │ │ └── prisma/ │ │ └── schema.prisma │ └── frontend/ # SvelteKit UI │ ├── src/ │ │ ├── routes/ # File-based routing │ │ ├── lib/ # Shared components │ │ └── stores/ # Svelte stores └── packages/ ├── shared/ # Shared types & utils └── config/ # Shared config ``` ## Core Module Usage ### 1. Intelligent Appointment Scheduling #### Create Appointment (Backend - NestJS) ```typescript // apps/backend/src/agenda/agenda.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { WhatsappService } from '../whatsapp/whatsapp.service'; @Injectable() export class AgendaService { constructor( private prisma: PrismaService, private whatsapp: WhatsappService ) {} async createAppointment(data: { patientId: string; practitionerId: string; startTime: Date; duration: number; // minutes type: 'PRESENCIAL' | 'VIRTUAL' | 'EVALUACION'; }) { // Check for scheduling conflicts const conflict = await this.prisma.appointment.findFirst({ where: { practitionerId: data.practitionerId, status: { not: 'CANCELLED' }, OR: [ { startTime: { lte: data.startTime, }, endTime: { gte: data.startTime, }, }, ], }, }); if (conflict) { throw new Error('Scheduling conflict detected'); } // Create appointment const appointment = await this.prisma.appointment.create({ data: { ...data, endTime: new Date(data.startTime.getTime() + data.duration * 60000), 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 dayStart = new Date(date.setHours(0, 0, 0, 0)); const dayEnd = new Date(date.setHours(23, 59, 59, 999)); const existingAppointments = await this.prisma.appointment.findMany({ where: { practitionerId, startTime: { gte: dayStart, lte: dayEnd }, status: { not: 'CANCELLED' }, }, orderBy: { startTime: 'asc' }, }); const workingHours = await this.prisma.practitionerSchedule.findFirst({ where: { practitionerId, dayOfWeek: date.getDay(), }, }); if (!workingHours) return []; // Generate available slots const slots = []; let currentTime = new Date( date.setHours( workingHours.startHour, workingHours.startMinute, 0, 0 ) ); const endTime = new Date( date.setHours(workingHours.endHour, workingHours.endMinute, 0, 0) ); while (currentTime < endTime) { const slotEnd = new Date(currentTime.getTime() + duration * 60000); const hasConflict = existingAppointments.some( (apt) => currentTime < new Date(apt.endTime) && slotEnd > new Date(apt.startTime) ); if (!hasConflict) { slots.push({ startTime: new Date(currentTime), endTime: slotEnd, }); } currentTime = slotEnd; } return slots; } } ``` #### Frontend Scheduling Component (Svelte 5) ```svelte <!-- apps/frontend/src/routes/agenda/+page.svelte --> <script lang="ts"> import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import type { Appointment } from '$lib/types'; let appointments = $state<Appointment[]>([]); let selectedDate = $state(new Date()); let loading = $state(false); async function loadAppointments() { loading = true; const response = await fetch(`/api/appointments?date=${selectedDate.toISOString()}`); appointments = await response.json(); loading = false; } async function createAppointment(data: { patientId: string; startTime: Date; duration: number; }) { const response = await fetch('/api/appointments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (response.ok) { await loadAppointments(); } } onMount(() => { loadAppointments(); }); </script> <div class="agenda-container"> <h1>Agenda Inteligente</h1> <input type="date" bind:value={selectedDate} onchange={loadAppointments} /> {#if loading} <div class="spinner">Cargando...</div> {:else} <div class="appointments-list"> {#each appointments as appointment} <div class="appointment-card"> <div class="time"> {new Date(appointment.startTime).toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })} </div> <div class="patient-name">{appointment.patient.name}</div> <div class="type-badge" class:virtual={appointment.type === 'VIRTUAL'}> {appointment.type} </div> </div> {/each} </div> {/if} </div> ``` ### 2. WhatsApp Automation with Baileys ```typescript // apps/backend/src/whatsapp/whatsapp.service.ts import { Injectable, Logger } from '@nestjs/common'; import makeWASocket, { DisconnectReason, useMultiFileAuthState, WAMessage, } from '@whiskeysockets/baileys'; import { Boom } from '@hapi/boom'; @Injectable() export class WhatsappService { private readonly logger = new Logger(WhatsappService.name); private sock: any; private connected = false; async initialize() { const { state, saveCreds } = await useMultiFileAuthState( process.env.WHATSAPP_SESSION_PATH || './whatsapp-sessions' ); this.sock = makeWASocket({ auth: state, printQRInTerminal: true, }); this.sock.ev.on('creds.update', saveCreds); this.sock.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(); } } else if (connection === 'open') { this.logger.log('WhatsApp connection established'); this.connected = true; } }); this.sock.ev.on('messages.upsert', async ({ messages }) => { await this.handleIncomingMessage(messages[0]); }); } async sendAppointmentConfirmation(appointment: any) { if (!this.connected) { this.logger.warn('WhatsApp not connected, skipping message'); return; } const message = ` 🗓️ *Confirmación de Turno* Hola ${appointment.patient.name}, Tu sesión está confirmada para: 📅 ${new Date(appointment.startTime).toLocaleDateString('es-AR')} 🕐 ${new Date(appointment.startTime).toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })} 👤 Profesional: ${appointment.practitioner.name} 📍 ${appointment.type === 'VIRTUAL' ? 'Videollamada (recibirás el link 10 min antes)' : appointment.practitioner.address} Respondé *SI* para confirmar o *NO* para cancelar. `; const phoneNumber = appointment.patient.phone.replace(/\D/g, ''); const jid = `${phoneNumber}@s.whatsapp.net`; await this.sock.sendMessage(jid, { text: message }); } async sendAppointmentReminder(appointment: any, hoursAhead: number) { const message = ` ⏰ *Recordatorio de Turno* Tu sesión es en ${hoursAhead} horas: 🕐 ${new Date(appointment.startTime).toLocaleTimeString('es-AR', { hour: '2-digit', minute: '2-digit' })} ${appointment.type === 'VIRTUAL' ? '🔗 Link de videollamada: ' + appointment.videoRoomUrl : '📍 ' + appointment.practitioner.address} ¡Te esperamos! `; const phoneNumber = appointment.patient.phone.replace(/\D/g, ''); const jid = `${phoneNumber}@s.whatsapp.net`; await this.sock.sendMessage(jid, { text: message }); } private async handleIncomingMessage(message: WAMessage) { if (!message.message) return; const text = message.message.conversation || message.message.extendedTextMessage?.text; const from = message.key.remoteJid; this.logger.log(`Received from ${from}: ${text}`); // Handle confirmation responses if (text?.toUpperCase() === 'SI' || text?.toUpperCase() === 'SÍ') { // Mark appointment as confirmed await this.sock.sendMessage(from, { text: '✅ Turno confirmado. ¡Gracias!' }); } else if (text?.toUpperCase() === 'NO') { await this.sock.sendMessage(from, { text: '❌ Entendido. ¿Querés reagendar? Contactate con la recepción.' }); } // Crisis detection keywords const crisisKeywords = ['crisis', 'urgencia', 'emergencia', 'ayuda'];
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기