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

Zur Installation springen

Quellinformationen

Repository
reason-machines/devtools-skills
Letzte Quellaktivität
12. Juli 2026 um 11:58
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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 }
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen