diff --git a/cfg/amar/.env.example b/cfg/amar/.env.example deleted file mode 100644 index 71093e5..0000000 --- a/cfg/amar/.env.example +++ /dev/null @@ -1,114 +0,0 @@ -# ============================================================================= -# AMAR Room - Configuration Template -# ============================================================================= -# Copy this to .env and fill in values for your environment. -# Uses absolute paths to plug soleprint to existing Dockerfiles. -# ============================================================================= - -# ============================================================================= -# DEPLOYMENT -# ============================================================================= -# Container name prefix (e.g., amar_backend, amar_db) -DEPLOYMENT_NAME=amar - -# ============================================================================= -# NETWORK -# ============================================================================= -NETWORK_NAME=amar_network - -# ============================================================================= -# PATHS (absolute paths to external code/dockerfiles - REQUIRED) -# ============================================================================= -BACKEND_PATH=/abs/path/to/amar_django_back -FRONTEND_PATH=/abs/path/to/amar_frontend -DOCKERFILE_BACKEND=/abs/path/to/Dockerfile.backend -DOCKERFILE_FRONTEND=/abs/path/to/Dockerfile.frontend - -# ============================================================================= -# DATABASE -# ============================================================================= -# Database dump file (relative to dumps/ directory) -DB_DUMP=test.sql - -# ============================================================================= -# PORTS -# ============================================================================= -BACKEND_PORT=8000 -FRONTEND_PORT=3000 - -# Soleprint port -SOLEPRINT_PORT=12000 - -# ============================================================================= -# BACKEND SERVER (Uvicorn) -# ============================================================================= -# Dev: 1 worker with hot reload -# Prod: 4 workers without reload -BACKEND_WORKERS=1 -BACKEND_RELOAD=--reload - -# Database connection -POSTGRES_DB=amarback -POSTGRES_USER=postgres -POSTGRES_PASSWORD=your_secure_password_here - -# ============================================================================= -# DJANGO -# ============================================================================= -SECRET_KEY=your_django_secret_key_here -DEBUG=False -ALLOWED_HOSTS=amar.room.mcrn.ar,localhost -DJANGO_ENV=production - -# ============================================================================= -# CORS -# ============================================================================= -CORS_ALLOW_ALL=false -CORS_ALLOWED_ORIGINS= - -# ============================================================================= -# GOOGLE SERVICES -# ============================================================================= -SUBJECT_CALENDAR= -SHEET_ID= -RANGE_NAME= -GOOGLE_MAPS_API_KEY= - -# ============================================================================= -# ANALYTICS -# ============================================================================= -GA4_MEASUREMENT_ID= -AMPLITUDE_API_KEY= -HOTJAR_API_KEY= - -# ============================================================================= -# MERCADO PAGO -# ============================================================================= -ACCESS_TOKEN_MERCADO_PAGO= -MP_PLATFORM_ACCESS_TOKEN= -USER_ID= - -# ============================================================================= -# WEB PUSH -# ============================================================================= -WEBPUSH_VAPID_PUBLIC_KEY= -WEBPUSH_VAPID_PRIVATE_KEY= -WEBPUSH_VAPID_ADMIN_EMAIL= - -# ============================================================================= -# INIT -# ============================================================================= -USER_PASSWORD=initial_admin_password - -# ============================================================================= -# FRONTEND -# ============================================================================= -NEXT_PUBLIC_APP_API_URL_BACKOFFICE= -NEXT_PUBLIC_APP_API_URL_STAGE= -NEXT_PUBLIC_IS_STAGE=false -NEXT_PUBLIC_FB_PIXEL_ID= -NEXT_PUBLIC_TAG_MANAGER= -NEXT_PUBLIC_WHATSAPP_CONTACT= -NEXT_PUBLIC_API_KEY= -NEXT_PUBLIC_AMPLITUDE_API_KEY= -NEXT_PUBLIC_GMAPS_API_KEY= diff --git a/cfg/amar/Dockerfile.backend b/cfg/amar/Dockerfile.backend deleted file mode 100644 index c0f757b..0000000 --- a/cfg/amar/Dockerfile.backend +++ /dev/null @@ -1,71 +0,0 @@ -# ============================================================================= -# Dockerfile for Django Backend with Uvicorn -# ============================================================================= -# Usage: -# Development: WORKERS=1 RELOAD=--reload (source mounted, hot reload) -# Production: WORKERS=4 RELOAD="" (no reload, multiple workers) - -# ============================================================================= -# Stage 1: Base with system dependencies -# ============================================================================= -FROM python:3.11-slim AS base - -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -WORKDIR /app - -# Install system dependencies (cached layer) -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - gdal-bin \ - libgdal-dev \ - libgeos-dev \ - libproj-dev \ - postgresql-client \ - # WeasyPrint dependencies (for django-afip PDF generation) - libglib2.0-0 \ - libpango-1.0-0 \ - libpangocairo-1.0-0 \ - libgdk-pixbuf-2.0-0 \ - libffi-dev \ - libcairo2 \ - libgirepository1.0-dev \ - gir1.2-pango-1.0 \ - fonts-dejavu-core \ - && rm -rf /var/lib/apt/lists/* - -# ============================================================================= -# Stage 2: Dependencies (cached layer) -# ============================================================================= -FROM base AS deps - -# Copy only requirements for dependency installation -COPY requirements.txt . - -# Install Python dependencies -RUN pip install --no-cache-dir -r requirements.txt - -# ============================================================================= -# Stage 3: Runtime (uvicorn with configurable workers) -# ============================================================================= -FROM base AS runtime - -# Copy dependencies from deps stage -COPY --from=deps /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages -COPY --from=deps /usr/local/bin /usr/local/bin - -# Copy requirements (for reference) -COPY requirements.txt . - -# Create directories -RUN mkdir -p /var/etc/static /app/media - -# Note: Source code mounted at runtime for dev, copied for prod -EXPOSE 8000 - -# Uvicorn with configurable workers and reload -# Dev: WORKERS=1 RELOAD=--reload -# Prod: WORKERS=4 RELOAD="" -CMD uvicorn amar_django_back.asgi:application --host 0.0.0.0 --port 8000 --workers ${WORKERS:-1} ${RELOAD} diff --git a/cfg/amar/Dockerfile.frontend b/cfg/amar/Dockerfile.frontend deleted file mode 100644 index eadd474..0000000 --- a/cfg/amar/Dockerfile.frontend +++ /dev/null @@ -1,80 +0,0 @@ -# ============================================================================= -# Multi-stage Dockerfile for Next.js Frontend -# ============================================================================= -# Usage: -# Development: docker compose up (mounts source, hot reload) -# Production: docker compose -f docker-compose.yml -f docker-compose.prod.yml up - -# ============================================================================= -# Stage 1: Dependencies (cached layer) -# ============================================================================= -FROM node:18-alpine AS deps - -WORKDIR /app - -# Copy only package files for dependency installation -COPY package*.json ./ - -# Install ALL dependencies (including devDependencies for dev mode) -RUN npm ci - -# ============================================================================= -# Stage 2: Development (hot reload, source mounted) -# ============================================================================= -FROM node:18-alpine AS development - -WORKDIR /app - -# Copy dependencies from deps stage -COPY --from=deps /app/node_modules ./node_modules - -# Copy package files (for npm scripts) -COPY package*.json ./ - -# Copy config files -COPY next.config.js postcss.config.js tailwind.config.js tsconfig.json ./ - -# Note: src/ and public/ are mounted at runtime for hot reload -# Start dev server -EXPOSE 3000 -CMD ["npm", "run", "dev"] - -# ============================================================================= -# Stage 3: Builder (compile for production) -# ============================================================================= -FROM node:18-alpine AS builder - -WORKDIR /app - -# Copy dependencies -COPY --from=deps /app/node_modules ./node_modules - -# Copy all source files -COPY . . - -# Build for production -RUN npm run build - -# ============================================================================= -# Stage 4: Production (optimized, minimal) -# ============================================================================= -FROM node:18-alpine AS production - -WORKDIR /app - -ENV NODE_ENV=production - -# Copy only production dependencies -COPY package*.json ./ -RUN npm ci --only=production && npm cache clean --force - -# Copy built application from builder -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/public ./public -COPY --from=builder /app/next.config.js ./ - -EXPOSE 3000 - -USER node - -CMD ["npm", "run", "start"] diff --git a/cfg/amar/config.json b/cfg/amar/config.json deleted file mode 100644 index 8971389..0000000 --- a/cfg/amar/config.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "framework": { - "name": "soleprint", - "slug": "soleprint", - "version": "0.1.0", - "description": "Development workflow and documentation system", - "tagline": "Mapping development footprints", - "icon": "👣", - "hub_port": 12000 - }, - "managed": { - "name": "amar", - "repos": { - "backend": "/home/mariano/wdir/ama/amar_django_back", - "frontend": "/home/mariano/wdir/ama/amar_frontend" - } - }, - "systems": [ - { - "key": "data_flow", - "name": "artery", - "slug": "artery", - "title": "Artery", - "tagline": "Todo lo vital", - "icon": "💉" - }, - { - "key": "documentation", - "name": "atlas", - "slug": "atlas", - "title": "Atlas", - "tagline": "Documentación accionable", - "icon": "🗺️" - }, - { - "key": "execution", - "name": "station", - "slug": "station", - "title": "Station", - "tagline": "Monitores, Entornos y Herramientas", - "icon": "🎛️" - } - ], - "components": { - "shared": { - "config": { - "name": "room", - "title": "Room", - "description": "Runtime environment configuration", - "plural": "rooms" - }, - "data": { - "name": "depot", - "title": "Depot", - "description": "Data storage / provisions", - "plural": "depots" - } - }, - "data_flow": { - "connector": { - "name": "vein", - "title": "Vein", - "description": "Stateless API connector", - "plural": "veins" - }, - "mock": { - "name": "shunt", - "title": "Shunt", - "description": "Fake connector for testing", - "plural": "shunts" - }, - "composed": { - "name": "pulse", - "title": "Pulse", - "description": "Composed data flow", - "plural": "pulses", - "formula": "Vein + Room + Depot" - }, - "app": { - "name": "plexus", - "title": "Plexus", - "description": "Full app with backend, frontend and DB", - "plural": "plexus" - } - }, - "documentation": { - "pattern": { - "name": "template", - "title": "Template", - "description": "Documentation pattern", - "plural": "templates" - }, - "library": { - "name": "book", - "title": "Book", - "description": "Documentation library" - }, - "composed": { - "name": "book", - "title": "Book", - "description": "Composed documentation", - "plural": "books", - "formula": "Template + Depot" - } - }, - "execution": { - "utility": { - "name": "tool", - "title": "Tool", - "description": "Execution utility", - "plural": "tools" - }, - "watcher": { - "name": "monitor", - "title": "Monitor", - "description": "Service monitor", - "plural": "monitors" - }, - "container": { - "name": "cabinet", - "title": "Cabinet", - "description": "Tool container", - "plural": "cabinets" - }, - "workspace": { - "name": "desk", - "title": "Desk", - "description": "Execution workspace" - }, - "workbench": { - "name": "desk", - "title": "Desk", - "description": "Work surface" - }, - "composed": { - "name": "desk", - "title": "Desk", - "description": "Composed execution bundle", - "plural": "desks", - "formula": "Cabinet + Room + Depots" - } - } - } -} diff --git a/cfg/amar/ctrl/logs.sh b/cfg/amar/ctrl/logs.sh deleted file mode 100755 index 45fd969..0000000 --- a/cfg/amar/ctrl/logs.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -# View amar room logs -# -# Usage: -# ./logs.sh # All logs (tail) -# ./logs.sh -f # Follow mode -# ./logs.sh backend # Specific container -# ./logs.sh soleprint # Soleprint logs - -set -e -cd "$(dirname "$0")/.." - -FOLLOW="" -TARGET="" - -for arg in "$@"; do - case $arg in - -f|--follow) FOLLOW="-f" ;; - *) TARGET="$arg" ;; - esac -done - -if [ -z "$TARGET" ]; then - echo "=== Amar ===" - docker compose logs --tail=20 $FOLLOW - echo "" - echo "=== Soleprint ===" - (cd soleprint && docker compose logs --tail=20 $FOLLOW) -elif [ "$TARGET" = "soleprint" ]; then - (cd soleprint && docker compose logs $FOLLOW) -else - docker logs $FOLLOW "amar_$TARGET" 2>/dev/null || docker logs $FOLLOW "$TARGET" -fi diff --git a/cfg/amar/ctrl/start.sh b/cfg/amar/ctrl/start.sh deleted file mode 100755 index 2249981..0000000 --- a/cfg/amar/ctrl/start.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# Start amar room (managed app + soleprint) -# -# Usage: -# ./start.sh # Start all (foreground) -# ./start.sh -d # Start all (detached) -# ./start.sh amar # Start only amar -# ./start.sh soleprint # Start only soleprint -# ./start.sh --build # Rebuild images - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CFG_DIR="$(dirname "$SCRIPT_DIR")" -GEN_DIR="$CFG_DIR/../../gen/amar" -SPR_DIR="$GEN_DIR/soleprint" - -BUILD="" -DETACH="" -TARGET="all" -NGINX="-f $CFG_DIR/soleprint/docker-compose.nginx.yml" - -for arg in "$@"; do - case $arg in - -d|--detached) DETACH="-d" ;; - --build) BUILD="--build" ;; - --no-nginx) NGINX="" ;; - amar) TARGET="amar" ;; - soleprint) TARGET="soleprint" ;; - esac -done - -if [ "$TARGET" = "all" ]; then - echo "Starting amar + soleprint..." - (cd "$CFG_DIR" && docker compose up $BUILD -d) - (cd "$SPR_DIR" && docker compose -f docker-compose.yml $NGINX up $BUILD -d) - if [ -z "$DETACH" ]; then - (cd "$CFG_DIR" && docker compose logs -f) & - (cd "$SPR_DIR" && docker compose logs -f) & - wait - fi -elif [ "$TARGET" = "amar" ]; then - echo "Starting amar..." - (cd "$CFG_DIR" && docker compose up $DETACH $BUILD) -elif [ "$TARGET" = "soleprint" ]; then - echo "Starting soleprint..." - (cd "$SPR_DIR" && docker compose -f docker-compose.yml $NGINX up $DETACH $BUILD) -fi - -if [ -n "$DETACH" ]; then - echo "" - docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(amar|soleprint|NAMES)" -fi diff --git a/cfg/amar/ctrl/status.sh b/cfg/amar/ctrl/status.sh deleted file mode 100755 index 31b92f9..0000000 --- a/cfg/amar/ctrl/status.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Show amar room status - -cd "$(dirname "$0")/.." - -echo "=== Containers ===" -docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(amar|soleprint|NAMES)" || echo "No containers running" diff --git a/cfg/amar/ctrl/stop.sh b/cfg/amar/ctrl/stop.sh deleted file mode 100755 index 818fdb3..0000000 --- a/cfg/amar/ctrl/stop.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Stop amar room (managed app + soleprint) -# -# Usage: -# ./stop.sh # Stop all -# ./stop.sh amar # Stop only amar -# ./stop.sh soleprint # Stop only soleprint - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CFG_DIR="$(dirname "$SCRIPT_DIR")" -GEN_DIR="$CFG_DIR/../../gen/amar" -SPR_DIR="$GEN_DIR/soleprint" - -TARGET="all" -[ -n "$1" ] && TARGET="$1" - -if [ "$TARGET" = "all" ] || [ "$TARGET" = "soleprint" ]; then - echo "Stopping soleprint..." - (cd "$SPR_DIR" && docker compose -f docker-compose.yml -f "$CFG_DIR/soleprint/docker-compose.nginx.yml" down 2>/dev/null || true) -fi - -if [ "$TARGET" = "all" ] || [ "$TARGET" = "amar" ]; then - echo "Stopping amar..." - (cd "$CFG_DIR" && docker compose down) -fi - -echo "Done." diff --git a/cfg/amar/ctrl/xtras/reload-db.sh b/cfg/amar/ctrl/xtras/reload-db.sh deleted file mode 100755 index f82f627..0000000 --- a/cfg/amar/ctrl/xtras/reload-db.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -# Smart database reload - only swaps if DB_DUMP changed -# -# Tracks which dump is currently loaded and only reloads if different. -# Edit DB_DUMP in .env and run this script - it handles the rest. -# -# Usage: -# ./reload-db.sh # Reload if DB_DUMP changed -# ./reload-db.sh --force # Force reload even if same - -set -e - -cd "$(dirname "$0")/../.." - -FORCE=false -if [ "$1" = "--force" ]; then - FORCE=true -fi - -# Get config from .env -DEPLOYMENT_NAME=$(grep "^DEPLOYMENT_NAME=" .env 2>/dev/null | cut -d'=' -f2 || echo "amar") -POSTGRES_DB=$(grep "^POSTGRES_DB=" .env 2>/dev/null | cut -d'=' -f2 || echo "amarback") -POSTGRES_USER=$(grep "^POSTGRES_USER=" .env 2>/dev/null | cut -d'=' -f2 || echo "postgres") -DB_DUMP=$(grep "^DB_DUMP=" .env 2>/dev/null | cut -d'=' -f2) - -if [ -z "$DB_DUMP" ]; then - echo "Error: DB_DUMP not set in .env" - echo "" - echo "Add to .env:" - echo " DB_DUMP=dev.sql" - echo "" - echo "Available dumps:" - ls -1 dumps/*.sql 2>/dev/null | sed 's/dumps\// /' || echo " No dumps found in dumps/" - exit 1 -fi - -DUMP_FILE="dumps/${DB_DUMP}" -if [ ! -f "$DUMP_FILE" ]; then - echo "Error: Dump file not found: $DUMP_FILE" - echo "" - echo "Available dumps:" - ls -1 dumps/*.sql 2>/dev/null | sed 's/dumps\// /' || echo " No dumps found in dumps/" - exit 1 -fi - -DB_CONTAINER="${DEPLOYMENT_NAME}_db" -BACKEND_CONTAINER="${DEPLOYMENT_NAME}_backend" -STATE_FILE=".db_state" - -# Check if db container is running -if ! docker ps --format "{{.Names}}" | grep -q "^${DB_CONTAINER}$"; then - echo "Error: Database container not running: $DB_CONTAINER" - echo "Start services first with: ./ctrl/start.sh -d" - exit 1 -fi - -# Check current state -if [ -f "$STATE_FILE" ] && [ "$FORCE" = false ]; then - CURRENT_DUMP=$(cat "$STATE_FILE") - if [ "$CURRENT_DUMP" = "$DB_DUMP" ]; then - echo "Database already loaded with: $DB_DUMP" - echo "Use --force to reload anyway" - exit 0 - fi - echo "Database dump changed: $CURRENT_DUMP → $DB_DUMP" -else - if [ "$FORCE" = true ]; then - echo "Force reloading database with: $DB_DUMP" - else - echo "Loading database with: $DB_DUMP" - fi -fi - -echo "" -read -p "Continue with database reload? (y/N) " -n 1 -r -echo "" -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "Cancelled." - exit 0 -fi - -echo "" -echo "[1/5] Stopping backend and celery services..." -docker compose stop backend celery celery-beat 2>/dev/null || true - -echo "" -echo "[2/5] Dropping and recreating database..." -docker exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d postgres -c "DROP DATABASE IF EXISTS $POSTGRES_DB WITH (FORCE);" -docker exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d postgres -c "CREATE DATABASE $POSTGRES_DB;" -docker exec "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION IF NOT EXISTS postgis_topology;" - -echo "" -echo "[3/5] Loading dump: $DB_DUMP..." -docker exec -i "$DB_CONTAINER" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$DUMP_FILE" - -echo "" -echo "[4/5] Restarting services and running migrations..." -docker compose start backend celery celery-beat - -echo "Waiting for backend to start..." -sleep 3 - -echo "Running migrations..." -docker exec "$BACKEND_CONTAINER" python manage.py migrate --noinput - -echo "" -echo "[5/5] Updating state..." -echo "$DB_DUMP" > "$STATE_FILE" - -echo "" -echo "==========================================" -echo " Database Reloaded Successfully" -echo "==========================================" -echo "" -echo "Current dump: $DB_DUMP" -echo "Database: $POSTGRES_DB" -echo "" diff --git a/cfg/amar/data/__init__.py b/cfg/amar/data/__init__.py deleted file mode 100644 index 9836af5..0000000 --- a/cfg/amar/data/__init__.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -Pawprint Data Layer - -JSON file storage (future: MongoDB) -""" - -import json -from pathlib import Path -from typing import List, Optional - -# Add parent to path for models import -import sys -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from models.pydantic import ( - Vein, Nest, Larder, Template, Tool, - Pulse, Book, Table, - VeinCollection, NestCollection, LarderCollection, - TemplateCollection, ToolCollection, - PulseCollection, BookCollection, TableCollection, - Status -) - -DATA_DIR = Path(__file__).parent.resolve() - -# Debug: print data dir on import -print(f"[data] DATA_DIR: {DATA_DIR}") -print(f"[data] DATA_DIR exists: {DATA_DIR.exists()}") -print(f"[data] veins.json exists: {(DATA_DIR / 'veins.json').exists()}") - - -def _load_json(filename: str) -> dict: - filepath = DATA_DIR / filename - if filepath.exists(): - with open(filepath) as f: - data = json.load(f) - print(f"[data] Loaded {filename}: {len(data.get('items', []))} items") - return data - print(f"[data] File not found: {filepath}") - return {"items": []} - - -def _save_json(filename: str, data: dict): - filepath = DATA_DIR / filename - with open(filepath, 'w') as f: - json.dump(data, f, indent=2) - - -# === Loaders === - -def get_veins() -> List[Vein]: - data = _load_json("veins.json") - return VeinCollection(**data).items - - -def get_nests() -> List[Nest]: - data = _load_json("nests.json") - return NestCollection(**data).items - - -def get_larders() -> List[Larder]: - data = _load_json("larders.json") - return LarderCollection(**data).items - - -def get_templates() -> List[Template]: - data = _load_json("templates.json") - return TemplateCollection(**data).items - - -def get_tools() -> List[Tool]: - data = _load_json("tools.json") - return ToolCollection(**data).items - - -def get_cabinets() -> list: - """Load cabinets (simple dict, no pydantic yet).""" - data = _load_json("cabinets.json") - return data.get("items", []) - - -def get_monitors() -> list: - """Load monitors (simple dict, no pydantic yet).""" - data = _load_json("monitors.json") - return data.get("items", []) - - -def get_pulses() -> List[Pulse]: - data = _load_json("pulses.json") - return PulseCollection(**data).items - - -def get_books() -> List[Book]: - data = _load_json("books.json") - return BookCollection(**data).items - - -def get_tables() -> List[Table]: - data = _load_json("tables.json") - return TableCollection(**data).items - - -# === Helpers === - -def get_vein(name: str) -> Optional[Vein]: - for v in get_veins(): - if v.name == name: - return v - return None - - -def get_nest(name: str) -> Optional[Nest]: - for n in get_nests(): - if n.name == name: - return n - return None - - -def get_larder(name: str) -> Optional[Larder]: - for l in get_larders(): - if l.name == name: - return l - return None - - -# === For frontend rendering === - -def get_artery_data() -> dict: - """Data for artery frontend.""" - return { - "veins": [v.model_dump() for v in get_veins()], - "nests": [n.model_dump() for n in get_nests()], - "larders": [l.model_dump() for l in get_larders()], - "pulses": [p.model_dump() for p in get_pulses()], - } - - -def get_album_data() -> dict: - """Data for album frontend.""" - return { - "templates": [t.model_dump() for t in get_templates()], - "larders": [l.model_dump() for l in get_larders()], - "books": [b.model_dump() for b in get_books()], - } - - -def get_ward_data() -> dict: - """Data for ward frontend.""" - return { - "tools": [t.model_dump() for t in get_tools()], - "monitors": get_monitors(), - "cabinets": get_cabinets(), - "nests": [n.model_dump() for n in get_nests()], - "larders": [l.model_dump() for l in get_larders()], - "tables": [t.model_dump() for t in get_tables()], - } diff --git a/cfg/amar/data/books.json b/cfg/amar/data/books.json deleted file mode 100644 index d201c7f..0000000 --- a/cfg/amar/data/books.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "items": [ - { - "name": "arch-model", - "slug": "arch-model", - "title": "Architecture Model", - "status": "ready", - "template": null, - "larder": { - "name": "arch-model", - "slug": "arch-model", - "title": "Architecture Model", - "status": "ready", - "source_template": null, - "data_path": "album/book/arch-model" - }, - "output_larder": null, - "system": "album" - }, - { - "name": "feature-flow", - "slug": "feature-flow", - "title": "Feature Flow Pipeline", - "status": "ready", - "template": null, - "larder": { - "name": "feature-flow", - "slug": "feature-flow", - "title": "Feature Flow Pipeline", - "status": "ready", - "source_template": null, - "data_path": "album/book/feature-flow" - }, - "output_larder": null, - "system": "album" - }, - { - "name": "gherkin-samples", - "slug": "gherkin-samples", - "title": "Gherkin Samples", - "status": "ready", - "template": null, - "larder": { - "name": "gherkin-samples", - "slug": "gherkin-samples", - "title": "Gherkin Samples", - "status": "ready", - "source_template": null, - "data_path": "album/book/gherkin-samples" - }, - "output_larder": null, - "system": "album" - }, - { - "name": "feature-form-samples", - "slug": "feature-form-samples", - "title": "Feature Form Samples", - "status": "ready", - "template": { - "name": "feature-form", - "slug": "feature-form", - "title": "Feature Form Template", - "status": "ready", - "template_path": "album/template/feature-form", - "system": "album" - }, - "larder": { - "name": "feature-form", - "slug": "feature-form", - "title": "Feature Forms", - "status": "ready", - "source_template": "feature-form", - "data_path": "album/book/feature-form-samples/feature-form" - }, - "output_larder": null, - "system": "album" - } - ] -} diff --git a/cfg/amar/data/depots.json b/cfg/amar/data/depots.json deleted file mode 100644 index 72be870..0000000 --- a/cfg/amar/data/depots.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "items": [ - { - "name": "feature-form", - "slug": "feature-form", - "title": "Feature Forms", - "status": "ready", - "source_template": "feature-form", - "data_path": "album/book/feature-form-samples/feature-form" - } - ] -} diff --git a/cfg/amar/data/desks.json b/cfg/amar/data/desks.json deleted file mode 100644 index 2feb210..0000000 --- a/cfg/amar/data/desks.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} diff --git a/cfg/amar/data/monitors.json b/cfg/amar/data/monitors.json deleted file mode 100644 index 503de9d..0000000 --- a/cfg/amar/data/monitors.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "items": [ - { - "name": "turnos", - "slug": "turnos", - "title": "Turnos Monitor", - "status": "dev", - "system": "ward", - "description": "Pipeline view of requests → turnos. Shows vet-petowner at a glance.", - "path": "ward/monitor/turnos" - }, - { - "name": "data_browse", - "slug": "data-browse", - "title": "Data Browse", - "status": "ready", - "system": "ward", - "description": "Quick navigation to test users and data states. Book/larder pattern with SQL mode for manual testing workflows.", - "path": "ward/monitor/data_browse" - } - ] -} diff --git a/cfg/amar/data/pulses.json b/cfg/amar/data/pulses.json deleted file mode 100644 index 2feb210..0000000 --- a/cfg/amar/data/pulses.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} diff --git a/cfg/amar/data/rooms.json b/cfg/amar/data/rooms.json deleted file mode 100644 index 9310a6e..0000000 --- a/cfg/amar/data/rooms.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "items": [ - {"name": "pawprint-local", "slug": "pawprint-local", "title": "Pawprint Local", "status": "dev", "config_path": "deploy/pawprint-local"} - ] -} diff --git a/cfg/amar/data/tables.json b/cfg/amar/data/tables.json deleted file mode 100644 index 2feb210..0000000 --- a/cfg/amar/data/tables.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} diff --git a/cfg/amar/data/template/feature-form/template.md b/cfg/amar/data/template/feature-form/template.md deleted file mode 100644 index 6b49e99..0000000 --- a/cfg/amar/data/template/feature-form/template.md +++ /dev/null @@ -1,38 +0,0 @@ -# {{nombre_flujo}} - -## Tipo de usuario -{{tipo_usuario}} - -## Donde empieza -{{punto_entrada}} - -## Que quiere hacer el usuario -{{objetivo}} - -## Pasos - -1. {{paso_1}} -2. {{paso_2}} -3. {{paso_3}} - -## Que deberia pasar - -- {{resultado_1}} -- {{resultado_2}} - -## Problemas comunes - -- {{problema_1}} -- {{problema_2}} - -## Casos especiales - -- {{caso_especial_1}} - -## Flujos relacionados - -- {{flujo_relacionado_1}} - -## Notas tecnicas - -- {{nota_tecnica_1}} diff --git a/cfg/amar/data/templates.json b/cfg/amar/data/templates.json deleted file mode 100644 index 781b31d..0000000 --- a/cfg/amar/data/templates.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "items": [ - { - "name": "feature-form", - "slug": "feature-form", - "title": "Feature Form Template", - "status": "ready", - "template_path": "data/template/feature-form", - "system": "album" - } - ] -} diff --git a/cfg/amar/data/tools.json b/cfg/amar/data/tools.json deleted file mode 100644 index 165fcc0..0000000 --- a/cfg/amar/data/tools.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "items": [ - { - "name": "tester", - "slug": "tester", - "title": "Contract Tests", - "status": "live", - "system": "ward", - "type": "app", - "description": "HTTP contract test runner with multi-environment support. Filter, run, and track tests against dev/stage/prod.", - "path": "ward/tools/tester", - "url": "/tools/tester/" - }, - { - "name": "datagen", - "slug": "datagen", - "title": "Test Data Generator", - "status": "live", - "system": "ward", - "type": "cli", - "description": "Generate realistic test data for Amar domain (users, pets, services) and MercadoPago API responses. Used by mock veins and test seeders.", - "path": "ward/tools/datagen", - "cli": "python -m datagen" - }, - { - "name": "generate_test_data", - "slug": "generate-test-data", - "title": "DB Test Data Extractor", - "status": "dev", - "system": "ward", - "type": "cli", - "description": "Extract representative subsets from PostgreSQL dumps for testing/development.", - "path": "ward/tools/generate_test_data", - "cli": "python -m generate_test_data" - }, - { - "name": "modelgen", - "slug": "modelgen", - "title": "Model Generator", - "status": "dev", - "system": "ward", - "type": "cli", - "description": "Generate platform-specific models (Pydantic, Django, Prisma) from JSON Schema.", - "path": "ward/tools/modelgen", - "cli": "python -m modelgen" - } - ] -} diff --git a/cfg/amar/data/veins.json b/cfg/amar/data/veins.json deleted file mode 100644 index 8b1ca3f..0000000 --- a/cfg/amar/data/veins.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "items": [ - { - "name": "jira", - "slug": "jira", - "title": "Jira", - "status": "live", - "system": "artery" - }, - { - "name": "slack", - "slug": "slack", - "title": "Slack", - "status": "building", - "system": "artery" - }, - { - "name": "google", - "slug": "google", - "title": "Google", - "status": "planned", - "system": "artery" - }, - { - "name": "maps", - "slug": "maps", - "title": "Maps", - "status": "planned", - "system": "artery" - }, - { - "name": "whatsapp", - "slug": "whatsapp", - "title": "WhatsApp", - "status": "planned", - "system": "artery" - }, - { - "name": "gnucash", - "slug": "gnucash", - "title": "GNUCash", - "status": "planned", - "system": "artery" - }, - { - "name": "vnc", - "slug": "vnc", - "title": "VNC", - "status": "planned", - "system": "artery" - }, - { - "name": "ia", - "slug": "ia", - "title": "IA", - "status": "planned", - "system": "artery" - } - ] -} diff --git a/cfg/amar/docker-compose.yml b/cfg/amar/docker-compose.yml deleted file mode 100644 index 47a6816..0000000 --- a/cfg/amar/docker-compose.yml +++ /dev/null @@ -1,202 +0,0 @@ -# Amar Room - Docker Compose -# -# Creates: db, redis, backend, celery, celery-beat, frontend -# Network: Joins external network defined by NETWORK_NAME -# -# Usage: -# cd cfg/amar && docker compose up -d -# -# Required Environment Variables (from .env): -# - DEPLOYMENT_NAME: Prefix for container names -# - NETWORK_NAME: Network to join -# - BACKEND_PATH, FRONTEND_PATH: Source code paths (absolute) -# - DOCKERFILE_BACKEND, DOCKERFILE_FRONTEND: Dockerfile paths (absolute) -# - DB_DUMP: Database dump file (relative to dumps/) -# - Plus all app-specific vars (POSTGRES_*, etc.) - -services: - db: - image: postgis/postgis:15-3.4 - container_name: ${DEPLOYMENT_NAME}_db - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - pgdata:/var/lib/postgresql/data - # Database dump loaded on init (if volume is empty) - - ./dumps/${DB_DUMP}:/docker-entrypoint-initdb.d/dump.sql:ro - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - default - - redis: - image: redis:7-alpine - container_name: ${DEPLOYMENT_NAME}_redis - volumes: - - redisdata:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - default - - backend: - build: - context: ${BACKEND_PATH} - dockerfile: ${DOCKERFILE_BACKEND} - target: runtime - container_name: ${DEPLOYMENT_NAME}_backend - environment: - - SECRET_KEY - - DEBUG - - ALLOWED_HOSTS - - DJANGO_ENV - - DB_NAME=${POSTGRES_DB} - - DB_USER=${POSTGRES_USER} - - DB_PASSWORD=${POSTGRES_PASSWORD} - - DB_HOST=db - - DB_PORT=5432 - - CELERY_BROKER_URL=redis://redis:6379/0 - - CELERY_RESULT_BACKEND=redis://redis:6379/0 - - CORS_ALLOW_ALL - - CORS_ALLOWED_ORIGINS - # Uvicorn config (dev: 1 worker + reload, prod: 4 workers) - - WORKERS=${BACKEND_WORKERS:-1} - - RELOAD=${BACKEND_RELOAD:---reload} - # Google - - SUBJECT_CALENDAR - - SHEET_ID - - RANGE_NAME - - GOOGLE_MAPS_API_KEY - # Analytics - - GA4_MEASUREMENT_ID - - AMPLITUDE_API_KEY - - HOTJAR_API_KEY - # Payments - - ACCESS_TOKEN_MERCADO_PAGO - - MP_PLATFORM_ACCESS_TOKEN - - USER_ID - # Push - - WEBPUSH_VAPID_PUBLIC_KEY - - WEBPUSH_VAPID_PRIVATE_KEY - - WEBPUSH_VAPID_ADMIN_EMAIL - # Init - - USER_PASSWORD - volumes: - - ${BACKEND_PATH}:/app - - backend_static:/var/etc/static - - backend_media:/app/media - ports: - - "${BACKEND_PORT}:8000" - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - networks: - - default - command: > - sh -c "python manage.py migrate --noinput && - uvicorn amar_django_back.asgi:application --host 0.0.0.0 --port 8000 --workers $${WORKERS:-1} $${RELOAD}" - - celery: - build: - context: ${BACKEND_PATH} - dockerfile: ${DOCKERFILE_BACKEND} - target: runtime - container_name: ${DEPLOYMENT_NAME}_celery - environment: - - SECRET_KEY - - DEBUG - - DJANGO_ENV - - DB_NAME=${POSTGRES_DB} - - DB_USER=${POSTGRES_USER} - - DB_PASSWORD=${POSTGRES_PASSWORD} - - DB_HOST=db - - DB_PORT=5432 - - CELERY_BROKER_URL=redis://redis:6379/0 - - CELERY_RESULT_BACKEND=redis://redis:6379/0 - volumes: - - ${BACKEND_PATH}:/app - - backend_media:/app/media - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - networks: - - default - command: celery -A amar_django_back worker -l INFO - - celery-beat: - build: - context: ${BACKEND_PATH} - dockerfile: ${DOCKERFILE_BACKEND} - target: runtime - container_name: ${DEPLOYMENT_NAME}_celery_beat - environment: - - SECRET_KEY - - DEBUG - - DJANGO_ENV - - DB_NAME=${POSTGRES_DB} - - DB_USER=${POSTGRES_USER} - - DB_PASSWORD=${POSTGRES_PASSWORD} - - DB_HOST=db - - DB_PORT=5432 - - CELERY_BROKER_URL=redis://redis:6379/0 - - CELERY_RESULT_BACKEND=redis://redis:6379/0 - volumes: - - ${BACKEND_PATH}:/app - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - networks: - - default - command: celery -A amar_django_back beat -l INFO - - frontend: - build: - context: ${FRONTEND_PATH} - dockerfile: ${DOCKERFILE_FRONTEND} - target: development - container_name: ${DEPLOYMENT_NAME}_frontend - environment: - - NEXT_PUBLIC_APP_API_URL_BACKOFFICE - - NEXT_PUBLIC_APP_API_URL_STAGE - - NEXT_PUBLIC_IS_STAGE - - NEXT_PUBLIC_FB_PIXEL_ID - - NEXT_PUBLIC_TAG_MANAGER - - NEXT_PUBLIC_WHATSAPP_CONTACT - - NEXT_PUBLIC_API_KEY - - NEXT_PUBLIC_AMPLITUDE_API_KEY - - NEXT_PUBLIC_GMAPS_API_KEY - volumes: - - ${FRONTEND_PATH}/src:/app/src - - ${FRONTEND_PATH}/public:/app/public - ports: - - "${FRONTEND_PORT}:3000" - depends_on: - - backend - networks: - - default - # CMD from Dockerfile.frontend development stage: npm run dev - -volumes: - pgdata: - redisdata: - backend_static: - backend_media: - -networks: - default: - external: true - name: ${NETWORK_NAME} diff --git a/cfg/amar/link/Dockerfile b/cfg/amar/link/Dockerfile deleted file mode 100644 index 0c5d1ca..0000000 --- a/cfg/amar/link/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -# Install dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copy application -COPY . . - -# Run -CMD ["python", "main.py"] diff --git a/cfg/amar/link/README.md b/cfg/amar/link/README.md deleted file mode 100644 index ba700e7..0000000 --- a/cfg/amar/link/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# Link Room - Adapter Layer - -Provides framework-agnostic data navigation between managed apps (AMAR) and soleprint. - -## Status: Initial Implementation ✅ - -**Working:** -- ✅ FastAPI service with adapter pattern -- ✅ BaseAdapter interface for pluggable frameworks -- ✅ DjangoAdapter with AMAR database queries -- ✅ Docker build and container starts -- ✅ `/health` endpoint (adapter loads successfully) -- ✅ `/api/queries` endpoint (lists available queries) - -**Pending:** -- ⏳ Database connection (needs DB_HOST env var fix) -- ⏳ Complete all entity queries (Pet, Vet, ServiceRequest, etc.) -- ⏳ Ward integration (consume JSON and render graph) - -## Architecture - -``` -Managed App (AMAR) ←─── link_room ───→ Soleprint (Ward) - ↓ ↓ ↓ - Database Adapter Layer Graph Renderer - (SQL → JSON) (JSON → SVG) -``` - -**JSON Contract:** -```json -{ - "nodes": [ - {"id": "User_123", "type": "User", "label": "john", "data": {...}} - ], - "edges": [ - {"from": "User_123", "to": "PetOwner_456", "label": "has profile"} - ], - "summary": { - "title": "User #123", - "credentials": "john | Password: Amar2025!", - "fields": {"Email": "john@example.com"} - } -} -``` - -## Endpoints - -- `GET /health` - Health check with adapter status -- `GET /api/queries` - List available predefined queries -- `GET /api/navigate?query=user_with_pets` - Query mode -- `GET /api/navigate?entity=User&id=123` - Entity navigation mode - -## Available Queries - -1. `user_with_pets` - User with Pet ownership -2. `user_with_requests` - User with ServiceRequests - -## Usage - -```bash -# Start (from core_room/ctrl) -./start.sh link_room -d --build - -# Test -curl http://localhost:8100/health -curl http://localhost:8100/api/queries -curl "http://localhost:8100/api/navigate?query=user_with_pets" - -# Logs -docker logs core_room_link_room -``` - -## Environment Variables - -From `core_room/.env`: -- `NEST_NAME` - Container naming -- `NETWORK_NAME` - Docker network -- `DB_HOST` - Database host (needs fix: should point to db container) -- `DB_PORT` - Database port -- `DB_NAME` - Database name -- `DB_USER` - Database user -- `DB_PASSWORD` - Database password -- `ADAPTER_TYPE` - Adapter to use (default: django) - -## Next Steps - -1. **Fix DB connection** - Set correct DB_HOST in core_room/.env -2. **Complete queries** - Add remaining entity types -3. **Ward integration** - Create ward consumer for JSON -4. **Add graphviz rendering** - Move from data_browse reference -5. **Test end-to-end** - Query → JSON → SVG → Display - -## Files - -``` -link_room/ -├── README.md # This file -├── main.py # FastAPI app with endpoints -├── requirements.txt # Python dependencies -├── Dockerfile # Container build -├── docker-compose.yml # Service definition -└── adapters/ - ├── __init__.py # BaseAdapter interface - └── django.py # DjangoAdapter implementation -``` - -## Design Goals - -✅ **Framework-agnostic** - Works with Django, Rails, Express, etc. -✅ **Decoupled** - Managed app owns data, link_room translates -✅ **Pluggable** - Adapters for different frameworks -✅ **Authenticated** - Ready for remote deployment -✅ **Incremental** - Build and test each piece - -## Reference - -Previous approach (databrowse direct DB) saved in: -- Branch: `ref/databrowse-direct-db` (ward repo) -- Problem: Tight coupling, won't work remote -- Solution: This adapter pattern diff --git a/cfg/amar/link/adapters/__init__.py b/cfg/amar/link/adapters/__init__.py deleted file mode 100644 index 0cb953e..0000000 --- a/cfg/amar/link/adapters/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Adapters for different managed app frameworks. -""" - -from typing import Dict, List, Any, Optional -from abc import ABC, abstractmethod - - -class BaseAdapter(ABC): - """Base adapter interface.""" - - def __init__(self, config: Dict[str, Any]): - """ - Initialize adapter with configuration. - - Args: - config: Database connection or API endpoint configuration - """ - self.config = config - - @abstractmethod - def navigate( - self, - query: Optional[str] = None, - entity: Optional[str] = None, - id: Optional[int] = None - ) -> Dict[str, Any]: - """ - Navigate data graph. - - Returns: - { - "nodes": [{"id": str, "type": str, "label": str, "data": dict}], - "edges": [{"from": str, "to": str, "label": str}], - "summary": {"title": str, "credentials": str|None, "fields": dict} - } - """ - pass - - @abstractmethod - def get_queries(self) -> List[str]: - """Return list of available query names.""" - pass diff --git a/cfg/amar/link/adapters/django.py b/cfg/amar/link/adapters/django.py deleted file mode 100644 index d33e921..0000000 --- a/cfg/amar/link/adapters/django.py +++ /dev/null @@ -1,235 +0,0 @@ -""" -Django adapter for AMAR. - -Queries AMAR's PostgreSQL database directly. -""" - -from typing import Dict, List, Any, Optional -from sqlalchemy import create_engine, text -from . import BaseAdapter - - -class DjangoAdapter(BaseAdapter): - """Adapter for Django/AMAR.""" - - def __init__(self, config: Dict[str, Any]): - super().__init__(config) - self.engine = self._create_engine() - - def _create_engine(self): - """Create SQLAlchemy engine from config.""" - db_url = ( - f"postgresql://{self.config['user']}:{self.config['password']}" - f"@{self.config['host']}:{self.config['port']}/{self.config['name']}" - ) - return create_engine(db_url, pool_pre_ping=True) - - def _execute(self, sql: str) -> List[Dict[str, Any]]: - """Execute SQL and return results as list of dicts.""" - with self.engine.connect() as conn: - result = conn.execute(text(sql)) - rows = result.fetchall() - columns = result.keys() - return [dict(zip(columns, row)) for row in rows] - - def get_queries(self) -> List[str]: - """Available predefined queries.""" - return [ - "user_with_pets", - "user_with_requests", - ] - - def navigate( - self, - query: Optional[str] = None, - entity: Optional[str] = None, - id: Optional[int] = None - ) -> Dict[str, Any]: - """Navigate data graph.""" - - if query: - return self._query_mode(query) - elif entity and id: - return self._entity_mode(entity, id) - else: - raise ValueError("Must provide either query or entity+id") - - def _query_mode(self, query_name: str) -> Dict[str, Any]: - """Execute predefined query.""" - - if query_name == "user_with_pets": - sql = """ - SELECT - u.id as user_id, u.username, u.email, - po.id as petowner_id, po.first_name, po.last_name, po.phone, - p.id as pet_id, p.name as pet_name, p.pet_type, p.age - FROM auth_user u - JOIN mascotas_petowner po ON po.user_id = u.id - JOIN mascotas_pet p ON p.owner_id = po.id - WHERE p.deleted = false - LIMIT 1 - """ - elif query_name == "user_with_requests": - sql = """ - SELECT - u.id as user_id, u.username, u.email, - po.id as petowner_id, po.first_name, po.last_name, - sr.id as request_id, sr.state, sr.created_at - FROM auth_user u - JOIN mascotas_petowner po ON po.user_id = u.id - JOIN solicitudes_servicerequest sr ON sr.petowner_id = po.id - WHERE sr.deleted = false - ORDER BY sr.created_at DESC - LIMIT 1 - """ - else: - raise ValueError(f"Unknown query: {query_name}") - - rows = self._execute(sql) - if not rows: - return self._empty_response() - - return self._rows_to_graph(rows[0]) - - def _entity_mode(self, entity: str, id: int) -> Dict[str, Any]: - """Navigate to specific entity.""" - - if entity == "User": - sql = f""" - SELECT - u.id as user_id, u.username, u.email, - po.id as petowner_id, po.first_name, po.last_name, po.phone - FROM auth_user u - LEFT JOIN mascotas_petowner po ON po.user_id = u.id - WHERE u.id = {id} - """ - else: - raise ValueError(f"Unknown entity: {entity}") - - rows = self._execute(sql) - if not rows: - return self._empty_response() - - return self._rows_to_graph(rows[0]) - - def _rows_to_graph(self, row: Dict[str, Any]) -> Dict[str, Any]: - """Convert SQL row to graph structure.""" - nodes = [] - edges = [] - - # User node - if "user_id" in row and row["user_id"]: - nodes.append({ - "id": f"User_{row['user_id']}", - "type": "User", - "label": row.get("username") or row.get("email", ""), - "data": { - "id": row["user_id"], - "username": row.get("username"), - "email": row.get("email"), - } - }) - - # PetOwner node - if "petowner_id" in row and row["petowner_id"]: - name = f"{row.get('first_name', '')} {row.get('last_name', '')}".strip() - nodes.append({ - "id": f"PetOwner_{row['petowner_id']}", - "type": "PetOwner", - "label": name or "PetOwner", - "data": { - "id": row["petowner_id"], - "first_name": row.get("first_name"), - "last_name": row.get("last_name"), - "phone": row.get("phone"), - } - }) - if "user_id" in row and row["user_id"]: - edges.append({ - "from": f"User_{row['user_id']}", - "to": f"PetOwner_{row['petowner_id']}", - "label": "has profile" - }) - - # Pet node - if "pet_id" in row and row["pet_id"]: - nodes.append({ - "id": f"Pet_{row['pet_id']}", - "type": "Pet", - "label": row.get("pet_name", "Pet"), - "data": { - "id": row["pet_id"], - "name": row.get("pet_name"), - "pet_type": row.get("pet_type"), - "age": row.get("age"), - } - }) - if "petowner_id" in row and row["petowner_id"]: - edges.append({ - "from": f"PetOwner_{row['petowner_id']}", - "to": f"Pet_{row['pet_id']}", - "label": "owns" - }) - - # ServiceRequest node - if "request_id" in row and row["request_id"]: - nodes.append({ - "id": f"ServiceRequest_{row['request_id']}", - "type": "ServiceRequest", - "label": f"Request #{row['request_id']}", - "data": { - "id": row["request_id"], - "state": row.get("state"), - "created_at": str(row.get("created_at", "")), - } - }) - if "petowner_id" in row and row["petowner_id"]: - edges.append({ - "from": f"PetOwner_{row['petowner_id']}", - "to": f"ServiceRequest_{row['request_id']}", - "label": "requested" - }) - - # Build summary from first User node - summary = self._build_summary(nodes) - - return { - "nodes": nodes, - "edges": edges, - "summary": summary - } - - def _build_summary(self, nodes: List[Dict]) -> Dict[str, Any]: - """Build summary from nodes.""" - - # Find User node - user_node = next((n for n in nodes if n["type"] == "User"), None) - if user_node: - data = user_node["data"] - return { - "title": f"User #{data['id']}", - "credentials": f"{data.get('username', 'N/A')} | Password: Amar2025!", - "fields": { - "Email": data.get("email", "N/A"), - "Username": data.get("username", "N/A"), - } - } - - # Fallback - return { - "title": "No data", - "credentials": None, - "fields": {} - } - - def _empty_response(self) -> Dict[str, Any]: - """Return empty response structure.""" - return { - "nodes": [], - "edges": [], - "summary": { - "title": "No data found", - "credentials": None, - "fields": {} - } - } diff --git a/cfg/amar/link/docker-compose.yml b/cfg/amar/link/docker-compose.yml deleted file mode 100644 index e525610..0000000 --- a/cfg/amar/link/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -services: - link_nest: - build: - context: . - dockerfile: Dockerfile - container_name: ${NEST_NAME}_link_nest - ports: - - "8100:8000" - environment: - - PORT=8000 - - ADAPTER_TYPE=${ADAPTER_TYPE:-django} - - DB_HOST=${DB_HOST} - - DB_PORT=${DB_PORT} - - DB_NAME=${DB_NAME} - - DB_USER=${DB_USER} - - DB_PASSWORD=${DB_PASSWORD} - volumes: - - ./:/app - networks: - - default - -networks: - default: - external: true - name: ${NETWORK_NAME} diff --git a/cfg/amar/link/main.py b/cfg/amar/link/main.py deleted file mode 100644 index 790e584..0000000 --- a/cfg/amar/link/main.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -Link Room - Adapter layer between managed apps and soleprint. - -Exposes standardized JSON endpoints for data navigation. -Framework-agnostic via pluggable adapters. -""" - -import os -from typing import Optional -from fastapi import FastAPI, HTTPException - -app = FastAPI(title="Link Room", version="0.1.0") - -# Lazy-loaded adapter instance -_adapter = None - - -def get_adapter(): - """Get or create adapter instance.""" - global _adapter - if _adapter is None: - adapter_type = os.getenv("ADAPTER_TYPE", "django") - - # Database config from environment - db_config = { - "host": os.getenv("DB_HOST", "localhost"), - "port": int(os.getenv("DB_PORT", "5432")), - "name": os.getenv("DB_NAME", "amarback"), - "user": os.getenv("DB_USER", "postgres"), - "password": os.getenv("DB_PASSWORD", ""), - } - - if adapter_type == "django": - from adapters.django import DjangoAdapter - _adapter = DjangoAdapter(db_config) - else: - raise ValueError(f"Unknown adapter type: {adapter_type}") - - return _adapter - - -@app.get("/health") -def health(): - """Health check.""" - adapter_type = os.getenv("ADAPTER_TYPE", "django") - - # Test adapter connection - adapter_ok = False - try: - adapter = get_adapter() - adapter_ok = True - except Exception as e: - print(f"Adapter error: {e}") - - return { - "status": "ok" if adapter_ok else "degraded", - "service": "link-room", - "adapter": adapter_type, - "adapter_loaded": adapter_ok, - } - - -@app.get("/api/queries") -def list_queries(): - """List available predefined queries.""" - adapter = get_adapter() - return { - "queries": adapter.get_queries() - } - - -@app.get("/api/navigate") -def navigate(query: Optional[str] = None, entity: Optional[str] = None, id: Optional[int] = None): - """ - Navigate data graph. - - Query mode: ?query=user_with_pets - Navigation mode: ?entity=User&id=123 - - Returns: - { - "nodes": [...], - "edges": [...], - "summary": {...} - } - """ - try: - adapter = get_adapter() - result = adapter.navigate(query=query, entity=entity, id=id) - return result - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - except Exception as e: - print(f"Navigate error: {e}") - raise HTTPException(status_code=500, detail=str(e)) - - -if __name__ == "__main__": - import uvicorn - uvicorn.run( - "main:app", - host="0.0.0.0", - port=int(os.getenv("PORT", "8000")), - reload=True, - ) diff --git a/cfg/amar/link/requirements.txt b/cfg/amar/link/requirements.txt deleted file mode 100644 index f55dadc..0000000 --- a/cfg/amar/link/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi -uvicorn[standard] -psycopg2-binary -sqlalchemy diff --git a/cfg/amar/soleprint/Dockerfile.fastapi b/cfg/amar/soleprint/Dockerfile.fastapi deleted file mode 100644 index 9848d09..0000000 --- a/cfg/amar/soleprint/Dockerfile.fastapi +++ /dev/null @@ -1,20 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - gcc \ - libpq-dev \ - && rm -rf /var/lib/apt/lists/* - -# Install Python dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copy application -COPY . . - -EXPOSE 8000 - -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/cfg/amar/soleprint/README.nginx.md b/cfg/amar/soleprint/README.nginx.md deleted file mode 100644 index f50f16f..0000000 --- a/cfg/amar/soleprint/README.nginx.md +++ /dev/null @@ -1,118 +0,0 @@ -# Nginx for Local Development - -## Overview - -The `docker-compose.nginx.yml` file provides an **optional** nginx container for local development. This is **NOT** used on AWS (which uses bare metal nginx). - -## When to Use - -Use nginx container when you want to access services via friendly domains locally: -- `http://amarmascotas.local.com` → amar -- `http://soleprint.local.com` → soleprint + services - -## Setup - -### 1. Generate Nginx Config - -```bash -cd ../ctrl/server -./setup.sh --local -``` - -This creates `/tmp/core_room.conf` with your local domain routing. - -### 2. Ensure /etc/hosts is configured - -```bash -# Should already be there, but verify: -grep "127.0.0.1.*amarmascotas.local.com" /etc/hosts -grep "127.0.0.1.*soleprint.local.com" /etc/hosts -``` - -### 3. Stop bare metal nginx (if running) - -```bash -sudo systemctl stop nginx -# Optional: disable so it doesn't start on boot -sudo systemctl disable nginx -``` - -### 4. Start services WITH nginx - -```bash -cd ../ctrl -./start.sh --with-nginx -# OR manually: -cd ../soleprint -docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d -``` - -## Without Nginx (Default) - -If you don't use nginx, services are accessed via ports: -- `http://localhost:3000` → amar frontend -- `http://localhost:8000` → amar backend -- `http://localhost:13000` → soleprint -- `http://localhost:13001` → artery -- `http://localhost:13002` → album -- `http://localhost:13003` → ward - -The default `docker-compose.yml` exposes these ports, so it works either way. - -## Switching Between Nginx and Direct Access - -**To use nginx routing:** -```bash -# Stop direct port access -cd ../ctrl && ./stop.sh - -# Start with nginx -./start.sh --with-nginx -``` - -**To go back to direct ports:** -```bash -# Stop nginx version -cd ../soleprint -docker compose -f docker-compose.yml -f docker-compose.nginx.yml down - -# Start without nginx -cd ../ctrl && ./start.sh -``` - -## AWS Production - -On AWS, **do NOT use** `docker-compose.nginx.yml`. The AWS setup uses bare metal nginx configured via `ctrl/server/setup.sh --production`. - -## Troubleshooting - -**Port 80 already in use:** -```bash -# Check what's using it -ss -tlnp | grep :80 - -# If it's nginx bare metal -sudo systemctl stop nginx - -# If it's another container -docker ps | grep :80 -docker stop -``` - -**Config not found error:** -```bash -# Make sure you ran setup first -cd ../ctrl/server && ./setup.sh --local - -# Verify config exists -ls -la /tmp/core_room.conf -``` - -**DNS not resolving:** -```bash -# Check /etc/hosts -cat /etc/hosts | grep local.com - -# Test DNS -ping -c 1 amarmascotas.local.com -``` diff --git a/cfg/amar/soleprint/artery/shunts/amar/.env.example b/cfg/amar/soleprint/artery/shunts/amar/.env.example deleted file mode 100644 index 2e61175..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -# Amar (MOCK) Vein Configuration -API_PORT=8005 - -# Mock data settings -MOCK_DATA_PATH=./mock_data - -# Mock behavior -ENABLE_RANDOM_DELAYS=true -MIN_DELAY_MS=100 -MAX_DELAY_MS=500 - -# Simulate errors -ERROR_RATE=0.0 # 0.0 to 1.0 (0% to 100%) diff --git a/cfg/amar/soleprint/artery/shunts/amar/README.md b/cfg/amar/soleprint/artery/shunts/amar/README.md deleted file mode 100644 index e027a15..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# Amar (MOCK) Vein - -Mock Amar API for testing - returns predictable test data for turnero flow testing. - -## Purpose - -Enables testing of the turnero flow (VET-535-540) without hitting the real Amar backend: -- Guest petowner creation (VET-536) -- Pet creation (VET-537) -- Cart creation and price calculation (VET-538) -- Service/category filtering (VET-539, VET-540) -- Service request creation - -## Quick Start - -```bash -# Install dependencies -pip install -r requirements.txt - -# Start the mock server -python run.py - -# API docs: http://localhost:8005/docs -# Health check: http://localhost:8005/health -``` - -## Configuration - -Copy `.env.example` to `.env` and adjust: - -```bash -API_PORT=8005 # Server port -ENABLE_RANDOM_DELAYS=true # Add realistic delays -MIN_DELAY_MS=100 # Minimum delay -MAX_DELAY_MS=500 # Maximum delay -ERROR_RATE=0.0 # Error rate (0.0 to 1.0) -``` - -## API Endpoints - -### Turnero Flow (VET-536-540) - -```bash -# Create guest petowner (VET-536) -POST /api/v1/pet-owners/ -{ - "address": "Av. Santa Fe 1234, Palermo" -} - -# Create pet (VET-537) -POST /api/v1/pets/ -{ - "owner_id": 1234, - "name": "Luna", - "species": "DOG", - "age": 3, - "age_unit": "years" -} - -# Create cart (VET-538) -POST /api/v1/cart/ -{ - "owner_id": 1234 -} - -# Add item to cart -POST /api/v1/cart/{cart_id}/items/ -{ - "service_id": 1, - "pet_id": 5678, - "quantity": 1 -} - -# List services (VET-540) -GET /api/v1/services/?species=DOG&neighborhood_id=1 - -# List categories (VET-539) -GET /api/v1/categories/?species=DOG&neighborhood_id=1 - -# Create service request -POST /solicitudes/service-requests/?cart_id=12345 -``` - -### Mock Control - -```bash -# Get mock database stats -GET /mock/stats - -# Reset mock database -GET /mock/reset -``` - -## Response Format - -All responses include `_mock: true` to identify mock data: - -```json -{ - "id": 1234, - "address": "Av. Santa Fe 1234, Palermo", - "is_guest": true, - "_mock": true -} -``` - -## Testing Scenarios - -### Complete Turnero Flow - -```python -import requests - -BASE_URL = "http://localhost:8005" - -# Step 1: Create guest petowner -owner_resp = requests.post(f"{BASE_URL}/api/v1/pet-owners/", json={ - "address": "Av. Santa Fe 1234, Palermo" -}) -owner = owner_resp.json() - -# Step 2: Create pet -pet_resp = requests.post(f"{BASE_URL}/api/v1/pets/", json={ - "owner_id": owner["id"], - "name": "Luna", - "species": "DOG", - "age": 3, - "age_unit": "years" -}) -pet = pet_resp.json() - -# Step 3: Create cart -cart_resp = requests.post(f"{BASE_URL}/api/v1/cart/", json={ - "owner_id": owner["id"] -}) -cart = cart_resp.json() - -# Step 4: Get available services -services_resp = requests.get( - f"{BASE_URL}/api/v1/services/", - params={"species": "DOG", "neighborhood_id": owner["neighborhood"]["id"]} -) -services = services_resp.json() - -# Step 5: Add service to cart -cart_resp = requests.post(f"{BASE_URL}/api/v1/cart/{cart['id']}/items/", json={ - "service_id": services[0]["id"], - "pet_id": pet["id"], - "quantity": 1 -}) -cart = cart_resp.json() -print(f"Cart total: ${cart['resume']['total']}") - -# Step 6: Create service request -request_resp = requests.post( - f"{BASE_URL}/solicitudes/service-requests/", - params={"cart_id": cart["id"]} -) -service_request = request_resp.json() -print(f"Service request created: {service_request['id']}") -``` - -## Data Generator - -This vein uses the independent `datagen` tool from `ward/tools/datagen/amar.py`. -See `ward/tools/datagen/README.md` for data generation details. - -## Notes - -- Mock database is in-memory (resets on server restart) -- Use `/mock/reset` to clear data during testing -- All IDs are randomly generated on creation -- Multi-pet discounts are automatically calculated diff --git a/cfg/amar/soleprint/artery/shunts/amar/__init__.py b/cfg/amar/soleprint/artery/shunts/amar/__init__.py deleted file mode 100644 index 54f82fb..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Amar (MOCK) vein - Mock Amar API for testing.""" diff --git a/cfg/amar/soleprint/artery/shunts/amar/api/__init__.py b/cfg/amar/soleprint/artery/shunts/amar/api/__init__.py deleted file mode 100644 index 9430ca4..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""API routes for Amar mock vein.""" diff --git a/cfg/amar/soleprint/artery/shunts/amar/api/routes.py b/cfg/amar/soleprint/artery/shunts/amar/api/routes.py deleted file mode 100644 index 9c74eac..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/api/routes.py +++ /dev/null @@ -1,302 +0,0 @@ -"""API routes for Amar (MOCK) vein - Mock Amar API for testing.""" - -import asyncio -import random -from fastapi import APIRouter, HTTPException, Query -from fastapi.responses import JSONResponse -from typing import Optional, List, Dict, Any -from pydantic import BaseModel - -# Import datagen from ward/tools -import sys -from pathlib import Path -ward_tools_path = Path(__file__).parent.parent.parent.parent.parent / "ward" / "tools" -sys.path.insert(0, str(ward_tools_path)) - -from datagen.amar import AmarDataGenerator - -from ..core.config import settings - -router = APIRouter() - -# In-memory storage for mock data (reset on restart) -MOCK_DB = { - "petowners": {}, - "pets": {}, - "carts": {}, - "service_requests": {}, -} - - -# Request/Response Models -class CreatePetOwnerRequest(BaseModel): - address: str - email: Optional[str] = None - phone: Optional[str] = None - - -class CreatePetRequest(BaseModel): - owner_id: int - name: str - species: str # DOG, CAT - age: Optional[int] = None - age_unit: str = "years" # years, months - - -class CreateCartRequest(BaseModel): - owner_id: int - - -class AddCartItemRequest(BaseModel): - service_id: int - pet_id: int - quantity: int = 1 - - -async def _mock_delay(): - """Add realistic delay if enabled.""" - if settings.enable_random_delays: - delay_ms = random.randint(settings.min_delay_ms, settings.max_delay_ms) - await asyncio.sleep(delay_ms / 1000) - - -def _maybe_error(): - """Randomly raise an error based on error_rate.""" - if random.random() < settings.error_rate: - raise HTTPException(500, "Mock error: Simulated failure") - - -@router.get("/health") -async def health(): - """Health check endpoint.""" - return { - "status": "ok", - "vein": "Amar\n(MOCK)", - "message": "Mock Amar API for testing", - "_mock": True, - } - - -@router.post("/api/v1/pet-owners/") -async def create_petowner(request: CreatePetOwnerRequest): - """Create a guest petowner (VET-536).""" - await _mock_delay() - _maybe_error() - - owner = AmarDataGenerator.petowner( - address=request.address, - is_guest=True, - email=request.email, - phone=request.phone, - ) - - # Store in mock DB - MOCK_DB["petowners"][owner["id"]] = owner - - owner["_mock"] = True - return owner - - -@router.get("/api/v1/pet-owners/{owner_id}") -async def get_petowner(owner_id: int): - """Get petowner by ID.""" - await _mock_delay() - _maybe_error() - - owner = MOCK_DB["petowners"].get(owner_id) - if not owner: - raise HTTPException(404, f"PetOwner {owner_id} not found") - - return owner - - -@router.post("/api/v1/pets/") -async def create_pet(request: CreatePetRequest): - """Create a pet (VET-537).""" - await _mock_delay() - _maybe_error() - - # Verify owner exists - if request.owner_id not in MOCK_DB["petowners"]: - raise HTTPException(404, f"Owner {request.owner_id} not found") - - pet = AmarDataGenerator.pet( - owner_id=request.owner_id, - name=request.name, - species=request.species, - age_value=request.age, - age_unit=request.age_unit, - ) - - # Store in mock DB - MOCK_DB["pets"][pet["id"]] = pet - - pet["_mock"] = True - return pet - - -@router.get("/api/v1/pets/{pet_id}") -async def get_pet(pet_id: int): - """Get pet by ID.""" - await _mock_delay() - _maybe_error() - - pet = MOCK_DB["pets"].get(pet_id) - if not pet: - raise HTTPException(404, f"Pet {pet_id} not found") - - return pet - - -@router.post("/api/v1/cart/") -async def create_cart(request: CreateCartRequest): - """Create a cart (VET-538).""" - await _mock_delay() - _maybe_error() - - # Verify owner exists - if request.owner_id not in MOCK_DB["petowners"]: - raise HTTPException(404, f"Owner {request.owner_id} not found") - - cart = AmarDataGenerator.cart(owner_id=request.owner_id) - - # Store in mock DB - MOCK_DB["carts"][cart["id"]] = cart - - cart["_mock"] = True - return cart - - -@router.post("/api/v1/cart/{cart_id}/items/") -async def add_cart_item(cart_id: int, request: AddCartItemRequest): - """Add item to cart and recalculate summary.""" - await _mock_delay() - _maybe_error() - - cart = MOCK_DB["carts"].get(cart_id) - if not cart: - raise HTTPException(404, f"Cart {cart_id} not found") - - # Get service price - services = AmarDataGenerator.SERVICES - service = next((s for s in services if s["id"] == request.service_id), None) - if not service: - raise HTTPException(404, f"Service {request.service_id} not found") - - # Add item - item = { - "service_id": request.service_id, - "service_name": service["name"], - "pet_id": request.pet_id, - "quantity": request.quantity, - "price": service["price"], - } - - cart["items"].append(item) - - # Recalculate summary - cart = AmarDataGenerator.calculate_cart_summary(cart, cart["items"]) - MOCK_DB["carts"][cart_id] = cart - - cart["_mock"] = True - return cart - - -@router.get("/api/v1/cart/{cart_id}") -async def get_cart(cart_id: int): - """Get cart by ID.""" - await _mock_delay() - _maybe_error() - - cart = MOCK_DB["carts"].get(cart_id) - if not cart: - raise HTTPException(404, f"Cart {cart_id} not found") - - return cart - - -@router.get("/api/v1/services/") -async def list_services( - species: Optional[str] = Query(None), - neighborhood_id: Optional[int] = Query(None), -): - """List available services filtered by species and neighborhood (VET-540).""" - await _mock_delay() - _maybe_error() - - services = AmarDataGenerator.filter_services( - species=species, - neighborhood_id=neighborhood_id, - ) - - for service in services: - service["_mock"] = True - - return services - - -@router.get("/api/v1/categories/") -async def list_categories( - species: Optional[str] = Query(None), - neighborhood_id: Optional[int] = Query(None), -): - """List categories with available services (VET-539).""" - await _mock_delay() - _maybe_error() - - categories = AmarDataGenerator.filter_categories( - species=species, - neighborhood_id=neighborhood_id, - ) - - for category in categories: - category["_mock"] = True - - return categories - - -@router.post("/solicitudes/service-requests/") -async def create_service_request(cart_id: int, requested_date: Optional[str] = None): - """Create a service request.""" - await _mock_delay() - _maybe_error() - - cart = MOCK_DB["carts"].get(cart_id) - if not cart: - raise HTTPException(404, f"Cart {cart_id} not found") - - request = AmarDataGenerator.service_request( - cart_id=cart_id, - requested_date=requested_date, - ) - - MOCK_DB["service_requests"][request["id"]] = request - - request["_mock"] = True - return request - - -@router.get("/mock/reset") -async def reset_mock_db(): - """Reset the mock database (useful for testing).""" - MOCK_DB["petowners"].clear() - MOCK_DB["pets"].clear() - MOCK_DB["carts"].clear() - MOCK_DB["service_requests"].clear() - - return { - "message": "Mock database reset", - "_mock": True, - } - - -@router.get("/mock/stats") -async def mock_stats(): - """Get mock database statistics.""" - return { - "petowners": len(MOCK_DB["petowners"]), - "pets": len(MOCK_DB["pets"]), - "carts": len(MOCK_DB["carts"]), - "service_requests": len(MOCK_DB["service_requests"]), - "_mock": True, - } diff --git a/cfg/amar/soleprint/artery/shunts/amar/core/__init__.py b/cfg/amar/soleprint/artery/shunts/amar/core/__init__.py deleted file mode 100644 index b0281c0..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Core logic for Amar mock API.""" diff --git a/cfg/amar/soleprint/artery/shunts/amar/core/config.py b/cfg/amar/soleprint/artery/shunts/amar/core/config.py deleted file mode 100644 index c93dc14..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/core/config.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Configuration for Amar mock vein.""" - -from pathlib import Path -from pydantic_settings import BaseSettings - -ENV_FILE = Path(__file__).parent.parent / ".env" - - -class AmarMockConfig(BaseSettings): - """Configuration for Amar (MOCK) vein.""" - - api_port: int = 8005 - mock_data_path: str = "./mock_data" - - # Mock behavior - enable_random_delays: bool = True - min_delay_ms: int = 100 - max_delay_ms: int = 500 - error_rate: float = 0.0 # 0.0 to 1.0 - - model_config = { - "env_file": ENV_FILE if ENV_FILE.exists() else None, - "env_file_encoding": "utf-8", - } - - -settings = AmarMockConfig() diff --git a/cfg/amar/soleprint/artery/shunts/amar/main.py b/cfg/amar/soleprint/artery/shunts/amar/main.py deleted file mode 100644 index 1e9ae94..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/main.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Amar (MOCK) Vein - FastAPI app.""" - -from pathlib import Path -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from fastapi.templating import Jinja2Templates -from fastapi.middleware.cors import CORSMiddleware -from .api.routes import router -from .core.config import settings - -app = FastAPI( - title="Amar API (MOCK)", - description="Mock Amar API for testing - returns predictable test data", - version="0.1.0", -) - -# Enable CORS for testing from frontend -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # In production, specify exact origins - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# Templates for configuration UI -templates = Jinja2Templates(directory=Path(__file__).parent / "templates") - -@app.get("/", response_class=HTMLResponse) -def index(request: Request): - """Mock configuration UI.""" - return templates.TemplateResponse(request, "index.html") - -# Include router at root (matches real Amar API structure) -app.include_router(router) - - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=settings.api_port) diff --git a/cfg/amar/soleprint/artery/shunts/amar/requirements.txt b/cfg/amar/soleprint/artery/shunts/amar/requirements.txt deleted file mode 100644 index aaa3fb5..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -fastapi>=0.104.0 -uvicorn>=0.24.0 -pydantic>=2.0.0 -pydantic-settings>=2.0.0 diff --git a/cfg/amar/soleprint/artery/shunts/amar/run.py b/cfg/amar/soleprint/artery/shunts/amar/run.py deleted file mode 100644 index a2064ab..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/run.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Standalone runner for Amar mock vein.""" - -import logging -import uvicorn -from main import app -from core.config import settings - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -if __name__ == "__main__": - logger.info(f"Starting Amar (MOCK) vein on port {settings.api_port}") - logger.info(f"API docs: http://localhost:{settings.api_port}/docs") - logger.info(f"Health check: http://localhost:{settings.api_port}/health") - uvicorn.run(app, host="0.0.0.0", port=settings.api_port, reload=True) diff --git a/cfg/amar/soleprint/artery/shunts/amar/templates/index.html b/cfg/amar/soleprint/artery/shunts/amar/templates/index.html deleted file mode 100644 index 24220f6..0000000 --- a/cfg/amar/soleprint/artery/shunts/amar/templates/index.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - Amar API (MOCK) - Configuration - - - -
-
-

Amar API MOCK

-
Configure mock responses for Amar backend endpoints
-
- - -
-
Mock Database Stats
-
-
-
-
-
Pet Owners
-
-
-
-
-
Pets
-
-
-
-
-
Carts
-
-
-
-
-
Requests
-
-
-
- -
-
- - -
-
Configure Endpoint Responses
-
-
-
- POST - /api/v1/pet-owners/ -
-
Create guest pet owner (VET-536)
-
-
-
- POST - /api/v1/pets/ -
-
Create pet (VET-537)
-
-
-
- POST - /api/v1/cart/ -
-
Create cart (VET-538)
-
-
-
- GET - /api/v1/services/ -
-
List services (VET-540)
-
-
-
- GET - /api/v1/categories/ -
-
List categories (VET-539)
-
-
-
- - - - - -
-
Quick Test
-

Test endpoint URL to hit for configured responses:

-
- http://localhost:8005/api/v1/pet-owners/ -
-
-
- - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.dot b/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.dot deleted file mode 100644 index 7c6da1c..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.dot +++ /dev/null @@ -1,184 +0,0 @@ -digraph BackendArchitecture { - // Graph settings - rankdir=TB - compound=true - splines=ortho - node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=11] - edge [fontname="Helvetica", fontsize=9] - - // Color scheme - // Django Core: #092E20 (dark green) - // Mascotas: #4A90D9 (blue) - // Productos: #50C878 (green) - // Solicitudes: #FF6B6B (coral) - // Common: #9B59B6 (purple) - // External: #F39C12 (orange) - // Payments: #E74C3C (red) - - label="AMAR Mascotas - Backend Architecture (Django)\n\n" - labelloc="t" - fontsize=16 - fontname="Helvetica-Bold" - - // Django Core Cluster - subgraph cluster_django_core { - label="Django Core" - style="rounded,filled" - fillcolor="#E8F5E9" - color="#2E7D32" - - auth_user [label="auth.User\n(Django Auth)", fillcolor="#C8E6C9"] - django_admin [label="Django Admin\nInterface", fillcolor="#C8E6C9"] - drf [label="Django REST\nFramework", fillcolor="#C8E6C9"] - jwt_auth [label="JWT Authentication\n(SimpleJWT)", fillcolor="#C8E6C9"] - } - - // Mascotas App Cluster - subgraph cluster_mascotas { - label="mascotas (Pets & Veterinarians)" - style="rounded,filled" - fillcolor="#E3F2FD" - color="#1565C0" - - petowner [label="PetOwner\n(Cliente/Tutor)", fillcolor="#BBDEFB"] - pet [label="Pet\n(Mascota)", fillcolor="#BBDEFB"] - veterinarian [label="Veterinarian\n(Profesional)", fillcolor="#BBDEFB"] - vetvisit [label="VetVisit\n(Consulta)", fillcolor="#BBDEFB"] - vetvisitreport [label="VetVisitReport\n(Informe Clinico)", fillcolor="#BBDEFB"] - availability [label="Availability /\nUnavailability", fillcolor="#BBDEFB"] - pet_health [label="PetVaccine /\nPetStudy", fillcolor="#BBDEFB"] - } - - // Productos App Cluster - subgraph cluster_productos { - label="productos (Services & Pricing)" - style="rounded,filled" - fillcolor="#E8F5E9" - color="#2E7D32" - - service [label="Service\n(Servicio)", fillcolor="#C8E6C9"] - category [label="Category / Group\n(Categorias)", fillcolor="#C8E6C9"] - prices [label="Prices\n(Precios)", fillcolor="#C8E6C9"] - discounts [label="Discounts\n(Descuentos)", fillcolor="#C8E6C9"] - cart [label="Cart / CartItem\n(Carrito)", fillcolor="#C8E6C9"] - combo [label="ServiceCombo\n(Paquetes)", fillcolor="#C8E6C9"] - } - - // Solicitudes App Cluster - subgraph cluster_solicitudes { - label="solicitudes (Service Requests)" - style="rounded,filled" - fillcolor="#FFEBEE" - color="#C62828" - - servicerequest [label="ServiceRequest\n(Solicitud)", fillcolor="#FFCDD2"] - statehistory [label="StateHistory\n(Historial)", fillcolor="#FFCDD2"] - vetasked [label="VeterinarianAsked\n(Vet Consultado)", fillcolor="#FFCDD2"] - reminders [label="Reminders\n(Recordatorios)", fillcolor="#FFCDD2"] - } - - // Common App Cluster - subgraph cluster_common { - label="common (Shared Models)" - style="rounded,filled" - fillcolor="#F3E5F5" - color="#7B1FA2" - - campaign [label="Campaign\n(Marketing)", fillcolor="#E1BEE7"] - tag [label="Tag\n(Etiquetas)", fillcolor="#E1BEE7"] - specialty [label="Specialty\n(Especialidades)", fillcolor="#E1BEE7"] - medication [label="Medication\n(Medicamentos)", fillcolor="#E1BEE7"] - breed [label="PetBreed / Vaccine\n/ Study", fillcolor="#E1BEE7"] - neighborhood [label="Neighborhood /\nProvince / Locality", fillcolor="#E1BEE7"] - turnfee [label="IndividualTurnFee\nGroup", fillcolor="#E1BEE7"] - } - - // Payments App Cluster - subgraph cluster_payments { - label="payments (Payment Processing)" - style="rounded,filled" - fillcolor="#FCE4EC" - color="#AD1457" - - mercadopago [label="MercadoPago\nAccount", fillcolor="#F8BBD9"] - mpnotification [label="MP Notification\n(Webhooks)", fillcolor="#F8BBD9"] - } - - // External Integrations Cluster - subgraph cluster_external { - label="External Integrations" - style="rounded,filled" - fillcolor="#FFF3E0" - color="#E65100" - - google_cal [label="Google Calendar\n(Agenda)", fillcolor="#FFE0B2"] - google_sheets [label="Google Sheets\n(Exports)", fillcolor="#FFE0B2"] - mercately [label="Mercately\n(WhatsApp)", fillcolor="#FFE0B2"] - afip [label="AFIP\n(Facturacion)", fillcolor="#FFE0B2"] - celery [label="Celery\n(Async Tasks)", fillcolor="#FFE0B2"] - } - - // AFIP Integration - subgraph cluster_afip { - label="django_afip (Invoicing)" - style="rounded,filled" - fillcolor="#FFFDE7" - color="#F9A825" - - receipt [label="Receipt\n(Comprobante)", fillcolor="#FFF9C4"] - taxpayer [label="TaxPayer\n(Contribuyente)", fillcolor="#FFF9C4"] - pos [label="PointOfSales\n(Punto de Venta)", fillcolor="#FFF9C4"] - } - - // Relationships - Core - auth_user -> petowner [label="1:1 optional", color="#666"] - auth_user -> veterinarian [label="1:1", color="#666"] - drf -> jwt_auth [style=dashed, color="#999"] - - // Relationships - Mascotas - petowner -> pet [label="1:N", color="#1565C0"] - petowner -> servicerequest [label="1:N", color="#1565C0"] - pet -> vetvisit [label="N:M", color="#1565C0"] - pet -> pet_health [label="1:N", color="#1565C0"] - veterinarian -> vetvisit [label="1:N", color="#1565C0"] - veterinarian -> availability [label="1:N", color="#1565C0"] - vetvisit -> vetvisitreport [label="1:N", color="#1565C0"] - vetvisit -> servicerequest [label="1:1", style=dashed, color="#1565C0"] - - // Relationships - Productos - service -> category [label="N:1", color="#2E7D32"] - service -> prices [label="1:N", color="#2E7D32"] - service -> discounts [label="1:N", color="#2E7D32"] - cart -> petowner [label="N:1", color="#2E7D32"] - cart -> veterinarian [label="N:1 optional", color="#2E7D32", style=dashed] - service -> cart [label="via CartItem", color="#2E7D32"] - combo -> service [label="contains", color="#2E7D32"] - - // Relationships - Solicitudes - servicerequest -> cart [label="1:1", color="#C62828"] - servicerequest -> statehistory [label="1:N", color="#C62828"] - servicerequest -> vetasked [label="1:N", color="#C62828"] - vetasked -> reminders [label="1:N", color="#C62828"] - veterinarian -> vetasked [label="N:1", color="#C62828"] - - // Relationships - Common - petowner -> neighborhood [label="N:1", color="#7B1FA2"] - veterinarian -> specialty [label="N:M", color="#7B1FA2"] - veterinarian -> neighborhood [label="N:M coverage", color="#7B1FA2"] - servicerequest -> campaign [label="N:1 optional", color="#7B1FA2", style=dashed] - servicerequest -> tag [label="N:M", color="#7B1FA2"] - vetvisitreport -> medication [label="references", color="#7B1FA2", style=dashed] - veterinarian -> turnfee [label="N:M", color="#7B1FA2"] - - // Relationships - Payments & External - servicerequest -> mercadopago [label="payment", color="#AD1457"] - mpnotification -> servicerequest [label="confirms", color="#AD1457"] - vetvisit -> google_cal [label="sync", color="#E65100", style=dashed] - servicerequest -> mercately [label="notify", color="#E65100", style=dashed] - reminders -> celery [label="scheduled", color="#E65100", style=dashed] - - // AFIP relationships - vetvisit -> receipt [label="1:1 optional", color="#F9A825", style=dashed] - receipt -> taxpayer [label="N:1", color="#F9A825"] - receipt -> pos [label="N:1", color="#F9A825"] -} diff --git a/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.svg b/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.svg deleted file mode 100644 index e8754ae..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/01-backend-architecture.svg +++ /dev/null @@ -1,585 +0,0 @@ - - - - - - -BackendArchitecture - -AMAR Mascotas - Backend Architecture (Django) - -cluster_django_core - -Django Core - - -cluster_mascotas - -mascotas (Pets & Veterinarians) - - -cluster_productos - -productos (Services & Pricing) - - -cluster_solicitudes - -solicitudes (Service Requests) - - -cluster_common - -common (Shared Models) - - -cluster_payments - -payments (Payment Processing) - - -cluster_external - -External Integrations - - -cluster_afip - -django_afip (Invoicing) - - - -auth_user - -auth.User -(Django Auth) - - - -petowner - -PetOwner -(Cliente/Tutor) - - - -auth_user->petowner - - -1:1 optional - - - -veterinarian - -Veterinarian -(Profesional) - - - -auth_user->veterinarian - - -1:1 - - - -django_admin - -Django Admin -Interface - - - -drf - -Django REST -Framework - - - -jwt_auth - -JWT Authentication -(SimpleJWT) - - - -drf->jwt_auth - - - - - -pet - -Pet -(Mascota) - - - -petowner->pet - - -1:N - - - -servicerequest - -ServiceRequest -(Solicitud) - - - -petowner->servicerequest - - -1:N - - - -neighborhood - -Neighborhood / -Province / Locality - - - -petowner->neighborhood - - -N:1 - - - -vetvisit - -VetVisit -(Consulta) - - - -pet->vetvisit - - -N:M - - - -pet_health - -PetVaccine / -PetStudy - - - -pet->pet_health - - -1:N - - - -veterinarian->vetvisit - - -1:N - - - -availability - -Availability / -Unavailability - - - -veterinarian->availability - - -1:N - - - -vetasked - -VeterinarianAsked -(Vet Consultado) - - - -veterinarian->vetasked - - -N:1 - - - -specialty - -Specialty -(Especialidades) - - - -veterinarian->specialty - - -N:M - - - -veterinarian->neighborhood - - -N:M coverage - - - -turnfee - -IndividualTurnFee -Group - - - -veterinarian->turnfee - - -N:M - - - -vetvisitreport - -VetVisitReport -(Informe Clinico) - - - -vetvisit->vetvisitreport - - -1:N - - - -vetvisit->servicerequest - - -1:1 - - - -google_cal - -Google Calendar -(Agenda) - - - -vetvisit->google_cal - - -sync - - - -receipt - -Receipt -(Comprobante) - - - -vetvisit->receipt - - -1:1 optional - - - -medication - -Medication -(Medicamentos) - - - -vetvisitreport->medication - - -references - - - -service - -Service -(Servicio) - - - -category - -Category / Group -(Categorias) - - - -service->category - - -N:1 - - - -prices - -Prices -(Precios) - - - -service->prices - - -1:N - - - -discounts - -Discounts -(Descuentos) - - - -service->discounts - - -1:N - - - -cart - -Cart / CartItem -(Carrito) - - - -service->cart - - -via CartItem - - - -cart->petowner - - -N:1 - - - -cart->veterinarian - - -N:1 optional - - - -combo - -ServiceCombo -(Paquetes) - - - -combo->service - - -contains - - - -servicerequest->cart - - -1:1 - - - -statehistory - -StateHistory -(Historial) - - - -servicerequest->statehistory - - -1:N - - - -servicerequest->vetasked - - -1:N - - - -campaign - -Campaign -(Marketing) - - - -servicerequest->campaign - - -N:1 optional - - - -tag - -Tag -(Etiquetas) - - - -servicerequest->tag - - -N:M - - - -mercadopago - -MercadoPago -Account - - - -servicerequest->mercadopago - - -payment - - - -mercately - -Mercately -(WhatsApp) - - - -servicerequest->mercately - - -notify - - - -reminders - -Reminders -(Recordatorios) - - - -vetasked->reminders - - -1:N - - - -celery - -Celery -(Async Tasks) - - - -reminders->celery - - -scheduled - - - -breed - -PetBreed / Vaccine -/ Study - - - -mpnotification - -MP Notification -(Webhooks) - - - -mpnotification->servicerequest - - -confirms - - - -google_sheets - -Google Sheets -(Exports) - - - -afip - -AFIP -(Facturacion) - - - -taxpayer - -TaxPayer -(Contribuyente) - - - -receipt->taxpayer - - -N:1 - - - -pos - -PointOfSales -(Punto de Venta) - - - -receipt->pos - - -N:1 - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.dot b/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.dot deleted file mode 100644 index ff349aa..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.dot +++ /dev/null @@ -1,184 +0,0 @@ -digraph FrontendArchitecture { - // Graph settings - rankdir=TB - compound=true - splines=ortho - node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=11] - edge [fontname="Helvetica", fontsize=9] - - label="AMAR Mascotas - Frontend Architecture (Next.js)\n\n" - labelloc="t" - fontsize=16 - fontname="Helvetica-Bold" - - // Next.js Core Cluster - subgraph cluster_nextjs { - label="Next.js 13+ (App Router)" - style="rounded,filled" - fillcolor="#E3F2FD" - color="#1565C0" - - app_router [label="App Router\n/app/*", fillcolor="#BBDEFB"] - layout [label="Layout Components\n(RootLayout, etc)", fillcolor="#BBDEFB"] - middleware [label="Middleware\n(Auth redirect)", fillcolor="#BBDEFB"] - } - - // Public Frontend Cluster - subgraph cluster_public { - label="Public Frontend (/(frontend))" - style="rounded,filled" - fillcolor="#E8F5E9" - color="#2E7D32" - - home [label="Home Page\n/", fillcolor="#C8E6C9"] - services_page [label="Services Catalog\n/servicios", fillcolor="#C8E6C9"] - cart_page [label="Cart\n/carrito", fillcolor="#C8E6C9"] - login_page [label="Login/Register\n/login", fillcolor="#C8E6C9"] - profile_page [label="User Profile\n/perfil", fillcolor="#C8E6C9"] - pets_page [label="My Pets\n/mascotas", fillcolor="#C8E6C9"] - requests_page [label="My Requests\n/solicitudes", fillcolor="#C8E6C9"] - } - - // Backoffice Cluster - subgraph cluster_backoffice { - label="Backoffice (/(backoffice)/admin)" - style="rounded,filled" - fillcolor="#FFF3E0" - color="#E65100" - - admin_dash [label="Dashboard\n/admin", fillcolor="#FFE0B2"] - admin_visits [label="Visits Management\n/admin/visits", fillcolor="#FFE0B2"] - admin_pets [label="Pets Overview\n/admin/pets", fillcolor="#FFE0B2"] - admin_requests [label="Service Requests\n/admin/solicitudes", fillcolor="#FFE0B2"] - admin_calendar [label="Calendar View\n/admin/calendario", fillcolor="#FFE0B2"] - } - - // Components Cluster - subgraph cluster_components { - label="Shared Components (/components)" - style="rounded,filled" - fillcolor="#F3E5F5" - color="#7B1FA2" - - sidebar [label="Sidebar\nNavigation", fillcolor="#E1BEE7"] - navbar [label="NavbarBackoffice\nTop Bar", fillcolor="#E1BEE7"] - visits_section [label="VisitsSection\n(List + Actions)", fillcolor="#E1BEE7"] - drawer [label="VisitsDrawer\n(Side Panel)", fillcolor="#E1BEE7"] - tables [label="DataTable\nComponents", fillcolor="#E1BEE7"] - forms [label="Form Components\n(Pet, Visit, etc)", fillcolor="#E1BEE7"] - } - - // Services Layer Cluster - subgraph cluster_services { - label="Services Layer (/services)" - style="rounded,filled" - fillcolor="#FFEBEE" - color="#C62828" - - http_service [label="HttpService\n(Axios wrapper)", fillcolor="#FFCDD2"] - auth_api [label="authAPI\n(login/register)", fillcolor="#FFCDD2"] - visits_api [label="visitsAPI\n(CRUD visits)", fillcolor="#FFCDD2"] - orders_api [label="OrdersAPI\n(service requests)", fillcolor="#FFCDD2"] - petowners_api [label="petOwnersAPI\n(clients)", fillcolor="#FFCDD2"] - vets_api [label="VeterinariansAPI\n(professionals)", fillcolor="#FFCDD2"] - services_api [label="servicesAPI\n(catalog)", fillcolor="#FFCDD2"] - cart_api [label="CartAPI\n(shopping)", fillcolor="#FFCDD2"] - } - - // State Management Cluster - subgraph cluster_state { - label="State Management (/redux, /contexts)" - style="rounded,filled" - fillcolor="#E0F7FA" - color="#00838F" - - redux_store [label="Redux Store\n(Global State)", fillcolor="#B2EBF2"] - auth_slice [label="Auth Slice\n(user, token)", fillcolor="#B2EBF2"] - visits_slice [label="Visits Slice\n(visit data)", fillcolor="#B2EBF2"] - cart_slice [label="Cart Slice\n(items)", fillcolor="#B2EBF2"] - auth_context [label="AuthContext\n(Provider)", fillcolor="#B2EBF2"] - } - - // Backend API Cluster (external) - subgraph cluster_backend { - label="Django Backend API" - style="rounded,filled" - fillcolor="#ECEFF1" - color="#455A64" - - api_mascotas [label="/mascotas/api/v1/\n(Pets, Vets, Visits)", fillcolor="#CFD8DC"] - api_productos [label="/productos/\n(Services, Prices)", fillcolor="#CFD8DC"] - api_solicitudes [label="/solicitudes/\n(Requests)", fillcolor="#CFD8DC"] - api_auth [label="/api/token/\n(JWT Auth)", fillcolor="#CFD8DC"] - api_payments [label="/payments/\n(MercadoPago)", fillcolor="#CFD8DC"] - } - - // User Types - subgraph cluster_users { - label="User Types" - style="rounded,filled" - fillcolor="#FCE4EC" - color="#AD1457" - - petowner_user [label="PetOwner\n(Cliente)", shape=ellipse, fillcolor="#F8BBD9"] - vet_user [label="Veterinarian\n(Profesional)", shape=ellipse, fillcolor="#F8BBD9"] - admin_user [label="Admin/Staff\n(Interno)", shape=ellipse, fillcolor="#F8BBD9"] - } - - // Relationships - Router - app_router -> layout [color="#1565C0"] - app_router -> middleware [color="#1565C0"] - - // Public pages - home -> services_page [color="#2E7D32"] - services_page -> cart_page [color="#2E7D32"] - cart_page -> login_page [label="if not auth", color="#2E7D32", style=dashed] - login_page -> profile_page [color="#2E7D32"] - profile_page -> pets_page [color="#2E7D32"] - profile_page -> requests_page [color="#2E7D32"] - - // Backoffice pages - admin_dash -> admin_visits [color="#E65100"] - admin_dash -> admin_pets [color="#E65100"] - admin_dash -> admin_requests [color="#E65100"] - admin_dash -> admin_calendar [color="#E65100"] - - // Components used by pages - admin_visits -> visits_section [color="#7B1FA2", style=dashed] - admin_visits -> drawer [color="#7B1FA2", style=dashed] - admin_dash -> sidebar [color="#7B1FA2", style=dashed] - admin_dash -> navbar [color="#7B1FA2", style=dashed] - - // Services to APIs - http_service -> auth_api [color="#C62828"] - http_service -> visits_api [color="#C62828"] - http_service -> orders_api [color="#C62828"] - http_service -> petowners_api [color="#C62828"] - http_service -> vets_api [color="#C62828"] - http_service -> services_api [color="#C62828"] - http_service -> cart_api [color="#C62828"] - - // Services to Backend - auth_api -> api_auth [label="POST /api/token/", color="#455A64"] - visits_api -> api_mascotas [label="CRUD /vet-visits/", color="#455A64"] - orders_api -> api_solicitudes [label="CRUD /service-requests/", color="#455A64"] - petowners_api -> api_mascotas [label="CRUD /pet-owners/", color="#455A64"] - vets_api -> api_mascotas [label="GET /veterinarians/", color="#455A64"] - services_api -> api_productos [label="GET /services/", color="#455A64"] - cart_api -> api_productos [label="CRUD /cart/", color="#455A64"] - - // State connections - auth_slice -> redux_store [color="#00838F"] - visits_slice -> redux_store [color="#00838F"] - cart_slice -> redux_store [color="#00838F"] - auth_context -> redux_store [color="#00838F", style=dashed] - - // User access paths - petowner_user -> home [label="public access", color="#AD1457"] - petowner_user -> profile_page [label="authenticated", color="#AD1457", style=dashed] - vet_user -> admin_dash [label="backoffice", color="#AD1457"] - admin_user -> admin_dash [label="full access", color="#AD1457"] - - // API routing through HttpService - http_service -> api_auth [label="JWT refresh", color="#455A64", style=dashed] -} diff --git a/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.svg b/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.svg deleted file mode 100644 index dc235d7..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/02-frontend-architecture.svg +++ /dev/null @@ -1,595 +0,0 @@ - - - - - - -FrontendArchitecture - -AMAR Mascotas - Frontend Architecture (Next.js) - -cluster_nextjs - -Next.js 13+ (App Router) - - -cluster_public - -Public Frontend (/(frontend)) - - -cluster_backoffice - -Backoffice (/(backoffice)/admin) - - -cluster_components - -Shared Components (/components) - - -cluster_services - -Services Layer (/services) - - -cluster_state - -State Management (/redux, /contexts) - - -cluster_backend - -Django Backend API - - -cluster_users - -User Types - - - -app_router - -App Router -/app/* - - - -layout - -Layout Components -(RootLayout, etc) - - - -app_router->layout - - - - - -middleware - -Middleware -(Auth redirect) - - - -app_router->middleware - - - - - -home - -Home Page -/ - - - -services_page - -Services Catalog -/servicios - - - -home->services_page - - - - - -cart_page - -Cart -/carrito - - - -services_page->cart_page - - - - - -login_page - -Login/Register -/login - - - -cart_page->login_page - - -if not auth - - - -profile_page - -User Profile -/perfil - - - -login_page->profile_page - - - - - -pets_page - -My Pets -/mascotas - - - -profile_page->pets_page - - - - - -requests_page - -My Requests -/solicitudes - - - -profile_page->requests_page - - - - - -admin_dash - -Dashboard -/admin - - - -admin_visits - -Visits Management -/admin/visits - - - -admin_dash->admin_visits - - - - - -admin_pets - -Pets Overview -/admin/pets - - - -admin_dash->admin_pets - - - - - -admin_requests - -Service Requests -/admin/solicitudes - - - -admin_dash->admin_requests - - - - - -admin_calendar - -Calendar View -/admin/calendario - - - -admin_dash->admin_calendar - - - - - -sidebar - -Sidebar -Navigation - - - -admin_dash->sidebar - - - - - -navbar - -NavbarBackoffice -Top Bar - - - -admin_dash->navbar - - - - - -visits_section - -VisitsSection -(List + Actions) - - - -admin_visits->visits_section - - - - - -drawer - -VisitsDrawer -(Side Panel) - - - -admin_visits->drawer - - - - - -tables - -DataTable -Components - - - -forms - -Form Components -(Pet, Visit, etc) - - - -http_service - -HttpService -(Axios wrapper) - - - -auth_api - -authAPI -(login/register) - - - -http_service->auth_api - - - - - -visits_api - -visitsAPI -(CRUD visits) - - - -http_service->visits_api - - - - - -orders_api - -OrdersAPI -(service requests) - - - -http_service->orders_api - - - - - -petowners_api - -petOwnersAPI -(clients) - - - -http_service->petowners_api - - - - - -vets_api - -VeterinariansAPI -(professionals) - - - -http_service->vets_api - - - - - -services_api - -servicesAPI -(catalog) - - - -http_service->services_api - - - - - -cart_api - -CartAPI -(shopping) - - - -http_service->cart_api - - - - - -api_auth - -/api/token/ -(JWT Auth) - - - -http_service->api_auth - - -JWT refresh - - - -auth_api->api_auth - - -POST /api/token/ - - - -api_mascotas - -/mascotas/api/v1/ -(Pets, Vets, Visits) - - - -visits_api->api_mascotas - - -CRUD /vet-visits/ - - - -api_solicitudes - -/solicitudes/ -(Requests) - - - -orders_api->api_solicitudes - - -CRUD /service-requests/ - - - -petowners_api->api_mascotas - - -CRUD /pet-owners/ - - - -vets_api->api_mascotas - - -GET /veterinarians/ - - - -api_productos - -/productos/ -(Services, Prices) - - - -services_api->api_productos - - -GET /services/ - - - -cart_api->api_productos - - -CRUD /cart/ - - - -redux_store - -Redux Store -(Global State) - - - -auth_slice - -Auth Slice -(user, token) - - - -auth_slice->redux_store - - - - - -visits_slice - -Visits Slice -(visit data) - - - -visits_slice->redux_store - - - - - -cart_slice - -Cart Slice -(items) - - - -cart_slice->redux_store - - - - - -auth_context - -AuthContext -(Provider) - - - -auth_context->redux_store - - - - - -api_payments - -/payments/ -(MercadoPago) - - - -petowner_user - -PetOwner -(Cliente) - - - -petowner_user->home - - -public access - - - -petowner_user->profile_page - - -authenticated - - - -vet_user - -Veterinarian -(Profesional) - - - -vet_user->admin_dash - - -backoffice - - - -admin_user - -Admin/Staff -(Interno) - - - -admin_user->admin_dash - - -full access - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/03-data-model.dot b/cfg/amar/soleprint/atlas/books/arch/03-data-model.dot deleted file mode 100644 index bb2a092..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/03-data-model.dot +++ /dev/null @@ -1,234 +0,0 @@ -digraph DataModel { - // Graph settings - rankdir=TB - compound=true - splines=ortho - node [shape=record, style="filled", fontname="Helvetica", fontsize=10] - edge [fontname="Helvetica", fontsize=8] - - label="AMAR Mascotas - Data Model (Entity Relationships)\n\n" - labelloc="t" - fontsize=16 - fontname="Helvetica-Bold" - - // === USERS & AUTHENTICATION === - subgraph cluster_auth { - label="Users & Authentication" - style="rounded,filled" - fillcolor="#E8F5E9" - color="#2E7D32" - - auth_user [label="{auth.User|id: PK\luser name: str\lemail: str\lis_staff: bool\lis_superuser: bool\l}", fillcolor="#C8E6C9"] - } - - // === PET OWNERS & PETS === - subgraph cluster_mascotas_owners { - label="Pet Owners & Pets" - style="rounded,filled" - fillcolor="#E3F2FD" - color="#1565C0" - - petowner [label="{PetOwner|id: PK\lemail: str (unique)\lphone: str\lfirst_name: str\llast_name: str\lneighborhood_id: FK\lcampaign_id: FK (opt)\lgeo_latitude: float\lgeo_longitude: float\laddress: str\l}", fillcolor="#BBDEFB"] - - pet [label="{Pet|id: PK\lowner_id: FK\lname: str\lpet_type: DOG/CAT\lbreed_id: FK (opt)\lgender: M/F\lage: int\lweight: decimal\lheight: decimal\lbirth_date: date\lallergies: text\lneutered: bool\lis_deceased: bool\lstate: puppy/adult/...\lprofile_picture: file\l}", fillcolor="#BBDEFB"] - - petvaccine [label="{PetVaccine|id: PK\lpet_id: FK\lvaccine_id: FK\lapplication_date: date\lnext_application: date\l}", fillcolor="#90CAF9"] - - petstudy [label="{PetStudy|id: PK\lpet_id: FK\lstudy_id: FK\ldate: date\lresult: text\limages: files\l}", fillcolor="#90CAF9"] - } - - // === VETERINARIANS === - subgraph cluster_veterinarians { - label="Veterinarians" - style="rounded,filled" - fillcolor="#FFF3E0" - color="#E65100" - - veterinarian [label="{Veterinarian|id: PK\luser_id: FK\llicense: str\lemail: str\lphone: str\l}", fillcolor="#FFE0B2"] - - availability [label="{Availability|id: PK\lveterinarian_id: FK\lstart_day: 0-6\lend_day: 0-6\lstart_time: time\lend_time: time\l}", fillcolor="#FFCC80"] - - unavailability [label="{Unavailability|id: PK\lveterinarian_id: FK\lstart_date: date\lend_date: date\lstart_time: time\lend_time: time\lreason: str\l}", fillcolor="#FFCC80"] - - vet_specialty [label="{M2M: Vet-Specialty|veterinarian_id: FK\lspecialty_id: FK\l}", shape=diamond, fillcolor="#FFB74D"] - - vet_neighborhood [label="{M2M: Vet-Neighborhood|veterinarian_id: FK\lneighborhood_id: FK\l}", shape=diamond, fillcolor="#FFB74D"] - } - - // === SERVICES & PRICING === - subgraph cluster_productos { - label="Services & Pricing" - style="rounded,filled" - fillcolor="#F3E5F5" - color="#7B1FA2" - - grupo [label="{Group|id: PK\lname: str\ldescription: text\l}", fillcolor="#E1BEE7"] - - category [label="{Category|id: PK\lgroup_id: FK\lname: str\ldescription: text\lvalue: int (order)\l}", fillcolor="#E1BEE7"] - - service [label="{Service|id: PK\lname: str\ldescription: text\lspecialty_id: FK\lcategory_id: FK\lduration: int (min)\lmodality: onsite/online\lpayment_sign_req: bool\lpet_type_filter: str\lage_filter: str\lweight_range: str\l}", fillcolor="#CE93D8"] - - prices [label="{Prices|id: PK\lservice_id: FK\lveterinarian_id: FK (opt)\lprice: decimal\lprofessional_fee: decimal\lpayment_sign: decimal\lfrom_date: date\lto_date: date\lactive: bool\l}", fillcolor="#BA68C8"] - - discounts [label="{Discounts|id: PK\lservice_id: FK\ldiscount: decimal (%)\lfrom_date: date\lto_date: date\lactive: bool\l}", fillcolor="#BA68C8"] - - servicecombo [label="{ServiceCombo|id: PK\lname: str\ldescription: text\ldiscount_percent: decimal\ldiscount_fixed: decimal\l}", fillcolor="#E1BEE7"] - } - - // === CART & CHECKOUT === - subgraph cluster_cart { - label="Cart & Checkout" - style="rounded,filled" - fillcolor="#E0F7FA" - color="#00838F" - - cart [label="{Cart|id: PK\lpetowner_id: FK\lveterinarian_id: FK (opt)\luse_vet_prices: bool\lapply_turn_fee: bool\l}", fillcolor="#B2EBF2"] - - cartitem [label="{CartItem|id: PK\lcart_id: FK\lpet_id: FK (opt)\lservice_id: FK\lprice: decimal\lquantity: int\ltotal: decimal (calc)\l}", fillcolor="#80DEEA"] - - cartresumeitem [label="{CartResumeItem|id: PK\lcart_id: FK\lconcept: SUBTOTAL/\l DESCUENTO/\l ADELANTO/\l TOTAL/\l COSTO_SERVICIO\lamount: decimal\lorder: int\l}", fillcolor="#80DEEA"] - - cartpetreason [label="{CartPetReason|id: PK\lcart_id: FK\lpet_id: FK\lreason: text\l}", fillcolor="#80DEEA"] - } - - // === SERVICE REQUESTS === - subgraph cluster_solicitudes { - label="Service Requests (Workflow)" - style="rounded,filled" - fillcolor="#FFEBEE" - color="#C62828" - - servicerequest [label="{ServiceRequest|id: PK\lpetowner_id: FK\lcart_id: FK\lveterinarian_id: FK (opt)\lstate: pending/vet_asked/\l vet_accepted/coordinated/\l payed/Confirmado/...\lreason: text\ldays_requested: JSON\ldate_coordinated: datetime\lhour_coordinated: time\lpay_number: str\lcampaign_id: FK (opt)\lattended_by_id: FK (opt)\l}", fillcolor="#FFCDD2"] - - statehistory [label="{StateHistory|id: PK\lservice_request_id: FK\lstate: str\ladditional_data: JSON\lcreated_at: datetime\luser_id: FK\l}", fillcolor="#EF9A9A"] - - vetasked [label="{VeterinarianAsked|id: PK\lservice_request_id: FK\lveterinarian_id: FK\ldate_asked: datetime\ldate_answered: datetime\laccepted: bool\l}", fillcolor="#EF9A9A"] - - vetreminder [label="{ScheduledVetReminder|id: PK\lvet_asked_id: FK\lscheduled_for: datetime\lprocessed_at: datetime\lstatus: pending/sent/...\lcelery_task_id: str\l}", fillcolor="#E57373"] - - payreminder [label="{PaymentReminder|id: PK\lservice_request_id: FK\lscheduled_for: datetime\lprocessed_at: datetime\lstatus: pending/sent/...\l}", fillcolor="#E57373"] - } - - // === VET VISITS === - subgraph cluster_vetvisits { - label="Veterinary Visits" - style="rounded,filled" - fillcolor="#FFFDE7" - color="#F9A825" - - vetvisit [label="{VetVisit|id: PK\lservice_request_id: FK (opt)\lowner_id: FK\lveterinarian_id: FK\ldate: date\lhour: time\lvisit_type: clinical/\l vaccination/\l telemedicina\lvisit_state: PENDING/\l IN_PROGRESS/\l COMPLETED/\l NO_REPORT/\l CANCELLED\lreason: text\lobservations: text\lprice: decimal\ldeposit: decimal\lvet_fee: decimal\lpay_transaction: str\lgoogle_event_id: str\lafip_receipt_id: FK (opt)\l}", fillcolor="#FFF9C4"] - - vetvisitreport [label="{VetVisitReport|id: PK\lvisit_id: FK\lpet_id: FK\lreason: text\lphysical_exam: text\ldiagnosis: text\ltreatment: text\lpdf_file: file\l}", fillcolor="#FFF59D"] - - vetvisitfollowup [label="{VetVisitFollowUp|id: PK\lreport_id: FK\ldate: date\ldescription: text\l}", fillcolor="#FFF176"] - - vetvisitpetreason [label="{VetVisitPetReason|id: PK\lvisit_id: FK\lpet_id: FK\lreason: text\l}", fillcolor="#FFF176"] - - visit_pets [label="{M2M: Visit-Pets|vetvisit_id: FK\lpet_id: FK\l}", shape=diamond, fillcolor="#FFEE58"] - } - - // === REFERENCE DATA === - subgraph cluster_reference { - label="Reference Data" - style="rounded,filled" - fillcolor="#ECEFF1" - color="#455A64" - - specialty [label="{Specialty|id: PK\lname: str\l}", fillcolor="#CFD8DC"] - neighborhood [label="{Neighborhood|id: PK\lname: str\ldistance_coefficient: decimal\lcoverage_area: GIS Polygon\l}", fillcolor="#CFD8DC"] - province [label="{Province|id: PK\lname: str\l}", fillcolor="#CFD8DC"] - locality [label="{Locality|id: PK\lprovince_id: FK\lname: str\l}", fillcolor="#CFD8DC"] - petbreed [label="{PetBreed|id: PK\lname: str\lpet_type: DOG/CAT\l}", fillcolor="#CFD8DC"] - vaccine [label="{Vaccine|id: PK\lname: str\lpet_type: DOG/CAT\lperiodicity: int (months)\l}", fillcolor="#CFD8DC"] - study [label="{Study|id: PK\lname: str\lpet_type: DOG/CAT\lgroup_id: FK (opt)\l}", fillcolor="#CFD8DC"] - campaign [label="{Campaign|id: PK\lname: str\lutm_source: str\lutm_medium: str\lis_active: bool\l}", fillcolor="#CFD8DC"] - tag [label="{Tag|id: PK\lname: str (unique)\l}", fillcolor="#CFD8DC"] - medication [label="{Medication|id: PK\lname: str\lgeneral_name_id: FK\ltype_id: FK\lpresentation_id: FK\lpet_type: str\l}", fillcolor="#CFD8DC"] - turnfeegroup [label="{IndividualTurnFeeGroup|id: PK\lname: str\lfee_percentage: decimal\l}", fillcolor="#CFD8DC"] - } - - // === AFIP/INVOICING === - subgraph cluster_afip { - label="AFIP Invoicing" - style="rounded,filled" - fillcolor="#FCE4EC" - color="#AD1457" - - receipt [label="{Receipt|id: PK\ldocument_number: bigint\lreceipt_number: int\lissued_date: date\ltotal_amount: decimal\lnet_taxed: decimal\lcae: str\lcae_expiration: date\l}", fillcolor="#F8BBD9"] - } - - // === RELATIONSHIPS === - - // Auth -> PetOwner/Vet - auth_user -> petowner [label="1:1 opt", style=dashed, color="#2E7D32"] - auth_user -> veterinarian [label="1:1", color="#2E7D32"] - - // PetOwner relationships - petowner -> pet [label="1:N owns", color="#1565C0"] - petowner -> neighborhood [label="N:1 lives in", color="#1565C0"] - petowner -> campaign [label="N:1 opt", style=dashed, color="#666"] - petowner -> cart [label="1:N", color="#00838F"] - petowner -> servicerequest [label="1:N requests", color="#C62828"] - petowner -> vetvisit [label="1:N as owner", color="#F9A825"] - - // Pet relationships - pet -> petbreed [label="N:1 opt", style=dashed, color="#666"] - pet -> petvaccine [label="1:N", color="#1565C0"] - pet -> petstudy [label="1:N", color="#1565C0"] - petvaccine -> vaccine [label="N:1", color="#666"] - petstudy -> study [label="N:1", color="#666"] - - // Veterinarian relationships - veterinarian -> vet_specialty [label="1:N", color="#E65100"] - vet_specialty -> specialty [label="N:1", color="#E65100"] - veterinarian -> vet_neighborhood [label="1:N coverage", color="#E65100"] - vet_neighborhood -> neighborhood [label="N:1", color="#E65100"] - veterinarian -> availability [label="1:N", color="#E65100"] - veterinarian -> unavailability [label="1:N", color="#E65100"] - veterinarian -> turnfeegroup [label="N:M", color="#E65100", style=dashed] - - // Service/Pricing relationships - grupo -> category [label="1:N", color="#7B1FA2"] - category -> service [label="1:N", color="#7B1FA2"] - service -> specialty [label="N:1 opt", style=dashed, color="#7B1FA2"] - service -> prices [label="1:N", color="#7B1FA2"] - service -> discounts [label="1:N", color="#7B1FA2"] - prices -> veterinarian [label="N:1 opt\n(vet-specific)", style=dashed, color="#7B1FA2"] - - // Cart relationships - cart -> veterinarian [label="N:1 opt\n(assigned vet)", style=dashed, color="#00838F"] - cart -> cartitem [label="1:N", color="#00838F"] - cart -> cartresumeitem [label="1:N", color="#00838F"] - cart -> cartpetreason [label="1:N", color="#00838F"] - cartitem -> service [label="N:1", color="#00838F"] - cartitem -> pet [label="N:1 opt", style=dashed, color="#00838F"] - cartpetreason -> pet [label="N:1", color="#00838F"] - - // ServiceRequest relationships - servicerequest -> cart [label="1:1", color="#C62828"] - servicerequest -> veterinarian [label="N:1 opt\n(assigned)", style=dashed, color="#C62828"] - servicerequest -> statehistory [label="1:N audit", color="#C62828"] - servicerequest -> vetasked [label="1:N", color="#C62828"] - servicerequest -> payreminder [label="1:N", color="#C62828"] - servicerequest -> campaign [label="N:1 opt", style=dashed, color="#666"] - servicerequest -> tag [label="N:M", color="#666"] - vetasked -> veterinarian [label="N:1", color="#C62828"] - vetasked -> vetreminder [label="1:N", color="#C62828"] - - // VetVisit relationships - vetvisit -> servicerequest [label="1:1 opt\n(from request)", style=dashed, color="#F9A825"] - vetvisit -> veterinarian [label="N:1", color="#F9A825"] - vetvisit -> visit_pets [label="1:N", color="#F9A825"] - visit_pets -> pet [label="N:1", color="#F9A825"] - vetvisit -> vetvisitreport [label="1:N", color="#F9A825"] - vetvisit -> vetvisitpetreason [label="1:N", color="#F9A825"] - vetvisitreport -> pet [label="N:1", color="#F9A825"] - vetvisitreport -> vetvisitfollowup [label="1:N", color="#F9A825"] - vetvisitpetreason -> pet [label="N:1", color="#F9A825"] - - // AFIP - vetvisit -> receipt [label="1:1 opt\n(invoice)", style=dashed, color="#AD1457"] - - // Geography - province -> locality [label="1:N", color="#666"] -} diff --git a/cfg/amar/soleprint/atlas/books/arch/03-data-model.svg b/cfg/amar/soleprint/atlas/books/arch/03-data-model.svg deleted file mode 100644 index ab40dcc..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/03-data-model.svg +++ /dev/null @@ -1,976 +0,0 @@ - - - - - - -DataModel - -AMAR Mascotas - Data Model (Entity Relationships) - -cluster_auth - -Users & Authentication - - -cluster_mascotas_owners - -Pet Owners & Pets - - -cluster_veterinarians - -Veterinarians - - -cluster_productos - -Services & Pricing - - -cluster_cart - -Cart & Checkout - - -cluster_solicitudes - -Service Requests (Workflow) - - -cluster_vetvisits - -Veterinary Visits - - -cluster_reference - -Reference Data - - -cluster_afip - -AFIP Invoicing - - - -auth_user - -auth.User - -id: PK -user name: str -email: str -is_staff: bool -is_superuser: bool - - - -petowner - -PetOwner - -id: PK -email: str (unique) -phone: str -first_name: str -last_name: str -neighborhood_id: FK -campaign_id: FK (opt) -geo_latitude: float -geo_longitude: float -address: str - - - -auth_user->petowner - - -1:1 opt - - - -veterinarian - -Veterinarian - -id: PK -user_id: FK -license: str -email: str -phone: str - - - -auth_user->veterinarian - - -1:1 - - - -pet - -Pet - -id: PK -owner_id: FK -name: str -pet_type: DOG/CAT -breed_id: FK (opt) -gender: M/F -age: int -weight: decimal -height: decimal -birth_date: date -allergies: text -neutered: bool -is_deceased: bool -state: puppy/adult/... -profile_picture: file - - - -petowner->pet - - -1:N owns - - - -cart - -Cart - -id: PK -petowner_id: FK -veterinarian_id: FK (opt) -use_vet_prices: bool -apply_turn_fee: bool - - - -petowner->cart - - -1:N - - - -servicerequest - -ServiceRequest - -id: PK -petowner_id: FK -cart_id: FK -veterinarian_id: FK (opt) -state: pending/vet_asked/ - vet_accepted/coordinated/ - payed/Confirmado/... -reason: text -days_requested: JSON -date_coordinated: datetime -hour_coordinated: time -pay_number: str -campaign_id: FK (opt) -attended_by_id: FK (opt) - - - -petowner->servicerequest - - -1:N requests - - - -vetvisit - -VetVisit - -id: PK -service_request_id: FK (opt) -owner_id: FK -veterinarian_id: FK -date: date -hour: time -visit_type: clinical/ - vaccination/ - telemedicina -visit_state: PENDING/ - IN_PROGRESS/ - COMPLETED/ - NO_REPORT/ - CANCELLED -reason: text -observations: text -price: decimal -deposit: decimal -vet_fee: decimal -pay_transaction: str -google_event_id: str -afip_receipt_id: FK (opt) - - - -petowner->vetvisit - - -1:N as owner - - - -neighborhood - -Neighborhood - -id: PK -name: str -distance_coefficient: decimal -coverage_area: GIS Polygon - - - -petowner->neighborhood - - -N:1 lives in - - - -campaign - -Campaign - -id: PK -name: str -utm_source: str -utm_medium: str -is_active: bool - - - -petowner->campaign - - -N:1 opt - - - -petvaccine - -PetVaccine - -id: PK -pet_id: FK -vaccine_id: FK -application_date: date -next_application: date - - - -pet->petvaccine - - -1:N - - - -petstudy - -PetStudy - -id: PK -pet_id: FK -study_id: FK -date: date -result: text -images: files - - - -pet->petstudy - - -1:N - - - -petbreed - -PetBreed - -id: PK -name: str -pet_type: DOG/CAT - - - -pet->petbreed - - -N:1 opt - - - -vaccine - -Vaccine - -id: PK -name: str -pet_type: DOG/CAT -periodicity: int (months) - - - -petvaccine->vaccine - - -N:1 - - - -study - -Study - -id: PK -name: str -pet_type: DOG/CAT -group_id: FK (opt) - - - -petstudy->study - - -N:1 - - - -availability - -Availability - -id: PK -veterinarian_id: FK -start_day: 0-6 -end_day: 0-6 -start_time: time -end_time: time - - - -veterinarian->availability - - -1:N - - - -unavailability - -Unavailability - -id: PK -veterinarian_id: FK -start_date: date -end_date: date -start_time: time -end_time: time -reason: str - - - -veterinarian->unavailability - - -1:N - - - -vet_specialty - -{M2M: Vet-Specialty|veterinarian_id: FK -specialty_id: FK -} - - - -veterinarian->vet_specialty - - -1:N - - - -vet_neighborhood - -{M2M: Vet-Neighborhood|veterinarian_id: FK -neighborhood_id: FK -} - - - -veterinarian->vet_neighborhood - - -1:N coverage - - - -turnfeegroup - -IndividualTurnFeeGroup - -id: PK -name: str -fee_percentage: decimal - - - -veterinarian->turnfeegroup - - -N:M - - - -specialty - -Specialty - -id: PK -name: str - - - -vet_specialty->specialty - - -N:1 - - - -vet_neighborhood->neighborhood - - -N:1 - - - -grupo - -Group - -id: PK -name: str -description: text - - - -category - -Category - -id: PK -group_id: FK -name: str -description: text -value: int (order) - - - -grupo->category - - -1:N - - - -service - -Service - -id: PK -name: str -description: text -specialty_id: FK -category_id: FK -duration: int (min) -modality: onsite/online -payment_sign_req: bool -pet_type_filter: str -age_filter: str -weight_range: str - - - -category->service - - -1:N - - - -prices - -Prices - -id: PK -service_id: FK -veterinarian_id: FK (opt) -price: decimal -professional_fee: decimal -payment_sign: decimal -from_date: date -to_date: date -active: bool - - - -service->prices - - -1:N - - - -discounts - -Discounts - -id: PK -service_id: FK -discount: decimal (%) -from_date: date -to_date: date -active: bool - - - -service->discounts - - -1:N - - - -service->specialty - - -N:1 opt - - - -prices->veterinarian - - -N:1 opt -(vet-specific) - - - -servicecombo - -ServiceCombo - -id: PK -name: str -description: text -discount_percent: decimal -discount_fixed: decimal - - - -cart->veterinarian - - -N:1 opt -(assigned vet) - - - -cartitem - -CartItem - -id: PK -cart_id: FK -pet_id: FK (opt) -service_id: FK -price: decimal -quantity: int -total: decimal (calc) - - - -cart->cartitem - - -1:N - - - -cartresumeitem - -CartResumeItem - -id: PK -cart_id: FK -concept: SUBTOTAL/ - DESCUENTO/ - ADELANTO/ - TOTAL/ - COSTO_SERVICIO -amount: decimal -order: int - - - -cart->cartresumeitem - - -1:N - - - -cartpetreason - -CartPetReason - -id: PK -cart_id: FK -pet_id: FK -reason: text - - - -cart->cartpetreason - - -1:N - - - -cartitem->pet - - -N:1 opt - - - -cartitem->service - - -N:1 - - - -cartpetreason->pet - - -N:1 - - - -servicerequest->veterinarian - - -N:1 opt -(assigned) - - - -servicerequest->cart - - -1:1 - - - -statehistory - -StateHistory - -id: PK -service_request_id: FK -state: str -additional_data: JSON -created_at: datetime -user_id: FK - - - -servicerequest->statehistory - - -1:N audit - - - -vetasked - -VeterinarianAsked - -id: PK -service_request_id: FK -veterinarian_id: FK -date_asked: datetime -date_answered: datetime -accepted: bool - - - -servicerequest->vetasked - - -1:N - - - -payreminder - -PaymentReminder - -id: PK -service_request_id: FK -scheduled_for: datetime -processed_at: datetime -status: pending/sent/... - - - -servicerequest->payreminder - - -1:N - - - -servicerequest->campaign - - -N:1 opt - - - -tag - -Tag - -id: PK -name: str (unique) - - - -servicerequest->tag - - -N:M - - - -vetasked->veterinarian - - -N:1 - - - -vetreminder - -ScheduledVetReminder - -id: PK -vet_asked_id: FK -scheduled_for: datetime -processed_at: datetime -status: pending/sent/... -celery_task_id: str - - - -vetasked->vetreminder - - -1:N - - - -vetvisit->veterinarian - - -N:1 - - - -vetvisit->servicerequest - - -1:1 opt -(from request) - - - -vetvisitreport - -VetVisitReport - -id: PK -visit_id: FK -pet_id: FK -reason: text -physical_exam: text -diagnosis: text -treatment: text -pdf_file: file - - - -vetvisit->vetvisitreport - - -1:N - - - -vetvisitpetreason - -VetVisitPetReason - -id: PK -visit_id: FK -pet_id: FK -reason: text - - - -vetvisit->vetvisitpetreason - - -1:N - - - -visit_pets - -{M2M: Visit-Pets|vetvisit_id: FK -pet_id: FK -} - - - -vetvisit->visit_pets - - -1:N - - - -receipt - -Receipt - -id: PK -document_number: bigint -receipt_number: int -issued_date: date -total_amount: decimal -net_taxed: decimal -cae: str -cae_expiration: date - - - -vetvisit->receipt - - -1:1 opt -(invoice) - - - -vetvisitreport->pet - - -N:1 - - - -vetvisitfollowup - -VetVisitFollowUp - -id: PK -report_id: FK -date: date -description: text - - - -vetvisitreport->vetvisitfollowup - - -1:N - - - -vetvisitpetreason->pet - - -N:1 - - - -visit_pets->pet - - -N:1 - - - -province - -Province - -id: PK -name: str - - - -locality - -Locality - -id: PK -province_id: FK -name: str - - - -province->locality - - -1:N - - - -medication - -Medication - -id: PK -name: str -general_name_id: FK -type_id: FK -presentation_id: FK -pet_type: str - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.dot b/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.dot deleted file mode 100644 index 9b4002f..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.dot +++ /dev/null @@ -1,195 +0,0 @@ -digraph DataModelSimple { - rankdir=TB - compound=true - splines=ortho - node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10] - edge [fontname="Helvetica", fontsize=8] - nodesep=0.3 - ranksep=1.2 - newrank=true - - label="AMAR Mascotas - Data Model Overview" - labelloc="t" - fontsize=16 - fontname="Helvetica-Bold" - - // === ROW 1: AUTH (center) === - subgraph cluster_auth { - label="Users & Auth" - style="rounded,filled" - fillcolor="#E8F5E9" - color="#2E7D32" - auth_user [label="auth.User", fillcolor="#C8E6C9"] - } - - // === ROW 2: LEFT COLUMN - Pet Owners === - subgraph cluster_mascotas { - label="Pet Owners & Pets" - style="rounded,filled" - fillcolor="#E3F2FD" - color="#1565C0" - - petowner [label="PetOwner", fillcolor="#BBDEFB"] - pet [label="Pet", fillcolor="#BBDEFB"] - petvaccine [label="PetVaccine", fillcolor="#90CAF9"] - petstudy [label="PetStudy", fillcolor="#90CAF9"] - } - - // === ROW 2: CENTER COLUMN - Veterinarians === - subgraph cluster_vets { - label="Veterinarians" - style="rounded,filled" - fillcolor="#FFF3E0" - color="#E65100" - - veterinarian [label="Veterinarian", fillcolor="#FFE0B2"] - availability [label="Availability", fillcolor="#FFCC80"] - unavailability [label="Unavailability", fillcolor="#FFCC80"] - vet_specialty [label="Vet-Specialty", shape=diamond, fillcolor="#FFB74D"] - vet_neighborhood [label="Vet-Neighborhood", shape=diamond, fillcolor="#FFB74D"] - } - - // === ROW 2: RIGHT COLUMN - Services === - subgraph cluster_productos { - label="Services & Pricing" - style="rounded,filled" - fillcolor="#F3E5F5" - color="#7B1FA2" - - grupo [label="Group", fillcolor="#E1BEE7"] - category [label="Category", fillcolor="#E1BEE7"] - service [label="Service", fillcolor="#CE93D8"] - prices [label="Prices", fillcolor="#BA68C8"] - discounts [label="Discounts", fillcolor="#BA68C8"] - } - - // === ROW 3: LEFT - Cart === - subgraph cluster_cart { - label="Cart & Checkout" - style="rounded,filled" - fillcolor="#E0F7FA" - color="#00838F" - - cart [label="Cart", fillcolor="#B2EBF2"] - cartitem [label="CartItem", fillcolor="#80DEEA"] - cartresumeitem [label="CartResumeItem", fillcolor="#80DEEA"] - cartpetreason [label="CartPetReason", fillcolor="#80DEEA"] - } - - // === ROW 3: CENTER - Requests === - subgraph cluster_solicitudes { - label="Service Requests" - style="rounded,filled" - fillcolor="#FFEBEE" - color="#C62828" - - servicerequest [label="ServiceRequest", fillcolor="#FFCDD2"] - statehistory [label="StateHistory", fillcolor="#EF9A9A"] - vetasked [label="VeterinarianAsked", fillcolor="#EF9A9A"] - vetreminder [label="VetReminder", fillcolor="#E57373"] - payreminder [label="PayReminder", fillcolor="#E57373"] - } - - // === ROW 3: RIGHT - Visits === - subgraph cluster_visits { - label="Veterinary Visits" - style="rounded,filled" - fillcolor="#FFFDE7" - color="#F9A825" - - vetvisit [label="VetVisit", fillcolor="#FFF9C4"] - vetvisitreport [label="VetVisitReport", fillcolor="#FFF59D"] - vetvisitfollowup [label="FollowUp", fillcolor="#FFF176"] - vetvisitpetreason [label="VisitPetReason", fillcolor="#FFF176"] - visit_pets [label="Visit-Pets", shape=diamond, fillcolor="#FFEE58"] - receipt [label="Receipt\n(AFIP)", fillcolor="#F8BBD9"] - } - - // === ROW 4: REFERENCE DATA (bottom, full width) === - subgraph cluster_reference { - label="Reference Data" - style="rounded,filled" - fillcolor="#ECEFF1" - color="#455A64" - - subgraph { - rank=same - specialty [label="Specialty", fillcolor="#CFD8DC"] - neighborhood [label="Neighborhood", fillcolor="#CFD8DC"] - province [label="Province", fillcolor="#CFD8DC"] - locality [label="Locality", fillcolor="#CFD8DC"] - petbreed [label="PetBreed", fillcolor="#CFD8DC"] - vaccine [label="Vaccine", fillcolor="#CFD8DC"] - } - subgraph { - rank=same - study [label="Study", fillcolor="#CFD8DC"] - campaign [label="Campaign", fillcolor="#CFD8DC"] - tag [label="Tag", fillcolor="#CFD8DC"] - medication [label="Medication", fillcolor="#CFD8DC"] - turnfeegroup [label="TurnFeeGroup", fillcolor="#CFD8DC"] - } - specialty -> study [style=invis] - province -> locality - } - - // === FORCE COLUMN ALIGNMENT WITH INVISIBLE EDGES === - // Column 1: Pets -> Cart - petowner -> cart [style=invis, weight=10] - // Column 2: Vets -> Requests - veterinarian -> servicerequest [style=invis, weight=10] - // Column 3: Services -> Visits - service -> vetvisit [style=invis, weight=10] - - // Force Reference Data to bottom - cart -> specialty [style=invis, weight=10] - servicerequest -> campaign [style=invis, weight=10] - vetvisit -> turnfeegroup [style=invis, weight=10] - - // === INTERNAL CLUSTER EDGES === - petowner -> pet [label="1:N"] - pet -> petvaccine [label="1:N"] - pet -> petstudy [label="1:N"] - - veterinarian -> availability - veterinarian -> unavailability - veterinarian -> vet_specialty - veterinarian -> vet_neighborhood - - grupo -> category [label="1:N"] - category -> service [label="1:N"] - service -> prices [label="1:N"] - service -> discounts [label="1:N"] - - cart -> cartitem [label="1:N"] - cart -> cartresumeitem - cart -> cartpetreason - - servicerequest -> statehistory [label="1:N"] - servicerequest -> vetasked [label="1:N"] - servicerequest -> payreminder - vetasked -> vetreminder - - vetvisit -> visit_pets - vetvisit -> vetvisitreport [label="1:N"] - vetvisit -> vetvisitpetreason - vetvisitreport -> vetvisitfollowup - vetvisit -> receipt [style=dashed] - - // === CROSS-CLUSTER RELATIONSHIPS === - auth_user -> petowner [label="1:1 opt", style=dashed] - auth_user -> veterinarian [label="1:1"] - - petowner -> cart [label="1:N"] - petowner -> servicerequest [label="1:N"] - servicerequest -> cart [label="1:1", constraint=false] - vetasked -> veterinarian [constraint=false] - vetvisit -> servicerequest [style=dashed, constraint=false] - - // Reference links - petvaccine -> vaccine [constraint=false] - petstudy -> study [constraint=false] - vet_specialty -> specialty [constraint=false] - vet_neighborhood -> neighborhood [constraint=false] - cartitem -> service [constraint=false] -} diff --git a/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.svg b/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.svg deleted file mode 100644 index 601d83b..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/04-data-model-simple.svg +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - -DataModelSimple - -AMAR Mascotas - Data Model Overview - -cluster_auth - -Users & Auth - - -cluster_mascotas - -Pet Owners & Pets - - -cluster_vets - -Veterinarians - - -cluster_productos - -Services & Pricing - - -cluster_cart - -Cart & Checkout - - -cluster_solicitudes - -Service Requests - - -cluster_visits - -Veterinary Visits - - -cluster_reference - -Reference Data - - - -auth_user - -auth.User - - - -petowner - -PetOwner - - - -auth_user->petowner - - -1:1 opt - - - -veterinarian - -Veterinarian - - - -auth_user->veterinarian - - -1:1 - - - -pet - -Pet - - - -petowner->pet - - -1:N - - - -cart - -Cart - - - - -petowner->cart - - -1:N - - - -servicerequest - -ServiceRequest - - - -petowner->servicerequest - - -1:N - - - -petvaccine - -PetVaccine - - - -pet->petvaccine - - -1:N - - - -petstudy - -PetStudy - - - -pet->petstudy - - -1:N - - - -vaccine - -Vaccine - - - -petvaccine->vaccine - - - - - -study - -Study - - - -petstudy->study - - - - - -availability - -Availability - - - -veterinarian->availability - - - - - -unavailability - -Unavailability - - - -veterinarian->unavailability - - - - - -vet_specialty - -Vet-Specialty - - - -veterinarian->vet_specialty - - - - - -vet_neighborhood - -Vet-Neighborhood - - - -veterinarian->vet_neighborhood - - - - - - -specialty - -Specialty - - - -vet_specialty->specialty - - - - - -neighborhood - -Neighborhood - - - -vet_neighborhood->neighborhood - - - - - -grupo - -Group - - - -category - -Category - - - -grupo->category - - -1:N - - - -service - -Service - - - -category->service - - -1:N - - - -prices - -Prices - - - -service->prices - - -1:N - - - -discounts - -Discounts - - - -service->discounts - - -1:N - - - -vetvisit - -VetVisit - - - - -cartitem - -CartItem - - - -cart->cartitem - - -1:N - - - -cartresumeitem - -CartResumeItem - - - -cart->cartresumeitem - - - - - -cartpetreason - -CartPetReason - - - -cart->cartpetreason - - - - - - -cartitem->service - - - - - -servicerequest->cart - - -1:1 - - - -statehistory - -StateHistory - - - -servicerequest->statehistory - - -1:N - - - -vetasked - -VeterinarianAsked - - - -servicerequest->vetasked - - -1:N - - - -payreminder - -PayReminder - - - -servicerequest->payreminder - - - - - -campaign - -Campaign - - - - -vetasked->veterinarian - - - - - -vetreminder - -VetReminder - - - -vetasked->vetreminder - - - - - -vetvisit->servicerequest - - - - - -vetvisitreport - -VetVisitReport - - - -vetvisit->vetvisitreport - - -1:N - - - -vetvisitpetreason - -VisitPetReason - - - -vetvisit->vetvisitpetreason - - - - - -visit_pets - -Visit-Pets - - - -vetvisit->visit_pets - - - - - -receipt - -Receipt -(AFIP) - - - -vetvisit->receipt - - - - - -turnfeegroup - -TurnFeeGroup - - - - -vetvisitfollowup - -FollowUp - - - -vetvisitreport->vetvisitfollowup - - - - - - -province - -Province - - - -locality - -Locality - - - -province->locality - - - - - -petbreed - -PetBreed - - - -tag - -Tag - - - -medication - -Medication - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/graph.html b/cfg/amar/soleprint/atlas/books/arch/graph.html deleted file mode 100644 index 999c0f1..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/graph.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Graph Viewer - AMAR Mascotas - - - -
- ← Index - -

Loading...

-
- - - - - -
-
- -
- Graph -
- - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/index.html b/cfg/amar/soleprint/atlas/books/arch/index.html deleted file mode 100644 index 9c88bb0..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/index.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - AMAR Mascotas - Architecture & Data Models - - - -
-

AMAR Mascotas

-

Architecture & Data Model Documentation

-
- -
- -
-
-

Data Model Overview

- View Full -
- - Data Model Overview - -
-

High-level entity relationships without field details. Shows the main actors, workflow, and data flow.

-

Clusters

-
    -
  • Users & Auth: Django auth.User as central identity
  • -
  • Pet Owners & Pets: Clients, their pets, vaccines, studies
  • -
  • Veterinarians: Vets with availability, specialties, coverage areas
  • -
  • Services & Pricing: Service catalog with dynamic pricing
  • -
  • Cart & Checkout: Shopping cart workflow
  • -
  • Service Requests: Order lifecycle with state machine
  • -
  • Veterinary Visits: Scheduled visits, reports, AFIP invoicing
  • -
  • Reference Data: Lookups (specialties, neighborhoods, vaccines, etc.)
  • -
-
-
- -
-
-

Backend Architecture

- View Full -
- - Backend Architecture - -
-

Django apps structure: mascotas, productos, solicitudes, common, payments, and external integrations.

- -

Celery Tasks

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TaskAppPurpose
send_veterinarian_followupsolicitudesRe-sends availability request to vet if still pending
run_payment_reminder_cronsolicitudesCron job to send payment reminders
create_vetvisit_in_sheetmascotasCreates row in Google Sheets for visit
update_vetvisit_in_sheetmascotasUpdates Google Sheets row
create_event_calendar_vetvisitmascotasCreates Google Calendar event
update_event_calendar_vetvisitmascotasUpdates Google Calendar event
create_user_ownermascotasCreates Django user for PetOwner + welcome email
generate_vetvisit_invoicemascotasGenerates AFIP invoice and PDF
fetch_mp_notification_detailspaymentsFetches MercadoPago webhook details
-

Celery handles async operations: external APIs (Google, MercadoPago, AFIP), scheduled reminders, and heavy processing (invoices, emails).

-
-
- -
-
-

Frontend Architecture

- View Full -
- - Frontend Architecture - -
-

Next.js 13+ App Router structure with public pages, backoffice, shared components, and services layer.

-

Key Areas

-
    -
  • Public Pages: Landing, service catalog, booking flow
  • -
  • Backoffice: Role-based dashboards (admin, vet, petowner)
  • -
  • Services Layer: API clients for backend communication
  • -
  • State Management: Redux store for cart, auth, UI state
  • -
  • Shared Components: Forms, tables, modals, navigation
  • -
-
-
- -
-
-

Detailed Data Model

- View Full -
- - Detailed Data Model - -
-

Complete entity-relationship diagram with all fields, types, and relationships.

-

Data Patterns

-
    -
  • Soft delete: deleted flag on most models
  • -
  • Audit trail: StateHistory for service requests
  • -
  • Geographic: PostGIS polygons for coverage areas
  • -
  • Versioned pricing: Date ranges on Prices
  • -
-
-
- - -
-

Key Findings

- -
-
-

User Types

-

All users connect to auth.User:

-
    -
  • PetOwner: Optional 1:1 link (created lazily)
  • -
  • Veterinarian: Required 1:1 link to User
  • -
  • Staff: Direct Django users with is_staff=True
  • -
-
- -
-

Core Workflow

-
- PetOwner → Cart → ServiceRequest → VeterinarianAsked → VetVisit → Report -
-

State Machine:

-
- pending → vet_asked → vet_accepted → coordinated → payed → Confirmado -
-
- -
-

Pricing Logic

-
    -
  • Base price × neighborhood.distance_coefficient
  • -
  • Optional vet-specific pricing via Prices.veterinarian_id
  • -
  • Turn fee surcharge via IndividualTurnFeeGroup
  • -
  • Time-based discounts via Discounts model
  • -
-
- -
-

External Integrations

-
    -
  • MercadoPago: Payment processing
  • -
  • Google Calendar: Visit synchronization
  • -
  • Google Sheets: Visit tracking spreadsheet
  • -
  • Mercately: WhatsApp notifications
  • -
  • AFIP: Argentine tax invoicing
  • -
-
-
-
- - -
-

Technology Stack

-
-
-

Backend

-
    -
  • Django 4.x
  • -
  • Django REST Framework
  • -
  • PostgreSQL + PostGIS
  • -
  • Celery (Redis)
  • -
  • JWT Authentication
  • -
  • django-afip
  • -
-
-
-

Frontend

-
    -
  • Next.js 13+ (App Router)
  • -
  • React 18+
  • -
  • TypeScript
  • -
  • Redux
  • -
  • Axios
  • -
  • Tailwind CSS
  • -
-
-
-

Infrastructure

-
    -
  • Docker
  • -
  • Nginx
  • -
  • AWS S3 (storage)
  • -
  • MercadoPago API
  • -
  • Google APIs
  • -
  • WhatsApp Business
  • -
-
-
-
-
- - - - diff --git a/cfg/amar/soleprint/atlas/books/arch/styles.css b/cfg/amar/soleprint/atlas/books/arch/styles.css deleted file mode 100644 index 851ca04..0000000 --- a/cfg/amar/soleprint/atlas/books/arch/styles.css +++ /dev/null @@ -1,565 +0,0 @@ -/* Reset and base */ -*, *::before, *::after { - box-sizing: border-box; -} - -:root { - --color-bg: #0f0f0f; - --color-surface: #1a1a1a; - --color-surface-hover: #252525; - --color-border: #333; - --color-text: #e0e0e0; - --color-text-muted: #888; - --color-accent: #4A90D9; - --color-accent-light: #6BA3E0; - --radius: 8px; - --shadow: 0 2px 8px rgba(0,0,0,0.3); -} - -html { - font-size: 16px; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif; - background: var(--color-bg); - color: var(--color-text); - line-height: 1.6; - margin: 0; - padding: 0; - min-height: 100vh; -} - -/* Header */ -header { - background: var(--color-surface); - border-bottom: 1px solid var(--color-border); - padding: 2rem; - text-align: center; -} - -header h1 { - margin: 0; - font-size: 2rem; - font-weight: 600; - color: var(--color-text); -} - -header .subtitle { - margin: 0.5rem 0 0; - color: var(--color-text-muted); - font-size: 1rem; -} - -/* Main content */ -main { - max-width: 1400px; - margin: 0 auto; - padding: 2rem; -} - -section { - margin-bottom: 3rem; -} - -section h2 { - font-size: 1.5rem; - font-weight: 600; - margin: 0 0 1.5rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid var(--color-border); -} - -/* Card Grid - max 3 columns */ -.card-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; -} - -@media (min-width: 1000px) { - .card-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (min-width: 700px) and (max-width: 999px) { - .card-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -/* Graph Cards */ -.card { - display: block; - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - overflow: hidden; - text-decoration: none; - color: inherit; - transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; -} - -.card:hover { - transform: translateY(-2px); - box-shadow: var(--shadow); - border-color: var(--color-accent); -} - -.card-preview { - aspect-ratio: 4/3; - background: #fff; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - padding: 1rem; -} - -.card-preview img { - max-width: 100%; - max-height: 100%; - object-fit: contain; -} - -.card-content { - padding: 1rem; -} - -.card-content h3 { - margin: 0 0 0.5rem; - font-size: 1.1rem; - font-weight: 600; - color: var(--color-accent-light); -} - -.card-content p { - margin: 0; - font-size: 0.9rem; - color: var(--color-text-muted); -} - -/* Findings Grid */ -.findings-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; -} - -@media (min-width: 1000px) { - .findings-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -.finding-card { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: 1.25rem; -} - -.finding-card h3 { - margin: 0 0 0.75rem; - font-size: 1rem; - font-weight: 600; - color: var(--color-accent-light); -} - -.finding-card p, -.finding-card ul { - margin: 0 0 0.75rem; - font-size: 0.9rem; -} - -.finding-card ul { - padding-left: 1.25rem; -} - -.finding-card li { - margin-bottom: 0.25rem; -} - -.finding-card code { - background: #2a2a2a; - padding: 0.1em 0.4em; - border-radius: 3px; - font-size: 0.85em; - color: #f0f0f0; -} - -.workflow-diagram { - background: #2a2a2a; - padding: 0.75rem; - border-radius: 4px; - margin-bottom: 0.75rem; - overflow-x: auto; -} - -.workflow-diagram code { - background: none; - padding: 0; - font-size: 0.8rem; - white-space: nowrap; -} - -.state-flow { - font-family: monospace; - font-size: 0.8rem; - color: var(--color-text-muted); - word-break: break-all; -} - -.stats-table { - width: 100%; - font-size: 0.85rem; - border-collapse: collapse; -} - -.stats-table td { - padding: 0.35rem 0; - border-bottom: 1px solid var(--color-border); -} - -.stats-table td:first-child { - font-family: monospace; - color: var(--color-accent-light); -} - -.stats-table td:last-child { - color: var(--color-text-muted); - text-align: right; -} - -/* Tech Stack Grid */ -.tech-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1.5rem; -} - -@media (min-width: 700px) { - .tech-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -.tech-column { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: 1.25rem; -} - -.tech-column h3 { - margin: 0 0 0.75rem; - font-size: 1rem; - font-weight: 600; - color: var(--color-accent-light); -} - -.tech-column ul { - margin: 0; - padding-left: 1.25rem; - font-size: 0.9rem; -} - -.tech-column li { - margin-bottom: 0.25rem; -} - -/* Footer */ -footer { - background: var(--color-surface); - border-top: 1px solid var(--color-border); - padding: 1.5rem 2rem; - text-align: center; - color: var(--color-text-muted); - font-size: 0.9rem; -} - -footer p { - margin: 0.25rem 0; -} - -/* Graph Viewer Page */ -.graph-viewer { - display: flex; - flex-direction: column; - min-height: 100vh; -} - -.graph-header { - background: var(--color-surface); - border-bottom: 1px solid var(--color-border); - padding: 1rem 2rem; - display: flex; - align-items: center; - gap: 1rem; - flex-shrink: 0; -} - -.graph-header .back-link { - color: var(--color-accent); - text-decoration: none; - font-size: 0.9rem; -} - -.graph-header .back-link:hover { - text-decoration: underline; -} - -.nav-controls { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.nav-controls button { - background: var(--color-surface-hover); - border: 1px solid var(--color-border); - color: var(--color-text); - padding: 0.4rem 0.7rem; - border-radius: 4px; - cursor: pointer; - font-size: 0.9rem; -} - -.nav-controls button:hover:not(:disabled) { - background: var(--color-border); -} - -.nav-controls button:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.nav-controls span { - font-size: 0.85rem; - color: var(--color-text-muted); - min-width: 3rem; - text-align: center; -} - -.graph-header h1 { - margin: 0; - font-size: 1.25rem; - font-weight: 600; - flex: 1; -} - -.graph-controls { - display: flex; - gap: 0.5rem; -} - -.graph-controls button { - background: var(--color-surface-hover); - border: 1px solid var(--color-border); - color: var(--color-text); - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - font-size: 0.85rem; -} - -.graph-controls button:hover { - background: var(--color-border); -} - -.graph-container { - flex: 1; - overflow: auto; - background: #fff; - display: flex; - align-items: center; - justify-content: center; - padding: 2rem; -} - -.graph-container img { - max-width: 100%; - max-height: 100%; - object-fit: contain; -} - -.graph-container.fit-width img { - width: 100%; - height: auto; - max-height: none; -} - -.graph-container.fit-height img { - height: calc(100vh - 80px); - width: auto; - max-width: none; -} - -.graph-container.actual-size img { - max-width: none; - max-height: none; -} - -/* Graph Sections (index page) */ -.graph-section { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius); - margin-bottom: 2rem; - overflow: hidden; -} - -.graph-header-row { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--color-border); -} - -.graph-header-row h2 { - margin: 0; - padding: 0; - border: none; - font-size: 1.25rem; -} - -.view-btn { - background: var(--color-accent); - color: #fff; - text-decoration: none; - padding: 0.5rem 1rem; - border-radius: 4px; - font-size: 0.85rem; - font-weight: 500; -} - -.view-btn:hover { - background: var(--color-accent-light); -} - -.graph-section .graph-preview { - display: block; - background: #fff; - max-height: 400px; - overflow: hidden; -} - -.graph-section .graph-preview img { - width: 100%; - height: auto; - object-fit: contain; - object-position: top left; -} - -.graph-details { - padding: 1.5rem; - border-top: 1px solid var(--color-border); -} - -.graph-details p { - margin: 0 0 1rem; - color: var(--color-text-muted); -} - -.graph-details h4 { - margin: 1.5rem 0 0.75rem; - font-size: 0.95rem; - font-weight: 600; - color: var(--color-accent-light); -} - -.graph-details h4:first-child { - margin-top: 0; -} - -.graph-details ul { - margin: 0; - padding-left: 1.25rem; - font-size: 0.9rem; -} - -.graph-details li { - margin-bottom: 0.35rem; -} - -.graph-details code { - background: #2a2a2a; - padding: 0.1em 0.4em; - border-radius: 3px; - font-size: 0.85em; -} - -/* Details Table */ -.details-table { - width: 100%; - border-collapse: collapse; - font-size: 0.85rem; - margin: 0.5rem 0 1rem; -} - -.details-table th { - text-align: left; - padding: 0.5rem; - background: #2a2a2a; - border-bottom: 1px solid var(--color-border); - font-weight: 600; - color: var(--color-text); -} - -.details-table td { - padding: 0.5rem; - border-bottom: 1px solid var(--color-border); - vertical-align: top; -} - -.details-table code { - background: #2a2a2a; - padding: 0.15em 0.4em; - border-radius: 3px; - font-size: 0.85em; - white-space: nowrap; -} - -.note { - font-size: 0.85rem; - color: var(--color-text-muted); - font-style: italic; - margin-top: 0.5rem; -} - -/* Mobile adjustments */ -@media (max-width: 600px) { - main { - padding: 1rem; - } - - header { - padding: 1.5rem 1rem; - } - - header h1 { - font-size: 1.5rem; - } - - .card-grid, - .findings-grid { - grid-template-columns: 1fr; - } - - .graph-header { - flex-wrap: wrap; - padding: 1rem; - } - - .graph-header h1 { - order: -1; - width: 100%; - margin-bottom: 0.5rem; - } - - .graph-controls { - flex-wrap: wrap; - } -} diff --git a/cfg/amar/soleprint/atlas/books/drive-index/index.html b/cfg/amar/soleprint/atlas/books/drive-index/index.html deleted file mode 100644 index 012e862..0000000 --- a/cfg/amar/soleprint/atlas/books/drive-index/index.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - Drive Index - Amar Mascotas - - - -
- -
-

Drive Index

-

Amar Mascotas - Company Drive Structure

-
- 12 folders - ~235 files -
-
- -
- Preview Mode - This shows the Drive folder structure with placeholder filenames. Real file data will be available once Google Vein (Google Drive sync) is implemented in Artery: -
    -
  • Auto-sync folder structure and file metadata
  • -
  • Direct links to files (requires Google Workspace login)
  • -
  • Live updates when Drive contents change
  • -
-
- -
- 01. Identidad3 files -
-
Brand assets, logos, typography
-
-
png logo_main.png
-
png logo_alt.png
-
ttf brand_font.ttf
-
-
-
- -
- 02. Marketing Contenidos8 files -
-
Social media content, templates, testimonials
-
-
docx social_template.docx
-
png post_image1.png
-
png post_image2.png
-
mov event_video.mov
-
png testimonials.png
-
-
-
- -
- 03. Marketing Growth8 files -
-
Paid media campaigns, performance dashboards
-
-
xlsx dashboard_report.xlsx
-
png ad_1080x1080.png
-
png ad_1200x628.png
-
png ad_stories.png
-
-
-
- -
- 05. ATC - Operaciones12 files -
-
Process manuals, workflows, support docs
-
-
docx manual_procesos.docx
-
jpg workflow_diagram.jpg
-
xlsx contacts_list.xlsx
-
xlsx postal_codes.xlsx
-
xlsx tasks_tracker.xlsx
-
-
-
- -
- 06. Supply7 files -
-
Vet network, lab partnerships, onboarding
-
-
pdf lab_prices.pdf
-
xlsx vet_onboarding.xlsx
-
pdf vet_cv_sample.pdf
-
mp4 meeting_recording.mp4
-
-
-
- -
- 07. Finanzas7 files -
-
Invoices, accounting, finance tracking
-
-
xlsx finance_tracker.xlsx
-
pdf invoice_may.pdf
-
pdf invoice_jun.pdf
-
pdf invoice_jul.pdf
-
-
-
- -
- 08. IT y Producto57 files -
-
Product specs, feature docs, references
-
-
docx product_index.docx
-
docx feature_specs.docx
-
xlsx platform_states.xlsx
-
xlsx medications_db.xlsx
-
xlsx events_mapping.xlsx
-
pptx benchmark.pptx
-
-
-
- -
- 09. DATA2 files -
-
Database exports, data backups
-
-
xlsx pets_database.xlsx
-
xlsx pets_backup.xlsx
-
-
-
- -
- Clientes - ventas22 files -
-
CRM exports, sales data, client info
-
-
xlsx crm_export.xlsx
-
xlsx churn_analysis.xlsx
-
xlsx leads_2023.xlsx
-
xlsx leads_2024.xlsx
-
pdf monthly_report.pdf
-
-
-
- -
- HR17 files -
-
Onboarding, job profiles, team docs
-
-
pptx onboarding_deck.pptx
-
docx job_profile_sales.docx
-
pdf job_profile_growth.pdf
-
jpg team_photo.jpg
-
-
-
- -
- Pitch Decks24 files -
-
Investor presentations, company decks
-
-
pptx pitch_deck_latest.pptx
-
docx video_script.docx
-
pptx product_timeline.pptx
-
pptx investor_update.pptx
-
-
-
- -
- z.Backup archivos viejos46 files -
-
Legacy files, old exports
-
-
xlsx old_vets_list.xlsx
-
xlsx org_chart.xlsx
-
xlsx data_room_2023.xlsx
-
xlsx legacy_export.xlsx
-
-
-
- -
pawprint/album
-
- - diff --git a/cfg/amar/soleprint/atlas/books/drive-index/index.md b/cfg/amar/soleprint/atlas/books/drive-index/index.md deleted file mode 100644 index fa36e5e..0000000 --- a/cfg/amar/soleprint/atlas/books/drive-index/index.md +++ /dev/null @@ -1,217 +0,0 @@ -# Drive Index - Amar Mascotas - -> vault source: `vault/drive/` -> 48 files | Last indexed: 2025-12-08 - -## Overview by Area - -| Area | Focus | Key Files | Workflow Potential | -|------|-------|-----------|-------------------| -| 01. Identidad | Brand assets | Illustrations, Typography | Design handoff | -| 02. Marketing Contenidos | Social/Content | Posts, Event media, Templates | Content calendar | -| 03. Marketing Growth | Paid/Performance | Ad creatives, Dashboards | Campaign tracking | -| 05. ATC - Operaciones | Ops/Support | Process manual, Workflow diagram | Onboarding, SOP | -| 06. Supply | Vets/Labs | Vet tracking, Lab prices, CVs | Vet onboarding | -| 07. Finanzas | Accounting | Invoices, Finance sheet | Reconciliation | -| Clientes | Sales/CRM | Fitsales export | Client tracking | -| Pitch Decks | Presentations | Company pitch | Investor/partner | - ---- - -## 01. Identidad Amar Mascotas -Brand identity assets. - -``` -Ilustraciones amar PNG/ -├── Asset 4@4x.png -└── Asset 11@4x.png - -Tipografia/Open_Sans/static/ -└── OpenSans_Condensed-Italic.ttf -``` - -**Workflow relevance**: Design system, brand consistency checks. - ---- - -## 02. Marketing Contenidos -Social media content and production assets. - -``` -Backup archivos viejos/ -├── Contenido evento amar 10 ago 2023/ -│ ├── IMG_7309.MOV -│ └── video-output-(...).mov -└── RRSS MSJ PREDETERMINADOS.docx ← Predefined social messages - -Produccion audiovisual Jul 24/ -└── Fotos Produccion Audiovisual JUL24/ - └── Vacunacion Fotos Produccion JUL 24/ - └── IMG_4620.JPG - -Redes sociales Amar/ -├── 02. FEBRERO 25 Finales/12-2/cambios/247.png -├── 08. AGOSTO 24 Finales Amar/ -│ ├── 13.8/1.png -│ └── 21.8/3.png -└── Comentarios clientes felices JUN-JUL24/ - └── Captura de pantalla (...).png ← Testimonials -``` - -**Workflow relevance**: -- `RRSS MSJ PREDETERMINADOS.docx` = template for ATC/social responses -- Monthly folders suggest content calendar exists -- Testimonials capture = social proof workflow - ---- - -## 03. Marketing Growth -Paid media campaigns and performance tracking. - -``` -Paid Media - Pauta digital/ -├── 05.MAY24 - ADS Testimonios/ -├── 08.AGO24 - ADS 10 OFF/ -│ ├── 1080 x 1080-17.png -│ ├── 1200 x 628-32.png -│ ├── 1200 x 628-33.png -│ ├── 1200 x 628-38.png -│ ├── 1200 x 628-39.png -│ └── 960 x 1200-30.png -├── Amar Mascotas - Reporte_Dash general(...)(2).xlsx -└── Amar Mascotas - Reporte_Dash general(...)(3).xlsx -``` - -**Workflow relevance**: -- Ad size variants = multi-platform campaigns (Meta, Google) -- Dashboard reports = performance tracking exists -- Monthly campaign folders = structured campaign workflow - ---- - -## 05. ATC - Operaciones -Operations, customer service, and legacy data. - -``` -Operaciones backup viejos/ -├── ATC - Manual de Procesos.docx ← KEY: Process documentation -├── work Flow servicio veterinario.jpg ← KEY: Service workflow diagram -├── Clientes a contactar.xlsx -├── codigos_postales_y_barrios (1).xlsx ← Coverage/delivery zones -├── domesticables ultimo.docx -├── domesticables ultimo.pdf -├── INFO.xlsx -├── Reporte DOMESTICABLES 11_5.xlsx -├── Tareas de Leila.xlsx -└── entrega/ - ├── prestashop/ - │ ├── clientes a escribir febrero 2023.csv - │ └── datos web vieja/ - │ └── reembolsos por falta de stock.csv - └── vendedores/somos pam/ - └── articulos desactivados en marzo 2023.csv -``` - -**Workflow relevance**: -- `ATC - Manual de Procesos.docx` = existing SOP (Gherkin source?) -- `work Flow servicio veterinario.jpg` = visual process (BDD source?) -- Legacy CSV = migration/history context -- Task tracking = ops workflow exists - ---- - -## 06. Supply (vetes-labo-clinicas) -Veterinarian network and lab partnerships. - -``` -Comunciacion/ -└── Veterinarios-46.jpg - -Formularios- Viejos - no usar/ -└── Reuniones Matias/ - ├── Grabación de pantalla 2024-08-20(...).mp4 - └── Grabación de pantalla 2024-08-21(...).mp4 - -Laboratorios/DiagnoTest/ -└── Diagnotest-ListaPrecios-Junio2025 (1).pdf ← Current pricing - -Veterinarios postulantes/ -├── Aldana Becker - Colaboracion Amar Mascotas.docx -├── Ana Christophersen/ -│ └── CV - Ana Christophersen.pdf -└── Ingreso Veterinarios.xlsx ← Vet onboarding tracker -``` - -**Workflow relevance**: -- `Ingreso Veterinarios.xlsx` = vet recruitment/onboarding flow -- Lab price list = service pricing reference -- Meeting recordings = training/knowledge transfer - ---- - -## 07. Finanzas y contabilidad -Invoices and financial tracking. - -``` -Facturas/ -├── May 23/Recibido/ -│ ├── FacturaAMARMASCOTASSA(...).pdf -│ └── FacturaAMARMASCOTASSA(...).pdf -├── Jun 23/Recibido/ -│ ├── nc 4-29.pdf -│ └── nc 4-30.pdf -└── Jul 23/Recibido/ - ├── FAC 567 ZUCCONI ALAN EDGARDO.pdf - └── Mercaba carlos roberto cozak.pdf - -Finanzas Amar mascotas.xlsx ← Main finance tracker -``` - -**Workflow relevance**: -- Monthly invoice organization = accounting workflow -- Finance xlsx = reconciliation process - ---- - -## Clientes - ventas - devoluciones -CRM and sales data. - -``` -Excels Clientes - ventas - devoluciones/ -└── Fitsales CRM vinculados hasta el 06 dic.xlsx -``` - -**Workflow relevance**: -- CRM export = client tracking, sales funnel - ---- - -## Pitch Decks - Presentaciones -Company presentations. - -``` -Copias Pitch Deck Cata/ -└── Copia de Pitch Deck - ABR 2025.pptx -``` - -**Workflow relevance**: -- Investor/partner communications - ---- - -## Workflow Discovery Summary - -### Documents to Review for BDD/Gherkin -1. `05. ATC/ATC - Manual de Procesos.docx` - existing process documentation -2. `05. ATC/work Flow servicio veterinario.jpg` - visual workflow -3. `02. Marketing/RRSS MSJ PREDETERMINADOS.docx` - response templates - -### Active Tracking Sheets -- `06. Supply/Ingreso Veterinarios.xlsx` - vet onboarding -- `Clientes/Fitsales CRM vinculados.xlsx` - client tracking -- `07. Finanzas/Finanzas Amar mascotas.xlsx` - finance - -### Potential Artery Connections -- Fitsales CRM → pulse for client data -- Finance xlsx → pulse for accounting -- Vet tracker → pulse for supply management diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/CLAUDE.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/CLAUDE.md deleted file mode 100644 index e02eb18..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/CLAUDE.md +++ /dev/null @@ -1,79 +0,0 @@ -# Ops Templates Sample Book - -## Purpose -**SAMPLE DATA** - Example filled ops/support templates for demonstration and testing purposes. These are realistic examples to work with while actual definitions are pending from ops/support team. - -Use these to: -- Understand the template structure -- Test tooling and workflows -- Demo the feature-flow pipeline - -Real templates will follow the same structure but with validated content. - -## Structure - -``` -ops-templates-sample/ -├── pet-owner/ # 5 flows for pet owners -│ ├── 01-registro.md -│ ├── 02-reservar-turno.md -│ ├── 03-gestion-mascotas.md -│ ├── 04-pago-turno.md -│ └── 05-historial-medico.md -├── veterinarian/ # 5 flows for vets -│ ├── 01-aceptar-solicitud.md -│ ├── 02-gestion-agenda.md -│ ├── 03-realizar-visita.md -│ ├── 04-zonas-cobertura.md -│ └── 05-historial-pacientes.md -└── backoffice/ # 5 flows for admins - ├── 01-gestion-solicitudes.md - ├── 02-gestion-usuarios.md - ├── 03-gestion-servicios.md - ├── 04-reembolsos.md - └── 05-reportes.md -``` - -## Template Format - -Each file follows the standard ops template: - -```markdown -# [Nombre del Flujo] - -## Tipo de usuario -## Donde empieza -## Que quiere hacer el usuario -## Pasos -## Que deberia pasar -## Problemas comunes -## Casos especiales -## Flujos relacionados -## Notas tecnicas -``` - -## Corresponding Gherkin - -Each template has corresponding `.feature` files in: -- `album/book/gherkin-sample/es/` - Spanish keywords (Dado/Cuando/Entonces) -- `album/book/gherkin-sample/en/` - English keywords (Given/When/Then) - -## Usage - -1. **Support/Ops**: Use templates to document new flows -2. **Dev**: Convert templates to Gherkin specs -3. **QA**: Use Gherkin to derive test cases -4. **Product**: Review templates for completeness - -## Source Template - -The base template is at: `album/template/ops-flow/plantilla-flujo.md` - -## Coverage - -| Area | Flows | Status | -|------|-------|--------| -| Pet Owner | 5 | Complete | -| Veterinarian | 5 | Complete | -| Backoffice | 5 | Complete | -| **Total** | **15** | | diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/detail.html b/cfg/amar/soleprint/atlas/books/feature-form-samples/detail.html deleted file mode 100644 index 0732da2..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/detail.html +++ /dev/null @@ -1,335 +0,0 @@ - - - - - - {{ filename }} - Ops Template - - - -
-
- -

{{ filename.replace('.md', '').replace('-', ' ').title() }}

-
- {{ user_type }} - Sample -
-
- -
-
-

User Flow Template

-
-
- -
-
- - - - -
- - - - diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/.larder b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/.larder deleted file mode 100644 index e69de29..0000000 diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md deleted file mode 100644 index 13661aa..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/01-gestion-solicitudes.md +++ /dev/null @@ -1,92 +0,0 @@ -# Gestion de Solicitudes de Servicio - -## Tipo de usuario -Administrador / Operaciones - -## Donde empieza -Backoffice `/admin/` -> Dashboard o Seccion "Solicitudes" - -## Que quiere hacer el usuario -Ver, filtrar y gestionar todas las solicitudes de servicio del sistema - -## Pasos - Ver solicitudes - -1. Acceder al backoffice -2. Ir a seccion "Solicitudes" -3. Ver listado con todas las solicitudes -4. Usar filtros para encontrar solicitudes especificas: - - Por estado (Pendiente, Coordinado, Pagado, Completado, Cancelado) - - Por fecha - - Por barrio/zona - - Por veterinario - - Por dueno - -## Pasos - Ver detalle de solicitud - -1. Click en una solicitud del listado -2. Ver toda la informacion: - - Datos del dueno - - Datos de la mascota - - Servicios solicitados - - Fechas preferidas - - Veterinario asignado (si hay) - - Estado de pago - - Historial de cambios de estado - -## Pasos - Asignar veterinario manualmente - -1. Abrir solicitud en estado "Pendiente" -2. Click en "Asignar veterinario" -3. Ver lista de vets disponibles en la zona -4. Seleccionar veterinario -5. Seleccionar fecha y hora -6. Confirmar asignacion -7. Sistema notifica al vet y al dueno - -## Pasos - Cambiar estado manualmente - -1. Abrir solicitud -2. Click en "Cambiar estado" -3. Seleccionar nuevo estado -4. Ingresar motivo (si aplica) -5. Confirmar -6. Se registra en el historial - -## Que deberia pasar - -- Vision completa de todas las solicitudes -- Capacidad de intervenir cuando algo falla -- Trazabilidad de cambios -- Notificaciones a las partes afectadas - -## Problemas comunes - -- Solicitud sin vet disponible en la zona -- Usuario pago pero webhook fallo -- Vet no puede asistir y hay que reasignar -- Dueno quiere cancelar pero ya pago -- Solicitudes duplicadas del mismo dueno - -## Casos especiales - -- Solicitud urgente sin vets disponibles -- Reasignacion a ultimo momento -- Cancelacion con reembolso -- Solicitud con datos incompletos -- Dueno no responde a confirmaciones - -## Flujos relacionados - -- Proceso de reembolso -- Gestion de usuarios -- Reportes - -## Notas tecnicas - -- APIs: - - `GET /solicitudes/api/v1/service-requests/` (listado con filtros) - - `GET /solicitudes/api/v1/service-requests/{id}/` (detalle) - - `PATCH /solicitudes/api/v1/service-requests/{id}/` (asignar vet) - - `PATCH /solicitudes/api/v1/change-service-request-state/` (cambiar estado) -- Estados validos: pending, coordinated, payed, completed, cancelled -- Permisos: solo staff puede ver todas las solicitudes diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md deleted file mode 100644 index db75f6f..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/02-gestion-usuarios.md +++ /dev/null @@ -1,106 +0,0 @@ -# Gestion de Usuarios - -## Tipo de usuario -Administrador / Operaciones - -## Donde empieza -Backoffice `/admin/` -> Seccion "Usuarios" o "Duenos" / "Veterinarios" - -## Que quiere hacer el usuario -Administrar duenos de mascotas y veterinarios del sistema - -## Pasos - Ver/Buscar duenos - -1. Ir a "Duenos de mascotas" -2. Ver listado de todos los duenos -3. Usar buscador (nombre, email, telefono) -4. Filtrar por: - - Tipo (Registrado / Invitado) - - Fecha de registro - - Con/sin turnos activos - -## Pasos - Ver detalle de dueno - -1. Click en un dueno -2. Ver informacion completa: - - Datos personales - - Direcciones registradas - - Mascotas - - Historial de solicitudes - - Historial de pagos - -## Pasos - Editar dueno - -1. En el detalle, click "Editar" -2. Modificar campos permitidos: - - Nombre - - Telefono - - Email - - Direccion -3. Guardar cambios - -## Pasos - Gestionar veterinarios - -1. Ir a "Veterinarios" -2. Ver listado con: - - Nombre y matricula - - Zonas de cobertura - - Estado (Activo/Inactivo) - - Turnos del mes - -## Pasos - Agregar nuevo veterinario - -1. Click en "Agregar veterinario" -2. Completar datos: - - Nombre completo - - Numero de matricula - - Email - - Telefono - - Especialidades - - Zonas de cobertura inicial -3. Crear credenciales de acceso -4. Guardar - -## Pasos - Desactivar veterinario - -1. Abrir perfil del vet -2. Click en "Desactivar" -3. Confirmar -4. Vet deja de recibir solicitudes -5. Visitas ya aceptadas se mantienen o se reasignan - -## Que deberia pasar - -- Gestion completa de usuarios del sistema -- Capacidad de corregir datos erroneos -- Alta/baja de veterinarios -- Vision del estado de cada usuario - -## Problemas comunes - -- Duenos duplicados (se registraron con email diferente) -- Vet quiere darse de baja pero tiene citas pendientes -- Email incorrecto y no reciben notificaciones -- Cuenta de invitado que quiere convertirse en registrada - -## Casos especiales - -- Fusionar cuentas duplicadas -- Transferir mascota a otro dueno -- Vet de vacaciones temporalmente -- Cambio de numero de matricula - -## Flujos relacionados - -- Gestion de solicitudes (ver historial por usuario) -- Reportes (filtrar por usuario) - -## Notas tecnicas - -- APIs: - - `GET /mascotas/api/v1/pet-owners/` (duenos) - - `GET /mascotas/api/v1/veterinarians/` (vets) - - `POST /mascotas/api/v1/veterinarians/` (crear vet) - - `PATCH /mascotas/api/v1/veterinarians/{id}/` (editar/desactivar) -- Permisos: solo staff puede modificar usuarios -- Soft delete para mantener historial diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md deleted file mode 100644 index 38a759b..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/03-gestion-servicios.md +++ /dev/null @@ -1,98 +0,0 @@ -# Gestion de Servicios y Precios - -## Tipo de usuario -Administrador - -## Donde empieza -Backoffice `/admin/` -> Seccion "Servicios" o Django Admin -> Productos - -## Que quiere hacer el usuario -Administrar el catalogo de servicios veterinarios y sus precios - -## Pasos - Ver servicios - -1. Ir a "Servicios" -2. Ver listado de todos los servicios -3. Filtrar por: - - Categoria (Vacunacion, Consulta, Cirugia, etc.) - - Tipo de mascota (Perro, Gato, Ambos) - - Estado (Activo/Inactivo) - -## Pasos - Agregar nuevo servicio - -1. Click en "Agregar servicio" -2. Completar datos: - - Nombre del servicio - - Descripcion - - Categoria - - Tipo de mascota aplicable - - Precio base - - Duracion estimada - - Requiere otros servicios (dependencias) -3. Guardar - -## Pasos - Editar servicio - -1. Abrir servicio existente -2. Modificar campos -3. Guardar -4. Cambios aplican a nuevas solicitudes - -## Pasos - Gestionar precios - -1. Abrir servicio -2. Ir a seccion "Precios" -3. Ver precio actual -4. Para cambiar precio: - - Click "Agregar precio" - - Ingresar monto - - Definir fecha de vigencia - - Guardar -5. Precio anterior queda en historial - -## Pasos - Configurar combos - -1. Ir a "Combos" o "Paquetes" -2. Crear nuevo combo: - - Nombre del combo - - Servicios incluidos - - Precio del combo (con descuento) - - Condiciones de aplicacion -3. Guardar - -## Que deberia pasar - -- Catalogo actualizado de servicios -- Precios correctos en el turnero -- Combos aplicados automaticamente -- Historial de precios para facturacion - -## Problemas comunes - -- Cambio de precio afecta solicitudes ya creadas -- Servicio deprecado pero con solicitudes pendientes -- Combo no se aplica automaticamente -- Precio diferente por zona/vet - -## Casos especiales - -- Servicio con precio variable (depende de peso mascota) -- Servicio estacional (solo ciertas epocas) -- Promocion temporal -- Precio especial por vet - -## Flujos relacionados - -- Turnero (muestra servicios disponibles) -- Facturacion (usa precios) - -## Notas tecnicas - -- APIs: - - `GET /productos/api/v1/services/` (servicios) - - `POST /productos/api/v1/services/` (crear) - - `PATCH /productos/api/v1/services/{id}/` (editar) - - `GET /productos/api/v1/prices/` (precios) - - `POST /productos/api/v1/prices/` (nuevo precio) -- Modelo Price tiene fecha de vigencia -- Precio se congela al momento de crear solicitud diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/04-reembolsos.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/04-reembolsos.md deleted file mode 100644 index 877ce8e..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/04-reembolsos.md +++ /dev/null @@ -1,82 +0,0 @@ -# Proceso de Reembolso - -## Tipo de usuario -Administrador / Operaciones - -## Donde empieza -Backoffice -> Solicitud pagada -> "Procesar reembolso" o Seccion "Pagos" - -## Que quiere hacer el usuario -Procesar un reembolso para un turno que fue cancelado despues del pago - -## Pasos - Identificar solicitud a reembolsar - -1. Buscar la solicitud por: - - ID de solicitud - - Nombre del dueno - - Numero de operacion de Mercado Pago -2. Verificar que esta en estado "Pagado" -3. Verificar que el pago fue exitoso - -## Pasos - Procesar reembolso - -1. Abrir detalle de la solicitud -2. Click en "Procesar reembolso" -3. Ver informacion del pago: - - Monto pagado - - Fecha de pago - - Metodo de pago - - ID de operacion MP -4. Seleccionar tipo de reembolso: - - Total - - Parcial (ingresar monto) -5. Ingresar motivo del reembolso -6. Confirmar -7. Sistema procesa reembolso en Mercado Pago -8. Actualizar estado de la solicitud - -## Pasos - Verificar reembolso - -1. Ir a seccion "Pagos" o historial de la solicitud -2. Verificar que el reembolso fue procesado -3. Ver estado del reembolso en Mercado Pago - -## Que deberia pasar - -- Reembolso procesado en Mercado Pago -- Dueno recibe el dinero (segun tiempos de MP) -- Solicitud actualiza estado -- Registro en el historial -- Dueno notificado por email - -## Problemas comunes - -- Reembolso parcial no soportado por el metodo de pago -- Plazo de reembolso vencido (politica de MP) -- Dueno reclama que no recibio el dinero -- Reembolso duplicado por error -- Pago original en disputa - -## Casos especiales - -- Reembolso por mala atencion -- Reembolso parcial (solo algunos servicios) -- Visita incompleta (reembolso proporcional) -- Dueno quiere credito en vez de reembolso -- Reembolso fuera de plazo (solucion alternativa) - -## Flujos relacionados - -- Gestion de solicitudes -- Cancelacion de turno -- Atencion al cliente - -## Notas tecnicas - -- API Mercado Pago: `POST /v1/payments/{id}/refunds` -- Tiempos de acreditacion: - - Tarjeta credito: 1-2 estados de cuenta - - Tarjeta debito: 5-10 dias habiles - - Dinero en cuenta MP: inmediato -- Plazo maximo para reembolso: 180 dias desde el pago -- Se debe guardar log de todos los reembolsos diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/05-reportes.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/05-reportes.md deleted file mode 100644 index 05bba99..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/backoffice/05-reportes.md +++ /dev/null @@ -1,99 +0,0 @@ -# Reportes y Dashboard - -## Tipo de usuario -Administrador / Gerencia - -## Donde empieza -Backoffice `/admin/` -> Dashboard o Seccion "Reportes" - -## Que quiere hacer el usuario -Ver metricas del negocio, generar reportes y analizar datos - -## Pasos - Ver dashboard - -1. Acceder al backoffice -2. Ver dashboard principal con: - - Solicitudes del dia/semana/mes - - Ingresos del periodo - - Visitas completadas - - Conversion (solicitudes -> pagos) - - Vets activos - -## Pasos - Generar reporte de solicitudes - -1. Ir a "Reportes" -> "Solicitudes" -2. Seleccionar rango de fechas -3. Filtrar por: - - Estado - - Zona - - Veterinario - - Tipo de servicio -4. Click "Generar" -5. Ver tabla con resultados -6. Opcion de exportar (CSV/Excel) - -## Pasos - Generar reporte de ingresos - -1. Ir a "Reportes" -> "Ingresos" -2. Seleccionar periodo -3. Agrupar por: - - Dia/Semana/Mes - - Veterinario - - Servicio - - Zona -4. Ver grafico y tabla -5. Exportar si es necesario - -## Pasos - Reporte de veterinarios - -1. Ir a "Reportes" -> "Veterinarios" -2. Ver para cada vet: - - Visitas completadas - - Calificacion promedio - - Ingresos generados - - Tasa de cancelacion -3. Filtrar por periodo -4. Ordenar por metrica - -## Pasos - Exportar datos - -1. En cualquier reporte, click "Exportar" -2. Seleccionar formato (CSV, Excel) -3. Descargar archivo -4. Usar para analisis externo o contabilidad - -## Que deberia pasar - -- Vision clara del estado del negocio -- Datos para toma de decisiones -- Reportes exportables para contabilidad -- Identificacion de tendencias - -## Problemas comunes - -- Datos no actualizados en tiempo real -- Filtros que no devuelven lo esperado -- Discrepancia entre reportes y realidad -- Exportacion con formato incorrecto - -## Casos especiales - -- Reporte de prueba vs produccion -- Datos historicos antes de implementar filtro -- Comparativa año vs año -- Reporte personalizado para inversores - -## Flujos relacionados - -- Gestion de solicitudes (fuente de datos) -- Facturacion AFIP -- Google Sheets sync - -## Notas tecnicas - -- API: - - `GET /mascotas/api/v1/stats-summary` (metricas principales) - - `GET /solicitudes/api/v1/service-requests/?date_from=X&date_to=Y` (datos crudos) -- Google Sheets: sincronizacion automatica via Celery -- Dashboard actualiza cada 5 minutos -- Reportes pesados: generar async y notificar cuando estan listos diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/01-registro.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/01-registro.md deleted file mode 100644 index 9948c3f..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/01-registro.md +++ /dev/null @@ -1,55 +0,0 @@ -# Registro de Usuario - -## Tipo de usuario -Dueno de mascota (nuevo) - -## Donde empieza -Pagina principal -> Boton "Registrarse" o `/register` - -## Que quiere hacer el usuario -Crear una cuenta nueva para poder reservar turnos y gestionar sus mascotas - -## Pasos - -1. Click en "Registrarse" en la pagina principal -2. Ingresar email -3. Ingresar contraseña -4. Confirmar contraseña -5. Aceptar terminos y condiciones -6. Click en "Crear cuenta" -7. Recibir email de verificacion -8. Click en el link de verificacion -9. Cuenta activada, redirige al dashboard - -## Que deberia pasar - -- Usuario creado en el sistema -- Email de bienvenida enviado -- Puede iniciar sesion inmediatamente -- Ve su dashboard vacio (sin mascotas, sin turnos) - -## Problemas comunes - -- El email ya esta registrado pero el usuario no lo recuerda -- El email de verificacion llega a spam -- La contraseña no cumple los requisitos minimos -- El usuario cierra el browser antes de verificar y no encuentra el email despues -- Usuarios intentan registrarse con email de otra persona - -## Casos especiales - -- Si el email existe como usuario invitado (del turnero), deberia linkear las cuentas -- Si el usuario ya tiene cuenta pero no verificada, reenviar email de verificacion -- Registro desde el flujo de turnero (ya ingreso datos, solo falta contraseña) - -## Flujos relacionados - -- Login (despues de registrarse) -- Turnero (puede disparar registro) -- Recuperar contraseña (si ya tenia cuenta y no recuerda) - -## Notas tecnicas - -- API: `POST /common/users/create/` -- Validacion de contraseña: minimo 8 caracteres, 1 numero, 1 mayuscula -- Verificacion expira en 24 horas diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md deleted file mode 100644 index f882aad..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/02-reservar-turno.md +++ /dev/null @@ -1,77 +0,0 @@ -# Reservar Turno (Turnero) - -## Tipo de usuario -Dueno de mascota (invitado, registrado, o recurrente) - -## Donde empieza -Pagina principal -> Boton "Agendar visita" o enlace directo `/turnero` - -## Que quiere hacer el usuario -Reservar un turno veterinario a domicilio para su mascota - -## Pasos - -1. Click en "Agendar visita" -2. Ingresar direccion en el popup de cobertura -3. Sistema verifica si hay cobertura en esa zona -4. Si hay cobertura, click "Siguiente" -5. Completar datos de la mascota: - - Nombre - - Tipo (Perro/Gato) - - Raza (opcional) - - Edad aproximada - - Peso aproximado - - Si esta castrado/a -6. Seleccionar categoria de servicio (Vacunacion, Consulta, etc.) -7. Seleccionar servicios especificos -8. Ver que algunos servicios se agregan automaticamente (ej: consulta clinica) -9. Seleccionar fechas preferidas (hasta 3) -10. Seleccionar franja horaria preferida -11. Ingresar datos de contacto: - - Nombre completo - - Telefono - - Email -12. Revisar resumen -13. Enviar solicitud - -## Que deberia pasar - -- Solicitud creada con estado "Pendiente" -- Email de confirmacion al usuario -- Notificacion a veterinarios de la zona -- Aparece en "Mis turnos" del usuario - -## Problemas comunes - -- No hay cobertura en la zona del usuario -- El autocompletado de direcciones no encuentra la calle -- Usuario no puede quitar "Consulta clinica" cuando selecciona vacunacion -- Confunden "fecha preferida" con "fecha confirmada" -- No entienden por que no pueden elegir hora exacta -- El total no se muestra hasta el final y sorprende al usuario - -## Casos especiales - -- Usuario invitado: se crea como guest, puede registrarse despues -- Usuario logueado: datos pre-llenados, mascota puede ser existente -- Usuario recurrente (mismo email): sistema detecta y sugiere linkear -- Mascota ya castrada: no mostrar servicio de castracion -- Servicios combo: algunos incluyen otros automaticamente -- Zona sin cobertura: mostrar formulario para avisar cuando haya - -## Flujos relacionados - -- Ver mis turnos (despues de reservar) -- Pagar turno (cuando se coordina) -- Cancelar turno -- Agregar mascota (si ya tiene cuenta) - -## Notas tecnicas - -- APIs: - - `POST /mascotas/api/v1/coverage-check/` (verificar cobertura) - - `POST /mascotas/api/v1/pet-owners/` (crear dueno invitado) - - `POST /mascotas/api/v1/pets/` (crear mascota) - - `GET /productos/api/v1/services/` (obtener servicios) - - `POST /solicitudes/api/v1/service-requests/` (crear solicitud) -- Estados de solicitud: pending -> coordinated -> payed -> completed diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md deleted file mode 100644 index 4e92310..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/03-gestion-mascotas.md +++ /dev/null @@ -1,79 +0,0 @@ -# Gestion de Mascotas - -## Tipo de usuario -Dueno de mascota (registrado) - -## Donde empieza -Dashboard -> Seccion "Mis mascotas" o `/user/pets` - -## Que quiere hacer el usuario -Agregar, editar o ver informacion de sus mascotas - -## Pasos - Agregar mascota - -1. Ir a "Mis mascotas" -2. Click en "Agregar mascota" -3. Completar formulario: - - Nombre (obligatorio) - - Tipo: Perro o Gato (obligatorio) - - Raza (opcional, lista segun tipo) - - Fecha de nacimiento o edad aproximada - - Peso en kg (opcional) - - Sexo - - Esta castrado/a (checkbox) - - Foto (opcional) -4. Guardar - -## Pasos - Editar mascota - -1. Ir a "Mis mascotas" -2. Click en la tarjeta de la mascota -3. Click en "Editar" -4. Modificar campos deseados -5. Guardar - -## Pasos - Ver historial medico - -1. Ir a "Mis mascotas" -2. Click en la tarjeta de la mascota -3. Ver seccion "Historial de visitas" -4. Click en una visita para ver el informe completo - -## Que deberia pasar - -- Mascota aparece en el listado -- Disponible para seleccionar en el turnero -- Historial medico accesible -- Foto visible en la tarjeta - -## Problemas comunes - -- Usuarios quieren agregar mascotas de otros tipos (conejo, ave) - no soportado -- No encuentran donde ver el historial medico -- Quieren eliminar mascota pero tiene turnos pendientes -- Foto muy grande no sube -- Confunden edad con fecha de nacimiento - -## Casos especiales - -- Mascota creada desde turnero como invitado: aparece cuando linkea cuenta -- Mascota fallecida: deberia poder marcarse (soft delete) -- Multiples mascotas con mismo nombre: permitido pero confuso -- Cambio de peso significativo: registrar historial? - -## Flujos relacionados - -- Reservar turno (seleccionar mascota existente) -- Ver historial medico -- Registro (crear primera mascota) - -## Notas tecnicas - -- APIs: - - `GET /mascotas/api/v1/pets/` (listar) - - `POST /mascotas/api/v1/pets/` (crear) - - `PATCH /mascotas/api/v1/pets/{id}/` (editar) - - `DELETE /mascotas/api/v1/pets/{id}/` (soft delete) - - `GET /mascotas/api/v1/vet-visits/?pet={id}` (historial) -- Tipos soportados: solo Perro y Gato por ahora -- Razas: lista predefinida por tipo diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/04-pago-turno.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/04-pago-turno.md deleted file mode 100644 index d0a2d44..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/04-pago-turno.md +++ /dev/null @@ -1,66 +0,0 @@ -# Pago de Turno - -## Tipo de usuario -Dueno de mascota (con turno coordinado) - -## Donde empieza -Email de confirmacion de turno -> Link "Pagar" o Dashboard -> Mis turnos -> Boton "Pagar" - -## Que quiere hacer el usuario -Pagar un turno que ya fue coordinado con fecha y veterinario asignado - -## Pasos - -1. Recibir email/notificacion de que el turno fue coordinado -2. Ver detalle del turno con fecha, hora y veterinario asignado -3. Verificar el monto total -4. Click en "Pagar" -5. Redireccion a Mercado Pago -6. Seleccionar metodo de pago: - - Tarjeta de credito/debito - - Dinero en cuenta MP - - Transferencia bancaria - - Efectivo (Rapipago/PagoFacil) -7. Completar pago -8. Redireccion de vuelta a la plataforma -9. Ver confirmacion de pago exitoso - -## Que deberia pasar - -- Estado del turno cambia a "Pagado" -- Email de confirmacion de pago -- Comprobante disponible -- Veterinario notificado -- Turno aparece como confirmado en el calendario - -## Problemas comunes - -- Usuario cierra el browser durante el pago y no sabe si se proceso -- Pago rechazado por fondos insuficientes -- Link de pago expira y no pueden pagr -- Quieren pagar en efectivo el dia de la visita -- Precio cambio entre coordinacion y pago -- Quieren aplicar descuento/cupon y no encuentran donde - -## Casos especiales - -- Pago parcial: no soportado actualmente -- Pago con tarjeta rechazada: puede reintentar -- Pago por transferencia: demora en acreditarse (pendiente de confirmacion) -- Usuario paga pero webhook falla: requiere intervencion manual -- Turno cancelado despues de pagar: proceso de reembolso - -## Flujos relacionados - -- Ver mis turnos (antes y despues de pagar) -- Cancelar turno (si decide no pagar) -- Contactar soporte (problemas con pago) - -## Notas tecnicas - -- APIs: - - `POST /payments/api/v1/requests/{id}/preference/` (generar link MP) - - Webhook: `POST /payments/mp/webhook/` (notificacion de MP) -- Integracion: Mercado Pago Checkout Pro -- Estados: coordinated -> payed (despues de webhook exitoso) -- Timeout de preferencia: 24 horas diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/05-historial-medico.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/05-historial-medico.md deleted file mode 100644 index 481cf0d..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/pet-owner/05-historial-medico.md +++ /dev/null @@ -1,79 +0,0 @@ -# Ver Historial Medico - -## Tipo de usuario -Dueno de mascota (registrado, con visitas completadas) - -## Donde empieza -Dashboard -> Mis mascotas -> Mascota -> "Ver historial" o Mis turnos -> Turno completado -> "Ver informe" - -## Que quiere hacer el usuario -Ver los informes medicos de las visitas veterinarias de su mascota - -## Pasos - Desde mascotas - -1. Ir a "Mis mascotas" -2. Seleccionar la mascota -3. Ver seccion "Historial de visitas" -4. Lista de visitas ordenadas por fecha (mas reciente primero) -5. Click en una visita para ver detalle - -## Pasos - Desde turnos - -1. Ir a "Mis turnos" -2. Filtrar por "Completados" -3. Click en un turno completado -4. Click en "Ver informe medico" - -## Que muestra el informe - -- Fecha de la visita -- Veterinario que atendio -- Motivo de consulta / servicios realizados -- Examen fisico: - - Peso registrado - - Temperatura - - Frecuencia cardiaca - - Observaciones generales -- Diagnostico -- Tratamiento indicado -- Medicamentos recetados (nombre, dosis, frecuencia, duracion) -- Estudios solicitados -- Proximos pasos / seguimiento recomendado -- Observaciones adicionales - -## Que deberia pasar - -- Usuario puede ver todos los informes de sus mascotas -- Puede descargar/imprimir el informe -- Informacion clara y entendible - -## Problemas comunes - -- Visita completada pero informe no cargado todavia -- Usuario quiere editar algo del informe (no puede, es del vet) -- Terminologia medica confusa -- No encuentra informe de visita antigua -- Quiere compartir informe con otro veterinario - -## Casos especiales - -- Visita sin informe: vet no lo cargo (deberia ser obligatorio) -- Informe incompleto: campos vacios -- Multiples visitas mismo dia: mostrar todas -- Mascota transferida de otro dueno: historial previo? -- Estudios pendientes: mostrar estado - -## Flujos relacionados - -- Gestion de mascotas -- Reservar turno de seguimiento -- Contactar veterinario (dudas sobre informe) - -## Notas tecnicas - -- APIs: - - `GET /mascotas/api/v1/vet-visits/?pet={id}` (listar visitas) - - `GET /mascotas/api/v1/vet-visits/{id}/` (detalle visita) - - `GET /mascotas/api/v1/vetvisitreport/?vet_visit={id}` (informe) -- PDF: generado on-demand o pre-generado? -- Permisos: solo dueno de la mascota puede ver diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md deleted file mode 100644 index bdf93be..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/01-aceptar-solicitud.md +++ /dev/null @@ -1,77 +0,0 @@ -# Aceptar/Rechazar Solicitud de Servicio - -## Tipo de usuario -Veterinario - -## Donde empieza -Dashboard veterinario `/vet/` -> Seccion "Solicitudes pendientes" - -## Que quiere hacer el usuario -Revisar las solicitudes de servicio en su zona y decidir si las acepta o rechaza - -## Pasos - Aceptar solicitud - -1. Ingresar al dashboard veterinario -2. Ver listado de solicitudes pendientes en su zona de cobertura -3. Click en una solicitud para ver detalle: - - Datos del dueno (nombre, telefono, direccion) - - Datos de la mascota (nombre, tipo, edad, historial previo) - - Servicios solicitados - - Fechas preferidas por el dueno -4. Verificar disponibilidad en esas fechas -5. Click en "Aceptar" -6. Seleccionar fecha y hora disponible -7. Confirmar aceptacion - -## Pasos - Rechazar solicitud - -1. Ver detalle de la solicitud -2. Click en "Rechazar" -3. Opcionalmente seleccionar motivo: - - No tengo disponibilidad - - Fuera de mi zona - - No realizo este servicio - - Otro -4. Confirmar rechazo - -## Que deberia pasar - -Al aceptar: -- Solicitud pasa a estado "Coordinado" -- Vet queda asignado a la solicitud -- Dueno recibe notificacion con fecha/hora y datos del vet -- Aparece en el calendario del vet - -Al rechazar: -- Solicitud sigue disponible para otros vets -- Si la rechazo yo, ya no la veo en mi lista - -## Problemas comunes - -- Multiples vets aceptan la misma solicitud casi simultaneamente -- El vet acepta pero el dueno no puede en esa fecha -- No hay suficientes vets en la zona y la solicitud queda sin atender -- Vet acepta por error y quiere deshacer -- Las fechas preferidas ya pasaron - -## Casos especiales - -- Solicitud con mascota que el vet ya atendio antes: mostrar historial -- Solicitud urgente/emergencia: destacar visualmente -- Vet con agenda completa: no deberia poder aceptar -- Solicitud con multiples mascotas: revisar tiempo necesario -- Zona limite: solicitud que aparece para vets de zonas adyacentes - -## Flujos relacionados - -- Ver mi agenda (despues de aceptar) -- Realizar visita -- Cancelar visita aceptada - -## Notas tecnicas - -- APIs: - - `GET /solicitudes/api/v1/service-requests/?state=pending&veterinarian_area=X` - - `PATCH /solicitudes/api/v1/service-requests/{id}/` (aceptar/rechazar) -- Race condition: backend debe manejar caso de aceptacion simultanea -- Push notification al dueno cuando se acepta diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md deleted file mode 100644 index c02d669..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/02-gestion-agenda.md +++ /dev/null @@ -1,87 +0,0 @@ -# Gestion de Agenda - -## Tipo de usuario -Veterinario - -## Donde empieza -Dashboard veterinario `/vet/` -> Menu "Mi agenda" o `/vet/turnos` - -## Que quiere hacer el usuario -Ver su calendario de visitas, gestionar disponibilidad y marcar dias no disponibles - -## Pasos - Ver agenda - -1. Ir a "Mi agenda" -2. Ver calendario con: - - Visitas confirmadas (con detalle al hacer click) - - Dias/horarios bloqueados - - Disponibilidad por franja horaria -3. Navegar entre semanas/meses -4. Filtrar por estado (pendientes, confirmadas, completadas) - -## Pasos - Configurar disponibilidad semanal - -1. Ir a "Configuracion" -> "Mi disponibilidad" -2. Para cada dia de la semana: - - Marcar si trabajo ese dia - - Definir franja horaria (ej: 9:00-18:00) - - Definir pausa (ej: 13:00-14:00) -3. Guardar configuracion - -## Pasos - Bloquear dias especificos - -1. En el calendario, click en un dia -2. Seleccionar "Marcar como no disponible" -3. Opcionalmente agregar motivo (vacaciones, curso, etc.) -4. Confirmar -5. Ese dia no aparece como opcion para nuevas solicitudes - -## Pasos - Ver detalle de visita - -1. Click en una visita en el calendario -2. Ver informacion completa: - - Dueno: nombre, telefono, direccion - - Mascota: nombre, tipo, edad, foto - - Servicios a realizar - - Historial previo de esa mascota - - Estado de pago - - Notas especiales - -## Que deberia pasar - -- Calendario siempre actualizado con visitas -- No puedo recibir solicitudes en dias bloqueados -- Cambios de disponibilidad afectan solo solicitudes futuras -- Puedo ver toda la informacion necesaria para la visita - -## Problemas comunes - -- Vet olvida desbloquear dia despues de terminar vacaciones -- Visitas muy seguidas en zonas distantes (logistica) -- Vet quiere mover visita a otro horario -- Visita cancelada a ultimo momento -- Vet no marco indisponibilidad y le llegan solicitudes que no puede atender - -## Casos especiales - -- Visita reprogramada: mostrar historial de cambios -- Visita cancelada por el dueno: liberar espacio en agenda -- Emergencia: poder agregar visita fuera de horario normal -- Feriados: bloquear automaticamente? -- Multiples visitas mismo dia/zona: optimizar ruta? - -## Flujos relacionados - -- Aceptar solicitud (agrega a la agenda) -- Realizar visita (inicia desde la agenda) -- Gestionar zonas de cobertura - -## Notas tecnicas - -- APIs: - - `GET /mascotas/api/v1/vet-visits/?veterinarian=X` - - `GET /mascotas/api/v1/vet_availabilities/` - - `PUT /mascotas/api/v1/vet_availabilities/{id}/` - - `POST /mascotas/api/v1/vet_unavailabilities/` (dias bloqueados) -- Vista calendario: usar libreria tipo FullCalendar -- Colores por estado: pendiente (amarillo), pagado (verde), completado (azul) diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md deleted file mode 100644 index d3eeb28..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/03-realizar-visita.md +++ /dev/null @@ -1,97 +0,0 @@ -# Realizar Visita y Crear Informe - -## Tipo de usuario -Veterinario - -## Donde empieza -Agenda del dia -> Visita programada -> "Iniciar visita" o `/vet/visita/{id}` - -## Que quiere hacer el usuario -Registrar la visita medica, crear el informe clinico y completar el flujo - -## Pasos - Antes de la visita - -1. Ver agenda del dia -2. Click en la visita programada -3. Ver datos del dueno (direccion, telefono) para llegar -4. Ver datos de la mascota e historial -5. Preparar insumos segun servicios solicitados - -## Pasos - Iniciar visita - -1. Al llegar al domicilio, click "Iniciar visita" -2. Estado cambia a "En progreso" -3. Se registra hora de inicio - -## Pasos - Crear informe medico - -1. Durante o despues de la atencion, ir a "Crear informe" -2. Completar examen fisico: - - Peso actual - - Temperatura - - Frecuencia cardiaca - - Frecuencia respiratoria - - Estado general (mucosas, hidratacion, etc.) -3. Registrar hallazgos clinicos -4. Escribir diagnostico -5. Indicar tratamiento: - - Descripcion del tratamiento - - Medicamentos recetados (nombre, dosis, frecuencia, duracion) - - Indicaciones especiales -6. Solicitar estudios si es necesario: - - Tipo de estudio - - Laboratorio sugerido -7. Programar seguimiento: - - Fecha sugerida de control - - Motivo del seguimiento -8. Agregar observaciones adicionales -9. Guardar informe - -## Pasos - Completar visita - -1. Verificar que el informe este guardado -2. Click en "Completar visita" -3. Confirmar finalizacion -4. Se genera factura automaticamente (AFIP) -5. Dueno recibe notificacion con acceso al informe - -## Que deberia pasar - -- Informe queda guardado y asociado a la mascota -- Dueno puede ver el informe desde su cuenta -- Se genera factura electronica -- Visita se marca como "Completada" -- Vet puede ver la visita en su historial - -## Problemas comunes - -- Vet olvida completar el informe y cierra la visita -- Mala conexion a internet durante la visita -- Informe muy largo y se pierde por timeout -- Dueno no esta en el domicilio -- Mascota agresiva, no se puede atender -- Necesita derivar a clinica - -## Casos especiales - -- Visita cancelada in situ: registrar motivo, posible cargo -- Emergencia durante visita rutinaria: agregar servicios -- Multiples mascotas en la visita: un informe por cada una -- Vet necesita segundo opinion: derivar a especialista -- Fallecimiento durante la visita: protocolo especial - -## Flujos relacionados - -- Ver historial de paciente (antes de atender) -- Cobro de la visita (Mercado Pago del vet) -- Seguimiento post-visita - -## Notas tecnicas - -- APIs: - - `PATCH /mascotas/api/v1/vet-visits/{id}/` (cambiar estado) - - `POST /mascotas/api/v1/vetvisitreport/` (crear informe) - - `GET /mascotas/api/v1/vet-visits/?pet=X` (historial previo) -- Integracion AFIP via django_afip -- Guardado automatico del informe cada 30 segundos -- Modo offline: guardar localmente y sincronizar despues diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md deleted file mode 100644 index 7601d92..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/04-zonas-cobertura.md +++ /dev/null @@ -1,66 +0,0 @@ -# Gestionar Zonas de Cobertura - -## Tipo de usuario -Veterinario - -## Donde empieza -Dashboard veterinario -> Menu "Mi cobertura" o Perfil -> "Zonas de atencion" - -## Que quiere hacer el usuario -Definir en que barrios/zonas esta dispuesto a atender visitas a domicilio - -## Pasos - Ver zonas actuales - -1. Ir a "Mi cobertura" -2. Ver mapa con zonas actualmente cubiertas -3. Ver listado de barrios seleccionados - -## Pasos - Agregar zona - -1. En el mapa o listado, buscar el barrio -2. Click en el barrio para seleccionarlo -3. El barrio se agrega a mi cobertura -4. Guardar cambios - -## Pasos - Quitar zona - -1. En mi listado de zonas -2. Click en "X" o deseleccionar el barrio -3. Confirmar eliminacion -4. Guardar cambios - -## Que deberia pasar - -- Solo recibo solicitudes de las zonas que seleccione -- Si agrego zona, empiezo a ver solicitudes pendientes de esa zona -- Si quito zona, dejo de ver nuevas solicitudes de esa zona -- Visitas ya aceptadas no se afectan - -## Problemas comunes - -- Vet quiere zona muy especifica (solo algunas calles) -- Zonas con poca demanda, vet no recibe solicitudes -- Zonas con mucha demanda, vet saturado -- Limites de zona confusos (calle pertenece a 2 barrios) -- Vet quiere cobertura condicional (solo ciertos dias) - -## Casos especiales - -- Zona nueva no listada: solicitar a ops que la agregue -- Cobertura temporal (ej: cubriendo a otro vet) -- Cobertura por horario (mañana en zona A, tarde en zona B) -- Vet se muda: actualizar todas las zonas - -## Flujos relacionados - -- Aceptar solicitud (solo veo solicitudes de mis zonas) -- Ver agenda (optimizar ruta por zonas) - -## Notas tecnicas - -- APIs: - - `GET /common/api/v1/neighborhoods/` (lista de barrios) - - `GET /mascotas/api/v1/veterinarians/{id}/` (zonas actuales) - - `PATCH /mascotas/api/v1/veterinarians/{id}/` (actualizar zonas) -- Modelo: Veterinarian tiene M2M con Neighborhood -- Coeficiente de distancia por barrio para calcular viabilidad diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md deleted file mode 100644 index 0944535..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/feature-form/veterinarian/05-historial-pacientes.md +++ /dev/null @@ -1,81 +0,0 @@ -# Ver Historial de Pacientes - -## Tipo de usuario -Veterinario - -## Donde empieza -Dashboard veterinario -> "Historia clinica" o `/vet/historia-clinica` - -## Que quiere hacer el usuario -Buscar y revisar el historial medico de pacientes que ha atendido - -## Pasos - Buscar paciente - -1. Ir a "Historia clinica" -2. Usar el buscador: - - Por nombre del dueno - - Por nombre de la mascota - - Por telefono - - Por email -3. Ver resultados de busqueda -4. Click en el paciente deseado - -## Pasos - Ver historial de mascota - -1. Seleccionar la mascota -2. Ver ficha de la mascota: - - Datos basicos (nombre, tipo, raza, edad) - - Foto - - Peso historico - - Estado de vacunacion -3. Ver listado de visitas (todas, no solo las mias) -4. Click en una visita para ver el informe completo - -## Pasos - Revisar informe anterior - -1. En el listado de visitas, click en una -2. Ver informe completo: - - Fecha y veterinario que atendio - - Examen fisico - - Diagnostico - - Tratamiento - - Medicamentos indicados - - Estudios solicitados - - Seguimiento - -## Que deberia pasar - -- Puedo ver historial completo de pacientes que atendi -- Puedo ver informes de otros vets (para continuidad de atencion) -- Tengo contexto para tomar decisiones clinicas - -## Problemas comunes - -- Busqueda no encuentra al paciente (nombre mal escrito) -- Mascota atendida por otro vet, no tengo acceso? -- Historial muy extenso, dificil encontrar info relevante -- Informes anteriores incompletos o ilegibles -- Duplicados de pacientes (mismo dueno registrado 2 veces) - -## Casos especiales - -- Mascota nueva sin historial -- Mascota atendida en otra clinica (historial externo) -- Dueno tiene multiples mascotas -- Mascota transferida a otro dueno -- Paciente de urgencia/emergencia sin cita previa - -## Flujos relacionados - -- Realizar visita (consultar historial antes/durante) -- Aceptar solicitud (ver si ya lo atendi antes) - -## Notas tecnicas - -- APIs: - - `GET /mascotas/api/v1/pet-owners/?search=X` - - `GET /mascotas/api/v1/pets/?pet_owner=X` - - `GET /mascotas/api/v1/vet-visits/?pet=X` - - `GET /mascotas/api/v1/vetvisitreport/?vet_visit=X` -- Permisos: vet puede ver historial de cualquier paciente que haya atendido -- Busqueda: usar indice de texto completo para rendimiento diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/index.html b/cfg/amar/soleprint/atlas/books/feature-form-samples/index.html deleted file mode 100644 index bc91620..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/index.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - Ops Templates Sample - Album - - - -
-
- -

Ops Templates Sample

-

User flow documentation templates - non-technical format for support/ops teams

- Sample Data -
- - - - - - - -
-

Template Structure

-

Each template includes: User type, Entry point, Goal, Steps, Expected result, Common problems, Edge cases, Related flows, Technical notes.

-

These templates transform into Gherkin specs for testing.

-
- - -
- - diff --git a/cfg/amar/soleprint/atlas/books/feature-form-samples/template/feature-form.md b/cfg/amar/soleprint/atlas/books/feature-form-samples/template/feature-form.md deleted file mode 100644 index de3c18c..0000000 --- a/cfg/amar/soleprint/atlas/books/feature-form-samples/template/feature-form.md +++ /dev/null @@ -1,91 +0,0 @@ -# Plantilla: Documentacion de Flujos - -## Para: Equipo de Soporte/Operaciones - -Usa esta plantilla para documentar cualquier accion/flujo del sistema. - ---- - -## Plantilla - -``` -### [Nombre del Flujo] - -**Tipo de usuario:** [Dueno de mascota / Veterinario / Admin] - -**Donde empieza:** [Que pagina/boton/link] - -**Que quiere hacer el usuario:** [Objetivo en una oracion] - -**Pasos:** -1. [Primera cosa que hace el usuario] -2. [Segunda cosa que hace el usuario] -3. [etc.] - -**Que deberia pasar:** [Resultado esperado cuando todo funciona] - -**Problemas comunes:** -- [Problema 1] -- [Problema 2] - -**Casos especiales:** -- [Caso especial 1] -- [Caso especial 2] - -**Flujos relacionados:** [Otros flujos que se conectan con este] -``` - ---- - -## Ejemplo Completo - -### Reservar turno de vacunacion - -**Tipo de usuario:** Dueno de mascota - -**Donde empieza:** Pagina principal -> Boton "Agendar visita" - -**Que quiere hacer el usuario:** Reservar un turno de vacunacion para su gato - -**Pasos:** -1. Click en "Agendar visita" en la pagina principal -2. Ingresar direccion en el popup -3. Click en "Siguiente" -4. Completar datos mascota: nombre, tipo (gato), edad -5. Seleccionar categoria "Vacunacion" -6. Elegir la vacuna especifica -7. Ver que "Consulta clinica" se agrega automaticamente -8. Elegir fechas preferidas -9. Ingresar datos de contacto (nombre, telefono, email) -10. Enviar la solicitud - -**Que deberia pasar:** -- Ver mensaje de confirmacion -- Recibir email de confirmacion -- La solicitud aparece en "Mis turnos" con estado "Pendiente" - -**Problemas comunes:** -- Los usuarios intentan quitar "Consulta clinica" y no pueden -- El autocompletado de direcciones a veces no encuentra su calle -- Algunos usuarios no ven el link "Ya soy cliente" y crean cuentas duplicadas - -**Casos especiales:** -- Si la mascota ya esta castrada, el servicio de castracion no deberia aparecer -- Si el usuario abandona a mitad del flujo, sus datos se guardan como "invitado" -- Algunos barrios no tienen cobertura - deberia mostrar un error claro - -**Flujos relacionados:** -- "Ver mis turnos" (para ver el estado despues de reservar) -- "Pagar turno" (cuando se solicita el pago) - ---- - -## Preguntas a Considerar - -1. **Camino feliz:** Que pasa cuando todo funciona perfecto? -2. **Validacion:** Que errores puede ver el usuario? -3. **Permisos:** Quien puede hacer esto? -4. **Estados:** Esta accion cambia segun algun estado? -5. **Dependencias:** Esto requiere que algo mas haya pasado antes? -6. **Efectos secundarios:** Esto dispara emails, notificaciones? -7. **Deshacer:** El usuario puede revertir esta accion? diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/CLAUDE.md b/cfg/amar/soleprint/atlas/books/gherkin-samples/CLAUDE.md deleted file mode 100644 index 1b58a54..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/CLAUDE.md +++ /dev/null @@ -1,114 +0,0 @@ -# Gherkin Sample Book - -## Purpose -**SAMPLE DATA** - Example BDD/Gherkin `.feature` files for demonstration and testing purposes. These show realistic examples of how feature files will look when actual ops templates are defined. - -Use these to: -- Understand Gherkin structure and patterns -- Compare Spanish vs English keywords -- Test tooling (behave, pytest-bdd, playwright-bdd) -- Demo the feature-flow pipeline - -Real feature files will be derived from validated ops templates. - -## Structure - -``` -gherkin-sample/ -├── es/ # Spanish keywords (Dado/Cuando/Entonces) -│ ├── pet-owner/ -│ ├── veterinarian/ -│ └── backoffice/ -└── en/ # English keywords (Given/When/Then) - ├── pet-owner/ - ├── veterinarian/ - └── backoffice/ -``` - -## Language Versions - -**Spanish Keywords (`es/`)** -- Uses `# language: es` directive -- Keywords: Característica, Escenario, Dado, Cuando, Entonces, Y, Pero -- Keywords: Esquema del escenario, Ejemplos, Antecedentes, Regla -- For teams preferring Spanish BDD syntax - -**English Keywords (`en/`)** -- Standard Gherkin syntax -- Keywords: Feature, Scenario, Given, When, Then, And, But -- Keywords: Scenario Outline, Examples, Background, Rule -- Same Spanish content, different keywords - -## Feature Files - -### Pet Owner (5 files) -| File | Description | -|------|-------------| -| 01-registro.feature | User registration flow | -| 02-reservar-turno.feature | Book appointment (turnero) | -| 03-gestion-mascotas.feature | Pet management | -| 04-pago-turno.feature | Payment flow | -| 05-historial-medico.feature | Medical history | - -### Veterinarian (5 files) -| File | Description | -|------|-------------| -| 01-aceptar-solicitud.feature | Accept/reject requests | -| 02-gestion-agenda.feature | Schedule management | -| 03-realizar-visita.feature | Conduct visit & report | -| 04-zonas-cobertura.feature | Coverage area management | -| 05-historial-pacientes.feature | Patient history | - -### Backoffice (5 files) -| File | Description | -|------|-------------| -| 01-gestion-solicitudes.feature | Request management | -| 02-gestion-usuarios.feature | User management | -| 03-gestion-servicios.feature | Services & pricing | -| 04-reembolsos.feature | Refund process | -| 05-reportes.feature | Reports & dashboard | - -## Gherkin Patterns Used - -### Basic -- Feature, Scenario, Given/When/Then - -### Intermediate -- **Background**: Shared setup for all scenarios -- **Scenario Outline + Examples**: Test variations without duplication -- **Data Tables**: Multiple items in one step - -### Advanced -- **Rule**: Group related scenarios -- **Doc Strings**: Large text blocks (diagnoses, reports) -- **Tags**: For filtering (@smoke, @critical, @wip) - -## Comment Headers - -Each file includes metadata comments: -```gherkin -# Fuente: album/book/ops-templates/... -# Drive: [Google Drive reference] -# Tests Backend: pytest tests/contracts/... -# Tests Frontend: npx playwright test ... -``` - -## Tools - -To run/validate these files: - -**Python** -- behave -- pytest-bdd - -**JavaScript/TypeScript** -- playwright-bdd -- jest-cucumber - -**IDE Support** -- VS Code: Cucumber (Gherkin) Full Support extension -- JetBrains: Built-in Gherkin plugin - -## Source Templates - -Sample ops templates at: `album/book/ops-templates-sample/` diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/detail.html b/cfg/amar/soleprint/atlas/books/gherkin-samples/detail.html deleted file mode 100644 index 4752009..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/detail.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - {{ filename }} - Gherkin - - - - - -
-
- -

{{ filename.replace('.feature', '').replace('-', ' ').title() }}

-
- {{ 'Espanol' if lang == 'es' else 'English' }} - {{ user_type }} - Sample -
-
- -
-
- {{ filename }} - -
-
{{ content }}
-
- - - - -
- - - - - - - diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature deleted file mode 100644 index 72c1400..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/01-gestion-solicitudes.feature +++ /dev/null @@ -1,104 +0,0 @@ -# Fuente: album/book/ops-templates/backoffice/01-gestion-solicitudes.md -# Drive: 05. ATC - Operaciones/Procedimientos -# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py -# Tests Frontend: npx playwright test admin-requests.spec.ts - -Feature: Gestion de solicitudes de servicio - - Como administrador del backoffice - Quiero gestionar todas las solicitudes del sistema - Para asegurar que se atiendan correctamente - - Background: - Given que estoy logueado como administrador - And estoy en la seccion "Solicitudes" del backoffice - - # ============================================ - # VER Y FILTRAR SOLICITUDES - # ============================================ - - Scenario: Ver listado de todas las solicitudes - When cargo la pagina de solicitudes - Then deberia ver un listado paginado - And cada solicitud deberia mostrar: - | campo | - | ID | - | Dueno | - | Mascota | - | Servicios | - | Estado | - | Fecha | - - Scenario Outline: Filtrar solicitudes por estado - When filtro por estado "" - Then solo deberia ver solicitudes con estado "" - - Examples: - | estado | - | Pendiente | - | Coordinado | - | Pagado | - | Completado | - | Cancelado | - - # ============================================ - # ASIGNAR VETERINARIO MANUALMENTE - # ============================================ - - Scenario: Asignar veterinario a solicitud pendiente - Given que hay una solicitud pendiente en "Palermo" - And no fue aceptada por ningun veterinario - When abro el detalle de la solicitud - And hago click en "Asignar veterinario" - Then deberia ver lista de veterinarios con cobertura en Palermo - - When selecciono "Dra. Garcia" - And selecciono fecha "15 de enero" hora "10:00" - And confirmo la asignacion - Then la solicitud deberia pasar a estado "Coordinado" - And deberia estar asignada a Dra. Garcia - And el dueno deberia recibir notificacion - And el veterinario deberia recibir notificacion - - # ============================================ - # CAMBIAR ESTADO MANUALMENTE - # ============================================ - - Scenario: Cambiar estado de solicitud - Given que hay una solicitud en estado "Coordinado" - And el pago se proceso pero el webhook fallo - When abro la solicitud - And hago click en "Cambiar estado" - And selecciono "Pagado" - And ingreso motivo "Pago confirmado manualmente - ID MP: 12345" - And confirmo el cambio - Then la solicitud deberia pasar a "Pagado" - And el cambio deberia registrarse en el historial - - Rule: Solo ciertos cambios de estado son validos - - Scenario: No puedo volver a estado anterior - Given que hay una solicitud en estado "Completado" - When intento cambiar el estado a "Pagado" - Then deberia ver error "No se puede volver a un estado anterior" - - Scenario: Puedo cancelar desde cualquier estado - Given que hay una solicitud en estado "Coordinado" - When cambio el estado a "Cancelado" - And ingreso motivo de cancelacion - Then la solicitud deberia cancelarse - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Reasignar veterinario - Given que hay una solicitud asignada a "Dr. Lopez" - And Dr. Lopez no puede asistir - When abro la solicitud - And hago click en "Reasignar" - And selecciono otro veterinario - And confirmo - Then Dr. Lopez deberia ser notificado de la desasignacion - And el nuevo vet deberia ser notificado - And el dueno deberia ser notificado del cambio diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/02-gestion-usuarios.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/02-gestion-usuarios.feature deleted file mode 100644 index 75224f2..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/02-gestion-usuarios.feature +++ /dev/null @@ -1,88 +0,0 @@ -# Fuente: album/book/ops-templates/backoffice/02-gestion-usuarios.md -# Drive: 05. ATC - Operaciones/Procedimientos -# Tests Backend: pytest tests/contracts/mascotas/test_pet_owners.py -# Tests Frontend: npx playwright test admin-users.spec.ts - -Feature: Gestion de usuarios - - Como administrador del backoffice - Quiero gestionar duenos y veterinarios - Para mantener la base de usuarios actualizada - - Background: - Given que estoy logueado como administrador - And estoy en el backoffice - - # ============================================ - # GESTION DE DUENOS - # ============================================ - - Scenario: Buscar dueno de mascota - Given que estoy en la seccion "Duenos" - When busco "maria@ejemplo.com" - Then deberia ver a Maria Garcia en los resultados - And deberia ver sus mascotas listadas - - Scenario: Ver perfil completo de dueno - Given que encontre a "Maria Garcia" - When hago click en su perfil - Then deberia ver: - | seccion | contenido | - | Datos personales | Nombre, email, telefono | - | Direcciones | Direcciones registradas | - | Mascotas | Lista de mascotas | - | Historial | Solicitudes anteriores | - - Scenario: Editar datos de dueno - Given que estoy viendo el perfil de un dueno - When hago click en "Editar" - And cambio el telefono a "1155559999" - And guardo los cambios - Then el telefono deberia actualizarse - - # ============================================ - # GESTION DE VETERINARIOS - # ============================================ - - Scenario: Agregar nuevo veterinario - Given que estoy en la seccion "Veterinarios" - When hago click en "Agregar veterinario" - And completo los datos: - | campo | valor | - | Nombre | Dr. Juan Perez | - | Matricula | MV-12345 | - | Email | jperez@ejemplo.com | - | Telefono | 1155551234 | - And selecciono zonas de cobertura: - | zona | - | Palermo | - | Recoleta | - And genero credenciales de acceso - And guardo - Then deberia crearse el veterinario - And deberia poder loguearse con sus credenciales - - Scenario: Intentar desactivar veterinario con citas pendientes - Given que el veterinario "Dra. Garcia" tiene 3 citas pendientes - When intento desactivarla - Then deberia ver advertencia "Tiene 3 citas pendientes" - And deberia ver opciones: - | opcion | - | Reasignar citas y desactivar | - | Cancelar | - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Fusionar cuentas duplicadas - Given que hay dos cuentas para el mismo dueno: - | cuenta | email | mascotas | - | Cuenta1 | maria@gmail.com | Luna | - | Cuenta2 | maria@hotmail.com | Rocky | - When selecciono ambas cuentas - And hago click en "Fusionar" - And elijo Cuenta1 como principal - And confirmo - Then deberia existir solo Cuenta1 - And deberia tener ambas mascotas diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/03-gestion-servicios.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/03-gestion-servicios.feature deleted file mode 100644 index 2cf27a0..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/03-gestion-servicios.feature +++ /dev/null @@ -1,91 +0,0 @@ -# Fuente: album/book/ops-templates/backoffice/03-gestion-servicios.md -# Drive: 08. IT y Producto/Catalogo Servicios -# Tests Backend: pytest tests/contracts/productos/test_services.py -# Tests Frontend: npx playwright test admin-services.spec.ts - -Feature: Gestion de servicios y precios - - Como administrador - Quiero gestionar el catalogo de servicios - Para mantener la oferta actualizada - - Background: - Given que estoy logueado como administrador - And estoy en la seccion "Servicios" - - # ============================================ - # VER CATALOGO DE SERVICIOS - # ============================================ - - Scenario: Ver listado de servicios - When cargo la pagina de servicios - Then deberia ver todos los servicios organizados por categoria - And cada servicio deberia mostrar: - | campo | - | Nombre | - | Categoria | - | Tipo mascota | - | Precio actual | - | Estado | - - # ============================================ - # CREAR Y EDITAR SERVICIOS - # ============================================ - - Scenario: Agregar nuevo servicio - When hago click en "Agregar servicio" - And completo los datos: - | campo | valor | - | Nombre | Vacuna Quintuple Felina | - | Descripcion | Protege contra 5 enfermedades | - | Categoria | Vacunacion | - | Tipo mascota | Gato | - | Precio | 15000 | - And guardo el servicio - Then el servicio deberia crearse - And deberia aparecer en el turnero para gatos - - Scenario: Desactivar servicio - Given que existe el servicio "Servicio Antiguo" - When abro el servicio - And hago click en "Desactivar" - And confirmo - Then el servicio no deberia aparecer en el turnero - - # ============================================ - # GESTION DE PRECIOS - # ============================================ - - Scenario: Actualizar precio de servicio - Given que "Consulta clinica" tiene precio actual de 10000 - When abro el servicio - And voy a la seccion "Precios" - And hago click en "Agregar precio" - And ingreso nuevo precio 12000 - And selecciono fecha de vigencia "01/02/2024" - And guardo - Then deberia crearse el nuevo precio - And el precio anterior deberia quedar en historial - - Rule: El precio se congela al crear la solicitud - - Scenario: Cambio de precio no afecta solicitudes existentes - Given que hay una solicitud pendiente con "Consulta clinica" a 10000 - When cambio el precio de "Consulta clinica" a 12000 - Then la solicitud deberia mantener el precio de 10000 - - # ============================================ - # CONFIGURAR COMBOS - # ============================================ - - Scenario: Crear combo de servicios - When voy a "Combos" - And hago click en "Agregar combo" - And configuro: - | campo | valor | - | Nombre | Plan Preventivo Felino | - | Servicios | Vacunacion, Desparasitacion | - | Precio combo | 25000 | - And guardo - Then el combo deberia crearse - And deberia aplicarse automaticamente en el turnero diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/04-reembolsos.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/04-reembolsos.feature deleted file mode 100644 index 118b2af..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/04-reembolsos.feature +++ /dev/null @@ -1,86 +0,0 @@ -# Fuente: album/book/ops-templates/backoffice/04-reembolsos.md -# Drive: 07. Finanzas y contabilidad/Reembolsos -# Tests Backend: pytest tests/contracts/payments/test_refunds.py -# Tests Frontend: npx playwright test admin-refunds.spec.ts - -Feature: Proceso de reembolso - - Como administrador - Quiero procesar reembolsos de pagos - Para resolver cancelaciones y problemas - - Background: - Given que estoy logueado como administrador - And estoy en el backoffice - - # ============================================ - # PROCESAR REEMBOLSO - # ============================================ - - Scenario: Reembolso total exitoso - Given que hay una solicitud pagada por 15000 - And la visita fue cancelada - When abro la solicitud - And hago click en "Procesar reembolso" - And selecciono "Reembolso total" - And ingreso motivo "Cancelacion por indisponibilidad del veterinario" - And confirmo el reembolso - Then deberia enviarse la solicitud de reembolso a Mercado Pago - And deberia ver mensaje "Reembolso en proceso" - And el dueno deberia recibir email de confirmacion - - Scenario: Reembolso parcial - Given que hay una solicitud pagada por 20000 - And solo se realizo parte del servicio - When proceso reembolso parcial por 10000 - And ingreso motivo "Servicio parcialmente completado" - And confirmo - Then deberia procesarse reembolso por 10000 - And deberia quedar registro del monto reembolsado - - # ============================================ - # TIEMPOS DE ACREDITACION - # ============================================ - - Scenario Outline: Informar tiempo de acreditacion segun metodo - Given que el pago original fue con "" - When proceso el reembolso - Then deberia informar al usuario: - """ - El reembolso se acreditara en - """ - - Examples: - | metodo | tiempo | - | Tarjeta credito | 1-2 resumenes de cuenta | - | Tarjeta debito | 5-10 dias habiles | - | Dinero en cuenta MP | forma inmediata | - - # ============================================ - # VALIDACIONES - # ============================================ - - Scenario: No puedo reembolsar mas del monto pagado - Given que hay una solicitud pagada por 15000 - When intento reembolsar 20000 - Then deberia ver error "El monto supera el pago original" - - Scenario: Reembolso duplicado - Given que ya procese un reembolso total para una solicitud - When intento procesar otro reembolso - Then deberia ver error "Esta solicitud ya fue reembolsada" - - Scenario: Pago fuera de plazo de reembolso - Given que hay un pago de hace 200 dias - When intento reembolsar - Then deberia ver advertencia "Fuera de plazo de reembolso de MP (180 dias)" - - # ============================================ - # SEGUIMIENTO - # ============================================ - - Scenario: Ver estado del reembolso - Given que procese un reembolso hace 2 dias - When veo el detalle del reembolso - Then deberia ver el estado actual en Mercado Pago - And deberia ver historial de estados diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/05-reportes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/05-reportes.feature deleted file mode 100644 index 43032a0..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/backoffice/05-reportes.feature +++ /dev/null @@ -1,97 +0,0 @@ -# Fuente: album/book/ops-templates/backoffice/05-reportes.md -# Drive: 07. Finanzas y contabilidad/Reportes -# Tests Backend: pytest tests/contracts/mascotas/test_stats.py -# Tests Frontend: npx playwright test admin-reports.spec.ts - -Feature: Reportes y dashboard - - Como administrador o gerente - Quiero ver metricas y generar reportes - Para tomar decisiones informadas - - Background: - Given que estoy logueado como administrador - And estoy en el backoffice - - # ============================================ - # DASHBOARD - # ============================================ - - Scenario: Ver dashboard principal - When accedo al dashboard - Then deberia ver metricas resumidas: - | metrica | periodo | - | Solicitudes nuevas | Hoy | - | Visitas completadas | Semana | - | Ingresos | Mes | - | Veterinarios activos | Actual | - | Tasa de conversion | Mes | - - Scenario: Ver grafico de tendencia - When veo el grafico de solicitudes - Then deberia ver la evolucion de los ultimos 30 dias - And deberia poder comparar con periodo anterior - - # ============================================ - # REPORTE DE SOLICITUDES - # ============================================ - - Scenario: Generar reporte de solicitudes - When voy a "Reportes" -> "Solicitudes" - And selecciono periodo "Enero 2024" - And hago click en "Generar" - Then deberia ver tabla con solicitudes del periodo - And deberia ver totales por estado - - Scenario: Filtrar reporte por multiples criterios - When genero reporte con filtros: - | filtro | valor | - | Periodo | Enero 2024 | - | Zona | Palermo | - | Veterinario | Dra. Garcia | - Then deberia ver solo solicitudes que cumplan todos los criterios - - # ============================================ - # REPORTE DE INGRESOS - # ============================================ - - Scenario: Ver ingresos por periodo - When voy a "Reportes" -> "Ingresos" - And selecciono "Ultimo trimestre" - Then deberia ver: - | dato | - | Ingresos totales | - | Cantidad de pagos | - | Ticket promedio | - | Reembolsos realizados | - | Ingreso neto | - - Scenario: Ingresos agrupados por veterinario - When agrupo el reporte por "Veterinario" - Then deberia ver para cada vet: - | dato | - | Visitas completadas | - | Ingresos generados | - | Porcentaje del total | - - # ============================================ - # EXPORTACION - # ============================================ - - Scenario Outline: Exportar reporte en diferentes formatos - Given que tengo un reporte generado - When hago click en "Exportar" - And selecciono formato "" - Then deberia descargarse el archivo en formato - - Examples: - | formato | - | CSV | - | Excel | - | PDF | - - Scenario: Exportar reporte grande de forma asincrona - Given que genere un reporte con mas de 10000 registros - When hago click en "Exportar" - Then deberia ver mensaje "Generando exportacion..." - And deberia recibir notificacion cuando este listo diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/01-registro.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/01-registro.feature deleted file mode 100644 index 1a97123..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/01-registro.feature +++ /dev/null @@ -1,91 +0,0 @@ -# Fuente: album/book/ops-templates/pet-owner/01-registro.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/common/test_users.py -# Tests Frontend: npx playwright test auth.spec.ts - -Feature: Registro de usuario - - Como visitante de la plataforma - Quiero poder crear una cuenta - Para gestionar mis mascotas y reservar turnos - - # ============================================ - # CAMINO FELIZ - # ============================================ - - Scenario: Registro exitoso con datos validos - Given que estoy en la pagina de registro - When ingreso email "nuevo@ejemplo.com" - And ingreso contraseña "Password123" - And confirmo contraseña "Password123" - And acepto los terminos y condiciones - And hago click en "Crear cuenta" - Then deberia ver mensaje "Te enviamos un email de verificacion" - And deberia recibir email de verificacion - - Scenario: Verificar email y activar cuenta - Given que me registre con email "nuevo@ejemplo.com" - And recibi el email de verificacion - When hago click en el link de verificacion - Then mi cuenta deberia estar activa - And deberia ser redirigido al dashboard - - # ============================================ - # VALIDACIONES - # ============================================ - - Scenario Outline: Registro con datos invalidos - Given que estoy en la pagina de registro - When ingreso email "" - And ingreso contraseña "" - And confirmo contraseña "" - And hago click en "Crear cuenta" - Then deberia ver error "" - - Examples: - | email | password | confirmacion | mensaje_error | - | invalido | Password123 | Password123 | Email invalido | - | test@test.com | 123 | 123 | Contraseña muy corta | - | test@test.com | password | password | Debe contener al menos un numero | - | test@test.com | Password123 | Diferente123 | Las contraseñas no coinciden | - - Scenario: Registro con email ya existente - Given que existe un usuario con email "existente@ejemplo.com" - And estoy en la pagina de registro - When ingreso email "existente@ejemplo.com" - And completo el resto del formulario correctamente - And hago click en "Crear cuenta" - Then deberia ver error "Este email ya esta registrado" - And deberia ver link "Recuperar contraseña" - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Registro linkea con cuenta invitado existente - # Usuario que reservo turno como invitado y ahora quiere registrarse - Given que existe un usuario invitado con email "invitado@ejemplo.com" - And ese usuario tiene una mascota "Luna" registrada - And estoy en la pagina de registro - When me registro con email "invitado@ejemplo.com" - And verifico mi cuenta - Then deberia ver mi mascota "Luna" en el dashboard - And deberia ver mis turnos anteriores - - Scenario: Registro desde flujo de turnero - # Usuario empezo a reservar turno y decide crear cuenta - Given que estoy en el paso final del turnero - And ingrese mis datos de contacto - When hago click en "Crear cuenta para guardar mis datos" - Then deberia ver formulario simplificado - And mi email ya deberia estar pre-llenado - And solo deberia ingresar contraseña - - Scenario: Reenviar email de verificacion - Given que me registre pero no verifique mi cuenta - And estoy en la pagina de login - When intento iniciar sesion - Then deberia ver "Tu cuenta no esta verificada" - And deberia ver boton "Reenviar email" - When hago click en "Reenviar email" - Then deberia recibir nuevo email de verificacion diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/02-reservar-turno.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/02-reservar-turno.feature deleted file mode 100644 index f8ee384..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/02-reservar-turno.feature +++ /dev/null @@ -1,140 +0,0 @@ -# Fuente: album/book/ops-templates/pet-owner/02-reservar-turno.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/workflows/test_turnero_general.py -# Tests Frontend: npx playwright test turnero.spec.ts -# Relacionado: def/work_plan/10-flow-turnero.md - -Feature: Reservar turno veterinario (Turnero) - - Como dueno de mascota - Quiero reservar un turno veterinario a domicilio - Para que atiendan a mi mascota sin salir de casa - - Background: - Given que estoy en la pagina del turnero - - # ============================================ - # VERIFICACION DE COBERTURA - # ============================================ - - Scenario: Verificar cobertura en zona disponible - When ingreso direccion "Av Santa Fe 1234, CABA" - Then deberia ver mensaje "Tenemos cobertura en tu zona" - And deberia poder continuar al siguiente paso - - Scenario: Zona sin cobertura - When ingreso direccion "Calle Principal 100, Ushuaia" - Then deberia ver mensaje "Aun no tenemos cobertura en tu zona" - And deberia ver formulario "Avisame cuando lleguen" - - # ============================================ - # FLUJO COMPLETO POR TIPO DE USUARIO - # ============================================ - - Scenario Outline: Reservar turno como - Given que soy un usuario - And tengo cobertura en mi zona - When completo los datos de mi mascota: - | campo | valor | - | nombre | Luna | - | tipo | Gato | - | edad | 2 años | - | castrada | Si | - And selecciono servicios: - | servicio | - | Vacunacion | - And selecciono fechas preferidas: - | fecha | franja | - | 2024-01-15 | Mañana | - | 2024-01-16 | Tarde | - And completo datos de contacto con email "" - And envio la solicitud - Then deberia crearse una solicitud en estado "Pendiente" - And el dueno deberia ser - And deberia recibir email de confirmacion - - Examples: - | tipo_usuario | email | estado_dueno | - | invitado | nuevo@test.com | creado como invitado | - | registrado | user@test.com | mi cuenta existente | - | recurrente | conocido@test.com | identificado por email | - - # ============================================ - # SELECCION DE SERVICIOS - # ============================================ - - Scenario: Servicios filtrados por tipo de mascota - Given que agregue una mascota tipo "Gato" - When veo los servicios disponibles - Then deberia ver "Vacuna triple felina" - And deberia ver "Vacuna antirabica" - But no deberia ver "Vacuna sextuple canina" - - Scenario: Consulta clinica se agrega automaticamente con vacunacion - Given que estoy seleccionando servicios - When selecciono "Vacunacion" - Then "Consulta clinica" deberia agregarse automaticamente - And deberia ver nota "Incluye revision general" - And no deberia poder quitar "Consulta clinica" - - Scenario: Servicios combo con descuento - Given que estoy seleccionando servicios - When agrego los siguientes servicios: - | servicio | - | Vacunacion | - | Desparasitacion | - | Antipulgas | - Then deberia ver "Plan preventivo completo" - And el total deberia incluir descuento de combo - - Scenario: Castracion no disponible para mascota castrada - Given que mi mascota esta marcada como castrada - When veo los servicios disponibles - Then no deberia ver "Castracion" - - # ============================================ - # DATOS DE CONTACTO Y CUENTA - # ============================================ - - Scenario: Pre-llenado de datos para usuario logueado - Given que estoy logueado como "maria@ejemplo.com" - And tengo registrada mascota "Firulais" - When inicio el flujo de turnero - Then mi direccion deberia estar pre-llenada - And deberia poder seleccionar "Firulais" de mis mascotas - And mis datos de contacto ya deberian estar completos - - Scenario: Detectar usuario existente por email - Given que soy usuario invitado - And existe una cuenta con email "existente@ejemplo.com" - When ingreso email "existente@ejemplo.com" en datos de contacto - Then deberia ver "Ya tenes cuenta con este email" - And deberia ver opciones: - | opcion | - | Iniciar sesion | - | Continuar como invitado | - - # ============================================ - # EDGE CASES - # ============================================ - - Scenario: Usuario abandona flujo a mitad - Given que complete los datos de mascota - And cerre el navegador sin enviar - When vuelvo a la pagina del turnero - Then deberia poder recuperar mi progreso - # Nota: datos guardados en localStorage o session - - Scenario: Multiples mascotas en una solicitud - Given que quiero atender a 2 mascotas - When agrego mascota "Luna" tipo "Gato" - And agrego mascota "Rocky" tipo "Perro" - And selecciono servicios para cada una - Then deberia crearse una solicitud con 2 mascotas - And el precio deberia reflejar ambas - - Scenario: Franja horaria especifica - Given que solo puedo por la mañana - When selecciono franja "Mañana (9-12hs)" - Then la solicitud deberia registrar esa preferencia - # Nota: Es preferencia, no garantia diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature deleted file mode 100644 index c618e7b..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/03-gestion-mascotas.feature +++ /dev/null @@ -1,153 +0,0 @@ -# Fuente: album/book/ops-templates/pet-owner/03-gestion-mascotas.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_pets.py -# Tests Frontend: npx playwright test pets.spec.ts - -Feature: Gestion de mascotas - - Como dueno de mascota registrado - Quiero gestionar la informacion de mis mascotas - Para tenerla actualizada y acceder a su historial medico - - Background: - Given que estoy logueado como dueno de mascota - And estoy en la seccion "Mis mascotas" - - # ============================================ - # AGREGAR MASCOTA - # ============================================ - - Scenario: Agregar mascota con datos minimos - When hago click en "Agregar mascota" - And completo el formulario: - | campo | valor | - | nombre | Luna | - | tipo | Gato | - And hago click en "Guardar" - Then deberia ver "Luna" en mi lista de mascotas - And Luna deberia aparecer disponible en el turnero - - Scenario: Agregar mascota con datos completos - When hago click en "Agregar mascota" - And completo el formulario: - | campo | valor | - | nombre | Rocky | - | tipo | Perro | - | raza | Labrador | - | fecha_nacimiento | 2020-03-15 | - | peso | 25 | - | sexo | Macho | - | castrado | Si | - And subo una foto de Rocky - And hago click en "Guardar" - Then deberia ver "Rocky" con su foto en mi lista - And deberia ver badge "Castrado" - - Scenario Outline: Validacion de datos de mascota - When intento agregar mascota con igual a "" - Then deberia ver error "" - - Examples: - | campo | valor | mensaje | - | nombre | | El nombre es obligatorio | - | nombre | A | Nombre muy corto | - | tipo | | Selecciona el tipo | - | peso | -5 | El peso debe ser positivo | - | peso | 500 | Peso fuera de rango | - - # ============================================ - # EDITAR MASCOTA - # ============================================ - - Scenario: Editar peso de mascota - Given que tengo una mascota "Luna" con peso 4kg - When edito a Luna - And cambio el peso a 5kg - And guardo los cambios - Then Luna deberia mostrar peso "5 kg" - - Scenario: Marcar mascota como castrada - Given que tengo una mascota "Rocky" no castrado - When edito a Rocky - And marco "Esta castrado" - And guardo los cambios - Then Rocky deberia mostrar badge "Castrado" - And el servicio "Castracion" no deberia aparecer para Rocky en el turnero - - Scenario: Actualizar foto de mascota - Given que tengo una mascota "Luna" sin foto - When edito a Luna - And subo una nueva foto - And guardo los cambios - Then deberia ver la foto de Luna en su tarjeta - - # ============================================ - # ELIMINAR MASCOTA - # ============================================ - - Scenario: Eliminar mascota sin historial - Given que tengo una mascota "Nuevo" sin visitas - When hago click en "Eliminar" para Nuevo - And confirmo la eliminacion - Then Nuevo no deberia aparecer en mi lista - # Nota: Es soft delete - - Scenario: Eliminar mascota con historial medico - Given que tengo una mascota "Luna" con visitas anteriores - When hago click en "Eliminar" para Luna - Then deberia ver advertencia "Luna tiene historial medico" - And deberia ver "El historial se conservara pero no podras verlo" - When confirmo la eliminacion - Then Luna no deberia aparecer en mi lista - - Scenario: No puedo eliminar mascota con turno pendiente - Given que tengo una mascota "Rocky" con turno pendiente - When intento eliminar a Rocky - Then deberia ver error "Rocky tiene turnos pendientes" - And deberia ver sugerencia "Cancela los turnos primero" - - # ============================================ - # VER HISTORIAL MEDICO - # ============================================ - - Scenario: Ver historial de visitas de mascota - Given que tengo una mascota "Luna" con 3 visitas completadas - When hago click en Luna - And voy a la seccion "Historial" - Then deberia ver 3 visitas listadas - And deberian estar ordenadas por fecha descendente - - Scenario: Ver detalle de visita - Given que tengo una mascota "Luna" con visitas - When veo el historial de Luna - And hago click en la primera visita - Then deberia ver: - | campo | - | Fecha | - | Veterinario | - | Diagnostico | - | Tratamiento | - | Medicamentos | - - Scenario: Mascota sin historial - Given que tengo una mascota "Nuevo" recien agregada - When veo el perfil de Nuevo - Then la seccion "Historial" deberia estar vacia - And deberia ver mensaje "Aun no hay visitas registradas" - And deberia ver boton "Reservar primer turno" - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Mascota heredada de cuenta invitado - Given que me registre con email "juan@test.com" - And previamente reserve turno como invitado para "Firulais" - When voy a "Mis mascotas" - Then deberia ver "Firulais" en mi lista - And deberia ver su historial de visitas previas - - Scenario: Razas filtradas por tipo - When agrego una mascota tipo "Gato" - Then las razas disponibles deberian ser razas de gato - And no deberia ver razas de perro como "Labrador" diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/04-pago-turno.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/04-pago-turno.feature deleted file mode 100644 index ee3f5e6..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/04-pago-turno.feature +++ /dev/null @@ -1,140 +0,0 @@ -# Fuente: album/book/ops-templates/pet-owner/04-pago-turno.md -# Drive: 07. Finanzas y contabilidad/Mercado Pago -# Tests Backend: pytest tests/contracts/payments/test_mercadopago.py -# Tests Frontend: npx playwright test payment.spec.ts - -Feature: Pago de turno - - Como dueno de mascota con turno coordinado - Quiero pagar mi turno online - Para confirmar la visita del veterinario - - Background: - Given que tengo un turno en estado "Coordinado" - And el turno tiene asignado veterinario "Dra. Garcia" - And la fecha asignada es "15 de enero a las 10:00" - - # ============================================ - # FLUJO DE PAGO EXITOSO - # ============================================ - - Scenario: Pagar turno con tarjeta de credito - Given que estoy en el detalle de mi turno coordinado - When hago click en "Pagar" - Then deberia ser redirigido a Mercado Pago - And deberia ver el monto correcto - - When selecciono "Tarjeta de credito" - And completo los datos de la tarjeta - And confirmo el pago - Then deberia volver a la plataforma - And deberia ver "Pago exitoso" - And el turno deberia estar en estado "Pagado" - - Scenario: Recibir confirmacion de pago - Given que complete el pago exitosamente - Then deberia recibir email de confirmacion - And el email deberia contener: - | campo | - | Fecha del turno | - | Direccion | - | Veterinario asignado | - | Monto pagado | - | Numero de operacion | - - # ============================================ - # METODOS DE PAGO - # ============================================ - - Scenario Outline: Pagar con diferentes metodos - Given que estoy en Mercado Pago - When selecciono metodo "" - And completo el pago - Then el pago deberia ser - And el estado de acreditacion deberia ser "" - - Examples: - | metodo | resultado | acreditacion | - | Tarjeta credito | exitoso | inmediata | - | Tarjeta debito | exitoso | inmediata | - | Dinero en cuenta | exitoso | inmediata | - | Transferencia | pendiente | 1-2 dias | - | Rapipago | pendiente | hasta 24hs | - - # ============================================ - # MANEJO DE ERRORES - # ============================================ - - Scenario: Pago rechazado por fondos insuficientes - Given que estoy en Mercado Pago - When intento pagar con tarjeta sin fondos - Then deberia ver error "Fondos insuficientes" - And deberia poder reintentar con otra tarjeta - And el turno deberia seguir en estado "Coordinado" - - Scenario: Usuario cancela el pago - Given que estoy en Mercado Pago - When hago click en "Volver al sitio" - Then deberia volver a la plataforma - And deberia ver mensaje "El pago fue cancelado" - And deberia ver boton "Reintentar pago" - And el turno deberia seguir en estado "Coordinado" - - Scenario: Cierre de browser durante pago - Given que estoy en Mercado Pago - And cierro el navegador accidentalmente - When vuelvo a la plataforma - And voy a "Mis turnos" - Then deberia poder ver el estado real del pago - # Si se proceso: Pagado. Si no: Coordinado con opcion de pagar - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Link de pago expirado - Given que recibi el link de pago hace mas de 24 horas - When hago click en el link - Then deberia ver "Este link ha expirado" - And deberia ver "Contacta a soporte para generar uno nuevo" - - Scenario: Intento pagar turno ya pagado - Given que mi turno ya esta en estado "Pagado" - When accedo al link de pago - Then deberia ver "Este turno ya fue pagado" - And deberia ver boton "Ver detalle del turno" - - Scenario: Precio cambio desde la coordinacion - # Caso muy raro pero posible - Given que el precio del servicio aumento desde que se coordino - When voy a pagar - Then deberia ver el precio original acordado - # El precio se congela al momento de coordinacion - - # ============================================ - # INTEGRACION CON WEBHOOK - # ============================================ - - Rule: El estado del turno se actualiza via webhook de Mercado Pago - - Scenario: Webhook confirma pago aprobado - Given que el usuario completo el pago en Mercado Pago - When Mercado Pago envia webhook con status "approved" - Then el turno deberia cambiar a estado "Pagado" - And el veterinario deberia recibir notificacion - And el usuario deberia recibir email de confirmacion - - Scenario: Webhook informa pago pendiente - Given que el usuario pago con transferencia bancaria - When Mercado Pago envia webhook con status "pending" - Then el turno deberia quedarse en "Coordinado" - And deberia registrarse el pago pendiente - And el usuario deberia recibir email "Esperando acreditacion" - - Scenario: Webhook falla pero pago se proceso - # Caso de error que requiere intervencion manual - Given que el usuario pago exitosamente - But el webhook fallo por error de red - Then el turno seguira en "Coordinado" - And el equipo de ops deberia recibir alerta - And deberian poder actualizar manualmente diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/05-historial-medico.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/05-historial-medico.feature deleted file mode 100644 index 837447a..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/pet-owner/05-historial-medico.feature +++ /dev/null @@ -1,147 +0,0 @@ -# Fuente: album/book/ops-templates/pet-owner/05-historial-medico.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test medical-history.spec.ts - -Feature: Ver historial medico - - Como dueno de mascota - Quiero ver el historial medico de mis mascotas - Para tener registro de sus visitas y tratamientos - - Background: - Given que estoy logueado como dueno de mascota - And tengo una mascota "Luna" con visitas completadas - - # ============================================ - # ACCESO AL HISTORIAL - # ============================================ - - Scenario: Acceder al historial desde mascotas - Given que estoy en "Mis mascotas" - When hago click en "Luna" - And voy a la seccion "Historial de visitas" - Then deberia ver lista de visitas de Luna - - Scenario: Acceder al historial desde turnos - Given que estoy en "Mis turnos" - And filtro por "Completados" - When hago click en un turno de Luna - And hago click en "Ver informe medico" - Then deberia ver el informe de esa visita - - # ============================================ - # VISUALIZACION DE VISITAS - # ============================================ - - Scenario: Ver lista de visitas ordenadas - Given que Luna tiene visitas en las siguientes fechas: - | fecha | - | 2024-01-15 | - | 2023-11-20 | - | 2023-06-10 | - When veo el historial de Luna - Then deberia ver las visitas ordenadas de mas reciente a mas antigua - And la primera deberia ser del "15 de enero 2024" - - Scenario: Ver detalle completo de informe - When hago click en una visita completada - Then deberia ver los siguientes datos: - | seccion | contenido | - | Informacion | Fecha, veterinario, servicios | - | Examen fisico | Peso, temperatura, FC, obs | - | Diagnostico | Descripcion del diagnostico | - | Tratamiento | Plan de tratamiento | - | Medicamentos | Lista con dosis y frecuencia | - | Estudios | Estudios solicitados y estado | - | Seguimiento | Recomendaciones, proximo control | - - Scenario: Ver informe con medicamentos recetados - Given que la visita incluye medicamentos: - """ - 1. Amoxicilina 250mg - 1 comprimido cada 12hs por 7 dias - 2. Meloxicam 1.5mg - 1 comprimido por dia por 3 dias - """ - When veo el informe - Then deberia ver la lista de medicamentos - And cada medicamento deberia mostrar: - | campo | - | Nombre | - | Dosis | - | Frecuencia | - | Duracion | - - # ============================================ - # DESCARGA Y COMPARTIR - # ============================================ - - Scenario: Descargar informe como PDF - Given que estoy viendo un informe medico - When hago click en "Descargar PDF" - Then deberia descargarse un archivo PDF - And el PDF deberia contener toda la informacion del informe - And deberia tener el logo de Amar Mascotas - - Scenario: Imprimir informe - Given que estoy viendo un informe medico - When hago click en "Imprimir" - Then deberia abrirse el dialogo de impresion - And el formato deberia ser optimizado para impresion - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Visita completada sin informe cargado - # El vet no cargo el informe todavia - Given que Luna tiene una visita marcada como "Completada" - But el veterinario no cargo el informe aun - When intento ver el informe - Then deberia ver mensaje "Informe pendiente" - And deberia ver "El veterinario esta completando el informe" - - Scenario: Informe con estudios pendientes - Given que el informe solicita estudios de laboratorio - And los resultados aun no estan disponibles - When veo el informe - Then deberia ver seccion "Estudios solicitados" - And deberia ver estado "Pendiente de resultados" - And deberia ver nota "Se notificara cuando esten listos" - - Scenario: Mascota con multiples visitas el mismo dia - # Raro pero posible: consulta de urgencia + seguimiento - Given que Luna tuvo 2 visitas el 15 de enero - When veo el historial - Then deberia ver ambas visitas listadas - And deberian estar diferenciadas por hora - - # ============================================ - # PERMISOS Y PRIVACIDAD - # ============================================ - - Rule: Solo el dueno puede ver el historial de su mascota - - Scenario: Dueno ve historial de su mascota - Given que soy dueno de Luna - When accedo al historial de Luna - Then deberia poder verlo completo - - Scenario: No puedo ver historial de mascota ajena - Given que existe mascota "Rocky" de otro dueno - When intento acceder al historial de Rocky - Then deberia ver error "No tienes acceso" - # 403 Forbidden - - # ============================================ - # BUSQUEDA Y FILTROS - # ============================================ - - Scenario: Filtrar historial por tipo de servicio - Given que Luna tiene visitas de vacunacion y consulta - When filtro por "Vacunacion" - Then solo deberia ver visitas de vacunacion - - Scenario: Buscar en historial por fecha - When busco visitas del "2023" - Then solo deberia ver visitas del 2023 - And no deberia ver visitas del 2024 diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature deleted file mode 100644 index d708ef7..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/01-aceptar-solicitud.feature +++ /dev/null @@ -1,109 +0,0 @@ -# Fuente: album/book/ops-templates/veterinarian/01-aceptar-solicitud.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py -# Tests Frontend: npx playwright test vet-requests.spec.ts - -Feature: Aceptar o rechazar solicitudes de servicio - - Como veterinario de la plataforma - Quiero revisar y responder a solicitudes en mi zona - Para gestionar mi agenda de visitas a domicilio - - Background: - Given que estoy logueado como veterinario - And tengo cobertura en los barrios "Palermo" y "Recoleta" - And estoy en el dashboard de veterinario - - # ============================================ - # VER SOLICITUDES PENDIENTES - # ============================================ - - Scenario: Ver lista de solicitudes en mi zona - When veo la seccion "Solicitudes pendientes" - Then deberia ver solo solicitudes de "Palermo" y "Recoleta" - And no deberia ver solicitudes de otros barrios - - Scenario: Ver detalle de solicitud - Given que hay una solicitud pendiente - When hago click en la solicitud - Then deberia ver: - | seccion | contenido | - | Dueno | Nombre, telefono, direccion | - | Mascota | Nombre, tipo, edad, foto | - | Servicios | Lista de servicios solicitados | - | Fechas | Fechas preferidas por el dueno | - | Historial | Visitas anteriores si las hay | - - # ============================================ - # ACEPTAR SOLICITUD - # ============================================ - - Scenario: Aceptar solicitud con fecha disponible - Given que hay una solicitud para el barrio "Palermo" - And el dueno prefiere fechas: - | fecha | franja | - | 2024-01-15 | Mañana | - | 2024-01-16 | Tarde | - And tengo disponibilidad el 15 de enero a las 10:00 - When hago click en "Aceptar" - And selecciono fecha "15 de enero" hora "10:00" - And confirmo la aceptacion - Then la solicitud deberia pasar a estado "Coordinado" - And deberia quedar asignada a mi - And el dueno deberia recibir notificacion con mis datos - And la visita deberia aparecer en mi agenda - - Scenario: Aceptar solicitud con datos de mascota que ya atendi - Given que hay una solicitud para mascota "Luna" - And yo atendi a "Luna" anteriormente - When veo el detalle de la solicitud - Then deberia ver badge "Paciente recurrente" - And deberia ver el historial de mis visitas anteriores a Luna - - # ============================================ - # RECHAZAR SOLICITUD - # ============================================ - - Scenario Outline: Rechazar solicitud con motivo - Given que hay una solicitud pendiente - When hago click en "Rechazar" - And selecciono motivo "" - And confirmo el rechazo - Then la solicitud deberia desaparecer de mi lista - And deberia seguir visible para otros veterinarios - - Examples: - | motivo | - | No tengo disponibilidad | - | Fuera de mi zona | - | No realizo este servicio | - | Otro | - - # ============================================ - # RACE CONDITIONS - # ============================================ - - Rule: Solo un veterinario puede aceptar cada solicitud - - Scenario: Otro vet acepta mientras estoy viendo - Given que estoy viendo el detalle de una solicitud - And otro veterinario acepta la misma solicitud - When intento aceptarla - Then deberia ver error "Esta solicitud ya fue aceptada" - And deberia ser redirigido al listado - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Solicitud urgente destacada - Given que hay una solicitud marcada como "Urgente" - When veo el listado de solicitudes - Then deberia ver la solicitud con indicador de urgencia - And deberia aparecer primero en la lista - - Scenario: No puedo aceptar con agenda completa - Given que tengo mi agenda completa para las fechas de la solicitud - When intento aceptar la solicitud - Then deberia ver advertencia "No tienes disponibilidad en las fechas preferidas" - And deberia poder proponer una fecha alternativa diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/02-gestion-agenda.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/02-gestion-agenda.feature deleted file mode 100644 index 7ef5b94..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/02-gestion-agenda.feature +++ /dev/null @@ -1,99 +0,0 @@ -# Fuente: album/book/ops-templates/veterinarian/02-gestion-agenda.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_availability.py -# Tests Frontend: npx playwright test vet-schedule.spec.ts - -Feature: Gestion de agenda veterinaria - - Como veterinario - Quiero gestionar mi calendario y disponibilidad - Para organizar mis visitas a domicilio eficientemente - - Background: - Given que estoy logueado como veterinario - And estoy en la seccion "Mi agenda" - - # ============================================ - # VER AGENDA - # ============================================ - - Scenario: Ver calendario semanal - When veo mi agenda en vista semanal - Then deberia ver los 7 dias de la semana - And deberia ver mis visitas programadas - And cada visita deberia mostrar hora y nombre del paciente - - Scenario: Ver visitas con codigo de color por estado - Given que tengo visitas en diferentes estados - When veo mi agenda - Then las visitas deberian mostrarse con colores: - | estado | color | - | Coordinado | amarillo | - | Pagado | verde | - | En progreso | azul | - | Completado | gris | - - # ============================================ - # CONFIGURAR DISPONIBILIDAD SEMANAL - # ============================================ - - Scenario: Configurar horario laboral - When voy a "Configuracion" -> "Mi disponibilidad" - And configuro mi horario: - | dia | trabajo | desde | hasta | pausa_desde | pausa_hasta | - | Lunes | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Martes | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Miercoles | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Jueves | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Viernes | Si | 09:00 | 15:00 | | | - | Sabado | No | | | | | - | Domingo | No | | | | | - And guardo la configuracion - Then mi disponibilidad deberia actualizarse - And no deberia recibir solicitudes fuera de ese horario - - # ============================================ - # BLOQUEAR DIAS ESPECIFICOS - # ============================================ - - Scenario: Marcar dia como no disponible - Given que necesito el 20 de enero libre - When hago click en el dia 20 de enero en el calendario - And selecciono "Marcar como no disponible" - And ingreso motivo "Vacaciones" - And confirmo - Then el dia 20 deberia mostrarse como bloqueado - And no deberia poder aceptar solicitudes para ese dia - - Scenario: Bloquear rango de fechas - Given que tomo vacaciones del 15 al 22 de enero - When voy a "Agregar indisponibilidad" - And selecciono fecha inicio "15 de enero" - And selecciono fecha fin "22 de enero" - And ingreso motivo "Vacaciones" - And confirmo - Then todos esos dias deberian mostrarse como bloqueados - - # ============================================ - # IMPACTO EN SOLICITUDES - # ============================================ - - Rule: Los bloqueos no afectan visitas ya aceptadas - - Scenario: Bloquear dia con visita ya aceptada - Given que tengo una visita aceptada para el 15 de enero - When intento bloquear el 15 de enero - Then deberia ver advertencia "Tienes una visita programada ese dia" - And deberia poder elegir: - | opcion | - | Cancelar el bloqueo | - | Bloquear y contactar al dueno | - - Rule: Cambios de disponibilidad solo afectan solicitudes futuras - - Scenario: Cambiar horario no afecta visitas existentes - Given que tengo una visita a las 17:00 el lunes - When cambio mi horario del lunes para terminar a las 16:00 - And guardo los cambios - Then la visita de las 17:00 deberia mantenerse - And deberia ver advertencia sobre la inconsistencia diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/03-realizar-visita.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/03-realizar-visita.feature deleted file mode 100644 index cc6dbd5..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/03-realizar-visita.feature +++ /dev/null @@ -1,127 +0,0 @@ -# Fuente: album/book/ops-templates/veterinarian/03-realizar-visita.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test vet-visit.spec.ts - -Feature: Realizar visita y crear informe medico - - Como veterinario - Quiero registrar la atencion medica de mis visitas - Para mantener el historial clinico del paciente - - Background: - Given que estoy logueado como veterinario - And tengo una visita programada para hoy con mascota "Luna" - And la visita esta en estado "Pagado" - - # ============================================ - # INICIAR VISITA - # ============================================ - - Scenario: Ver informacion antes de la visita - When accedo al detalle de la visita - Then deberia ver la direccion para llegar - And deberia ver el telefono del dueno para confirmar - And deberia ver los servicios a realizar - And deberia ver el historial previo de Luna - - Scenario: Iniciar visita al llegar - Given que llegue al domicilio - When hago click en "Iniciar visita" - Then el estado deberia cambiar a "En progreso" - And deberia registrarse la hora de inicio - And deberia habilitarse el boton "Crear informe" - - # ============================================ - # CREAR INFORME MEDICO - # ============================================ - - Scenario: Completar informe con examen fisico - Given que la visita esta en progreso - When voy a "Crear informe" - And completo el examen fisico: - | campo | valor | - | Peso | 4.5 kg | - | Temperatura | 38.5 °C | - | Frecuencia cardiaca | 120 lpm | - | Frecuencia resp | 25 rpm | - | Mucosas | Rosadas | - | Hidratacion | Normal | - And guardo el informe - Then el examen fisico deberia guardarse - - Scenario: Agregar diagnostico y tratamiento - Given que complete el examen fisico - When agrego el diagnostico: - """ - Otitis externa bilateral leve. - Paciente presenta prurito y secrecion ceruminosa. - Sin signos de infeccion secundaria. - """ - And agrego el tratamiento: - """ - Limpieza de oidos con solucion fisiologica. - Aplicacion de gotas oticas antibioticas. - Control en 7 dias. - """ - And guardo el informe - Then el diagnostico y tratamiento deberian guardarse - - Scenario: Recetar medicamentos - Given que estoy creando el informe - When agrego medicamentos: - | nombre | dosis | frecuencia | duracion | - | Otomax gotas | 5 gotas | cada 12 horas | 7 dias | - | Meloxicam 1.5mg | 1 comp | cada 24 horas | 3 dias | - And guardo el informe - Then los medicamentos deberian aparecer en el informe - And el dueno podra verlos desde su cuenta - - # ============================================ - # COMPLETAR VISITA - # ============================================ - - Scenario: Completar visita con informe - Given que el informe esta completo - When hago click en "Completar visita" - And confirmo la finalizacion - Then la visita deberia pasar a estado "Completado" - And deberia generarse factura electronica (AFIP) - And el dueno deberia recibir notificacion - And el informe deberia ser visible para el dueno - - Scenario: No puedo completar sin informe - Given que la visita esta en progreso - But no cree ningun informe - When intento completar la visita - Then deberia ver error "Debes crear el informe antes de completar" - - # ============================================ - # GUARDADO AUTOMATICO - # ============================================ - - Rule: El informe se guarda automaticamente - - Scenario: Guardado automatico cada 30 segundos - Given que estoy escribiendo el informe - When pasan 30 segundos - Then deberia ver indicador "Guardado automaticamente" - - Scenario: Recuperar informe despues de desconexion - Given que estaba escribiendo el informe - And perdi conexion a internet - When recupero la conexion - Then deberia recuperar mi progreso - And no deberia perder lo que escribi - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Scenario: Cancelar visita in situ - Given que llegue al domicilio - But el dueno no esta - When marco la visita como "Cancelada in situ" - And selecciono motivo "Dueno ausente" - Then la visita deberia marcarse como cancelada - And deberia generarse cargo por visita fallida diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature deleted file mode 100644 index 16df339..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/04-zonas-cobertura.feature +++ /dev/null @@ -1,83 +0,0 @@ -# Fuente: album/book/ops-templates/veterinarian/04-zonas-cobertura.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_veterinarians.py -# Tests Frontend: npx playwright test vet-coverage.spec.ts - -Feature: Gestionar zonas de cobertura - - Como veterinario - Quiero definir en que zonas atiendo - Para recibir solo solicitudes que puedo cubrir - - Background: - Given que estoy logueado como veterinario - And estoy en la seccion "Mi cobertura" - - # ============================================ - # VER ZONAS ACTUALES - # ============================================ - - Scenario: Ver mapa con mis zonas de cobertura - When cargo la pagina de cobertura - Then deberia ver un mapa de la ciudad - And mis zonas cubiertas deberian estar resaltadas - And deberia ver un listado de barrios seleccionados - - Scenario: Ver estadisticas por zona - Given que tengo cobertura en "Palermo" y "Recoleta" - When veo el detalle de mis zonas - Then deberia ver para cada zona: - | metrica | - | Solicitudes este mes | - | Visitas completadas | - | Otros vets en la zona | - - # ============================================ - # AGREGAR ZONAS - # ============================================ - - Scenario: Agregar barrio desde el mapa - Given que no tengo cobertura en "Belgrano" - When hago click en "Belgrano" en el mapa - And confirmo agregar la zona - Then "Belgrano" deberia aparecer en mi lista de zonas - And deberia empezar a ver solicitudes de Belgrano - - Scenario: Agregar multiples zonas a la vez - When selecciono los barrios: - | barrio | - | Colegiales | - | Chacarita | - | Villa Crespo | - And hago click en "Agregar seleccionados" - Then los 3 barrios deberian agregarse a mi cobertura - - # ============================================ - # QUITAR ZONAS - # ============================================ - - Scenario: Quitar zona de cobertura - Given que tengo cobertura en "Recoleta" - When hago click en "X" junto a "Recoleta" - And confirmo quitar la zona - Then "Recoleta" no deberia estar en mi lista - And no deberia ver nuevas solicitudes de Recoleta - - # ============================================ - # IMPACTO EN SOLICITUDES - # ============================================ - - Rule: Los cambios de zona afectan solo solicitudes nuevas - - Scenario: Agregar zona muestra solicitudes existentes - Given que no tengo cobertura en "Belgrano" - And hay 3 solicitudes pendientes en Belgrano - When agrego "Belgrano" a mi cobertura - Then deberia ver las 3 solicitudes pendientes de Belgrano - - Scenario: Quitar zona no afecta visitas aceptadas - Given que tengo cobertura en "Recoleta" - And tengo una visita aceptada en Recoleta para mañana - When quito "Recoleta" de mi cobertura - Then la visita de mañana deberia mantenerse - But no deberia ver nuevas solicitudes de Recoleta diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/05-historial-pacientes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/05-historial-pacientes.feature deleted file mode 100644 index c5bcf9e..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/en/veterinarian/05-historial-pacientes.feature +++ /dev/null @@ -1,100 +0,0 @@ -# Fuente: album/book/ops-templates/veterinarian/05-historial-pacientes.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test vet-history.spec.ts - -Feature: Ver historial de pacientes - - Como veterinario - Quiero acceder al historial medico de pacientes - Para tener contexto clinico en mis atenciones - - Background: - Given que estoy logueado como veterinario - And estoy en la seccion "Historia clinica" - - # ============================================ - # BUSCAR PACIENTES - # ============================================ - - Scenario Outline: Buscar paciente por diferentes criterios - When busco por con valor "" - Then deberia ver resultados que coincidan - - Examples: - | criterio | valor | - | nombre dueno | Maria Garcia | - | nombre mascota | Luna | - | telefono | 1155551234 | - | email | maria@ejemplo.com | - - Scenario: Busqueda sin resultados - When busco "ZZZZZ paciente inexistente" - Then deberia ver mensaje "No se encontraron resultados" - And deberia ver sugerencia "Verifica la ortografia" - - # ============================================ - # VER FICHA DE MASCOTA - # ============================================ - - Scenario: Ver ficha completa de mascota - Given que encontre a la mascota "Luna" - When hago click en Luna - Then deberia ver la ficha con: - | seccion | contenido | - | Datos basicos | Nombre, tipo, raza, edad, peso | - | Foto | Foto de la mascota | - | Dueno | Nombre y contacto del dueno | - | Vacunacion | Estado de vacunas | - | Historial | Lista de visitas | - - # ============================================ - # VER HISTORIAL DE VISITAS - # ============================================ - - Scenario: Ver listado de visitas - Given que estoy viendo la ficha de "Luna" - And Luna tiene 5 visitas completadas - When veo la seccion "Historial de visitas" - Then deberia ver las 5 visitas listadas - And deberian estar ordenadas de mas reciente a mas antigua - - Scenario: Ver informe de visita de otro veterinario - Given que Luna fue atendida por "Dra. Rodriguez" - And yo no la atendi en esa visita - When hago click en esa visita - Then deberia poder ver el informe completo - # Para continuidad de atencion - - # ============================================ - # FILTROS Y NAVEGACION - # ============================================ - - Scenario: Filtrar historial por tipo de servicio - Given que estoy viendo el historial de "Luna" - When filtro por servicio "Vacunacion" - Then solo deberia ver visitas de vacunacion - - Scenario: Filtrar por mis atenciones - Given que estoy viendo el historial de "Luna" - And Luna fue atendida por varios veterinarios - When marco "Solo mis atenciones" - Then solo deberia ver las visitas que yo realice - - # ============================================ - # PERMISOS - # ============================================ - - Rule: Veterinarios pueden ver historial de pacientes que atendieron - - Scenario: Puedo ver historial de paciente que atendi - Given que yo atendi a "Luna" al menos una vez - When busco a Luna - Then deberia poder ver su historial completo - - Scenario: Puedo ver historial de paciente con solicitud pendiente - Given que hay una solicitud pendiente para "Rocky" - And la solicitud esta en mi zona - When busco a Rocky - Then deberia poder ver su historial - # Para evaluar si acepto la solicitud diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature deleted file mode 100644 index 042da52..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/01-gestion-solicitudes.feature +++ /dev/null @@ -1,158 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/backoffice/01-gestion-solicitudes.md -# Drive: 05. ATC - Operaciones/Procedimientos -# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py -# Tests Frontend: npx playwright test admin-requests.spec.ts - -Característica: Gestion de solicitudes de servicio - - Como administrador del backoffice - Quiero gestionar todas las solicitudes del sistema - Para asegurar que se atiendan correctamente - - Antecedentes: - Dado que estoy logueado como administrador - Y estoy en la seccion "Solicitudes" del backoffice - - # ============================================ - # VER Y FILTRAR SOLICITUDES - # ============================================ - - Escenario: Ver listado de todas las solicitudes - Cuando cargo la pagina de solicitudes - Entonces deberia ver un listado paginado - Y cada solicitud deberia mostrar: - | campo | - | ID | - | Dueno | - | Mascota | - | Servicios | - | Estado | - | Fecha | - - Esquema del escenario: Filtrar solicitudes por estado - Cuando filtro por estado "" - Entonces solo deberia ver solicitudes con estado "" - - Ejemplos: - | estado | - | Pendiente | - | Coordinado | - | Pagado | - | Completado | - | Cancelado | - - Escenario: Buscar solicitud especifica - Cuando busco por email "maria@ejemplo.com" - Entonces deberia ver solo solicitudes de ese dueno - - Escenario: Filtrar por rango de fechas - Cuando filtro desde "01/01/2024" hasta "31/01/2024" - Entonces solo deberia ver solicitudes de enero 2024 - - # ============================================ - # ASIGNAR VETERINARIO MANUALMENTE - # ============================================ - - Escenario: Asignar veterinario a solicitud pendiente - Dado que hay una solicitud pendiente en "Palermo" - Y no fue aceptada por ningun veterinario - Cuando abro el detalle de la solicitud - Y hago click en "Asignar veterinario" - Entonces deberia ver lista de veterinarios con cobertura en Palermo - - Cuando selecciono "Dra. Garcia" - Y selecciono fecha "15 de enero" hora "10:00" - Y confirmo la asignacion - Entonces la solicitud deberia pasar a estado "Coordinado" - Y deberia estar asignada a Dra. Garcia - Y el dueno deberia recibir notificacion - Y el veterinario deberia recibir notificacion - - Escenario: Ver disponibilidad de veterinarios antes de asignar - Dado que estoy asignando un veterinario - Cuando veo la lista de veterinarios disponibles - Entonces deberia ver para cada uno: - | informacion | - | Nombre | - | Disponibilidad | - | Visitas del dia | - | Distancia a destino | - - # ============================================ - # CAMBIAR ESTADO MANUALMENTE - # ============================================ - - Escenario: Cambiar estado de solicitud - Dado que hay una solicitud en estado "Coordinado" - Y el pago se proceso pero el webhook fallo - Cuando abro la solicitud - Y hago click en "Cambiar estado" - Y selecciono "Pagado" - Y ingreso motivo "Pago confirmado manualmente - ID MP: 12345" - Y confirmo el cambio - Entonces la solicitud deberia pasar a "Pagado" - Y el cambio deberia registrarse en el historial - Y deberia quedar mi usuario como responsable del cambio - - Regla: Solo ciertos cambios de estado son validos - - Escenario: No puedo volver a estado anterior - Dado que hay una solicitud en estado "Completado" - Cuando intento cambiar el estado a "Pagado" - Entonces deberia ver error "No se puede volver a un estado anterior" - - Escenario: Puedo cancelar desde cualquier estado - Dado que hay una solicitud en estado "Coordinado" - Cuando cambio el estado a "Cancelado" - Y ingreso motivo de cancelacion - Entonces la solicitud deberia cancelarse - - # ============================================ - # VER DETALLE COMPLETO - # ============================================ - - Escenario: Ver historial de cambios de una solicitud - Dado que hay una solicitud con varios cambios de estado - Cuando abro el detalle de la solicitud - Y voy a la pestaña "Historial" - Entonces deberia ver todos los cambios con: - | campo | - | Fecha y hora | - | Estado anterior | - | Estado nuevo | - | Usuario | - | Motivo | - - Escenario: Ver informacion de pago - Dado que hay una solicitud pagada - Cuando abro el detalle - Entonces deberia ver seccion "Pago" con: - | campo | - | Monto | - | Fecha de pago | - | Metodo | - | ID operacion MP | - | Estado del pago | - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Reasignar veterinario - Dado que hay una solicitud asignada a "Dr. Lopez" - Y Dr. Lopez no puede asistir - Cuando abro la solicitud - Y hago click en "Reasignar" - Y selecciono otro veterinario - Y confirmo - Entonces Dr. Lopez deberia ser notificado de la desasignacion - Y el nuevo vet deberia ser notificado - Y el dueno deberia ser notificado del cambio - - Escenario: Solicitud sin veterinarios disponibles - Dado que hay una solicitud pendiente en una zona remota - Y no hay veterinarios con cobertura en esa zona - Cuando intento asignar veterinario - Entonces deberia ver mensaje "No hay veterinarios disponibles" - Y deberia poder expandir la busqueda a zonas cercanas diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/02-gestion-usuarios.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/02-gestion-usuarios.feature deleted file mode 100644 index b82fb36..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/02-gestion-usuarios.feature +++ /dev/null @@ -1,131 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/backoffice/02-gestion-usuarios.md -# Drive: 05. ATC - Operaciones/Procedimientos -# Tests Backend: pytest tests/contracts/mascotas/test_pet_owners.py -# Tests Frontend: npx playwright test admin-users.spec.ts - -Característica: Gestion de usuarios - - Como administrador del backoffice - Quiero gestionar duenos y veterinarios - Para mantener la base de usuarios actualizada - - Antecedentes: - Dado que estoy logueado como administrador - Y estoy en el backoffice - - # ============================================ - # GESTION DE DUENOS - # ============================================ - - Escenario: Buscar dueno de mascota - Dado que estoy en la seccion "Duenos" - Cuando busco "maria@ejemplo.com" - Entonces deberia ver a Maria Garcia en los resultados - Y deberia ver sus mascotas listadas - Y deberia ver cantidad de turnos - - Escenario: Ver perfil completo de dueno - Dado que encontre a "Maria Garcia" - Cuando hago click en su perfil - Entonces deberia ver: - | seccion | contenido | - | Datos personales | Nombre, email, telefono | - | Direcciones | Direcciones registradas | - | Mascotas | Lista de mascotas | - | Historial | Solicitudes anteriores | - | Pagos | Historial de pagos | - - Escenario: Editar datos de dueno - Dado que estoy viendo el perfil de un dueno - Cuando hago click en "Editar" - Y cambio el telefono a "1155559999" - Y guardo los cambios - Entonces el telefono deberia actualizarse - Y deberia registrarse quien hizo el cambio - - Esquema del escenario: Filtrar duenos por tipo - Dado que estoy en la lista de duenos - Cuando filtro por tipo "" - Entonces solo deberia ver usuarios - - Ejemplos: - | tipo | - | Registrados | - | Invitados | - - # ============================================ - # GESTION DE VETERINARIOS - # ============================================ - - Escenario: Ver lista de veterinarios - Cuando voy a la seccion "Veterinarios" - Entonces deberia ver listado de todos los vets - Y cada vet deberia mostrar: - | campo | - | Nombre | - | Matricula | - | Zonas | - | Estado | - | Visitas mes | - - Escenario: Agregar nuevo veterinario - Dado que estoy en la seccion "Veterinarios" - Cuando hago click en "Agregar veterinario" - Y completo los datos: - | campo | valor | - | Nombre | Dr. Juan Perez | - | Matricula | MV-12345 | - | Email | jperez@ejemplo.com | - | Telefono | 1155551234 | - | Especialidades | Clinica general | - Y selecciono zonas de cobertura: - | zona | - | Palermo | - | Recoleta | - Y genero credenciales de acceso - Y guardo - Entonces deberia crearse el veterinario - Y deberia poder loguearse con sus credenciales - - Escenario: Desactivar veterinario sin citas pendientes - Dado que el veterinario "Dr. Lopez" no tiene citas pendientes - Cuando abro su perfil - Y hago click en "Desactivar" - Y confirmo la desactivacion - Entonces Dr. Lopez deberia estar inactivo - Y no deberia recibir nuevas solicitudes - Y no deberia poder loguearse - - Escenario: Intentar desactivar veterinario con citas pendientes - Dado que el veterinario "Dra. Garcia" tiene 3 citas pendientes - Cuando intento desactivarla - Entonces deberia ver advertencia "Tiene 3 citas pendientes" - Y deberia ver opciones: - | opcion | - | Reasignar citas y desactivar | - | Cancelar | - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Fusionar cuentas duplicadas - Dado que hay dos cuentas para el mismo dueno: - | cuenta | email | mascotas | - | Cuenta1 | maria@gmail.com | Luna | - | Cuenta2 | maria@hotmail.com | Rocky | - Cuando selecciono ambas cuentas - Y hago click en "Fusionar" - Y elijo Cuenta1 como principal - Y confirmo - Entonces deberia existir solo Cuenta1 - Y deberia tener ambas mascotas - Y el historial deberia combinarse - - Escenario: Convertir invitado a registrado - Dado que hay un usuario invitado con email "invitado@test.com" - Cuando abro su perfil - Y hago click en "Enviar invitacion a registrarse" - Entonces deberia enviarse email con link de registro - Y el usuario podra crear contraseña y activar cuenta diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/03-gestion-servicios.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/03-gestion-servicios.feature deleted file mode 100644 index cac9c3d..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/03-gestion-servicios.feature +++ /dev/null @@ -1,131 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/backoffice/03-gestion-servicios.md -# Drive: 08. IT y Producto/Catalogo Servicios -# Tests Backend: pytest tests/contracts/productos/test_services.py -# Tests Frontend: npx playwright test admin-services.spec.ts - -Característica: Gestion de servicios y precios - - Como administrador - Quiero gestionar el catalogo de servicios - Para mantener la oferta actualizada - - Antecedentes: - Dado que estoy logueado como administrador - Y estoy en la seccion "Servicios" - - # ============================================ - # VER CATALOGO DE SERVICIOS - # ============================================ - - Escenario: Ver listado de servicios - Cuando cargo la pagina de servicios - Entonces deberia ver todos los servicios organizados por categoria - Y cada servicio deberia mostrar: - | campo | - | Nombre | - | Categoria | - | Tipo mascota | - | Precio actual | - | Estado | - - Esquema del escenario: Filtrar servicios - Cuando filtro por "" - Entonces solo deberia ver servicios que coincidan - - Ejemplos: - | filtro | valor | - | categoria | Vacunacion | - | tipo_mascota | Gato | - | estado | Activo | - - # ============================================ - # CREAR Y EDITAR SERVICIOS - # ============================================ - - Escenario: Agregar nuevo servicio - Cuando hago click en "Agregar servicio" - Y completo los datos: - | campo | valor | - | Nombre | Vacuna Quintuple Felina | - | Descripcion | Protege contra 5 enfermedades | - | Categoria | Vacunacion | - | Tipo mascota | Gato | - | Precio | 15000 | - | Duracion | 30 minutos | - Y guardo el servicio - Entonces el servicio deberia crearse - Y deberia aparecer en el turnero para gatos - - Escenario: Editar servicio existente - Dado que existe el servicio "Consulta clinica" - Cuando abro el servicio - Y cambio la descripcion - Y guardo - Entonces la descripcion deberia actualizarse - Y las solicitudes existentes no deberian afectarse - - Escenario: Desactivar servicio - Dado que existe el servicio "Servicio Antiguo" - Cuando abro el servicio - Y hago click en "Desactivar" - Y confirmo - Entonces el servicio no deberia aparecer en el turnero - Y las solicitudes existentes deberian mantenerse - - # ============================================ - # GESTION DE PRECIOS - # ============================================ - - Escenario: Actualizar precio de servicio - Dado que "Consulta clinica" tiene precio actual de 10000 - Cuando abro el servicio - Y voy a la seccion "Precios" - Y hago click en "Agregar precio" - Y ingreso nuevo precio 12000 - Y selecciono fecha de vigencia "01/02/2024" - Y guardo - Entonces deberia crearse el nuevo precio - Y el precio anterior deberia quedar en historial - Y el nuevo precio deberia aplicar desde la fecha indicada - - Escenario: Ver historial de precios - Dado que un servicio tuvo varios cambios de precio - Cuando veo la seccion "Precios" - Entonces deberia ver historial con: - | campo | - | Precio | - | Fecha desde | - | Fecha hasta | - | Usuario | - - Regla: El precio se congela al crear la solicitud - - Escenario: Cambio de precio no afecta solicitudes existentes - Dado que hay una solicitud pendiente con "Consulta clinica" a 10000 - Cuando cambio el precio de "Consulta clinica" a 12000 - Entonces la solicitud deberia mantener el precio de 10000 - - # ============================================ - # CONFIGURAR COMBOS Y DEPENDENCIAS - # ============================================ - - Escenario: Crear combo de servicios - Cuando voy a "Combos" - Y hago click en "Agregar combo" - Y configuro: - | campo | valor | - | Nombre | Plan Preventivo Felino | - | Servicios | Vacunacion, Desparasitacion, Antipulgas | - | Precio combo | 25000 | - | Descuento | 20% | - Y guardo - Entonces el combo deberia crearse - Y deberia aplicarse automaticamente cuando se seleccionen esos servicios - - Escenario: Configurar servicio dependiente - Dado que "Vacunacion" requiere "Consulta clinica" - Cuando configuro la dependencia - Entonces al seleccionar "Vacunacion" en el turnero - Y "Consulta clinica" deberia agregarse automaticamente - Y no deberia poder quitarse diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/04-reembolsos.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/04-reembolsos.feature deleted file mode 100644 index 40e1e16..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/04-reembolsos.feature +++ /dev/null @@ -1,141 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/backoffice/04-reembolsos.md -# Drive: 07. Finanzas y contabilidad/Reembolsos -# Tests Backend: pytest tests/contracts/payments/test_refunds.py -# Tests Frontend: npx playwright test admin-refunds.spec.ts - -Característica: Proceso de reembolso - - Como administrador - Quiero procesar reembolsos de pagos - Para resolver cancelaciones y problemas - - Antecedentes: - Dado que estoy logueado como administrador - Y estoy en el backoffice - - # ============================================ - # IDENTIFICAR PAGO A REEMBOLSAR - # ============================================ - - Escenario: Buscar solicitud pagada por ID de operacion - Cuando busco por ID de Mercado Pago "12345678" - Entonces deberia encontrar la solicitud asociada - Y deberia ver los detalles del pago - - Escenario: Ver detalles de pago - Dado que encontre una solicitud pagada - Cuando veo la seccion de pago - Entonces deberia ver: - | campo | - | Monto pagado | - | Fecha de pago | - | Metodo de pago | - | ID operacion MP | - | Estado en MP | - - # ============================================ - # PROCESAR REEMBOLSO - # ============================================ - - Escenario: Reembolso total exitoso - Dado que hay una solicitud pagada por 15000 - Y la visita fue cancelada - Cuando abro la solicitud - Y hago click en "Procesar reembolso" - Y selecciono "Reembolso total" - Y ingreso motivo "Cancelacion por indisponibilidad del veterinario" - Y confirmo el reembolso - Entonces deberia enviarse la solicitud de reembolso a Mercado Pago - Y deberia ver mensaje "Reembolso en proceso" - Y la solicitud deberia registrar el reembolso - Y el dueno deberia recibir email de confirmacion - - Escenario: Reembolso parcial - Dado que hay una solicitud pagada por 20000 - Y solo se realizo parte del servicio - Cuando proceso reembolso parcial por 10000 - Y ingreso motivo "Servicio parcialmente completado" - Y confirmo - Entonces deberia procesarse reembolso por 10000 - Y deberia quedar registro del monto reembolsado - Y deberia quedar registro del monto retenido - - # ============================================ - # TIEMPOS DE ACREDITACION - # ============================================ - - Esquema del escenario: Informar tiempo de acreditacion segun metodo - Dado que el pago original fue con "" - Cuando proceso el reembolso - Entonces deberia informar al usuario: - """ - El reembolso se acreditara en - """ - - Ejemplos: - | metodo | tiempo | - | Tarjeta credito | 1-2 resumenes de cuenta | - | Tarjeta debito | 5-10 dias habiles | - | Dinero en cuenta MP | forma inmediata | - | Transferencia | 5-10 dias habiles | - - # ============================================ - # VALIDACIONES Y ERRORES - # ============================================ - - Escenario: No puedo reembolsar mas del monto pagado - Dado que hay una solicitud pagada por 15000 - Cuando intento reembolsar 20000 - Entonces deberia ver error "El monto supera el pago original" - - Escenario: Reembolso duplicado - Dado que ya procese un reembolso total para una solicitud - Cuando intento procesar otro reembolso - Entonces deberia ver error "Esta solicitud ya fue reembolsada" - - Escenario: Pago fuera de plazo de reembolso - Dado que hay un pago de hace 200 dias - Cuando intento reembolsar - Entonces deberia ver advertencia "Fuera de plazo de reembolso de MP (180 dias)" - Y deberia sugerir "Contactar al dueno para solucion alternativa" - - # ============================================ - # SEGUIMIENTO - # ============================================ - - Escenario: Ver estado del reembolso - Dado que procese un reembolso hace 2 dias - Cuando veo el detalle del reembolso - Entonces deberia ver el estado actual en Mercado Pago - Y deberia ver historial de estados: - | estado | fecha | - | Solicitado | 01/01/2024 | - | En proceso | 01/01/2024 | - | Completado | 03/01/2024 | - - Escenario: Reembolso rechazado por MP - Dado que Mercado Pago rechazo el reembolso - Cuando veo el estado - Entonces deberia ver "Reembolso rechazado" - Y deberia ver el motivo del rechazo - Y deberia poder contactar al dueno con alternativas - - # ============================================ - # REGISTRO Y AUDITORIA - # ============================================ - - Regla: Todos los reembolsos quedan registrados - - Escenario: Ver historial de reembolsos - Cuando voy a "Reportes" -> "Reembolsos" - Entonces deberia ver lista de todos los reembolsos - Y cada uno deberia mostrar: - | campo | - | Solicitud | - | Monto original | - | Monto reembolso | - | Motivo | - | Procesado por | - | Fecha | - | Estado | diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/05-reportes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/05-reportes.feature deleted file mode 100644 index d7016aa..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/backoffice/05-reportes.feature +++ /dev/null @@ -1,155 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/backoffice/05-reportes.md -# Drive: 07. Finanzas y contabilidad/Reportes -# Tests Backend: pytest tests/contracts/mascotas/test_stats.py -# Tests Frontend: npx playwright test admin-reports.spec.ts - -Característica: Reportes y dashboard - - Como administrador o gerente - Quiero ver metricas y generar reportes - Para tomar decisiones informadas - - Antecedentes: - Dado que estoy logueado como administrador - Y estoy en el backoffice - - # ============================================ - # DASHBOARD - # ============================================ - - Escenario: Ver dashboard principal - Cuando accedo al dashboard - Entonces deberia ver metricas resumidas: - | metrica | periodo | - | Solicitudes nuevas | Hoy | - | Visitas completadas | Semana | - | Ingresos | Mes | - | Veterinarios activos | Actual | - | Tasa de conversion | Mes | - - Escenario: Dashboard actualiza periodicamente - Dado que estoy en el dashboard - Cuando pasan 5 minutos - Entonces los datos deberian actualizarse - Y deberia ver indicador de ultima actualizacion - - Escenario: Ver grafico de tendencia - Cuando veo el grafico de solicitudes - Entonces deberia ver la evolucion de los ultimos 30 dias - Y deberia poder comparar con periodo anterior - - # ============================================ - # REPORTE DE SOLICITUDES - # ============================================ - - Escenario: Generar reporte de solicitudes - Cuando voy a "Reportes" -> "Solicitudes" - Y selecciono periodo "Enero 2024" - Y hago click en "Generar" - Entonces deberia ver tabla con solicitudes del periodo - Y deberia ver totales por estado: - | estado | cantidad | - | Pendiente | X | - | Coordinado | X | - | Pagado | X | - | Completado | X | - | Cancelado | X | - - Escenario: Filtrar reporte por multiples criterios - Cuando genero reporte con filtros: - | filtro | valor | - | Periodo | Enero 2024 | - | Zona | Palermo | - | Veterinario | Dra. Garcia | - | Estado | Completado | - Entonces deberia ver solo solicitudes que cumplan todos los criterios - - # ============================================ - # REPORTE DE INGRESOS - # ============================================ - - Escenario: Ver ingresos por periodo - Cuando voy a "Reportes" -> "Ingresos" - Y selecciono "Ultimo trimestre" - Entonces deberia ver: - | dato | - | Ingresos totales | - | Cantidad de pagos | - | Ticket promedio | - | Reembolsos realizados | - | Ingreso neto | - - Escenario: Ingresos agrupados por veterinario - Cuando agrupo el reporte por "Veterinario" - Entonces deberia ver para cada vet: - | dato | - | Visitas completadas | - | Ingresos generados | - | Porcentaje del total | - Y deberian estar ordenados de mayor a menor - - Escenario: Ingresos agrupados por servicio - Cuando agrupo el reporte por "Servicio" - Entonces deberia ver los servicios mas vendidos - Y deberia ver el ingreso de cada uno - Y deberia ver la cantidad de veces realizado - - # ============================================ - # REPORTE DE VETERINARIOS - # ============================================ - - Escenario: Ver performance de veterinarios - Cuando voy a "Reportes" -> "Veterinarios" - Entonces deberia ver para cada veterinario: - | metrica | - | Visitas completadas | - | Calificacion promedio | - | Tasa de aceptacion | - | Tasa de cancelacion | - | Ingresos generados | - - Escenario: Identificar veterinarios con problemas - Dado que el reporte muestra la tasa de cancelacion - Cuando ordeno por "Tasa de cancelacion" descendente - Entonces deberia ver primero los vets con mas cancelaciones - Y deberia poder investigar los motivos - - # ============================================ - # EXPORTACION - # ============================================ - - Esquema del escenario: Exportar reporte en diferentes formatos - Dado que tengo un reporte generado - Cuando hago click en "Exportar" - Y selecciono formato "" - Entonces deberia descargarse el archivo en formato - - Ejemplos: - | formato | - | CSV | - | Excel | - | PDF | - - Escenario: Exportar reporte grande de forma asincrona - Dado que genere un reporte con mas de 10000 registros - Cuando hago click en "Exportar" - Entonces deberia ver mensaje "Generando exportacion..." - Y deberia recibir notificacion cuando este listo - Y deberia poder descargar desde el centro de notificaciones - - # ============================================ - # REPORTES PROGRAMADOS - # ============================================ - - Escenario: Programar reporte semanal - Cuando voy a "Reportes" -> "Programados" - Y creo nuevo reporte programado: - | campo | valor | - | Tipo | Ingresos | - | Frecuencia | Semanal | - | Dia | Lunes | - | Destinatarios | gerencia@ejemplo.com | - Y guardo - Entonces deberia enviarse el reporte cada lunes - Y los destinatarios deberian recibirlo por email diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/01-registro.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/01-registro.feature deleted file mode 100644 index 97c720e..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/01-registro.feature +++ /dev/null @@ -1,92 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/pet-owner/01-registro.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/common/test_users.py -# Tests Frontend: npx playwright test auth.spec.ts - -Característica: Registro de usuario - - Como visitante de la plataforma - Quiero poder crear una cuenta - Para gestionar mis mascotas y reservar turnos - - # ============================================ - # CAMINO FELIZ - # ============================================ - - Escenario: Registro exitoso con datos validos - Dado que estoy en la pagina de registro - Cuando ingreso email "nuevo@ejemplo.com" - Y ingreso contraseña "Password123" - Y confirmo contraseña "Password123" - Y acepto los terminos y condiciones - Y hago click en "Crear cuenta" - Entonces deberia ver mensaje "Te enviamos un email de verificacion" - Y deberia recibir email de verificacion - - Escenario: Verificar email y activar cuenta - Dado que me registre con email "nuevo@ejemplo.com" - Y recibi el email de verificacion - Cuando hago click en el link de verificacion - Entonces mi cuenta deberia estar activa - Y deberia ser redirigido al dashboard - - # ============================================ - # VALIDACIONES - # ============================================ - - Esquema del escenario: Registro con datos invalidos - Dado que estoy en la pagina de registro - Cuando ingreso email "" - Y ingreso contraseña "" - Y confirmo contraseña "" - Y hago click en "Crear cuenta" - Entonces deberia ver error "" - - Ejemplos: - | email | password | confirmacion | mensaje_error | - | invalido | Password123 | Password123 | Email invalido | - | test@test.com | 123 | 123 | Contraseña muy corta | - | test@test.com | password | password | Debe contener al menos un numero | - | test@test.com | Password123 | Diferente123 | Las contraseñas no coinciden | - - Escenario: Registro con email ya existente - Dado que existe un usuario con email "existente@ejemplo.com" - Y estoy en la pagina de registro - Cuando ingreso email "existente@ejemplo.com" - Y completo el resto del formulario correctamente - Y hago click en "Crear cuenta" - Entonces deberia ver error "Este email ya esta registrado" - Y deberia ver link "Recuperar contraseña" - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Registro linkea con cuenta invitado existente - # Usuario que reservo turno como invitado y ahora quiere registrarse - Dado que existe un usuario invitado con email "invitado@ejemplo.com" - Y ese usuario tiene una mascota "Luna" registrada - Y estoy en la pagina de registro - Cuando me registro con email "invitado@ejemplo.com" - Y verifico mi cuenta - Entonces deberia ver mi mascota "Luna" en el dashboard - Y deberia ver mis turnos anteriores - - Escenario: Registro desde flujo de turnero - # Usuario empezo a reservar turno y decide crear cuenta - Dado que estoy en el paso final del turnero - Y ingrese mis datos de contacto - Cuando hago click en "Crear cuenta para guardar mis datos" - Entonces deberia ver formulario simplificado - Y mi email ya deberia estar pre-llenado - Y solo deberia ingresar contraseña - - Escenario: Reenviar email de verificacion - Dado que me registre pero no verifique mi cuenta - Y estoy en la pagina de login - Cuando intento iniciar sesion - Entonces deberia ver "Tu cuenta no esta verificada" - Y deberia ver boton "Reenviar email" - Cuando hago click en "Reenviar email" - Entonces deberia recibir nuevo email de verificacion diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/02-reservar-turno.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/02-reservar-turno.feature deleted file mode 100644 index f28e208..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/02-reservar-turno.feature +++ /dev/null @@ -1,141 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/pet-owner/02-reservar-turno.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/workflows/test_turnero_general.py -# Tests Frontend: npx playwright test turnero.spec.ts -# Relacionado: def/work_plan/10-flow-turnero.md - -Característica: Reservar turno veterinario (Turnero) - - Como dueno de mascota - Quiero reservar un turno veterinario a domicilio - Para que atiendan a mi mascota sin salir de casa - - Antecedentes: - Dado que estoy en la pagina del turnero - - # ============================================ - # VERIFICACION DE COBERTURA - # ============================================ - - Escenario: Verificar cobertura en zona disponible - Cuando ingreso direccion "Av Santa Fe 1234, CABA" - Entonces deberia ver mensaje "Tenemos cobertura en tu zona" - Y deberia poder continuar al siguiente paso - - Escenario: Zona sin cobertura - Cuando ingreso direccion "Calle Principal 100, Ushuaia" - Entonces deberia ver mensaje "Aun no tenemos cobertura en tu zona" - Y deberia ver formulario "Avisame cuando lleguen" - - # ============================================ - # FLUJO COMPLETO POR TIPO DE USUARIO - # ============================================ - - Esquema del escenario: Reservar turno como - Dado que soy un usuario - Y tengo cobertura en mi zona - Cuando completo los datos de mi mascota: - | campo | valor | - | nombre | Luna | - | tipo | Gato | - | edad | 2 años | - | castrada | Si | - Y selecciono servicios: - | servicio | - | Vacunacion | - Y selecciono fechas preferidas: - | fecha | franja | - | 2024-01-15 | Mañana | - | 2024-01-16 | Tarde | - Y completo datos de contacto con email "" - Y envio la solicitud - Entonces deberia crearse una solicitud en estado "Pendiente" - Y el dueno deberia ser - Y deberia recibir email de confirmacion - - Ejemplos: - | tipo_usuario | email | estado_dueno | - | invitado | nuevo@test.com | creado como invitado | - | registrado | user@test.com | mi cuenta existente | - | recurrente | conocido@test.com | identificado por email | - - # ============================================ - # SELECCION DE SERVICIOS - # ============================================ - - Escenario: Servicios filtrados por tipo de mascota - Dado que agregue una mascota tipo "Gato" - Cuando veo los servicios disponibles - Entonces deberia ver "Vacuna triple felina" - Y deberia ver "Vacuna antirabica" - Pero no deberia ver "Vacuna sextuple canina" - - Escenario: Consulta clinica se agrega automaticamente con vacunacion - Dado que estoy seleccionando servicios - Cuando selecciono "Vacunacion" - Entonces "Consulta clinica" deberia agregarse automaticamente - Y deberia ver nota "Incluye revision general" - Y no deberia poder quitar "Consulta clinica" - - Escenario: Servicios combo con descuento - Dado que estoy seleccionando servicios - Cuando agrego los siguientes servicios: - | servicio | - | Vacunacion | - | Desparasitacion | - | Antipulgas | - Entonces deberia ver "Plan preventivo completo" - Y el total deberia incluir descuento de combo - - Escenario: Castracion no disponible para mascota castrada - Dado que mi mascota esta marcada como castrada - Cuando veo los servicios disponibles - Entonces no deberia ver "Castracion" - - # ============================================ - # DATOS DE CONTACTO Y CUENTA - # ============================================ - - Escenario: Pre-llenado de datos para usuario logueado - Dado que estoy logueado como "maria@ejemplo.com" - Y tengo registrada mascota "Firulais" - Cuando inicio el flujo de turnero - Entonces mi direccion deberia estar pre-llenada - Y deberia poder seleccionar "Firulais" de mis mascotas - Y mis datos de contacto ya deberian estar completos - - Escenario: Detectar usuario existente por email - Dado que soy usuario invitado - Y existe una cuenta con email "existente@ejemplo.com" - Cuando ingreso email "existente@ejemplo.com" en datos de contacto - Entonces deberia ver "Ya tenes cuenta con este email" - Y deberia ver opciones: - | opcion | - | Iniciar sesion | - | Continuar como invitado | - - # ============================================ - # EDGE CASES - # ============================================ - - Escenario: Usuario abandona flujo a mitad - Dado que complete los datos de mascota - Y cerre el navegador sin enviar - Cuando vuelvo a la pagina del turnero - Entonces deberia poder recuperar mi progreso - # Nota: datos guardados en localStorage o session - - Escenario: Multiples mascotas en una solicitud - Dado que quiero atender a 2 mascotas - Cuando agrego mascota "Luna" tipo "Gato" - Y agrego mascota "Rocky" tipo "Perro" - Y selecciono servicios para cada una - Entonces deberia crearse una solicitud con 2 mascotas - Y el precio deberia reflejar ambas - - Escenario: Franja horaria especifica - Dado que solo puedo por la mañana - Cuando selecciono franja "Mañana (9-12hs)" - Entonces la solicitud deberia registrar esa preferencia - # Nota: Es preferencia, no garantia diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature deleted file mode 100644 index 99c2939..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/03-gestion-mascotas.feature +++ /dev/null @@ -1,154 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/pet-owner/03-gestion-mascotas.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_pets.py -# Tests Frontend: npx playwright test pets.spec.ts - -Característica: Gestion de mascotas - - Como dueno de mascota registrado - Quiero gestionar la informacion de mis mascotas - Para tenerla actualizada y acceder a su historial medico - - Antecedentes: - Dado que estoy logueado como dueno de mascota - Y estoy en la seccion "Mis mascotas" - - # ============================================ - # AGREGAR MASCOTA - # ============================================ - - Escenario: Agregar mascota con datos minimos - Cuando hago click en "Agregar mascota" - Y completo el formulario: - | campo | valor | - | nombre | Luna | - | tipo | Gato | - Y hago click en "Guardar" - Entonces deberia ver "Luna" en mi lista de mascotas - Y Luna deberia aparecer disponible en el turnero - - Escenario: Agregar mascota con datos completos - Cuando hago click en "Agregar mascota" - Y completo el formulario: - | campo | valor | - | nombre | Rocky | - | tipo | Perro | - | raza | Labrador | - | fecha_nacimiento | 2020-03-15 | - | peso | 25 | - | sexo | Macho | - | castrado | Si | - Y subo una foto de Rocky - Y hago click en "Guardar" - Entonces deberia ver "Rocky" con su foto en mi lista - Y deberia ver badge "Castrado" - - Esquema del escenario: Validacion de datos de mascota - Cuando intento agregar mascota con igual a "" - Entonces deberia ver error "" - - Ejemplos: - | campo | valor | mensaje | - | nombre | | El nombre es obligatorio | - | nombre | A | Nombre muy corto | - | tipo | | Selecciona el tipo | - | peso | -5 | El peso debe ser positivo | - | peso | 500 | Peso fuera de rango | - - # ============================================ - # EDITAR MASCOTA - # ============================================ - - Escenario: Editar peso de mascota - Dado que tengo una mascota "Luna" con peso 4kg - Cuando edito a Luna - Y cambio el peso a 5kg - Y guardo los cambios - Entonces Luna deberia mostrar peso "5 kg" - - Escenario: Marcar mascota como castrada - Dado que tengo una mascota "Rocky" no castrado - Cuando edito a Rocky - Y marco "Esta castrado" - Y guardo los cambios - Entonces Rocky deberia mostrar badge "Castrado" - Y el servicio "Castracion" no deberia aparecer para Rocky en el turnero - - Escenario: Actualizar foto de mascota - Dado que tengo una mascota "Luna" sin foto - Cuando edito a Luna - Y subo una nueva foto - Y guardo los cambios - Entonces deberia ver la foto de Luna en su tarjeta - - # ============================================ - # ELIMINAR MASCOTA - # ============================================ - - Escenario: Eliminar mascota sin historial - Dado que tengo una mascota "Nuevo" sin visitas - Cuando hago click en "Eliminar" para Nuevo - Y confirmo la eliminacion - Entonces Nuevo no deberia aparecer en mi lista - # Nota: Es soft delete - - Escenario: Eliminar mascota con historial medico - Dado que tengo una mascota "Luna" con visitas anteriores - Cuando hago click en "Eliminar" para Luna - Entonces deberia ver advertencia "Luna tiene historial medico" - Y deberia ver "El historial se conservara pero no podras verlo" - Cuando confirmo la eliminacion - Entonces Luna no deberia aparecer en mi lista - - Escenario: No puedo eliminar mascota con turno pendiente - Dado que tengo una mascota "Rocky" con turno pendiente - Cuando intento eliminar a Rocky - Entonces deberia ver error "Rocky tiene turnos pendientes" - Y deberia ver sugerencia "Cancela los turnos primero" - - # ============================================ - # VER HISTORIAL MEDICO - # ============================================ - - Escenario: Ver historial de visitas de mascota - Dado que tengo una mascota "Luna" con 3 visitas completadas - Cuando hago click en Luna - Y voy a la seccion "Historial" - Entonces deberia ver 3 visitas listadas - Y deberian estar ordenadas por fecha descendente - - Escenario: Ver detalle de visita - Dado que tengo una mascota "Luna" con visitas - Cuando veo el historial de Luna - Y hago click en la primera visita - Entonces deberia ver: - | campo | - | Fecha | - | Veterinario | - | Diagnostico | - | Tratamiento | - | Medicamentos | - - Escenario: Mascota sin historial - Dado que tengo una mascota "Nuevo" recien agregada - Cuando veo el perfil de Nuevo - Entonces la seccion "Historial" deberia estar vacia - Y deberia ver mensaje "Aun no hay visitas registradas" - Y deberia ver boton "Reservar primer turno" - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Mascota heredada de cuenta invitado - Dado que me registre con email "juan@test.com" - Y previamente reserve turno como invitado para "Firulais" - Cuando voy a "Mis mascotas" - Entonces deberia ver "Firulais" en mi lista - Y deberia ver su historial de visitas previas - - Escenario: Razas filtradas por tipo - Cuando agrego una mascota tipo "Gato" - Entonces las razas disponibles deberian ser razas de gato - Y no deberia ver razas de perro como "Labrador" diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/04-pago-turno.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/04-pago-turno.feature deleted file mode 100644 index 2c9e11f..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/04-pago-turno.feature +++ /dev/null @@ -1,141 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/pet-owner/04-pago-turno.md -# Drive: 07. Finanzas y contabilidad/Mercado Pago -# Tests Backend: pytest tests/contracts/payments/test_mercadopago.py -# Tests Frontend: npx playwright test payment.spec.ts - -Característica: Pago de turno - - Como dueno de mascota con turno coordinado - Quiero pagar mi turno online - Para confirmar la visita del veterinario - - Antecedentes: - Dado que tengo un turno en estado "Coordinado" - Y el turno tiene asignado veterinario "Dra. Garcia" - Y la fecha asignada es "15 de enero a las 10:00" - - # ============================================ - # FLUJO DE PAGO EXITOSO - # ============================================ - - Escenario: Pagar turno con tarjeta de credito - Dado que estoy en el detalle de mi turno coordinado - Cuando hago click en "Pagar" - Entonces deberia ser redirigido a Mercado Pago - Y deberia ver el monto correcto - - Cuando selecciono "Tarjeta de credito" - Y completo los datos de la tarjeta - Y confirmo el pago - Entonces deberia volver a la plataforma - Y deberia ver "Pago exitoso" - Y el turno deberia estar en estado "Pagado" - - Escenario: Recibir confirmacion de pago - Dado que complete el pago exitosamente - Entonces deberia recibir email de confirmacion - Y el email deberia contener: - | campo | - | Fecha del turno | - | Direccion | - | Veterinario asignado | - | Monto pagado | - | Numero de operacion | - - # ============================================ - # METODOS DE PAGO - # ============================================ - - Esquema del escenario: Pagar con diferentes metodos - Dado que estoy en Mercado Pago - Cuando selecciono metodo "" - Y completo el pago - Entonces el pago deberia ser - Y el estado de acreditacion deberia ser "" - - Ejemplos: - | metodo | resultado | acreditacion | - | Tarjeta credito | exitoso | inmediata | - | Tarjeta debito | exitoso | inmediata | - | Dinero en cuenta | exitoso | inmediata | - | Transferencia | pendiente | 1-2 dias | - | Rapipago | pendiente | hasta 24hs | - - # ============================================ - # MANEJO DE ERRORES - # ============================================ - - Escenario: Pago rechazado por fondos insuficientes - Dado que estoy en Mercado Pago - Cuando intento pagar con tarjeta sin fondos - Entonces deberia ver error "Fondos insuficientes" - Y deberia poder reintentar con otra tarjeta - Y el turno deberia seguir en estado "Coordinado" - - Escenario: Usuario cancela el pago - Dado que estoy en Mercado Pago - Cuando hago click en "Volver al sitio" - Entonces deberia volver a la plataforma - Y deberia ver mensaje "El pago fue cancelado" - Y deberia ver boton "Reintentar pago" - Y el turno deberia seguir en estado "Coordinado" - - Escenario: Cierre de browser durante pago - Dado que estoy en Mercado Pago - Y cierro el navegador accidentalmente - Cuando vuelvo a la plataforma - Y voy a "Mis turnos" - Entonces deberia poder ver el estado real del pago - # Si se proceso: Pagado. Si no: Coordinado con opcion de pagar - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Link de pago expirado - Dado que recibi el link de pago hace mas de 24 horas - Cuando hago click en el link - Entonces deberia ver "Este link ha expirado" - Y deberia ver "Contacta a soporte para generar uno nuevo" - - Escenario: Intento pagar turno ya pagado - Dado que mi turno ya esta en estado "Pagado" - Cuando accedo al link de pago - Entonces deberia ver "Este turno ya fue pagado" - Y deberia ver boton "Ver detalle del turno" - - Escenario: Precio cambio desde la coordinacion - # Caso muy raro pero posible - Dado que el precio del servicio aumento desde que se coordino - Cuando voy a pagar - Entonces deberia ver el precio original acordado - # El precio se congela al momento de coordinacion - - # ============================================ - # INTEGRACION CON WEBHOOK - # ============================================ - - Regla: El estado del turno se actualiza via webhook de Mercado Pago - - Escenario: Webhook confirma pago aprobado - Dado que el usuario completo el pago en Mercado Pago - Cuando Mercado Pago envia webhook con status "approved" - Entonces el turno deberia cambiar a estado "Pagado" - Y el veterinario deberia recibir notificacion - Y el usuario deberia recibir email de confirmacion - - Escenario: Webhook informa pago pendiente - Dado que el usuario pago con transferencia bancaria - Cuando Mercado Pago envia webhook con status "pending" - Entonces el turno deberia quedarse en "Coordinado" - Y deberia registrarse el pago pendiente - Y el usuario deberia recibir email "Esperando acreditacion" - - Escenario: Webhook falla pero pago se proceso - # Caso de error que requiere intervencion manual - Dado que el usuario pago exitosamente - Pero el webhook fallo por error de red - Entonces el turno seguira en "Coordinado" - Y el equipo de ops deberia recibir alerta - Y deberian poder actualizar manualmente diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/05-historial-medico.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/05-historial-medico.feature deleted file mode 100644 index e56fc4e..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/pet-owner/05-historial-medico.feature +++ /dev/null @@ -1,148 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/pet-owner/05-historial-medico.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test medical-history.spec.ts - -Característica: Ver historial medico - - Como dueno de mascota - Quiero ver el historial medico de mis mascotas - Para tener registro de sus visitas y tratamientos - - Antecedentes: - Dado que estoy logueado como dueno de mascota - Y tengo una mascota "Luna" con visitas completadas - - # ============================================ - # ACCESO AL HISTORIAL - # ============================================ - - Escenario: Acceder al historial desde mascotas - Dado que estoy en "Mis mascotas" - Cuando hago click en "Luna" - Y voy a la seccion "Historial de visitas" - Entonces deberia ver lista de visitas de Luna - - Escenario: Acceder al historial desde turnos - Dado que estoy en "Mis turnos" - Y filtro por "Completados" - Cuando hago click en un turno de Luna - Y hago click en "Ver informe medico" - Entonces deberia ver el informe de esa visita - - # ============================================ - # VISUALIZACION DE VISITAS - # ============================================ - - Escenario: Ver lista de visitas ordenadas - Dado que Luna tiene visitas en las siguientes fechas: - | fecha | - | 2024-01-15 | - | 2023-11-20 | - | 2023-06-10 | - Cuando veo el historial de Luna - Entonces deberia ver las visitas ordenadas de mas reciente a mas antigua - Y la primera deberia ser del "15 de enero 2024" - - Escenario: Ver detalle completo de informe - Cuando hago click en una visita completada - Entonces deberia ver los siguientes datos: - | seccion | contenido | - | Informacion | Fecha, veterinario, servicios | - | Examen fisico | Peso, temperatura, FC, obs | - | Diagnostico | Descripcion del diagnostico | - | Tratamiento | Plan de tratamiento | - | Medicamentos | Lista con dosis y frecuencia | - | Estudios | Estudios solicitados y estado | - | Seguimiento | Recomendaciones, proximo control | - - Escenario: Ver informe con medicamentos recetados - Dado que la visita incluye medicamentos: - """ - 1. Amoxicilina 250mg - 1 comprimido cada 12hs por 7 dias - 2. Meloxicam 1.5mg - 1 comprimido por dia por 3 dias - """ - Cuando veo el informe - Entonces deberia ver la lista de medicamentos - Y cada medicamento deberia mostrar: - | campo | - | Nombre | - | Dosis | - | Frecuencia | - | Duracion | - - # ============================================ - # DESCARGA Y COMPARTIR - # ============================================ - - Escenario: Descargar informe como PDF - Dado que estoy viendo un informe medico - Cuando hago click en "Descargar PDF" - Entonces deberia descargarse un archivo PDF - Y el PDF deberia contener toda la informacion del informe - Y deberia tener el logo de Amar Mascotas - - Escenario: Imprimir informe - Dado que estoy viendo un informe medico - Cuando hago click en "Imprimir" - Entonces deberia abrirse el dialogo de impresion - Y el formato deberia ser optimizado para impresion - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Visita completada sin informe cargado - # El vet no cargo el informe todavia - Dado que Luna tiene una visita marcada como "Completada" - Pero el veterinario no cargo el informe aun - Cuando intento ver el informe - Entonces deberia ver mensaje "Informe pendiente" - Y deberia ver "El veterinario esta completando el informe" - - Escenario: Informe con estudios pendientes - Dado que el informe solicita estudios de laboratorio - Y los resultados aun no estan disponibles - Cuando veo el informe - Entonces deberia ver seccion "Estudios solicitados" - Y deberia ver estado "Pendiente de resultados" - Y deberia ver nota "Se notificara cuando esten listos" - - Escenario: Mascota con multiples visitas el mismo dia - # Raro pero posible: consulta de urgencia + seguimiento - Dado que Luna tuvo 2 visitas el 15 de enero - Cuando veo el historial - Entonces deberia ver ambas visitas listadas - Y deberian estar diferenciadas por hora - - # ============================================ - # PERMISOS Y PRIVACIDAD - # ============================================ - - Regla: Solo el dueno puede ver el historial de su mascota - - Escenario: Dueno ve historial de su mascota - Dado que soy dueno de Luna - Cuando accedo al historial de Luna - Entonces deberia poder verlo completo - - Escenario: No puedo ver historial de mascota ajena - Dado que existe mascota "Rocky" de otro dueno - Cuando intento acceder al historial de Rocky - Entonces deberia ver error "No tienes acceso" - # 403 Forbidden - - # ============================================ - # BUSQUEDA Y FILTROS - # ============================================ - - Escenario: Filtrar historial por tipo de servicio - Dado que Luna tiene visitas de vacunacion y consulta - Cuando filtro por "Vacunacion" - Entonces solo deberia ver visitas de vacunacion - - Escenario: Buscar en historial por fecha - Cuando busco visitas del "2023" - Entonces solo deberia ver visitas del 2023 - Y no deberia ver visitas del 2024 diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature deleted file mode 100644 index 9792239..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/01-aceptar-solicitud.feature +++ /dev/null @@ -1,129 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/veterinarian/01-aceptar-solicitud.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/solicitudes/test_service_requests.py -# Tests Frontend: npx playwright test vet-requests.spec.ts - -Característica: Aceptar o rechazar solicitudes de servicio - - Como veterinario de la plataforma - Quiero revisar y responder a solicitudes en mi zona - Para gestionar mi agenda de visitas a domicilio - - Antecedentes: - Dado que estoy logueado como veterinario - Y tengo cobertura en los barrios "Palermo" y "Recoleta" - Y estoy en el dashboard de veterinario - - # ============================================ - # VER SOLICITUDES PENDIENTES - # ============================================ - - Escenario: Ver lista de solicitudes en mi zona - Cuando veo la seccion "Solicitudes pendientes" - Entonces deberia ver solo solicitudes de "Palermo" y "Recoleta" - Y no deberia ver solicitudes de otros barrios - - Escenario: Ver detalle de solicitud - Dado que hay una solicitud pendiente - Cuando hago click en la solicitud - Entonces deberia ver: - | seccion | contenido | - | Dueno | Nombre, telefono, direccion | - | Mascota | Nombre, tipo, edad, foto | - | Servicios | Lista de servicios solicitados | - | Fechas | Fechas preferidas por el dueno | - | Historial | Visitas anteriores si las hay | - - # ============================================ - # ACEPTAR SOLICITUD - # ============================================ - - Escenario: Aceptar solicitud con fecha disponible - Dado que hay una solicitud para el barrio "Palermo" - Y el dueno prefiere fechas: - | fecha | franja | - | 2024-01-15 | Mañana | - | 2024-01-16 | Tarde | - Y tengo disponibilidad el 15 de enero a las 10:00 - Cuando hago click en "Aceptar" - Y selecciono fecha "15 de enero" hora "10:00" - Y confirmo la aceptacion - Entonces la solicitud deberia pasar a estado "Coordinado" - Y deberia quedar asignada a mi - Y el dueno deberia recibir notificacion con mis datos - Y la visita deberia aparecer en mi agenda - - Escenario: Aceptar solicitud con datos de mascota que ya atendi - Dado que hay una solicitud para mascota "Luna" - Y yo atendi a "Luna" anteriormente - Cuando veo el detalle de la solicitud - Entonces deberia ver badge "Paciente recurrente" - Y deberia ver el historial de mis visitas anteriores a Luna - - # ============================================ - # RECHAZAR SOLICITUD - # ============================================ - - Esquema del escenario: Rechazar solicitud con motivo - Dado que hay una solicitud pendiente - Cuando hago click en "Rechazar" - Y selecciono motivo "" - Y confirmo el rechazo - Entonces la solicitud deberia desaparecer de mi lista - Y deberia seguir visible para otros veterinarios - - Ejemplos: - | motivo | - | No tengo disponibilidad | - | Fuera de mi zona | - | No realizo este servicio | - | Otro | - - Escenario: Rechazar sin seleccionar motivo - Dado que hay una solicitud pendiente - Cuando hago click en "Rechazar" - Y confirmo sin seleccionar motivo - Entonces deberia poder rechazar igual - # El motivo es opcional - - # ============================================ - # RACE CONDITIONS - # ============================================ - - Regla: Solo un veterinario puede aceptar cada solicitud - - Escenario: Otro vet acepta mientras estoy viendo - Dado que estoy viendo el detalle de una solicitud - Y otro veterinario acepta la misma solicitud - Cuando intento aceptarla - Entonces deberia ver error "Esta solicitud ya fue aceptada" - Y deberia ser redirigido al listado - - Escenario: Solicitud desaparece cuando otro la acepta - Dado que estoy viendo el listado de solicitudes - Y otro veterinario acepta una solicitud - Entonces esa solicitud deberia desaparecer de mi lista - # Idealmente en tiempo real via websocket - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Solicitud urgente destacada - Dado que hay una solicitud marcada como "Urgente" - Cuando veo el listado de solicitudes - Entonces deberia ver la solicitud con indicador de urgencia - Y deberia aparecer primero en la lista - - Escenario: Solicitud con multiples mascotas - Dado que hay una solicitud para 2 mascotas - Cuando veo el detalle - Entonces deberia ver informacion de ambas mascotas - Y deberia ver nota sobre tiempo estimado adicional - - Escenario: No puedo aceptar con agenda completa - Dado que tengo mi agenda completa para las fechas de la solicitud - Cuando intento aceptar la solicitud - Entonces deberia ver advertencia "No tienes disponibilidad en las fechas preferidas" - Y deberia poder proponer una fecha alternativa diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/02-gestion-agenda.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/02-gestion-agenda.feature deleted file mode 100644 index 3fe6149..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/02-gestion-agenda.feature +++ /dev/null @@ -1,134 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/veterinarian/02-gestion-agenda.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_availability.py -# Tests Frontend: npx playwright test vet-schedule.spec.ts - -Característica: Gestion de agenda veterinaria - - Como veterinario - Quiero gestionar mi calendario y disponibilidad - Para organizar mis visitas a domicilio eficientemente - - Antecedentes: - Dado que estoy logueado como veterinario - Y estoy en la seccion "Mi agenda" - - # ============================================ - # VER AGENDA - # ============================================ - - Escenario: Ver calendario semanal - Cuando veo mi agenda en vista semanal - Entonces deberia ver los 7 dias de la semana - Y deberia ver mis visitas programadas - Y cada visita deberia mostrar hora y nombre del paciente - - Escenario: Ver visitas con codigo de color por estado - Dado que tengo visitas en diferentes estados - Cuando veo mi agenda - Entonces las visitas deberian mostrarse con colores: - | estado | color | - | Coordinado | amarillo | - | Pagado | verde | - | En progreso | azul | - | Completado | gris | - - Escenario: Ver detalle de visita desde el calendario - Dado que tengo una visita programada para hoy - Cuando hago click en la visita - Entonces deberia ver popup con: - | campo | valor | - | Hora | 10:00 - 11:00 | - | Dueno | Maria Garcia | - | Direccion | Av Santa Fe 1234 | - | Mascota | Luna (Gato, 3 años) | - | Servicios | Vacunacion, Consulta | - | Estado pago | Pagado | - Y deberia ver boton "Ver detalle completo" - - # ============================================ - # CONFIGURAR DISPONIBILIDAD SEMANAL - # ============================================ - - Escenario: Configurar horario laboral - Cuando voy a "Configuracion" -> "Mi disponibilidad" - Y configuro mi horario: - | dia | trabajo | desde | hasta | pausa_desde | pausa_hasta | - | Lunes | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Martes | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Miercoles | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Jueves | Si | 09:00 | 18:00 | 13:00 | 14:00 | - | Viernes | Si | 09:00 | 15:00 | | | - | Sabado | No | | | | | - | Domingo | No | | | | | - Y guardo la configuracion - Entonces mi disponibilidad deberia actualizarse - Y no deberia recibir solicitudes fuera de ese horario - - # ============================================ - # BLOQUEAR DIAS ESPECIFICOS - # ============================================ - - Escenario: Marcar dia como no disponible - Dado que necesito el 20 de enero libre - Cuando hago click en el dia 20 de enero en el calendario - Y selecciono "Marcar como no disponible" - Y ingreso motivo "Vacaciones" - Y confirmo - Entonces el dia 20 deberia mostrarse como bloqueado - Y no deberia poder aceptar solicitudes para ese dia - - Escenario: Bloquear rango de fechas - Dado que tomo vacaciones del 15 al 22 de enero - Cuando voy a "Agregar indisponibilidad" - Y selecciono fecha inicio "15 de enero" - Y selecciono fecha fin "22 de enero" - Y ingreso motivo "Vacaciones" - Y confirmo - Entonces todos esos dias deberian mostrarse como bloqueados - - Escenario: Desbloquear dia - Dado que tengo el 20 de enero bloqueado - Y ya no necesito ese dia libre - Cuando hago click en el dia bloqueado - Y selecciono "Quitar bloqueo" - Entonces el dia deberia estar disponible nuevamente - - # ============================================ - # IMPACTO EN SOLICITUDES - # ============================================ - - Regla: Los bloqueos no afectan visitas ya aceptadas - - Escenario: Bloquear dia con visita ya aceptada - Dado que tengo una visita aceptada para el 15 de enero - Cuando intento bloquear el 15 de enero - Entonces deberia ver advertencia "Tienes una visita programada ese dia" - Y deberia poder elegir: - | opcion | - | Cancelar el bloqueo | - | Bloquear y contactar al dueno | - - Regla: Cambios de disponibilidad solo afectan solicitudes futuras - - Escenario: Cambiar horario no afecta visitas existentes - Dado que tengo una visita a las 17:00 el lunes - Cuando cambio mi horario del lunes para terminar a las 16:00 - Y guardo los cambios - Entonces la visita de las 17:00 deberia mantenerse - Y deberia ver advertencia sobre la inconsistencia - - # ============================================ - # NAVEGACION - # ============================================ - - Escenario: Navegar entre semanas - Cuando hago click en "Semana siguiente" - Entonces deberia ver el calendario de la proxima semana - Y deberia poder volver con "Semana anterior" - - Escenario: Ir a fecha especifica - Cuando hago click en el selector de fecha - Y selecciono "15 de marzo" - Entonces deberia ver la semana que contiene el 15 de marzo diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/03-realizar-visita.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/03-realizar-visita.feature deleted file mode 100644 index a5ecc4f..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/03-realizar-visita.feature +++ /dev/null @@ -1,159 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/veterinarian/03-realizar-visita.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test vet-visit.spec.ts - -Característica: Realizar visita y crear informe medico - - Como veterinario - Quiero registrar la atencion medica de mis visitas - Para mantener el historial clinico del paciente - - Antecedentes: - Dado que estoy logueado como veterinario - Y tengo una visita programada para hoy con mascota "Luna" - Y la visita esta en estado "Pagado" - - # ============================================ - # INICIAR VISITA - # ============================================ - - Escenario: Ver informacion antes de la visita - Cuando accedo al detalle de la visita - Entonces deberia ver la direccion para llegar - Y deberia ver el telefono del dueno para confirmar - Y deberia ver los servicios a realizar - Y deberia ver el historial previo de Luna - - Escenario: Iniciar visita al llegar - Dado que llegue al domicilio - Cuando hago click en "Iniciar visita" - Entonces el estado deberia cambiar a "En progreso" - Y deberia registrarse la hora de inicio - Y deberia habilitarse el boton "Crear informe" - - # ============================================ - # CREAR INFORME MEDICO - # ============================================ - - Escenario: Completar informe con examen fisico - Dado que la visita esta en progreso - Cuando voy a "Crear informe" - Y completo el examen fisico: - | campo | valor | - | Peso | 4.5 kg | - | Temperatura | 38.5 °C | - | Frecuencia cardiaca | 120 lpm | - | Frecuencia resp | 25 rpm | - | Mucosas | Rosadas | - | Hidratacion | Normal | - Y guardo el informe - Entonces el examen fisico deberia guardarse - - Escenario: Agregar diagnostico y tratamiento - Dado que complete el examen fisico - Cuando agrego el diagnostico: - """ - Otitis externa bilateral leve. - Paciente presenta prurito y secrecion ceruminosa. - Sin signos de infeccion secundaria. - """ - Y agrego el tratamiento: - """ - Limpieza de oidos con solucion fisiologica. - Aplicacion de gotas oticas antibioticas. - Control en 7 dias. - """ - Y guardo el informe - Entonces el diagnostico y tratamiento deberian guardarse - - Escenario: Recetar medicamentos - Dado que estoy creando el informe - Cuando agrego medicamentos: - | nombre | dosis | frecuencia | duracion | - | Otomax gotas | 5 gotas | cada 12 horas | 7 dias | - | Meloxicam 1.5mg | 1 comp | cada 24 horas | 3 dias | - Y guardo el informe - Entonces los medicamentos deberian aparecer en el informe - Y el dueno podra verlos desde su cuenta - - Escenario: Solicitar estudios complementarios - Dado que necesito estudios de laboratorio - Cuando agrego estudio: - | tipo | indicaciones | - | Hemograma completo | Ayuno de 8 horas | - | Perfil hepatico | Para control pre-quirurgico | - Y guardo el informe - Entonces los estudios deberian quedar registrados - Y deberian mostrarse como "Pendientes de resultado" - - # ============================================ - # COMPLETAR VISITA - # ============================================ - - Escenario: Completar visita con informe - Dado que el informe esta completo - Cuando hago click en "Completar visita" - Y confirmo la finalizacion - Entonces la visita deberia pasar a estado "Completado" - Y deberia generarse factura electronica (AFIP) - Y el dueno deberia recibir notificacion - Y el informe deberia ser visible para el dueno - - Escenario: No puedo completar sin informe - Dado que la visita esta en progreso - Pero no cree ningun informe - Cuando intento completar la visita - Entonces deberia ver error "Debes crear el informe antes de completar" - - # ============================================ - # GUARDADO AUTOMATICO - # ============================================ - - Regla: El informe se guarda automaticamente - - Escenario: Guardado automatico cada 30 segundos - Dado que estoy escribiendo el informe - Cuando pasan 30 segundos - Entonces deberia ver indicador "Guardado automaticamente" - - Escenario: Recuperar informe despues de desconexion - Dado que estaba escribiendo el informe - Y perdi conexion a internet - Cuando recupero la conexion - Entonces deberia recuperar mi progreso - Y no deberia perder lo que escribi - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Visita con multiples mascotas - Dado que la visita incluye a "Luna" y "Rocky" - Cuando creo el informe - Entonces deberia crear un informe separado para cada mascota - Y ambos informes deberian estar asociados a la misma visita - - Escenario: Cancelar visita in situ - Dado que llegue al domicilio - Pero el dueno no esta - Cuando marco la visita como "Cancelada in situ" - Y selecciono motivo "Dueno ausente" - Entonces la visita deberia marcarse como cancelada - Y deberia generarse cargo por visita fallida - # Segun politicas de la empresa - - Escenario: Emergencia durante visita rutinaria - Dado que estoy en una consulta de vacunacion - Y detecto una condicion que requiere atencion urgente - Cuando agrego servicio "Consulta de urgencia" - Entonces deberia poder documentar ambos servicios - Y el precio deberia ajustarse - - Escenario: Derivar a clinica - Dado que el paciente necesita atencion en clinica - Cuando marco "Derivacion a clinica" - Y especifico el motivo - Entonces deberia quedar registrada la derivacion - Y el dueno deberia recibir instrucciones diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature deleted file mode 100644 index 6056215..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/04-zonas-cobertura.feature +++ /dev/null @@ -1,121 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/veterinarian/04-zonas-cobertura.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_veterinarians.py -# Tests Frontend: npx playwright test vet-coverage.spec.ts - -Característica: Gestionar zonas de cobertura - - Como veterinario - Quiero definir en que zonas atiendo - Para recibir solo solicitudes que puedo cubrir - - Antecedentes: - Dado que estoy logueado como veterinario - Y estoy en la seccion "Mi cobertura" - - # ============================================ - # VER ZONAS ACTUALES - # ============================================ - - Escenario: Ver mapa con mis zonas de cobertura - Cuando cargo la pagina de cobertura - Entonces deberia ver un mapa de la ciudad - Y mis zonas cubiertas deberian estar resaltadas - Y deberia ver un listado de barrios seleccionados - - Escenario: Ver estadisticas por zona - Dado que tengo cobertura en "Palermo" y "Recoleta" - Cuando veo el detalle de mis zonas - Entonces deberia ver para cada zona: - | metrica | - | Solicitudes este mes | - | Visitas completadas | - | Otros vets en la zona | - - # ============================================ - # AGREGAR ZONAS - # ============================================ - - Escenario: Agregar barrio desde el mapa - Dado que no tengo cobertura en "Belgrano" - Cuando hago click en "Belgrano" en el mapa - Y confirmo agregar la zona - Entonces "Belgrano" deberia aparecer en mi lista de zonas - Y deberia empezar a ver solicitudes de Belgrano - - Escenario: Agregar barrio desde el buscador - Cuando busco "Nuñez" en el buscador de barrios - Y selecciono "Nuñez" de los resultados - Y confirmo agregar la zona - Entonces "Nuñez" deberia aparecer en mi lista - - Escenario: Agregar multiples zonas a la vez - Cuando selecciono los barrios: - | barrio | - | Colegiales | - | Chacarita | - | Villa Crespo | - Y hago click en "Agregar seleccionados" - Entonces los 3 barrios deberian agregarse a mi cobertura - - # ============================================ - # QUITAR ZONAS - # ============================================ - - Escenario: Quitar zona de cobertura - Dado que tengo cobertura en "Recoleta" - Cuando hago click en "X" junto a "Recoleta" - Y confirmo quitar la zona - Entonces "Recoleta" no deberia estar en mi lista - Y no deberia ver nuevas solicitudes de Recoleta - - Escenario: Quitar zona con solicitudes pendientes - Dado que tengo cobertura en "Palermo" - Y hay solicitudes pendientes en Palermo que no acepte - Cuando intento quitar "Palermo" - Entonces deberia ver advertencia "Hay X solicitudes pendientes en esta zona" - Y deberia poder confirmar de todas formas - # Las solicitudes pendientes siguen disponibles para otros vets - - # ============================================ - # IMPACTO EN SOLICITUDES - # ============================================ - - Regla: Los cambios de zona afectan solo solicitudes nuevas - - Escenario: Agregar zona muestra solicitudes existentes - Dado que no tengo cobertura en "Belgrano" - Y hay 3 solicitudes pendientes en Belgrano - Cuando agrego "Belgrano" a mi cobertura - Entonces deberia ver las 3 solicitudes pendientes de Belgrano - - Escenario: Quitar zona no afecta visitas aceptadas - Dado que tengo cobertura en "Recoleta" - Y tengo una visita aceptada en Recoleta para mañana - Cuando quito "Recoleta" de mi cobertura - Entonces la visita de mañana deberia mantenerse - Pero no deberia ver nuevas solicitudes de Recoleta - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Zona no disponible en el sistema - Dado que quiero cubrir "Barrio Nuevo" - Pero ese barrio no esta en el sistema - Cuando busco "Barrio Nuevo" - Entonces deberia ver mensaje "Zona no encontrada" - Y deberia ver opcion "Solicitar que agreguen esta zona" - - Escenario: Zona con baja demanda - Dado que agrego una zona con poca actividad - Cuando pasan 30 dias sin solicitudes - Entonces deberia recibir sugerencia "Considera expandir tu cobertura" - - Escenario: Zona saturada de veterinarios - Dado que quiero agregar "Palermo" - Y hay muchos veterinarios cubriendo Palermo - Cuando agrego la zona - Entonces deberia ver nota "Esta zona tiene alta cobertura de veterinarios" - # Informativo, no bloquea diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/05-historial-pacientes.feature b/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/05-historial-pacientes.feature deleted file mode 100644 index 16c9915..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/es/veterinarian/05-historial-pacientes.feature +++ /dev/null @@ -1,157 +0,0 @@ -# language: es -# Fuente: album/book/ops-templates/veterinarian/05-historial-pacientes.md -# Drive: 08. IT y Producto/Funcionalidades Plataforma Veterinario -# Tests Backend: pytest tests/contracts/mascotas/test_vet_visits.py -# Tests Frontend: npx playwright test vet-history.spec.ts - -Característica: Ver historial de pacientes - - Como veterinario - Quiero acceder al historial medico de pacientes - Para tener contexto clinico en mis atenciones - - Antecedentes: - Dado que estoy logueado como veterinario - Y estoy en la seccion "Historia clinica" - - # ============================================ - # BUSCAR PACIENTES - # ============================================ - - Esquema del escenario: Buscar paciente por diferentes criterios - Cuando busco por con valor "" - Entonces deberia ver resultados que coincidan - - Ejemplos: - | criterio | valor | - | nombre dueno | Maria Garcia | - | nombre mascota | Luna | - | telefono | 1155551234 | - | email | maria@ejemplo.com | - - Escenario: Busqueda sin resultados - Cuando busco "ZZZZZ paciente inexistente" - Entonces deberia ver mensaje "No se encontraron resultados" - Y deberia ver sugerencia "Verifica la ortografia" - - Escenario: Busqueda con multiples resultados - Cuando busco "Garcia" - Y hay varios duenos con apellido Garcia - Entonces deberia ver lista de coincidencias - Y cada resultado deberia mostrar: - | campo | - | Nombre completo | - | Email | - | Mascotas | - - # ============================================ - # VER FICHA DE MASCOTA - # ============================================ - - Escenario: Ver ficha completa de mascota - Dado que encontre a la mascota "Luna" - Cuando hago click en Luna - Entonces deberia ver la ficha con: - | seccion | contenido | - | Datos basicos | Nombre, tipo, raza, edad, peso | - | Foto | Foto de la mascota | - | Dueno | Nombre y contacto del dueno | - | Vacunacion | Estado de vacunas | - | Historial | Lista de visitas | - - Escenario: Ver grafico de peso historico - Dado que estoy viendo la ficha de "Luna" - Y Luna tuvo multiples visitas con peso registrado - Cuando veo la seccion "Evolucion" - Entonces deberia ver grafico de peso en el tiempo - Y deberia poder detectar tendencias - - # ============================================ - # VER HISTORIAL DE VISITAS - # ============================================ - - Escenario: Ver listado de visitas - Dado que estoy viendo la ficha de "Luna" - Y Luna tiene 5 visitas completadas - Cuando veo la seccion "Historial de visitas" - Entonces deberia ver las 5 visitas listadas - Y deberian estar ordenadas de mas reciente a mas antigua - Y cada visita deberia mostrar: - | campo | - | Fecha | - | Veterinario | - | Servicios | - | Diagnostico | - - Escenario: Ver informe de visita de otro veterinario - Dado que Luna fue atendida por "Dra. Rodriguez" - Y yo no la atendi en esa visita - Cuando hago click en esa visita - Entonces deberia poder ver el informe completo - # Para continuidad de atencion - - Escenario: Ver informe detallado - Cuando hago click en una visita - Entonces deberia ver el informe con: - | seccion | contenido | - | Examen fisico | Peso, temperatura, signos vitales | - | Diagnostico | Descripcion del diagnostico | - | Tratamiento | Plan de tratamiento indicado | - | Medicamentos | Lista con dosis y duracion | - | Estudios | Estudios solicitados y resultados | - | Observaciones | Notas adicionales | - - # ============================================ - # FILTROS Y NAVEGACION - # ============================================ - - Escenario: Filtrar historial por tipo de servicio - Dado que estoy viendo el historial de "Luna" - Cuando filtro por servicio "Vacunacion" - Entonces solo deberia ver visitas de vacunacion - - Escenario: Filtrar historial por fecha - Dado que estoy viendo el historial de "Luna" - Cuando filtro por año "2023" - Entonces solo deberia ver visitas del 2023 - - Escenario: Filtrar por mis atenciones - Dado que estoy viendo el historial de "Luna" - Y Luna fue atendida por varios veterinarios - Cuando marco "Solo mis atenciones" - Entonces solo deberia ver las visitas que yo realice - - # ============================================ - # PERMISOS - # ============================================ - - Regla: Veterinarios pueden ver historial de pacientes que atendieron - - Escenario: Puedo ver historial de paciente que atendi - Dado que yo atendi a "Luna" al menos una vez - Cuando busco a Luna - Entonces deberia poder ver su historial completo - - Escenario: Puedo ver historial de paciente con solicitud pendiente - Dado que hay una solicitud pendiente para "Rocky" - Y la solicitud esta en mi zona - Cuando busco a Rocky - Entonces deberia poder ver su historial - # Para evaluar si acepto la solicitud - - # ============================================ - # CASOS ESPECIALES - # ============================================ - - Escenario: Paciente sin historial previo - Dado que busco a "Nuevo" que nunca fue atendido - Cuando veo su ficha - Entonces el historial deberia estar vacio - Y deberia ver mensaje "Sin visitas registradas" - - Escenario: Dueno con multiples mascotas - Dado que busque al dueno "Maria Garcia" - Y Maria tiene 3 mascotas - Cuando veo su perfil - Entonces deberia ver las 3 mascotas listadas - Y deberia poder navegar al historial de cada una diff --git a/cfg/amar/soleprint/atlas/books/gherkin-samples/index.html b/cfg/amar/soleprint/atlas/books/gherkin-samples/index.html deleted file mode 100644 index b295873..0000000 --- a/cfg/amar/soleprint/atlas/books/gherkin-samples/index.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - Gherkin Samples - Album - - - -
-
- -

Gherkin Samples

-

BDD feature files - Given/When/Then specifications derived from ops templates

- Sample Data -
- -
- - -
- - - - - - - - -
- - - - diff --git a/cfg/amar/soleprint/docker-compose.nginx.yml b/cfg/amar/soleprint/docker-compose.nginx.yml deleted file mode 100644 index bd05c46..0000000 --- a/cfg/amar/soleprint/docker-compose.nginx.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Nginx Reverse Proxy for Local Development -# -# This is OPTIONAL - only for local development -# AWS uses bare metal nginx instead -# -# Usage: -# docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d -# -# Before using: -# 1. Generate nginx config: cd ../ctrl/server && ./setup.sh --local -# 2. Stop bare metal nginx: sudo systemctl stop nginx (if installed) -# -# This nginx container will: -# - Listen on port 80 -# - Route amarmascotas.local.com to amar frontend/backend -# - Route soleprint.local.com to soleprint services - -services: - nginx: - image: nginx:alpine - container_name: ${DEPLOYMENT_NAME}_nginx - ports: - - "${NGINX_PORT:-8030}:80" - volumes: - # Mount template that will be processed with envsubst - - ./nginx/local.conf:/etc/nginx/conf.d/default.conf:ro - env_file: - - .env - environment: - - DEPLOYMENT_NAME=${DEPLOYMENT_NAME} - - ROOM_NAME=${ROOM_NAME} - - MANAGED_DOMAIN=${MANAGED_DOMAIN} - - SOLEPRINT_DOMAIN=${SOLEPRINT_DOMAIN} - networks: - - default - depends_on: - - soleprint - restart: unless-stopped - -networks: - default: - external: true - name: ${NETWORK_NAME} diff --git a/cfg/amar/soleprint/docker-compose.yml b/cfg/amar/soleprint/docker-compose.yml deleted file mode 100644 index 6b7fb33..0000000 --- a/cfg/amar/soleprint/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Soleprint Services - Amar Room -# -# Runs soleprint hub as a single service -# Artery, atlas, station are accessed via path-based routing -# -# Usage: -# cd gen/amar/soleprint && docker compose up - -name: ${DEPLOYMENT_NAME} - -services: - soleprint: - build: - context: . - dockerfile: Dockerfile - container_name: ${DEPLOYMENT_NAME}_soleprint - user: "${UID:-1000}:${GID:-1000}" - volumes: - - .:/app - ports: - - "${SOLEPRINT_PORT}:8000" - env_file: - - .env - environment: - - ARTERY_EXTERNAL_URL=/artery - - ATLAS_EXTERNAL_URL=/atlas - - STATION_EXTERNAL_URL=/station - networks: - - default - command: uvicorn run:app --host 0.0.0.0 --port 8000 --reload - -networks: - default: - name: ${NETWORK_NAME} diff --git a/cfg/amar/soleprint/station/monitors/turnos/__init__.py b/cfg/amar/soleprint/station/monitors/turnos/__init__.py deleted file mode 100644 index 15ea7cc..0000000 --- a/cfg/amar/soleprint/station/monitors/turnos/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Turnos Monitor - At-a-glance view of request/turno pipeline. - -Shows the flow: Request -> Vet Accept -> Payment -> Turno -Color-coded by state, minimal info (vet - pet owner). -""" diff --git a/cfg/amar/soleprint/station/monitors/turnos/index.html b/cfg/amar/soleprint/station/monitors/turnos/index.html deleted file mode 100644 index 11907fe..0000000 --- a/cfg/amar/soleprint/station/monitors/turnos/index.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - Turnos · {{ nest_name }} - - - -
-

- Turnos - {{ nest_name }} -

-
-
- Pipeline - List -
-
- {{ total }} - activos -
-
-
- - {% if total == 0 %} -
- No hay solicitudes activas -
- {% else %} -
- {% for state in active_states %} - {% set info = states.get(state, ('?', '#888', 99)) %} - {% set items = by_state.get(state, []) %} -
-
- {{ info[0] }} - {{ items|length }} -
-
- {% for item in items %} -
-
#{{ item.id }}
-
{{ item.vet }}
-
{{ item.petowner }}
-
- {% if item.is_paid %}{% endif %} - {% if item.is_scheduled %}{% endif %} - {{ item.age_hours }}h -
-
- {% endfor %} -
-
- {% endfor %} -
- {% endif %} - - - - diff --git a/cfg/amar/soleprint/station/monitors/turnos/list.html b/cfg/amar/soleprint/station/monitors/turnos/list.html deleted file mode 100644 index 096a7ed..0000000 --- a/cfg/amar/soleprint/station/monitors/turnos/list.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - Turnos List · {{ nest_name }} - - - -
-

- Turnos - {{ nest_name }} -

-
-
- Pipeline - List -
-
{{ total }}
-
-
- - {% if total == 0 %} -
No hay solicitudes activas
- {% else %} - - - - - - - - - - - - - {% for item in items %} - - - - - - - - - {% endfor %} - -
#EstadoVeterinarioClienteFlagsEdad
{{ item.id }} - - {{ item.state_label }} - - {{ item.vet }}{{ item.petowner }} - {% if item.is_paid %}{% endif %} - {% if item.is_scheduled %}{% endif %} - {{ item.age_hours }}h
- {% endif %} - - - - diff --git a/cfg/amar/soleprint/station/monitors/turnos/main.py b/cfg/amar/soleprint/station/monitors/turnos/main.py deleted file mode 100644 index 5a2ad31..0000000 --- a/cfg/amar/soleprint/station/monitors/turnos/main.py +++ /dev/null @@ -1,270 +0,0 @@ -""" -Turnos Monitor - At-a-glance view of the request/turno pipeline. - -Request -> Vet Accept + Payment -> Turno (VetVisit) - -Run standalone: - python -m ward.tools.monitors.turnos.main - -Or use uvicorn: - uvicorn ward.tools.monitors.turnos.main:app --port 12010 --reload -""" - -import os -from datetime import datetime -from pathlib import Path -from typing import Optional - -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from fastapi.templating import Jinja2Templates -from sqlalchemy import create_engine, text -from sqlalchemy.engine import Engine - -app = FastAPI(title="Turnos Monitor", version="0.1.0") - -templates = Jinja2Templates(directory=Path(__file__).parent) - -# ============================================================================= -# NEST CONFIG - Pluggable environment targeting -# ============================================================================= -# Default nest: local development database -# Override with env vars or future nest selector UI - -NEST_CONFIG = { - "name": os.getenv("NEST_NAME", "local"), - "db": { - "host": os.getenv("DB_HOST", "localhost"), - "port": os.getenv("DB_PORT", "5433"), # local default - "name": os.getenv("DB_NAME", "amarback"), - "user": os.getenv("DB_USER", "mariano"), - "password": os.getenv("DB_PASSWORD", ""), - } -} - - -def get_db_url() -> str: - """Build database URL from nest config.""" - db = NEST_CONFIG["db"] - return f"postgresql://{db['user']}:{db['password']}@{db['host']}:{db['port']}/{db['name']}" - - -# ============================================================================= -# STATE DEFINITIONS -# ============================================================================= -# Pipeline states with labels, colors, and order - -STATES = { - "pending": ("Sin Atender", "#fbbf24", 1), # amber - "in_progress_vet": ("Buscando Vet", "#f97316", 2), # orange - "vet_asked": ("Esperando Vet", "#fb923c", 3), # orange light - "vet_accepted": ("Vet OK", "#4ade80", 4), # green - "in_progress_pay": ("Esp. Pago", "#60a5fa", 5), # blue - "payed": ("Pagado", "#2dd4bf", 6), # teal - "coordinated": ("Coordinado", "#22c55e", 7), # green - "not_coordinated": ("Sin Coord.", "#facc15", 8), # yellow - "completed": ("Turno", "#059669", 9), # emerald - "rejected": ("Rechazado", "#f87171", 10), # red -} - -# States to show in the active pipeline (exclude end states) -ACTIVE_STATES = [ - "pending", - "in_progress_vet", - "vet_asked", - "vet_accepted", - "in_progress_pay", - "payed", - "not_coordinated", - "coordinated", -] - -# ============================================================================= -# DATABASE -# ============================================================================= - -_engine: Optional[Engine] = None - - -def get_engine() -> Optional[Engine]: - """Get or create database engine (lazy singleton).""" - global _engine - if _engine is None: - try: - _engine = create_engine(get_db_url(), pool_pre_ping=True) - except Exception as e: - print(f"[turnos] DB engine error: {e}") - return _engine - - -def fetch_active_requests() -> list[dict]: - """Fetch active service requests with vet and petowner info.""" - engine = get_engine() - if not engine: - return [] - - query = text(""" - SELECT - sr.id, - sr.state, - sr.created_at, - sr.date_coordinated, - sr.hour_coordinated, - sr.pay_number, - po.first_name || ' ' || COALESCE(po.last_name, '') as petowner_name, - COALESCE(v.first_name || ' ' || v.last_name, '') as vet_name - FROM solicitudes_servicerequest sr - JOIN mascotas_petowner po ON sr.petowner_id = po.id - LEFT JOIN mascotas_veterinarian v ON sr.veterinarian_id = v.id - WHERE sr.state = ANY(:states) - ORDER BY - CASE sr.state - WHEN 'pending' THEN 1 - WHEN 'in_progress_vet' THEN 2 - WHEN 'vet_asked' THEN 3 - WHEN 'vet_accepted' THEN 4 - WHEN 'in_progress_pay' THEN 5 - WHEN 'payed' THEN 6 - WHEN 'not_coordinated' THEN 7 - WHEN 'coordinated' THEN 8 - ELSE 9 - END, - sr.created_at DESC - """) - - try: - with engine.connect() as conn: - result = conn.execute(query, {"states": ACTIVE_STATES}) - rows = result.fetchall() - - requests = [] - for row in rows: - state_info = STATES.get(row.state, ("?", "#888", 99)) - age_h = _hours_since(row.created_at) - - requests.append({ - "id": row.id, - "state": row.state, - "state_label": state_info[0], - "state_color": state_info[1], - "petowner": row.petowner_name.strip(), - "vet": row.vet_name.strip() if row.vet_name else "-", - "is_paid": bool(row.pay_number), - "is_scheduled": bool(row.date_coordinated and row.hour_coordinated), - "age_hours": age_h, - "age_class": "old" if age_h > 48 else ("warn" if age_h > 24 else ""), - }) - return requests - - except Exception as e: - print(f"[turnos] Query error: {e}") - return [] - - -def fetch_counts() -> dict[str, int]: - """Fetch count per state.""" - engine = get_engine() - if not engine: - return {} - - query = text(""" - SELECT state, COUNT(*) as cnt - FROM solicitudes_servicerequest - WHERE state = ANY(:states) - GROUP BY state - """) - - try: - with engine.connect() as conn: - result = conn.execute(query, {"states": ACTIVE_STATES}) - return {row.state: row.cnt for row in result.fetchall()} - except Exception as e: - print(f"[turnos] Count error: {e}") - return {} - - -def _hours_since(dt: Optional[datetime]) -> int: - """Hours since datetime.""" - if not dt: - return 0 - try: - now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now() - return int((now - dt).total_seconds() / 3600) - except: - return 0 - - -# ============================================================================= -# ROUTES -# ============================================================================= - -@app.get("/health") -def health(): - """Health check.""" - engine = get_engine() - db_ok = False - if engine: - try: - with engine.connect() as conn: - conn.execute(text("SELECT 1")) - db_ok = True - except: - pass - - return { - "status": "ok" if db_ok else "degraded", - "service": "turnos-monitor", - "nest": NEST_CONFIG["name"], - "database": "connected" if db_ok else "disconnected", - } - - -@app.get("/", response_class=HTMLResponse) -def index(request: Request, view: str = "pipeline"): - """Main monitor view. ?view=list for list view.""" - requests_data = fetch_active_requests() - counts = fetch_counts() - - # Group by state - by_state = {s: [] for s in ACTIVE_STATES} - for req in requests_data: - if req["state"] in by_state: - by_state[req["state"]].append(req) - - template = "list.html" if view == "list" else "index.html" - - return templates.TemplateResponse(template, { - "request": request, - "items": requests_data, - "by_state": by_state, - "counts": counts, - "states": STATES, - "active_states": ACTIVE_STATES, - "total": len(requests_data), - "nest_name": NEST_CONFIG["name"], - "view": view, - }) - - -@app.get("/api/data") -def api_data(): - """JSON API endpoint.""" - return { - "nest": NEST_CONFIG["name"], - "requests": fetch_active_requests(), - "counts": fetch_counts(), - } - - -# ============================================================================= -# MAIN -# ============================================================================= - -if __name__ == "__main__": - import uvicorn - uvicorn.run( - "main:app", - host="0.0.0.0", - port=int(os.getenv("PORT", "12010")), - reload=True, - ) diff --git a/cfg/amar/soleprint/station/tools/databrowse/depot/scenarios.json b/cfg/amar/soleprint/station/tools/databrowse/depot/scenarios.json deleted file mode 100644 index 71fe2e5..0000000 --- a/cfg/amar/soleprint/station/tools/databrowse/depot/scenarios.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$comment": "Test scenarios emerge from actual usage and conversations. This is just the format.", - "scenarios": [ - { - "_example": "This is an example scenario structure - real scenarios will be added as needed", - "name": "Example Scenario", - "slug": "example-scenario", - "description": "Description of what this scenario tests", - "role": "USER", - "entity": "PetOwner", - "view": "petowners_by_state", - "filters": { - "has_pets": true, - "has_requests": false - }, - "test_cases": [ - "Test case 1", - "Test case 2" - ], - "priority": "medium", - "complexity": "medium", - "notes": "Additional notes about this scenario" - } - ] -} diff --git a/cfg/amar/soleprint/station/tools/databrowse/depot/schema.json b/cfg/amar/soleprint/station/tools/databrowse/depot/schema.json deleted file mode 100644 index 0492b62..0000000 --- a/cfg/amar/soleprint/station/tools/databrowse/depot/schema.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AMAR Data Model", - "description": "Test-oriented data model for AMAR Mascotas. Focused on test scenarios and user navigation.", - "version": "0.1.0", - "meta": { - "purpose": "Enable quick navigation to appropriate test users/scenarios", - "modes": ["sql", "api"], - "graph_generators": ["graphviz", "mermaid", "d3", "custom"] - }, - "definitions": { - "UserRole": { - "type": "string", - "enum": ["USER", "VET", "ADMIN"], - "description": "User roles detected from: is_staff → ADMIN, linked to Veterinarian → VET, else USER" - }, - "RequestState": { - "type": "string", - "enum": [ - "pending", - "in_progress_vet", - "vet_asked", - "vet_accepted", - "in_progress_pay", - "payed", - "coordinated", - "not_coordinated", - "completed", - "rejected" - ], - "description": "Service request lifecycle states" - }, - "User": { - "type": "object", - "description": "Django auth.User - central identity", - "table": "auth_user", - "properties": { - "id": {"type": "integer", "column": "id"}, - "username": {"type": "string", "column": "username"}, - "email": {"type": "string", "column": "email"}, - "first_name": {"type": "string", "column": "first_name"}, - "last_name": {"type": "string", "column": "last_name"}, - "is_staff": {"type": "boolean", "column": "is_staff"}, - "is_active": {"type": "boolean", "column": "is_active"}, - "date_joined": {"type": "string", "format": "date-time", "column": "date_joined"} - }, - "computed": { - "role": { - "description": "Derived from is_staff, veterinarian link, or default USER", - "sql": "CASE WHEN is_staff THEN 'ADMIN' WHEN EXISTS (SELECT 1 FROM mascotas_veterinarian WHERE user_id = auth_user.id) THEN 'VET' ELSE 'USER' END" - } - }, - "required": ["id", "username"] - }, - "PetOwner": { - "type": "object", - "description": "Pet owner (client)", - "table": "mascotas_petowner", - "properties": { - "id": {"type": "integer", "column": "id"}, - "user_id": {"type": "integer", "column": "user_id"}, - "first_name": {"type": "string", "column": "first_name"}, - "last_name": {"type": "string", "column": "last_name"}, - "email": {"type": "string", "column": "email"}, - "phone": {"type": "string", "column": "phone"}, - "dni": {"type": "string", "column": "dni"}, - "address": {"type": "string", "column": "address"}, - "created_at": {"type": "string", "format": "date-time", "column": "created_at"} - }, - "computed": { - "has_pets": { - "description": "Has at least one pet", - "sql": "EXISTS (SELECT 1 FROM mascotas_pet WHERE petowner_id = mascotas_petowner.id AND deleted = false)" - }, - "has_coverage": { - "description": "Has active coverage", - "sql": "EXISTS (SELECT 1 FROM mascotas_coverage WHERE petowner_id = mascotas_petowner.id AND active = true AND deleted = false)" - }, - "has_requests": { - "description": "Has any service requests", - "sql": "EXISTS (SELECT 1 FROM solicitudes_servicerequest WHERE petowner_id = mascotas_petowner.id)" - }, - "has_turnos": { - "description": "Has scheduled turnos", - "sql": "EXISTS (SELECT 1 FROM mascotas_vetvisit WHERE petowner_id = mascotas_petowner.id)" - } - }, - "required": ["id", "first_name"] - }, - "Veterinarian": { - "type": "object", - "description": "Veterinarian service provider", - "table": "mascotas_veterinarian", - "properties": { - "id": {"type": "integer", "column": "id"}, - "user_id": {"type": "integer", "column": "user_id"}, - "first_name": {"type": "string", "column": "first_name"}, - "last_name": {"type": "string", "column": "last_name"}, - "email": {"type": "string", "column": "email"}, - "phone": {"type": "string", "column": "phone"}, - "matricula": {"type": "string", "column": "matricula"}, - "created_at": {"type": "string", "format": "date-time", "column": "created_at"} - }, - "computed": { - "has_availability": { - "description": "Has configured availability", - "sql": "EXISTS (SELECT 1 FROM mascotas_availability WHERE veterinarian_id = mascotas_veterinarian.id AND deleted = false)" - }, - "has_specialties": { - "description": "Has assigned specialties", - "sql": "EXISTS (SELECT 1 FROM mascotas_veterinarian_specialties WHERE veterinarian_id = mascotas_veterinarian.id)" - }, - "has_coverage_areas": { - "description": "Has coverage neighborhoods", - "sql": "EXISTS (SELECT 1 FROM mascotas_veterinarian_neighborhoods WHERE veterinarian_id = mascotas_veterinarian.id)" - }, - "active_requests": { - "description": "Count of active service requests", - "sql": "(SELECT COUNT(*) FROM solicitudes_servicerequest WHERE veterinarian_id = mascotas_veterinarian.id AND state NOT IN ('completed', 'rejected'))" - }, - "completed_visits": { - "description": "Count of completed visits", - "sql": "(SELECT COUNT(*) FROM mascotas_vetvisit WHERE veterinarian_id = mascotas_veterinarian.id)" - } - }, - "required": ["id", "first_name"] - }, - "Pet": { - "type": "object", - "description": "Pet belonging to PetOwner", - "table": "mascotas_pet", - "properties": { - "id": {"type": "integer", "column": "id"}, - "petowner_id": {"type": "integer", "column": "petowner_id"}, - "name": {"type": "string", "column": "name"}, - "pet_type": {"type": "string", "column": "pet_type"}, - "breed_id": {"type": "integer", "column": "breed_id"}, - "age_years": {"type": "integer", "column": "age_years"}, - "created_at": {"type": "string", "format": "date-time", "column": "created_at"} - }, - "computed": { - "has_vaccines": { - "description": "Has vaccine records", - "sql": "EXISTS (SELECT 1 FROM mascotas_petvaccination WHERE pet_id = mascotas_pet.id)" - }, - "has_studies": { - "description": "Has study records", - "sql": "EXISTS (SELECT 1 FROM mascotas_petstudy WHERE pet_id = mascotas_pet.id)" - } - }, - "required": ["id", "petowner_id", "name"] - }, - "ServiceRequest": { - "type": "object", - "description": "Service request (order) - main workflow entity", - "table": "solicitudes_servicerequest", - "properties": { - "id": {"type": "integer", "column": "id"}, - "petowner_id": {"type": "integer", "column": "petowner_id"}, - "veterinarian_id": {"type": "integer", "column": "veterinarian_id"}, - "state": {"$ref": "#/definitions/RequestState", "column": "state"}, - "pay_number": {"type": "string", "column": "pay_number"}, - "date_coordinated": {"type": "string", "format": "date", "column": "date_coordinated"}, - "hour_coordinated": {"type": "string", "format": "time", "column": "hour_coordinated"}, - "created_at": {"type": "string", "format": "date-time", "column": "created_at"} - }, - "computed": { - "has_cart": { - "description": "Has associated cart", - "sql": "EXISTS (SELECT 1 FROM productos_servicecart WHERE service_request_id = solicitudes_servicerequest.id)" - }, - "has_payment": { - "description": "Has payment record", - "sql": "pay_number IS NOT NULL AND pay_number != ''" - }, - "has_turno": { - "description": "Has created turno", - "sql": "EXISTS (SELECT 1 FROM mascotas_vetvisit WHERE service_request_id = solicitudes_servicerequest.id)" - }, - "age_hours": { - "description": "Hours since creation", - "sql": "EXTRACT(EPOCH FROM (NOW() - created_at)) / 3600" - } - }, - "required": ["id", "petowner_id", "state"] - }, - "VetVisit": { - "type": "object", - "description": "Scheduled veterinary visit (turno)", - "table": "mascotas_vetvisit", - "properties": { - "id": {"type": "integer", "column": "id"}, - "service_request_id": {"type": "integer", "column": "service_request_id"}, - "veterinarian_id": {"type": "integer", "column": "veterinarian_id"}, - "petowner_id": {"type": "integer", "column": "petowner_id"}, - "visit_date": {"type": "string", "format": "date", "column": "visit_date"}, - "visit_hour": {"type": "string", "format": "time", "column": "visit_hour"}, - "created_at": {"type": "string", "format": "date-time", "column": "created_at"} - }, - "computed": { - "has_report": { - "description": "Has post-visit report", - "sql": "EXISTS (SELECT 1 FROM mascotas_vetvisitreport WHERE vet_visit_id = mascotas_vetvisit.id)" - }, - "has_invoice": { - "description": "Has generated invoice", - "sql": "invoice_number IS NOT NULL" - }, - "is_completed": { - "description": "Visit has been completed", - "sql": "visit_date < CURRENT_DATE OR (visit_date = CURRENT_DATE AND visit_hour < CURRENT_TIME)" - } - }, - "required": ["id", "service_request_id"] - } - } -} diff --git a/cfg/amar/soleprint/station/tools/databrowse/depot/views.json b/cfg/amar/soleprint/station/tools/databrowse/depot/views.json deleted file mode 100644 index a045d9f..0000000 --- a/cfg/amar/soleprint/station/tools/databrowse/depot/views.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "views": [ - { - "name": "users_by_role", - "title": "Users by Role", - "slug": "users-by-role", - "description": "All users grouped by role (USER/VET/ADMIN) for quick login selection", - "mode": "sql", - "entity": "User", - "group_by": "role", - "order_by": "username ASC", - "fields": ["id", "username", "email", "first_name", "last_name", "is_staff", "is_active", "date_joined"], - "display_fields": { - "id": {"label": "ID", "width": "60px"}, - "username": {"label": "Username", "width": "150px", "primary": true}, - "email": {"label": "Email", "width": "200px"}, - "first_name": {"label": "First Name", "width": "120px"}, - "last_name": {"label": "Last Name", "width": "120px"}, - "is_active": {"label": "Active", "width": "80px", "type": "boolean"} - }, - "actions": { - "login_as": { - "label": "Login as this user", - "type": "command", - "command": "echo 'Login as {{username}}' | pbcopy" - }, - "copy_credentials": { - "label": "Copy credentials", - "type": "copy", - "template": "{{username}} / Amar2025!" - } - } - }, - { - "name": "petowners_by_state", - "title": "Pet Owners by Data State", - "slug": "petowners-by-state", - "description": "Pet owners grouped by data state (has_pets, has_coverage, has_requests, has_turnos)", - "mode": "sql", - "entity": "PetOwner", - "group_by": "state_category", - "order_by": "created_at DESC", - "fields": ["id", "user_id", "first_name", "last_name", "email", "phone", "has_pets", "has_coverage", "has_requests", "has_turnos"], - "computed_group": { - "state_category": { - "sql": "CASE WHEN has_turnos THEN 'with_turnos' WHEN has_requests THEN 'with_requests' WHEN has_pets THEN 'with_pets' WHEN has_coverage THEN 'with_coverage' ELSE 'new' END", - "labels": { - "new": "New Users (Empty)", - "with_coverage": "With Coverage Only", - "with_pets": "With Pets Only", - "with_requests": "With Requests", - "with_turnos": "With Scheduled Turnos" - } - } - }, - "display_fields": { - "id": {"label": "ID", "width": "60px"}, - "first_name": {"label": "First Name", "width": "120px"}, - "last_name": {"label": "Last Name", "width": "120px", "primary": true}, - "email": {"label": "Email", "width": "200px"}, - "phone": {"label": "Phone", "width": "120px"}, - "has_pets": {"label": "Pets", "width": "60px", "type": "icon"}, - "has_coverage": {"label": "Coverage", "width": "80px", "type": "icon"}, - "has_requests": {"label": "Requests", "width": "80px", "type": "icon"}, - "has_turnos": {"label": "Turnos", "width": "70px", "type": "icon"} - }, - "actions": { - "login_as": { - "label": "Login as petowner", - "type": "link", - "template": "/login?user_id={{user_id}}" - } - } - }, - { - "name": "vets_by_availability", - "title": "Veterinarians by Availability", - "slug": "vets-by-availability", - "description": "Vets grouped by availability and active work status", - "mode": "sql", - "entity": "Veterinarian", - "group_by": "availability_status", - "order_by": "active_requests DESC, completed_visits DESC", - "fields": ["id", "user_id", "first_name", "last_name", "email", "phone", "matricula", "has_availability", "has_specialties", "has_coverage_areas", "active_requests", "completed_visits"], - "computed_group": { - "availability_status": { - "sql": "CASE WHEN NOT has_availability THEN 'no_availability' WHEN active_requests > 3 THEN 'very_busy' WHEN active_requests > 0 THEN 'busy' ELSE 'available' END", - "labels": { - "no_availability": "No Availability Configured", - "available": "Available (No Active Requests)", - "busy": "Busy (1-3 Active Requests)", - "very_busy": "Very Busy (4+ Active Requests)" - } - } - }, - "display_fields": { - "id": {"label": "ID", "width": "60px"}, - "first_name": {"label": "First Name", "width": "120px"}, - "last_name": {"label": "Last Name", "width": "120px", "primary": true}, - "matricula": {"label": "Matricula", "width": "100px"}, - "phone": {"label": "Phone", "width": "120px"}, - "has_availability": {"label": "Avail", "width": "60px", "type": "icon"}, - "has_specialties": {"label": "Spec", "width": "60px", "type": "icon"}, - "has_coverage_areas": {"label": "Areas", "width": "60px", "type": "icon"}, - "active_requests": {"label": "Active", "width": "70px", "type": "number"}, - "completed_visits": {"label": "Completed", "width": "90px", "type": "number"} - }, - "actions": { - "login_as": { - "label": "Login as vet", - "type": "link", - "template": "/login?user_id={{user_id}}" - } - } - }, - { - "name": "requests_pipeline", - "title": "Service Requests Pipeline", - "slug": "requests-pipeline", - "description": "Active service requests grouped by state (like turnos monitor)", - "mode": "sql", - "entity": "ServiceRequest", - "group_by": "state", - "order_by": "created_at DESC", - "fields": ["id", "petowner_id", "veterinarian_id", "state", "pay_number", "date_coordinated", "hour_coordinated", "has_cart", "has_payment", "has_turno", "age_hours", "created_at"], - "filter": { - "state": ["pending", "in_progress_vet", "vet_asked", "vet_accepted", "in_progress_pay", "payed", "coordinated", "not_coordinated"] - }, - "display_fields": { - "id": {"label": "ID", "width": "60px", "primary": true}, - "petowner_id": {"label": "Owner", "width": "70px", "type": "link"}, - "veterinarian_id": {"label": "Vet", "width": "60px", "type": "link"}, - "state": {"label": "State", "width": "120px", "type": "badge"}, - "has_cart": {"label": "Cart", "width": "50px", "type": "icon"}, - "has_payment": {"label": "Pay", "width": "50px", "type": "icon"}, - "has_turno": {"label": "Turno", "width": "50px", "type": "icon"}, - "age_hours": {"label": "Age (h)", "width": "70px", "type": "number"} - }, - "state_colors": { - "pending": "#fbbf24", - "in_progress_vet": "#f97316", - "vet_asked": "#fb923c", - "vet_accepted": "#4ade80", - "in_progress_pay": "#60a5fa", - "payed": "#2dd4bf", - "coordinated": "#22c55e", - "not_coordinated": "#facc15" - }, - "actions": { - "view_details": { - "label": "View request", - "type": "link", - "template": "/admin/solicitudes/servicerequest/{{id}}/change/" - } - } - }, - { - "name": "full_graph", - "title": "Full Data Graph", - "slug": "full-graph", - "description": "Complete data model graph showing all entities and relationships", - "mode": "graph", - "graph_type": "erd", - "entities": ["User", "PetOwner", "Veterinarian", "Pet", "ServiceRequest", "VetVisit"], - "relationships": [ - {"from": "User", "to": "PetOwner", "type": "1:1", "via": "user_id"}, - {"from": "User", "to": "Veterinarian", "type": "1:1", "via": "user_id"}, - {"from": "PetOwner", "to": "Pet", "type": "1:N", "via": "petowner_id"}, - {"from": "PetOwner", "to": "ServiceRequest", "type": "1:N", "via": "petowner_id"}, - {"from": "Veterinarian", "to": "ServiceRequest", "type": "1:N", "via": "veterinarian_id"}, - {"from": "ServiceRequest", "to": "VetVisit", "type": "1:1", "via": "service_request_id"}, - {"from": "Veterinarian", "to": "VetVisit", "type": "1:N", "via": "veterinarian_id"} - }, - "layout": "hierarchical", - "generators": ["graphviz", "mermaid", "d3"] - } - ] -} diff --git a/cfg/amar/soleprint/station/tools/datagen/amar.py b/cfg/amar/soleprint/station/tools/datagen/amar.py deleted file mode 100644 index 612d3d3..0000000 --- a/cfg/amar/soleprint/station/tools/datagen/amar.py +++ /dev/null @@ -1,255 +0,0 @@ -"""Data generator for Amar domain models - can be plugged into any nest.""" - -import random -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional - - -class AmarDataGenerator: - """Generates realistic test data for Amar domain models.""" - - # Sample data pools - FIRST_NAMES = ["Lucas", "María", "Juan", "Carolina", "Diego", "Valentina", "Martín", "Sofía", "Mateo", "Emma"] - LAST_NAMES = ["González", "Rodríguez", "Pérez", "García", "Martínez", "López", "Fernández", "Sánchez"] - - PET_NAMES = ["Luna", "Max", "Bella", "Rocky", "Coco", "Toby", "Mia", "Charlie", "Lola", "Simba"] - PET_SPECIES = [ - {"id": 1, "name": "Perro", "code": "DOG"}, - {"id": 2, "name": "Gato", "code": "CAT"} - ] - - NEIGHBORHOODS = [ - {"id": 1, "name": "Palermo", "has_coverage": True, "zone": "CABA"}, - {"id": 2, "name": "Recoleta", "has_coverage": True, "zone": "CABA"}, - {"id": 3, "name": "Belgrano", "has_coverage": True, "zone": "CABA"}, - {"id": 4, "name": "Caballito", "has_coverage": True, "zone": "CABA"}, - {"id": 5, "name": "Mataderos", "has_coverage": False, "zone": "CABA"}, - {"id": 6, "name": "Villa Urquiza", "has_coverage": True, "zone": "CABA"}, - ] - - SERVICE_CATEGORIES = [ - {"id": 1, "name": "Consultas", "description": "Consultas veterinarias"}, - {"id": 2, "name": "Vacunación", "description": "Vacunas y antiparasitarios"}, - {"id": 3, "name": "Estudios", "description": "Análisis y estudios clínicos"}, - {"id": 4, "name": "Videollamada", "description": "Consultas por videollamada"}, - ] - - SERVICES = [ - {"id": 1, "category_id": 1, "name": "Consulta general clínica programada", "price": 95000, "species": ["DOG", "CAT"]}, - {"id": 2, "category_id": 2, "name": "Vacuna Antirrábica", "price": 7000, "species": ["DOG", "CAT"]}, - {"id": 3, "category_id": 2, "name": "Sextuple", "price": 12000, "species": ["DOG"]}, - {"id": 4, "category_id": 2, "name": "Triple Felina", "price": 11000, "species": ["CAT"]}, - {"id": 5, "category_id": 3, "name": "Análisis de sangre", "price": 25000, "species": ["DOG", "CAT"]}, - {"id": 6, "category_id": 3, "name": "Ecografía", "price": 35000, "species": ["DOG", "CAT"]}, - {"id": 7, "category_id": 4, "name": "Consulta por videollamada", "price": 15000, "species": ["DOG", "CAT"]}, - ] - - @classmethod - def petowner(cls, address: str = None, is_guest: bool = True, **overrides) -> Dict[str, Any]: - """Generate a petowner. - - Args: - address: Owner address - is_guest: Whether this is a guest user - **overrides: Override any fields - """ - owner_id = overrides.get("id", random.randint(1000, 9999)) - first_name = overrides.get("first_name", random.choice(cls.FIRST_NAMES) if not is_guest else "") - last_name = overrides.get("last_name", random.choice(cls.LAST_NAMES) if not is_guest else "") - address = address or f"{random.choice(['Av.', 'Calle'])} {random.choice(['Corrientes', 'Santa Fe', 'Córdoba', 'Rivadavia'])} {random.randint(1000, 5000)}" - - # Determine neighborhood from address or random - neighborhood = overrides.get("neighborhood", random.choice([n for n in cls.NEIGHBORHOODS if n["has_coverage"]])) - - data = { - "id": owner_id, - "first_name": first_name, - "last_name": last_name, - "email": f"guest_{owner_id}@amarmascotas.ar" if is_guest else f"{first_name.lower()}.{last_name.lower()}@example.com", - "phone": f"+54911{random.randint(10000000, 99999999)}", - "address": address, - "neighborhood": neighborhood, - "is_guest": is_guest, - "created_at": datetime.now().isoformat(), - } - - # Apply overrides - data.update(overrides) - return data - - @classmethod - def pet(cls, owner_id: int, name: str = None, species: str = "DOG", age_value: int = None, age_unit: str = "years", **overrides) -> Dict[str, Any]: - """Generate a pet. - - Args: - owner_id: Owner ID - name: Pet name - species: Pet species code (DOG, CAT) - age_value: Age value - age_unit: Age unit (years, months) - **overrides: Override any fields - """ - species_data = next((s for s in cls.PET_SPECIES if s["code"] == species.upper()), cls.PET_SPECIES[0]) - age = age_value or random.randint(1, 10) - name = name or random.choice(cls.PET_NAMES) - - data = { - "id": random.randint(1000, 9999), - "owner_id": owner_id, - "name": name, - "species": species_data, - "age": age, - "age_unit": age_unit, - "age_in_months": age if age_unit == "months" else age * 12, - "created_at": datetime.now().isoformat(), - } - - data.update(overrides) - return data - - @classmethod - def cart(cls, owner_id: int, **overrides) -> Dict[str, Any]: - """Generate an empty cart. - - Args: - owner_id: Owner ID - **overrides: Override any fields - """ - cart_id = overrides.get("id", random.randint(10000, 99999)) - - data = { - "id": cart_id, - "owner_id": owner_id, - "items": [], - "resume": { - "subtotal": 0.0, - "discounts": 0.0, - "total": 0.0, - }, - "resume_items": [], - "created_at": datetime.now().isoformat(), - } - - data.update(overrides) - return data - - @classmethod - def filter_services(cls, species: str = None, neighborhood_id: int = None) -> List[Dict[str, Any]]: - """Filter services by species and neighborhood coverage. - - Args: - species: Species code to filter by (DOG, CAT) - neighborhood_id: Neighborhood ID for coverage check - """ - services = cls.SERVICES.copy() - - if species: - species_code = species.upper() - services = [s for s in services if species_code in s["species"]] - - # Neighborhood coverage - only videollamada if no coverage - if neighborhood_id: - neighborhood = next((n for n in cls.NEIGHBORHOODS if n["id"] == neighborhood_id), None) - if not neighborhood or not neighborhood.get("has_coverage"): - services = [s for s in services if s["category_id"] == 4] # Only videollamada - - return [ - { - "id": s["id"], - "name": s["name"], - "category_id": s["category_id"], - "price": s["price"], - "currency": "ARS", - "available": True, - } - for s in services - ] - - @classmethod - def filter_categories(cls, species: str = None, neighborhood_id: int = None) -> List[Dict[str, Any]]: - """Filter categories that have available services. - - Args: - species: Species code to filter by - neighborhood_id: Neighborhood ID for coverage check - """ - available_services = cls.filter_services(species, neighborhood_id) - service_category_ids = {s["category_id"] for s in available_services} - - return [ - { - "id": cat["id"], - "name": cat["name"], - "description": cat["description"], - "service_count": len([s for s in available_services if s["category_id"] == cat["id"]]), - } - for cat in cls.SERVICE_CATEGORIES - if cat["id"] in service_category_ids - ] - - @classmethod - def calculate_cart_summary(cls, cart: Dict[str, Any], items: List[Dict[str, Any]]) -> Dict[str, Any]: - """Calculate cart totals with discounts and splits. - - Args: - cart: Cart data - items: List of cart items with price and quantity - """ - subtotal = sum(item.get("price", 0) * item.get("quantity", 1) for item in items) - - # Multi-pet discount - pet_count = len({item.get("pet_id") for item in items if item.get("pet_id")}) - discount_rate = 0.0 - if pet_count >= 2: - discount_rate = 0.10 # 10% for 2+ pets - - discount_amount = subtotal * discount_rate - total = subtotal - discount_amount - - resume_items = [ - {"concept": "SUBTOTAL", "amount": subtotal}, - ] - - if discount_amount > 0: - resume_items.append({"concept": "DESCUENTO MULTIMASCOTA", "amount": -discount_amount}) - - # Calculate vet honorarios (52% of total in real system) - honorarios = total * 0.52 - resume_items.append({"concept": "HONORARIOS", "amount": honorarios}) - resume_items.append({"concept": "TOTAL", "amount": total}) - - return { - **cart, - "items": items, - "resume": { - "subtotal": round(subtotal, 2), - "discounts": round(discount_amount, 2), - "total": round(total, 2), - }, - "resume_items": resume_items, - "updated_at": datetime.now().isoformat(), - } - - @classmethod - def service_request(cls, cart_id: int, requested_date: str = None, **overrides) -> Dict[str, Any]: - """Generate a service request. - - Args: - cart_id: Cart ID - requested_date: ISO format date string - **overrides: Override any fields - """ - request_id = overrides.get("id", random.randint(100000, 999999)) - requested_date = requested_date or (datetime.now() + timedelta(days=random.randint(1, 7))).isoformat() - - data = { - "id": request_id, - "cart_id": cart_id, - "requested_date": requested_date, - "state": "PENDING", - "veterinarian": None, - "created_at": datetime.now().isoformat(), - } - - data.update(overrides) - return data diff --git a/cfg/amar/soleprint/station/tools/tester/tests/README.md b/cfg/amar/soleprint/station/tools/tester/tests/README.md deleted file mode 100644 index 19a6871..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Contract Tests - -API contract tests organized by Django app, with optional workflow tests. - -## Testing Modes - -Two modes via `CONTRACT_TEST_MODE` environment variable: - -| Mode | Command | Description | -|------|---------|-------------| -| **api** (default) | `pytest tests/contracts/` | Fast, Django test client, test DB | -| **live** | `CONTRACT_TEST_MODE=live pytest tests/contracts/` | Real HTTP, LiveServerTestCase, test DB | - -### Mode Comparison - -| | `api` (default) | `live` | -|---|---|---| -| **Base class** | `APITestCase` | `LiveServerTestCase` | -| **HTTP** | In-process (Django test client) | Real HTTP via `requests` | -| **Auth** | `force_authenticate()` | JWT tokens via API | -| **Database** | Django test DB (isolated) | Django test DB (isolated) | -| **Speed** | ~3-5 sec | ~15-30 sec | -| **Server** | None (in-process) | Auto-started by Django | - -### Key Point: Both Modes Use Test Database - -Neither mode touches your real database. Django automatically: -1. Creates a test database (prefixed with `test_`) -2. Runs migrations -3. Destroys it after tests complete - -## File Structure - -``` -tests/contracts/ -├── base.py # Mode switcher (imports from base_api or base_live) -├── base_api.py # APITestCase implementation -├── base_live.py # LiveServerTestCase implementation -├── conftest.py # pytest-django configuration -├── endpoints.py # API paths (single source of truth) -├── helpers.py # Shared test data helpers -│ -├── mascotas/ # Django app: mascotas -│ ├── test_pet_owners.py -│ ├── test_pets.py -│ └── test_coverage.py -│ -├── productos/ # Django app: productos -│ ├── test_services.py -│ └── test_cart.py -│ -├── solicitudes/ # Django app: solicitudes -│ └── test_service_requests.py -│ -└── workflows/ # Multi-step API sequences (e.g., turnero booking flow) - └── test_turnero_general.py -``` - -## Running Tests - -```bash -# All contract tests -pytest tests/contracts/ - -# Single app -pytest tests/contracts/mascotas/ - -# Single file -pytest tests/contracts/mascotas/test_pet_owners.py - -# Live mode (real HTTP) -CONTRACT_TEST_MODE=live pytest tests/contracts/ -``` diff --git a/cfg/amar/soleprint/station/tools/tester/tests/__init__.py b/cfg/amar/soleprint/station/tools/tester/tests/__init__.py deleted file mode 100644 index 9abad81..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Contract tests - black-box HTTP tests that validate API contracts -# These tests are decoupled from Django and can run against any implementation diff --git a/cfg/amar/soleprint/station/tools/tester/tests/base.py b/cfg/amar/soleprint/station/tools/tester/tests/base.py deleted file mode 100644 index 08d9570..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/base.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Re-export from parent — room tests import from here.""" -from ..base import ContractTestCase, get_base_url - -__all__ = ["ContractTestCase", "get_base_url"] diff --git a/cfg/amar/soleprint/station/tools/tester/tests/conftest.py b/cfg/amar/soleprint/station/tools/tester/tests/conftest.py deleted file mode 100644 index cfbc6dd..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/conftest.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Contract Tests Configuration - -Supports two testing modes via CONTRACT_TEST_MODE environment variable: - - # Fast mode (default) - Django test client, test DB - pytest tests/contracts/ - - # Live mode - Real HTTP with LiveServerTestCase, test DB - CONTRACT_TEST_MODE=live pytest tests/contracts/ -""" - -import os -import pytest - -# Let pytest-django handle Django setup via pytest.ini DJANGO_SETTINGS_MODULE - - -def pytest_configure(config): - """Register custom markers""" - config.addinivalue_line( - "markers", "workflow: marks test as a workflow/flow test (runs endpoint tests in sequence)" - ) - - -@pytest.fixture(scope="session") -def contract_test_mode(): - """Return current test mode""" - return os.environ.get("CONTRACT_TEST_MODE", "api") diff --git a/cfg/amar/soleprint/station/tools/tester/tests/endpoints.py b/cfg/amar/soleprint/station/tools/tester/tests/endpoints.py deleted file mode 100644 index 7af2031..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/endpoints.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -API Endpoints - Single source of truth for contract tests. - -If API paths or versioning changes, update here only. -""" - - -class Endpoints: - """API endpoint paths""" - - # ========================================================================== - # Mascotas - # ========================================================================== - PET_OWNERS = "/mascotas/api/v1/pet-owners/" - PET_OWNER_DETAIL = "/mascotas/api/v1/pet-owners/{id}/" - PETS = "/mascotas/api/v1/pets/" - PET_DETAIL = "/mascotas/api/v1/pets/{id}/" - COVERAGE_CHECK = "/mascotas/api/v1/coverage/check/" - - # ========================================================================== - # Productos - # ========================================================================== - SERVICES = "/productos/api/v1/services/" - CATEGORIES = "/productos/api/v1/categories/" - CART = "/productos/api/v1/cart/" - CART_DETAIL = "/productos/api/v1/cart/{id}/" - - # ========================================================================== - # Solicitudes - # ========================================================================== - SERVICE_REQUESTS = "/solicitudes/service-requests/" - SERVICE_REQUEST_DETAIL = "/solicitudes/service-requests/{id}/" - - # ========================================================================== - # Auth - # ========================================================================== - TOKEN = "/api/token/" - TOKEN_REFRESH = "/api/token/refresh/" diff --git a/cfg/amar/soleprint/station/tools/tester/tests/helpers.py b/cfg/amar/soleprint/station/tools/tester/tests/helpers.py deleted file mode 100644 index 4fa5b0b..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/helpers.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Contract Tests - Shared test data helpers. - -Used across all endpoint tests to generate consistent test data. -""" - -import time - - -def unique_email(prefix="test"): - """Generate unique email for test data""" - return f"{prefix}_{int(time.time() * 1000)}@contract-test.local" - - -def sample_pet_owner(email=None): - """Generate sample pet owner data""" - return { - "first_name": "Test", - "last_name": "Usuario", - "email": email or unique_email("owner"), - "phone": "1155667788", - "address": "Av. Santa Fe 1234", - "geo_latitude": -34.5955, - "geo_longitude": -58.4166, - } - - -SAMPLE_CAT = { - "name": "TestCat", - "pet_type": "CAT", - "is_neutered": False, -} - -SAMPLE_DOG = { - "name": "TestDog", - "pet_type": "DOG", - "is_neutered": False, -} - -SAMPLE_NEUTERED_CAT = { - "name": "NeuteredCat", - "pet_type": "CAT", - "is_neutered": True, -} diff --git a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/__init__.py b/cfg/amar/soleprint/station/tools/tester/tests/mascotas/__init__.py deleted file mode 100644 index 4438cda..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Contract tests for mascotas app endpoints diff --git a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_coverage.py b/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_coverage.py deleted file mode 100644 index 978d8df..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_coverage.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -Contract Tests: Coverage Check API - -Endpoint: /mascotas/api/v1/coverage/check/ -App: mascotas - -Used to check if a location has veterinary coverage before proceeding with turnero. -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints - - -class TestCoverageCheck(ContractTestCase): - """GET /mascotas/api/v1/coverage/check/""" - - def test_with_coordinates_returns_200(self): - """Coverage check should accept lat/lng parameters""" - response = self.get(Endpoints.COVERAGE_CHECK, params={ - "lat": -34.6037, - "lng": -58.3816, - }) - - self.assert_status(response, 200) - - def test_returns_coverage_boolean(self): - """Coverage check should return coverage boolean""" - response = self.get(Endpoints.COVERAGE_CHECK, params={ - "lat": -34.6037, - "lng": -58.3816, - }) - - self.assert_status(response, 200) - self.assert_has_fields(response.data, "coverage") - self.assertIsInstance(response.data["coverage"], bool) - - def test_returns_vet_count(self): - """Coverage check should return number of available vets""" - response = self.get(Endpoints.COVERAGE_CHECK, params={ - "lat": -34.6037, - "lng": -58.3816, - }) - - self.assert_status(response, 200) - self.assert_has_fields(response.data, "vet_count") - self.assertIsInstance(response.data["vet_count"], int) - - def test_without_coordinates_fails(self): - """Coverage check without coordinates should fail""" - response = self.get(Endpoints.COVERAGE_CHECK) - - # Should return 400 or similar error - self.assertIn(response.status_code, [400, 422]) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pet_owners.py b/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pet_owners.py deleted file mode 100644 index 6977ec4..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pet_owners.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Contract Tests: Pet Owners API - -Endpoint: /mascotas/api/v1/pet-owners/ -App: mascotas - -Related Tickets: -- VET-536: Paso 0 - Test creación del petowner invitado -- VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general - -Context: In the turnero general flow (guest booking), a "guest" pet owner is created -with a mock email (e.g., invitado-1759415377297@example.com). This user is fundamental -for subsequent steps as it provides the address used to filter available services. - -TBD: PetOwnerViewSet needs pagination - currently loads all records on list(). - See mascotas/views/api/v1/views/petowner_views.py:72 - Using email filter in tests to avoid loading 14k+ records. -""" - -import time -from ..base import ContractTestCase -from ..endpoints import Endpoints -from ..helpers import sample_pet_owner - - -class TestPetOwnerCreate(ContractTestCase): - """POST /mascotas/api/v1/pet-owners/ - - VET-536: Tests for guest petowner creation (Step 0 of turnero flow) - """ - - def test_create_returns_201(self): - """ - Creating a pet owner returns 201 with the created resource. - - Request (from production turnero): - POST /mascotas/api/v1/pet-owners/ - { - "first_name": "Juan", - "last_name": "Pérez", - "email": "invitado-1733929847293@example.com", - "phone": "1155667788", - "address": "Av. Santa Fe 1234, Buenos Aires", - "geo_latitude": -34.5955, - "geo_longitude": -58.4166 - } - - Response (201): - { - "id": 12345, - "first_name": "Juan", - "last_name": "Pérez", - "email": "invitado-1733929847293@example.com", - "phone": "1155667788", - "address": "Av. Santa Fe 1234, Buenos Aires", - "geo_latitude": -34.5955, - "geo_longitude": -58.4166, - "pets": [], - "created_at": "2024-12-11T15:30:47.293Z" - } - """ - data = sample_pet_owner() - - response = self.post(Endpoints.PET_OWNERS, data) - - self.assert_status(response, 201) - self.assert_has_fields(response.data, "id", "email", "first_name", "last_name") - self.assertEqual(response.data["email"], data["email"]) - - def test_requires_email(self): - """ - Pet owner creation requires email (current behavior). - - Note: The turnero guest flow uses a mock email created by frontend - (e.g., invitado-1759415377297@example.com). The API always requires email. - This test ensures the contract enforcement - no petowner without email. - """ - data = { - "address": "Av. Corrientes 1234", - "first_name": "Invitado", - "last_name": str(int(time.time())), - } - - response = self.post(Endpoints.PET_OWNERS, data) - - self.assert_status(response, 400) - - def test_duplicate_email_returns_existing(self): - """ - Creating pet owner with existing email returns the existing record. - - Note: API has upsert behavior - returns 200 with existing record, - not 400 error. This allows frontend to "create or get" in one call. - Important for guest flow - if user refreshes/retries, we don't create duplicates. - """ - data = sample_pet_owner() - first_response = self.post(Endpoints.PET_OWNERS, data) - first_id = first_response.data["id"] - - response = self.post(Endpoints.PET_OWNERS, data) # Same email - - # Returns 200 with existing record (upsert behavior) - self.assert_status(response, 200) - self.assertEqual(response.data["id"], first_id) - - def test_address_and_geolocation_persisted(self): - """ - Pet owner address and geolocation coordinates are persisted correctly. - - The address is critical for the turnero flow - it's used to filter available - services by location. Geolocation (lat/lng) may be obtained from Google Maps API. - """ - data = sample_pet_owner() - - response = self.post(Endpoints.PET_OWNERS, data) - - self.assert_status(response, 201) - self.assert_has_fields(response.data, "address", "geo_latitude", "geo_longitude") - self.assertEqual(response.data["address"], data["address"]) - # Verify geolocation fields are numeric (not null/empty) - self.assertIsNotNone(response.data.get("geo_latitude")) - self.assertIsNotNone(response.data.get("geo_longitude")) - - -class TestPetOwnerRetrieve(ContractTestCase): - """GET /mascotas/api/v1/pet-owners/{id}/""" - - def test_get_by_id_returns_200(self): - """GET pet owner by ID returns owner details""" - # Create owner first - data = sample_pet_owner() - create_response = self.post(Endpoints.PET_OWNERS, data) - owner_id = create_response.data["id"] - - response = self.get(Endpoints.PET_OWNER_DETAIL.format(id=owner_id)) - - self.assert_status(response, 200) - self.assertEqual(response.data["id"], owner_id) - self.assert_has_fields(response.data, "id", "first_name", "last_name", "address", "pets") - - def test_nonexistent_returns_404(self): - """GET non-existent owner returns 404""" - response = self.get(Endpoints.PET_OWNER_DETAIL.format(id=999999)) - - self.assert_status(response, 404) - - -class TestPetOwnerList(ContractTestCase): - """GET /mascotas/api/v1/pet-owners/""" - - def test_list_with_email_filter_returns_200(self): - """GET pet owners filtered by email returns 200""" - # Filter by email to avoid loading 14k+ records (no pagination on this endpoint) - response = self.get(Endpoints.PET_OWNERS, params={"email": "nonexistent@test.com"}) - - self.assert_status(response, 200) - - def test_list_filter_by_email_works(self): - """Can filter pet owners by email""" - # Create a pet owner first - data = sample_pet_owner() - self.post(Endpoints.PET_OWNERS, data) - - # Filter by that email - response = self.get(Endpoints.PET_OWNERS, params={"email": data["email"]}) - - self.assert_status(response, 200) - # Should find exactly one - results = response.data if isinstance(response.data, list) else response.data.get("results", []) - self.assertEqual(len(results), 1) - self.assertEqual(results[0]["email"], data["email"]) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pets.py b/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pets.py deleted file mode 100644 index 978ee2c..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/mascotas/test_pets.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Contract Tests: Pets API - -Endpoint: /mascotas/api/v1/pets/ -App: mascotas - -Related Tickets: -- VET-537: Paso 1 - Test creación de la mascota vinculada al petowner invitado -- VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general - -Context: In the turnero general flow (Step 1), a pet is created and linked to the guest -pet owner. The pet data (type, name, neutered status) combined with the owner's address -is used to filter available services and veterinarians. -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints -from ..helpers import ( - sample_pet_owner, - unique_email, - SAMPLE_CAT, - SAMPLE_DOG, - SAMPLE_NEUTERED_CAT, -) - - -class TestPetCreate(ContractTestCase): - """POST /mascotas/api/v1/pets/ - - VET-537: Tests for pet creation linked to guest petowner (Step 1 of turnero flow) - """ - - def _create_owner(self): - """Helper to create a pet owner""" - data = sample_pet_owner(unique_email("pet_owner")) - response = self.post(Endpoints.PET_OWNERS, data) - return response.data["id"] - - def test_create_cat_returns_201(self): - """ - Creating a cat returns 201 with pet_type CAT. - - Request (from production turnero): - POST /mascotas/api/v1/pets/ - { - "name": "Luna", - "pet_type": "CAT", - "is_neutered": false, - "owner": 12345 - } - - Response (201): - { - "id": 67890, - "name": "Luna", - "pet_type": "CAT", - "is_neutered": false, - "owner": 12345, - "breed": null, - "birth_date": null, - "created_at": "2024-12-11T15:31:15.123Z" - } - """ - owner_id = self._create_owner() - data = {**SAMPLE_CAT, "owner": owner_id} - - response = self.post(Endpoints.PETS, data) - - self.assert_status(response, 201) - self.assert_has_fields(response.data, "id", "name", "pet_type", "owner") - self.assertEqual(response.data["pet_type"], "CAT") - self.assertEqual(response.data["name"], "TestCat") - - def test_create_dog_returns_201(self): - """ - Creating a dog returns 201 with pet_type DOG. - - Validates that both major pet types (CAT/DOG) are supported in the contract. - """ - owner_id = self._create_owner() - data = {**SAMPLE_DOG, "owner": owner_id} - - response = self.post(Endpoints.PETS, data) - - self.assert_status(response, 201) - self.assertEqual(response.data["pet_type"], "DOG") - - def test_neutered_status_persisted(self): - """ - Neutered status is persisted correctly. - - This is important business data that may affect service recommendations - or veterinarian assignments. - """ - owner_id = self._create_owner() - data = {**SAMPLE_NEUTERED_CAT, "owner": owner_id} - - response = self.post(Endpoints.PETS, data) - - self.assert_status(response, 201) - self.assertTrue(response.data["is_neutered"]) - - def test_requires_owner(self): - """ - Pet creation without owner should fail. - - Enforces the required link between pet and petowner - critical for the - turnero flow where pets must be associated with the guest user. - """ - data = SAMPLE_CAT.copy() - - response = self.post(Endpoints.PETS, data) - - self.assert_status(response, 400) - - def test_invalid_pet_type_rejected(self): - """ - Invalid pet_type should be rejected. - - Currently only CAT and DOG are supported. This test ensures the contract - validates pet types correctly. - """ - owner_id = self._create_owner() - data = { - "name": "InvalidPet", - "pet_type": "HAMSTER", - "owner": owner_id, - } - - response = self.post(Endpoints.PETS, data) - - self.assert_status(response, 400) - - -class TestPetRetrieve(ContractTestCase): - """GET /mascotas/api/v1/pets/{id}/""" - - def _create_owner_with_pet(self): - """Helper to create owner and pet""" - owner_data = sample_pet_owner(unique_email("pet_owner")) - owner_response = self.post(Endpoints.PET_OWNERS, owner_data) - owner_id = owner_response.data["id"] - - pet_data = {**SAMPLE_CAT, "owner": owner_id} - pet_response = self.post(Endpoints.PETS, pet_data) - return pet_response.data["id"] - - def test_get_by_id_returns_200(self): - """GET pet by ID returns pet details""" - pet_id = self._create_owner_with_pet() - - response = self.get(Endpoints.PET_DETAIL.format(id=pet_id)) - - self.assert_status(response, 200) - self.assertEqual(response.data["id"], pet_id) - - def test_nonexistent_returns_404(self): - """GET non-existent pet returns 404""" - response = self.get(Endpoints.PET_DETAIL.format(id=999999)) - - self.assert_status(response, 404) - - -class TestPetList(ContractTestCase): - """GET /mascotas/api/v1/pets/""" - - def test_list_returns_200(self): - """GET pets list returns 200 (with pagination)""" - response = self.get(Endpoints.PETS, params={"page_size": 1}) - - self.assert_status(response, 200) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/productos/__init__.py b/cfg/amar/soleprint/station/tools/tester/tests/productos/__init__.py deleted file mode 100644 index 1c6975b..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/productos/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Contract tests for productos app endpoints diff --git a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_cart.py b/cfg/amar/soleprint/station/tools/tester/tests/productos/test_cart.py deleted file mode 100644 index 238dc4f..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_cart.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Contract Tests: Cart API - -Endpoint: /productos/api/v1/cart/ -App: productos - -Related Tickets: -- VET-538: Test creación de cart vinculado al petowner -- VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general - -Context: In the turnero general flow (Step 2), a cart is created for the guest petowner. -The cart holds selected services and calculates price summary (subtotals, discounts, total). - -TBD: CartViewSet needs pagination/filtering - list endpoint hangs on large dataset. - See productos/api/v1/viewsets.py:93 -""" - -import pytest -from ..base import ContractTestCase -from ..endpoints import Endpoints -from ..helpers import sample_pet_owner, unique_email - - -class TestCartCreate(ContractTestCase): - """POST /productos/api/v1/cart/ - - VET-538: Tests for cart creation linked to petowner (Step 2 of turnero flow) - """ - - def _create_petowner(self): - """Helper to create a pet owner""" - data = sample_pet_owner(unique_email("cart_owner")) - response = self.post(Endpoints.PET_OWNERS, data) - return response.data["id"] - - def test_create_cart_for_petowner(self): - """ - Creating a cart returns 201 and links to petowner. - - Request (from production turnero): - POST /productos/api/v1/cart/ - { - "petowner": 12345, - "services": [] - } - - Response (201): - { - "id": 789, - "petowner": 12345, - "veterinarian": null, - "items": [], - "resume": [ - {"concept": "SUBTOTAL", "amount": "0.00", "order": 1}, - {"concept": "COSTO_SERVICIO", "amount": "0.00", "order": 2}, - {"concept": "DESCUENTO", "amount": "0.00", "order": 3}, - {"concept": "TOTAL", "amount": "0.00", "order": 4}, - {"concept": "ADELANTO", "amount": "0.00", "order": 5} - ], - "extra_details": "", - "pets": [], - "pet_reasons": [] - } - """ - owner_id = self._create_petowner() - data = { - "petowner": owner_id, - "services": [] - } - - response = self.post(Endpoints.CART, data) - - self.assert_status(response, 201) - self.assert_has_fields(response.data, "id", "petowner", "items") - self.assertEqual(response.data["petowner"], owner_id) - - def test_cart_has_price_summary_fields(self): - """ - Cart response includes price summary fields. - - These fields are critical for turnero flow - user needs to see: - - resume: array with price breakdown (SUBTOTAL, DESCUENTO, TOTAL, etc) - - items: cart items with individual pricing - """ - owner_id = self._create_petowner() - data = {"petowner": owner_id, "services": []} - - response = self.post(Endpoints.CART, data) - - self.assert_status(response, 201) - # Price fields should exist (may be 0 for empty cart) - self.assert_has_fields(response.data, "resume", "items") - - def test_empty_cart_has_zero_totals(self): - """ - Empty cart (no services) has zero price totals. - - Validates initial state before services are added. - """ - owner_id = self._create_petowner() - data = {"petowner": owner_id, "services": []} - - response = self.post(Endpoints.CART, data) - - self.assert_status(response, 201) - # Empty cart should have resume with zero amounts - self.assertIn("resume", response.data) - # Find TOTAL concept in resume - total_item = next((item for item in response.data["resume"] if item["concept"] == "TOTAL"), None) - self.assertIsNotNone(total_item) - self.assertEqual(total_item["amount"], "0.00") - - -class TestCartRetrieve(ContractTestCase): - """GET /productos/api/v1/cart/{id}/""" - - def _create_petowner_with_cart(self): - """Helper to create petowner and cart""" - owner_data = sample_pet_owner(unique_email("cart_owner")) - owner_response = self.post(Endpoints.PET_OWNERS, owner_data) - owner_id = owner_response.data["id"] - - cart_data = {"petowner": owner_id, "services": []} - cart_response = self.post(Endpoints.CART, cart_data) - return cart_response.data["id"] - - def test_get_cart_by_id_returns_200(self): - """GET cart by ID returns cart details""" - cart_id = self._create_petowner_with_cart() - - response = self.get(Endpoints.CART_DETAIL.format(id=cart_id)) - - self.assert_status(response, 200) - self.assertEqual(response.data["id"], cart_id) - - def test_detail_returns_404_for_nonexistent(self): - """GET /cart/{id}/ returns 404 for non-existent cart""" - response = self.get(Endpoints.CART_DETAIL.format(id=999999)) - self.assert_status(response, 404) - - -class TestCartList(ContractTestCase): - """GET /productos/api/v1/cart/""" - - @pytest.mark.skip(reason="TBD: Cart list hangs - needs pagination/filtering. Checking if dead code.") - def test_list_returns_200(self): - """GET /cart/ returns 200""" - response = self.get(Endpoints.CART) - self.assert_status(response, 200) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_categories.py b/cfg/amar/soleprint/station/tools/tester/tests/productos/test_categories.py deleted file mode 100644 index 84e23a8..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_categories.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Contract Tests: Categories API - -Endpoint: /productos/api/v1/categories/ -App: productos - -Returns service categories filtered by location availability. -Categories without available services in location should be hidden. -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints - - -class TestCategoriesList(ContractTestCase): - """GET /productos/api/v1/categories/""" - - def test_list_returns_200(self): - """GET categories returns 200""" - response = self.get(Endpoints.CATEGORIES, params={"page_size": 10}) - - self.assert_status(response, 200) - - def test_returns_list(self): - """GET categories returns a list""" - response = self.get(Endpoints.CATEGORIES, params={"page_size": 10}) - - self.assert_status(response, 200) - data = response.data - # Handle paginated or non-paginated response - categories = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(categories, list) - - def test_categories_have_required_fields(self): - """ - Each category should have id, name, and description. - - Request (from production turnero): - GET /productos/api/v1/categories/ - - Response (200): - [ - { - "id": 1, - "name": "Consulta General", - "description": "Consultas veterinarias generales" - }, - { - "id": 2, - "name": "Vacunación", - "description": "Servicios de vacunación" - } - ] - """ - response = self.get(Endpoints.CATEGORIES, params={"page_size": 10}) - - data = response.data - categories = data["results"] if isinstance(data, dict) and "results" in data else data - - if len(categories) > 0: - category = categories[0] - self.assert_has_fields(category, "id", "name", "description") - - def test_only_active_categories_returned(self): - """ - Only active categories are returned in the list. - - Business rule: Inactive categories should not be visible to users. - """ - response = self.get(Endpoints.CATEGORIES, params={"page_size": 50}) - - data = response.data - categories = data["results"] if isinstance(data, dict) and "results" in data else data - - # All categories should be active (no 'active': False in response) - # This is enforced at queryset level in CategoryViewSet - self.assertIsInstance(categories, list) - - -class TestCategoryRetrieve(ContractTestCase): - """GET /productos/api/v1/categories/{id}/""" - - def test_get_category_by_id_returns_200(self): - """ - GET category by ID returns category details. - - First fetch list to get a valid ID, then retrieve that category. - """ - # Get first category - list_response = self.get(Endpoints.CATEGORIES, params={"page_size": 1}) - if list_response.status_code != 200: - self.skipTest("No categories available for testing") - - data = list_response.data - categories = data["results"] if isinstance(data, dict) and "results" in data else data - - if len(categories) == 0: - self.skipTest("No categories available for testing") - - category_id = categories[0]["id"] - - # Test detail endpoint - response = self.get(f"{Endpoints.CATEGORIES}{category_id}/") - - self.assert_status(response, 200) - self.assertEqual(response.data["id"], category_id) - - def test_nonexistent_category_returns_404(self): - """GET non-existent category returns 404""" - response = self.get(f"{Endpoints.CATEGORIES}999999/") - - self.assert_status(response, 404) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_services.py b/cfg/amar/soleprint/station/tools/tester/tests/productos/test_services.py deleted file mode 100644 index f423842..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/productos/test_services.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Contract Tests: Services API - -Endpoint: /productos/api/v1/services/ -App: productos - -Returns available veterinary services filtered by pet type and location. -Critical for vet assignment automation. -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints -from ..helpers import sample_pet_owner, unique_email, SAMPLE_CAT, SAMPLE_DOG - - -class TestServicesList(ContractTestCase): - """GET /productos/api/v1/services/""" - - def test_list_returns_200(self): - """GET services returns 200""" - response = self.get(Endpoints.SERVICES, params={"page_size": 10}) - - self.assert_status(response, 200) - - def test_returns_list(self): - """GET services returns a list""" - response = self.get(Endpoints.SERVICES, params={"page_size": 10}) - - self.assert_status(response, 200) - data = response.data - # Handle paginated or non-paginated response - services = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(services, list) - - def test_services_have_required_fields(self): - """Each service should have id and name""" - response = self.get(Endpoints.SERVICES, params={"page_size": 10}) - - data = response.data - services = data["results"] if isinstance(data, dict) and "results" in data else data - - if len(services) > 0: - service = services[0] - self.assert_has_fields(service, "id", "name") - - def test_accepts_pet_id_filter(self): - """Services endpoint accepts pet_id parameter""" - response = self.get(Endpoints.SERVICES, params={"pet_id": 1}) - - # Should not error (even if pet doesn't exist, endpoint should handle gracefully) - self.assertIn(response.status_code, [200, 404]) - - -class TestServicesFiltering(ContractTestCase): - """GET /productos/api/v1/services/ with filters""" - - def _create_owner_with_cat(self): - """Helper to create owner and cat""" - owner_data = sample_pet_owner(unique_email("service_owner")) - owner_response = self.post(Endpoints.PET_OWNERS, owner_data) - owner_id = owner_response.data["id"] - - pet_data = {**SAMPLE_CAT, "owner": owner_id} - pet_response = self.post(Endpoints.PETS, pet_data) - return pet_response.data["id"] - - def _create_owner_with_dog(self): - """Helper to create owner and dog""" - owner_data = sample_pet_owner(unique_email("service_owner")) - owner_response = self.post(Endpoints.PET_OWNERS, owner_data) - owner_id = owner_response.data["id"] - - pet_data = {**SAMPLE_DOG, "owner": owner_id} - pet_response = self.post(Endpoints.PETS, pet_data) - return pet_response.data["id"] - - def test_filter_services_by_cat(self): - """ - Services filtered by cat pet_id returns appropriate services. - - Request (from production turnero): - GET /productos/api/v1/services/?pet_id=123 - - Response structure validates services available for CAT type. - """ - cat_id = self._create_owner_with_cat() - response = self.get(Endpoints.SERVICES, params={"pet_id": cat_id, "page_size": 10}) - - # Should return services or handle gracefully - self.assertIn(response.status_code, [200, 404]) - if response.status_code == 200: - data = response.data - services = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(services, list) - - def test_filter_services_by_dog(self): - """ - Services filtered by dog pet_id returns appropriate services. - - Different pet types may have different service availability. - """ - dog_id = self._create_owner_with_dog() - response = self.get(Endpoints.SERVICES, params={"pet_id": dog_id, "page_size": 10}) - - self.assertIn(response.status_code, [200, 404]) - if response.status_code == 200: - data = response.data - services = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(services, list) - - def test_services_without_pet_returns_all(self): - """ - Services without pet filter returns all available services. - - Used for initial service browsing before pet selection. - """ - response = self.get(Endpoints.SERVICES, params={"page_size": 10}) - - self.assert_status(response, 200) - data = response.data - services = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(services, list) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/__init__.py b/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/__init__.py deleted file mode 100644 index ece88b5..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Contract tests for solicitudes app endpoints diff --git a/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/test_service_requests.py b/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/test_service_requests.py deleted file mode 100644 index 551dab9..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/solicitudes/test_service_requests.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Contract Tests: Service Requests API - -Endpoint: /solicitudes/service-requests/ -App: solicitudes - -Creates and manages service requests (appointment bookings). -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints - - -class TestServiceRequestList(ContractTestCase): - """GET /solicitudes/service-requests/""" - - def test_list_returns_200(self): - """GET should return list of service requests (with pagination)""" - response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 1}) - - self.assert_status(response, 200) - - def test_returns_list(self): - """GET should return a list (possibly paginated)""" - response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 10}) - - data = response.data - requests_list = data["results"] if isinstance(data, dict) and "results" in data else data - self.assertIsInstance(requests_list, list) - - -class TestServiceRequestFields(ContractTestCase): - """Field validation for service requests""" - - def test_has_state_field(self): - """Service requests should have a state/status field""" - response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 1}) - - data = response.data - requests_list = data["results"] if isinstance(data, dict) and "results" in data else data - - if len(requests_list) > 0: - req = requests_list[0] - has_state = "state" in req or "status" in req - self.assertTrue(has_state, "Service request should have state/status field") - - -class TestServiceRequestCreate(ContractTestCase): - """POST /solicitudes/service-requests/""" - - def test_create_requires_fields(self): - """Creating service request with empty data should fail""" - response = self.post(Endpoints.SERVICE_REQUESTS, {}) - - # Should return 400 with validation errors - self.assert_status(response, 400) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/workflows/__init__.py b/cfg/amar/soleprint/station/tools/tester/tests/workflows/__init__.py deleted file mode 100644 index d63482a..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/workflows/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Contract tests for frontend workflows (compositions of endpoint tests) diff --git a/cfg/amar/soleprint/station/tools/tester/tests/workflows/test_turnero_general.py b/cfg/amar/soleprint/station/tools/tester/tests/workflows/test_turnero_general.py deleted file mode 100644 index 9891dbf..0000000 --- a/cfg/amar/soleprint/station/tools/tester/tests/workflows/test_turnero_general.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Workflow Test: General Turnero Flow - -This is a COMPOSITION test that validates the full turnero flow -by calling endpoints in sequence. Use this to ensure the flow works -end-to-end, but individual endpoint behavior is tested in app folders. - -Flow: -1. Check coverage at address -2. Create pet owner (guest with mock email) -3. Create pet for owner -4. Get available services for pet -5. Create service request - -Frontend route: /turnos/ -User type: Guest (invitado) -""" - -from ..base import ContractTestCase -from ..endpoints import Endpoints -from ..helpers import sample_pet_owner, unique_email, SAMPLE_CAT - - -class TestTurneroGeneralFlow(ContractTestCase): - """ - End-to-end flow test for general turnero. - - Note: This tests the SEQUENCE of calls, not individual endpoint behavior. - Individual endpoint tests are in mascotas/, productos/, solicitudes/. - """ - - def test_full_flow_sequence(self): - """ - Complete turnero flow should work end-to-end. - - This test validates that a guest user can complete the full - appointment booking flow. - """ - # Step 0: Check coverage at address - coverage_response = self.get(Endpoints.COVERAGE_CHECK, params={ - "lat": -34.6037, - "lng": -58.3816, - }) - self.assert_status(coverage_response, 200) - - # Step 1: Create pet owner (frontend creates mock email for guest) - mock_email = unique_email("invitado") - owner_data = sample_pet_owner(mock_email) - owner_response = self.post(Endpoints.PET_OWNERS, owner_data) - self.assert_status(owner_response, 201) - owner_id = owner_response.data["id"] - - # Step 2: Create pet for owner - pet_data = {**SAMPLE_CAT, "owner": owner_id} - pet_response = self.post(Endpoints.PETS, pet_data) - self.assert_status(pet_response, 201) - pet_id = pet_response.data["id"] - - # Step 3: Get services (optionally filtered by pet) - services_response = self.get(Endpoints.SERVICES, params={"pet_id": pet_id}) - # Services endpoint may return 200 even without pet filter - self.assertIn(services_response.status_code, [200, 404]) - - # Note: Steps 4-5 (select date/time, create service request) require - # more setup (available times, cart, etc.) and are tested separately.