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

Ir a la instalación

Datos de origen

Repositorio
reason-machines/devtools-skills
Última actividad en el origen
12 de julio de 2026 a las 11:58
Idioma detectado de SKILL.md
inglés
Estrellas
4
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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 }
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub