Skip to main content

sesion-mental-health-platform

AI-powered mental health practice management platform for psychologists with scheduling, WhatsApp automation, AFIP billing, video consultations, and Claude AI integration

Jump to install

Source facts

Repository
reason-machines/devtools-skills
Last source activity
July 12, 2026 at 14:34
Detected SKILL.md language
English
Stars
4
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
sesion-mental-health-platform
description
AI-powered mental health practice management platform for psychologists with scheduling, WhatsApp automation, AFIP billing, video consultations, and Claude AI integration
triggers
["how do I set up the Sesión mental health platform","configure WhatsApp automation for patient appointments","integrate AFIP electronic invoicing in Sesión","set up video consultations with LiveKit","use Claude AI for clinical notes in Sesión","configure MercadoPago payment integration","implement patient journey workflows","troubleshoot Sesión WhatsApp connection"]
# Sesión Mental Health Platform > Skill by [ara.so](https://ara.so) — Devtools Skills collection. Sesión is a comprehensive SaaS platform for psychology clinics and independent practitioners in Argentina. It orchestrates appointment scheduling, automated WhatsApp messaging (via Baileys), AFIP-compliant electronic invoicing, secure video consultations (LiveKit), and AI-powered clinical assistance using Claude Opus 4.6 and Sonnet 4.6. Built with SvelteKit 5, NestJS, Prisma, and TypeScript. ## Installation ### Prerequisites ```bash # Required versions node >= 18.0.0 pnpm >= 8.0.0 postgresql >= 14.0 redis >= 7.0 ``` ### Clone and Setup ```bash git clone https://github.com/fahad-hamid/psique-workflow-clinic.git cd psique-workflow-clinic # Install dependencies pnpm 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" # 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_DIR="./sessions" WHATSAPP_QR_TIMEOUT=60000 # LiveKit Video LIVEKIT_API_KEY="${LIVEKIT_API_KEY}" LIVEKIT_API_SECRET="${LIVEKIT_API_SECRET}" LIVEKIT_WS_URL="wss://your-livekit.cloud.livekit.cloud" # Payment Gateways MERCADOPAGO_ACCESS_TOKEN="${MERCADOPAGO_ACCESS_TOKEN}" STRIPE_SECRET_KEY="${STRIPE_SECRET_KEY}" # AFIP Integration AFIP_CUIT="${AFIP_CUIT}" AFIP_CERT_PATH="./certs/afip.crt" AFIP_KEY_PATH="./certs/afip.key" AFIP_PRODUCTION=false # Application JWT_SECRET="${JWT_SECRET}" APP_URL="http://localhost:5173" API_URL="http://localhost:3000" ``` ### Database Setup ```bash # Run migrations pnpm prisma migrate dev # Seed initial data pnpm prisma db seed ``` ### Start Development Servers ```bash # Frontend (SvelteKit) pnpm dev # Backend (NestJS) pnpm api:dev # Full stack pnpm dev:full ``` ## Architecture Overview ``` sesion/ ├── apps/ │ ├── web/ # SvelteKit 5 frontend │ ├── api/ # NestJS backend │ └── worker/ # Background job processor ├── packages/ │ ├── db/ # Prisma schema & migrations │ ├── ai/ # Claude AI orchestration │ ├── whatsapp/ # Baileys integration │ ├── video/ # LiveKit wrapper │ ├── billing/ # AFIP invoicing │ └── shared/ # Common types & utils └── prisma/ └── schema.prisma ``` ## Core Modules ### 1. Appointment Scheduling #### Create Appointment (NestJS Controller) ```typescript // apps/api/src/appointments/appointments.controller.ts import { Controller, Post, Body, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { AppointmentsService } from './appointments.service'; import { CreateAppointmentDto } from './dto/create-appointment.dto'; @Controller('appointments') @UseGuards(JwtAuthGuard) export class AppointmentsController { constructor(private readonly appointmentsService: AppointmentsService) {} @Post() async create(@Body() dto: CreateAppointmentDto) { return this.appointmentsService.create(dto); } } ``` #### Appointment Service with Conflict Detection ```typescript // apps/api/src/appointments/appointments.service.ts import { Injectable, ConflictException } from '@nestjs/common'; import { PrismaService } from '@sesion/db'; import { addMinutes, isWithinInterval } from 'date-fns'; @Injectable() export class AppointmentsService { constructor(private prisma: PrismaService) {} async create(dto: CreateAppointmentDto) { // Check for conflicts const conflicts = await this.prisma.appointment.findMany({ where: { practitionerId: dto.practitionerId, status: { not: 'CANCELLED' }, scheduledAt: { gte: dto.scheduledAt, lt: addMinutes(dto.scheduledAt, dto.durationMinutes || 45) } } }); if (conflicts.length > 0) { throw new ConflictException('Time slot already booked'); } // Create appointment const appointment = await this.prisma.appointment.create({ data: { ...dto, status: 'SCHEDULED' }, include: { patient: true, practitioner: true } }); // Trigger WhatsApp confirmation await this.whatsappService.sendConfirmation(appointment); return appointment; } async findUpcoming(practitionerId: string) { return this.prisma.appointment.findMany({ where: { practitionerId, scheduledAt: { gte: new Date() }, status: { in: ['SCHEDULED', 'CONFIRMED'] } }, include: { patient: true }, orderBy: { scheduledAt: 'asc' } }); } } ``` #### Frontend: Appointment Calendar (Svelte 5) ```svelte <!-- apps/web/src/routes/agenda/+page.svelte --> <script lang="ts"> import { onMount } from 'svelte'; import { goto } from '$app/navigation'; let appointments = $state([]); let selectedDate = $state(new Date()); let loading = $state(false); async function loadAppointments() { loading = true; const res = await fetch('/api/appointments/upcoming', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); appointments = await res.json(); loading = false; } async function createAppointment(event: CustomEvent) { const res = await fetch('/api/appointments', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify(event.detail) }); if (res.ok) { await loadAppointments(); } } onMount(loadAppointments); </script> <div class="agenda-container"> <header> <h1>Agenda</h1> <button onclick={() => goto('/appointments/new')}> Nueva Cita </button> </header> {#if loading} <div class="skeleton"></div> {:else} <div class="appointments-grid"> {#each appointments as apt} <div class="appointment-card" class:urgent={apt.isUrgent}> <time>{new Date(apt.scheduledAt).toLocaleString('es-AR')}</time> <h3>{apt.patient.name}</h3> <span class="type">{apt.type}</span> <span class="status">{apt.status}</span> </div> {/each} </div> {/if} </div> ``` ### 2. WhatsApp Automation (Baileys) #### WhatsApp Connection Service ```typescript // packages/whatsapp/src/whatsapp.service.ts import makeWASocket, { DisconnectReason, useMultiFileAuthState, makeInMemoryStore } from '@whiskeysockets/baileys'; import { Boom } from '@hapi/boom'; import { Injectable, Logger } from '@nestjs/common'; @Injectable() export class WhatsAppService { private sock: any; private readonly logger = new Logger(WhatsAppService.name); async connect() { const { state, saveCreds } = await useMultiFileAuthState( process.env.WHATSAPP_SESSION_DIR || './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, qr } = update; if (qr) { this.logger.log('QR Code generated, scan with WhatsApp'); } if (connection === 'close') { const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) { this.logger.log('Reconnecting...'); this.connect(); } } else if (connection === 'open') { this.logger.log('WhatsApp connected successfully'); } }); this.sock.ev.on('messages.upsert', async ({ messages }) => { await this.handleIncomingMessage(messages[0]); }); } async sendReminder(appointment: Appointment) { const patientNumber = appointment.patient.phone.replace(/\D/g, '') + '@s.whatsapp.net'; const message = `Hola ${appointment.patient.name}! 👋\n\n` + `Te recordamos tu sesión programada:\n` + `📅 ${format(appointment.scheduledAt, "dd/MM/yyyy 'a las' HH:mm", { locale: es })}\n` + `👨‍⚕️ Con: ${appointment.practitioner.name}\n\n` + `Por favor confirma tu asistencia respondiendo SI o NO.`; await this.sock.sendMessage(patientNumber, { text: message }); await this.prisma.whatsappMessage.create({ data: { appointmentId: appointment.id, to: patientNumber, content: message, type: 'REMINDER', status: 'SENT' } }); } async handleIncomingMessage(message: any) { if (!message.message?.conversation) return; const text = message.message.conversation.toLowerCase(); const from = message.key.remoteJid; // Check for confirmation replies if (['si', 'sí', 'confirmo'].includes(text)) { await this.confirmAppointment(from); } else if (['no', 'cancelar'].includes(text)) { await this.requestCancellation(from); } // Crisis keyword detection if (this.detectCrisisKeywords(text)) { await this.alertPractitioner(from, text); } } private detectCrisisKeywords(text: string): boolean { const crisisKeywords = [ 'suicidio', 'matarme', 'terminar con todo', 'no puedo más', 'crisis', 'emergencia' ]; return crisisKeywords.some(kw => text.includes(kw)); } } ``` #### Automated Reminder Scheduler ```typescript // apps/worker/src/jobs/reminder.job.ts import { Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { PrismaService } from '@sesion/db'; import { WhatsAppService } from '@sesion/whatsapp'; import { subHours } from 'date-fns'; @Injectable() export class ReminderJob { constructor( private prisma: PrismaService, private whatsapp: WhatsAppService ) {} @Cron(CronExpression.EVERY_HOUR) async send24HourReminders() { const tomorrow = new Date(); tomorrow.setHours(tomorrow.getHours() + 24); const appointments = await this.prisma.appointment.findMany({ where: { scheduledAt: { gte: tomorrow, lt: new Date(tomorrow.getTime() + 60 * 60 * 1000) // +1 hour window }, status: 'SCHEDULED', reminderSent24h: false }, include: { patient: true, practitioner: true } }); for (const apt of appointments) { await this.whatsapp.sendReminder(apt); await this.prisma.appointment.update({ where: { id: apt.id }, data: { reminderSent24h: true } }); } } } ``` ### 3. AFIP Electronic Invoicing #### Invoice Generation Service ```typescript // packages/billing/src/afip.service.ts import { Injectable } from '@nestjs/common'; import Afip from '@afipsdk/afip.js'; import { PrismaService } from '@sesion/db'; @Injectable() export class AfipService { private afip: Afip; constructor(private prisma: PrismaService) { this.afip = new Afip({ CUIT: process.env.AFIP_CUIT, cert: process.env.AFIP_CERT_PATH,
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub