spr migrated books, and tester
This commit is contained in:
425
CLAUDE.md
425
CLAUDE.md
@@ -15,67 +15,91 @@ spr/
|
|||||||
├── CLAUDE.md # You are here
|
├── CLAUDE.md # You are here
|
||||||
├── README.md # User-facing docs
|
├── README.md # User-facing docs
|
||||||
├── schema.json # Source of truth for models
|
├── schema.json # Source of truth for models
|
||||||
├── cfg/ # Framework configurations
|
├── build.py # Build tool (dev/deploy)
|
||||||
│ ├── soleprint.config.json # Model definitions
|
│
|
||||||
│ └── amar/ # Room-specific configs (absolute paths to Dockerfiles)
|
├── cfg/ # Room configurations
|
||||||
│ ├── .env.example
|
│ ├── soleprint.config.json # Framework model definitions
|
||||||
|
│ └── amar/ # AMAR room config
|
||||||
|
│ ├── .env
|
||||||
│ ├── docker-compose.yml
|
│ ├── docker-compose.yml
|
||||||
│ └── link/ # Databrowse adapter for amar
|
│ ├── Dockerfile.backend
|
||||||
|
│ ├── Dockerfile.frontend
|
||||||
|
│ ├── ctrl/ # Room-specific scripts
|
||||||
|
│ ├── databrowse/depot/ # AMAR schema for databrowse
|
||||||
|
│ ├── tester/tests/ # AMAR-specific tests
|
||||||
|
│ ├── monitors/turnos/ # AMAR-specific monitors
|
||||||
|
│ ├── models/ # AMAR models (pydantic, django, prisma)
|
||||||
|
│ └── link/ # Databrowse adapter
|
||||||
│
|
│
|
||||||
├── ctrl/ # Soleprint room's own ctrl
|
├── ctrl/ # Soleprint standalone ctrl (Docker)
|
||||||
|
│ ├── build.sh # python build.py dev --cfg <room>
|
||||||
|
│ ├── start.sh # docker compose up
|
||||||
|
│ ├── stop.sh
|
||||||
|
│ └── logs.sh
|
||||||
│
|
│
|
||||||
├── artery/ # VERSIONED - Vital connections
|
├── artery/ # Vital connections
|
||||||
│ ├── veins/ # Single-responsibility connectors
|
│ ├── veins/ # Stateless API connectors
|
||||||
|
│ │ ├── jira/
|
||||||
|
│ │ ├── slack/
|
||||||
|
│ │ ├── google/
|
||||||
|
│ │ ├── base.py
|
||||||
|
│ │ ├── oauth.py
|
||||||
|
│ │ └── PATTERNS.md
|
||||||
|
│ ├── shunts/ # Fake connectors for testing
|
||||||
|
│ │ └── example/
|
||||||
│ ├── pulses/ # Composed: Vein + Room + Depot
|
│ ├── pulses/ # Composed: Vein + Room + Depot
|
||||||
│ ├── room/ # Base room code and ctrl templates
|
│ ├── plexuses/ # Full apps: backend + frontend + DB
|
||||||
│ └── depots/ # Data storage
|
│ └── room/ # Base room templates
|
||||||
│
|
│
|
||||||
├── atlas/ # VERSIONED - Documentation system
|
├── atlas/ # Documentation system
|
||||||
│ ├── templates/ # Gherkin, BDD patterns
|
│ ├── book/ # Gherkin samples, feature docs, arch diagrams
|
||||||
│ ├── books/ # Composed: Template + Depot
|
│ ├── static/ # Prism syntax highlighting
|
||||||
│ └── depots/ # Data storage
|
|
||||||
│
|
|
||||||
├── station/ # VERSIONED - Tools & execution
|
|
||||||
│ ├── tools/ # Utilities, generators, runners
|
|
||||||
│ │ ├── modelgen/ # Model generation from config/codebases
|
|
||||||
│ │ ├── datagen/ # Test data generation
|
|
||||||
│ │ ├── tester/ # Test runner (BDD/playwright)
|
|
||||||
│ │ └── ...
|
|
||||||
│ ├── desks/ # Composed: Cabinet + Room + Depots
|
|
||||||
│ ├── rooms/ # Environment configs
|
|
||||||
│ └── depots/ # Data storage
|
|
||||||
│
|
|
||||||
├── data/ # JSON content files (versioned)
|
|
||||||
│
|
|
||||||
├── soleprint/ # VERSIONED - Core coordinator
|
|
||||||
│ ├── main.py # Multi-port entry point (production)
|
|
||||||
│ ├── run.py # Single-port bare-metal dev server
|
|
||||||
│ ├── index.html # Landing page
|
|
||||||
│ ├── requirements.txt # Dependencies
|
|
||||||
│ └── dataloader/ # Data loading module
|
|
||||||
│
|
|
||||||
├── gen/ # RUNNABLE instance (gitignored, copies)
|
|
||||||
│ ├── main.py
|
│ ├── main.py
|
||||||
│ ├── run.py
|
│ └── index.html
|
||||||
|
│
|
||||||
|
├── station/ # Tools & execution
|
||||||
|
│ ├── tools/
|
||||||
|
│ │ ├── modelgen/ # Model generation
|
||||||
|
│ │ ├── datagen/ # Test data generation
|
||||||
|
│ │ ├── tester/ # BDD/playwright test runner
|
||||||
|
│ │ ├── graphgen/ # Graph generation
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── monitors/
|
||||||
|
│ └── databrowse/ # SQL data browser (generic)
|
||||||
|
│
|
||||||
|
├── data/ # JSON content files
|
||||||
|
│ ├── rooms.json
|
||||||
|
│ ├── depots.json
|
||||||
|
│ ├── veins.json
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
├── soleprint/ # Core entry points (versioned)
|
||||||
|
│ ├── main.py # Multi-port entry
|
||||||
|
│ ├── run.py # Single-port dev server
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
│ ├── requirements.txt
|
│ ├── requirements.txt
|
||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
│ ├── dataloader/
|
│ └── dataloader/
|
||||||
│ ├── artery/
|
|
||||||
│ ├── atlas/
|
|
||||||
│ ├── station/
|
|
||||||
│ ├── data/
|
|
||||||
│ ├── cfg/
|
|
||||||
│ └── models/ # Generated by modelgen
|
|
||||||
│ └── pydantic/
|
|
||||||
│
|
│
|
||||||
└── mainroom/ # Orchestration: soleprint ↔ managed room
|
├── gen/ # Built instance (gitignored)
|
||||||
├── ctrl/ # Orchestration commands (acts on mainroom)
|
│ ├── ... (copies from soleprint/, artery/, atlas/, station/)
|
||||||
├── sbwrapper/ # Sidebar wrapper UI
|
│ ├── models/ # Generated by modelgen
|
||||||
└── soleprint/ # Docker configs for soleprint services
|
│ └── docker-compose.yml # For standalone Docker
|
||||||
├── docker-compose.yml
|
│
|
||||||
├── docker-compose.nginx.yml # Path-based routing
|
└── mainroom/ # Orchestration: soleprint + managed room
|
||||||
└── Dockerfile.fastapi
|
├── amar -> ../cfg/amar # Symlink to room config
|
||||||
|
├── soleprint/ # Soleprint Docker config
|
||||||
|
│ ├── docker-compose.yml
|
||||||
|
│ └── docker-compose.nginx.yml
|
||||||
|
├── sbwrapper/ # Sidebar wrapper UI
|
||||||
|
│ ├── config.json
|
||||||
|
│ ├── sidebar.js
|
||||||
|
│ └── sidebar.css
|
||||||
|
└── ctrl/ # Orchestration scripts
|
||||||
|
├── start.sh # Start amar + soleprint
|
||||||
|
├── stop.sh
|
||||||
|
├── deploy.sh # Deploy to AWS
|
||||||
|
└── server/ # AWS setup scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
## The Four Systems
|
## The Four Systems
|
||||||
@@ -87,130 +111,90 @@ spr/
|
|||||||
| **Atlas** | Actionable documentation | Mapeando el recorrido |
|
| **Atlas** | Actionable documentation | Mapeando el recorrido |
|
||||||
| **Station** | Tools, environments, execution | Centro de control |
|
| **Station** | Tools, environments, execution | Centro de control |
|
||||||
|
|
||||||
## Model Hierarchy
|
## Artery Hierarchy
|
||||||
|
|
||||||
```
|
```
|
||||||
Shared: Room (configs), Depot (data)
|
Vein ──────► Pulse ──────► Plexus
|
||||||
System-specific: Vein (artery), Template (atlas), Tool (station)
|
│ │ │
|
||||||
Composed: Pulse (artery), Book (atlas), Desk (station)
|
│ │ └── Full app: backend + frontend + DB
|
||||||
|
│ │ (e.g., WhatsApp with chat UI)
|
||||||
|
│ │
|
||||||
|
│ └── Composed: Vein + Room + Depot
|
||||||
|
│ (e.g., Jira vein for specific project)
|
||||||
|
│
|
||||||
|
└── Stateless API connector
|
||||||
|
(e.g., Jira, Slack, Google)
|
||||||
|
|
||||||
|
|
||||||
|
Shunt ─── Fake connector for testing
|
||||||
|
(e.g., mercadopago mock with UI to set responses)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Formulas:**
|
| Type | State | Frontend | Deploy |
|
||||||
- Pulse = Vein + Room + Depot
|
|------|-------|----------|--------|
|
||||||
- Book = Template + Depot
|
| Vein | None (or OAuth) | Optional test UI | With soleprint |
|
||||||
- Desk = Cabinet + Room + Depots
|
| Pulse | Vein + config | Uses vein's | With soleprint |
|
||||||
|
| Plexus | Full app state | Required | Self-contained |
|
||||||
|
| Shunt | Configurable responses | Config UI | With soleprint |
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
### Rooms (Environments)
|
### Rooms
|
||||||
A **Room** is an environment with soleprint context, features, and conventions:
|
A **Room** is an environment config with:
|
||||||
- Every room has a `ctrl/` folder with commands that act only on that room
|
- `ctrl/` folder with commands for that room
|
||||||
- Tools are pluggable into any room
|
- `.env` with paths and settings
|
||||||
- Managed projects work with their own defaults (env vars set by mainroom ctrl for orchestration)
|
- Room-specific configs (databrowse depot, tester tests, monitors, models)
|
||||||
|
|
||||||
### Mainroom
|
### Mainroom
|
||||||
The **mainroom** orchestrates interaction between soleprint and managed rooms:
|
Orchestrates soleprint + managed room together:
|
||||||
- `sbwrapper/` - Sidebar UI overlay for any managed app (quick login, Jira info, etc.)
|
- `mainroom/amar` → symlink to `cfg/amar`
|
||||||
- `soleprint/` - Docker configs for running soleprint services
|
- `mainroom/soleprint/` → soleprint Docker config
|
||||||
- `ctrl/` - Mainroom-level orchestration commands (start.sh, stop.sh, etc.)
|
- `mainroom/sbwrapper/` → sidebar overlay for quick login, Jira info
|
||||||
|
- `mainroom/ctrl/` → start/stop/deploy scripts
|
||||||
|
|
||||||
Soleprint can run without a managed room (for testing veins, etc.).
|
### Build & Gen
|
||||||
|
- `soleprint/` = Versioned source
|
||||||
### cfg/ - Configuration
|
- `gen/` = Built instance (gitignored, Docker-ready)
|
||||||
- `cfg/soleprint.config.json` - Framework model definitions
|
- `python build.py dev --cfg amar` copies everything + room config
|
||||||
- `cfg/<room>/` - Room-specific configs (e.g., `cfg/amar/`)
|
|
||||||
- Uses absolute paths to external Dockerfiles
|
|
||||||
- Room-specific tools/adapters (e.g., databrowse link adapter)
|
|
||||||
|
|
||||||
### soleprint/ vs gen/
|
|
||||||
- `soleprint/` = Versioned core files (main.py, run.py, dataloader, index.html)
|
|
||||||
- `gen/` = Gitignored runnable instance (copies, not symlinks - Docker compatible)
|
|
||||||
- `gen/models/` = Generated models
|
|
||||||
|
|
||||||
**Development:** Edit source → `python build.py dev` → run from gen/
|
|
||||||
|
|
||||||
### Modelgen (Generic Tool)
|
|
||||||
Lives in `station/tools/modelgen/`. It:
|
|
||||||
1. Reads `cfg/soleprint.config.json` (source of truth)
|
|
||||||
2. Generates Pydantic models to `gen/models/`
|
|
||||||
3. Generation is **one-time per client** (like install)
|
|
||||||
4. Runs standalone (no model dependencies) for bootstrap
|
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
### Build Tool
|
### Soleprint Standalone (no managed room)
|
||||||
|
|
||||||
The build script at spr root handles both development and deployment builds:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# From spr/
|
|
||||||
python build.py dev # Build gen/ from source (copies)
|
|
||||||
python build.py dev --cfg amar # Include amar room config
|
|
||||||
python build.py deploy --output /path/ # Build for production
|
|
||||||
python build.py models # Only regenerate models
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Up Dev Environment
|
|
||||||
```bash
|
```bash
|
||||||
|
# Build
|
||||||
cd spr/
|
cd spr/
|
||||||
python build.py dev # Creates gen/ with symlinks
|
python build.py dev
|
||||||
|
|
||||||
cd gen/
|
# Run with Docker
|
||||||
python3 -m venv .venv
|
./ctrl/start.sh
|
||||||
.venv/bin/pip install -r requirements.txt
|
|
||||||
.venv/bin/python run.py # Single-port bare-metal dev server
|
# Or bare-metal
|
||||||
# or
|
cd gen && .venv/bin/python run.py
|
||||||
.venv/bin/python main.py # Multi-port (production-like)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bare-metal vs Docker
|
### Soleprint + Amar (with managed room)
|
||||||
- **Bare-metal:** `python run.py` - Single port, all routes internal, for soleprint dev
|
|
||||||
- **Docker:** Use mainroom for managed rooms - separate containers, nginx routing
|
|
||||||
|
|
||||||
### Building for Deployment
|
|
||||||
```bash
|
```bash
|
||||||
|
# Build soleprint with amar config
|
||||||
cd spr/
|
cd spr/
|
||||||
python build.py deploy --output ../deploy/soleprint/ --cfg amar
|
python build.py dev --cfg amar
|
||||||
|
|
||||||
# Then deploy:
|
# Create shared network
|
||||||
rsync -av ../deploy/soleprint/ server:/app/soleprint/
|
docker network create soleprint_network
|
||||||
ssh server 'cd /app/soleprint && ./start.sh'
|
|
||||||
|
|
||||||
# Or use mainroom ctrl scripts:
|
# Start everything
|
||||||
cd mainroom/soleprint/ctrl/local
|
cd mainroom/ctrl
|
||||||
./deploy.sh
|
./start.sh -d # Detached
|
||||||
|
./start.sh # Foreground (logs)
|
||||||
|
./stop.sh # Stop all
|
||||||
```
|
```
|
||||||
|
|
||||||
### Orchestrating with Managed Room
|
### Deploy to AWS
|
||||||
```bash
|
```bash
|
||||||
cd spr/mainroom/ctrl
|
cd mainroom/ctrl
|
||||||
./start.sh # Sets env vars, starts soleprint + managed room services
|
./deploy.sh --dry-run # Preview
|
||||||
|
./deploy.sh # Deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
### Worktrees
|
|
||||||
Feature development in: `/home/mariano/wdir/wts/spr/<branch>`
|
|
||||||
|
|
||||||
Planned:
|
|
||||||
- `databrowse` - Data browser tool (uses modelgen extract)
|
|
||||||
- `sbwrapper` - Sidebar wrapper development
|
|
||||||
|
|
||||||
## External References
|
|
||||||
|
|
||||||
| What | Location | Notes |
|
|
||||||
|------|----------|-------|
|
|
||||||
| Amar Backend | `ama/amar_django_back` | Example managed room |
|
|
||||||
| Amar Frontend | `ama/amar_frontend` | Example managed room |
|
|
||||||
|
|
||||||
## Tools Status
|
|
||||||
|
|
||||||
| Tool | Location | Status | Notes |
|
|
||||||
|------|----------|--------|-------|
|
|
||||||
| modelgen | station/tools/modelgen | Working | Generic model generation (used by build.py, databrowse) |
|
|
||||||
| datagen | station/tools/datagen | Working | Test data generation |
|
|
||||||
| tester | station/tools/tester | Advanced | Full BDD/playwright |
|
|
||||||
| infra | station/tools/infra | Idea | Cloud deploy scripts |
|
|
||||||
| graphgen | station/tools/graphgen | Idea | Graph generation |
|
|
||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
|
|
||||||
| Service | Port |
|
| Service | Port |
|
||||||
@@ -219,60 +203,119 @@ Planned:
|
|||||||
| Artery | 12001 |
|
| Artery | 12001 |
|
||||||
| Atlas | 12002 |
|
| Atlas | 12002 |
|
||||||
| Station | 12003 |
|
| Station | 12003 |
|
||||||
|
| Amar Backend | 8000 |
|
||||||
|
| Amar Frontend | 3000 |
|
||||||
|
|
||||||
## Current State
|
## Tools
|
||||||
|
|
||||||
**Done:**
|
| Tool | Location | Status |
|
||||||
- [x] Project structure finalized
|
|------|----------|--------|
|
||||||
- [x] Schema.json in place
|
| modelgen | station/tools/modelgen | Working |
|
||||||
- [x] Modelgen in station/tools/
|
| datagen | station/tools/datagen | Working |
|
||||||
- [x] soleprint/gen separation with symlinks
|
| tester | station/tools/tester | Advanced |
|
||||||
- [x] Mainroom structure
|
| graphgen | station/tools/graphgen | WIP |
|
||||||
- [x] Docker configs with nginx path-based routing
|
|
||||||
- [x] Build tool with dev/deploy modes and --cfg argument
|
|
||||||
- [x] Bare-metal run.py for single-port dev
|
|
||||||
- [x] cfg/amar/ with absolute paths pattern
|
|
||||||
- [x] Renamed: hub→soleprint, config→cfg, nest→room, pawprint→soleprint
|
|
||||||
|
|
||||||
**Next:**
|
## Monitors
|
||||||
1. [ ] Test mainroom/soleprint/ctrl scripts
|
|
||||||
2. [ ] Test mainroom with managed room (amar)
|
|
||||||
3. [ ] Worktree for databrowse (uses modelgen extract)
|
|
||||||
4. [ ] Worktree for sbwrapper
|
|
||||||
|
|
||||||
## Files Ignored (gitignore)
|
| Monitor | Location | Notes |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| databrowse | station/monitors/databrowse | Generic SQL browser |
|
||||||
|
| turnos | cfg/amar/monitors/turnos | AMAR-specific |
|
||||||
|
|
||||||
- `fails/` - Previous attempts, reference only
|
## Veins
|
||||||
- `def/` - Definition drafts
|
|
||||||
- `gen/` - Entire folder gitignored (regenerate with `python build.py dev`)
|
| Vein | Location | Auth |
|
||||||
- `__pycache__/`, `*.pyc`
|
|------|----------|------|
|
||||||
- `venv/`, `.venv/`
|
| jira | artery/veins/jira | Token |
|
||||||
|
| slack | artery/veins/slack | Token |
|
||||||
|
| google | artery/veins/google | OAuth2 |
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build for dev (from spr/)
|
# === Build ===
|
||||||
python build.py dev
|
python build.py dev # Soleprint only
|
||||||
python build.py dev --cfg amar # With amar room config
|
python build.py dev --cfg amar # With amar config
|
||||||
|
python build.py deploy --output /path/ # Production build
|
||||||
|
|
||||||
# Start dev server (bare-metal, single-port)
|
# === Standalone (soleprint only) ===
|
||||||
cd gen && .venv/bin/python run.py
|
./ctrl/build.sh amar # Build with amar
|
||||||
|
./ctrl/start.sh # Docker start
|
||||||
|
./ctrl/stop.sh # Docker stop
|
||||||
|
|
||||||
# Start production-like (multi-port)
|
# === With Managed Room (mainroom) ===
|
||||||
cd gen && .venv/bin/python main.py
|
cd mainroom/ctrl
|
||||||
|
./start.sh -d # Start detached
|
||||||
|
./start.sh amar # Start only amar
|
||||||
|
./start.sh soleprint # Start only soleprint
|
||||||
|
./stop.sh # Stop all
|
||||||
|
./deploy.sh # Deploy to AWS
|
||||||
|
|
||||||
# Health check
|
# === Bare-metal Dev ===
|
||||||
curl localhost:12000/health
|
cd gen
|
||||||
|
.venv/bin/python run.py # Single-port dev server
|
||||||
|
|
||||||
# Build for deployment
|
# === Health Checks ===
|
||||||
python build.py deploy --output /path/to/deploy/ --cfg amar
|
curl localhost:12000/health # Soleprint
|
||||||
|
curl localhost:8000/health # Amar backend
|
||||||
# Deploy via ctrl scripts
|
|
||||||
cd mainroom/soleprint/ctrl/local && ./deploy.sh
|
|
||||||
|
|
||||||
# Docker (via mainroom)
|
|
||||||
cd mainroom/soleprint && docker compose up -d
|
|
||||||
|
|
||||||
# Modelgen (generic tool)
|
|
||||||
python -m station.tools.modelgen from-config -c cfg/soleprint.config.json -o gen/models/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Integration with ppl/ (Infrastructure)
|
||||||
|
|
||||||
|
The `ppl/` repo manages infrastructure alongside spr:
|
||||||
|
|
||||||
|
```
|
||||||
|
wdir/
|
||||||
|
├── spr/ # This repo (soleprint)
|
||||||
|
├── ppl/ # Pipelines & infrastructure
|
||||||
|
│ ├── ctrl/
|
||||||
|
│ │ ├── deploy-gen.sh # Build spr + deploy via mainroom
|
||||||
|
│ │ └── dns.sh # Route53 DNS management
|
||||||
|
│ ├── ci/ # Woodpecker CI configs
|
||||||
|
│ ├── gateway/ # Nginx/Caddy configs
|
||||||
|
│ └── pipelines/ # CI/CD pipelines
|
||||||
|
└── ama/ # Amar source code
|
||||||
|
├── amar_django_back/
|
||||||
|
└── amar_frontend/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy from ppl/
|
||||||
|
```bash
|
||||||
|
cd ppl/ctrl
|
||||||
|
./deploy-gen.sh # Build spr + deploy to AWS
|
||||||
|
./deploy-gen.sh --dry-run # Preview
|
||||||
|
./dns.sh add soleprint # Add soleprint.mcrn.ar DNS
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Structure (mcrn.ar)
|
||||||
|
```
|
||||||
|
~/mainroom/ # Deployed mainroom
|
||||||
|
├── amar/ # Amar Docker services
|
||||||
|
├── soleprint/ # Soleprint Docker services
|
||||||
|
└── ctrl/ # Server-side scripts
|
||||||
|
|
||||||
|
# Services run on:
|
||||||
|
soleprint.mcrn.ar:12000 # Soleprint
|
||||||
|
amar.mcrn.ar # Amar (nginx proxied)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding New Services
|
||||||
|
1. Add DNS: `ppl/ctrl/dns.sh add <service>`
|
||||||
|
2. Add nginx config in `ppl/gateway/`
|
||||||
|
3. Add docker-compose in `mainroom/<service>/`
|
||||||
|
4. Update `mainroom/ctrl/start.sh` if needed
|
||||||
|
|
||||||
|
## External Paths
|
||||||
|
|
||||||
|
| What | Path |
|
||||||
|
|------|------|
|
||||||
|
| Amar Backend | /home/mariano/wdir/ama/amar_django_back |
|
||||||
|
| Amar Frontend | /home/mariano/wdir/ama/amar_frontend |
|
||||||
|
| Venv | /home/mariano/wdir/venv/spr |
|
||||||
|
| Pipelines | /home/mariano/wdir/ppl |
|
||||||
|
|
||||||
|
## Files Ignored
|
||||||
|
|
||||||
|
- `gen/` - Regenerate with `python build.py dev`
|
||||||
|
- `fails/`, `def/` - Drafts
|
||||||
|
- `__pycache__/`, `.venv/`
|
||||||
|
|||||||
152
README.md
152
README.md
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
> Cada paso deja huella / Each step leaves a mark
|
> Cada paso deja huella / Each step leaves a mark
|
||||||
|
|
||||||
Development workflow and documentation platform. Run, test, and document everything in one place.
|
Development workflow platform. Run, test, and document everything in one place.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build dev environment
|
# Build
|
||||||
python build.py dev
|
python build.py dev --cfg amar
|
||||||
|
|
||||||
# Run
|
# Run standalone (Docker)
|
||||||
cd gen
|
./ctrl/start.sh
|
||||||
python3 -m venv .venv
|
|
||||||
.venv/bin/pip install -r requirements.txt
|
# Or bare-metal
|
||||||
.venv/bin/python run.py # Single-port bare-metal dev
|
cd gen && .venv/bin/python run.py
|
||||||
# Visit http://localhost:12000
|
# Visit http://localhost:12000
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,73 +23,72 @@ python3 -m venv .venv
|
|||||||
| | System | What it does |
|
| | System | What it does |
|
||||||
|---|--------|--------------|
|
|---|--------|--------------|
|
||||||
| 👣 | **Soleprint** | Core coordinator, routing, landing page |
|
| 👣 | **Soleprint** | Core coordinator, routing, landing page |
|
||||||
| 💉 | **Artery** | Connectors to external services (Jira, Slack, APIs) |
|
| 💉 | **Artery** | Connectors to external services (Jira, Slack, Google) |
|
||||||
| 🗺️ | **Atlas** | Actionable documentation (BDD, Gherkin, specs) |
|
| 🗺️ | **Atlas** | Actionable documentation (BDD, Gherkin, specs) |
|
||||||
| 🎛️ | **Station** | Tools, environments, test runners |
|
| 🎛️ | **Station** | Tools (modelgen, tester) and monitors (databrowse) |
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
spr/
|
spr/
|
||||||
├── schema.json # Model definitions (source of truth)
|
├── build.py # Build tool
|
||||||
├── cfg/ # Framework configuration
|
├── cfg/ # Room configurations
|
||||||
│ ├── soleprint.config.json
|
│ ├── soleprint.config.json
|
||||||
│ └── amar/ # Room-specific configs
|
│ └── amar/ # AMAR room (docker-compose, tests, models)
|
||||||
|
├── ctrl/ # Standalone Docker scripts
|
||||||
│
|
│
|
||||||
├── soleprint/ # Core coordinator (versioned)
|
├── artery/ # Connectors
|
||||||
│ ├── main.py # Multi-port entry point
|
│ ├── veins/ # Jira, Slack, Google
|
||||||
│ ├── run.py # Single-port bare-metal dev
|
│ ├── shunts/ # Fake connectors for testing
|
||||||
│ └── dataloader/
|
│ └── plexuses/ # Full apps (backend + frontend)
|
||||||
│
|
│
|
||||||
├── artery/ # Connectors (versioned)
|
├── atlas/ # Documentation
|
||||||
├── atlas/ # Documentation (versioned)
|
│ └── book/ # Gherkin samples, feature docs
|
||||||
├── station/ # Tools (versioned)
|
|
||||||
│ └── tools/
|
|
||||||
│ ├── modelgen/ # Generates models from config
|
|
||||||
│ ├── datagen/ # Test data generation
|
|
||||||
│ └── tester/ # BDD/contract test runner
|
|
||||||
│
|
│
|
||||||
├── data/ # JSON content
|
├── station/ # Tools & monitors
|
||||||
├── gen/ # Runnable instance (symlinks + generated)
|
│ ├── tools/ # modelgen, tester, datagen
|
||||||
|
│ └── monitors/ # databrowse
|
||||||
│
|
│
|
||||||
└── mainroom/ # Orchestration: soleprint ↔ managed room
|
├── soleprint/ # Core (versioned)
|
||||||
├── ctrl/ # Orchestration commands
|
├── gen/ # Built instance (gitignored)
|
||||||
|
│
|
||||||
|
└── mainroom/ # Orchestration with managed room
|
||||||
|
├── amar -> cfg/amar
|
||||||
|
├── soleprint/ # Soleprint Docker config
|
||||||
├── sbwrapper/ # Sidebar wrapper UI
|
├── sbwrapper/ # Sidebar wrapper UI
|
||||||
└── soleprint/ # Docker configs
|
└── ctrl/ # start, stop, deploy scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
## Components
|
## Artery Hierarchy
|
||||||
|
|
||||||
**Shared:**
|
```
|
||||||
- **Room** - Environment configuration
|
Vein ──► Pulse ──► Plexus
|
||||||
- **Depot** - Data storage
|
│ │ │
|
||||||
|
│ │ └── Full app (backend + frontend + DB)
|
||||||
|
│ └── Composed (Vein + Room + Depot)
|
||||||
|
└── Stateless API connector
|
||||||
|
|
||||||
**System-specific:**
|
Shunt ── Fake connector for testing
|
||||||
- **Vein** (Artery) - Single connector
|
|
||||||
- **Template** (Atlas) - Doc pattern
|
|
||||||
- **Tool** (Station) - Utility
|
|
||||||
|
|
||||||
**Composed:**
|
|
||||||
- **Pulse** = Vein + Room + Depot
|
|
||||||
- **Book** = Template + Depot
|
|
||||||
- **Desk** = Cabinet + Room + Depots
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Build and run
|
|
||||||
```bash
|
|
||||||
python build.py dev # Soleprint only
|
|
||||||
python build.py dev --cfg amar # With amar room config
|
|
||||||
|
|
||||||
cd gen
|
|
||||||
.venv/bin/python run.py # Bare-metal single-port
|
|
||||||
.venv/bin/python main.py # Multi-port (production-like)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run with Docker (via mainroom)
|
## Usage
|
||||||
|
|
||||||
|
### Standalone (soleprint only)
|
||||||
```bash
|
```bash
|
||||||
cd mainroom/soleprint
|
python build.py dev
|
||||||
docker compose up -d
|
./ctrl/start.sh # Docker
|
||||||
|
./ctrl/stop.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Managed Room (amar + soleprint)
|
||||||
|
```bash
|
||||||
|
python build.py dev --cfg amar
|
||||||
|
docker network create soleprint_network
|
||||||
|
|
||||||
|
cd mainroom/ctrl
|
||||||
|
./start.sh -d # Start detached
|
||||||
|
./stop.sh # Stop all
|
||||||
|
./deploy.sh # Deploy to AWS
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ports
|
## Ports
|
||||||
@@ -97,36 +96,25 @@ docker compose up -d
|
|||||||
| Service | Port |
|
| Service | Port |
|
||||||
|---------|------|
|
|---------|------|
|
||||||
| Soleprint | 12000 |
|
| Soleprint | 12000 |
|
||||||
| Artery | 12001 |
|
| Amar Backend | 8000 |
|
||||||
| Atlas | 12002 |
|
| Amar Frontend | 3000 |
|
||||||
| Station | 12003 |
|
|
||||||
|
|
||||||
## Architecture
|
## Tools
|
||||||
|
|
||||||
```
|
| Tool | Purpose |
|
||||||
soleprint/ → Versioned core files (main.py, run.py, dataloader)
|
|------|---------|
|
||||||
gen/ → Runnable instance (symlinks to soleprint/ + systems)
|
| modelgen | Generate models from config |
|
||||||
gen/models/ → Generated once per client (like install)
|
| tester | BDD/playwright test runner |
|
||||||
cfg/ → Configuration (copied to gen/cfg/)
|
| datagen | Test data generation |
|
||||||
|
| databrowse | SQL data browser |
|
||||||
|
|
||||||
mainroom/ → Orchestration layer
|
## Veins
|
||||||
├── sbwrapper → UI overlay for managed apps
|
|
||||||
└── soleprint → Docker for soleprint services
|
|
||||||
```
|
|
||||||
|
|
||||||
## Background
|
| Vein | Auth |
|
||||||
|
|------|------|
|
||||||
Born from the friction of:
|
| jira | Token |
|
||||||
- Testing requiring PRs on small teams
|
| slack | Token |
|
||||||
- Documentation scattered across tools
|
| google | OAuth2 |
|
||||||
- Quick API connectors taking too long to set up
|
|
||||||
- No self-contained environment to experiment freely
|
|
||||||
|
|
||||||
Soleprint lets you run everything in isolation while building reusable pieces.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
TBD
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,15 @@ Hierarchy (simple → complex):
|
|||||||
└── Stateless API connector
|
└── Stateless API connector
|
||||||
(e.g., Jira client, Slack client)
|
(e.g., Jira client, Slack client)
|
||||||
|
|
||||||
|
|
||||||
|
Shunt ─── Fake connector for testing
|
||||||
|
(e.g., mercadopago shunt with configurable responses)
|
||||||
|
|
||||||
Components:
|
Components:
|
||||||
- veins/ Stateless connectors (core/ + api/)
|
- veins/ Stateless connectors (core/ + api/)
|
||||||
- pulses/ Composed: Vein + Room + Depot
|
- pulses/ Composed: Vein + Room + Depot
|
||||||
- plexuses/ Full applications with frontend
|
- plexuses/ Full applications with frontend
|
||||||
|
- shunts/ Fake connectors for testing (configurable responses)
|
||||||
- rooms/ Environment configs
|
- rooms/ Environment configs
|
||||||
- depots/ Data storage
|
- depots/ Data storage
|
||||||
|
|
||||||
@@ -31,6 +36,12 @@ Differences:
|
|||||||
| Frontend | Optional test UI | None (uses vein) | Required full frontend |
|
| Frontend | Optional test UI | None (uses vein) | Required full frontend |
|
||||||
| Webhooks | No | No | Yes |
|
| Webhooks | No | No | Yes |
|
||||||
| Deploy | With soleprint | With soleprint | Self-contained (docker) |
|
| Deploy | With soleprint | With soleprint | Self-contained (docker) |
|
||||||
|
|
||||||
|
| Aspect | Shunt |
|
||||||
|
|------------|---------------------------------------------------------------|
|
||||||
|
| Purpose | Fake/mock external service for testing |
|
||||||
|
| Frontend | Config UI to set responses |
|
||||||
|
| Deploy | With soleprint (replaces real vein during testing) |
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import veins
|
from . import veins
|
||||||
|
|||||||
18
artery/shunts/__init__.py
Normal file
18
artery/shunts/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
Shunts - Fake connectors for testing.
|
||||||
|
|
||||||
|
A shunt redirects flow when the real service isn't available.
|
||||||
|
Each shunt mimics a vein's interface with configurable responses.
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
shunts/<service>/
|
||||||
|
├── main.py # FastAPI with config UI
|
||||||
|
├── depot/
|
||||||
|
│ └── responses.json # Configurable fake responses
|
||||||
|
└── README.md
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
1. Start the shunt instead of the real vein
|
||||||
|
2. Configure responses via the UI or responses.json
|
||||||
|
3. Run tests against the shunt
|
||||||
|
"""
|
||||||
37
artery/shunts/example/README.md
Normal file
37
artery/shunts/example/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Example Shunt
|
||||||
|
|
||||||
|
Template for creating fake service connectors for testing.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the shunt
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
# Or with uvicorn
|
||||||
|
uvicorn main:app --port 8099 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating a New Shunt
|
||||||
|
|
||||||
|
1. Copy this directory:
|
||||||
|
```bash
|
||||||
|
cp -r shunts/example shunts/mercadopago
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `depot/responses.json` with fake responses
|
||||||
|
|
||||||
|
3. Update `main.py` to match the real vein's API endpoints
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `depot/responses.json` to configure fake responses:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"GET /endpoint": {"response": "data"},
|
||||||
|
"POST /endpoint": {"success": true}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The shunt UI at `/` shows current configuration.
|
||||||
15
artery/shunts/example/depot/responses.json
Normal file
15
artery/shunts/example/depot/responses.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"GET /users": [
|
||||||
|
{"id": 1, "name": "Test User", "email": "test@example.com"}
|
||||||
|
],
|
||||||
|
"GET /users/1": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Test User",
|
||||||
|
"email": "test@example.com"
|
||||||
|
},
|
||||||
|
"POST /users": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "Created User",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
93
artery/shunts/example/main.py
Normal file
93
artery/shunts/example/main.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
Example Shunt - Template for creating fake service connectors.
|
||||||
|
|
||||||
|
Copy this to create a new shunt:
|
||||||
|
cp -r shunts/example shunts/mercadopago
|
||||||
|
|
||||||
|
Then customize:
|
||||||
|
- Update responses.json with fake responses
|
||||||
|
- Add endpoints matching the real vein's API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
|
|
||||||
|
app = FastAPI(title="Example Shunt", description="Fake service for testing")
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).parent
|
||||||
|
DEPOT_DIR = Path(os.getenv("SHUNT_DEPOT", str(BASE_DIR / "depot")))
|
||||||
|
|
||||||
|
# Load responses
|
||||||
|
RESPONSES_FILE = DEPOT_DIR / "responses.json"
|
||||||
|
responses = {}
|
||||||
|
if RESPONSES_FILE.exists():
|
||||||
|
responses = json.loads(RESPONSES_FILE.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok", "shunt": "example"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
def config_ui():
|
||||||
|
"""Simple UI to view/edit responses."""
|
||||||
|
return f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Example Shunt</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: system-ui; background: #111827; color: #f3f4f6; padding: 2rem; }}
|
||||||
|
h1 {{ color: #60a5fa; }}
|
||||||
|
pre {{ background: #1f2937; padding: 1rem; border-radius: 8px; overflow-x: auto; }}
|
||||||
|
.info {{ color: #9ca3af; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Example Shunt</h1>
|
||||||
|
<p class="info">This shunt returns configurable fake responses for testing.</p>
|
||||||
|
<h2>Current Responses</h2>
|
||||||
|
<pre>{json.dumps(responses, indent=2)}</pre>
|
||||||
|
<p class="info">Edit {RESPONSES_FILE} to change responses.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/{endpoint:path}")
|
||||||
|
def fake_get(endpoint: str):
|
||||||
|
"""Return configured response for GET requests."""
|
||||||
|
key = f"GET /{endpoint}"
|
||||||
|
if key in responses:
|
||||||
|
return responses[key]
|
||||||
|
return {"error": "not configured", "endpoint": endpoint, "method": "GET"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/{endpoint:path}")
|
||||||
|
async def fake_post(endpoint: str, request: Request):
|
||||||
|
"""Return configured response for POST requests."""
|
||||||
|
key = f"POST /{endpoint}"
|
||||||
|
if key in responses:
|
||||||
|
return responses[key]
|
||||||
|
body = (
|
||||||
|
await request.json()
|
||||||
|
if request.headers.get("content-type") == "application/json"
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"error": "not configured",
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"method": "POST",
|
||||||
|
"received": body,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8099)
|
||||||
56
atlas/.dockerignore
Normal file
56
atlas/.dockerignore
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Data and logs
|
||||||
|
data/
|
||||||
|
*.log
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Node (for frontend)
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
.next/
|
||||||
3
atlas/.gitignore
vendored
Normal file
3
atlas/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
def
|
||||||
|
__pycache__
|
||||||
|
drive
|
||||||
116
atlas/CLAUDE.md
Normal file
116
atlas/CLAUDE.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# ALBUM: Documentation System
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Documentation, templates, and data. Composed into books.
|
||||||
|
|
||||||
|
### Book Types
|
||||||
|
|
||||||
|
**Single-larder book** (template: null)
|
||||||
|
- Book and larder are interchangeable
|
||||||
|
- Just static content served directly
|
||||||
|
- Listed in books.json, NOT in larders.json
|
||||||
|
|
||||||
|
**Templated book** (template: {...})
|
||||||
|
- Template defines the structure
|
||||||
|
- Larder must contain only elements matching that template
|
||||||
|
- Template validates/constrains the larder content
|
||||||
|
- Shows a styled landing page linking to template and larder
|
||||||
|
|
||||||
|
### Larders vs Books
|
||||||
|
|
||||||
|
- **Larders** = buckets that connect to other systems (e.g., drive, external data sources)
|
||||||
|
- **Books** = standalone content, may be single-larder or template+larder
|
||||||
|
- Single-larder books are listed as books, not larders
|
||||||
|
|
||||||
|
### Templated Book Structure
|
||||||
|
|
||||||
|
A templated book must contain a `template/` folder with the template definition inside:
|
||||||
|
|
||||||
|
```
|
||||||
|
book/{book-slug}/
|
||||||
|
├── template/ # REQUIRED for templated books
|
||||||
|
│ └── {template-files}
|
||||||
|
├── {larder-name}/ # The actual data (marked with .larder file)
|
||||||
|
│ └── .larder
|
||||||
|
├── index.html # Larder browser (used at /book/{slug}/larder/)
|
||||||
|
└── detail.html # Detail view template (if needed)
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
book/feature-form-samples/
|
||||||
|
├── template/ # Template definition
|
||||||
|
│ └── plantilla-flujo.md
|
||||||
|
├── feature-form/ # Larder data (constrained by template)
|
||||||
|
│ ├── .larder
|
||||||
|
│ ├── pet-owner/
|
||||||
|
│ ├── veterinarian/
|
||||||
|
│ └── backoffice/
|
||||||
|
├── index.html # Larder browser
|
||||||
|
└── detail.html # Detail renderer
|
||||||
|
```
|
||||||
|
|
||||||
|
Routes for templated books:
|
||||||
|
- `/book/{slug}/` → Landing page (template + larder links)
|
||||||
|
- `/book/{slug}/template/` → Template definition
|
||||||
|
- `/book/{slug}/larder/` → Larder browser
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Template (Patterns)
|
||||||
|
|
||||||
|
**Gherkin/BDD**
|
||||||
|
- Status: Pending
|
||||||
|
- Goal: .feature files, simple templates for non-tech
|
||||||
|
|
||||||
|
**Index Templates**
|
||||||
|
- Status: Pending
|
||||||
|
- Goal: HTML generators for indexes
|
||||||
|
|
||||||
|
### Vault (Data)
|
||||||
|
|
||||||
|
**drive/**
|
||||||
|
- Status: Downloaded
|
||||||
|
- Contents: Company drive (identity, marketing, ops, supply, finance, clients, pitches)
|
||||||
|
|
||||||
|
### Book (Composed Docs)
|
||||||
|
|
||||||
|
**drive-index**
|
||||||
|
- Status: Priority
|
||||||
|
- Goal: Two indexes (internal full, public privacy-conscious)
|
||||||
|
|
||||||
|
**flow-docs**
|
||||||
|
- Status: Pending
|
||||||
|
- Goal: User flow documentation (pet owner, vet, ops)
|
||||||
|
|
||||||
|
## Upward Report
|
||||||
|
```
|
||||||
|
ALBUM: Drive index priority. Template + vault → book composition defined.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
1. Drive index book (HTML from vault/drive)
|
||||||
|
2. One gherkin template example
|
||||||
|
3. Flow documentation structure
|
||||||
|
|
||||||
|
## Vault Contents (vault/drive)
|
||||||
|
- 01.Identidad Amar Mascotas
|
||||||
|
- 02. Marketing contenidos
|
||||||
|
- 03. Marketing Growth
|
||||||
|
- 05. ATC - Operaciones
|
||||||
|
- 06. Supply (vetes-labo-clinicas)
|
||||||
|
- 07. Finanzas y contabilidad
|
||||||
|
- Clientes - ventas - devoluciones
|
||||||
|
- Pitch Decks - Presentaciones
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
- **URL**: https://album.mcrn.ar
|
||||||
|
- **Port**: 12002
|
||||||
|
- **Service**: `systemctl status album`
|
||||||
|
|
||||||
|
FastAPI app serving documentation. Currently serves index.html at root, expandable for book browsing.
|
||||||
|
|
||||||
|
## Upstream (for main pawprint thread)
|
||||||
|
|
||||||
|
This is now a separate repo. See pawprint/UPSTREAM.md for merge notes.
|
||||||
118
atlas/book-template.html
Normal file
118
atlas/book-template.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ book.title }} · Album</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { background: #0a0a0a; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #e5e5e5;
|
||||||
|
background: #15803d;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.logo { width: 64px; height: 64px; color: white; }
|
||||||
|
h1 { font-size: 2rem; margin: 0; color: white; }
|
||||||
|
.tagline {
|
||||||
|
color: rgba(255,255,255,0.85);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
section h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
.composition {
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 2px solid #15803d;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.composition h3 { margin: 0 0 0.75rem 0; font-size: 1.1rem; color: #15803d; }
|
||||||
|
.components {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.component {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1a1a1a;
|
||||||
|
transition: all 0.15s;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.component:hover {
|
||||||
|
border-color: #15803d;
|
||||||
|
box-shadow: 0 4px 12px rgba(21, 128, 61, 0.15);
|
||||||
|
}
|
||||||
|
.component h4 { margin: 0 0 0.5rem 0; font-size: 1rem; color: #15803d; }
|
||||||
|
.component p { margin: 0; font-size: 0.9rem; color: #666; }
|
||||||
|
.component .arrow { float: right; color: #15803d; font-size: 1.2rem; }
|
||||||
|
footer {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.3);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
}
|
||||||
|
footer a { color: white; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<svg class="logo" viewBox="0 0 48 48" fill="currentColor">
|
||||||
|
<path d="M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z" opacity="0.3"/>
|
||||||
|
<path d="M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z" opacity="0.5"/>
|
||||||
|
<path d="M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<line x1="24" y1="10" x2="24" y2="42" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
<h1>{{ book.title }}</h1>
|
||||||
|
</header>
|
||||||
|
<p class="tagline">Templated book</p>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="composition">
|
||||||
|
<h3>{{ book.title }}</h3>
|
||||||
|
<div class="components">
|
||||||
|
<a href="/book/{{ book.slug }}/template/" class="component">
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
<h4>{{ book.template.title }}</h4>
|
||||||
|
</a>
|
||||||
|
<a href="/book/{{ book.slug }}/larder/" class="component">
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
<h4>{{ book.larder.title }}</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/">← Album</a>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
184
atlas/book/arch-model/01-backend-architecture.dot
Normal file
184
atlas/book/arch-model/01-backend-architecture.dot
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
585
atlas/book/arch-model/01-backend-architecture.svg
Normal file
585
atlas/book/arch-model/01-backend-architecture.svg
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 14.0.5 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: BackendArchitecture Pages: 1 -->
|
||||||
|
<svg width="1559pt" height="935pt"
|
||||||
|
viewBox="0.00 0.00 1559.00 935.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 931.4)">
|
||||||
|
<title>BackendArchitecture</title>
|
||||||
|
<polygon fill="white" stroke="none" points="-4,4 -4,-931.4 1555,-931.4 1555,4 -4,4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="775.5" y="-908.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AMAR Mascotas - Backend Architecture (Django)</text>
|
||||||
|
<g id="clust1" class="cluster">
|
||||||
|
<title>cluster_django_core</title>
|
||||||
|
<path fill="#e8f5e9" stroke="#2e7d32" d="M169,-496.4C169,-496.4 449,-496.4 449,-496.4 455,-496.4 461,-502.4 461,-508.4 461,-508.4 461,-726 461,-726 461,-732 455,-738 449,-738 449,-738 169,-738 169,-738 163,-738 157,-732 157,-726 157,-726 157,-508.4 157,-508.4 157,-502.4 163,-496.4 169,-496.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="309" y="-718.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Django Core</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster_mascotas</title>
|
||||||
|
<path fill="#e3f2fd" stroke="#1565c0" d="M481,-143.2C481,-143.2 733,-143.2 733,-143.2 739,-143.2 745,-149.2 745,-155.2 745,-155.2 745,-590.8 745,-590.8 745,-596.8 739,-602.8 733,-602.8 733,-602.8 481,-602.8 481,-602.8 475,-602.8 469,-596.8 469,-590.8 469,-590.8 469,-155.2 469,-155.2 469,-149.2 475,-143.2 481,-143.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="607" y="-583.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">mascotas (Pets & Veterinarians)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust3" class="cluster">
|
||||||
|
<title>cluster_productos</title>
|
||||||
|
<path fill="#e8f5e9" stroke="#2e7d32" d="M765,-496.4C765,-496.4 1141,-496.4 1141,-496.4 1147,-496.4 1153,-502.4 1153,-508.4 1153,-508.4 1153,-861.2 1153,-861.2 1153,-867.2 1147,-873.2 1141,-873.2 1141,-873.2 765,-873.2 765,-873.2 759,-873.2 753,-867.2 753,-861.2 753,-861.2 753,-508.4 753,-508.4 753,-502.4 759,-496.4 765,-496.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="953" y="-854" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">productos (Services & Pricing)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_solicitudes</title>
|
||||||
|
<path fill="#ffebee" stroke="#c62828" d="M1084,-143.2C1084,-143.2 1290,-143.2 1290,-143.2 1296,-143.2 1302,-149.2 1302,-155.2 1302,-155.2 1302,-455.6 1302,-455.6 1302,-461.6 1296,-467.6 1290,-467.6 1290,-467.6 1084,-467.6 1084,-467.6 1078,-467.6 1072,-461.6 1072,-455.6 1072,-455.6 1072,-155.2 1072,-155.2 1072,-149.2 1078,-143.2 1084,-143.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1187" y="-448.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">solicitudes (Service Requests)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust5" class="cluster">
|
||||||
|
<title>cluster_common</title>
|
||||||
|
<path fill="#f3e5f5" stroke="#7b1fa2" d="M20,-8C20,-8 774,-8 774,-8 780,-8 786,-14 786,-20 786,-20 786,-102.4 786,-102.4 786,-108.4 780,-114.4 774,-114.4 774,-114.4 20,-114.4 20,-114.4 14,-114.4 8,-108.4 8,-102.4 8,-102.4 8,-20 8,-20 8,-14 14,-8 20,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="397" y="-95.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">common (Shared Models)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_payments</title>
|
||||||
|
<path fill="#fce4ec" stroke="#ad1457" d="M1173,-496.4C1173,-496.4 1395,-496.4 1395,-496.4 1401,-496.4 1407,-502.4 1407,-508.4 1407,-508.4 1407,-590.8 1407,-590.8 1407,-596.8 1401,-602.8 1395,-602.8 1395,-602.8 1173,-602.8 1173,-602.8 1167,-602.8 1161,-596.8 1161,-590.8 1161,-590.8 1161,-508.4 1161,-508.4 1161,-502.4 1167,-496.4 1173,-496.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1284" y="-583.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">payments (Payment Processing)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust7" class="cluster">
|
||||||
|
<title>cluster_external</title>
|
||||||
|
<path fill="#fff3e0" stroke="#e65100" d="M1039,-8C1039,-8 1531,-8 1531,-8 1537,-8 1543,-14 1543,-20 1543,-20 1543,-102.4 1543,-102.4 1543,-108.4 1537,-114.4 1531,-114.4 1531,-114.4 1039,-114.4 1039,-114.4 1033,-114.4 1027,-108.4 1027,-102.4 1027,-102.4 1027,-20 1027,-20 1027,-14 1033,-8 1039,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1285" y="-95.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">External Integrations</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust8" class="cluster">
|
||||||
|
<title>cluster_afip</title>
|
||||||
|
<path fill="#fffde7" stroke="#f9a825" d="M806,-8C806,-8 1007,-8 1007,-8 1013,-8 1019,-14 1019,-20 1019,-20 1019,-237.6 1019,-237.6 1019,-243.6 1013,-249.6 1007,-249.6 1007,-249.6 806,-249.6 806,-249.6 800,-249.6 794,-243.6 794,-237.6 794,-237.6 794,-20 794,-20 794,-14 800,-8 806,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="906.5" y="-230.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">django_afip (Invoicing)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>auth_user</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M440.93,-675.6C440.93,-675.6 381.07,-675.6 381.07,-675.6 375.07,-675.6 369.07,-669.6 369.07,-663.6 369.07,-663.6 369.07,-651.6 369.07,-651.6 369.07,-645.6 375.07,-639.6 381.07,-639.6 381.07,-639.6 440.93,-639.6 440.93,-639.6 446.93,-639.6 452.93,-645.6 452.93,-651.6 452.93,-651.6 452.93,-663.6 452.93,-663.6 452.93,-669.6 446.93,-675.6 440.93,-675.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="411" y="-660.9" font-family="Helvetica,sans-Serif" font-size="11.00">auth.User</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="411" y="-647.7" font-family="Helvetica,sans-Serif" font-size="11.00">(Django Auth)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>petowner</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M725.15,-540.4C725.15,-540.4 662.85,-540.4 662.85,-540.4 656.85,-540.4 650.85,-534.4 650.85,-528.4 650.85,-528.4 650.85,-516.4 650.85,-516.4 650.85,-510.4 656.85,-504.4 662.85,-504.4 662.85,-504.4 725.15,-504.4 725.15,-504.4 731.15,-504.4 737.15,-510.4 737.15,-516.4 737.15,-516.4 737.15,-528.4 737.15,-528.4 737.15,-534.4 731.15,-540.4 725.15,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="694" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">PetOwner</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="694" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Cliente/Tutor)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->petowner -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>auth_user->petowner</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M424.94,-639.31C424.94,-602.3 424.94,-522 424.94,-522 424.94,-522 638.96,-522 638.96,-522"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="638.96,-525.5 648.96,-522 638.96,-518.5 638.96,-525.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="645.27" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">1:1 optional</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>veterinarian</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M544.18,-405.2C544.18,-405.2 489.82,-405.2 489.82,-405.2 483.82,-405.2 477.82,-399.2 477.82,-393.2 477.82,-393.2 477.82,-381.2 477.82,-381.2 477.82,-375.2 483.82,-369.2 489.82,-369.2 489.82,-369.2 544.18,-369.2 544.18,-369.2 550.18,-369.2 556.18,-375.2 556.18,-381.2 556.18,-381.2 556.18,-393.2 556.18,-393.2 556.18,-399.2 550.18,-405.2 544.18,-405.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="517" y="-390.5" font-family="Helvetica,sans-Serif" font-size="11.00">Veterinarian</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="517" y="-377.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Profesional)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->veterinarian -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>auth_user->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M453.39,-664C477.9,-664 503.94,-664 503.94,-664 503.94,-664 503.94,-416.88 503.94,-416.88"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="507.44,-416.88 503.94,-406.88 500.44,-416.88 507.44,-416.88"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="470.26" y="-519.7" font-family="Helvetica,sans-Serif" font-size="9.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- django_admin -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>django_admin</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M339.55,-675.6C339.55,-675.6 278.45,-675.6 278.45,-675.6 272.45,-675.6 266.45,-669.6 266.45,-663.6 266.45,-663.6 266.45,-651.6 266.45,-651.6 266.45,-645.6 272.45,-639.6 278.45,-639.6 278.45,-639.6 339.55,-639.6 339.55,-639.6 345.55,-639.6 351.55,-645.6 351.55,-651.6 351.55,-651.6 351.55,-663.6 351.55,-663.6 351.55,-669.6 345.55,-675.6 339.55,-675.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="309" y="-660.9" font-family="Helvetica,sans-Serif" font-size="11.00">Django Admin</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="309" y="-647.7" font-family="Helvetica,sans-Serif" font-size="11.00">Interface</text>
|
||||||
|
</g>
|
||||||
|
<!-- drf -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>drf</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M236.63,-675.6C236.63,-675.6 177.37,-675.6 177.37,-675.6 171.37,-675.6 165.37,-669.6 165.37,-663.6 165.37,-663.6 165.37,-651.6 165.37,-651.6 165.37,-645.6 171.37,-639.6 177.37,-639.6 177.37,-639.6 236.63,-639.6 236.63,-639.6 242.63,-639.6 248.63,-645.6 248.63,-651.6 248.63,-651.6 248.63,-663.6 248.63,-663.6 248.63,-669.6 242.63,-675.6 236.63,-675.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="207" y="-660.9" font-family="Helvetica,sans-Serif" font-size="11.00">Django REST</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="207" y="-647.7" font-family="Helvetica,sans-Serif" font-size="11.00">Framework</text>
|
||||||
|
</g>
|
||||||
|
<!-- jwt_auth -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>jwt_auth</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M264.69,-540.4C264.69,-540.4 177.31,-540.4 177.31,-540.4 171.31,-540.4 165.31,-534.4 165.31,-528.4 165.31,-528.4 165.31,-516.4 165.31,-516.4 165.31,-510.4 171.31,-504.4 177.31,-504.4 177.31,-504.4 264.69,-504.4 264.69,-504.4 270.69,-504.4 276.69,-510.4 276.69,-516.4 276.69,-516.4 276.69,-528.4 276.69,-528.4 276.69,-534.4 270.69,-540.4 264.69,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="221" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">JWT Authentication</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="221" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(SimpleJWT)</text>
|
||||||
|
</g>
|
||||||
|
<!-- drf->jwt_auth -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>drf->jwt_auth</title>
|
||||||
|
<path fill="none" stroke="#999999" stroke-dasharray="5,2" d="M207,-639.37C207,-639.37 207,-552.29 207,-552.29"/>
|
||||||
|
<polygon fill="#999999" stroke="#999999" points="210.5,-552.29 207,-542.29 203.5,-552.29 210.5,-552.29"/>
|
||||||
|
</g>
|
||||||
|
<!-- pet -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>pet</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M714.45,-405.2C714.45,-405.2 673.55,-405.2 673.55,-405.2 667.55,-405.2 661.55,-399.2 661.55,-393.2 661.55,-393.2 661.55,-381.2 661.55,-381.2 661.55,-375.2 667.55,-369.2 673.55,-369.2 673.55,-369.2 714.45,-369.2 714.45,-369.2 720.45,-369.2 726.45,-375.2 726.45,-381.2 726.45,-381.2 726.45,-393.2 726.45,-393.2 726.45,-399.2 720.45,-405.2 714.45,-405.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="694" y="-390.5" font-family="Helvetica,sans-Serif" font-size="11.00">Pet</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="694" y="-377.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Mascota)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->pet -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>petowner->pet</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M694,-504.17C694,-504.17 694,-417.09 694,-417.09"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="697.5,-417.09 694,-407.09 690.5,-417.09 697.5,-417.09"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="701" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest -->
|
||||||
|
<g id="node18" class="node">
|
||||||
|
<title>servicerequest</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1163.82,-405.2C1163.82,-405.2 1094.18,-405.2 1094.18,-405.2 1088.18,-405.2 1082.18,-399.2 1082.18,-393.2 1082.18,-393.2 1082.18,-381.2 1082.18,-381.2 1082.18,-375.2 1088.18,-369.2 1094.18,-369.2 1094.18,-369.2 1163.82,-369.2 1163.82,-369.2 1169.82,-369.2 1175.82,-375.2 1175.82,-381.2 1175.82,-381.2 1175.82,-393.2 1175.82,-393.2 1175.82,-399.2 1169.82,-405.2 1163.82,-405.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1129" y="-390.5" font-family="Helvetica,sans-Serif" font-size="11.00">ServiceRequest</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1129" y="-377.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Solicitud)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->servicerequest -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>petowner->servicerequest</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M729.12,-504.02C729.12,-468.96 729.12,-396 729.12,-396 729.12,-396 1070.44,-396 1070.44,-396"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="1070.44,-399.5 1080.44,-396 1070.44,-392.5 1070.44,-399.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1005" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- neighborhood -->
|
||||||
|
<g id="node27" class="node">
|
||||||
|
<title>neighborhood</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M109.63,-52C109.63,-52 28.37,-52 28.37,-52 22.37,-52 16.37,-46 16.37,-40 16.37,-40 16.37,-28 16.37,-28 16.37,-22 22.37,-16 28.37,-16 28.37,-16 109.63,-16 109.63,-16 115.63,-16 121.63,-22 121.63,-28 121.63,-28 121.63,-40 121.63,-40 121.63,-46 115.63,-52 109.63,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="69" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Neighborhood /</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="69" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">Province / Locality</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->neighborhood -->
|
||||||
|
<g id="edge24" class="edge">
|
||||||
|
<title>petowner->neighborhood</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M655.77,-504.11C655.77,-441.12 655.77,-237 655.77,-237 655.77,-237 86.54,-237 86.54,-237 86.54,-237 86.54,-63.89 86.54,-63.89"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="90.04,-63.89 86.54,-53.89 83.04,-63.89 90.04,-63.89"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="86" y="-301.7" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>vetvisit</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M725.37,-322.4C725.37,-322.4 682.63,-322.4 682.63,-322.4 676.63,-322.4 670.63,-316.4 670.63,-310.4 670.63,-310.4 670.63,-298.4 670.63,-298.4 670.63,-292.4 676.63,-286.4 682.63,-286.4 682.63,-286.4 725.37,-286.4 725.37,-286.4 731.37,-286.4 737.37,-292.4 737.37,-298.4 737.37,-298.4 737.37,-310.4 737.37,-310.4 737.37,-316.4 731.37,-322.4 725.37,-322.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="704" y="-307.7" font-family="Helvetica,sans-Serif" font-size="11.00">VetVisit</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="704" y="-294.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Consulta)</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->vetvisit -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>pet->vetvisit</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M698.54,-368.82C698.54,-368.82 698.54,-334.24 698.54,-334.24"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="702.04,-334.24 698.54,-324.24 695.04,-334.24 702.04,-334.24"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="710.25" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet_health -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>pet_health</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M640.88,-322.4C640.88,-322.4 587.12,-322.4 587.12,-322.4 581.12,-322.4 575.12,-316.4 575.12,-310.4 575.12,-310.4 575.12,-298.4 575.12,-298.4 575.12,-292.4 581.12,-286.4 587.12,-286.4 587.12,-286.4 640.88,-286.4 640.88,-286.4 646.88,-286.4 652.88,-292.4 652.88,-298.4 652.88,-298.4 652.88,-310.4 652.88,-310.4 652.88,-316.4 646.88,-322.4 640.88,-322.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="614" y="-307.7" font-family="Helvetica,sans-Serif" font-size="11.00">PetVaccine /</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="614" y="-294.5" font-family="Helvetica,sans-Serif" font-size="11.00">PetStudy</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->pet_health -->
|
||||||
|
<g id="edge7" class="edge">
|
||||||
|
<title>pet->pet_health</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M666.09,-368.72C666.09,-342.59 666.09,-298 666.09,-298 666.09,-298 664.73,-298 664.73,-298"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="664.73,-294.5 654.73,-298 664.73,-301.5 664.73,-294.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="623" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vetvisit -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>veterinarian->vetvisit</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M556.46,-387C598.38,-387 658.66,-387 658.66,-387 658.66,-387 658.66,-310 658.66,-310 658.66,-310 659.85,-310 659.85,-310"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="659.06,-313.5 669.06,-310 659.06,-306.5 659.06,-313.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="668" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- availability -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>availability</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M545.4,-322.4C545.4,-322.4 488.6,-322.4 488.6,-322.4 482.6,-322.4 476.6,-316.4 476.6,-310.4 476.6,-310.4 476.6,-298.4 476.6,-298.4 476.6,-292.4 482.6,-286.4 488.6,-286.4 488.6,-286.4 545.4,-286.4 545.4,-286.4 551.4,-286.4 557.4,-292.4 557.4,-298.4 557.4,-298.4 557.4,-310.4 557.4,-310.4 557.4,-316.4 551.4,-322.4 545.4,-322.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="517" y="-307.7" font-family="Helvetica,sans-Serif" font-size="11.00">Availability /</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="517" y="-294.5" font-family="Helvetica,sans-Serif" font-size="11.00">Unavailability</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->availability -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>veterinarian->availability</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M503.94,-368.82C503.94,-368.82 503.94,-334.24 503.94,-334.24"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="507.44,-334.24 503.94,-324.24 500.44,-334.24 507.44,-334.24"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="524" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked -->
|
||||||
|
<g id="node20" class="node">
|
||||||
|
<title>vetasked</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1173.94,-322.4C1173.94,-322.4 1092.06,-322.4 1092.06,-322.4 1086.06,-322.4 1080.06,-316.4 1080.06,-310.4 1080.06,-310.4 1080.06,-298.4 1080.06,-298.4 1080.06,-292.4 1086.06,-286.4 1092.06,-286.4 1092.06,-286.4 1173.94,-286.4 1173.94,-286.4 1179.94,-286.4 1185.94,-292.4 1185.94,-298.4 1185.94,-298.4 1185.94,-310.4 1185.94,-310.4 1185.94,-316.4 1179.94,-322.4 1173.94,-322.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1133" y="-307.7" font-family="Helvetica,sans-Serif" font-size="11.00">VeterinarianAsked</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1133" y="-294.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Vet Consultado)</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vetasked -->
|
||||||
|
<g id="edge23" class="edge">
|
||||||
|
<title>veterinarian->vetasked</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M530.06,-368.88C530.06,-354.89 530.06,-338 530.06,-338 530.06,-338 1129,-338 1129,-338 1129,-338 1129,-334.29 1129,-334.29"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1132.5,-334.29 1129,-324.29 1125.5,-334.29 1132.5,-334.29"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="959" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- specialty -->
|
||||||
|
<g id="node24" class="node">
|
||||||
|
<title>specialty</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M224.66,-52C224.66,-52 151.34,-52 151.34,-52 145.34,-52 139.34,-46 139.34,-40 139.34,-40 139.34,-28 139.34,-28 139.34,-22 145.34,-16 151.34,-16 151.34,-16 224.66,-16 224.66,-16 230.66,-16 236.66,-22 236.66,-28 236.66,-28 236.66,-40 236.66,-40 236.66,-46 230.66,-52 224.66,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="188" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Specialty</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="188" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Especialidades)</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->specialty -->
|
||||||
|
<g id="edge25" class="edge">
|
||||||
|
<title>veterinarian->specialty</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M477.62,-387C392.9,-387 200.99,-387 200.99,-387 200.99,-387 200.99,-64 200.99,-64"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="204.49,-64 200.99,-54 197.49,-64 204.49,-64"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="289.25" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->neighborhood -->
|
||||||
|
<g id="edge26" class="edge">
|
||||||
|
<title>veterinarian->neighborhood</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M477.71,-396C365.47,-396 51.46,-396 51.46,-396 51.46,-396 51.46,-63.96 51.46,-63.96"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="54.96,-63.96 51.46,-53.96 47.96,-63.96 54.96,-63.96"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="158.01" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:M coverage</text>
|
||||||
|
</g>
|
||||||
|
<!-- turnfee -->
|
||||||
|
<g id="node28" class="node">
|
||||||
|
<title>turnfee</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M347.02,-52C347.02,-52 266.98,-52 266.98,-52 260.98,-52 254.98,-46 254.98,-40 254.98,-40 254.98,-28 254.98,-28 254.98,-22 260.98,-16 266.98,-16 266.98,-16 347.02,-16 347.02,-16 353.02,-16 359.02,-22 359.02,-28 359.02,-28 359.02,-40 359.02,-40 359.02,-46 353.02,-52 347.02,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="307" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">IndividualTurnFee</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="307" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">Group</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->turnfee -->
|
||||||
|
<g id="edge30" class="edge">
|
||||||
|
<title>veterinarian->turnfee</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M477.44,-378C429.56,-378 355.29,-378 355.29,-378 355.29,-378 355.29,-64.01 355.29,-64.01"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="358.79,-64.01 355.29,-54.01 351.79,-64.01 358.79,-64.01"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="364.25" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>vetvisitreport</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M694.34,-187.2C694.34,-187.2 621.66,-187.2 621.66,-187.2 615.66,-187.2 609.66,-181.2 609.66,-175.2 609.66,-175.2 609.66,-163.2 609.66,-163.2 609.66,-157.2 615.66,-151.2 621.66,-151.2 621.66,-151.2 694.34,-151.2 694.34,-151.2 700.34,-151.2 706.34,-157.2 706.34,-163.2 706.34,-163.2 706.34,-175.2 706.34,-175.2 706.34,-181.2 700.34,-187.2 694.34,-187.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="658" y="-172.5" font-family="Helvetica,sans-Serif" font-size="11.00">VetVisitReport</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="658" y="-159.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Informe Clinico)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->vetvisitreport -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>vetvisit->vetvisitreport</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M688.49,-286.17C688.49,-286.17 688.49,-199.09 688.49,-199.09"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="691.99,-199.09 688.49,-189.09 684.99,-199.09 691.99,-199.09"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="671" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->servicerequest -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>vetvisit->servicerequest</title>
|
||||||
|
<path fill="none" stroke="#1565c0" stroke-dasharray="5,2" d="M731.8,-322.73C731.8,-347.07 731.8,-387 731.8,-387 731.8,-387 1070.36,-387 1070.36,-387"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="1070.36,-390.5 1080.36,-387 1070.36,-383.5 1070.36,-390.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="749.26" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- google_cal -->
|
||||||
|
<g id="node31" class="node">
|
||||||
|
<title>google_cal</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1122.58,-52C1122.58,-52 1047.42,-52 1047.42,-52 1041.42,-52 1035.42,-46 1035.42,-40 1035.42,-40 1035.42,-28 1035.42,-28 1035.42,-22 1041.42,-16 1047.42,-16 1047.42,-16 1122.58,-16 1122.58,-16 1128.58,-16 1134.58,-22 1134.58,-28 1134.58,-28 1134.58,-40 1134.58,-40 1134.58,-46 1128.58,-52 1122.58,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1085" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Google Calendar</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1085" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Agenda)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->google_cal -->
|
||||||
|
<g id="edge33" class="edge">
|
||||||
|
<title>vetvisit->google_cal</title>
|
||||||
|
<path fill="none" stroke="#e65100" stroke-dasharray="5,2" d="M737.59,-304C824.11,-304 1047.82,-304 1047.82,-304 1047.82,-304 1047.82,-63.65 1047.82,-63.65"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1051.32,-63.65 1047.82,-53.65 1044.32,-63.65 1051.32,-63.65"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1054.25" y="-166.5" font-family="Helvetica,sans-Serif" font-size="9.00">sync</text>
|
||||||
|
</g>
|
||||||
|
<!-- receipt -->
|
||||||
|
<g id="node36" class="node">
|
||||||
|
<title>receipt</title>
|
||||||
|
<path fill="#fff9c4" stroke="black" d="M880.99,-187.2C880.99,-187.2 815.01,-187.2 815.01,-187.2 809.01,-187.2 803.01,-181.2 803.01,-175.2 803.01,-175.2 803.01,-163.2 803.01,-163.2 803.01,-157.2 809.01,-151.2 815.01,-151.2 815.01,-151.2 880.99,-151.2 880.99,-151.2 886.99,-151.2 892.99,-157.2 892.99,-163.2 892.99,-163.2 892.99,-175.2 892.99,-175.2 892.99,-181.2 886.99,-187.2 880.99,-187.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="848" y="-172.5" font-family="Helvetica,sans-Serif" font-size="11.00">Receipt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="848" y="-159.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Comprobante)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->receipt -->
|
||||||
|
<g id="edge36" class="edge">
|
||||||
|
<title>vetvisit->receipt</title>
|
||||||
|
<path fill="none" stroke="#f9a825" stroke-dasharray="5,2" d="M722.89,-286.14C722.89,-249.18 722.89,-169 722.89,-169 722.89,-169 791.32,-169 791.32,-169"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="791.32,-172.5 801.32,-169 791.32,-165.5 791.32,-172.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="727.27" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:1 optional</text>
|
||||||
|
</g>
|
||||||
|
<!-- medication -->
|
||||||
|
<g id="node25" class="node">
|
||||||
|
<title>medication</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M678.43,-52C678.43,-52 607.57,-52 607.57,-52 601.57,-52 595.57,-46 595.57,-40 595.57,-40 595.57,-28 595.57,-28 595.57,-22 601.57,-16 607.57,-16 607.57,-16 678.43,-16 678.43,-16 684.43,-16 690.43,-22 690.43,-28 690.43,-28 690.43,-40 690.43,-40 690.43,-46 684.43,-52 678.43,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="643" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Medication</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="643" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Medicamentos)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport->medication -->
|
||||||
|
<g id="edge29" class="edge">
|
||||||
|
<title>vetvisitreport->medication</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M650.04,-150.97C650.04,-150.97 650.04,-63.89 650.04,-63.89"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="653.54,-63.89 650.04,-53.89 646.54,-63.89 653.54,-63.89"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="667.26" y="-125.1" font-family="Helvetica,sans-Serif" font-size="9.00">references</text>
|
||||||
|
</g>
|
||||||
|
<!-- service -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>service</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M972.22,-675.6C972.22,-675.6 933.78,-675.6 933.78,-675.6 927.78,-675.6 921.78,-669.6 921.78,-663.6 921.78,-663.6 921.78,-651.6 921.78,-651.6 921.78,-645.6 927.78,-639.6 933.78,-639.6 933.78,-639.6 972.22,-639.6 972.22,-639.6 978.22,-639.6 984.22,-645.6 984.22,-651.6 984.22,-651.6 984.22,-663.6 984.22,-663.6 984.22,-669.6 978.22,-675.6 972.22,-675.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="953" y="-660.9" font-family="Helvetica,sans-Serif" font-size="11.00">Service</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="953" y="-647.7" font-family="Helvetica,sans-Serif" font-size="11.00">(Servicio)</text>
|
||||||
|
</g>
|
||||||
|
<!-- category -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>category</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M849.19,-540.4C849.19,-540.4 772.81,-540.4 772.81,-540.4 766.81,-540.4 760.81,-534.4 760.81,-528.4 760.81,-528.4 760.81,-516.4 760.81,-516.4 760.81,-510.4 766.81,-504.4 772.81,-504.4 772.81,-504.4 849.19,-504.4 849.19,-504.4 855.19,-504.4 861.19,-510.4 861.19,-516.4 861.19,-516.4 861.19,-528.4 861.19,-528.4 861.19,-534.4 855.19,-540.4 849.19,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="811" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">Category / Group</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="811" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Categorias)</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->category -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>service->category</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M921.49,-652C879.65,-652 811,-652 811,-652 811,-652 811,-552.21 811,-552.21"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="814.5,-552.21 811,-542.21 807.5,-552.21 814.5,-552.21"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="835" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- prices -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>prices</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M927,-540.4C927,-540.4 891,-540.4 891,-540.4 885,-540.4 879,-534.4 879,-528.4 879,-528.4 879,-516.4 879,-516.4 879,-510.4 885,-504.4 891,-504.4 891,-504.4 927,-504.4 927,-504.4 933,-504.4 939,-510.4 939,-516.4 939,-516.4 939,-528.4 939,-528.4 939,-534.4 933,-540.4 927,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="909" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">Prices</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="909" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Precios)</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->prices -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>service->prices</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M930.39,-639.37C930.39,-639.37 930.39,-552.29 930.39,-552.29"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="933.89,-552.29 930.39,-542.29 926.89,-552.29 933.89,-552.29"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="929" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- discounts -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>discounts</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M1026.71,-540.4C1026.71,-540.4 969.29,-540.4 969.29,-540.4 963.29,-540.4 957.29,-534.4 957.29,-528.4 957.29,-528.4 957.29,-516.4 957.29,-516.4 957.29,-510.4 963.29,-504.4 969.29,-504.4 969.29,-504.4 1026.71,-504.4 1026.71,-504.4 1032.71,-504.4 1038.71,-510.4 1038.71,-516.4 1038.71,-516.4 1038.71,-528.4 1038.71,-528.4 1038.71,-534.4 1032.71,-540.4 1026.71,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="998" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">Discounts</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="998" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Descuentos)</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->discounts -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>service->discounts</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M970.76,-639.37C970.76,-639.37 970.76,-552.29 970.76,-552.29"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="974.26,-552.29 970.76,-542.29 967.26,-552.29 974.26,-552.29"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="992" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart -->
|
||||||
|
<g id="node16" class="node">
|
||||||
|
<title>cart</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M1133.06,-540.4C1133.06,-540.4 1068.94,-540.4 1068.94,-540.4 1062.94,-540.4 1056.94,-534.4 1056.94,-528.4 1056.94,-528.4 1056.94,-516.4 1056.94,-516.4 1056.94,-510.4 1062.94,-504.4 1068.94,-504.4 1068.94,-504.4 1133.06,-504.4 1133.06,-504.4 1139.06,-504.4 1145.06,-510.4 1145.06,-516.4 1145.06,-516.4 1145.06,-528.4 1145.06,-528.4 1145.06,-534.4 1139.06,-540.4 1133.06,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1101" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">Cart / CartItem</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1101" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Carrito)</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->cart -->
|
||||||
|
<g id="edge17" class="edge">
|
||||||
|
<title>service->cart</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M984.39,-658C1027.83,-658 1101,-658 1101,-658 1101,-658 1101,-552.2 1101,-552.2"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="1104.5,-552.2 1101,-542.2 1097.5,-552.2 1104.5,-552.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1089.26" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">via CartItem</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->petowner -->
|
||||||
|
<g id="edge15" class="edge">
|
||||||
|
<title>cart->petowner</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M1064.64,-504.13C1064.64,-489.36 1064.64,-471 1064.64,-471 1064.64,-471 734.47,-471 734.47,-471 734.47,-471 734.47,-492.62 734.47,-492.62"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="730.97,-492.62 734.47,-502.62 737.97,-492.62 730.97,-492.62"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="871" y="-613.5" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->veterinarian -->
|
||||||
|
<g id="edge16" class="edge">
|
||||||
|
<title>cart->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" stroke-dasharray="5,2" d="M1072.35,-504.09C1072.35,-479.26 1072.35,-438 1072.35,-438 1072.35,-438 530.06,-438 530.06,-438 530.06,-438 530.06,-417.05 530.06,-417.05"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="533.56,-417.05 530.06,-407.05 526.56,-417.05 533.56,-417.05"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="886.02" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:1 optional</text>
|
||||||
|
</g>
|
||||||
|
<!-- combo -->
|
||||||
|
<g id="node17" class="node">
|
||||||
|
<title>combo</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M985.07,-810.8C985.07,-810.8 920.93,-810.8 920.93,-810.8 914.93,-810.8 908.93,-804.8 908.93,-798.8 908.93,-798.8 908.93,-786.8 908.93,-786.8 908.93,-780.8 914.93,-774.8 920.93,-774.8 920.93,-774.8 985.07,-774.8 985.07,-774.8 991.07,-774.8 997.07,-780.8 997.07,-786.8 997.07,-786.8 997.07,-798.8 997.07,-798.8 997.07,-804.8 991.07,-810.8 985.07,-810.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="953" y="-796.1" font-family="Helvetica,sans-Serif" font-size="11.00">ServiceCombo</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="953" y="-782.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Paquetes)</text>
|
||||||
|
</g>
|
||||||
|
<!-- combo->service -->
|
||||||
|
<g id="edge18" class="edge">
|
||||||
|
<title>combo->service</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M953,-774.57C953,-774.57 953,-687.49 953,-687.49"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="956.5,-687.49 953,-677.49 949.5,-687.49 956.5,-687.49"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="969.76" y="-748.7" font-family="Helvetica,sans-Serif" font-size="9.00">contains</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->cart -->
|
||||||
|
<g id="edge19" class="edge">
|
||||||
|
<title>servicerequest->cart</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1113.62,-405.43C1113.62,-405.43 1113.62,-492.51 1113.62,-492.51"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1110.12,-492.51 1113.62,-502.51 1117.12,-492.51 1110.12,-492.51"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1121.26" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- statehistory -->
|
||||||
|
<g id="node19" class="node">
|
||||||
|
<title>statehistory</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1267.95,-322.4C1267.95,-322.4 1216.05,-322.4 1216.05,-322.4 1210.05,-322.4 1204.05,-316.4 1204.05,-310.4 1204.05,-310.4 1204.05,-298.4 1204.05,-298.4 1204.05,-292.4 1210.05,-286.4 1216.05,-286.4 1216.05,-286.4 1267.95,-286.4 1267.95,-286.4 1273.95,-286.4 1279.95,-292.4 1279.95,-298.4 1279.95,-298.4 1279.95,-310.4 1279.95,-310.4 1279.95,-316.4 1273.95,-322.4 1267.95,-322.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1242" y="-307.7" font-family="Helvetica,sans-Serif" font-size="11.00">StateHistory</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1242" y="-294.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Historial)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->statehistory -->
|
||||||
|
<g id="edge20" class="edge">
|
||||||
|
<title>servicerequest->statehistory</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1176.2,-378C1201.95,-378 1228.64,-378 1228.64,-378 1228.64,-378 1228.64,-334.11 1228.64,-334.11"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1232.14,-334.11 1228.64,-324.11 1225.14,-334.11 1232.14,-334.11"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1212" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->vetasked -->
|
||||||
|
<g id="edge21" class="edge">
|
||||||
|
<title>servicerequest->vetasked</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1152.41,-368.82C1152.41,-368.82 1152.41,-334.24 1152.41,-334.24"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1155.91,-334.24 1152.41,-324.24 1148.91,-334.24 1155.91,-334.24"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1137" y="-343.1" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- campaign -->
|
||||||
|
<g id="node22" class="node">
|
||||||
|
<title>campaign</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M436.81,-52C436.81,-52 389.19,-52 389.19,-52 383.19,-52 377.19,-46 377.19,-40 377.19,-40 377.19,-28 377.19,-28 377.19,-22 383.19,-16 389.19,-16 389.19,-16 436.81,-16 436.81,-16 442.81,-16 448.81,-22 448.81,-28 448.81,-28 448.81,-40 448.81,-40 448.81,-46 442.81,-52 436.81,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="413" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Campaign</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="413" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Marketing)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->campaign -->
|
||||||
|
<g id="edge27" class="edge">
|
||||||
|
<title>servicerequest->campaign</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M1105.59,-369.05C1105.59,-361.29 1105.59,-354 1105.59,-354 1105.59,-354 401.06,-354 401.06,-354 401.06,-354 401.06,-63.63 401.06,-63.63"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="404.56,-63.63 401.06,-53.63 397.56,-63.64 404.56,-63.63"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="437.02" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:1 optional</text>
|
||||||
|
</g>
|
||||||
|
<!-- tag -->
|
||||||
|
<g id="node23" class="node">
|
||||||
|
<title>tag</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M765.59,-52C765.59,-52 720.41,-52 720.41,-52 714.41,-52 708.41,-46 708.41,-40 708.41,-40 708.41,-28 708.41,-28 708.41,-22 714.41,-16 720.41,-16 720.41,-16 765.59,-16 765.59,-16 771.59,-16 777.59,-22 777.59,-28 777.59,-28 777.59,-40 777.59,-40 777.59,-46 771.59,-52 765.59,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="743" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Tag</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="743" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Etiquetas)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->tag -->
|
||||||
|
<g id="edge28" class="edge">
|
||||||
|
<title>servicerequest->tag</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M1081.96,-378C984.44,-378 769.2,-378 769.2,-378 769.2,-378 769.2,-64.01 769.2,-64.01"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="772.7,-64.01 769.2,-54.01 765.7,-64.01 772.7,-64.01"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="783.25" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- mercadopago -->
|
||||||
|
<g id="node29" class="node">
|
||||||
|
<title>mercadopago</title>
|
||||||
|
<path fill="#f8bbd9" stroke="black" d="M1241.24,-540.4C1241.24,-540.4 1180.76,-540.4 1180.76,-540.4 1174.76,-540.4 1168.76,-534.4 1168.76,-528.4 1168.76,-528.4 1168.76,-516.4 1168.76,-516.4 1168.76,-510.4 1174.76,-504.4 1180.76,-504.4 1180.76,-504.4 1241.24,-504.4 1241.24,-504.4 1247.24,-504.4 1253.24,-510.4 1253.24,-516.4 1253.24,-516.4 1253.24,-528.4 1253.24,-528.4 1253.24,-534.4 1247.24,-540.4 1241.24,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1211" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">MercadoPago</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1211" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">Account</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->mercadopago -->
|
||||||
|
<g id="edge31" class="edge">
|
||||||
|
<title>servicerequest->mercadopago</title>
|
||||||
|
<path fill="none" stroke="#ad1457" d="M1172.29,-405.43C1172.29,-405.43 1172.29,-492.51 1172.29,-492.51"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="1168.79,-492.51 1172.29,-502.51 1175.79,-492.51 1168.79,-492.51"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1205.26" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">payment</text>
|
||||||
|
</g>
|
||||||
|
<!-- mercately -->
|
||||||
|
<g id="node33" class="node">
|
||||||
|
<title>mercately</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1523.04,-52C1523.04,-52 1472.96,-52 1472.96,-52 1466.96,-52 1460.96,-46 1460.96,-40 1460.96,-40 1460.96,-28 1460.96,-28 1460.96,-22 1466.96,-16 1472.96,-16 1472.96,-16 1523.04,-16 1523.04,-16 1529.04,-16 1535.04,-22 1535.04,-28 1535.04,-28 1535.04,-40 1535.04,-40 1535.04,-46 1529.04,-52 1523.04,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1498" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Mercately</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1498" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(WhatsApp)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->mercately -->
|
||||||
|
<g id="edge34" class="edge">
|
||||||
|
<title>servicerequest->mercately</title>
|
||||||
|
<path fill="none" stroke="#e65100" stroke-dasharray="5,2" d="M1176.11,-387C1275.59,-387 1498,-387 1498,-387 1498,-387 1498,-64 1498,-64"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1501.5,-64 1498,-54 1494.5,-64 1501.5,-64"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1350.76" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">notify</text>
|
||||||
|
</g>
|
||||||
|
<!-- reminders -->
|
||||||
|
<g id="node21" class="node">
|
||||||
|
<title>reminders</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1249.9,-187.2C1249.9,-187.2 1182.1,-187.2 1182.1,-187.2 1176.1,-187.2 1170.1,-181.2 1170.1,-175.2 1170.1,-175.2 1170.1,-163.2 1170.1,-163.2 1170.1,-157.2 1176.1,-151.2 1182.1,-151.2 1182.1,-151.2 1249.9,-151.2 1249.9,-151.2 1255.9,-151.2 1261.9,-157.2 1261.9,-163.2 1261.9,-163.2 1261.9,-175.2 1261.9,-175.2 1261.9,-181.2 1255.9,-187.2 1249.9,-187.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1216" y="-172.5" font-family="Helvetica,sans-Serif" font-size="11.00">Reminders</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1216" y="-159.3" font-family="Helvetica,sans-Serif" font-size="11.00">(Recordatorios)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked->reminders -->
|
||||||
|
<g id="edge22" class="edge">
|
||||||
|
<title>vetasked->reminders</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1178.02,-286.17C1178.02,-286.17 1178.02,-199.09 1178.02,-199.09"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1181.52,-199.09 1178.02,-189.09 1174.52,-199.09 1181.52,-199.09"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1181" y="-260.3" font-family="Helvetica,sans-Serif" font-size="9.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- celery -->
|
||||||
|
<g id="node35" class="node">
|
||||||
|
<title>celery</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1332.84,-52C1332.84,-52 1271.16,-52 1271.16,-52 1265.16,-52 1259.16,-46 1259.16,-40 1259.16,-40 1259.16,-28 1259.16,-28 1259.16,-22 1265.16,-16 1271.16,-16 1271.16,-16 1332.84,-16 1332.84,-16 1338.84,-16 1344.84,-22 1344.84,-28 1344.84,-28 1344.84,-40 1344.84,-40 1344.84,-46 1338.84,-52 1332.84,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1302" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Celery</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1302" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Async Tasks)</text>
|
||||||
|
</g>
|
||||||
|
<!-- reminders->celery -->
|
||||||
|
<g id="edge35" class="edge">
|
||||||
|
<title>reminders->celery</title>
|
||||||
|
<path fill="none" stroke="#e65100" stroke-dasharray="5,2" d="M1250.27,-150.97C1250.27,-114.06 1250.27,-34 1250.27,-34 1250.27,-34 1251.13,-34 1251.13,-34"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1247.3,-37.5 1257.3,-34 1247.3,-30.5 1247.3,-37.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1270.52" y="-125.1" font-family="Helvetica,sans-Serif" font-size="9.00">scheduled</text>
|
||||||
|
</g>
|
||||||
|
<!-- breed -->
|
||||||
|
<g id="node26" class="node">
|
||||||
|
<title>breed</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M565.08,-52C565.08,-52 478.92,-52 478.92,-52 472.92,-52 466.92,-46 466.92,-40 466.92,-40 466.92,-28 466.92,-28 466.92,-22 472.92,-16 478.92,-16 478.92,-16 565.08,-16 565.08,-16 571.08,-16 577.08,-22 577.08,-28 577.08,-28 577.08,-40 577.08,-40 577.08,-46 571.08,-52 565.08,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="522" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">PetBreed / Vaccine</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="522" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">/ Study</text>
|
||||||
|
</g>
|
||||||
|
<!-- mpnotification -->
|
||||||
|
<g id="node30" class="node">
|
||||||
|
<title>mpnotification</title>
|
||||||
|
<path fill="#f8bbd9" stroke="black" d="M1348.99,-540.4C1348.99,-540.4 1283.01,-540.4 1283.01,-540.4 1277.01,-540.4 1271.01,-534.4 1271.01,-528.4 1271.01,-528.4 1271.01,-516.4 1271.01,-516.4 1271.01,-510.4 1277.01,-504.4 1283.01,-504.4 1283.01,-504.4 1348.99,-504.4 1348.99,-504.4 1354.99,-504.4 1360.99,-510.4 1360.99,-516.4 1360.99,-516.4 1360.99,-528.4 1360.99,-528.4 1360.99,-534.4 1354.99,-540.4 1348.99,-540.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1316" y="-525.7" font-family="Helvetica,sans-Serif" font-size="11.00">MP Notification</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1316" y="-512.5" font-family="Helvetica,sans-Serif" font-size="11.00">(Webhooks)</text>
|
||||||
|
</g>
|
||||||
|
<!-- mpnotification->servicerequest -->
|
||||||
|
<g id="edge32" class="edge">
|
||||||
|
<title>mpnotification->servicerequest</title>
|
||||||
|
<path fill="none" stroke="#ad1457" d="M1275.48,-504.02C1275.48,-468.96 1275.48,-396 1275.48,-396 1275.48,-396 1187.67,-396 1187.67,-396"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="1187.67,-392.5 1177.67,-396 1187.67,-399.5 1187.67,-392.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1305" y="-478.3" font-family="Helvetica,sans-Serif" font-size="9.00">confirms</text>
|
||||||
|
</g>
|
||||||
|
<!-- google_sheets -->
|
||||||
|
<g id="node32" class="node">
|
||||||
|
<title>google_sheets</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1229.39,-52C1229.39,-52 1164.61,-52 1164.61,-52 1158.61,-52 1152.61,-46 1152.61,-40 1152.61,-40 1152.61,-28 1152.61,-28 1152.61,-22 1158.61,-16 1164.61,-16 1164.61,-16 1229.39,-16 1229.39,-16 1235.39,-16 1241.39,-22 1241.39,-28 1241.39,-28 1241.39,-40 1241.39,-40 1241.39,-46 1235.39,-52 1229.39,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1197" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">Google Sheets</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1197" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Exports)</text>
|
||||||
|
</g>
|
||||||
|
<!-- afip -->
|
||||||
|
<g id="node34" class="node">
|
||||||
|
<title>afip</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1431.4,-52C1431.4,-52 1374.6,-52 1374.6,-52 1368.6,-52 1362.6,-46 1362.6,-40 1362.6,-40 1362.6,-28 1362.6,-28 1362.6,-22 1368.6,-16 1374.6,-16 1374.6,-16 1431.4,-16 1431.4,-16 1437.4,-16 1443.4,-22 1443.4,-28 1443.4,-28 1443.4,-40 1443.4,-40 1443.4,-46 1437.4,-52 1431.4,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1403" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">AFIP</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1403" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Facturacion)</text>
|
||||||
|
</g>
|
||||||
|
<!-- taxpayer -->
|
||||||
|
<g id="node37" class="node">
|
||||||
|
<title>taxpayer</title>
|
||||||
|
<path fill="#fff9c4" stroke="black" d="M881.91,-52C881.91,-52 814.09,-52 814.09,-52 808.09,-52 802.09,-46 802.09,-40 802.09,-40 802.09,-28 802.09,-28 802.09,-22 808.09,-16 814.09,-16 814.09,-16 881.91,-16 881.91,-16 887.91,-16 893.91,-22 893.91,-28 893.91,-28 893.91,-40 893.91,-40 893.91,-46 887.91,-52 881.91,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="848" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">TaxPayer</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="848" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Contribuyente)</text>
|
||||||
|
</g>
|
||||||
|
<!-- receipt->taxpayer -->
|
||||||
|
<g id="edge37" class="edge">
|
||||||
|
<title>receipt->taxpayer</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M848,-150.97C848,-150.97 848,-63.89 848,-63.89"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="851.5,-63.89 848,-53.89 844.5,-63.89 851.5,-63.89"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="855" y="-125.1" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- pos -->
|
||||||
|
<g id="node38" class="node">
|
||||||
|
<title>pos</title>
|
||||||
|
<path fill="#fff9c4" stroke="black" d="M998.58,-52C998.58,-52 923.42,-52 923.42,-52 917.42,-52 911.42,-46 911.42,-40 911.42,-40 911.42,-28 911.42,-28 911.42,-22 917.42,-16 923.42,-16 923.42,-16 998.58,-16 998.58,-16 1004.58,-16 1010.58,-22 1010.58,-28 1010.58,-28 1010.58,-40 1010.58,-40 1010.58,-46 1004.58,-52 998.58,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="961" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">PointOfSales</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="961" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">(Punto de Venta)</text>
|
||||||
|
</g>
|
||||||
|
<!-- receipt->pos -->
|
||||||
|
<g id="edge38" class="edge">
|
||||||
|
<title>receipt->pos</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M893.36,-169C910.17,-169 925.21,-169 925.21,-169 925.21,-169 925.21,-64 925.21,-64"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="928.71,-64 925.21,-54 921.71,-64 928.71,-64"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="930" y="-125.1" font-family="Helvetica,sans-Serif" font-size="9.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 50 KiB |
184
atlas/book/arch-model/02-frontend-architecture.dot
Normal file
184
atlas/book/arch-model/02-frontend-architecture.dot
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
595
atlas/book/arch-model/02-frontend-architecture.svg
Normal file
595
atlas/book/arch-model/02-frontend-architecture.svg
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 14.0.5 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: FrontendArchitecture Pages: 1 -->
|
||||||
|
<svg width="2391pt" height="715pt"
|
||||||
|
viewBox="0.00 0.00 2391.00 715.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 710.85)">
|
||||||
|
<title>FrontendArchitecture</title>
|
||||||
|
<polygon fill="white" stroke="none" points="-4,4 -4,-710.85 2387,-710.85 2387,4 -4,4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1191.5" y="-687.65" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AMAR Mascotas - Frontend Architecture (Next.js)</text>
|
||||||
|
<g id="clust1" class="cluster">
|
||||||
|
<title>cluster_nextjs</title>
|
||||||
|
<path fill="#e3f2fd" stroke="#1565c0" d="M20,-452.8C20,-452.8 230,-452.8 230,-452.8 236,-452.8 242,-458.8 242,-464.8 242,-464.8 242,-634.32 242,-634.32 242,-640.32 236,-646.32 230,-646.32 230,-646.32 20,-646.32 20,-646.32 14,-646.32 8,-640.32 8,-634.32 8,-634.32 8,-464.8 8,-464.8 8,-458.8 14,-452.8 20,-452.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="125" y="-627.12" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Next.js 13+ (App Router)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster_public</title>
|
||||||
|
<path fill="#e8f5e9" stroke="#2e7d32" d="M262,-8C262,-8 454,-8 454,-8 460,-8 466,-14 466,-20 466,-20 466,-520 466,-520 466,-526 460,-532 454,-532 454,-532 262,-532 262,-532 256,-532 250,-526 250,-520 250,-520 250,-20 250,-20 250,-14 256,-8 262,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="358" y="-512.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Public Frontend (/(frontend))</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust3" class="cluster">
|
||||||
|
<title>cluster_backoffice</title>
|
||||||
|
<path fill="#fff3e0" stroke="#e65100" d="M647,-344.8C647,-344.8 1095,-344.8 1095,-344.8 1101,-344.8 1107,-350.8 1107,-356.8 1107,-356.8 1107,-520 1107,-520 1107,-526 1101,-532 1095,-532 1095,-532 647,-532 647,-532 641,-532 635,-526 635,-520 635,-520 635,-356.8 635,-356.8 635,-350.8 641,-344.8 647,-344.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="871" y="-512.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Backoffice (/(backoffice)/admin)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_components</title>
|
||||||
|
<path fill="#f3e5f5" stroke="#7b1fa2" d="M486,-236.8C486,-236.8 1091,-236.8 1091,-236.8 1097,-236.8 1103,-242.8 1103,-248.8 1103,-248.8 1103,-304 1103,-304 1103,-310 1097,-316 1091,-316 1091,-316 486,-316 486,-316 480,-316 474,-310 474,-304 474,-304 474,-248.8 474,-248.8 474,-242.8 480,-236.8 486,-236.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="788.5" y="-296.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Shared Components (/components)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust5" class="cluster">
|
||||||
|
<title>cluster_services</title>
|
||||||
|
<path fill="#ffebee" stroke="#c62828" d="M1222,-452.8C1222,-452.8 1922,-452.8 1922,-452.8 1928,-452.8 1934,-458.8 1934,-464.8 1934,-464.8 1934,-634.32 1934,-634.32 1934,-640.32 1928,-646.32 1922,-646.32 1922,-646.32 1222,-646.32 1222,-646.32 1216,-646.32 1210,-640.32 1210,-634.32 1210,-634.32 1210,-464.8 1210,-464.8 1210,-458.8 1216,-452.8 1222,-452.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1572" y="-627.12" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Services Layer (/services)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_state</title>
|
||||||
|
<path fill="#e0f7fa" stroke="#00838f" d="M2031,-452.8C2031,-452.8 2363,-452.8 2363,-452.8 2369,-452.8 2375,-458.8 2375,-464.8 2375,-464.8 2375,-634.32 2375,-634.32 2375,-640.32 2369,-646.32 2363,-646.32 2363,-646.32 2031,-646.32 2031,-646.32 2025,-646.32 2019,-640.32 2019,-634.32 2019,-634.32 2019,-464.8 2019,-464.8 2019,-458.8 2025,-452.8 2031,-452.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2197" y="-627.12" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">State Management (/redux, /contexts)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust7" class="cluster">
|
||||||
|
<title>cluster_backend</title>
|
||||||
|
<path fill="#eceff1" stroke="#455a64" d="M1382,-344.8C1382,-344.8 1892,-344.8 1892,-344.8 1898,-344.8 1904,-350.8 1904,-356.8 1904,-356.8 1904,-412 1904,-412 1904,-418 1898,-424 1892,-424 1892,-424 1382,-424 1382,-424 1376,-424 1370,-418 1370,-412 1370,-412 1370,-356.8 1370,-356.8 1370,-350.8 1376,-344.8 1382,-344.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1637" y="-404.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Django Backend API</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust8" class="cluster">
|
||||||
|
<title>cluster_users</title>
|
||||||
|
<path fill="#fce4ec" stroke="#ad1457" d="M824,-560.8C824,-560.8 1158,-560.8 1158,-560.8 1164,-560.8 1170,-566.8 1170,-572.8 1170,-572.8 1170,-640.65 1170,-640.65 1170,-646.65 1164,-652.65 1158,-652.65 1158,-652.65 824,-652.65 824,-652.65 818,-652.65 812,-646.65 812,-640.65 812,-640.65 812,-572.8 812,-572.8 812,-566.8 818,-560.8 824,-560.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="991" y="-633.45" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">User Types</text>
|
||||||
|
</g>
|
||||||
|
<!-- app_router -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>app_router</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M155.82,-611.12C155.82,-611.12 108.18,-611.12 108.18,-611.12 102.18,-611.12 96.18,-605.12 96.18,-599.12 96.18,-599.12 96.18,-587.12 96.18,-587.12 96.18,-581.12 102.18,-575.12 108.18,-575.12 108.18,-575.12 155.82,-575.12 155.82,-575.12 161.82,-575.12 167.82,-581.12 167.82,-587.12 167.82,-587.12 167.82,-599.12 167.82,-599.12 167.82,-605.12 161.82,-611.12 155.82,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="132" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">App Router</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="132" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">/app/*</text>
|
||||||
|
</g>
|
||||||
|
<!-- layout -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>layout</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M118.23,-496.8C118.23,-496.8 27.77,-496.8 27.77,-496.8 21.77,-496.8 15.77,-490.8 15.77,-484.8 15.77,-484.8 15.77,-472.8 15.77,-472.8 15.77,-466.8 21.77,-460.8 27.77,-460.8 27.77,-460.8 118.23,-460.8 118.23,-460.8 124.23,-460.8 130.23,-466.8 130.23,-472.8 130.23,-472.8 130.23,-484.8 130.23,-484.8 130.23,-490.8 124.23,-496.8 118.23,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="73" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">Layout Components</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="73" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(RootLayout, etc)</text>
|
||||||
|
</g>
|
||||||
|
<!-- app_router->layout -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>app_router->layout</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M113.2,-574.76C113.2,-574.76 113.2,-508.64 113.2,-508.64"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="116.7,-508.64 113.2,-498.64 109.7,-508.64 116.7,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- middleware -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>middleware</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M221.84,-496.8C221.84,-496.8 160.16,-496.8 160.16,-496.8 154.16,-496.8 148.16,-490.8 148.16,-484.8 148.16,-484.8 148.16,-472.8 148.16,-472.8 148.16,-466.8 154.16,-460.8 160.16,-460.8 160.16,-460.8 221.84,-460.8 221.84,-460.8 227.84,-460.8 233.84,-466.8 233.84,-472.8 233.84,-472.8 233.84,-484.8 233.84,-484.8 233.84,-490.8 227.84,-496.8 221.84,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="191" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">Middleware</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="191" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Auth redirect)</text>
|
||||||
|
</g>
|
||||||
|
<!-- app_router->middleware -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>app_router->middleware</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M157.99,-574.76C157.99,-574.76 157.99,-508.64 157.99,-508.64"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="161.49,-508.64 157.99,-498.64 154.49,-508.64 161.49,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- home -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>home</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M442.04,-496.8C442.04,-496.8 391.96,-496.8 391.96,-496.8 385.96,-496.8 379.96,-490.8 379.96,-484.8 379.96,-484.8 379.96,-472.8 379.96,-472.8 379.96,-466.8 385.96,-460.8 391.96,-460.8 391.96,-460.8 442.04,-460.8 442.04,-460.8 448.04,-460.8 454.04,-466.8 454.04,-472.8 454.04,-472.8 454.04,-484.8 454.04,-484.8 454.04,-490.8 448.04,-496.8 442.04,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="417" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">Home Page</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="417" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">/</text>
|
||||||
|
</g>
|
||||||
|
<!-- services_page -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>services_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M445.57,-388.8C445.57,-388.8 370.43,-388.8 370.43,-388.8 364.43,-388.8 358.43,-382.8 358.43,-376.8 358.43,-376.8 358.43,-364.8 358.43,-364.8 358.43,-358.8 364.43,-352.8 370.43,-352.8 370.43,-352.8 445.57,-352.8 445.57,-352.8 451.57,-352.8 457.57,-358.8 457.57,-364.8 457.57,-364.8 457.57,-376.8 457.57,-376.8 457.57,-382.8 451.57,-388.8 445.57,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="408" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">Services Catalog</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="408" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">/servicios</text>
|
||||||
|
</g>
|
||||||
|
<!-- home->services_page -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>home->services_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M417,-460.48C417,-460.48 417,-400.72 417,-400.72"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="420.5,-400.72 417,-390.72 413.5,-400.72 420.5,-400.72"/>
|
||||||
|
</g>
|
||||||
|
<!-- cart_page -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>cart_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M425,-280.8C425,-280.8 395,-280.8 395,-280.8 389,-280.8 383,-274.8 383,-268.8 383,-268.8 383,-256.8 383,-256.8 383,-250.8 389,-244.8 395,-244.8 395,-244.8 425,-244.8 425,-244.8 431,-244.8 437,-250.8 437,-256.8 437,-256.8 437,-268.8 437,-268.8 437,-274.8 431,-280.8 425,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="410" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">Cart</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="410" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">/carrito</text>
|
||||||
|
</g>
|
||||||
|
<!-- services_page->cart_page -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>services_page->cart_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M410,-352.48C410,-352.48 410,-292.72 410,-292.72"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="413.5,-292.72 410,-282.72 406.5,-292.72 413.5,-292.72"/>
|
||||||
|
</g>
|
||||||
|
<!-- login_page -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>login_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M444.47,-198C444.47,-198 381.53,-198 381.53,-198 375.53,-198 369.53,-192 369.53,-186 369.53,-186 369.53,-174 369.53,-174 369.53,-168 375.53,-162 381.53,-162 381.53,-162 444.47,-162 444.47,-162 450.47,-162 456.47,-168 456.47,-174 456.47,-174 456.47,-186 456.47,-186 456.47,-192 450.47,-198 444.47,-198"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="413" y="-183.3" font-family="Helvetica,sans-Serif" font-size="11.00">Login/Register</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="413" y="-170.1" font-family="Helvetica,sans-Serif" font-size="11.00">/login</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart_page->login_page -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>cart_page->login_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" stroke-dasharray="5,2" d="M410,-244.42C410,-244.42 410,-209.84 410,-209.84"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="413.5,-209.84 410,-199.84 406.5,-209.84 413.5,-209.84"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="432.76" y="-218.7" font-family="Helvetica,sans-Serif" font-size="9.00">if not auth</text>
|
||||||
|
</g>
|
||||||
|
<!-- profile_page -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>profile_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M438.73,-125C438.73,-125 389.27,-125 389.27,-125 383.27,-125 377.27,-119 377.27,-113 377.27,-113 377.27,-101 377.27,-101 377.27,-95 383.27,-89 389.27,-89 389.27,-89 438.73,-89 438.73,-89 444.73,-89 450.73,-95 450.73,-101 450.73,-101 450.73,-113 450.73,-113 450.73,-119 444.73,-125 438.73,-125"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="414" y="-110.3" font-family="Helvetica,sans-Serif" font-size="11.00">User Profile</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="414" y="-97.1" font-family="Helvetica,sans-Serif" font-size="11.00">/perfil</text>
|
||||||
|
</g>
|
||||||
|
<!-- login_page->profile_page -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>login_page->profile_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M414,-161.58C414,-161.58 414,-136.93 414,-136.93"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="417.5,-136.93 414,-126.93 410.5,-136.93 417.5,-136.93"/>
|
||||||
|
</g>
|
||||||
|
<!-- pets_page -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>pets_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M347.06,-52C347.06,-52 304.94,-52 304.94,-52 298.94,-52 292.94,-46 292.94,-40 292.94,-40 292.94,-28 292.94,-28 292.94,-22 298.94,-16 304.94,-16 304.94,-16 347.06,-16 347.06,-16 353.06,-16 359.06,-22 359.06,-28 359.06,-28 359.06,-40 359.06,-40 359.06,-46 353.06,-52 347.06,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="326" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">My Pets</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="326" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">/mascotas</text>
|
||||||
|
</g>
|
||||||
|
<!-- profile_page->pets_page -->
|
||||||
|
<g id="edge7" class="edge">
|
||||||
|
<title>profile_page->pets_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M376.8,-107C352.66,-107 325.68,-107 325.68,-107 325.68,-107 325.68,-63.93 325.68,-63.93"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="329.18,-63.93 325.68,-53.93 322.18,-63.93 329.18,-63.93"/>
|
||||||
|
</g>
|
||||||
|
<!-- requests_page -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>requests_page</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M445.09,-52C445.09,-52 388.91,-52 388.91,-52 382.91,-52 376.91,-46 376.91,-40 376.91,-40 376.91,-28 376.91,-28 376.91,-22 382.91,-16 388.91,-16 388.91,-16 445.09,-16 445.09,-16 451.09,-16 457.09,-22 457.09,-28 457.09,-28 457.09,-40 457.09,-40 457.09,-46 451.09,-52 445.09,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="417" y="-37.3" font-family="Helvetica,sans-Serif" font-size="11.00">My Requests</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="417" y="-24.1" font-family="Helvetica,sans-Serif" font-size="11.00">/solicitudes</text>
|
||||||
|
</g>
|
||||||
|
<!-- profile_page->requests_page -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>profile_page->requests_page</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M414,-88.58C414,-88.58 414,-63.93 414,-63.93"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="417.5,-63.93 414,-53.93 410.5,-63.93 417.5,-63.93"/>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>admin_dash</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M892.91,-496.8C892.91,-496.8 847.09,-496.8 847.09,-496.8 841.09,-496.8 835.09,-490.8 835.09,-484.8 835.09,-484.8 835.09,-472.8 835.09,-472.8 835.09,-466.8 841.09,-460.8 847.09,-460.8 847.09,-460.8 892.91,-460.8 892.91,-460.8 898.91,-460.8 904.91,-466.8 904.91,-472.8 904.91,-472.8 904.91,-484.8 904.91,-484.8 904.91,-490.8 898.91,-496.8 892.91,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="870" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">Dashboard</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="870" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">/admin</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_visits -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>admin_visits</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M740.77,-388.8C740.77,-388.8 655.23,-388.8 655.23,-388.8 649.23,-388.8 643.23,-382.8 643.23,-376.8 643.23,-376.8 643.23,-364.8 643.23,-364.8 643.23,-358.8 649.23,-352.8 655.23,-352.8 655.23,-352.8 740.77,-352.8 740.77,-352.8 746.77,-352.8 752.77,-358.8 752.77,-364.8 752.77,-364.8 752.77,-376.8 752.77,-376.8 752.77,-382.8 746.77,-388.8 740.77,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="698" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">Visits Management</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="698" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">/admin/visits</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->admin_visits -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>admin_dash->admin_visits</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M834.78,-468C784.46,-468 698,-468 698,-468 698,-468 698,-400.6 698,-400.6"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="701.5,-400.6 698,-390.6 694.5,-400.6 701.5,-400.6"/>
|
||||||
|
</g>
|
||||||
|
<!-- admin_pets -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>admin_pets</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M845.45,-388.8C845.45,-388.8 782.55,-388.8 782.55,-388.8 776.55,-388.8 770.55,-382.8 770.55,-376.8 770.55,-376.8 770.55,-364.8 770.55,-364.8 770.55,-358.8 776.55,-352.8 782.55,-352.8 782.55,-352.8 845.45,-352.8 845.45,-352.8 851.45,-352.8 857.45,-358.8 857.45,-364.8 857.45,-364.8 857.45,-376.8 857.45,-376.8 857.45,-382.8 851.45,-388.8 845.45,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="814" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">Pets Overview</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="814" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">/admin/pets</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->admin_pets -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>admin_dash->admin_pets</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M846.27,-460.48C846.27,-460.48 846.27,-400.72 846.27,-400.72"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="849.77,-400.72 846.27,-390.72 842.77,-400.72 849.77,-400.72"/>
|
||||||
|
</g>
|
||||||
|
<!-- admin_requests -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>admin_requests</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M966.72,-388.8C966.72,-388.8 887.28,-388.8 887.28,-388.8 881.28,-388.8 875.28,-382.8 875.28,-376.8 875.28,-376.8 875.28,-364.8 875.28,-364.8 875.28,-358.8 881.28,-352.8 887.28,-352.8 887.28,-352.8 966.72,-352.8 966.72,-352.8 972.72,-352.8 978.72,-358.8 978.72,-364.8 978.72,-364.8 978.72,-376.8 978.72,-376.8 978.72,-382.8 972.72,-388.8 966.72,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="927" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">Service Requests</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="927" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">/admin/solicitudes</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->admin_requests -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>admin_dash->admin_requests</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M890.1,-460.48C890.1,-460.48 890.1,-400.72 890.1,-400.72"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="893.6,-400.72 890.1,-390.72 886.6,-400.72 893.6,-400.72"/>
|
||||||
|
</g>
|
||||||
|
<!-- admin_calendar -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>admin_calendar</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M1087.41,-388.8C1087.41,-388.8 1008.59,-388.8 1008.59,-388.8 1002.59,-388.8 996.59,-382.8 996.59,-376.8 996.59,-376.8 996.59,-364.8 996.59,-364.8 996.59,-358.8 1002.59,-352.8 1008.59,-352.8 1008.59,-352.8 1087.41,-352.8 1087.41,-352.8 1093.41,-352.8 1099.41,-358.8 1099.41,-364.8 1099.41,-364.8 1099.41,-376.8 1099.41,-376.8 1099.41,-382.8 1093.41,-388.8 1087.41,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1048" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">Calendar View</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1048" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">/admin/calendario</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->admin_calendar -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>admin_dash->admin_calendar</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M905.19,-470C949.1,-470 1018.5,-470 1018.5,-470 1018.5,-470 1018.5,-400.51 1018.5,-400.51"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1022,-400.51 1018.5,-390.51 1015,-400.51 1022,-400.51"/>
|
||||||
|
</g>
|
||||||
|
<!-- sidebar -->
|
||||||
|
<g id="node16" class="node">
|
||||||
|
<title>sidebar</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M537.99,-280.8C537.99,-280.8 494.01,-280.8 494.01,-280.8 488.01,-280.8 482.01,-274.8 482.01,-268.8 482.01,-268.8 482.01,-256.8 482.01,-256.8 482.01,-250.8 488.01,-244.8 494.01,-244.8 494.01,-244.8 537.99,-244.8 537.99,-244.8 543.99,-244.8 549.99,-250.8 549.99,-256.8 549.99,-256.8 549.99,-268.8 549.99,-268.8 549.99,-274.8 543.99,-280.8 537.99,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="516" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">Sidebar</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="516" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">Navigation</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->sidebar -->
|
||||||
|
<g id="edge15" class="edge">
|
||||||
|
<title>admin_dash->sidebar</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M834.76,-482C745.22,-482 516,-482 516,-482 516,-482 516,-292.63 516,-292.63"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="519.5,-292.63 516,-282.63 512.5,-292.63 519.5,-292.63"/>
|
||||||
|
</g>
|
||||||
|
<!-- navbar -->
|
||||||
|
<g id="node17" class="node">
|
||||||
|
<title>navbar</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M658.1,-280.8C658.1,-280.8 579.9,-280.8 579.9,-280.8 573.9,-280.8 567.9,-274.8 567.9,-268.8 567.9,-268.8 567.9,-256.8 567.9,-256.8 567.9,-250.8 573.9,-244.8 579.9,-244.8 579.9,-244.8 658.1,-244.8 658.1,-244.8 664.1,-244.8 670.1,-250.8 670.1,-256.8 670.1,-256.8 670.1,-268.8 670.1,-268.8 670.1,-274.8 664.1,-280.8 658.1,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="619" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">NavbarBackoffice</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="619" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">Top Bar</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_dash->navbar -->
|
||||||
|
<g id="edge16" class="edge">
|
||||||
|
<title>admin_dash->navbar</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M834.61,-475C762.55,-475 605.56,-475 605.56,-475 605.56,-475 605.56,-292.77 605.56,-292.77"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="609.06,-292.77 605.56,-282.77 602.06,-292.77 609.06,-292.77"/>
|
||||||
|
</g>
|
||||||
|
<!-- visits_section -->
|
||||||
|
<g id="node18" class="node">
|
||||||
|
<title>visits_section</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M765.53,-280.8C765.53,-280.8 700.47,-280.8 700.47,-280.8 694.47,-280.8 688.47,-274.8 688.47,-268.8 688.47,-268.8 688.47,-256.8 688.47,-256.8 688.47,-250.8 694.47,-244.8 700.47,-244.8 700.47,-244.8 765.53,-244.8 765.53,-244.8 771.53,-244.8 777.53,-250.8 777.53,-256.8 777.53,-256.8 777.53,-268.8 777.53,-268.8 777.53,-274.8 771.53,-280.8 765.53,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="733" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">VisitsSection</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="733" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">(List + Actions)</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_visits->visits_section -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>admin_visits->visits_section</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M709.91,-352.48C709.91,-352.48 709.91,-292.72 709.91,-292.72"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="713.41,-292.72 709.91,-282.72 706.41,-292.72 713.41,-292.72"/>
|
||||||
|
</g>
|
||||||
|
<!-- drawer -->
|
||||||
|
<g id="node19" class="node">
|
||||||
|
<title>drawer</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M860.86,-280.8C860.86,-280.8 807.14,-280.8 807.14,-280.8 801.14,-280.8 795.14,-274.8 795.14,-268.8 795.14,-268.8 795.14,-256.8 795.14,-256.8 795.14,-250.8 801.14,-244.8 807.14,-244.8 807.14,-244.8 860.86,-244.8 860.86,-244.8 866.86,-244.8 872.86,-250.8 872.86,-256.8 872.86,-256.8 872.86,-268.8 872.86,-268.8 872.86,-274.8 866.86,-280.8 860.86,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="834" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">VisitsDrawer</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="834" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Side Panel)</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_visits->drawer -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>admin_visits->drawer</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M731.34,-352.59C731.34,-336.98 731.34,-317 731.34,-317 731.34,-317 826.29,-317 826.29,-317 826.29,-317 826.29,-292.66 826.29,-292.66"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="829.79,-292.66 826.29,-282.66 822.79,-292.66 829.79,-292.66"/>
|
||||||
|
</g>
|
||||||
|
<!-- tables -->
|
||||||
|
<g id="node20" class="node">
|
||||||
|
<title>tables</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M957.18,-280.8C957.18,-280.8 902.82,-280.8 902.82,-280.8 896.82,-280.8 890.82,-274.8 890.82,-268.8 890.82,-268.8 890.82,-256.8 890.82,-256.8 890.82,-250.8 896.82,-244.8 902.82,-244.8 902.82,-244.8 957.18,-244.8 957.18,-244.8 963.18,-244.8 969.18,-250.8 969.18,-256.8 969.18,-256.8 969.18,-268.8 969.18,-268.8 969.18,-274.8 963.18,-280.8 957.18,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="930" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">DataTable</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="930" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">Components</text>
|
||||||
|
</g>
|
||||||
|
<!-- forms -->
|
||||||
|
<g id="node21" class="node">
|
||||||
|
<title>forms</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M1082.54,-280.8C1082.54,-280.8 999.46,-280.8 999.46,-280.8 993.46,-280.8 987.46,-274.8 987.46,-268.8 987.46,-268.8 987.46,-256.8 987.46,-256.8 987.46,-250.8 993.46,-244.8 999.46,-244.8 999.46,-244.8 1082.54,-244.8 1082.54,-244.8 1088.54,-244.8 1094.54,-250.8 1094.54,-256.8 1094.54,-256.8 1094.54,-268.8 1094.54,-268.8 1094.54,-274.8 1088.54,-280.8 1082.54,-280.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1041" y="-266.1" font-family="Helvetica,sans-Serif" font-size="11.00">Form Components</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1041" y="-252.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Pet, Visit, etc)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service -->
|
||||||
|
<g id="node22" class="node">
|
||||||
|
<title>http_service</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1644.51,-611.12C1644.51,-611.12 1575.49,-611.12 1575.49,-611.12 1569.49,-611.12 1563.49,-605.12 1563.49,-599.12 1563.49,-599.12 1563.49,-587.12 1563.49,-587.12 1563.49,-581.12 1569.49,-575.12 1575.49,-575.12 1575.49,-575.12 1644.51,-575.12 1644.51,-575.12 1650.51,-575.12 1656.51,-581.12 1656.51,-587.12 1656.51,-587.12 1656.51,-599.12 1656.51,-599.12 1656.51,-605.12 1650.51,-611.12 1644.51,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1610" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">HttpService</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1610" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(Axios wrapper)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_api -->
|
||||||
|
<g id="node23" class="node">
|
||||||
|
<title>auth_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1914.15,-496.8C1914.15,-496.8 1851.85,-496.8 1851.85,-496.8 1845.85,-496.8 1839.85,-490.8 1839.85,-484.8 1839.85,-484.8 1839.85,-472.8 1839.85,-472.8 1839.85,-466.8 1845.85,-460.8 1851.85,-460.8 1851.85,-460.8 1914.15,-460.8 1914.15,-460.8 1920.15,-460.8 1926.15,-466.8 1926.15,-472.8 1926.15,-472.8 1926.15,-484.8 1926.15,-484.8 1926.15,-490.8 1920.15,-496.8 1914.15,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1883" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">authAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1883" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(login/register)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->auth_api -->
|
||||||
|
<g id="edge17" class="edge">
|
||||||
|
<title>http_service->auth_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1656.93,-602C1734.99,-602 1883,-602 1883,-602 1883,-602 1883,-508.76 1883,-508.76"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1886.5,-508.76 1883,-498.76 1879.5,-508.76 1886.5,-508.76"/>
|
||||||
|
</g>
|
||||||
|
<!-- visits_api -->
|
||||||
|
<g id="node24" class="node">
|
||||||
|
<title>visits_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1810.3,-496.8C1810.3,-496.8 1751.7,-496.8 1751.7,-496.8 1745.7,-496.8 1739.7,-490.8 1739.7,-484.8 1739.7,-484.8 1739.7,-472.8 1739.7,-472.8 1739.7,-466.8 1745.7,-460.8 1751.7,-460.8 1751.7,-460.8 1810.3,-460.8 1810.3,-460.8 1816.3,-460.8 1822.3,-466.8 1822.3,-472.8 1822.3,-472.8 1822.3,-484.8 1822.3,-484.8 1822.3,-490.8 1816.3,-496.8 1810.3,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1781" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">visitsAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1781" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(CRUD visits)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->visits_api -->
|
||||||
|
<g id="edge18" class="edge">
|
||||||
|
<title>http_service->visits_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1656.73,-593C1707.36,-593 1781,-593 1781,-593 1781,-593 1781,-508.66 1781,-508.66"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1784.5,-508.66 1781,-498.66 1777.5,-508.66 1784.5,-508.66"/>
|
||||||
|
</g>
|
||||||
|
<!-- orders_api -->
|
||||||
|
<g id="node25" class="node">
|
||||||
|
<title>orders_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1309.71,-496.8C1309.71,-496.8 1230.29,-496.8 1230.29,-496.8 1224.29,-496.8 1218.29,-490.8 1218.29,-484.8 1218.29,-484.8 1218.29,-472.8 1218.29,-472.8 1218.29,-466.8 1224.29,-460.8 1230.29,-460.8 1230.29,-460.8 1309.71,-460.8 1309.71,-460.8 1315.71,-460.8 1321.71,-466.8 1321.71,-472.8 1321.71,-472.8 1321.71,-484.8 1321.71,-484.8 1321.71,-490.8 1315.71,-496.8 1309.71,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1270" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">OrdersAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1270" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(service requests)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->orders_api -->
|
||||||
|
<g id="edge19" class="edge">
|
||||||
|
<title>http_service->orders_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1563.08,-602C1469.81,-602 1270,-602 1270,-602 1270,-602 1270,-508.76 1270,-508.76"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1273.5,-508.76 1270,-498.76 1266.5,-508.76 1273.5,-508.76"/>
|
||||||
|
</g>
|
||||||
|
<!-- petowners_api -->
|
||||||
|
<g id="node26" class="node">
|
||||||
|
<title>petowners_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1709.46,-496.8C1709.46,-496.8 1646.54,-496.8 1646.54,-496.8 1640.54,-496.8 1634.54,-490.8 1634.54,-484.8 1634.54,-484.8 1634.54,-472.8 1634.54,-472.8 1634.54,-466.8 1640.54,-460.8 1646.54,-460.8 1646.54,-460.8 1709.46,-460.8 1709.46,-460.8 1715.46,-460.8 1721.46,-466.8 1721.46,-472.8 1721.46,-472.8 1721.46,-484.8 1721.46,-484.8 1721.46,-490.8 1715.46,-496.8 1709.46,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1678" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">petOwnersAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1678" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(clients)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->petowners_api -->
|
||||||
|
<g id="edge20" class="edge">
|
||||||
|
<title>http_service->petowners_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1645.53,-574.76C1645.53,-574.76 1645.53,-508.64 1645.53,-508.64"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1649.03,-508.64 1645.53,-498.64 1642.03,-508.64 1649.03,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- vets_api -->
|
||||||
|
<g id="node27" class="node">
|
||||||
|
<title>vets_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1604.27,-496.8C1604.27,-496.8 1529.73,-496.8 1529.73,-496.8 1523.73,-496.8 1517.73,-490.8 1517.73,-484.8 1517.73,-484.8 1517.73,-472.8 1517.73,-472.8 1517.73,-466.8 1523.73,-460.8 1529.73,-460.8 1529.73,-460.8 1604.27,-460.8 1604.27,-460.8 1610.27,-460.8 1616.27,-466.8 1616.27,-472.8 1616.27,-472.8 1616.27,-484.8 1616.27,-484.8 1616.27,-490.8 1610.27,-496.8 1604.27,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1567" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">VeterinariansAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1567" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(professionals)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->vets_api -->
|
||||||
|
<g id="edge21" class="edge">
|
||||||
|
<title>http_service->vets_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1589.88,-574.76C1589.88,-574.76 1589.88,-508.64 1589.88,-508.64"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1593.38,-508.64 1589.88,-498.64 1586.38,-508.64 1593.38,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- services_api -->
|
||||||
|
<g id="node28" class="node">
|
||||||
|
<title>services_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1488.04,-496.8C1488.04,-496.8 1437.96,-496.8 1437.96,-496.8 1431.96,-496.8 1425.96,-490.8 1425.96,-484.8 1425.96,-484.8 1425.96,-472.8 1425.96,-472.8 1425.96,-466.8 1431.96,-460.8 1437.96,-460.8 1437.96,-460.8 1488.04,-460.8 1488.04,-460.8 1494.04,-460.8 1500.04,-466.8 1500.04,-472.8 1500.04,-472.8 1500.04,-484.8 1500.04,-484.8 1500.04,-490.8 1494.04,-496.8 1488.04,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1463" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">servicesAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1463" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(catalog)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->services_api -->
|
||||||
|
<g id="edge22" class="edge">
|
||||||
|
<title>http_service->services_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1563.08,-584C1520.06,-584 1463,-584 1463,-584 1463,-584 1463,-508.64 1463,-508.64"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1466.5,-508.64 1463,-498.64 1459.5,-508.64 1466.5,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- cart_api -->
|
||||||
|
<g id="node29" class="node">
|
||||||
|
<title>cart_api</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M1395.99,-496.8C1395.99,-496.8 1352.01,-496.8 1352.01,-496.8 1346.01,-496.8 1340.01,-490.8 1340.01,-484.8 1340.01,-484.8 1340.01,-472.8 1340.01,-472.8 1340.01,-466.8 1346.01,-460.8 1352.01,-460.8 1352.01,-460.8 1395.99,-460.8 1395.99,-460.8 1401.99,-460.8 1407.99,-466.8 1407.99,-472.8 1407.99,-472.8 1407.99,-484.8 1407.99,-484.8 1407.99,-490.8 1401.99,-496.8 1395.99,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1374" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">CartAPI</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1374" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(shopping)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->cart_api -->
|
||||||
|
<g id="edge23" class="edge">
|
||||||
|
<title>http_service->cart_api</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1563.09,-593C1494.27,-593 1374,-593 1374,-593 1374,-593 1374,-508.66 1374,-508.66"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1377.5,-508.66 1374,-498.66 1370.5,-508.66 1377.5,-508.66"/>
|
||||||
|
</g>
|
||||||
|
<!-- api_auth -->
|
||||||
|
<g id="node38" class="node">
|
||||||
|
<title>api_auth</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1773.81,-388.8C1773.81,-388.8 1726.19,-388.8 1726.19,-388.8 1720.19,-388.8 1714.19,-382.8 1714.19,-376.8 1714.19,-376.8 1714.19,-364.8 1714.19,-364.8 1714.19,-358.8 1720.19,-352.8 1726.19,-352.8 1726.19,-352.8 1773.81,-352.8 1773.81,-352.8 1779.81,-352.8 1785.81,-358.8 1785.81,-364.8 1785.81,-364.8 1785.81,-376.8 1785.81,-376.8 1785.81,-382.8 1779.81,-388.8 1773.81,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1750" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">/api/token/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1750" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">(JWT Auth)</text>
|
||||||
|
</g>
|
||||||
|
<!-- http_service->api_auth -->
|
||||||
|
<g id="edge39" class="edge">
|
||||||
|
<title>http_service->api_auth</title>
|
||||||
|
<path fill="none" stroke="#455a64" stroke-dasharray="5,2" d="M1656.96,-584C1690.82,-584 1730.58,-584 1730.58,-584 1730.58,-584 1730.58,-400.49 1730.58,-400.49"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1734.08,-400.49 1730.58,-390.49 1727.08,-400.49 1734.08,-400.49"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1986.5" y="-476.1" font-family="Helvetica,sans-Serif" font-size="9.00">JWT refresh</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_api->api_auth -->
|
||||||
|
<g id="edge24" class="edge">
|
||||||
|
<title>auth_api->api_auth</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1867.88,-460.31C1867.88,-439.04 1867.88,-407 1867.88,-407 1867.88,-407 1770.44,-407 1770.44,-407 1770.44,-407 1770.44,-400.41 1770.44,-400.41"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1773.94,-400.41 1770.44,-390.41 1766.94,-400.41 1773.94,-400.41"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1909.27" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">POST /api/token/</text>
|
||||||
|
</g>
|
||||||
|
<!-- api_mascotas -->
|
||||||
|
<g id="node35" class="node">
|
||||||
|
<title>api_mascotas</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1683.93,-388.8C1683.93,-388.8 1602.07,-388.8 1602.07,-388.8 1596.07,-388.8 1590.07,-382.8 1590.07,-376.8 1590.07,-376.8 1590.07,-364.8 1590.07,-364.8 1590.07,-358.8 1596.07,-352.8 1602.07,-352.8 1602.07,-352.8 1683.93,-352.8 1683.93,-352.8 1689.93,-352.8 1695.93,-358.8 1695.93,-364.8 1695.93,-364.8 1695.93,-376.8 1695.93,-376.8 1695.93,-382.8 1689.93,-388.8 1683.93,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1643" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">/mascotas/api/v1/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1643" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Pets, Vets, Visits)</text>
|
||||||
|
</g>
|
||||||
|
<!-- visits_api->api_mascotas -->
|
||||||
|
<g id="edge25" class="edge">
|
||||||
|
<title>visits_api->api_mascotas</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1755.07,-460.59C1755.07,-444.98 1755.07,-425 1755.07,-425 1755.07,-425 1675.46,-425 1675.46,-425 1675.46,-425 1675.46,-400.66 1675.46,-400.66"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1678.96,-400.66 1675.46,-390.66 1671.96,-400.66 1678.96,-400.66"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1814.25" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">CRUD /vet-visits/</text>
|
||||||
|
</g>
|
||||||
|
<!-- api_solicitudes -->
|
||||||
|
<g id="node37" class="node">
|
||||||
|
<title>api_solicitudes</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1439.74,-388.8C1439.74,-388.8 1390.26,-388.8 1390.26,-388.8 1384.26,-388.8 1378.26,-382.8 1378.26,-376.8 1378.26,-376.8 1378.26,-364.8 1378.26,-364.8 1378.26,-358.8 1384.26,-352.8 1390.26,-352.8 1390.26,-352.8 1439.74,-352.8 1439.74,-352.8 1445.74,-352.8 1451.74,-358.8 1451.74,-364.8 1451.74,-364.8 1451.74,-376.8 1451.74,-376.8 1451.74,-382.8 1445.74,-388.8 1439.74,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1415" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">/solicitudes/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1415" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Requests)</text>
|
||||||
|
</g>
|
||||||
|
<!-- orders_api->api_solicitudes -->
|
||||||
|
<g id="edge26" class="edge">
|
||||||
|
<title>orders_api->api_solicitudes</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1270,-460.51C1270,-429.76 1270,-371 1270,-371 1270,-371 1366.52,-371 1366.52,-371"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1366.52,-374.5 1376.52,-371 1366.52,-367.5 1366.52,-374.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1320.76" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">CRUD /service-requests/</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowners_api->api_mascotas -->
|
||||||
|
<g id="edge27" class="edge">
|
||||||
|
<title>petowners_api->api_mascotas</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1655,-460.48C1655,-460.48 1655,-400.72 1655,-400.72"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1658.5,-400.72 1655,-390.72 1651.5,-400.72 1658.5,-400.72"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1715.01" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">CRUD /pet-owners/</text>
|
||||||
|
</g>
|
||||||
|
<!-- vets_api->api_mascotas -->
|
||||||
|
<g id="edge28" class="edge">
|
||||||
|
<title>vets_api->api_mascotas</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1603.17,-460.48C1603.17,-460.48 1603.17,-400.72 1603.17,-400.72"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1606.67,-400.72 1603.17,-390.72 1599.67,-400.72 1606.67,-400.72"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1609.76" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">GET /veterinarians/</text>
|
||||||
|
</g>
|
||||||
|
<!-- api_productos -->
|
||||||
|
<g id="node36" class="node">
|
||||||
|
<title>api_productos</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1560.09,-388.8C1560.09,-388.8 1481.91,-388.8 1481.91,-388.8 1475.91,-388.8 1469.91,-382.8 1469.91,-376.8 1469.91,-376.8 1469.91,-364.8 1469.91,-364.8 1469.91,-358.8 1475.91,-352.8 1481.91,-352.8 1481.91,-352.8 1560.09,-352.8 1560.09,-352.8 1566.09,-352.8 1572.09,-358.8 1572.09,-364.8 1572.09,-364.8 1572.09,-376.8 1572.09,-376.8 1572.09,-382.8 1566.09,-388.8 1560.09,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1521" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">/productos/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1521" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Services, Prices)</text>
|
||||||
|
</g>
|
||||||
|
<!-- services_api->api_productos -->
|
||||||
|
<g id="edge29" class="edge">
|
||||||
|
<title>services_api->api_productos</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1489.99,-460.48C1489.99,-460.48 1489.99,-400.72 1489.99,-400.72"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1493.49,-400.72 1489.99,-390.72 1486.49,-400.72 1493.49,-400.72"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1517.5" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">GET /services/</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart_api->api_productos -->
|
||||||
|
<g id="edge30" class="edge">
|
||||||
|
<title>cart_api->api_productos</title>
|
||||||
|
<path fill="none" stroke="#455a64" d="M1393.13,-460.48C1393.13,-451.67 1393.13,-443 1393.13,-443 1393.13,-443 1479.95,-443 1479.95,-443 1479.95,-443 1479.95,-400.53 1479.95,-400.53"/>
|
||||||
|
<polygon fill="#455a64" stroke="#455a64" points="1483.45,-400.53 1479.95,-390.53 1476.45,-400.53 1483.45,-400.53"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1433.25" y="-434.7" font-family="Helvetica,sans-Serif" font-size="9.00">CRUD /cart/</text>
|
||||||
|
</g>
|
||||||
|
<!-- redux_store -->
|
||||||
|
<g id="node30" class="node">
|
||||||
|
<title>redux_store</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M2228.93,-496.8C2228.93,-496.8 2169.07,-496.8 2169.07,-496.8 2163.07,-496.8 2157.07,-490.8 2157.07,-484.8 2157.07,-484.8 2157.07,-472.8 2157.07,-472.8 2157.07,-466.8 2163.07,-460.8 2169.07,-460.8 2169.07,-460.8 2228.93,-460.8 2228.93,-460.8 2234.93,-460.8 2240.93,-466.8 2240.93,-472.8 2240.93,-472.8 2240.93,-484.8 2240.93,-484.8 2240.93,-490.8 2234.93,-496.8 2228.93,-496.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2199" y="-482.1" font-family="Helvetica,sans-Serif" font-size="11.00">Redux Store</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2199" y="-468.9" font-family="Helvetica,sans-Serif" font-size="11.00">(Global State)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_slice -->
|
||||||
|
<g id="node31" class="node">
|
||||||
|
<title>auth_slice</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M2092.87,-611.12C2092.87,-611.12 2039.13,-611.12 2039.13,-611.12 2033.13,-611.12 2027.13,-605.12 2027.13,-599.12 2027.13,-599.12 2027.13,-587.12 2027.13,-587.12 2027.13,-581.12 2033.13,-575.12 2039.13,-575.12 2039.13,-575.12 2092.87,-575.12 2092.87,-575.12 2098.87,-575.12 2104.87,-581.12 2104.87,-587.12 2104.87,-587.12 2104.87,-599.12 2104.87,-599.12 2104.87,-605.12 2098.87,-611.12 2092.87,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2066" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">Auth Slice</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2066" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(user, token)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_slice->redux_store -->
|
||||||
|
<g id="edge31" class="edge">
|
||||||
|
<title>auth_slice->redux_store</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M2066,-574.79C2066,-542.53 2066,-479 2066,-479 2066,-479 2145.4,-479 2145.4,-479"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="2145.4,-482.5 2155.4,-479 2145.4,-475.5 2145.4,-482.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- visits_slice -->
|
||||||
|
<g id="node32" class="node">
|
||||||
|
<title>visits_slice</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M2179.59,-611.12C2179.59,-611.12 2134.41,-611.12 2134.41,-611.12 2128.41,-611.12 2122.41,-605.12 2122.41,-599.12 2122.41,-599.12 2122.41,-587.12 2122.41,-587.12 2122.41,-581.12 2128.41,-575.12 2134.41,-575.12 2134.41,-575.12 2179.59,-575.12 2179.59,-575.12 2185.59,-575.12 2191.59,-581.12 2191.59,-587.12 2191.59,-587.12 2191.59,-599.12 2191.59,-599.12 2191.59,-605.12 2185.59,-611.12 2179.59,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2157" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">Visits Slice</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2157" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(visit data)</text>
|
||||||
|
</g>
|
||||||
|
<!-- visits_slice->redux_store -->
|
||||||
|
<g id="edge32" class="edge">
|
||||||
|
<title>visits_slice->redux_store</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M2174.33,-574.76C2174.33,-574.76 2174.33,-508.64 2174.33,-508.64"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="2177.83,-508.64 2174.33,-498.64 2170.83,-508.64 2177.83,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- cart_slice -->
|
||||||
|
<g id="node33" class="node">
|
||||||
|
<title>cart_slice</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M2260.84,-611.12C2260.84,-611.12 2221.16,-611.12 2221.16,-611.12 2215.16,-611.12 2209.16,-605.12 2209.16,-599.12 2209.16,-599.12 2209.16,-587.12 2209.16,-587.12 2209.16,-581.12 2215.16,-575.12 2221.16,-575.12 2221.16,-575.12 2260.84,-575.12 2260.84,-575.12 2266.84,-575.12 2272.84,-581.12 2272.84,-587.12 2272.84,-587.12 2272.84,-599.12 2272.84,-599.12 2272.84,-605.12 2266.84,-611.12 2260.84,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2241" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">Cart Slice</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2241" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(items)</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart_slice->redux_store -->
|
||||||
|
<g id="edge33" class="edge">
|
||||||
|
<title>cart_slice->redux_store</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M2225.05,-574.76C2225.05,-574.76 2225.05,-508.64 2225.05,-508.64"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="2228.55,-508.64 2225.05,-498.64 2221.55,-508.64 2228.55,-508.64"/>
|
||||||
|
</g>
|
||||||
|
<!-- auth_context -->
|
||||||
|
<g id="node34" class="node">
|
||||||
|
<title>auth_context</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M2355.27,-611.12C2355.27,-611.12 2302.73,-611.12 2302.73,-611.12 2296.73,-611.12 2290.73,-605.12 2290.73,-599.12 2290.73,-599.12 2290.73,-587.12 2290.73,-587.12 2290.73,-581.12 2296.73,-575.12 2302.73,-575.12 2302.73,-575.12 2355.27,-575.12 2355.27,-575.12 2361.27,-575.12 2367.27,-581.12 2367.27,-587.12 2367.27,-587.12 2367.27,-599.12 2367.27,-599.12 2367.27,-605.12 2361.27,-611.12 2355.27,-611.12"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2329" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">AuthContext</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2329" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(Provider)</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_context->redux_store -->
|
||||||
|
<g id="edge34" class="edge">
|
||||||
|
<title>auth_context->redux_store</title>
|
||||||
|
<path fill="none" stroke="#00838f" stroke-dasharray="5,2" d="M2329,-574.79C2329,-542.53 2329,-479 2329,-479 2329,-479 2252.72,-479 2252.72,-479"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="2252.72,-475.5 2242.72,-479 2252.72,-482.5 2252.72,-475.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- api_payments -->
|
||||||
|
<g id="node39" class="node">
|
||||||
|
<title>api_payments</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1883.91,-388.8C1883.91,-388.8 1816.09,-388.8 1816.09,-388.8 1810.09,-388.8 1804.09,-382.8 1804.09,-376.8 1804.09,-376.8 1804.09,-364.8 1804.09,-364.8 1804.09,-358.8 1810.09,-352.8 1816.09,-352.8 1816.09,-352.8 1883.91,-352.8 1883.91,-352.8 1889.91,-352.8 1895.91,-358.8 1895.91,-364.8 1895.91,-364.8 1895.91,-376.8 1895.91,-376.8 1895.91,-382.8 1889.91,-388.8 1883.91,-388.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1850" y="-374.1" font-family="Helvetica,sans-Serif" font-size="11.00">/payments/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1850" y="-360.9" font-family="Helvetica,sans-Serif" font-size="11.00">(MercadoPago)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner_user -->
|
||||||
|
<g id="node40" class="node">
|
||||||
|
<title>petowner_user</title>
|
||||||
|
<ellipse fill="#f8bbd9" stroke="black" cx="866" cy="-593.12" rx="45.9" ry="24.32"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="866" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">PetOwner</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="866" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(Cliente)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner_user->home -->
|
||||||
|
<g id="edge35" class="edge">
|
||||||
|
<title>petowner_user->home</title>
|
||||||
|
<path fill="none" stroke="#ad1457" d="M827.6,-579.22C827.6,-551.07 827.6,-490 827.6,-490 827.6,-490 466.04,-490 466.04,-490"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="466.04,-486.5 456.04,-490 466.04,-493.5 466.04,-486.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="830.01" y="-542.7" font-family="Helvetica,sans-Serif" font-size="9.00">public access</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner_user->profile_page -->
|
||||||
|
<g id="edge36" class="edge">
|
||||||
|
<title>petowner_user->profile_page</title>
|
||||||
|
<path fill="none" stroke="#ad1457" stroke-dasharray="5,2" d="M819.7,-593C733.92,-593 558.94,-593 558.94,-593 558.94,-593 558.94,-107 558.94,-107 558.94,-107 462.46,-107 462.46,-107"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="462.46,-103.5 452.46,-107 462.46,-110.5 462.46,-103.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1154.02" y="-326.7" font-family="Helvetica,sans-Serif" font-size="9.00">authenticated</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_user -->
|
||||||
|
<g id="node41" class="node">
|
||||||
|
<title>vet_user</title>
|
||||||
|
<ellipse fill="#f8bbd9" stroke="black" cx="985" cy="-593.12" rx="55.41" ry="24.32"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="985" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">Veterinarian</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="985" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(Profesional)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_user->admin_dash -->
|
||||||
|
<g id="edge37" class="edge">
|
||||||
|
<title>vet_user->admin_dash</title>
|
||||||
|
<path fill="none" stroke="#ad1457" d="M954.15,-572.61C954.15,-542.01 954.15,-488 954.15,-488 954.15,-488 916.71,-488 916.71,-488"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="916.71,-484.5 906.71,-488 916.71,-491.5 916.71,-484.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="921.26" y="-542.7" font-family="Helvetica,sans-Serif" font-size="9.00">backoffice</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_user -->
|
||||||
|
<g id="node42" class="node">
|
||||||
|
<title>admin_user</title>
|
||||||
|
<ellipse fill="#f8bbd9" stroke="black" cx="1110" cy="-593.12" rx="51.52" ry="24.32"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1110" y="-596.42" font-family="Helvetica,sans-Serif" font-size="11.00">Admin/Staff</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1110" y="-583.22" font-family="Helvetica,sans-Serif" font-size="11.00">(Interno)</text>
|
||||||
|
</g>
|
||||||
|
<!-- admin_user->admin_dash -->
|
||||||
|
<g id="edge38" class="edge">
|
||||||
|
<title>admin_user->admin_dash</title>
|
||||||
|
<path fill="none" stroke="#ad1457" d="M1078.95,-573.25C1078.95,-540.55 1078.95,-479 1078.95,-479 1078.95,-479 916.49,-479 916.49,-479"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="916.49,-475.5 906.49,-479 916.49,-482.5 916.49,-475.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1181.01" y="-542.7" font-family="Helvetica,sans-Serif" font-size="9.00">full access</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 51 KiB |
234
atlas/book/arch-model/03-data-model.dot
Normal file
234
atlas/book/arch-model/03-data-model.dot
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
976
atlas/book/arch-model/03-data-model.svg
Normal file
976
atlas/book/arch-model/03-data-model.svg
Normal file
@@ -0,0 +1,976 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 14.0.5 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: DataModel Pages: 1 -->
|
||||||
|
<svg width="2547pt" height="1691pt"
|
||||||
|
viewBox="0.00 0.00 2547.00 1691.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1687.2)">
|
||||||
|
<title>DataModel</title>
|
||||||
|
<polygon fill="white" stroke="none" points="-4,4 -4,-1687.2 2543,-1687.2 2543,4 -4,4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1269.5" y="-1664" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AMAR Mascotas - Data Model (Entity Relationships)</text>
|
||||||
|
<g id="clust1" class="cluster">
|
||||||
|
<title>cluster_auth</title>
|
||||||
|
<path fill="#e8f5e9" stroke="#2e7d32" d="M550,-1367.6C550,-1367.6 703,-1367.6 703,-1367.6 709,-1367.6 715,-1373.6 715,-1379.6 715,-1379.6 715,-1515 715,-1515 715,-1521 709,-1527 703,-1527 703,-1527 550,-1527 550,-1527 544,-1527 538,-1521 538,-1515 538,-1515 538,-1379.6 538,-1379.6 538,-1373.6 544,-1367.6 550,-1367.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="626.5" y="-1507.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Users & Authentication</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster_mascotas_owners</title>
|
||||||
|
<path fill="#e3f2fd" stroke="#1565c0" d="M482,-494.2C482,-494.2 678,-494.2 678,-494.2 684,-494.2 690,-500.2 690,-506.2 690,-506.2 690,-1192.4 690,-1192.4 690,-1198.4 684,-1204.4 678,-1204.4 678,-1204.4 482,-1204.4 482,-1204.4 476,-1204.4 470,-1198.4 470,-1192.4 470,-1192.4 470,-506.2 470,-506.2 470,-500.2 476,-494.2 482,-494.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="580" y="-1185.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Pet Owners & Pets</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust3" class="cluster">
|
||||||
|
<title>cluster_veterinarians</title>
|
||||||
|
<path fill="#fff3e0" stroke="#e65100" d="M806,-317.6C806,-317.6 1867,-317.6 1867,-317.6 1873,-317.6 1879,-323.6 1879,-329.6 1879,-329.6 1879,-647.6 1879,-647.6 1879,-653.6 1873,-659.6 1867,-659.6 1867,-659.6 806,-659.6 806,-659.6 800,-659.6 794,-653.6 794,-647.6 794,-647.6 794,-329.6 794,-329.6 794,-323.6 800,-317.6 806,-317.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1336.5" y="-640.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Veterinarians</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_productos</title>
|
||||||
|
<path fill="#f3e5f5" stroke="#7b1fa2" d="M67,-476.2C67,-476.2 318,-476.2 318,-476.2 324,-476.2 330,-482.2 330,-488.2 330,-488.2 330,-1515 330,-1515 330,-1521 324,-1527 318,-1527 318,-1527 67,-1527 67,-1527 61,-1527 55,-1521 55,-1515 55,-1515 55,-488.2 55,-488.2 55,-482.2 61,-476.2 67,-476.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="192.5" y="-1507.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Services & Pricing</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust5" class="cluster">
|
||||||
|
<title>cluster_cart</title>
|
||||||
|
<path fill="#e0f7fa" stroke="#00838f" d="M740,-991C740,-991 1066,-991 1066,-991 1072,-991 1078,-997 1078,-1003 1078,-1003 1078,-1515 1078,-1515 1078,-1521 1072,-1527 1066,-1527 1066,-1527 740,-1527 740,-1527 734,-1527 728,-1521 728,-1515 728,-1515 728,-1003 728,-1003 728,-997 734,-991 740,-991"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="903" y="-1507.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Cart & Checkout</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_solicitudes</title>
|
||||||
|
<path fill="#ffebee" stroke="#c62828" d="M1899,-494.2C1899,-494.2 2297,-494.2 2297,-494.2 2303,-494.2 2309,-500.2 2309,-506.2 2309,-506.2 2309,-1216.4 2309,-1216.4 2309,-1222.4 2303,-1228.4 2297,-1228.4 2297,-1228.4 1899,-1228.4 1899,-1228.4 1893,-1228.4 1887,-1222.4 1887,-1216.4 1887,-1216.4 1887,-506.2 1887,-506.2 1887,-500.2 1893,-494.2 1899,-494.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2098" y="-1209.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Service Requests (Workflow)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust7" class="cluster">
|
||||||
|
<title>cluster_vetvisits</title>
|
||||||
|
<path fill="#fffde7" stroke="#f9a825" d="M1186,-762.8C1186,-762.8 1726,-762.8 1726,-762.8 1732,-762.8 1738,-768.8 1738,-774.8 1738,-774.8 1738,-1617 1738,-1617 1738,-1623 1732,-1629 1726,-1629 1726,-1629 1186,-1629 1186,-1629 1180,-1629 1174,-1623 1174,-1617 1174,-1617 1174,-774.8 1174,-774.8 1174,-768.8 1180,-762.8 1186,-762.8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1456" y="-1609.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Veterinary Visits</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust8" class="cluster">
|
||||||
|
<title>cluster_reference</title>
|
||||||
|
<path fill="#eceff1" stroke="#455a64" d="M20,-8C20,-8 1189,-8 1189,-8 1195,-8 1201,-14 1201,-20 1201,-20 1201,-278 1201,-278 1201,-284 1195,-290 1189,-290 1189,-290 20,-290 20,-290 14,-290 8,-284 8,-278 8,-278 8,-20 8,-20 8,-14 14,-8 20,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="604.5" y="-270.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reference Data</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust9" class="cluster">
|
||||||
|
<title>cluster_afip</title>
|
||||||
|
<path fill="#fce4ec" stroke="#ad1457" d="M2399,-997C2399,-997 2519,-997 2519,-997 2525,-997 2531,-1003 2531,-1009 2531,-1009 2531,-1180.4 2531,-1180.4 2531,-1186.4 2525,-1192.4 2519,-1192.4 2519,-1192.4 2399,-1192.4 2399,-1192.4 2393,-1192.4 2387,-1186.4 2387,-1180.4 2387,-1180.4 2387,-1009 2387,-1009 2387,-1003 2393,-997 2399,-997"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2459" y="-1173.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AFIP Invoicing</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>auth_user</title>
|
||||||
|
<polygon fill="#c8e6c9" stroke="black" points="604.14,-1376.1 604.14,-1464.1 701.86,-1464.1 701.86,-1376.1 604.14,-1376.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="653" y="-1451.1" font-family="Helvetica,sans-Serif" font-size="10.00">auth.User</text>
|
||||||
|
<polyline fill="none" stroke="black" points="604.14,-1444.1 701.86,-1444.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="612.14" y="-1431.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="612.14" y="-1419.1" font-family="Helvetica,sans-Serif" font-size="10.00">user name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="612.14" y="-1407.1" font-family="Helvetica,sans-Serif" font-size="10.00">email: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="612.14" y="-1395.1" font-family="Helvetica,sans-Serif" font-size="10.00">is_staff: bool</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="612.14" y="-1383.1" font-family="Helvetica,sans-Serif" font-size="10.00">is_superuser: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>petowner</title>
|
||||||
|
<polygon fill="#bbdefb" stroke="black" points="565.81,-993.5 565.81,-1141.5 680.19,-1141.5 680.19,-993.5 565.81,-993.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="623" y="-1128.5" font-family="Helvetica,sans-Serif" font-size="10.00">PetOwner</text>
|
||||||
|
<polyline fill="none" stroke="black" points="565.81,-1121.5 680.19,-1121.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1108.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1096.5" font-family="Helvetica,sans-Serif" font-size="10.00">email: str (unique)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1084.5" font-family="Helvetica,sans-Serif" font-size="10.00">phone: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00">first_name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00">last_name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">neighborhood_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">campaign_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1024.5" font-family="Helvetica,sans-Serif" font-size="10.00">geo_latitude: float</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1012.5" font-family="Helvetica,sans-Serif" font-size="10.00">geo_longitude: float</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="573.81" y="-1000.5" font-family="Helvetica,sans-Serif" font-size="10.00">address: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->petowner -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>auth_user->petowner</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" stroke-dasharray="5,2" d="M642.17,-1375.8C642.17,-1375.8 642.17,-1153.26 642.17,-1153.26"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="645.67,-1153.26 642.17,-1143.26 638.67,-1153.26 645.67,-1153.26"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="645.23" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>veterinarian</title>
|
||||||
|
<polygon fill="#ffe0b2" stroke="black" points="1538.04,-508.7 1538.04,-596.7 1607.96,-596.7 1607.96,-508.7 1538.04,-508.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1573" y="-583.7" font-family="Helvetica,sans-Serif" font-size="10.00">Veterinarian</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1538.04,-576.7 1607.96,-576.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1546.04" y="-563.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1546.04" y="-551.7" font-family="Helvetica,sans-Serif" font-size="10.00">user_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1546.04" y="-539.7" font-family="Helvetica,sans-Serif" font-size="10.00">license: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1546.04" y="-527.7" font-family="Helvetica,sans-Serif" font-size="10.00">email: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1546.04" y="-515.7" font-family="Helvetica,sans-Serif" font-size="10.00">phone: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->veterinarian -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>auth_user->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#2e7d32" d="M695.21,-1375.77C695.21,-1200.06 695.21,-562 695.21,-562 695.21,-562 1526.26,-562 1526.26,-562"/>
|
||||||
|
<polygon fill="#2e7d32" stroke="#2e7d32" points="1526.26,-565.5 1536.26,-562 1526.26,-558.5 1526.26,-565.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="787.56" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>pet</title>
|
||||||
|
<polygon fill="#bbdefb" stroke="black" points="574.97,-705.3 574.97,-913.3 681.03,-913.3 681.03,-705.3 574.97,-705.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="628" y="-900.3" font-family="Helvetica,sans-Serif" font-size="10.00">Pet</text>
|
||||||
|
<polyline fill="none" stroke="black" points="574.97,-893.3 681.03,-893.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-880.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-868.3" font-family="Helvetica,sans-Serif" font-size="10.00">owner_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-856.3" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-844.3" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type: DOG/CAT</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-832.3" font-family="Helvetica,sans-Serif" font-size="10.00">breed_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-820.3" font-family="Helvetica,sans-Serif" font-size="10.00">gender: M/F</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-808.3" font-family="Helvetica,sans-Serif" font-size="10.00">age: int</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-796.3" font-family="Helvetica,sans-Serif" font-size="10.00">weight: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-784.3" font-family="Helvetica,sans-Serif" font-size="10.00">height: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-772.3" font-family="Helvetica,sans-Serif" font-size="10.00">birth_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-760.3" font-family="Helvetica,sans-Serif" font-size="10.00">allergies: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-748.3" font-family="Helvetica,sans-Serif" font-size="10.00">neutered: bool</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-736.3" font-family="Helvetica,sans-Serif" font-size="10.00">is_deceased: bool</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-724.3" font-family="Helvetica,sans-Serif" font-size="10.00">state: puppy/adult/...</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="582.97" y="-712.3" font-family="Helvetica,sans-Serif" font-size="10.00">profile_picture: file</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->pet -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>petowner->pet</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M627.58,-993.16C627.58,-993.16 627.58,-924.87 627.58,-924.87"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="631.08,-924.87 627.58,-914.87 624.08,-924.87 631.08,-924.87"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="588.67" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N owns</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart -->
|
||||||
|
<g id="node17" class="node">
|
||||||
|
<title>cart</title>
|
||||||
|
<polygon fill="#b2ebf2" stroke="black" points="906.36,-1376.1 906.36,-1464.1 1029.64,-1464.1 1029.64,-1376.1 906.36,-1376.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="968" y="-1451.1" font-family="Helvetica,sans-Serif" font-size="10.00">Cart</text>
|
||||||
|
<polyline fill="none" stroke="black" points="906.36,-1444.1 1029.64,-1444.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="914.36" y="-1431.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="914.36" y="-1419.1" font-family="Helvetica,sans-Serif" font-size="10.00">petowner_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="914.36" y="-1407.1" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="914.36" y="-1395.1" font-family="Helvetica,sans-Serif" font-size="10.00">use_vet_prices: bool</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="914.36" y="-1383.1" font-family="Helvetica,sans-Serif" font-size="10.00">apply_turn_fee: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->cart -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>petowner->cart</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M680.65,-1130C765.7,-1130 914.82,-1130 914.82,-1130 914.82,-1130 914.82,-1364.21 914.82,-1364.21"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="911.32,-1364.21 914.82,-1374.21 918.32,-1364.21 911.32,-1364.21"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="790.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest -->
|
||||||
|
<g id="node21" class="node">
|
||||||
|
<title>servicerequest</title>
|
||||||
|
<polygon fill="#ffcdd2" stroke="black" points="1895.4,-969.5 1895.4,-1165.5 2032.6,-1165.5 2032.6,-969.5 1895.4,-969.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1964" y="-1152.5" font-family="Helvetica,sans-Serif" font-size="10.00">ServiceRequest</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1895.4,-1145.5 2032.6,-1145.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1132.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1120.5" font-family="Helvetica,sans-Serif" font-size="10.00">petowner_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1108.5" font-family="Helvetica,sans-Serif" font-size="10.00">cart_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1096.5" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1084.5" font-family="Helvetica,sans-Serif" font-size="10.00">state: pending/vet_asked/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00"> vet_accepted/coordinated/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00"> payed/Confirmado/...</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">reason: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">days_requested: JSON</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1024.5" font-family="Helvetica,sans-Serif" font-size="10.00">date_coordinated: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1012.5" font-family="Helvetica,sans-Serif" font-size="10.00">hour_coordinated: time</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-1000.5" font-family="Helvetica,sans-Serif" font-size="10.00">pay_number: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-988.5" font-family="Helvetica,sans-Serif" font-size="10.00">campaign_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1903.4" y="-976.5" font-family="Helvetica,sans-Serif" font-size="10.00">attended_by_id: FK (opt)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->servicerequest -->
|
||||||
|
<g id="edge7" class="edge">
|
||||||
|
<title>petowner->servicerequest</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M623.15,-1141.95C623.15,-1145.73 623.15,-1148 623.15,-1148 623.15,-1148 1883.69,-1148 1883.69,-1148"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1883.69,-1151.5 1893.69,-1148 1883.69,-1144.5 1883.69,-1151.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1109" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N requests</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit -->
|
||||||
|
<g id="node26" class="node">
|
||||||
|
<title>vetvisit</title>
|
||||||
|
<polygon fill="#fff9c4" stroke="black" points="1588.19,-1274.1 1588.19,-1566.1 1729.81,-1566.1 1729.81,-1274.1 1588.19,-1274.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1659" y="-1553.1" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisit</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1588.19,-1546.1 1729.81,-1546.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1533.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1521.1" font-family="Helvetica,sans-Serif" font-size="10.00">service_request_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1509.1" font-family="Helvetica,sans-Serif" font-size="10.00">owner_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1497.1" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1485.1" font-family="Helvetica,sans-Serif" font-size="10.00">date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1473.1" font-family="Helvetica,sans-Serif" font-size="10.00">hour: time</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1461.1" font-family="Helvetica,sans-Serif" font-size="10.00">visit_type: clinical/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1449.1" font-family="Helvetica,sans-Serif" font-size="10.00"> vaccination/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1437.1" font-family="Helvetica,sans-Serif" font-size="10.00"> telemedicina</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1425.1" font-family="Helvetica,sans-Serif" font-size="10.00">visit_state: PENDING/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1413.1" font-family="Helvetica,sans-Serif" font-size="10.00"> IN_PROGRESS/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1401.1" font-family="Helvetica,sans-Serif" font-size="10.00"> COMPLETED/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1389.1" font-family="Helvetica,sans-Serif" font-size="10.00"> NO_REPORT/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1377.1" font-family="Helvetica,sans-Serif" font-size="10.00"> CANCELLED</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1365.1" font-family="Helvetica,sans-Serif" font-size="10.00">reason: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1353.1" font-family="Helvetica,sans-Serif" font-size="10.00">observations: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1341.1" font-family="Helvetica,sans-Serif" font-size="10.00">price: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1329.1" font-family="Helvetica,sans-Serif" font-size="10.00">deposit: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1317.1" font-family="Helvetica,sans-Serif" font-size="10.00">vet_fee: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1305.1" font-family="Helvetica,sans-Serif" font-size="10.00">pay_transaction: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1293.1" font-family="Helvetica,sans-Serif" font-size="10.00">google_event_id: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1596.19" y="-1281.1" font-family="Helvetica,sans-Serif" font-size="10.00">afip_receipt_id: FK (opt)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->vetvisit -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>petowner->vetvisit</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M661.18,-1141.78C661.18,-1223.08 661.18,-1342 661.18,-1342 661.18,-1342 1576.53,-1342 1576.53,-1342"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1576.53,-1345.5 1586.53,-1342 1576.53,-1338.5 1576.53,-1345.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1249.57" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N as owner</text>
|
||||||
|
</g>
|
||||||
|
<!-- neighborhood -->
|
||||||
|
<g id="node32" class="node">
|
||||||
|
<title>neighborhood</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="139.79,-139.1 139.79,-215.1 284.21,-215.1 284.21,-139.1 139.79,-139.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="212" y="-202.1" font-family="Helvetica,sans-Serif" font-size="10.00">Neighborhood</text>
|
||||||
|
<polyline fill="none" stroke="black" points="139.79,-195.1 284.21,-195.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="147.79" y="-182.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="147.79" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="147.79" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">distance_coefficient: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="147.79" y="-146.1" font-family="Helvetica,sans-Serif" font-size="10.00">coverage_area: GIS Polygon</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->neighborhood -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>petowner->neighborhood</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M565.5,-1008C489.96,-1008 366.79,-1008 366.79,-1008 366.79,-1008 366.79,-210 366.79,-210 366.79,-210 295.92,-210 295.92,-210"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="295.92,-206.5 285.92,-210 295.92,-213.5 295.92,-206.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="25.56" y="-550.3" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 lives in</text>
|
||||||
|
</g>
|
||||||
|
<!-- campaign -->
|
||||||
|
<g id="node38" class="node">
|
||||||
|
<title>campaign</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="828.77,-133.1 828.77,-221.1 919.23,-221.1 919.23,-133.1 828.77,-133.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="874" y="-208.1" font-family="Helvetica,sans-Serif" font-size="10.00">Campaign</text>
|
||||||
|
<polyline fill="none" stroke="black" points="828.77,-201.1 919.23,-201.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="836.77" y="-188.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="836.77" y="-176.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="836.77" y="-164.1" font-family="Helvetica,sans-Serif" font-size="10.00">utm_source: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="836.77" y="-152.1" font-family="Helvetica,sans-Serif" font-size="10.00">utm_medium: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="836.77" y="-140.1" font-family="Helvetica,sans-Serif" font-size="10.00">is_active: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->campaign -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>petowner->campaign</title>
|
||||||
|
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M680.53,-1005C685.43,-1005 688.57,-1005 688.57,-1005 688.57,-1005 688.57,-210 688.57,-210 688.57,-210 817.13,-210 817.13,-210"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="817.13,-213.5 827.13,-210 817.13,-206.5 817.13,-213.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="408.9" y="-550.3" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- petvaccine -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>petvaccine</title>
|
||||||
|
<polygon fill="#90caf9" stroke="black" points="568.07,-508.7 568.07,-596.7 681.93,-596.7 681.93,-508.7 568.07,-508.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="625" y="-583.7" font-family="Helvetica,sans-Serif" font-size="10.00">PetVaccine</text>
|
||||||
|
<polyline fill="none" stroke="black" points="568.07,-576.7 681.93,-576.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="576.07" y="-563.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="576.07" y="-551.7" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="576.07" y="-539.7" font-family="Helvetica,sans-Serif" font-size="10.00">vaccine_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="576.07" y="-527.7" font-family="Helvetica,sans-Serif" font-size="10.00">application_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="576.07" y="-515.7" font-family="Helvetica,sans-Serif" font-size="10.00">next_application: date</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->petvaccine -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>pet->petvaccine</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M628,-704.93C628,-704.93 628,-608.65 628,-608.65"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="631.5,-608.65 628,-598.65 624.5,-608.65 631.5,-608.65"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="633.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- petstudy -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>petstudy</title>
|
||||||
|
<polygon fill="#90caf9" stroke="black" points="478.21,-502.7 478.21,-602.7 549.79,-602.7 549.79,-502.7 478.21,-502.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="514" y="-589.7" font-family="Helvetica,sans-Serif" font-size="10.00">PetStudy</text>
|
||||||
|
<polyline fill="none" stroke="black" points="478.21,-582.7 549.79,-582.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-569.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-557.7" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-545.7" font-family="Helvetica,sans-Serif" font-size="10.00">study_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-533.7" font-family="Helvetica,sans-Serif" font-size="10.00">date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-521.7" font-family="Helvetica,sans-Serif" font-size="10.00">result: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="486.21" y="-509.7" font-family="Helvetica,sans-Serif" font-size="10.00">images: files</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->petstudy -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>pet->petstudy</title>
|
||||||
|
<path fill="none" stroke="#1565c0" d="M574.67,-713C545,-713 514,-713 514,-713 514,-713 514,-614.49 514,-614.49"/>
|
||||||
|
<polygon fill="#1565c0" stroke="#1565c0" points="517.5,-614.49 514,-604.49 510.5,-614.49 517.5,-614.49"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="558.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- petbreed -->
|
||||||
|
<g id="node35" class="node">
|
||||||
|
<title>petbreed</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="16.26,-145.1 16.26,-209.1 121.74,-209.1 121.74,-145.1 16.26,-145.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="69" y="-196.1" font-family="Helvetica,sans-Serif" font-size="10.00">PetBreed</text>
|
||||||
|
<polyline fill="none" stroke="black" points="16.26,-189.1 121.74,-189.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="24.26" y="-176.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="24.26" y="-164.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="24.26" y="-152.1" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type: DOG/CAT</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->petbreed -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>pet->petbreed</title>
|
||||||
|
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M574.5,-721C429.5,-721 39.78,-721 39.78,-721 39.78,-721 39.78,-221.01 39.78,-221.01"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="43.28,-221.01 39.78,-211.01 36.28,-221.01 43.28,-221.01"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="457.9" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- vaccine -->
|
||||||
|
<g id="node36" class="node">
|
||||||
|
<title>vaccine</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="501.32,-139.1 501.32,-215.1 620.68,-215.1 620.68,-139.1 501.32,-139.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="561" y="-202.1" font-family="Helvetica,sans-Serif" font-size="10.00">Vaccine</text>
|
||||||
|
<polyline fill="none" stroke="black" points="501.32,-195.1 620.68,-195.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="509.32" y="-182.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="509.32" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="509.32" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type: DOG/CAT</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="509.32" y="-146.1" font-family="Helvetica,sans-Serif" font-size="10.00">periodicity: int (months)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petvaccine->vaccine -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>petvaccine->vaccine</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M594.38,-508.51C594.38,-508.51 594.38,-226.98 594.38,-226.98"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="597.88,-226.98 594.38,-216.98 590.88,-226.98 597.88,-226.98"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="581.22" y="-379.7" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- study -->
|
||||||
|
<g id="node37" class="node">
|
||||||
|
<title>study</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="378.26,-139.1 378.26,-215.1 483.74,-215.1 483.74,-139.1 378.26,-139.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="431" y="-202.1" font-family="Helvetica,sans-Serif" font-size="10.00">Study</text>
|
||||||
|
<polyline fill="none" stroke="black" points="378.26,-195.1 483.74,-195.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="386.26" y="-182.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="386.26" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="386.26" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type: DOG/CAT</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="386.26" y="-146.1" font-family="Helvetica,sans-Serif" font-size="10.00">group_id: FK (opt)</text>
|
||||||
|
</g>
|
||||||
|
<!-- petstudy->study -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>petstudy->study</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M492.53,-502.42C492.53,-400.05 492.53,-177 492.53,-177 492.53,-177 491.7,-177 491.7,-177"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="495.74,-173.5 485.74,-177 495.74,-180.5 495.74,-173.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="495.22" y="-379.7" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- availability -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>availability</title>
|
||||||
|
<polygon fill="#ffcc80" stroke="black" points="1653.03,-332.1 1653.03,-432.1 1752.97,-432.1 1752.97,-332.1 1653.03,-332.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1703" y="-419.1" font-family="Helvetica,sans-Serif" font-size="10.00">Availability</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1653.03,-412.1 1752.97,-412.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-399.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-387.1" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-375.1" font-family="Helvetica,sans-Serif" font-size="10.00">start_day: 0-6</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-363.1" font-family="Helvetica,sans-Serif" font-size="10.00">end_day: 0-6</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-351.1" font-family="Helvetica,sans-Serif" font-size="10.00">start_time: time</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1661.03" y="-339.1" font-family="Helvetica,sans-Serif" font-size="10.00">end_time: time</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->availability -->
|
||||||
|
<g id="edge18" class="edge">
|
||||||
|
<title>veterinarian->availability</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M1608.25,-530C1642.2,-530 1688.41,-530 1688.41,-530 1688.41,-530 1688.41,-444.1 1688.41,-444.1"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1691.91,-444.1 1688.41,-434.1 1684.91,-444.1 1691.91,-444.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1653.22" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- unavailability -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>unavailability</title>
|
||||||
|
<polygon fill="#ffcc80" stroke="black" points="1771.03,-326.1 1771.03,-438.1 1870.97,-438.1 1870.97,-326.1 1771.03,-326.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1821" y="-425.1" font-family="Helvetica,sans-Serif" font-size="10.00">Unavailability</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1771.03,-418.1 1870.97,-418.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-405.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-393.1" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-381.1" font-family="Helvetica,sans-Serif" font-size="10.00">start_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-369.1" font-family="Helvetica,sans-Serif" font-size="10.00">end_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-357.1" font-family="Helvetica,sans-Serif" font-size="10.00">start_time: time</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-345.1" font-family="Helvetica,sans-Serif" font-size="10.00">end_time: time</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1779.03" y="-333.1" font-family="Helvetica,sans-Serif" font-size="10.00">reason: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->unavailability -->
|
||||||
|
<g id="edge19" class="edge">
|
||||||
|
<title>veterinarian->unavailability</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M1608.27,-553C1676.74,-553 1821,-553 1821,-553 1821,-553 1821,-450.05 1821,-450.05"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1824.5,-450.05 1821,-440.05 1817.5,-450.05 1824.5,-450.05"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1729.22" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_specialty -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>vet_specialty</title>
|
||||||
|
<polygon fill="#ffb74d" stroke="black" points="1442,-426.1 1249.44,-382.1 1442,-338.1 1634.56,-382.1 1442,-426.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1283.24" y="-391.1" font-family="Helvetica,sans-Serif" font-size="10.00">{M2M: Vet-Specialty|veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1283.24" y="-379.1" font-family="Helvetica,sans-Serif" font-size="10.00">specialty_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1442" y="-367.1" font-family="Helvetica,sans-Serif" font-size="10.00">}</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vet_specialty -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>veterinarian->vet_specialty</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M1584.65,-508.32C1584.65,-508.32 1584.65,-405.5 1584.65,-405.5"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1588.15,-405.5 1584.65,-395.5 1581.15,-405.5 1588.15,-405.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1505.22" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_neighborhood -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>vet_neighborhood</title>
|
||||||
|
<polygon fill="#ffb74d" stroke="black" points="1017,-426.1 802.19,-382.1 1017,-338.1 1231.81,-382.1 1017,-426.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="838.97" y="-391.1" font-family="Helvetica,sans-Serif" font-size="10.00">{M2M: Vet-Neighborhood|veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="838.97" y="-379.1" font-family="Helvetica,sans-Serif" font-size="10.00">neighborhood_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1017" y="-367.1" font-family="Helvetica,sans-Serif" font-size="10.00">}</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vet_neighborhood -->
|
||||||
|
<g id="edge16" class="edge">
|
||||||
|
<title>veterinarian->vet_neighborhood</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M1537.71,-544C1447.43,-544 1215.15,-544 1215.15,-544 1215.15,-544 1215.15,-397.3 1215.15,-397.3"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1218.65,-397.3 1215.15,-387.3 1211.65,-397.3 1218.65,-397.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1273.79" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">1:N coverage</text>
|
||||||
|
</g>
|
||||||
|
<!-- turnfeegroup -->
|
||||||
|
<g id="node41" class="node">
|
||||||
|
<title>turnfeegroup</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="1067.24,-145.1 1067.24,-209.1 1192.76,-209.1 1192.76,-145.1 1067.24,-145.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1130" y="-196.1" font-family="Helvetica,sans-Serif" font-size="10.00">IndividualTurnFeeGroup</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1067.24,-189.1 1192.76,-189.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1075.24" y="-176.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1075.24" y="-164.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1075.24" y="-152.1" font-family="Helvetica,sans-Serif" font-size="10.00">fee_percentage: decimal</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->turnfeegroup -->
|
||||||
|
<g id="edge20" class="edge">
|
||||||
|
<title>veterinarian->turnfeegroup</title>
|
||||||
|
<path fill="none" stroke="#e65100" stroke-dasharray="5,2" d="M1537.74,-526C1452.2,-526 1240.63,-526 1240.63,-526 1240.63,-526 1240.63,-177 1240.63,-177 1240.63,-177 1204.58,-177 1204.58,-177"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="1204.58,-173.5 1194.58,-177 1204.58,-180.5 1204.58,-173.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="777.33" y="-379.7" font-family="Helvetica,sans-Serif" font-size="8.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- specialty -->
|
||||||
|
<g id="node31" class="node">
|
||||||
|
<title>specialty</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="753.16,-151.1 753.16,-203.1 810.84,-203.1 810.84,-151.1 753.16,-151.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="782" y="-190.1" font-family="Helvetica,sans-Serif" font-size="10.00">Specialty</text>
|
||||||
|
<polyline fill="none" stroke="black" points="753.16,-183.1 810.84,-183.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="761.16" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="761.16" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_specialty->specialty -->
|
||||||
|
<g id="edge15" class="edge">
|
||||||
|
<title>vet_specialty->specialty</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M1442,-337.81C1442,-315.45 1442,-293 1442,-293 1442,-293 806.51,-293 806.51,-293 806.51,-293 806.51,-214.91 806.51,-214.91"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="810.01,-214.91 806.51,-204.91 803.01,-214.91 810.01,-214.91"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="751.22" y="-300.4" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_neighborhood->neighborhood -->
|
||||||
|
<g id="edge17" class="edge">
|
||||||
|
<title>vet_neighborhood->neighborhood</title>
|
||||||
|
<path fill="none" stroke="#e65100" d="M800.26,-382C572.59,-382 247.21,-382 247.21,-382 247.21,-382 247.21,-226.67 247.21,-226.67"/>
|
||||||
|
<polygon fill="#e65100" stroke="#e65100" points="250.71,-226.67 247.21,-216.67 243.71,-226.67 250.71,-226.67"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="657.22" y="-300.4" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- grupo -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>grupo</title>
|
||||||
|
<polygon fill="#e1bee7" stroke="black" points="85.98,-1388.1 85.98,-1452.1 172.02,-1452.1 172.02,-1388.1 85.98,-1388.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="129" y="-1439.1" font-family="Helvetica,sans-Serif" font-size="10.00">Group</text>
|
||||||
|
<polyline fill="none" stroke="black" points="85.98,-1432.1 172.02,-1432.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="93.98" y="-1419.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="93.98" y="-1407.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="93.98" y="-1395.1" font-family="Helvetica,sans-Serif" font-size="10.00">description: text</text>
|
||||||
|
</g>
|
||||||
|
<!-- category -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>category</title>
|
||||||
|
<polygon fill="#e1bee7" stroke="black" points="166.6,-1023.5 166.6,-1111.5 255.4,-1111.5 255.4,-1023.5 166.6,-1023.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="211" y="-1098.5" font-family="Helvetica,sans-Serif" font-size="10.00">Category</text>
|
||||||
|
<polyline fill="none" stroke="black" points="166.6,-1091.5 255.4,-1091.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="174.6" y="-1078.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="174.6" y="-1066.5" font-family="Helvetica,sans-Serif" font-size="10.00">group_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="174.6" y="-1054.5" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="174.6" y="-1042.5" font-family="Helvetica,sans-Serif" font-size="10.00">description: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="174.6" y="-1030.5" font-family="Helvetica,sans-Serif" font-size="10.00">value: int (order)</text>
|
||||||
|
</g>
|
||||||
|
<!-- grupo->category -->
|
||||||
|
<g id="edge21" class="edge">
|
||||||
|
<title>grupo->category</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M172.38,-1420C177.51,-1420 180.96,-1420 180.96,-1420 180.96,-1420 180.96,-1123.3 180.96,-1123.3"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="184.46,-1123.3 180.96,-1113.3 177.46,-1123.3 184.46,-1123.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="151.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- service -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>service</title>
|
||||||
|
<polygon fill="#ce93d8" stroke="black" points="199.63,-729.3 199.63,-889.3 322.37,-889.3 322.37,-729.3 199.63,-729.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="261" y="-876.3" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||||
|
<polyline fill="none" stroke="black" points="199.63,-869.3 322.37,-869.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-856.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-844.3" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-832.3" font-family="Helvetica,sans-Serif" font-size="10.00">description: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-820.3" font-family="Helvetica,sans-Serif" font-size="10.00">specialty_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-808.3" font-family="Helvetica,sans-Serif" font-size="10.00">category_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-796.3" font-family="Helvetica,sans-Serif" font-size="10.00">duration: int (min)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-784.3" font-family="Helvetica,sans-Serif" font-size="10.00">modality: onsite/online</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-772.3" font-family="Helvetica,sans-Serif" font-size="10.00">payment_sign_req: bool</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-760.3" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type_filter: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-748.3" font-family="Helvetica,sans-Serif" font-size="10.00">age_filter: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="207.63" y="-736.3" font-family="Helvetica,sans-Serif" font-size="10.00">weight_range: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- category->service -->
|
||||||
|
<g id="edge22" class="edge">
|
||||||
|
<title>category->service</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M227.52,-1023.11C227.52,-1023.11 227.52,-901.07 227.52,-901.07"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="231.02,-901.07 227.52,-891.07 224.02,-901.08 231.02,-901.07"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="250.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- prices -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>prices</title>
|
||||||
|
<polygon fill="#ba68c8" stroke="black" points="63.3,-484.7 63.3,-620.7 192.7,-620.7 192.7,-484.7 63.3,-484.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="128" y="-607.7" font-family="Helvetica,sans-Serif" font-size="10.00">Prices</text>
|
||||||
|
<polyline fill="none" stroke="black" points="63.3,-600.7 192.7,-600.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-587.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-575.7" font-family="Helvetica,sans-Serif" font-size="10.00">service_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-563.7" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-551.7" font-family="Helvetica,sans-Serif" font-size="10.00">price: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-539.7" font-family="Helvetica,sans-Serif" font-size="10.00">professional_fee: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-527.7" font-family="Helvetica,sans-Serif" font-size="10.00">payment_sign: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-515.7" font-family="Helvetica,sans-Serif" font-size="10.00">from_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-503.7" font-family="Helvetica,sans-Serif" font-size="10.00">to_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="71.3" y="-491.7" font-family="Helvetica,sans-Serif" font-size="10.00">active: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->prices -->
|
||||||
|
<g id="edge24" class="edge">
|
||||||
|
<title>service->prices</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M204.92,-728.98C204.92,-673.26 204.92,-609 204.92,-609 204.92,-609 203.72,-609 203.72,-609"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="204.41,-605.5 194.41,-609 204.41,-612.5 204.41,-605.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="184.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- discounts -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>discounts</title>
|
||||||
|
<polygon fill="#ba68c8" stroke="black" points="210.2,-502.7 210.2,-602.7 321.8,-602.7 321.8,-502.7 210.2,-502.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="266" y="-589.7" font-family="Helvetica,sans-Serif" font-size="10.00">Discounts</text>
|
||||||
|
<polyline fill="none" stroke="black" points="210.2,-582.7 321.8,-582.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-569.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-557.7" font-family="Helvetica,sans-Serif" font-size="10.00">service_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-545.7" font-family="Helvetica,sans-Serif" font-size="10.00">discount: decimal (%)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-533.7" font-family="Helvetica,sans-Serif" font-size="10.00">from_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-521.7" font-family="Helvetica,sans-Serif" font-size="10.00">to_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="218.2" y="-509.7" font-family="Helvetica,sans-Serif" font-size="10.00">active: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->discounts -->
|
||||||
|
<g id="edge25" class="edge">
|
||||||
|
<title>service->discounts</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" d="M247.4,-728.81C247.4,-728.81 247.4,-614.57 247.4,-614.57"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="250.9,-614.57 247.4,-604.57 243.9,-614.57 250.9,-614.57"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="270.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->specialty -->
|
||||||
|
<g id="edge23" class="edge">
|
||||||
|
<title>service->specialty</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M284.6,-728.87C284.6,-675.42 284.6,-615 284.6,-615 284.6,-615 777.67,-615 777.67,-615 777.67,-615 777.67,-215.02 777.67,-215.02"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="781.17,-215.02 777.67,-205.02 774.17,-215.02 781.17,-215.02"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="538.9" y="-459" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- prices->veterinarian -->
|
||||||
|
<g id="edge26" class="edge">
|
||||||
|
<title>prices->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#7b1fa2" stroke-dasharray="5,2" d="M192.84,-493C472.5,-493 1561.35,-493 1561.35,-493 1561.35,-493 1561.35,-496.86 1561.35,-496.86"/>
|
||||||
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="1557.85,-496.86 1561.35,-506.86 1564.85,-496.86 1557.85,-496.86"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="434" y="-679.6" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="434" y="-670" font-family="Helvetica,sans-Serif" font-size="8.00">(vet-specific)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicecombo -->
|
||||||
|
<g id="node16" class="node">
|
||||||
|
<title>servicecombo</title>
|
||||||
|
<polygon fill="#e1bee7" stroke="black" points="189.91,-1376.1 189.91,-1464.1 322.09,-1464.1 322.09,-1376.1 189.91,-1376.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="256" y="-1451.1" font-family="Helvetica,sans-Serif" font-size="10.00">ServiceCombo</text>
|
||||||
|
<polyline fill="none" stroke="black" points="189.91,-1444.1 322.09,-1444.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="197.91" y="-1431.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="197.91" y="-1419.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="197.91" y="-1407.1" font-family="Helvetica,sans-Serif" font-size="10.00">description: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="197.91" y="-1395.1" font-family="Helvetica,sans-Serif" font-size="10.00">discount_percent: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="197.91" y="-1383.1" font-family="Helvetica,sans-Serif" font-size="10.00">discount_fixed: decimal</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->veterinarian -->
|
||||||
|
<g id="edge27" class="edge">
|
||||||
|
<title>cart->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#00838f" stroke-dasharray="5,2" d="M949.06,-1375.77C949.06,-1202.29 949.06,-579 949.06,-579 949.06,-579 1526.15,-579 1526.15,-579"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="1526.15,-582.5 1536.15,-579 1526.15,-575.5 1526.15,-582.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1789.12" y="-943.8" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1789.12" y="-934.2" font-family="Helvetica,sans-Serif" font-size="8.00">(assigned vet)</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartitem -->
|
||||||
|
<g id="node18" class="node">
|
||||||
|
<title>cartitem</title>
|
||||||
|
<polygon fill="#80deea" stroke="black" points="735.93,-1011.5 735.93,-1123.5 838.07,-1123.5 838.07,-1011.5 735.93,-1011.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="787" y="-1110.5" font-family="Helvetica,sans-Serif" font-size="10.00">CartItem</text>
|
||||||
|
<polyline fill="none" stroke="black" points="735.93,-1103.5 838.07,-1103.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1090.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1078.5" font-family="Helvetica,sans-Serif" font-size="10.00">cart_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1066.5" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK (opt)</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1054.5" font-family="Helvetica,sans-Serif" font-size="10.00">service_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1042.5" font-family="Helvetica,sans-Serif" font-size="10.00">price: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1030.5" font-family="Helvetica,sans-Serif" font-size="10.00">quantity: int</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="743.93" y="-1018.5" font-family="Helvetica,sans-Serif" font-size="10.00">total: decimal (calc)</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartitem -->
|
||||||
|
<g id="edge28" class="edge">
|
||||||
|
<title>cart->cartitem</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M923.27,-1375.74C923.27,-1291.12 923.27,-1115 923.27,-1115 923.27,-1115 850.03,-1115 850.03,-1115"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="850.03,-1111.5 840.03,-1115 850.03,-1118.5 850.03,-1111.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="862.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartresumeitem -->
|
||||||
|
<g id="node19" class="node">
|
||||||
|
<title>cartresumeitem</title>
|
||||||
|
<polygon fill="#80deea" stroke="black" points="957.93,-999.5 957.93,-1135.5 1070.07,-1135.5 1070.07,-999.5 957.93,-999.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1014" y="-1122.5" font-family="Helvetica,sans-Serif" font-size="10.00">CartResumeItem</text>
|
||||||
|
<polyline fill="none" stroke="black" points="957.93,-1115.5 1070.07,-1115.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1102.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1090.5" font-family="Helvetica,sans-Serif" font-size="10.00">cart_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1078.5" font-family="Helvetica,sans-Serif" font-size="10.00">concept: SUBTOTAL/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1066.5" font-family="Helvetica,sans-Serif" font-size="10.00"> DESCUENTO/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1054.5" font-family="Helvetica,sans-Serif" font-size="10.00"> ADELANTO/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1042.5" font-family="Helvetica,sans-Serif" font-size="10.00"> TOTAL/</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1030.5" font-family="Helvetica,sans-Serif" font-size="10.00"> COSTO_SERVICIO</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1018.5" font-family="Helvetica,sans-Serif" font-size="10.00">amount: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="965.93" y="-1006.5" font-family="Helvetica,sans-Serif" font-size="10.00">order: int</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartresumeitem -->
|
||||||
|
<g id="edge29" class="edge">
|
||||||
|
<title>cart->cartresumeitem</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M981.83,-1375.8C981.83,-1375.8 981.83,-1147.31 981.83,-1147.31"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="985.33,-1147.31 981.83,-1137.31 978.33,-1147.31 985.33,-1147.31"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="990.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartpetreason -->
|
||||||
|
<g id="node20" class="node">
|
||||||
|
<title>cartpetreason</title>
|
||||||
|
<polygon fill="#80deea" stroke="black" points="855.82,-1029.5 855.82,-1105.5 940.18,-1105.5 940.18,-1029.5 855.82,-1029.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="898" y="-1092.5" font-family="Helvetica,sans-Serif" font-size="10.00">CartPetReason</text>
|
||||||
|
<polyline fill="none" stroke="black" points="855.82,-1085.5 940.18,-1085.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="863.82" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="863.82" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00">cart_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="863.82" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="863.82" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">reason: text</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartpetreason -->
|
||||||
|
<g id="edge30" class="edge">
|
||||||
|
<title>cart->cartpetreason</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M931.73,-1375.8C931.73,-1375.8 931.73,-1117.47 931.73,-1117.47"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="935.23,-1117.47 931.73,-1107.47 928.23,-1117.47 935.23,-1117.47"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="919.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartitem->pet -->
|
||||||
|
<g id="edge32" class="edge">
|
||||||
|
<title>cartitem->pet</title>
|
||||||
|
<path fill="none" stroke="#00838f" stroke-dasharray="5,2" d="M747.42,-1011.12C747.42,-964.19 747.42,-905 747.42,-905 747.42,-905 692.94,-905 692.94,-905"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="692.94,-901.5 682.94,-905 692.94,-908.5 692.94,-901.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="640.9" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartitem->service -->
|
||||||
|
<g id="edge31" class="edge">
|
||||||
|
<title>cartitem->service</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M741.67,-1011.15C741.67,-997.62 741.67,-987 741.67,-987 741.67,-987 288.75,-987 288.75,-987 288.75,-987 288.75,-901.09 288.75,-901.09"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="292.25,-901.09 288.75,-891.09 285.25,-901.09 292.25,-901.09"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="446.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartpetreason->pet -->
|
||||||
|
<g id="edge33" class="edge">
|
||||||
|
<title>cartpetreason->pet</title>
|
||||||
|
<path fill="none" stroke="#00838f" d="M898,-1029.01C898,-978.45 898,-896 898,-896 898,-896 692.86,-896 692.86,-896"/>
|
||||||
|
<polygon fill="#00838f" stroke="#00838f" points="692.86,-892.5 682.86,-896 692.86,-899.5 692.86,-892.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="683.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->veterinarian -->
|
||||||
|
<g id="edge35" class="edge">
|
||||||
|
<title>servicerequest->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#c62828" stroke-dasharray="5,2" d="M1895.22,-975C1780.2,-975 1561.26,-975 1561.26,-975 1561.26,-975 1561.26,-608.61 1561.26,-608.61"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1564.76,-608.61 1561.26,-598.61 1557.76,-608.61 1564.76,-608.61"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1858.68" y="-811.7" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1858.68" y="-802.1" font-family="Helvetica,sans-Serif" font-size="8.00">(assigned)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->cart -->
|
||||||
|
<g id="edge34" class="edge">
|
||||||
|
<title>servicerequest->cart</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1895.08,-1154C1675.36,-1154 1005.73,-1154 1005.73,-1154 1005.73,-1154 1005.73,-1364.35 1005.73,-1364.35"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1002.23,-1364.35 1005.73,-1374.35 1009.23,-1364.35 1002.23,-1364.35"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1160.56" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- statehistory -->
|
||||||
|
<g id="node22" class="node">
|
||||||
|
<title>statehistory</title>
|
||||||
|
<polygon fill="#ef9a9a" stroke="black" points="2182.86,-759.3 2182.86,-859.3 2301.14,-859.3 2301.14,-759.3 2182.86,-759.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2242" y="-846.3" font-family="Helvetica,sans-Serif" font-size="10.00">StateHistory</text>
|
||||||
|
<polyline fill="none" stroke="black" points="2182.86,-839.3 2301.14,-839.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-826.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-814.3" font-family="Helvetica,sans-Serif" font-size="10.00">service_request_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-802.3" font-family="Helvetica,sans-Serif" font-size="10.00">state: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-790.3" font-family="Helvetica,sans-Serif" font-size="10.00">additional_data: JSON</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-778.3" font-family="Helvetica,sans-Serif" font-size="10.00">created_at: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2190.86" y="-766.3" font-family="Helvetica,sans-Serif" font-size="10.00">user_id: FK</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->statehistory -->
|
||||||
|
<g id="edge36" class="edge">
|
||||||
|
<title>servicerequest->statehistory</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M2033.08,-987C2115.41,-987 2242,-987 2242,-987 2242,-987 2242,-871.26 2242,-871.26"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="2245.5,-871.26 2242,-861.26 2238.5,-871.26 2245.5,-871.26"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2179.01" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N audit</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked -->
|
||||||
|
<g id="node23" class="node">
|
||||||
|
<title>vetasked</title>
|
||||||
|
<polygon fill="#ef9a9a" stroke="black" points="1894.85,-759.3 1894.85,-859.3 2023.15,-859.3 2023.15,-759.3 1894.85,-759.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1959" y="-846.3" font-family="Helvetica,sans-Serif" font-size="10.00">VeterinarianAsked</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1894.85,-839.3 2023.15,-839.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-826.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-814.3" font-family="Helvetica,sans-Serif" font-size="10.00">service_request_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-802.3" font-family="Helvetica,sans-Serif" font-size="10.00">veterinarian_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-790.3" font-family="Helvetica,sans-Serif" font-size="10.00">date_asked: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-778.3" font-family="Helvetica,sans-Serif" font-size="10.00">date_answered: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1902.85" y="-766.3" font-family="Helvetica,sans-Serif" font-size="10.00">accepted: bool</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->vetasked -->
|
||||||
|
<g id="edge37" class="edge">
|
||||||
|
<title>servicerequest->vetasked</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1959.27,-969.15C1959.27,-969.15 1959.27,-871.22 1959.27,-871.22"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1962.77,-871.22 1959.27,-861.22 1955.77,-871.22 1962.77,-871.22"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1967.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- payreminder -->
|
||||||
|
<g id="node25" class="node">
|
||||||
|
<title>payreminder</title>
|
||||||
|
<polygon fill="#e57373" stroke="black" points="2041.63,-765.3 2041.63,-853.3 2164.37,-853.3 2164.37,-765.3 2041.63,-765.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2103" y="-840.3" font-family="Helvetica,sans-Serif" font-size="10.00">PaymentReminder</text>
|
||||||
|
<polyline fill="none" stroke="black" points="2041.63,-833.3 2164.37,-833.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2049.63" y="-820.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2049.63" y="-808.3" font-family="Helvetica,sans-Serif" font-size="10.00">service_request_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2049.63" y="-796.3" font-family="Helvetica,sans-Serif" font-size="10.00">scheduled_for: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2049.63" y="-784.3" font-family="Helvetica,sans-Serif" font-size="10.00">processed_at: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2049.63" y="-772.3" font-family="Helvetica,sans-Serif" font-size="10.00">status: pending/sent/...</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->payreminder -->
|
||||||
|
<g id="edge38" class="edge">
|
||||||
|
<title>servicerequest->payreminder</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M2029.45,-969.03C2029.45,-895.98 2029.45,-809 2029.45,-809 2029.45,-809 2030.63,-809 2030.63,-809"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="2029.75,-812.5 2039.75,-809 2029.75,-805.5 2029.75,-812.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2055.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->campaign -->
|
||||||
|
<g id="edge39" class="edge">
|
||||||
|
<title>servicerequest->campaign</title>
|
||||||
|
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M2026.3,-969.29C2026.3,-754.09 2026.3,-260 2026.3,-260 2026.3,-260 874,-260 874,-260 874,-260 874,-232.98 874,-232.98"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="877.5,-232.98 874,-222.98 870.5,-232.98 877.5,-232.98"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2341.9" y="-550.3" font-family="Helvetica,sans-Serif" font-size="8.00">N:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- tag -->
|
||||||
|
<g id="node39" class="node">
|
||||||
|
<title>tag</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="638.43,-151.1 638.43,-203.1 735.57,-203.1 735.57,-151.1 638.43,-151.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="687" y="-190.1" font-family="Helvetica,sans-Serif" font-size="10.00">Tag</text>
|
||||||
|
<polyline fill="none" stroke="black" points="638.43,-183.1 735.57,-183.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="646.43" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="646.43" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str (unique)</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->tag -->
|
||||||
|
<g id="edge40" class="edge">
|
||||||
|
<title>servicerequest->tag</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M1895.27,-981C1634.21,-981 718.72,-981 718.72,-981 718.72,-981 718.72,-214.9 718.72,-214.9"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="722.22,-214.9 718.72,-204.9 715.22,-214.9 722.22,-214.9"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="756.33" y="-550.3" font-family="Helvetica,sans-Serif" font-size="8.00">N:M</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked->veterinarian -->
|
||||||
|
<g id="edge41" class="edge">
|
||||||
|
<title>vetasked->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1894.62,-765C1787.53,-765 1584.47,-765 1584.47,-765 1584.47,-765 1584.47,-608.46 1584.47,-608.46"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1587.97,-608.46 1584.47,-598.46 1580.97,-608.46 1587.97,-608.46"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1848.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetreminder -->
|
||||||
|
<g id="node24" class="node">
|
||||||
|
<title>vetreminder</title>
|
||||||
|
<polygon fill="#e57373" stroke="black" points="1897.63,-502.7 1897.63,-602.7 2020.37,-602.7 2020.37,-502.7 1897.63,-502.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1959" y="-589.7" font-family="Helvetica,sans-Serif" font-size="10.00">ScheduledVetReminder</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1897.63,-582.7 2020.37,-582.7"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-569.7" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-557.7" font-family="Helvetica,sans-Serif" font-size="10.00">vet_asked_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-545.7" font-family="Helvetica,sans-Serif" font-size="10.00">scheduled_for: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-533.7" font-family="Helvetica,sans-Serif" font-size="10.00">processed_at: datetime</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-521.7" font-family="Helvetica,sans-Serif" font-size="10.00">status: pending/sent/...</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1905.63" y="-509.7" font-family="Helvetica,sans-Serif" font-size="10.00">celery_task_id: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked->vetreminder -->
|
||||||
|
<g id="edge42" class="edge">
|
||||||
|
<title>vetasked->vetreminder</title>
|
||||||
|
<path fill="none" stroke="#c62828" d="M1959,-758.91C1959,-758.91 1959,-614.49 1959,-614.49"/>
|
||||||
|
<polygon fill="#c62828" stroke="#c62828" points="1962.5,-614.49 1959,-604.49 1955.5,-614.49 1962.5,-614.49"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1965.22" y="-674.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->veterinarian -->
|
||||||
|
<g id="edge44" class="edge">
|
||||||
|
<title>vetvisit->veterinarian</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1617.03,-1273.86C1617.03,-1031.98 1617.03,-575 1617.03,-575 1617.03,-575 1616.13,-575 1616.13,-575"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1619.63,-571.5 1609.63,-575 1619.63,-578.5 1619.63,-571.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2373.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->servicerequest -->
|
||||||
|
<g id="edge43" class="edge">
|
||||||
|
<title>vetvisit->servicerequest</title>
|
||||||
|
<path fill="none" stroke="#f9a825" stroke-dasharray="5,2" d="M1695.23,-1273.71C1695.23,-1214.56 1695.23,-1160 1695.23,-1160 1695.23,-1160 1883.62,-1160 1883.62,-1160"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1883.62,-1163.5 1893.62,-1160 1883.62,-1156.5 1883.62,-1163.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1864.12" y="-1248.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:1 opt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1864.12" y="-1238.8" font-family="Helvetica,sans-Serif" font-size="8.00">(from request)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport -->
|
||||||
|
<g id="node27" class="node">
|
||||||
|
<title>vetvisitreport</title>
|
||||||
|
<polygon fill="#fff59d" stroke="black" points="1626.09,-1005.5 1626.09,-1129.5 1729.91,-1129.5 1729.91,-1005.5 1626.09,-1005.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1678" y="-1116.5" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisitReport</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1626.09,-1109.5 1729.91,-1109.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1096.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1084.5" font-family="Helvetica,sans-Serif" font-size="10.00">visit_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00">reason: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">physical_exam: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">diagnosis: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1024.5" font-family="Helvetica,sans-Serif" font-size="10.00">treatment: text</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1634.09" y="-1012.5" font-family="Helvetica,sans-Serif" font-size="10.00">pdf_file: file</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->vetvisitreport -->
|
||||||
|
<g id="edge47" class="edge">
|
||||||
|
<title>vetvisit->vetvisitreport</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1660.66,-1273.63C1660.66,-1273.63 1660.66,-1141.19 1660.66,-1141.19"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1664.16,-1141.19 1660.66,-1131.19 1657.16,-1141.19 1664.16,-1141.19"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1675.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitpetreason -->
|
||||||
|
<g id="node29" class="node">
|
||||||
|
<title>vetvisitpetreason</title>
|
||||||
|
<polygon fill="#fff176" stroke="black" points="1508.31,-1029.5 1508.31,-1105.5 1607.69,-1105.5 1607.69,-1029.5 1508.31,-1029.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1558" y="-1092.5" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisitPetReason</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1508.31,-1085.5 1607.69,-1085.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1516.31" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1516.31" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00">visit_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1516.31" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1516.31" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">reason: text</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->vetvisitpetreason -->
|
||||||
|
<g id="edge48" class="edge">
|
||||||
|
<title>vetvisit->vetvisitpetreason</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1597.94,-1273.63C1597.94,-1273.63 1597.94,-1117.18 1597.94,-1117.18"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1601.44,-1117.18 1597.94,-1107.18 1594.44,-1117.18 1601.44,-1117.18"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1598.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- visit_pets -->
|
||||||
|
<g id="node30" class="node">
|
||||||
|
<title>visit_pets</title>
|
||||||
|
<polygon fill="#ffee58" stroke="black" points="1336,-1111.5 1181.81,-1067.5 1336,-1023.5 1490.19,-1067.5 1336,-1111.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1210.47" y="-1076.5" font-family="Helvetica,sans-Serif" font-size="10.00">{M2M: Visit-Pets|vetvisit_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1210.47" y="-1064.5" font-family="Helvetica,sans-Serif" font-size="10.00">pet_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1336" y="-1052.5" font-family="Helvetica,sans-Serif" font-size="10.00">}</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->visit_pets -->
|
||||||
|
<g id="edge45" class="edge">
|
||||||
|
<title>vetvisit->visit_pets</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1587.72,-1308C1492.6,-1308 1336,-1308 1336,-1308 1336,-1308 1336,-1123.41 1336,-1123.41"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1339.5,-1123.41 1336,-1113.41 1332.5,-1123.41 1339.5,-1123.41"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1476.22" y="-1243.6" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- receipt -->
|
||||||
|
<g id="node42" class="node">
|
||||||
|
<title>receipt</title>
|
||||||
|
<polygon fill="#f8bbd9" stroke="black" points="2394.58,-1005.5 2394.58,-1129.5 2523.42,-1129.5 2523.42,-1005.5 2394.58,-1005.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2459" y="-1116.5" font-family="Helvetica,sans-Serif" font-size="10.00">Receipt</text>
|
||||||
|
<polyline fill="none" stroke="black" points="2394.58,-1109.5 2523.42,-1109.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1096.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1084.5" font-family="Helvetica,sans-Serif" font-size="10.00">document_number: bigint</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1072.5" font-family="Helvetica,sans-Serif" font-size="10.00">receipt_number: int</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1060.5" font-family="Helvetica,sans-Serif" font-size="10.00">issued_date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1048.5" font-family="Helvetica,sans-Serif" font-size="10.00">total_amount: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1036.5" font-family="Helvetica,sans-Serif" font-size="10.00">net_taxed: decimal</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1024.5" font-family="Helvetica,sans-Serif" font-size="10.00">cae: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="2402.58" y="-1012.5" font-family="Helvetica,sans-Serif" font-size="10.00">cae_expiration: date</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->receipt -->
|
||||||
|
<g id="edge52" class="edge">
|
||||||
|
<title>vetvisit->receipt</title>
|
||||||
|
<path fill="none" stroke="#ad1457" stroke-dasharray="5,2" d="M1729.99,-1420C1925.78,-1420 2459,-1420 2459,-1420 2459,-1420 2459,-1141.22 2459,-1141.22"/>
|
||||||
|
<polygon fill="#ad1457" stroke="#ad1457" points="2462.5,-1141.22 2459,-1131.22 2455.5,-1141.22 2462.5,-1141.22"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2444.12" y="-1248.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:1 opt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2444.12" y="-1238.8" font-family="Helvetica,sans-Serif" font-size="8.00">(invoice)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport->pet -->
|
||||||
|
<g id="edge49" class="edge">
|
||||||
|
<title>vetvisitreport->pet</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1662.74,-1005.24C1662.74,-947.02 1662.74,-869 1662.74,-869 1662.74,-869 692.97,-869 692.97,-869"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="692.97,-865.5 682.97,-869 692.97,-872.5 692.97,-865.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1237.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitfollowup -->
|
||||||
|
<g id="node28" class="node">
|
||||||
|
<title>vetvisitfollowup</title>
|
||||||
|
<polygon fill="#fff176" stroke="black" points="1632.21,-771.3 1632.21,-847.3 1723.79,-847.3 1723.79,-771.3 1632.21,-771.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1678" y="-834.3" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisitFollowUp</text>
|
||||||
|
<polyline fill="none" stroke="black" points="1632.21,-827.3 1723.79,-827.3"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1640.21" y="-814.3" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1640.21" y="-802.3" font-family="Helvetica,sans-Serif" font-size="10.00">report_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1640.21" y="-790.3" font-family="Helvetica,sans-Serif" font-size="10.00">date: date</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="1640.21" y="-778.3" font-family="Helvetica,sans-Serif" font-size="10.00">description: text</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport->vetvisitfollowup -->
|
||||||
|
<g id="edge50" class="edge">
|
||||||
|
<title>vetvisitreport->vetvisitfollowup</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1693.26,-1005.33C1693.26,-1005.33 1693.26,-859.22 1693.26,-859.22"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="1696.76,-859.22 1693.26,-849.22 1689.76,-859.22 1696.76,-859.22"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1684.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitpetreason->pet -->
|
||||||
|
<g id="edge51" class="edge">
|
||||||
|
<title>vetvisitpetreason->pet</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1523.18,-1029.15C1523.18,-973.78 1523.18,-878 1523.18,-878 1523.18,-878 692.83,-878 692.83,-878"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="692.83,-874.5 682.83,-878 692.83,-881.5 692.83,-874.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="753.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- visit_pets->pet -->
|
||||||
|
<g id="edge46" class="edge">
|
||||||
|
<title>visit_pets->pet</title>
|
||||||
|
<path fill="none" stroke="#f9a825" d="M1198.48,-1062.29C1198.48,-1032.7 1198.48,-887 1198.48,-887 1198.48,-887 692.93,-887 692.93,-887"/>
|
||||||
|
<polygon fill="#f9a825" stroke="#f9a825" points="692.93,-883.5 682.93,-887 692.93,-890.5 692.93,-883.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="718.22" y="-939" font-family="Helvetica,sans-Serif" font-size="8.00">N:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- province -->
|
||||||
|
<g id="node33" class="node">
|
||||||
|
<title>province</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="302.16,-151.1 302.16,-203.1 359.84,-203.1 359.84,-151.1 302.16,-151.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="331" y="-190.1" font-family="Helvetica,sans-Serif" font-size="10.00">Province</text>
|
||||||
|
<polyline fill="none" stroke="black" points="302.16,-183.1 359.84,-183.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="310.16" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="310.16" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- locality -->
|
||||||
|
<g id="node34" class="node">
|
||||||
|
<title>locality</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="288.26,-16.5 288.26,-80.5 373.74,-80.5 373.74,-16.5 288.26,-16.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="331" y="-67.5" font-family="Helvetica,sans-Serif" font-size="10.00">Locality</text>
|
||||||
|
<polyline fill="none" stroke="black" points="288.26,-60.5 373.74,-60.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="296.26" y="-47.5" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="296.26" y="-35.5" font-family="Helvetica,sans-Serif" font-size="10.00">province_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="296.26" y="-23.5" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
</g>
|
||||||
|
<!-- province->locality -->
|
||||||
|
<g id="edge53" class="edge">
|
||||||
|
<title>province->locality</title>
|
||||||
|
<path fill="none" stroke="#666666" d="M331,-150.77C331,-150.77 331,-92.24 331,-92.24"/>
|
||||||
|
<polygon fill="#666666" stroke="#666666" points="334.5,-92.24 331,-82.24 327.5,-92.24 334.5,-92.24"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="337.22" y="-101.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- medication -->
|
||||||
|
<g id="node40" class="node">
|
||||||
|
<title>medication</title>
|
||||||
|
<polygon fill="#cfd8dc" stroke="black" points="937.19,-127.1 937.19,-227.1 1048.81,-227.1 1048.81,-127.1 937.19,-127.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="993" y="-214.1" font-family="Helvetica,sans-Serif" font-size="10.00">Medication</text>
|
||||||
|
<polyline fill="none" stroke="black" points="937.19,-207.1 1048.81,-207.1"/>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-194.1" font-family="Helvetica,sans-Serif" font-size="10.00">id: PK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-182.1" font-family="Helvetica,sans-Serif" font-size="10.00">name: str</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-170.1" font-family="Helvetica,sans-Serif" font-size="10.00">general_name_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-158.1" font-family="Helvetica,sans-Serif" font-size="10.00">type_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-146.1" font-family="Helvetica,sans-Serif" font-size="10.00">presentation_id: FK</text>
|
||||||
|
<text xml:space="preserve" text-anchor="start" x="945.19" y="-134.1" font-family="Helvetica,sans-Serif" font-size="10.00">pet_type: str</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 84 KiB |
195
atlas/book/arch-model/04-data-model-simple.dot
Normal file
195
atlas/book/arch-model/04-data-model-simple.dot
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
540
atlas/book/arch-model/04-data-model-simple.svg
Normal file
540
atlas/book/arch-model/04-data-model-simple.svg
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 14.0.5 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: DataModelSimple Pages: 1 -->
|
||||||
|
<svg width="2433pt" height="780pt"
|
||||||
|
viewBox="0.00 0.00 2433.00 780.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 776.4)">
|
||||||
|
<title>DataModelSimple</title>
|
||||||
|
<polygon fill="white" stroke="none" points="-4,4 -4,-776.4 2429,-776.4 2429,4 -4,4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1212.5" y="-753.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AMAR Mascotas - Data Model Overview</text>
|
||||||
|
<g id="clust1" class="cluster">
|
||||||
|
<title>cluster_auth</title>
|
||||||
|
<path fill="#e8f5e9" stroke="#2e7d32" d="M890,-658C890,-658 975,-658 975,-658 981,-658 987,-664 987,-670 987,-670 987,-725.2 987,-725.2 987,-731.2 981,-737.2 975,-737.2 975,-737.2 890,-737.2 890,-737.2 884,-737.2 878,-731.2 878,-725.2 878,-725.2 878,-670 878,-670 878,-664 884,-658 890,-658"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="932.5" y="-718" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Users & Auth</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster_mascotas</title>
|
||||||
|
<path fill="#e3f2fd" stroke="#1565c0" d="M1662,-262.6C1662,-262.6 1799,-262.6 1799,-262.6 1805,-262.6 1811,-268.6 1811,-274.6 1811,-274.6 1811,-593.4 1811,-593.4 1811,-599.4 1805,-605.4 1799,-605.4 1799,-605.4 1662,-605.4 1662,-605.4 1656,-605.4 1650,-599.4 1650,-593.4 1650,-593.4 1650,-274.6 1650,-274.6 1650,-268.6 1656,-262.6 1662,-262.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1730.5" y="-586.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Pet Owners & Pets</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust3" class="cluster">
|
||||||
|
<title>cluster_vets</title>
|
||||||
|
<path fill="#fff3e0" stroke="#e65100" d="M20,-262.6C20,-262.6 509,-262.6 509,-262.6 515,-262.6 521,-268.6 521,-274.6 521,-274.6 521,-461.6 521,-461.6 521,-467.6 515,-473.6 509,-473.6 509,-473.6 20,-473.6 20,-473.6 14,-473.6 8,-467.6 8,-461.6 8,-461.6 8,-274.6 8,-274.6 8,-268.6 14,-262.6 20,-262.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="264.5" y="-454.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Veterinarians</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_productos</title>
|
||||||
|
<path fill="#f3e5f5" stroke="#7b1fa2" d="M2277,-262.6C2277,-262.6 2405,-262.6 2405,-262.6 2411,-262.6 2417,-268.6 2417,-274.6 2417,-274.6 2417,-725.2 2417,-725.2 2417,-731.2 2411,-737.2 2405,-737.2 2405,-737.2 2277,-737.2 2277,-737.2 2271,-737.2 2265,-731.2 2265,-725.2 2265,-725.2 2265,-274.6 2265,-274.6 2265,-268.6 2271,-262.6 2277,-262.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2341" y="-718" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Services & Pricing</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust5" class="cluster">
|
||||||
|
<title>cluster_cart</title>
|
||||||
|
<path fill="#e0f7fa" stroke="#00838f" d="M853,-262.6C853,-262.6 1119,-262.6 1119,-262.6 1125,-262.6 1131,-268.6 1131,-274.6 1131,-274.6 1131,-461.6 1131,-461.6 1131,-467.6 1125,-473.6 1119,-473.6 1119,-473.6 853,-473.6 853,-473.6 847,-473.6 841,-467.6 841,-461.6 841,-461.6 841,-274.6 841,-274.6 841,-268.6 847,-262.6 853,-262.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="986" y="-454.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Cart & Checkout</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_solicitudes</title>
|
||||||
|
<path fill="#ffebee" stroke="#c62828" d="M541,-8C541,-8 821,-8 821,-8 827,-8 833,-14 833,-20 833,-20 833,-329.8 833,-329.8 833,-335.8 827,-341.8 821,-341.8 821,-341.8 541,-341.8 541,-341.8 535,-341.8 529,-335.8 529,-329.8 529,-329.8 529,-20 529,-20 529,-14 535,-8 541,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="681" y="-322.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Service Requests</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust7" class="cluster">
|
||||||
|
<title>cluster_visits</title>
|
||||||
|
<path fill="#fffde7" stroke="#f9a825" d="M1869,-8C1869,-8 2245,-8 2245,-8 2251,-8 2257,-14 2257,-20 2257,-20 2257,-329.8 2257,-329.8 2257,-335.8 2251,-341.8 2245,-341.8 2245,-341.8 1869,-341.8 1869,-341.8 1863,-341.8 1857,-335.8 1857,-329.8 1857,-329.8 1857,-20 1857,-20 1857,-14 1863,-8 1869,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2057" y="-322.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Veterinary Visits</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust8" class="cluster">
|
||||||
|
<title>cluster_reference</title>
|
||||||
|
<path fill="#eceff1" stroke="#455a64" d="M1151,-131C1151,-131 1630,-131 1630,-131 1636,-131 1642,-137 1642,-143 1642,-143 1642,-329.8 1642,-329.8 1642,-335.8 1636,-341.8 1630,-341.8 1630,-341.8 1151,-341.8 1151,-341.8 1145,-341.8 1139,-335.8 1139,-329.8 1139,-329.8 1139,-143 1139,-143 1139,-137 1145,-131 1151,-131"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1390.5" y="-322.6" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reference Data</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>auth_user</title>
|
||||||
|
<path fill="#c8e6c9" stroke="black" d="M949.68,-702C949.68,-702 914.32,-702 914.32,-702 908.32,-702 902.32,-696 902.32,-690 902.32,-690 902.32,-678 902.32,-678 902.32,-672 908.32,-666 914.32,-666 914.32,-666 949.68,-666 949.68,-666 955.68,-666 961.68,-672 961.68,-678 961.68,-678 961.68,-690 961.68,-690 961.68,-696 955.68,-702 949.68,-702"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="932" y="-681" font-family="Helvetica,sans-Serif" font-size="10.00">auth.User</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>petowner</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M1706.23,-570.2C1706.23,-570.2 1669.77,-570.2 1669.77,-570.2 1663.77,-570.2 1657.77,-564.2 1657.77,-558.2 1657.77,-558.2 1657.77,-546.2 1657.77,-546.2 1657.77,-540.2 1663.77,-534.2 1669.77,-534.2 1669.77,-534.2 1706.23,-534.2 1706.23,-534.2 1712.23,-534.2 1718.23,-540.2 1718.23,-546.2 1718.23,-546.2 1718.23,-558.2 1718.23,-558.2 1718.23,-564.2 1712.23,-570.2 1706.23,-570.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1688" y="-549.2" font-family="Helvetica,sans-Serif" font-size="10.00">PetOwner</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->petowner -->
|
||||||
|
<g id="edge32" class="edge">
|
||||||
|
<title>auth_user->petowner</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M943.44,-665.62C943.44,-631.77 943.44,-563 943.44,-563 943.44,-563 1645.91,-563 1645.91,-563"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1645.91,-566.5 1655.91,-563 1645.91,-559.5 1645.91,-566.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1213.23" y="-615.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:1 opt</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>veterinarian</title>
|
||||||
|
<path fill="#ffe0b2" stroke="black" d="M500.96,-438.4C500.96,-438.4 455.04,-438.4 455.04,-438.4 449.04,-438.4 443.04,-432.4 443.04,-426.4 443.04,-426.4 443.04,-414.4 443.04,-414.4 443.04,-408.4 449.04,-402.4 455.04,-402.4 455.04,-402.4 500.96,-402.4 500.96,-402.4 506.96,-402.4 512.96,-408.4 512.96,-414.4 512.96,-414.4 512.96,-426.4 512.96,-426.4 512.96,-432.4 506.96,-438.4 500.96,-438.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="478" y="-417.4" font-family="Helvetica,sans-Serif" font-size="10.00">Veterinarian</text>
|
||||||
|
</g>
|
||||||
|
<!-- auth_user->veterinarian -->
|
||||||
|
<g id="edge33" class="edge">
|
||||||
|
<title>auth_user->veterinarian</title>
|
||||||
|
<path fill="none" stroke="black" d="M914.19,-665.54C914.19,-607.58 914.19,-433 914.19,-433 914.19,-433 524.84,-433 524.84,-433"/>
|
||||||
|
<polygon fill="black" stroke="black" points="524.84,-429.5 514.84,-433 524.84,-436.5 524.84,-429.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="667.56" y="-549.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>pet</title>
|
||||||
|
<path fill="#bbdefb" stroke="black" d="M1706,-438.4C1706,-438.4 1676,-438.4 1676,-438.4 1670,-438.4 1664,-432.4 1664,-426.4 1664,-426.4 1664,-414.4 1664,-414.4 1664,-408.4 1670,-402.4 1676,-402.4 1676,-402.4 1706,-402.4 1706,-402.4 1712,-402.4 1718,-408.4 1718,-414.4 1718,-414.4 1718,-426.4 1718,-426.4 1718,-432.4 1712,-438.4 1706,-438.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1691" y="-417.4" font-family="Helvetica,sans-Serif" font-size="10.00">Pet</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->pet -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>petowner->pet</title>
|
||||||
|
<path fill="none" stroke="black" d="M1691,-533.87C1691,-533.87 1691,-450.08 1691,-450.08"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1694.5,-450.08 1691,-440.08 1687.5,-450.08 1694.5,-450.08"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1696.22" y="-484" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart -->
|
||||||
|
<g id="node16" class="node">
|
||||||
|
<title>cart</title>
|
||||||
|
<path fill="#b2ebf2" stroke="black" d="M1111,-438.4C1111,-438.4 1081,-438.4 1081,-438.4 1075,-438.4 1069,-432.4 1069,-426.4 1069,-426.4 1069,-414.4 1069,-414.4 1069,-408.4 1075,-402.4 1081,-402.4 1081,-402.4 1111,-402.4 1111,-402.4 1117,-402.4 1123,-408.4 1123,-414.4 1123,-414.4 1123,-426.4 1123,-426.4 1123,-432.4 1117,-438.4 1111,-438.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1096" y="-417.4" font-family="Helvetica,sans-Serif" font-size="10.00">Cart</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->cart -->
|
||||||
|
<!-- petowner->cart -->
|
||||||
|
<g id="edge34" class="edge">
|
||||||
|
<title>petowner->cart</title>
|
||||||
|
<path fill="none" stroke="black" d="M1657.48,-541C1537.52,-541 1105,-541 1105,-541 1105,-541 1105,-450.23 1105,-450.23"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1108.5,-450.23 1105,-440.23 1101.5,-450.23 1108.5,-450.23"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1233.22" y="-484" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest -->
|
||||||
|
<g id="node20" class="node">
|
||||||
|
<title>servicerequest</title>
|
||||||
|
<path fill="#ffcdd2" stroke="black" d="M763.3,-306.6C763.3,-306.6 700.7,-306.6 700.7,-306.6 694.7,-306.6 688.7,-300.6 688.7,-294.6 688.7,-294.6 688.7,-282.6 688.7,-282.6 688.7,-276.6 694.7,-270.6 700.7,-270.6 700.7,-270.6 763.3,-270.6 763.3,-270.6 769.3,-270.6 775.3,-276.6 775.3,-282.6 775.3,-282.6 775.3,-294.6 775.3,-294.6 775.3,-300.6 769.3,-306.6 763.3,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="732" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">ServiceRequest</text>
|
||||||
|
</g>
|
||||||
|
<!-- petowner->servicerequest -->
|
||||||
|
<g id="edge35" class="edge">
|
||||||
|
<title>petowner->servicerequest</title>
|
||||||
|
<path fill="none" stroke="black" d="M1657.27,-556C1493.64,-556 732,-556 732,-556 732,-556 732,-318.49 732,-318.49"/>
|
||||||
|
<polygon fill="black" stroke="black" points="735.5,-318.49 732,-308.49 728.5,-318.49 735.5,-318.49"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="782.22" y="-418" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- petvaccine -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>petvaccine</title>
|
||||||
|
<path fill="#90caf9" stroke="black" d="M1712.29,-306.6C1712.29,-306.6 1669.71,-306.6 1669.71,-306.6 1663.71,-306.6 1657.71,-300.6 1657.71,-294.6 1657.71,-294.6 1657.71,-282.6 1657.71,-282.6 1657.71,-276.6 1663.71,-270.6 1669.71,-270.6 1669.71,-270.6 1712.29,-270.6 1712.29,-270.6 1718.29,-270.6 1724.29,-276.6 1724.29,-282.6 1724.29,-282.6 1724.29,-294.6 1724.29,-294.6 1724.29,-300.6 1718.29,-306.6 1712.29,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1691" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">PetVaccine</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->petvaccine -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>pet->petvaccine</title>
|
||||||
|
<path fill="none" stroke="black" d="M1691,-402.07C1691,-402.07 1691,-318.28 1691,-318.28"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1694.5,-318.28 1691,-308.28 1687.5,-318.28 1694.5,-318.28"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1697.22" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- petstudy -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>petstudy</title>
|
||||||
|
<path fill="#90caf9" stroke="black" d="M1791.29,-306.6C1791.29,-306.6 1758.71,-306.6 1758.71,-306.6 1752.71,-306.6 1746.71,-300.6 1746.71,-294.6 1746.71,-294.6 1746.71,-282.6 1746.71,-282.6 1746.71,-276.6 1752.71,-270.6 1758.71,-270.6 1758.71,-270.6 1791.29,-270.6 1791.29,-270.6 1797.29,-270.6 1803.29,-276.6 1803.29,-282.6 1803.29,-282.6 1803.29,-294.6 1803.29,-294.6 1803.29,-300.6 1797.29,-306.6 1791.29,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1775" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">PetStudy</text>
|
||||||
|
</g>
|
||||||
|
<!-- pet->petstudy -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>pet->petstudy</title>
|
||||||
|
<path fill="none" stroke="black" d="M1718.04,-414C1742.6,-414 1775,-414 1775,-414 1775,-414 1775,-318.35 1775,-318.35"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1778.5,-318.35 1775,-308.35 1771.5,-318.35 1778.5,-318.35"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1758.22" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vaccine -->
|
||||||
|
<g id="node36" class="node">
|
||||||
|
<title>vaccine</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1189,-306.6C1189,-306.6 1159,-306.6 1159,-306.6 1153,-306.6 1147,-300.6 1147,-294.6 1147,-294.6 1147,-282.6 1147,-282.6 1147,-276.6 1153,-270.6 1159,-270.6 1159,-270.6 1189,-270.6 1189,-270.6 1195,-270.6 1201,-276.6 1201,-282.6 1201,-282.6 1201,-294.6 1201,-294.6 1201,-300.6 1195,-306.6 1189,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1174" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Vaccine</text>
|
||||||
|
</g>
|
||||||
|
<!-- petvaccine->vaccine -->
|
||||||
|
<g id="edge39" class="edge">
|
||||||
|
<title>petvaccine->vaccine</title>
|
||||||
|
<path fill="none" stroke="black" d="M1691,-270.32C1691,-257.63 1691,-243 1691,-243 1691,-243 1174.1,-243 1174.1,-243 1174.1,-243 1174.1,-258.8 1174.1,-258.8"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1170.6,-258.8 1174.1,-268.8 1177.6,-258.8 1170.6,-258.8"/>
|
||||||
|
</g>
|
||||||
|
<!-- study -->
|
||||||
|
<g id="node37" class="node">
|
||||||
|
<title>study</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1356,-175C1356,-175 1326,-175 1326,-175 1320,-175 1314,-169 1314,-163 1314,-163 1314,-151 1314,-151 1314,-145 1320,-139 1326,-139 1326,-139 1356,-139 1356,-139 1362,-139 1368,-145 1368,-151 1368,-151 1368,-163 1368,-163 1368,-169 1362,-175 1356,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1341" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">Study</text>
|
||||||
|
</g>
|
||||||
|
<!-- petstudy->study -->
|
||||||
|
<g id="edge40" class="edge">
|
||||||
|
<title>petstudy->study</title>
|
||||||
|
<path fill="none" stroke="black" d="M1775,-270.23C1775,-244.81 1775,-202 1775,-202 1775,-202 1353.18,-202 1353.18,-202 1353.18,-202 1353.18,-186.82 1353.18,-186.82"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1356.68,-186.82 1353.18,-176.82 1349.68,-186.82 1356.68,-186.82"/>
|
||||||
|
</g>
|
||||||
|
<!-- availability -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>availability</title>
|
||||||
|
<path fill="#ffcc80" stroke="black" d="M67.62,-306.6C67.62,-306.6 28.38,-306.6 28.38,-306.6 22.38,-306.6 16.38,-300.6 16.38,-294.6 16.38,-294.6 16.38,-282.6 16.38,-282.6 16.38,-276.6 22.38,-270.6 28.38,-270.6 28.38,-270.6 67.62,-270.6 67.62,-270.6 73.62,-270.6 79.62,-276.6 79.62,-282.6 79.62,-282.6 79.62,-294.6 79.62,-294.6 79.62,-300.6 73.62,-306.6 67.62,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="48" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Availability</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->availability -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>veterinarian->availability</title>
|
||||||
|
<path fill="none" stroke="black" d="M442.81,-429C340.11,-429 48,-429 48,-429 48,-429 48,-318.47 48,-318.47"/>
|
||||||
|
<polygon fill="black" stroke="black" points="51.5,-318.47 48,-308.47 44.5,-318.47 51.5,-318.47"/>
|
||||||
|
</g>
|
||||||
|
<!-- unavailability -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>unavailability</title>
|
||||||
|
<path fill="#ffcc80" stroke="black" d="M164.46,-306.6C164.46,-306.6 113.54,-306.6 113.54,-306.6 107.54,-306.6 101.54,-300.6 101.54,-294.6 101.54,-294.6 101.54,-282.6 101.54,-282.6 101.54,-276.6 107.54,-270.6 113.54,-270.6 113.54,-270.6 164.46,-270.6 164.46,-270.6 170.46,-270.6 176.46,-276.6 176.46,-282.6 176.46,-282.6 176.46,-294.6 176.46,-294.6 176.46,-300.6 170.46,-306.6 164.46,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="139" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Unavailability</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->unavailability -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>veterinarian->unavailability</title>
|
||||||
|
<path fill="none" stroke="black" d="M442.68,-420C355.85,-420 139,-420 139,-420 139,-420 139,-318.38 139,-318.38"/>
|
||||||
|
<polygon fill="black" stroke="black" points="142.5,-318.38 139,-308.38 135.5,-318.38 142.5,-318.38"/>
|
||||||
|
</g>
|
||||||
|
<!-- vet_specialty -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>vet_specialty</title>
|
||||||
|
<path fill="#ffb74d" stroke="black" d="M250.45,-303.34C250.45,-303.34 209.84,-291.86 209.84,-291.86 204.06,-290.23 204.06,-286.97 209.84,-285.34 209.84,-285.34 250.45,-273.86 250.45,-273.86 256.23,-272.23 267.77,-272.23 273.55,-273.86 273.55,-273.86 314.16,-285.34 314.16,-285.34 319.94,-286.97 319.94,-290.23 314.16,-291.86 314.16,-291.86 273.55,-303.34 273.55,-303.34 267.77,-304.97 256.23,-304.97 250.45,-303.34"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="262" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Vet-Specialty</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vet_specialty -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>veterinarian->vet_specialty</title>
|
||||||
|
<path fill="none" stroke="black" d="M442.82,-411C381.61,-411 262,-411 262,-411 262,-411 262,-318.44 262,-318.44"/>
|
||||||
|
<polygon fill="black" stroke="black" points="265.5,-318.44 262,-308.44 258.5,-318.44 265.5,-318.44"/>
|
||||||
|
</g>
|
||||||
|
<!-- vet_neighborhood -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>vet_neighborhood</title>
|
||||||
|
<path fill="#ffb74d" stroke="black" d="M418.27,-304.05C418.27,-304.05 359.09,-291.15 359.09,-291.15 353.23,-289.88 353.23,-287.32 359.09,-286.05 359.09,-286.05 418.27,-273.15 418.27,-273.15 424.14,-271.88 435.86,-271.88 441.73,-273.15 441.73,-273.15 500.91,-286.05 500.91,-286.05 506.77,-287.32 506.77,-289.88 500.91,-291.15 500.91,-291.15 441.73,-304.05 441.73,-304.05 435.86,-305.32 424.14,-305.32 418.27,-304.05"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="430" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Vet-Neighborhood</text>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->vet_neighborhood -->
|
||||||
|
<g id="edge15" class="edge">
|
||||||
|
<title>veterinarian->vet_neighborhood</title>
|
||||||
|
<path fill="none" stroke="black" d="M466.24,-402.07C466.24,-402.07 466.24,-310.63 466.24,-310.63"/>
|
||||||
|
<polygon fill="black" stroke="black" points="469.74,-310.63 466.24,-300.63 462.74,-310.63 469.74,-310.63"/>
|
||||||
|
</g>
|
||||||
|
<!-- veterinarian->servicerequest -->
|
||||||
|
<!-- specialty -->
|
||||||
|
<g id="node31" class="node">
|
||||||
|
<title>specialty</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1622.29,-306.6C1622.29,-306.6 1589.71,-306.6 1589.71,-306.6 1583.71,-306.6 1577.71,-300.6 1577.71,-294.6 1577.71,-294.6 1577.71,-282.6 1577.71,-282.6 1577.71,-276.6 1583.71,-270.6 1589.71,-270.6 1589.71,-270.6 1622.29,-270.6 1622.29,-270.6 1628.29,-270.6 1634.29,-276.6 1634.29,-282.6 1634.29,-282.6 1634.29,-294.6 1634.29,-294.6 1634.29,-300.6 1628.29,-306.6 1622.29,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1606" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Specialty</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_specialty->specialty -->
|
||||||
|
<g id="edge41" class="edge">
|
||||||
|
<title>vet_specialty->specialty</title>
|
||||||
|
<path fill="none" stroke="black" d="M262,-270.22C262,-263.27 262,-257 262,-257 262,-257 1591.73,-257 1591.73,-257 1591.73,-257 1591.73,-258.7 1591.73,-258.7"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1588.23,-258.7 1591.73,-268.7 1595.23,-258.7 1588.23,-258.7"/>
|
||||||
|
</g>
|
||||||
|
<!-- neighborhood -->
|
||||||
|
<g id="node32" class="node">
|
||||||
|
<title>neighborhood</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1289.41,-306.6C1289.41,-306.6 1234.59,-306.6 1234.59,-306.6 1228.59,-306.6 1222.59,-300.6 1222.59,-294.6 1222.59,-294.6 1222.59,-282.6 1222.59,-282.6 1222.59,-276.6 1228.59,-270.6 1234.59,-270.6 1234.59,-270.6 1289.41,-270.6 1289.41,-270.6 1295.41,-270.6 1301.41,-276.6 1301.41,-282.6 1301.41,-282.6 1301.41,-294.6 1301.41,-294.6 1301.41,-300.6 1295.41,-306.6 1289.41,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1262" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Neighborhood</text>
|
||||||
|
</g>
|
||||||
|
<!-- vet_neighborhood->neighborhood -->
|
||||||
|
<g id="edge42" class="edge">
|
||||||
|
<title>vet_neighborhood->neighborhood</title>
|
||||||
|
<path fill="none" stroke="black" d="M489.44,-293.99C489.44,-307 489.44,-339 489.44,-339 489.44,-339 1262,-339 1262,-339 1262,-339 1262,-318.59 1262,-318.59"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1265.5,-318.59 1262,-308.59 1258.5,-318.59 1265.5,-318.59"/>
|
||||||
|
</g>
|
||||||
|
<!-- grupo -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>grupo</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M2316,-702C2316,-702 2286,-702 2286,-702 2280,-702 2274,-696 2274,-690 2274,-690 2274,-678 2274,-678 2274,-672 2280,-666 2286,-666 2286,-666 2316,-666 2316,-666 2322,-666 2328,-672 2328,-678 2328,-678 2328,-690 2328,-690 2328,-696 2322,-702 2316,-702"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2301" y="-681" font-family="Helvetica,sans-Serif" font-size="10.00">Group</text>
|
||||||
|
</g>
|
||||||
|
<!-- category -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>category</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M2317.29,-570.2C2317.29,-570.2 2284.71,-570.2 2284.71,-570.2 2278.71,-570.2 2272.71,-564.2 2272.71,-558.2 2272.71,-558.2 2272.71,-546.2 2272.71,-546.2 2272.71,-540.2 2278.71,-534.2 2284.71,-534.2 2284.71,-534.2 2317.29,-534.2 2317.29,-534.2 2323.29,-534.2 2329.29,-540.2 2329.29,-546.2 2329.29,-546.2 2329.29,-558.2 2329.29,-558.2 2329.29,-564.2 2323.29,-570.2 2317.29,-570.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2301" y="-549.2" font-family="Helvetica,sans-Serif" font-size="10.00">Category</text>
|
||||||
|
</g>
|
||||||
|
<!-- grupo->category -->
|
||||||
|
<g id="edge16" class="edge">
|
||||||
|
<title>grupo->category</title>
|
||||||
|
<path fill="none" stroke="black" d="M2301,-665.67C2301,-665.67 2301,-581.88 2301,-581.88"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2304.5,-581.88 2301,-571.88 2297.5,-581.88 2304.5,-581.88"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2307.22" y="-615.8" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- service -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>service</title>
|
||||||
|
<path fill="#ce93d8" stroke="black" d="M2315,-438.4C2315,-438.4 2285,-438.4 2285,-438.4 2279,-438.4 2273,-432.4 2273,-426.4 2273,-426.4 2273,-414.4 2273,-414.4 2273,-408.4 2279,-402.4 2285,-402.4 2285,-402.4 2315,-402.4 2315,-402.4 2321,-402.4 2327,-408.4 2327,-414.4 2327,-414.4 2327,-426.4 2327,-426.4 2327,-432.4 2321,-438.4 2315,-438.4"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2300" y="-417.4" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||||
|
</g>
|
||||||
|
<!-- category->service -->
|
||||||
|
<g id="edge17" class="edge">
|
||||||
|
<title>category->service</title>
|
||||||
|
<path fill="none" stroke="black" d="M2300,-533.87C2300,-533.87 2300,-450.08 2300,-450.08"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2303.5,-450.08 2300,-440.08 2296.5,-450.08 2303.5,-450.08"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2306.22" y="-484" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- prices -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>prices</title>
|
||||||
|
<path fill="#ba68c8" stroke="black" d="M2315,-306.6C2315,-306.6 2285,-306.6 2285,-306.6 2279,-306.6 2273,-300.6 2273,-294.6 2273,-294.6 2273,-282.6 2273,-282.6 2273,-276.6 2279,-270.6 2285,-270.6 2285,-270.6 2315,-270.6 2315,-270.6 2321,-270.6 2327,-276.6 2327,-282.6 2327,-282.6 2327,-294.6 2327,-294.6 2327,-300.6 2321,-306.6 2315,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2300" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Prices</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->prices -->
|
||||||
|
<g id="edge18" class="edge">
|
||||||
|
<title>service->prices</title>
|
||||||
|
<path fill="none" stroke="black" d="M2309,-402.07C2309,-402.07 2309,-318.28 2309,-318.28"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2312.5,-318.28 2309,-308.28 2305.5,-318.28 2312.5,-318.28"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2306.22" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- discounts -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>discounts</title>
|
||||||
|
<path fill="#ba68c8" stroke="black" d="M2396.95,-306.6C2396.95,-306.6 2361.05,-306.6 2361.05,-306.6 2355.05,-306.6 2349.05,-300.6 2349.05,-294.6 2349.05,-294.6 2349.05,-282.6 2349.05,-282.6 2349.05,-276.6 2355.05,-270.6 2361.05,-270.6 2361.05,-270.6 2396.95,-270.6 2396.95,-270.6 2402.95,-270.6 2408.95,-276.6 2408.95,-282.6 2408.95,-282.6 2408.95,-294.6 2408.95,-294.6 2408.95,-300.6 2402.95,-306.6 2396.95,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2379" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Discounts</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->discounts -->
|
||||||
|
<g id="edge19" class="edge">
|
||||||
|
<title>service->discounts</title>
|
||||||
|
<path fill="none" stroke="black" d="M2327.19,-420C2350.04,-420 2379,-420 2379,-420 2379,-420 2379,-318.38 2379,-318.38"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2382.5,-318.38 2379,-308.38 2375.5,-318.38 2382.5,-318.38"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2365.22" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit -->
|
||||||
|
<g id="node25" class="node">
|
||||||
|
<title>vetvisit</title>
|
||||||
|
<path fill="#fff9c4" stroke="black" d="M2081,-306.6C2081,-306.6 2051,-306.6 2051,-306.6 2045,-306.6 2039,-300.6 2039,-294.6 2039,-294.6 2039,-282.6 2039,-282.6 2039,-276.6 2045,-270.6 2051,-270.6 2051,-270.6 2081,-270.6 2081,-270.6 2087,-270.6 2093,-276.6 2093,-282.6 2093,-282.6 2093,-294.6 2093,-294.6 2093,-300.6 2087,-306.6 2081,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2066" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisit</text>
|
||||||
|
</g>
|
||||||
|
<!-- service->vetvisit -->
|
||||||
|
<!-- cartitem -->
|
||||||
|
<g id="node17" class="node">
|
||||||
|
<title>cartitem</title>
|
||||||
|
<path fill="#80deea" stroke="black" d="M891.17,-306.6C891.17,-306.6 860.83,-306.6 860.83,-306.6 854.83,-306.6 848.83,-300.6 848.83,-294.6 848.83,-294.6 848.83,-282.6 848.83,-282.6 848.83,-276.6 854.83,-270.6 860.83,-270.6 860.83,-270.6 891.17,-270.6 891.17,-270.6 897.17,-270.6 903.17,-276.6 903.17,-282.6 903.17,-282.6 903.17,-294.6 903.17,-294.6 903.17,-300.6 897.17,-306.6 891.17,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="876" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">CartItem</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartitem -->
|
||||||
|
<g id="edge20" class="edge">
|
||||||
|
<title>cart->cartitem</title>
|
||||||
|
<path fill="none" stroke="black" d="M1068.78,-413C1011.66,-413 884.49,-413 884.49,-413 884.49,-413 884.49,-318.47 884.49,-318.47"/>
|
||||||
|
<polygon fill="black" stroke="black" points="887.99,-318.47 884.49,-308.47 880.99,-318.47 887.99,-318.47"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="924.22" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- cartresumeitem -->
|
||||||
|
<g id="node18" class="node">
|
||||||
|
<title>cartresumeitem</title>
|
||||||
|
<path fill="#80deea" stroke="black" d="M1004.79,-306.6C1004.79,-306.6 937.21,-306.6 937.21,-306.6 931.21,-306.6 925.21,-300.6 925.21,-294.6 925.21,-294.6 925.21,-282.6 925.21,-282.6 925.21,-276.6 931.21,-270.6 937.21,-270.6 937.21,-270.6 1004.79,-270.6 1004.79,-270.6 1010.79,-270.6 1016.79,-276.6 1016.79,-282.6 1016.79,-282.6 1016.79,-294.6 1016.79,-294.6 1016.79,-300.6 1010.79,-306.6 1004.79,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="971" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">CartResumeItem</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartresumeitem -->
|
||||||
|
<g id="edge21" class="edge">
|
||||||
|
<title>cart->cartresumeitem</title>
|
||||||
|
<path fill="none" stroke="black" d="M1068.5,-408C1036.87,-408 989.23,-408 989.23,-408 989.23,-408 989.23,-318.51 989.23,-318.51"/>
|
||||||
|
<polygon fill="black" stroke="black" points="992.73,-318.51 989.23,-308.51 985.73,-318.51 992.73,-318.51"/>
|
||||||
|
</g>
|
||||||
|
<!-- cartpetreason -->
|
||||||
|
<g id="node19" class="node">
|
||||||
|
<title>cartpetreason</title>
|
||||||
|
<path fill="#80deea" stroke="black" d="M1111.18,-306.6C1111.18,-306.6 1050.82,-306.6 1050.82,-306.6 1044.82,-306.6 1038.82,-300.6 1038.82,-294.6 1038.82,-294.6 1038.82,-282.6 1038.82,-282.6 1038.82,-276.6 1044.82,-270.6 1050.82,-270.6 1050.82,-270.6 1111.18,-270.6 1111.18,-270.6 1117.18,-270.6 1123.18,-276.6 1123.18,-282.6 1123.18,-282.6 1123.18,-294.6 1123.18,-294.6 1123.18,-300.6 1117.18,-306.6 1111.18,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1081" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">CartPetReason</text>
|
||||||
|
</g>
|
||||||
|
<!-- cart->cartpetreason -->
|
||||||
|
<g id="edge22" class="edge">
|
||||||
|
<title>cart->cartpetreason</title>
|
||||||
|
<path fill="none" stroke="black" d="M1096,-402.07C1096,-402.07 1096,-318.28 1096,-318.28"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1099.5,-318.28 1096,-308.28 1092.5,-318.28 1099.5,-318.28"/>
|
||||||
|
</g>
|
||||||
|
<!-- cart->specialty -->
|
||||||
|
<!-- cartitem->service -->
|
||||||
|
<g id="edge43" class="edge">
|
||||||
|
<title>cartitem->service</title>
|
||||||
|
<path fill="none" stroke="black" d="M866.66,-307.07C866.66,-331.09 866.66,-370 866.66,-370 866.66,-370 2291,-370 2291,-370 2291,-370 2291,-390.41 2291,-390.41"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2287.5,-390.41 2291,-400.41 2294.5,-390.41 2287.5,-390.41"/>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->cart -->
|
||||||
|
<g id="edge36" class="edge">
|
||||||
|
<title>servicerequest->cart</title>
|
||||||
|
<path fill="none" stroke="black" d="M753.65,-306.82C753.65,-344.61 753.65,-428 753.65,-428 753.65,-428 1057.05,-428 1057.05,-428"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1057.05,-431.5 1067.05,-428 1057.05,-424.5 1057.05,-431.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1349.56" y="-352.2" font-family="Helvetica,sans-Serif" font-size="8.00">1:1</text>
|
||||||
|
</g>
|
||||||
|
<!-- statehistory -->
|
||||||
|
<g id="node21" class="node">
|
||||||
|
<title>statehistory</title>
|
||||||
|
<path fill="#ef9a9a" stroke="black" d="M595.23,-175C595.23,-175 548.77,-175 548.77,-175 542.77,-175 536.77,-169 536.77,-163 536.77,-163 536.77,-151 536.77,-151 536.77,-145 542.77,-139 548.77,-139 548.77,-139 595.23,-139 595.23,-139 601.23,-139 607.23,-145 607.23,-151 607.23,-151 607.23,-163 607.23,-163 607.23,-169 601.23,-175 595.23,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="572" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">StateHistory</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->statehistory -->
|
||||||
|
<g id="edge23" class="edge">
|
||||||
|
<title>servicerequest->statehistory</title>
|
||||||
|
<path fill="none" stroke="black" d="M688.28,-289C640.91,-289 572,-289 572,-289 572,-289 572,-186.87 572,-186.87"/>
|
||||||
|
<polygon fill="black" stroke="black" points="575.5,-186.87 572,-176.87 568.5,-186.87 575.5,-186.87"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="635.22" y="-220.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked -->
|
||||||
|
<g id="node22" class="node">
|
||||||
|
<title>vetasked</title>
|
||||||
|
<path fill="#ef9a9a" stroke="black" d="M714.86,-175C714.86,-175 641.14,-175 641.14,-175 635.14,-175 629.14,-169 629.14,-163 629.14,-163 629.14,-151 629.14,-151 629.14,-145 635.14,-139 641.14,-139 641.14,-139 714.86,-139 714.86,-139 720.86,-139 726.86,-145 726.86,-151 726.86,-151 726.86,-163 726.86,-163 726.86,-169 720.86,-175 714.86,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="678" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">VeterinarianAsked</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->vetasked -->
|
||||||
|
<g id="edge24" class="edge">
|
||||||
|
<title>servicerequest->vetasked</title>
|
||||||
|
<path fill="none" stroke="black" d="M707.78,-270.3C707.78,-270.3 707.78,-186.66 707.78,-186.66"/>
|
||||||
|
<polygon fill="black" stroke="black" points="711.28,-186.66 707.78,-176.66 704.28,-186.66 711.28,-186.66"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="707.22" y="-220.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- payreminder -->
|
||||||
|
<g id="node24" class="node">
|
||||||
|
<title>payreminder</title>
|
||||||
|
<path fill="#e57373" stroke="black" d="M813.29,-175C813.29,-175 760.71,-175 760.71,-175 754.71,-175 748.71,-169 748.71,-163 748.71,-163 748.71,-151 748.71,-151 748.71,-145 754.71,-139 760.71,-139 760.71,-139 813.29,-139 813.29,-139 819.29,-139 825.29,-145 825.29,-151 825.29,-151 825.29,-163 825.29,-163 825.29,-169 819.29,-175 813.29,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="787" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">PayReminder</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->payreminder -->
|
||||||
|
<g id="edge25" class="edge">
|
||||||
|
<title>servicerequest->payreminder</title>
|
||||||
|
<path fill="none" stroke="black" d="M757.57,-270.3C757.57,-270.3 757.57,-186.66 757.57,-186.66"/>
|
||||||
|
<polygon fill="black" stroke="black" points="761.07,-186.66 757.57,-176.66 754.07,-186.66 761.07,-186.66"/>
|
||||||
|
</g>
|
||||||
|
<!-- campaign -->
|
||||||
|
<g id="node38" class="node">
|
||||||
|
<title>campaign</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1196.79,-175C1196.79,-175 1159.21,-175 1159.21,-175 1153.21,-175 1147.21,-169 1147.21,-163 1147.21,-163 1147.21,-151 1147.21,-151 1147.21,-145 1153.21,-139 1159.21,-139 1159.21,-139 1196.79,-139 1196.79,-139 1202.79,-139 1208.79,-145 1208.79,-151 1208.79,-151 1208.79,-163 1208.79,-163 1208.79,-169 1202.79,-175 1196.79,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1178" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">Campaign</text>
|
||||||
|
</g>
|
||||||
|
<!-- servicerequest->campaign -->
|
||||||
|
<!-- vetasked->veterinarian -->
|
||||||
|
<g id="edge37" class="edge">
|
||||||
|
<title>vetasked->veterinarian</title>
|
||||||
|
<path fill="none" stroke="black" d="M658.92,-175.35C658.92,-234.76 658.92,-418 658.92,-418 658.92,-418 524.83,-418 524.83,-418"/>
|
||||||
|
<polygon fill="black" stroke="black" points="524.83,-414.5 514.83,-418 524.83,-421.5 524.83,-414.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- vetreminder -->
|
||||||
|
<g id="node23" class="node">
|
||||||
|
<title>vetreminder</title>
|
||||||
|
<path fill="#e57373" stroke="black" d="M703.18,-52C703.18,-52 652.82,-52 652.82,-52 646.82,-52 640.82,-46 640.82,-40 640.82,-40 640.82,-28 640.82,-28 640.82,-22 646.82,-16 652.82,-16 652.82,-16 703.18,-16 703.18,-16 709.18,-16 715.18,-22 715.18,-28 715.18,-28 715.18,-40 715.18,-40 715.18,-46 709.18,-52 703.18,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="678" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00">VetReminder</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetasked->vetreminder -->
|
||||||
|
<g id="edge26" class="edge">
|
||||||
|
<title>vetasked->vetreminder</title>
|
||||||
|
<path fill="none" stroke="black" d="M678,-138.59C678,-138.59 678,-63.65 678,-63.65"/>
|
||||||
|
<polygon fill="black" stroke="black" points="681.5,-63.65 678,-53.65 674.5,-63.65 681.5,-63.65"/>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->servicerequest -->
|
||||||
|
<g id="edge38" class="edge">
|
||||||
|
<title>vetvisit->servicerequest</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M2042.05,-270.38C2042.05,-253.18 2042.05,-230 2042.05,-230 2042.05,-230 766.43,-230 766.43,-230 766.43,-230 766.43,-258.87 766.43,-258.87"/>
|
||||||
|
<polygon fill="black" stroke="black" points="762.93,-258.87 766.43,-268.87 769.93,-258.87 762.93,-258.87"/>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport -->
|
||||||
|
<g id="node26" class="node">
|
||||||
|
<title>vetvisitreport</title>
|
||||||
|
<path fill="#fff59d" stroke="black" d="M1932.96,-175C1932.96,-175 1877.04,-175 1877.04,-175 1871.04,-175 1865.04,-169 1865.04,-163 1865.04,-163 1865.04,-151 1865.04,-151 1865.04,-145 1871.04,-139 1877.04,-139 1877.04,-139 1932.96,-139 1932.96,-139 1938.96,-139 1944.96,-145 1944.96,-151 1944.96,-151 1944.96,-163 1944.96,-163 1944.96,-169 1938.96,-175 1932.96,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1905" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">VetVisitReport</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->vetvisitreport -->
|
||||||
|
<g id="edge28" class="edge">
|
||||||
|
<title>vetvisit->vetvisitreport</title>
|
||||||
|
<path fill="none" stroke="black" d="M2038.69,-289C1992.75,-289 1905,-289 1905,-289 1905,-289 1905,-186.87 1905,-186.87"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1908.5,-186.87 1905,-176.87 1901.5,-186.87 1908.5,-186.87"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1981.22" y="-220.4" font-family="Helvetica,sans-Serif" font-size="8.00">1:N</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitpetreason -->
|
||||||
|
<g id="node28" class="node">
|
||||||
|
<title>vetvisitpetreason</title>
|
||||||
|
<path fill="#fff176" stroke="black" d="M2039.18,-175C2039.18,-175 1978.82,-175 1978.82,-175 1972.82,-175 1966.82,-169 1966.82,-163 1966.82,-163 1966.82,-151 1966.82,-151 1966.82,-145 1972.82,-139 1978.82,-139 1978.82,-139 2039.18,-139 2039.18,-139 2045.18,-139 2051.18,-145 2051.18,-151 2051.18,-151 2051.18,-163 2051.18,-163 2051.18,-169 2045.18,-175 2039.18,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2009" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">VisitPetReason</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->vetvisitpetreason -->
|
||||||
|
<g id="edge29" class="edge">
|
||||||
|
<title>vetvisit->vetvisitpetreason</title>
|
||||||
|
<path fill="none" stroke="black" d="M2048.14,-270.3C2048.14,-270.3 2048.14,-186.66 2048.14,-186.66"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2051.64,-186.66 2048.14,-176.66 2044.64,-186.66 2051.64,-186.66"/>
|
||||||
|
</g>
|
||||||
|
<!-- visit_pets -->
|
||||||
|
<g id="node29" class="node">
|
||||||
|
<title>visit_pets</title>
|
||||||
|
<path fill="#ffee58" stroke="black" d="M2111.72,-170.9C2111.72,-170.9 2084.76,-161.1 2084.76,-161.1 2079.12,-159.05 2079.12,-154.95 2084.76,-152.9 2084.76,-152.9 2111.72,-143.1 2111.72,-143.1 2117.36,-141.05 2128.64,-141.05 2134.28,-143.1 2134.28,-143.1 2161.24,-152.9 2161.24,-152.9 2166.88,-154.95 2166.88,-159.05 2161.24,-161.1 2161.24,-161.1 2134.28,-170.9 2134.28,-170.9 2128.64,-172.95 2117.36,-172.95 2111.72,-170.9"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2123" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">Visit-Pets</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->visit_pets -->
|
||||||
|
<g id="edge27" class="edge">
|
||||||
|
<title>vetvisit->visit_pets</title>
|
||||||
|
<path fill="none" stroke="black" d="M2083.24,-270.3C2083.24,-270.3 2083.24,-172.32 2083.24,-172.32"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2086.74,-172.32 2083.24,-162.32 2079.74,-172.32 2086.74,-172.32"/>
|
||||||
|
</g>
|
||||||
|
<!-- receipt -->
|
||||||
|
<g id="node30" class="node">
|
||||||
|
<title>receipt</title>
|
||||||
|
<path fill="#f8bbd9" stroke="black" d="M2237,-175C2237,-175 2207,-175 2207,-175 2201,-175 2195,-169 2195,-163 2195,-163 2195,-151 2195,-151 2195,-145 2201,-139 2207,-139 2207,-139 2237,-139 2237,-139 2243,-139 2249,-145 2249,-151 2249,-151 2249,-163 2249,-163 2249,-169 2243,-175 2237,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2222" y="-160" font-family="Helvetica,sans-Serif" font-size="10.00">Receipt</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="2222" y="-148" font-family="Helvetica,sans-Serif" font-size="10.00">(AFIP)</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->receipt -->
|
||||||
|
<g id="edge31" class="edge">
|
||||||
|
<title>vetvisit->receipt</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M2093.17,-289C2137.87,-289 2222,-289 2222,-289 2222,-289 2222,-186.87 2222,-186.87"/>
|
||||||
|
<polygon fill="black" stroke="black" points="2225.5,-186.87 2222,-176.87 2218.5,-186.87 2225.5,-186.87"/>
|
||||||
|
</g>
|
||||||
|
<!-- turnfeegroup -->
|
||||||
|
<g id="node41" class="node">
|
||||||
|
<title>turnfeegroup</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1621.79,-175C1621.79,-175 1564.21,-175 1564.21,-175 1558.21,-175 1552.21,-169 1552.21,-163 1552.21,-163 1552.21,-151 1552.21,-151 1552.21,-145 1558.21,-139 1564.21,-139 1564.21,-139 1621.79,-139 1621.79,-139 1627.79,-139 1633.79,-145 1633.79,-151 1633.79,-151 1633.79,-163 1633.79,-163 1633.79,-169 1627.79,-175 1621.79,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1593" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">TurnFeeGroup</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisit->turnfeegroup -->
|
||||||
|
<!-- vetvisitfollowup -->
|
||||||
|
<g id="node27" class="node">
|
||||||
|
<title>vetvisitfollowup</title>
|
||||||
|
<path fill="#fff176" stroke="black" d="M1921.84,-52C1921.84,-52 1888.16,-52 1888.16,-52 1882.16,-52 1876.16,-46 1876.16,-40 1876.16,-40 1876.16,-28 1876.16,-28 1876.16,-22 1882.16,-16 1888.16,-16 1888.16,-16 1921.84,-16 1921.84,-16 1927.84,-16 1933.84,-22 1933.84,-28 1933.84,-28 1933.84,-40 1933.84,-40 1933.84,-46 1927.84,-52 1921.84,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1905" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00">FollowUp</text>
|
||||||
|
</g>
|
||||||
|
<!-- vetvisitreport->vetvisitfollowup -->
|
||||||
|
<g id="edge30" class="edge">
|
||||||
|
<title>vetvisitreport->vetvisitfollowup</title>
|
||||||
|
<path fill="none" stroke="black" d="M1905,-138.59C1905,-138.59 1905,-63.65 1905,-63.65"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1908.5,-63.65 1905,-53.65 1901.5,-63.65 1908.5,-63.65"/>
|
||||||
|
</g>
|
||||||
|
<!-- specialty->study -->
|
||||||
|
<!-- province -->
|
||||||
|
<g id="node33" class="node">
|
||||||
|
<title>province</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1366.45,-306.6C1366.45,-306.6 1335.55,-306.6 1335.55,-306.6 1329.55,-306.6 1323.55,-300.6 1323.55,-294.6 1323.55,-294.6 1323.55,-282.6 1323.55,-282.6 1323.55,-276.6 1329.55,-270.6 1335.55,-270.6 1335.55,-270.6 1366.45,-270.6 1366.45,-270.6 1372.45,-270.6 1378.45,-276.6 1378.45,-282.6 1378.45,-282.6 1378.45,-294.6 1378.45,-294.6 1378.45,-300.6 1372.45,-306.6 1366.45,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1351" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Province</text>
|
||||||
|
</g>
|
||||||
|
<!-- locality -->
|
||||||
|
<g id="node34" class="node">
|
||||||
|
<title>locality</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1464,-306.6C1464,-306.6 1434,-306.6 1434,-306.6 1428,-306.6 1422,-300.6 1422,-294.6 1422,-294.6 1422,-282.6 1422,-282.6 1422,-276.6 1428,-270.6 1434,-270.6 1434,-270.6 1464,-270.6 1464,-270.6 1470,-270.6 1476,-276.6 1476,-282.6 1476,-282.6 1476,-294.6 1476,-294.6 1476,-300.6 1470,-306.6 1464,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1449" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">Locality</text>
|
||||||
|
</g>
|
||||||
|
<!-- province->locality -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>province->locality</title>
|
||||||
|
<path fill="none" stroke="black" d="M1378.82,-289C1378.82,-289 1410.27,-289 1410.27,-289"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1410.27,-292.5 1420.27,-289 1410.27,-285.5 1410.27,-292.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- petbreed -->
|
||||||
|
<g id="node35" class="node">
|
||||||
|
<title>petbreed</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1543.85,-306.6C1543.85,-306.6 1510.15,-306.6 1510.15,-306.6 1504.15,-306.6 1498.15,-300.6 1498.15,-294.6 1498.15,-294.6 1498.15,-282.6 1498.15,-282.6 1498.15,-276.6 1504.15,-270.6 1510.15,-270.6 1510.15,-270.6 1543.85,-270.6 1543.85,-270.6 1549.85,-270.6 1555.85,-276.6 1555.85,-282.6 1555.85,-282.6 1555.85,-294.6 1555.85,-294.6 1555.85,-300.6 1549.85,-306.6 1543.85,-306.6"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1527" y="-285.6" font-family="Helvetica,sans-Serif" font-size="10.00">PetBreed</text>
|
||||||
|
</g>
|
||||||
|
<!-- tag -->
|
||||||
|
<g id="node39" class="node">
|
||||||
|
<title>tag</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1432,-175C1432,-175 1402,-175 1402,-175 1396,-175 1390,-169 1390,-163 1390,-163 1390,-151 1390,-151 1390,-145 1396,-139 1402,-139 1402,-139 1432,-139 1432,-139 1438,-139 1444,-145 1444,-151 1444,-151 1444,-163 1444,-163 1444,-169 1438,-175 1432,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1417" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">Tag</text>
|
||||||
|
</g>
|
||||||
|
<!-- medication -->
|
||||||
|
<g id="node40" class="node">
|
||||||
|
<title>medication</title>
|
||||||
|
<path fill="#cfd8dc" stroke="black" d="M1518.18,-175C1518.18,-175 1477.82,-175 1477.82,-175 1471.82,-175 1465.82,-169 1465.82,-163 1465.82,-163 1465.82,-151 1465.82,-151 1465.82,-145 1471.82,-139 1477.82,-139 1477.82,-139 1518.18,-139 1518.18,-139 1524.18,-139 1530.18,-145 1530.18,-151 1530.18,-151 1530.18,-163 1530.18,-163 1530.18,-169 1524.18,-175 1518.18,-175"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1498" y="-154" font-family="Helvetica,sans-Serif" font-size="10.00">Medication</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 43 KiB |
120
atlas/book/arch-model/graph.html
Normal file
120
atlas/book/arch-model/graph.html
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Graph Viewer - AMAR Mascotas</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body class="graph-viewer">
|
||||||
|
<header class="graph-header">
|
||||||
|
<a href="index.html" class="back-link">← Index</a>
|
||||||
|
<div class="nav-controls">
|
||||||
|
<button onclick="navigate(-1)" id="btn-prev" title="Previous (←)">◀</button>
|
||||||
|
<span id="nav-position">1 / 4</span>
|
||||||
|
<button onclick="navigate(1)" id="btn-next" title="Next (→)">▶</button>
|
||||||
|
</div>
|
||||||
|
<h1 id="graph-title">Loading...</h1>
|
||||||
|
<div class="graph-controls">
|
||||||
|
<button onclick="setMode('fit')">Fit</button>
|
||||||
|
<button onclick="setMode('fit-width')">Width</button>
|
||||||
|
<button onclick="setMode('fit-height')">Height</button>
|
||||||
|
<button onclick="setMode('actual-size')">100%</button>
|
||||||
|
<button onclick="downloadSvg()">↓ SVG</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="graph-container" id="graph-container">
|
||||||
|
<img id="graph-img" src="" alt="Graph">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const graphOrder = [
|
||||||
|
'04-data-model-simple',
|
||||||
|
'01-backend-architecture',
|
||||||
|
'02-frontend-architecture',
|
||||||
|
'03-data-model'
|
||||||
|
];
|
||||||
|
|
||||||
|
const graphs = {
|
||||||
|
'04-data-model-simple': {
|
||||||
|
title: 'Data Model Overview',
|
||||||
|
file: '04-data-model-simple.svg'
|
||||||
|
},
|
||||||
|
'01-backend-architecture': {
|
||||||
|
title: 'Backend Architecture',
|
||||||
|
file: '01-backend-architecture.svg'
|
||||||
|
},
|
||||||
|
'02-frontend-architecture': {
|
||||||
|
title: 'Frontend Architecture',
|
||||||
|
file: '02-frontend-architecture.svg'
|
||||||
|
},
|
||||||
|
'03-data-model': {
|
||||||
|
title: 'Detailed Data Model',
|
||||||
|
file: '03-data-model.svg'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
let graphKey = params.get('g') || '04-data-model-simple';
|
||||||
|
let currentIndex = graphOrder.indexOf(graphKey);
|
||||||
|
if (currentIndex === -1) currentIndex = 0;
|
||||||
|
|
||||||
|
function loadGraph(key) {
|
||||||
|
const graph = graphs[key];
|
||||||
|
document.getElementById('graph-title').textContent = graph.title;
|
||||||
|
document.getElementById('graph-img').src = graph.file;
|
||||||
|
document.title = graph.title + ' - AMAR Mascotas';
|
||||||
|
history.replaceState(null, '', '?g=' + key);
|
||||||
|
graphKey = key;
|
||||||
|
updateNavHints();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNavHints() {
|
||||||
|
const idx = graphOrder.indexOf(graphKey);
|
||||||
|
const prevBtn = document.getElementById('btn-prev');
|
||||||
|
const nextBtn = document.getElementById('btn-next');
|
||||||
|
prevBtn.disabled = idx === 0;
|
||||||
|
nextBtn.disabled = idx === graphOrder.length - 1;
|
||||||
|
document.getElementById('nav-position').textContent = (idx + 1) + ' / ' + graphOrder.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigate(direction) {
|
||||||
|
const idx = graphOrder.indexOf(graphKey);
|
||||||
|
const newIdx = idx + direction;
|
||||||
|
if (newIdx >= 0 && newIdx < graphOrder.length) {
|
||||||
|
currentIndex = newIdx;
|
||||||
|
loadGraph(graphOrder[newIdx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMode(mode) {
|
||||||
|
const container = document.getElementById('graph-container');
|
||||||
|
container.className = 'graph-container ' + mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadSvg() {
|
||||||
|
const graph = graphs[graphKey];
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = graph.file;
|
||||||
|
link.download = graph.file;
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
navigate(-1);
|
||||||
|
} else if (e.key === 'ArrowRight') {
|
||||||
|
navigate(1);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
loadGraph(graphOrder[currentIndex]);
|
||||||
|
setMode('fit');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
245
atlas/book/arch-model/index.html
Normal file
245
atlas/book/arch-model/index.html
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AMAR Mascotas - Architecture & Data Models</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>AMAR Mascotas</h1>
|
||||||
|
<p class="subtitle">Architecture & Data Model Documentation</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- Graph Sections -->
|
||||||
|
<section class="graph-section" id="data-model-simple">
|
||||||
|
<div class="graph-header-row">
|
||||||
|
<h2>Data Model Overview</h2>
|
||||||
|
<a href="graph.html?g=04-data-model-simple" class="view-btn">View Full</a>
|
||||||
|
</div>
|
||||||
|
<a href="graph.html?g=04-data-model-simple" class="graph-preview">
|
||||||
|
<img src="04-data-model-simple.svg" alt="Data Model Overview">
|
||||||
|
</a>
|
||||||
|
<div class="graph-details">
|
||||||
|
<p>High-level entity relationships without field details. Shows the main actors, workflow, and data flow.</p>
|
||||||
|
<h4>Clusters</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Users & Auth</strong>: Django auth.User as central identity</li>
|
||||||
|
<li><strong>Pet Owners & Pets</strong>: Clients, their pets, vaccines, studies</li>
|
||||||
|
<li><strong>Veterinarians</strong>: Vets with availability, specialties, coverage areas</li>
|
||||||
|
<li><strong>Services & Pricing</strong>: Service catalog with dynamic pricing</li>
|
||||||
|
<li><strong>Cart & Checkout</strong>: Shopping cart workflow</li>
|
||||||
|
<li><strong>Service Requests</strong>: Order lifecycle with state machine</li>
|
||||||
|
<li><strong>Veterinary Visits</strong>: Scheduled visits, reports, AFIP invoicing</li>
|
||||||
|
<li><strong>Reference Data</strong>: Lookups (specialties, neighborhoods, vaccines, etc.)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="graph-section" id="backend">
|
||||||
|
<div class="graph-header-row">
|
||||||
|
<h2>Backend Architecture</h2>
|
||||||
|
<a href="graph.html?g=01-backend-architecture" class="view-btn">View Full</a>
|
||||||
|
</div>
|
||||||
|
<a href="graph.html?g=01-backend-architecture" class="graph-preview">
|
||||||
|
<img src="01-backend-architecture.svg" alt="Backend Architecture">
|
||||||
|
</a>
|
||||||
|
<div class="graph-details">
|
||||||
|
<p>Django apps structure: mascotas, productos, solicitudes, common, payments, and external integrations.</p>
|
||||||
|
|
||||||
|
<h4>Celery Tasks</h4>
|
||||||
|
<table class="details-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Task</th><th>App</th><th>Purpose</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>send_veterinarian_followup</code></td>
|
||||||
|
<td>solicitudes</td>
|
||||||
|
<td>Re-sends availability request to vet if still pending</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>run_payment_reminder_cron</code></td>
|
||||||
|
<td>solicitudes</td>
|
||||||
|
<td>Cron job to send payment reminders</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>create_vetvisit_in_sheet</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Creates row in Google Sheets for visit</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>update_vetvisit_in_sheet</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Updates Google Sheets row</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>create_event_calendar_vetvisit</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Creates Google Calendar event</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>update_event_calendar_vetvisit</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Updates Google Calendar event</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>create_user_owner</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Creates Django user for PetOwner + welcome email</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>generate_vetvisit_invoice</code></td>
|
||||||
|
<td>mascotas</td>
|
||||||
|
<td>Generates AFIP invoice and PDF</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>fetch_mp_notification_details</code></td>
|
||||||
|
<td>payments</td>
|
||||||
|
<td>Fetches MercadoPago webhook details</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="note">Celery handles async operations: external APIs (Google, MercadoPago, AFIP), scheduled reminders, and heavy processing (invoices, emails).</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="graph-section" id="frontend">
|
||||||
|
<div class="graph-header-row">
|
||||||
|
<h2>Frontend Architecture</h2>
|
||||||
|
<a href="graph.html?g=02-frontend-architecture" class="view-btn">View Full</a>
|
||||||
|
</div>
|
||||||
|
<a href="graph.html?g=02-frontend-architecture" class="graph-preview">
|
||||||
|
<img src="02-frontend-architecture.svg" alt="Frontend Architecture">
|
||||||
|
</a>
|
||||||
|
<div class="graph-details">
|
||||||
|
<p>Next.js 13+ App Router structure with public pages, backoffice, shared components, and services layer.</p>
|
||||||
|
<h4>Key Areas</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Public Pages</strong>: Landing, service catalog, booking flow</li>
|
||||||
|
<li><strong>Backoffice</strong>: Role-based dashboards (admin, vet, petowner)</li>
|
||||||
|
<li><strong>Services Layer</strong>: API clients for backend communication</li>
|
||||||
|
<li><strong>State Management</strong>: Redux store for cart, auth, UI state</li>
|
||||||
|
<li><strong>Shared Components</strong>: Forms, tables, modals, navigation</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="graph-section" id="data-model-detailed">
|
||||||
|
<div class="graph-header-row">
|
||||||
|
<h2>Detailed Data Model</h2>
|
||||||
|
<a href="graph.html?g=03-data-model" class="view-btn">View Full</a>
|
||||||
|
</div>
|
||||||
|
<a href="graph.html?g=03-data-model" class="graph-preview">
|
||||||
|
<img src="03-data-model.svg" alt="Detailed Data Model">
|
||||||
|
</a>
|
||||||
|
<div class="graph-details">
|
||||||
|
<p>Complete entity-relationship diagram with all fields, types, and relationships.</p>
|
||||||
|
<h4>Data Patterns</h4>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Soft delete</strong>: <code>deleted</code> flag on most models</li>
|
||||||
|
<li><strong>Audit trail</strong>: <code>StateHistory</code> for service requests</li>
|
||||||
|
<li><strong>Geographic</strong>: PostGIS polygons for coverage areas</li>
|
||||||
|
<li><strong>Versioned pricing</strong>: Date ranges on <code>Prices</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Findings Section -->
|
||||||
|
<section class="findings-section">
|
||||||
|
<h2>Key Findings</h2>
|
||||||
|
|
||||||
|
<div class="findings-grid">
|
||||||
|
<article class="finding-card">
|
||||||
|
<h3>User Types</h3>
|
||||||
|
<p>All users connect to <code>auth.User</code>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>PetOwner</strong>: Optional 1:1 link (created lazily)</li>
|
||||||
|
<li><strong>Veterinarian</strong>: Required 1:1 link to User</li>
|
||||||
|
<li><strong>Staff</strong>: Direct Django users with <code>is_staff=True</code></li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding-card">
|
||||||
|
<h3>Core Workflow</h3>
|
||||||
|
<div class="workflow-diagram">
|
||||||
|
<code>PetOwner → Cart → ServiceRequest → VeterinarianAsked → VetVisit → Report</code>
|
||||||
|
</div>
|
||||||
|
<p><strong>State Machine:</strong></p>
|
||||||
|
<div class="state-flow">
|
||||||
|
pending → vet_asked → vet_accepted → coordinated → payed → Confirmado
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding-card">
|
||||||
|
<h3>Pricing Logic</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Base price × <code>neighborhood.distance_coefficient</code></li>
|
||||||
|
<li>Optional vet-specific pricing via <code>Prices.veterinarian_id</code></li>
|
||||||
|
<li>Turn fee surcharge via <code>IndividualTurnFeeGroup</code></li>
|
||||||
|
<li>Time-based discounts via <code>Discounts</code> model</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="finding-card">
|
||||||
|
<h3>External Integrations</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>MercadoPago</strong>: Payment processing</li>
|
||||||
|
<li><strong>Google Calendar</strong>: Visit synchronization</li>
|
||||||
|
<li><strong>Google Sheets</strong>: Visit tracking spreadsheet</li>
|
||||||
|
<li><strong>Mercately</strong>: WhatsApp notifications</li>
|
||||||
|
<li><strong>AFIP</strong>: Argentine tax invoicing</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Tech Stack Section -->
|
||||||
|
<section class="tech-section">
|
||||||
|
<h2>Technology Stack</h2>
|
||||||
|
<div class="tech-grid">
|
||||||
|
<div class="tech-column">
|
||||||
|
<h3>Backend</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Django 4.x</li>
|
||||||
|
<li>Django REST Framework</li>
|
||||||
|
<li>PostgreSQL + PostGIS</li>
|
||||||
|
<li>Celery (Redis)</li>
|
||||||
|
<li>JWT Authentication</li>
|
||||||
|
<li>django-afip</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tech-column">
|
||||||
|
<h3>Frontend</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Next.js 13+ (App Router)</li>
|
||||||
|
<li>React 18+</li>
|
||||||
|
<li>TypeScript</li>
|
||||||
|
<li>Redux</li>
|
||||||
|
<li>Axios</li>
|
||||||
|
<li>Tailwind CSS</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tech-column">
|
||||||
|
<h3>Infrastructure</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Docker</li>
|
||||||
|
<li>Nginx</li>
|
||||||
|
<li>AWS S3 (storage)</li>
|
||||||
|
<li>MercadoPago API</li>
|
||||||
|
<li>Google APIs</li>
|
||||||
|
<li>WhatsApp Business</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>AMAR Mascotas Architecture Documentation</p>
|
||||||
|
<p class="date">Generated: <time datetime="2024-12-14">December 2024</time></p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
565
atlas/book/arch-model/styles.css
Normal file
565
atlas/book/arch-model/styles.css
Normal file
@@ -0,0 +1,565 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
300
atlas/book/drive-index/index.html
Normal file
300
atlas/book/drive-index/index.html
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Drive Index - Amar Mascotas</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
.container { max-width: 900px; margin: 0 auto; }
|
||||||
|
.back { font-size: 0.85rem; margin-bottom: 1rem; }
|
||||||
|
.back a { color: #6366f1; text-decoration: none; }
|
||||||
|
.back a:hover { text-decoration: underline; }
|
||||||
|
header {
|
||||||
|
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
header h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
||||||
|
header p { opacity: 0.9; font-size: 0.85rem; }
|
||||||
|
.stats { display: flex; gap: 0.75rem; margin-top: 0.75rem; flex-wrap: wrap; }
|
||||||
|
.stat { background: rgba(255,255,255,0.2); padding: 0.35rem 0.75rem; border-radius: 6px; font-size: 0.8rem; }
|
||||||
|
.notice {
|
||||||
|
background: #fef3c7;
|
||||||
|
border: 1px solid #fcd34d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
.notice strong { display: block; margin-bottom: 0.5rem; }
|
||||||
|
.notice ul { margin: 0.5rem 0 0 1.25rem; }
|
||||||
|
.notice li { margin-bottom: 0.25rem; }
|
||||||
|
|
||||||
|
details {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
summary::-webkit-details-marker { display: none; }
|
||||||
|
summary:hover { background: #f9fafb; border-radius: 8px; }
|
||||||
|
summary .icon {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6366f1;
|
||||||
|
transition: transform 0.15s;
|
||||||
|
}
|
||||||
|
details[open] summary .icon { transform: rotate(90deg); }
|
||||||
|
summary .name { font-weight: 500; font-size: 0.9rem; flex: 1; }
|
||||||
|
summary .count {
|
||||||
|
background: #e0e7ff;
|
||||||
|
color: #4f46e5;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.folder-content {
|
||||||
|
padding: 0.5rem 1rem 0.75rem 2rem;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.folder-desc {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.file {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.file-ext {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0.15rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
min-width: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.file-ext.xlsx { background: #dcfce7; color: #166534; }
|
||||||
|
.file-ext.docx { background: #dbeafe; color: #1e40af; }
|
||||||
|
.file-ext.pdf { background: #fee2e2; color: #991b1b; }
|
||||||
|
.file-ext.png, .file-ext.jpg { background: #fef3c7; color: #92400e; }
|
||||||
|
.file-ext.mov, .file-ext.mp4 { background: #f3e8ff; color: #7c3aed; }
|
||||||
|
.file-ext.pptx { background: #ffedd5; color: #c2410c; }
|
||||||
|
.file-ext.ttf { background: #f1f5f9; color: #475569; }
|
||||||
|
|
||||||
|
footer { text-align: center; padding: 2rem; color: #999; font-size: 0.75rem; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="back"><a href="/">← Album</a></div>
|
||||||
|
<header>
|
||||||
|
<h1>Drive Index</h1>
|
||||||
|
<p>Amar Mascotas - Company Drive Structure</p>
|
||||||
|
<div class="stats">
|
||||||
|
<span class="stat">12 folders</span>
|
||||||
|
<span class="stat">~235 files</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="notice">
|
||||||
|
<strong>Preview Mode</strong>
|
||||||
|
This shows the Drive folder structure with placeholder filenames. Real file data will be available once <strong>Google Vein</strong> (Google Drive sync) is implemented in Artery:
|
||||||
|
<ul>
|
||||||
|
<li>Auto-sync folder structure and file metadata</li>
|
||||||
|
<li>Direct links to files (requires Google Workspace login)</li>
|
||||||
|
<li>Live updates when Drive contents change</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">01. Identidad</span><span class="count">3 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Brand assets, logos, typography</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext png">png</span> logo_main.png</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> logo_alt.png</div>
|
||||||
|
<div class="file"><span class="file-ext ttf">ttf</span> brand_font.ttf</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">02. Marketing Contenidos</span><span class="count">8 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Social media content, templates, testimonials</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> social_template.docx</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> post_image1.png</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> post_image2.png</div>
|
||||||
|
<div class="file"><span class="file-ext mov">mov</span> event_video.mov</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> testimonials.png</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">03. Marketing Growth</span><span class="count">8 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Paid media campaigns, performance dashboards</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> dashboard_report.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> ad_1080x1080.png</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> ad_1200x628.png</div>
|
||||||
|
<div class="file"><span class="file-ext png">png</span> ad_stories.png</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">05. ATC - Operaciones</span><span class="count">12 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Process manuals, workflows, support docs</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> manual_procesos.docx</div>
|
||||||
|
<div class="file"><span class="file-ext jpg">jpg</span> workflow_diagram.jpg</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> contacts_list.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> postal_codes.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> tasks_tracker.xlsx</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">06. Supply</span><span class="count">7 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Vet network, lab partnerships, onboarding</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> lab_prices.pdf</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> vet_onboarding.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> vet_cv_sample.pdf</div>
|
||||||
|
<div class="file"><span class="file-ext mp4">mp4</span> meeting_recording.mp4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">07. Finanzas</span><span class="count">7 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Invoices, accounting, finance tracking</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> finance_tracker.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> invoice_may.pdf</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> invoice_jun.pdf</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> invoice_jul.pdf</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">08. IT y Producto</span><span class="count">57 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Product specs, feature docs, references</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> product_index.docx</div>
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> feature_specs.docx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> platform_states.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> medications_db.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> events_mapping.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext pptx">pptx</span> benchmark.pptx</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">09. DATA</span><span class="count">2 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Database exports, data backups</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> pets_database.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> pets_backup.xlsx</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">Clientes - ventas</span><span class="count">22 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">CRM exports, sales data, client info</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> crm_export.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> churn_analysis.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> leads_2023.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> leads_2024.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> monthly_report.pdf</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">HR</span><span class="count">17 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Onboarding, job profiles, team docs</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext pptx">pptx</span> onboarding_deck.pptx</div>
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> job_profile_sales.docx</div>
|
||||||
|
<div class="file"><span class="file-ext pdf">pdf</span> job_profile_growth.pdf</div>
|
||||||
|
<div class="file"><span class="file-ext jpg">jpg</span> team_photo.jpg</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">Pitch Decks</span><span class="count">24 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Investor presentations, company decks</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext pptx">pptx</span> pitch_deck_latest.pptx</div>
|
||||||
|
<div class="file"><span class="file-ext docx">docx</span> video_script.docx</div>
|
||||||
|
<div class="file"><span class="file-ext pptx">pptx</span> product_timeline.pptx</div>
|
||||||
|
<div class="file"><span class="file-ext pptx">pptx</span> investor_update.pptx</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><span class="icon">▶</span><span class="name">z.Backup archivos viejos</span><span class="count">46 files</span></summary>
|
||||||
|
<div class="folder-content">
|
||||||
|
<div class="folder-desc">Legacy files, old exports</div>
|
||||||
|
<div class="file-list">
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> old_vets_list.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> org_chart.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> data_room_2023.xlsx</div>
|
||||||
|
<div class="file"><span class="file-ext xlsx">xlsx</span> legacy_export.xlsx</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<footer>pawprint/album</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
217
atlas/book/drive-index/index.md
Normal file
217
atlas/book/drive-index/index.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# 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
|
||||||
60
atlas/book/feature-flow/CLAUDE.md
Normal file
60
atlas/book/feature-flow/CLAUDE.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Feature Flow Book
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Presentation showing the feature standardization pipeline.
|
||||||
|
|
||||||
|
## The Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||||
|
│ OPS TEMPLATES │ -> │ BDD/GHERKIN │ -> │ TESTS │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ Non-technical │ │ .feature files │ │ Backend: │
|
||||||
|
│ User flows │ │ Given/When/Then │ │ API contracts │
|
||||||
|
│ From support │ │ Human readable │ │ Workflows │
|
||||||
|
│ │ │ │ │ Frontend: │
|
||||||
|
│ │ │ │ │ Page Objects │
|
||||||
|
│ │ │ │ │ E2E specs │
|
||||||
|
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `index-en.html` - English slide presentation (8 slides, arrow keys)
|
||||||
|
- `index-es.html` - Spanish slide presentation (8 slides, arrow keys)
|
||||||
|
|
||||||
|
## Slides Structure
|
||||||
|
1. Title
|
||||||
|
2. Pipeline Overview (3 columns)
|
||||||
|
3. Ops Templates
|
||||||
|
4. BDD/Gherkin
|
||||||
|
5. Gherkin File Organization (best practices)
|
||||||
|
6. Backend Tests (amar_django_back structure)
|
||||||
|
7. Frontend Tests (amar_frontend structure)
|
||||||
|
8. Per-Feature Checklist
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Ops Templates
|
||||||
|
- `album/template/ops-flow/plantilla-flujo.md`
|
||||||
|
- `def/work_plan/21-plantilla-flujos-usuario.md`
|
||||||
|
|
||||||
|
### BDD/Gherkin Examples
|
||||||
|
- `def/work_plan/10-flow-turnero.md` (full gherkin + tests example)
|
||||||
|
|
||||||
|
### Test Structure References
|
||||||
|
- `amar_django_back/tests/contracts/README.md`
|
||||||
|
- `amar_frontend/tests/README.md`
|
||||||
|
|
||||||
|
## Editing
|
||||||
|
Edit `index-en.html` or `index-es.html` directly.
|
||||||
|
Slides are `<section>` elements. Arrow keys to navigate.
|
||||||
|
|
||||||
|
## Flow Checklist (per feature)
|
||||||
|
|
||||||
|
- [ ] Ops template filled by support team
|
||||||
|
- [ ] Convert to .feature file (Gherkin spec)
|
||||||
|
- [ ] Backend: API contract tests per endpoint
|
||||||
|
- [ ] Backend: Workflow test (composition)
|
||||||
|
- [ ] Frontend: Page Object (if new page)
|
||||||
|
- [ ] Frontend: E2E spec (Playwright)
|
||||||
|
- [ ] Wire to CI
|
||||||
392
atlas/book/feature-flow/index-en.html
Normal file
392
atlas/book/feature-flow/index-en.html
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Feature Flow - Standardization Pipeline</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.slide { display: none; min-height: 100vh; padding: 3rem; }
|
||||||
|
.slide.active { display: flex; flex-direction: column; }
|
||||||
|
.nav { position: fixed; bottom: 2rem; right: 2rem; display: flex; gap: 0.5rem; z-index: 100; }
|
||||||
|
.nav button { background: #334155; border: none; color: #e2e8f0; padding: 0.75rem 1.25rem; border-radius: 6px; cursor: pointer; font-size: 1rem; }
|
||||||
|
.nav button:hover { background: #475569; }
|
||||||
|
.nav .counter { background: transparent; padding: 0.75rem 1rem; color: #64748b; }
|
||||||
|
.slide-title { justify-content: center; align-items: center; text-align: center; }
|
||||||
|
.slide-title h1 { font-size: 3.5rem; font-weight: 700; margin-bottom: 1rem; background: linear-gradient(135deg, #6366f1, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||||
|
.slide-title p { font-size: 1.5rem; color: #94a3b8; }
|
||||||
|
.slide-title .subtitle { margin-top: 3rem; font-size: 1rem; color: #64748b; }
|
||||||
|
.pipeline { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; margin: 2rem 0; }
|
||||||
|
.pipeline-step { background: #1e293b; border-radius: 12px; padding: 2rem; border-left: 4px solid; }
|
||||||
|
.pipeline-step.ops { border-color: #22c55e; }
|
||||||
|
.pipeline-step.bdd { border-color: #6366f1; }
|
||||||
|
.pipeline-step.tests { border-color: #f59e0b; }
|
||||||
|
.pipeline-step h3 { font-size: 1.25rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.pipeline-step .num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; font-weight: 700; }
|
||||||
|
.pipeline-step.ops .num { background: #22c55e; color: #0f172a; }
|
||||||
|
.pipeline-step.bdd .num { background: #6366f1; color: white; }
|
||||||
|
.pipeline-step.tests .num { background: #f59e0b; color: #0f172a; }
|
||||||
|
.pipeline-step ul { list-style: none; color: #94a3b8; font-size: 0.95rem; }
|
||||||
|
.pipeline-step li { padding: 0.4rem 0; padding-left: 1rem; border-left: 2px solid #334155; margin-bottom: 0.25rem; }
|
||||||
|
.slide h2 { font-size: 2rem; margin-bottom: 2rem; display: flex; align-items: center; gap: 1rem; }
|
||||||
|
.slide h2 .badge { padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
|
||||||
|
.slide h2 .badge.ops { background: #22c55e; color: #0f172a; }
|
||||||
|
.slide h2 .badge.bdd { background: #6366f1; color: white; }
|
||||||
|
.slide h2 .badge.tests { background: #f59e0b; color: #0f172a; }
|
||||||
|
pre { background: #1e293b; padding: 1.5rem; border-radius: 8px; overflow-x: auto; font-size: 0.8rem; line-height: 1.5; margin: 1rem 0; }
|
||||||
|
.keyword { color: #c084fc; }
|
||||||
|
.string { color: #4ade80; }
|
||||||
|
.comment { color: #64748b; }
|
||||||
|
.decorator { color: #f59e0b; }
|
||||||
|
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; flex: 1; }
|
||||||
|
.col { background: #1e293b; border-radius: 8px; padding: 1.5rem; }
|
||||||
|
.col h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||||
|
.checklist { list-style: none; font-size: 1.1rem; }
|
||||||
|
.checklist li { padding: 0.75rem 0; display: flex; align-items: center; gap: 0.75rem; border-bottom: 1px solid #334155; }
|
||||||
|
.checklist .check { width: 20px; height: 20px; border: 2px solid #475569; border-radius: 4px; }
|
||||||
|
.flow-list { list-style: none; }
|
||||||
|
.flow-list li { padding: 0.4rem 0; color: #94a3b8; font-size: 0.9rem; }
|
||||||
|
.flow-list li strong { color: #e2e8f0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="slide slide-title active" data-slide="0">
|
||||||
|
<h1>Feature Flow</h1>
|
||||||
|
<p>Standardization Pipeline</p>
|
||||||
|
<div class="subtitle">Ops Templates → BDD/Gherkin → Backend + Frontend Tests</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="1">
|
||||||
|
<h2>Pipeline Overview</h2>
|
||||||
|
<div class="pipeline">
|
||||||
|
<div class="pipeline-step ops">
|
||||||
|
<h3><span class="num">1</span> Ops Templates</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Non-technical language</li>
|
||||||
|
<li>User perspective flows</li>
|
||||||
|
<li>From support/ops team</li>
|
||||||
|
<li>Captures edge cases</li>
|
||||||
|
<li>Documents known problems</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="pipeline-step bdd">
|
||||||
|
<h3><span class="num">2</span> BDD/Gherkin</h3>
|
||||||
|
<ul>
|
||||||
|
<li>.feature files</li>
|
||||||
|
<li>Given/When/Then syntax</li>
|
||||||
|
<li>Human readable specs</li>
|
||||||
|
<li>Single source of truth</li>
|
||||||
|
<li>Maps to both test types</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="pipeline-step tests">
|
||||||
|
<h3><span class="num">3</span> Tests</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Backend:</strong> API contracts</li>
|
||||||
|
<li><strong>Backend:</strong> Workflows (compositions)</li>
|
||||||
|
<li><strong>Frontend:</strong> Page Objects</li>
|
||||||
|
<li><strong>Frontend:</strong> E2E specs (Playwright)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="2">
|
||||||
|
<h2><span class="badge ops">1</span> Ops Templates</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Template Structure</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">### [Flow Name]</span>
|
||||||
|
|
||||||
|
<span class="comment">User type:</span> Pet Owner / Vet / Admin
|
||||||
|
<span class="comment">Entry point:</span> Page/button/link
|
||||||
|
<span class="comment">Goal:</span> One sentence
|
||||||
|
|
||||||
|
<span class="keyword">Steps:</span>
|
||||||
|
1. First action
|
||||||
|
2. Second action
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
<span class="keyword">Expected result:</span>
|
||||||
|
- What should happen
|
||||||
|
|
||||||
|
<span class="keyword">Common problems:</span>
|
||||||
|
- Problem 1
|
||||||
|
|
||||||
|
<span class="keyword">Edge cases:</span>
|
||||||
|
- Special case 1
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Source</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Template:</strong> album/template/ops-flow/</li>
|
||||||
|
<li><strong>Reference:</strong> def/work_plan/21-plantilla</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Who Fills This</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>Support team (daily user contact)</li>
|
||||||
|
<li>Ops team (knows workarounds)</li>
|
||||||
|
<li>Product (requirements)</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Output</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>One .md per flow</li>
|
||||||
|
<li>Organized by user type</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="3">
|
||||||
|
<h2><span class="badge bdd">2</span> BDD/Gherkin</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>.feature File</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">Feature:</span> Turnero - Book appointment
|
||||||
|
|
||||||
|
<span class="keyword">Scenario:</span> Book vaccination for cat
|
||||||
|
<span class="decorator">Given</span> I am on the turnero page
|
||||||
|
<span class="decorator">When</span> I enter address <span class="string">"Av Santa Fe 1234"</span>
|
||||||
|
<span class="decorator">And</span> I click <span class="string">"Next"</span>
|
||||||
|
<span class="decorator">Then</span> a guest user should be created
|
||||||
|
|
||||||
|
<span class="decorator">When</span> I add pet <span class="string">"Koshka"</span> type <span class="string">"Cat"</span>
|
||||||
|
<span class="decorator">And</span> I select <span class="string">"Vaccination"</span>
|
||||||
|
<span class="decorator">Then</span> <span class="string">"Clinical consult"</span> is auto-added
|
||||||
|
|
||||||
|
<span class="keyword">Scenario:</span> Services filtered by pet type
|
||||||
|
<span class="decorator">Given</span> I added a cat
|
||||||
|
<span class="decorator">Then</span> I see cat vaccines
|
||||||
|
<span class="decorator">And</span> I dont see dog vaccines
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Keywords</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Feature</strong> = one capability</li>
|
||||||
|
<li><strong>Scenario</strong> = one behavior</li>
|
||||||
|
<li><strong>Given</strong> = precondition</li>
|
||||||
|
<li><strong>When</strong> = action</li>
|
||||||
|
<li><strong>Then</strong> = expected result</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Reference</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>def/work_plan/10-flow-turnero.md</li>
|
||||||
|
<li>Full example with Gherkin, API tests, Page Objects</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="4">
|
||||||
|
<h2><span class="badge bdd">2b</span> Gherkin File Organization</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Correct: One Feature = One File</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">features/</span>
|
||||||
|
├── pet-owner/
|
||||||
|
│ ├── registro.feature <span class="comment"># 6-8 scenarios</span>
|
||||||
|
│ ├── reservar-turno.feature <span class="comment"># 10-15 scenarios</span>
|
||||||
|
│ ├── gestion-mascotas.feature
|
||||||
|
│ └── pago.feature
|
||||||
|
├── veterinarian/
|
||||||
|
│ └── ...
|
||||||
|
└── backoffice/
|
||||||
|
└── ...
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Anti-pattern: One Scenario = One File</h4>
|
||||||
|
<pre style="border-left: 3px solid #ef4444;">
|
||||||
|
<span class="comment"># DON'T do this</span>
|
||||||
|
features/pet-owner/registro/
|
||||||
|
├── registro-exitoso.feature
|
||||||
|
├── registro-email-invalido.feature
|
||||||
|
├── registro-password-corto.feature
|
||||||
|
└── <span class="comment">... (dozens of tiny files)</span>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Why Multiple Scenarios per File</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Feature = Capability</strong> - one file describes one capability with all its behaviors</li>
|
||||||
|
<li><strong>Context stays together</strong> - Background, Rules share context</li>
|
||||||
|
<li><strong>Tooling expects it</strong> - test runners, reports, IDE navigation</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">When to Split</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Scenarios per file:</span>
|
||||||
|
5-20 <span class="string">Normal, keep as is</span>
|
||||||
|
20-40 <span class="string">Consider splitting</span>
|
||||||
|
40+ <span class="string">Definitely split</span>
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Folder Depth</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Good:</strong> 1-2 levels max</li>
|
||||||
|
<li><strong>Avoid:</strong> deep nesting</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="5">
|
||||||
|
<h2><span class="badge tests">3a</span> Backend Tests</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Structure (amar_django_back)</h4>
|
||||||
|
<pre>
|
||||||
|
tests/contracts/
|
||||||
|
├── base.py <span class="comment"># mode switcher</span>
|
||||||
|
├── endpoints.py <span class="comment"># API paths (single source)</span>
|
||||||
|
├── helpers.py <span class="comment"># test data</span>
|
||||||
|
│
|
||||||
|
├── mascotas/ <span class="comment"># app tests</span>
|
||||||
|
│ ├── test_pet_owners.py
|
||||||
|
│ ├── test_pets.py
|
||||||
|
│ └── test_coverage.py
|
||||||
|
├── productos/
|
||||||
|
│ ├── test_services.py
|
||||||
|
│ └── test_cart.py
|
||||||
|
├── solicitudes/
|
||||||
|
│ └── test_service_requests.py
|
||||||
|
│
|
||||||
|
└── <span class="keyword">workflows/</span> <span class="comment"># compositions</span>
|
||||||
|
└── test_turnero_general.py
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Two Test Modes</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Fast (Django test client)</span>
|
||||||
|
pytest tests/contracts/
|
||||||
|
|
||||||
|
<span class="comment"># Live (real HTTP)</span>
|
||||||
|
CONTRACT_TEST_MODE=live pytest
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Workflow = Composition</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Calls endpoints in sequence:</span>
|
||||||
|
1. Check coverage
|
||||||
|
2. Create pet owner
|
||||||
|
3. Create pet
|
||||||
|
4. Get services
|
||||||
|
5. Create request
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Key Files</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>endpoints.py</strong> - change paths here only</li>
|
||||||
|
<li><strong>helpers.py</strong> - sample data</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="6">
|
||||||
|
<h2><span class="badge tests">3b</span> Frontend Tests</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Structure (amar_frontend)</h4>
|
||||||
|
<pre>
|
||||||
|
tests/e2e/
|
||||||
|
├── <span class="keyword">pages/</span> <span class="comment"># Page Objects</span>
|
||||||
|
│ ├── BasePage.ts
|
||||||
|
│ ├── LoginPage.ts
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
└── login.spec.ts <span class="comment"># E2E test</span>
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Page Object Pattern</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">export class</span> LoginPage <span class="keyword">extends</span> BasePage {
|
||||||
|
<span class="keyword">get</span> emailInput() {
|
||||||
|
<span class="keyword">return</span> this.page.getByLabel(<span class="string">'Email'</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="keyword">async</span> login(email, password) {
|
||||||
|
<span class="keyword">await</span> this.emailInput.fill(email);
|
||||||
|
<span class="keyword">await</span> this.passwordInput.fill(password);
|
||||||
|
<span class="keyword">await</span> this.submitButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Running Tests</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># All tests</span>
|
||||||
|
npx playwright test
|
||||||
|
|
||||||
|
<span class="comment"># With UI</span>
|
||||||
|
npx playwright test --ui
|
||||||
|
|
||||||
|
<span class="comment"># Specific file</span>
|
||||||
|
npx playwright test login.spec.ts
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Locator Priority</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>1. getByRole() - buttons, links</li>
|
||||||
|
<li>2. getByLabel() - form fields</li>
|
||||||
|
<li>3. getByText() - visible text</li>
|
||||||
|
<li>4. getByTestId() - data-testid</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1rem;">Avoid</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>CSS class selectors</li>
|
||||||
|
<li>Complex XPath</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="7">
|
||||||
|
<h2>Per-Feature Checklist</h2>
|
||||||
|
<ul class="checklist">
|
||||||
|
<li><span class="check"></span> Ops template filled (support team)</li>
|
||||||
|
<li><span class="check"></span> Convert to .feature file (Gherkin spec)</li>
|
||||||
|
<li><span class="check"></span> Backend: API contract tests per endpoint</li>
|
||||||
|
<li><span class="check"></span> Backend: Workflow test (composition)</li>
|
||||||
|
<li><span class="check"></span> Frontend: Page Object (if new page)</li>
|
||||||
|
<li><span class="check"></span> Frontend: E2E spec (Playwright)</li>
|
||||||
|
<li><span class="check"></span> Wire to CI</li>
|
||||||
|
</ul>
|
||||||
|
<div style="margin-top: 2rem; color: #64748b; font-size: 0.9rem;">
|
||||||
|
<p><strong>Full example:</strong> def/work_plan/10-flow-turnero.md</p>
|
||||||
|
<p><strong>Backend README:</strong> amar_django_back/tests/contracts/README.md</p>
|
||||||
|
<p><strong>Frontend README:</strong> amar_frontend/tests/README.md</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="nav">
|
||||||
|
<button onclick="prevSlide()">←</button>
|
||||||
|
<span class="counter"><span id="current">1</span>/<span id="total">8</span></span>
|
||||||
|
<button onclick="nextSlide()">→</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let current = 0;
|
||||||
|
const slides = document.querySelectorAll('.slide');
|
||||||
|
const total = slides.length;
|
||||||
|
document.getElementById('total').textContent = total;
|
||||||
|
function showSlide(n) {
|
||||||
|
slides.forEach(s => s.classList.remove('active'));
|
||||||
|
current = (n + total) % total;
|
||||||
|
slides[current].classList.add('active');
|
||||||
|
document.getElementById('current').textContent = current + 1;
|
||||||
|
}
|
||||||
|
function nextSlide() { showSlide(current + 1); }
|
||||||
|
function prevSlide() { showSlide(current - 1); }
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
|
||||||
|
if (e.key === 'ArrowLeft') prevSlide();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
470
atlas/book/feature-flow/index-es.html
Normal file
470
atlas/book/feature-flow/index-es.html
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Feature Flow - Pipeline de Estandarizacion</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.slide {
|
||||||
|
display: none;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
.slide.active { display: flex; flex-direction: column; }
|
||||||
|
.nav {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.nav button {
|
||||||
|
background: #334155;
|
||||||
|
border: none;
|
||||||
|
color: #e2e8f0;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.nav button:hover { background: #475569; }
|
||||||
|
.nav .counter {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
.slide-title {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.slide-title h1 {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: linear-gradient(135deg, #6366f1, #a855f7);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.slide-title p { font-size: 1.5rem; color: #94a3b8; }
|
||||||
|
.slide-title .subtitle { margin-top: 3rem; font-size: 1rem; color: #64748b; }
|
||||||
|
.pipeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.pipeline-step {
|
||||||
|
background: #1e293b;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
.pipeline-step.ops { border-color: #22c55e; }
|
||||||
|
.pipeline-step.bdd { border-color: #6366f1; }
|
||||||
|
.pipeline-step.tests { border-color: #f59e0b; }
|
||||||
|
.pipeline-step h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.pipeline-step .num {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.pipeline-step.ops .num { background: #22c55e; color: #0f172a; }
|
||||||
|
.pipeline-step.bdd .num { background: #6366f1; color: white; }
|
||||||
|
.pipeline-step.tests .num { background: #f59e0b; color: #0f172a; }
|
||||||
|
.pipeline-step ul { list-style: none; color: #94a3b8; font-size: 0.95rem; }
|
||||||
|
.pipeline-step li { padding: 0.4rem 0; padding-left: 1rem; border-left: 2px solid #334155; margin-bottom: 0.25rem; }
|
||||||
|
.slide h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.slide h2 .badge {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.slide h2 .badge.ops { background: #22c55e; color: #0f172a; }
|
||||||
|
.slide h2 .badge.bdd { background: #6366f1; color: white; }
|
||||||
|
.slide h2 .badge.tests { background: #f59e0b; color: #0f172a; }
|
||||||
|
pre {
|
||||||
|
background: #1e293b;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.keyword { color: #c084fc; }
|
||||||
|
.string { color: #4ade80; }
|
||||||
|
.comment { color: #64748b; }
|
||||||
|
.decorator { color: #f59e0b; }
|
||||||
|
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; flex: 1; }
|
||||||
|
.col { background: #1e293b; border-radius: 8px; padding: 1.5rem; }
|
||||||
|
.col h4 { font-size: 0.9rem; color: #94a3b8; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||||
|
.checklist { list-style: none; font-size: 1.1rem; }
|
||||||
|
.checklist li { padding: 0.75rem 0; display: flex; align-items: center; gap: 0.75rem; border-bottom: 1px solid #334155; }
|
||||||
|
.checklist .check { width: 20px; height: 20px; border: 2px solid #475569; border-radius: 4px; }
|
||||||
|
.flow-list { list-style: none; }
|
||||||
|
.flow-list li { padding: 0.5rem 0; color: #94a3b8; }
|
||||||
|
.flow-list li strong { color: #e2e8f0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="slide slide-title active" data-slide="0">
|
||||||
|
<h1>Feature Flow</h1>
|
||||||
|
<p>Pipeline de Estandarizacion</p>
|
||||||
|
<div class="subtitle">Templates Ops → BDD/Gherkin → Tests Backend + Frontend</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="1">
|
||||||
|
<h2>Vision General del Pipeline</h2>
|
||||||
|
<div class="pipeline">
|
||||||
|
<div class="pipeline-step ops">
|
||||||
|
<h3><span class="num">1</span> Templates Ops</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Lenguaje no tecnico</li>
|
||||||
|
<li>Flujos desde el usuario</li>
|
||||||
|
<li>Del equipo de soporte/ops</li>
|
||||||
|
<li>Captura casos borde</li>
|
||||||
|
<li>Documenta problemas conocidos</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="pipeline-step bdd">
|
||||||
|
<h3><span class="num">2</span> BDD/Gherkin</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Archivos .feature</li>
|
||||||
|
<li>Sintaxis Given/When/Then</li>
|
||||||
|
<li>Specs legibles</li>
|
||||||
|
<li>Fuente unica de verdad</li>
|
||||||
|
<li>Mapea a ambos tipos de test</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="pipeline-step tests">
|
||||||
|
<h3><span class="num">3</span> Tests</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Backend:</strong> Contratos API</li>
|
||||||
|
<li><strong>Backend:</strong> Workflows (composiciones)</li>
|
||||||
|
<li><strong>Frontend:</strong> Page Objects</li>
|
||||||
|
<li><strong>Frontend:</strong> E2E specs (Playwright)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="2">
|
||||||
|
<h2><span class="badge ops">1</span> Templates Ops</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Estructura de la Plantilla</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">### [Nombre del Flujo]</span>
|
||||||
|
|
||||||
|
<span class="comment">Tipo de usuario:</span> Dueno / Vet / Admin
|
||||||
|
<span class="comment">Donde empieza:</span> Pagina/boton/link
|
||||||
|
<span class="comment">Objetivo:</span> Una oracion
|
||||||
|
|
||||||
|
<span class="keyword">Pasos:</span>
|
||||||
|
1. Primera accion
|
||||||
|
2. Segunda accion
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
<span class="keyword">Que deberia pasar:</span>
|
||||||
|
- Resultado esperado
|
||||||
|
|
||||||
|
<span class="keyword">Problemas comunes:</span>
|
||||||
|
- Problema 1
|
||||||
|
|
||||||
|
<span class="keyword">Casos especiales:</span>
|
||||||
|
- Caso especial 1
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Fuente</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Plantilla:</strong> album/template/ops-flow/</li>
|
||||||
|
<li><strong>Referencia:</strong> def/work_plan/21-plantilla</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Quien Completa Esto</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>Equipo de soporte (contacto diario)</li>
|
||||||
|
<li>Equipo de ops (conoce workarounds)</li>
|
||||||
|
<li>Producto (requerimientos)</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Output</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>Un .md por flujo</li>
|
||||||
|
<li>Organizado por tipo de usuario</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="3">
|
||||||
|
<h2><span class="badge bdd">2</span> BDD/Gherkin</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Archivo .feature</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">Feature:</span> Turnero - Reservar turno
|
||||||
|
|
||||||
|
<span class="keyword">Scenario:</span> Reservar vacuna para gato
|
||||||
|
<span class="decorator">Given</span> estoy en la pagina del turnero
|
||||||
|
<span class="decorator">When</span> ingreso direccion <span class="string">"Av Santa Fe 1234"</span>
|
||||||
|
<span class="decorator">And</span> hago click en <span class="string">"Siguiente"</span>
|
||||||
|
<span class="decorator">Then</span> se crea un usuario invitado
|
||||||
|
|
||||||
|
<span class="decorator">When</span> agrego mascota <span class="string">"Koshka"</span> tipo <span class="string">"Gato"</span>
|
||||||
|
<span class="decorator">And</span> selecciono <span class="string">"Vacunacion"</span>
|
||||||
|
<span class="decorator">Then</span> <span class="string">"Consulta clinica"</span> se agrega auto
|
||||||
|
|
||||||
|
<span class="keyword">Scenario:</span> Servicios filtrados por tipo
|
||||||
|
<span class="decorator">Given</span> agregue un gato
|
||||||
|
<span class="decorator">Then</span> veo vacunas felinas
|
||||||
|
<span class="decorator">And</span> no veo vacunas caninas
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Palabras Clave</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Feature</strong> = una funcionalidad</li>
|
||||||
|
<li><strong>Scenario</strong> = un comportamiento</li>
|
||||||
|
<li><strong>Given</strong> = precondicion</li>
|
||||||
|
<li><strong>When</strong> = accion</li>
|
||||||
|
<li><strong>Then</strong> = resultado esperado</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Referencia</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>def/work_plan/10-flow-turnero.md</li>
|
||||||
|
<li>Ejemplo completo con Gherkin, tests API, Page Objects</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="4">
|
||||||
|
<h2><span class="badge bdd">2b</span> Organizacion de Archivos Gherkin</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Correcto: Una Feature = Un Archivo</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">features/</span>
|
||||||
|
├── pet-owner/
|
||||||
|
│ ├── registro.feature <span class="comment"># 6-8 escenarios</span>
|
||||||
|
│ ├── reservar-turno.feature <span class="comment"># 10-15 escenarios</span>
|
||||||
|
│ ├── gestion-mascotas.feature
|
||||||
|
│ └── pago.feature
|
||||||
|
├── veterinarian/
|
||||||
|
│ └── ...
|
||||||
|
└── backoffice/
|
||||||
|
└── ...
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Anti-patron: Un Escenario = Un Archivo</h4>
|
||||||
|
<pre style="border-left: 3px solid #ef4444;">
|
||||||
|
<span class="comment"># NO hacer esto</span>
|
||||||
|
features/pet-owner/registro/
|
||||||
|
├── registro-exitoso.feature
|
||||||
|
├── registro-email-invalido.feature
|
||||||
|
├── registro-password-corto.feature
|
||||||
|
└── <span class="comment">... (docenas de archivos pequeños)</span>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Por Que Multiples Escenarios por Archivo</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Feature = Capacidad</strong> - un archivo describe una capacidad con todos sus comportamientos</li>
|
||||||
|
<li><strong>Contexto junto</strong> - Background, Rules comparten contexto</li>
|
||||||
|
<li><strong>Tooling lo espera</strong> - test runners, reportes, navegacion IDE</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1.5rem;">Cuando Dividir</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Escenarios por archivo:</span>
|
||||||
|
5-20 <span class="string">Normal, mantener</span>
|
||||||
|
20-40 <span class="string">Considerar dividir</span>
|
||||||
|
40+ <span class="string">Definitivamente dividir</span>
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Profundidad de Carpetas</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>Bien:</strong> 1-2 niveles max</li>
|
||||||
|
<li><strong>Evitar:</strong> anidamiento profundo</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="5">
|
||||||
|
<h2><span class="badge tests">3a</span> Tests Backend</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Estructura (amar_django_back)</h4>
|
||||||
|
<pre>
|
||||||
|
tests/contracts/
|
||||||
|
├── base.py <span class="comment"># switcher de modo</span>
|
||||||
|
├── endpoints.py <span class="comment"># paths API (fuente unica)</span>
|
||||||
|
├── helpers.py <span class="comment"># datos de prueba</span>
|
||||||
|
│
|
||||||
|
├── mascotas/ <span class="comment"># tests por app</span>
|
||||||
|
│ ├── test_pet_owners.py
|
||||||
|
│ ├── test_pets.py
|
||||||
|
│ └── test_coverage.py
|
||||||
|
├── productos/
|
||||||
|
│ ├── test_services.py
|
||||||
|
│ └── test_cart.py
|
||||||
|
├── solicitudes/
|
||||||
|
│ └── test_service_requests.py
|
||||||
|
│
|
||||||
|
└── <span class="keyword">workflows/</span> <span class="comment"># composiciones</span>
|
||||||
|
└── test_turnero_general.py
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Dos Modos de Test</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Rapido (Django test client)</span>
|
||||||
|
pytest tests/contracts/
|
||||||
|
|
||||||
|
<span class="comment"># Live (HTTP real)</span>
|
||||||
|
CONTRACT_TEST_MODE=live pytest
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Workflow = Composicion</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Llama endpoints en secuencia:</span>
|
||||||
|
1. Check cobertura
|
||||||
|
2. Crear pet owner
|
||||||
|
3. Crear mascota
|
||||||
|
4. Obtener servicios
|
||||||
|
5. Crear solicitud
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Archivos Clave</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li><strong>endpoints.py</strong> - cambiar paths solo aca</li>
|
||||||
|
<li><strong>helpers.py</strong> - datos de ejemplo</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="6">
|
||||||
|
<h2><span class="badge tests">3b</span> Tests Frontend</h2>
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="col">
|
||||||
|
<h4>Estructura (amar_frontend)</h4>
|
||||||
|
<pre>
|
||||||
|
tests/e2e/
|
||||||
|
├── <span class="keyword">pages/</span> <span class="comment"># Page Objects</span>
|
||||||
|
│ ├── BasePage.ts
|
||||||
|
│ ├── LoginPage.ts
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
└── login.spec.ts <span class="comment"># test E2E</span>
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Patron Page Object</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="keyword">export class</span> LoginPage <span class="keyword">extends</span> BasePage {
|
||||||
|
<span class="keyword">get</span> emailInput() {
|
||||||
|
<span class="keyword">return</span> this.page.getByLabel(<span class="string">'Email'</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="keyword">async</span> login(email, password) {
|
||||||
|
<span class="keyword">await</span> this.emailInput.fill(email);
|
||||||
|
<span class="keyword">await</span> this.passwordInput.fill(password);
|
||||||
|
<span class="keyword">await</span> this.submitButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4>Ejecutar Tests</h4>
|
||||||
|
<pre>
|
||||||
|
<span class="comment"># Todos los tests</span>
|
||||||
|
npx playwright test
|
||||||
|
|
||||||
|
<span class="comment"># Con UI</span>
|
||||||
|
npx playwright test --ui
|
||||||
|
|
||||||
|
<span class="comment"># Archivo especifico</span>
|
||||||
|
npx playwright test login.spec.ts
|
||||||
|
</pre>
|
||||||
|
<h4 style="margin-top: 1rem;">Prioridad de Locators</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>1. getByRole() - botones, links</li>
|
||||||
|
<li>2. getByLabel() - campos de form</li>
|
||||||
|
<li>3. getByText() - texto visible</li>
|
||||||
|
<li>4. getByTestId() - data-testid</li>
|
||||||
|
</ul>
|
||||||
|
<h4 style="margin-top: 1rem;">Evitar</h4>
|
||||||
|
<ul class="flow-list">
|
||||||
|
<li>Selectores de clases CSS</li>
|
||||||
|
<li>XPath complejos</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-slide="7">
|
||||||
|
<h2>Checklist por Feature</h2>
|
||||||
|
<ul class="checklist">
|
||||||
|
<li><span class="check"></span> Template ops completado (equipo soporte)</li>
|
||||||
|
<li><span class="check"></span> Convertir a archivo .feature (spec Gherkin)</li>
|
||||||
|
<li><span class="check"></span> Backend: Tests de contrato API por endpoint</li>
|
||||||
|
<li><span class="check"></span> Backend: Test workflow (composicion)</li>
|
||||||
|
<li><span class="check"></span> Frontend: Page Object (si es pagina nueva)</li>
|
||||||
|
<li><span class="check"></span> Frontend: E2E spec (Playwright)</li>
|
||||||
|
<li><span class="check"></span> Conectar a CI</li>
|
||||||
|
</ul>
|
||||||
|
<div style="margin-top: 2rem; color: #64748b; font-size: 0.9rem;">
|
||||||
|
<p><strong>Ejemplo completo:</strong> def/work_plan/10-flow-turnero.md</p>
|
||||||
|
<p><strong>README Backend:</strong> amar_django_back/tests/contracts/README.md</p>
|
||||||
|
<p><strong>README Frontend:</strong> amar_frontend/tests/README.md</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="nav">
|
||||||
|
<button onclick="prevSlide()">←</button>
|
||||||
|
<span class="counter"><span id="current">1</span>/<span id="total">8</span></span>
|
||||||
|
<button onclick="nextSlide()">→</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let current = 0;
|
||||||
|
const slides = document.querySelectorAll('.slide');
|
||||||
|
const total = slides.length;
|
||||||
|
document.getElementById('total').textContent = total;
|
||||||
|
function showSlide(n) {
|
||||||
|
slides.forEach(s => s.classList.remove('active'));
|
||||||
|
current = (n + total) % total;
|
||||||
|
slides[current].classList.add('active');
|
||||||
|
document.getElementById('current').textContent = current + 1;
|
||||||
|
}
|
||||||
|
function nextSlide() { showSlide(current + 1); }
|
||||||
|
function prevSlide() { showSlide(current - 1); }
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
|
||||||
|
if (e.key === 'ArrowLeft') prevSlide();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
79
atlas/book/feature-form-samples/CLAUDE.md
Normal file
79
atlas/book/feature-form-samples/CLAUDE.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# 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** | |
|
||||||
335
atlas/book/feature-form-samples/detail.html
Normal file
335
atlas/book/feature-form-samples/detail.html
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ filename }} - Ops Template</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #1e293b;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container { max-width: 800px; margin: 0 auto; padding: 2rem 1rem; }
|
||||||
|
header { margin-bottom: 1.5rem; }
|
||||||
|
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
.breadcrumb a { color: #15803d; text-decoration: none; }
|
||||||
|
.breadcrumb a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 1.5rem; color: #15803d; }
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.meta span {
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
.meta .sample { background: #fef3c7; color: #92400e; }
|
||||||
|
|
||||||
|
/* Form-like styling */
|
||||||
|
.form-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.form-header {
|
||||||
|
background: linear-gradient(135deg, #15803d, #22c55e);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
.form-header h2 { font-size: 1.1rem; font-weight: 600; }
|
||||||
|
.form-body { padding: 1.5rem; }
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
.field:last-child { margin-bottom: 0; }
|
||||||
|
.field-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
.field-label .edit-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.field-label .edit-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.field-value {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #1e293b;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
}
|
||||||
|
.field-value:focus-within {
|
||||||
|
border-color: #15803d;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba(21, 128, 61, 0.1);
|
||||||
|
}
|
||||||
|
.field-value.multiline {
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
.field-value ul, .field-value ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
}
|
||||||
|
.field-value li {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.field-value p {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
.field-value p:last-child { margin-bottom: 0; }
|
||||||
|
.field-textarea {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-family: inherit;
|
||||||
|
border: 1px solid #15803d;
|
||||||
|
border-radius: 6px;
|
||||||
|
resize: vertical;
|
||||||
|
box-shadow: 0 0 0 3px rgba(21, 128, 61, 0.1);
|
||||||
|
}
|
||||||
|
.field.editing .field-value { display: none; }
|
||||||
|
.field.editing .field-textarea { display: block; }
|
||||||
|
|
||||||
|
/* Special field styles */
|
||||||
|
.field-steps .field-value {
|
||||||
|
counter-reset: step;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.field-steps ol {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.field-steps li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.field-steps li::before {
|
||||||
|
counter-increment: step;
|
||||||
|
content: counter(step);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: #15803d;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-problems .field-value {
|
||||||
|
background: #fef2f2;
|
||||||
|
border-color: #fecaca;
|
||||||
|
}
|
||||||
|
.field-problems li::before {
|
||||||
|
content: '!';
|
||||||
|
color: #dc2626;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-special .field-value {
|
||||||
|
background: #fffbeb;
|
||||||
|
border-color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-technical .field-value {
|
||||||
|
background: #f0fdf4;
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.field-technical code {
|
||||||
|
background: #dcfce7;
|
||||||
|
padding: 0.1rem 0.35rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.sidebar h3 { font-size: 0.8rem; color: #15803d; margin-bottom: 0.75rem; text-transform: uppercase; }
|
||||||
|
.sidebar a {
|
||||||
|
display: block;
|
||||||
|
color: #15803d;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.35rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.sidebar a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
footer a { color: #15803d; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">Album</a> / <a href="/book/ops-templates/">Ops Templates</a> / {{ user_type }}
|
||||||
|
</div>
|
||||||
|
<h1>{{ filename.replace('.md', '').replace('-', ' ').title() }}</h1>
|
||||||
|
<div class="meta">
|
||||||
|
<span>{{ user_type }}</span>
|
||||||
|
<span class="sample">Sample</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<h2>User Flow Template</h2>
|
||||||
|
</div>
|
||||||
|
<div class="form-body" id="form-content">
|
||||||
|
<!-- Content will be parsed and inserted here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar">
|
||||||
|
<h3>Related</h3>
|
||||||
|
<a href="/book/gherkin/es/{{ user_type }}/{{ filename.replace('.md', '.feature') }}">Gherkin (Spanish)</a>
|
||||||
|
<a href="/book/gherkin/en/{{ user_type }}/{{ filename.replace('.md', '.feature') }}">Gherkin (English)</a>
|
||||||
|
<a href="/book/feature-flow/">Feature Flow Pipeline</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/book/ops-templates/">← All Templates</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const rawContent = {{ content | tojson }};
|
||||||
|
|
||||||
|
function parseMarkdown(md) {
|
||||||
|
const sections = {};
|
||||||
|
let currentSection = null;
|
||||||
|
let currentContent = [];
|
||||||
|
|
||||||
|
const lines = md.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('## ')) {
|
||||||
|
if (currentSection) {
|
||||||
|
sections[currentSection] = currentContent.join('\n').trim();
|
||||||
|
}
|
||||||
|
currentSection = line.replace('## ', '').trim();
|
||||||
|
currentContent = [];
|
||||||
|
} else if (line.startsWith('# ')) {
|
||||||
|
sections['_title'] = line.replace('# ', '').trim();
|
||||||
|
} else if (currentSection) {
|
||||||
|
currentContent.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentSection) {
|
||||||
|
sections[currentSection] = currentContent.join('\n').trim();
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatContent(content) {
|
||||||
|
if (!content) return '';
|
||||||
|
|
||||||
|
// Convert markdown lists to HTML
|
||||||
|
let html = content
|
||||||
|
.replace(/^(\d+)\.\s+(.*)$/gm, '<li>$2</li>')
|
||||||
|
.replace(/^-\s+(.*)$/gm, '<li>$1</li>')
|
||||||
|
.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||||
|
|
||||||
|
// Wrap consecutive li elements in ul/ol
|
||||||
|
if (html.includes('<li>')) {
|
||||||
|
if (content.match(/^\d+\./m)) {
|
||||||
|
html = '<ol>' + html + '</ol>';
|
||||||
|
} else {
|
||||||
|
html = '<ul>' + html + '</ul>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert line breaks to paragraphs for non-list content
|
||||||
|
if (!html.includes('<li>')) {
|
||||||
|
html = html.split('\n').filter(l => l.trim()).map(l => '<p>' + l + '</p>').join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createField(label, content, className = '') {
|
||||||
|
if (!content) return '';
|
||||||
|
return `
|
||||||
|
<div class="field ${className}">
|
||||||
|
<label class="field-label">${label} <span class="edit-icon" onclick="toggleEdit(this)" title="Edit field">✎</span></label>
|
||||||
|
<div class="field-value ${content.includes('\n') ? 'multiline' : ''}">${formatContent(content)}</div>
|
||||||
|
<textarea class="field-textarea">${content}</textarea>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEdit(icon) {
|
||||||
|
const field = icon.closest('.field');
|
||||||
|
field.classList.toggle('editing');
|
||||||
|
if (field.classList.contains('editing')) {
|
||||||
|
field.querySelector('.field-textarea').focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sections = parseMarkdown(rawContent);
|
||||||
|
const container = document.getElementById('form-content');
|
||||||
|
|
||||||
|
const fieldMap = [
|
||||||
|
{ key: 'Tipo de usuario', label: 'Tipo de Usuario', class: '' },
|
||||||
|
{ key: 'Donde empieza', label: 'Punto de Entrada', class: '' },
|
||||||
|
{ key: 'Que quiere hacer el usuario', label: 'Objetivo del Usuario', class: '' },
|
||||||
|
{ key: 'Pasos', label: 'Pasos', class: 'field-steps' },
|
||||||
|
{ key: 'Que deberia pasar', label: 'Resultado Esperado', class: '' },
|
||||||
|
{ key: 'Problemas comunes', label: 'Problemas Comunes', class: 'field-problems' },
|
||||||
|
{ key: 'Casos especiales', label: 'Casos Especiales', class: 'field-special' },
|
||||||
|
{ key: 'Flujos relacionados', label: 'Flujos Relacionados', class: '' },
|
||||||
|
{ key: 'Notas tecnicas', label: 'Notas Tecnicas', class: 'field-technical' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
for (const field of fieldMap) {
|
||||||
|
if (sections[field.key]) {
|
||||||
|
html += createField(field.label, sections[field.key], field.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.innerHTML = html;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# 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)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# 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
|
||||||
143
atlas/book/feature-form-samples/index.html
Normal file
143
atlas/book/feature-form-samples/index.html
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Ops Templates Sample - Album</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #1e293b;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container { max-width: 1000px; margin: 0 auto; padding: 2rem 1rem; }
|
||||||
|
header { margin-bottom: 2rem; }
|
||||||
|
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
.breadcrumb a { color: #15803d; text-decoration: none; }
|
||||||
|
.breadcrumb a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 2rem; color: #15803d; }
|
||||||
|
.subtitle { color: #64748b; margin-top: 0.5rem; }
|
||||||
|
.sample-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.user-type {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.user-type h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #15803d;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.user-type h2::before {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #22c55e;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.templates-list { list-style: none; }
|
||||||
|
.templates-list li {
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.templates-list li:last-child { border-bottom: none; }
|
||||||
|
.templates-list a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
.templates-list a:hover { color: #15803d; }
|
||||||
|
.templates-list .name { font-weight: 500; }
|
||||||
|
.templates-list .arrow { color: #94a3b8; }
|
||||||
|
.info-box {
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.info-box h3 { font-size: 0.9rem; color: #15803d; margin-bottom: 0.5rem; }
|
||||||
|
footer {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
footer a { color: #15803d; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="breadcrumb"><a href="/">Album</a> / Ops Templates</div>
|
||||||
|
<h1>Ops Templates Sample</h1>
|
||||||
|
<p class="subtitle">User flow documentation templates - non-technical format for support/ops teams</p>
|
||||||
|
<span class="sample-badge">Sample Data</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Pet Owner</h2>
|
||||||
|
<ul class="templates-list">
|
||||||
|
<li><a href="/book/feature-form-samples/larder/pet-owner/01-registro.md"><span class="name">01. Registro de Usuario</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/pet-owner/02-reservar-turno.md"><span class="name">02. Reservar Turno</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/pet-owner/03-gestion-mascotas.md"><span class="name">03. Gestion de Mascotas</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/pet-owner/04-pago-turno.md"><span class="name">04. Pago de Turno</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/pet-owner/05-historial-medico.md"><span class="name">05. Historial Medico</span><span class="arrow">→</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Veterinarian</h2>
|
||||||
|
<ul class="templates-list">
|
||||||
|
<li><a href="/book/feature-form-samples/larder/veterinarian/01-aceptar-solicitud.md"><span class="name">01. Aceptar/Rechazar Solicitud</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/veterinarian/02-gestion-agenda.md"><span class="name">02. Gestion de Agenda</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/veterinarian/03-realizar-visita.md"><span class="name">03. Realizar Visita</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/veterinarian/04-zonas-cobertura.md"><span class="name">04. Zonas de Cobertura</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/veterinarian/05-historial-pacientes.md"><span class="name">05. Historial de Pacientes</span><span class="arrow">→</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Backoffice</h2>
|
||||||
|
<ul class="templates-list">
|
||||||
|
<li><a href="/book/feature-form-samples/larder/backoffice/01-gestion-solicitudes.md"><span class="name">01. Gestion de Solicitudes</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/backoffice/02-gestion-usuarios.md"><span class="name">02. Gestion de Usuarios</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/backoffice/03-gestion-servicios.md"><span class="name">03. Gestion de Servicios</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/backoffice/04-reembolsos.md"><span class="name">04. Reembolsos</span><span class="arrow">→</span></a></li>
|
||||||
|
<li><a href="/book/feature-form-samples/larder/backoffice/05-reportes.md"><span class="name">05. Reportes</span><span class="arrow">→</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<h3>Template Structure</h3>
|
||||||
|
<p>Each template includes: User type, Entry point, Goal, Steps, Expected result, Common problems, Edge cases, Related flows, Technical notes.</p>
|
||||||
|
<p style="margin-top: 0.5rem;">These templates transform into <a href="/book/gherkin-samples/">Gherkin specs</a> for testing.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/">← Album</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
91
atlas/book/feature-form-samples/template/feature-form.md
Normal file
91
atlas/book/feature-form-samples/template/feature-form.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# 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?
|
||||||
114
atlas/book/gherkin-samples/CLAUDE.md
Normal file
114
atlas/book/gherkin-samples/CLAUDE.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# 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/`
|
||||||
161
atlas/book/gherkin-samples/detail.html
Normal file
161
atlas/book/gherkin-samples/detail.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ filename }} - Gherkin</title>
|
||||||
|
<link rel="stylesheet" href="/static/prism/prism-tomorrow.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/prism/prism-line-numbers.min.css">
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
|
||||||
|
header { margin-bottom: 2rem; }
|
||||||
|
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
.breadcrumb a { color: #818cf8; text-decoration: none; }
|
||||||
|
.breadcrumb a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 1.5rem; color: #e2e8f0; }
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.meta span {
|
||||||
|
background: #1e293b;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
.meta .lang-es { border-left: 3px solid #f59e0b; }
|
||||||
|
.meta .lang-en { border-left: 3px solid #3b82f6; }
|
||||||
|
.meta .sample { background: #422006; color: #fbbf24; }
|
||||||
|
.content {
|
||||||
|
background: #1e293b;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.content-header {
|
||||||
|
background: #334155;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.content-header .filename { color: #94a3b8; }
|
||||||
|
.content-header .copy-btn {
|
||||||
|
background: #475569;
|
||||||
|
border: none;
|
||||||
|
color: #e2e8f0;
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.content-header .copy-btn:hover { background: #64748b; }
|
||||||
|
pre[class*="language-"] {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
code[class*="language-"] {
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.sidebar-box {
|
||||||
|
background: #1e293b;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.sidebar-box h3 { font-size: 0.8rem; color: #64748b; text-transform: uppercase; margin-bottom: 0.75rem; }
|
||||||
|
.sidebar-box a {
|
||||||
|
display: block;
|
||||||
|
color: #818cf8;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.35rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.sidebar-box a:hover { text-decoration: underline; }
|
||||||
|
footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #334155;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
footer a { color: #818cf8; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">Album</a> / <a href="/book/gherkin/">Gherkin</a> / {{ lang }} / {{ user_type }}
|
||||||
|
</div>
|
||||||
|
<h1>{{ filename.replace('.feature', '').replace('-', ' ').title() }}</h1>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="lang-{{ lang }}">{{ 'Espanol' if lang == 'es' else 'English' }}</span>
|
||||||
|
<span>{{ user_type }}</span>
|
||||||
|
<span class="sample">Sample</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-header">
|
||||||
|
<span class="filename">{{ filename }}</span>
|
||||||
|
<button class="copy-btn" onclick="copyContent()">Copy</button>
|
||||||
|
</div>
|
||||||
|
<pre class="line-numbers"><code id="gherkin-content" class="language-gherkin">{{ content }}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="sidebar-box">
|
||||||
|
<h3>Source</h3>
|
||||||
|
<a href="/book/ops-templates/{{ user_type }}/{{ filename.replace('.feature', '.md') }}">Ops Template</a>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-box">
|
||||||
|
<h3>Other Language</h3>
|
||||||
|
{% if lang == 'es' %}
|
||||||
|
<a href="/book/gherkin/en/{{ user_type }}/{{ filename }}">English Version</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/book/gherkin/es/{{ user_type }}/{{ filename }}">Spanish Version</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-box">
|
||||||
|
<h3>Pipeline</h3>
|
||||||
|
<a href="/book/feature-flow/">Feature Flow</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/book/gherkin/">← All Gherkin Files</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/prism/prism.min.js"></script>
|
||||||
|
<script src="/static/prism/prism-gherkin.min.js"></script>
|
||||||
|
<script src="/static/prism/prism-line-numbers.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function copyContent() {
|
||||||
|
const content = document.getElementById('gherkin-content').textContent;
|
||||||
|
navigator.clipboard.writeText(content);
|
||||||
|
const btn = document.querySelector('.copy-btn');
|
||||||
|
btn.textContent = 'Copied!';
|
||||||
|
setTimeout(() => btn.textContent = 'Copy', 2000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# 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 "<estado>"
|
||||||
|
Then solo deberia ver solicitudes con estado "<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
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 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 "<metodo>"
|
||||||
|
When proceso el reembolso
|
||||||
|
Then deberia informar al usuario:
|
||||||
|
"""
|
||||||
|
El reembolso se acreditara en <tiempo>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
97
atlas/book/gherkin-samples/en/backoffice/05-reportes.feature
Normal file
97
atlas/book/gherkin-samples/en/backoffice/05-reportes.feature
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# 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 "<formato>"
|
||||||
|
Then deberia descargarse el archivo en formato <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
|
||||||
91
atlas/book/gherkin-samples/en/pet-owner/01-registro.feature
Normal file
91
atlas/book/gherkin-samples/en/pet-owner/01-registro.feature
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# 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 "<email>"
|
||||||
|
And ingreso contraseña "<password>"
|
||||||
|
And confirmo contraseña "<confirmacion>"
|
||||||
|
And hago click en "Crear cuenta"
|
||||||
|
Then deberia ver error "<mensaje_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
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# 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 <tipo_usuario>
|
||||||
|
Given que soy un usuario <tipo_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 "<email>"
|
||||||
|
And envio la solicitud
|
||||||
|
Then deberia crearse una solicitud en estado "Pendiente"
|
||||||
|
And el dueno deberia ser <estado_dueno>
|
||||||
|
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
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# 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 <campo> igual a "<valor>"
|
||||||
|
Then deberia ver error "<mensaje>"
|
||||||
|
|
||||||
|
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"
|
||||||
140
atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature
Normal file
140
atlas/book/gherkin-samples/en/pet-owner/04-pago-turno.feature
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# 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 "<metodo>"
|
||||||
|
And completo el pago
|
||||||
|
Then el pago deberia ser <resultado>
|
||||||
|
And el estado de acreditacion deberia ser "<acreditacion>"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# 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 "<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
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# 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 <criterio> con valor "<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
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
# 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 "<estado>"
|
||||||
|
Entonces solo deberia ver solicitudes con estado "<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
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# 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 "<tipo>"
|
||||||
|
Entonces solo deberia ver usuarios <tipo>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# 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 <filtro> "<valor>"
|
||||||
|
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
|
||||||
141
atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature
Normal file
141
atlas/book/gherkin-samples/es/backoffice/04-reembolsos.feature
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# 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 "<metodo>"
|
||||||
|
Cuando proceso el reembolso
|
||||||
|
Entonces deberia informar al usuario:
|
||||||
|
"""
|
||||||
|
El reembolso se acreditara en <tiempo>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 |
|
||||||
155
atlas/book/gherkin-samples/es/backoffice/05-reportes.feature
Normal file
155
atlas/book/gherkin-samples/es/backoffice/05-reportes.feature
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# 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 "<formato>"
|
||||||
|
Entonces deberia descargarse el archivo en formato <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
|
||||||
92
atlas/book/gherkin-samples/es/pet-owner/01-registro.feature
Normal file
92
atlas/book/gherkin-samples/es/pet-owner/01-registro.feature
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# 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 "<email>"
|
||||||
|
Y ingreso contraseña "<password>"
|
||||||
|
Y confirmo contraseña "<confirmacion>"
|
||||||
|
Y hago click en "Crear cuenta"
|
||||||
|
Entonces deberia ver error "<mensaje_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
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# 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 <tipo_usuario>
|
||||||
|
Dado que soy un usuario <tipo_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 "<email>"
|
||||||
|
Y envio la solicitud
|
||||||
|
Entonces deberia crearse una solicitud en estado "Pendiente"
|
||||||
|
Y el dueno deberia ser <estado_dueno>
|
||||||
|
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
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# 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 <campo> igual a "<valor>"
|
||||||
|
Entonces deberia ver error "<mensaje>"
|
||||||
|
|
||||||
|
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"
|
||||||
141
atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature
Normal file
141
atlas/book/gherkin-samples/es/pet-owner/04-pago-turno.feature
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# 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 "<metodo>"
|
||||||
|
Y completo el pago
|
||||||
|
Entonces el pago deberia ser <resultado>
|
||||||
|
Y el estado de acreditacion deberia ser "<acreditacion>"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# 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 "<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
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# 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 <criterio> con valor "<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
|
||||||
248
atlas/book/gherkin-samples/index.html
Normal file
248
atlas/book/gherkin-samples/index.html
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Gherkin Samples - Album</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #1e293b;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container { max-width: 1000px; margin: 0 auto; padding: 2rem 1rem; }
|
||||||
|
header { margin-bottom: 2rem; }
|
||||||
|
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
.breadcrumb a { color: #6366f1; text-decoration: none; }
|
||||||
|
.breadcrumb a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 2rem; color: #6366f1; }
|
||||||
|
.subtitle { color: #64748b; margin-top: 0.5rem; }
|
||||||
|
.sample-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.lang-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.lang-tab {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: white;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
.lang-tab.active {
|
||||||
|
border-color: #6366f1;
|
||||||
|
color: #6366f1;
|
||||||
|
background: #eef2ff;
|
||||||
|
}
|
||||||
|
.lang-section { display: none; }
|
||||||
|
.lang-section.active { display: block; }
|
||||||
|
.user-type {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.user-type h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #6366f1;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.user-type h2::before {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #818cf8;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.features-list { list-style: none; }
|
||||||
|
.features-list li {
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.features-list li:last-child { border-bottom: none; }
|
||||||
|
.features-list a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
.features-list a:hover { color: #6366f1; }
|
||||||
|
.features-list .name { font-weight: 500; }
|
||||||
|
.features-list .ext { color: #94a3b8; font-size: 0.85rem; }
|
||||||
|
.features-list .arrow { color: #94a3b8; }
|
||||||
|
.info-box {
|
||||||
|
background: #eef2ff;
|
||||||
|
border: 1px solid #c7d2fe;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.info-box h3 { font-size: 0.9rem; color: #6366f1; margin-bottom: 0.5rem; }
|
||||||
|
.keywords {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.keyword {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #c7d2fe;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
footer a { color: #6366f1; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="breadcrumb"><a href="/">Album</a> / Gherkin Samples</div>
|
||||||
|
<h1>Gherkin Samples</h1>
|
||||||
|
<p class="subtitle">BDD feature files - Given/When/Then specifications derived from ops templates</p>
|
||||||
|
<span class="sample-badge">Sample Data</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="lang-tabs">
|
||||||
|
<button class="lang-tab active" onclick="showLang('es')">Espanol (Dado/Cuando/Entonces)</button>
|
||||||
|
<button class="lang-tab" onclick="showLang('en')">English (Given/When/Then)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Spanish -->
|
||||||
|
<div id="lang-es" class="lang-section active">
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Pet Owner</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/es/pet-owner/01-registro.feature"><span class="name">01. Registro de Usuario</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/pet-owner/02-reservar-turno.feature"><span class="name">02. Reservar Turno</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/pet-owner/03-gestion-mascotas.feature"><span class="name">03. Gestion de Mascotas</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/pet-owner/04-pago-turno.feature"><span class="name">04. Pago de Turno</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/pet-owner/05-historial-medico.feature"><span class="name">05. Historial Medico</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Veterinarian</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/es/veterinarian/01-aceptar-solicitud.feature"><span class="name">01. Aceptar/Rechazar Solicitud</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/veterinarian/02-gestion-agenda.feature"><span class="name">02. Gestion de Agenda</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/veterinarian/03-realizar-visita.feature"><span class="name">03. Realizar Visita</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/veterinarian/04-zonas-cobertura.feature"><span class="name">04. Zonas de Cobertura</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/veterinarian/05-historial-pacientes.feature"><span class="name">05. Historial de Pacientes</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Backoffice</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/es/backoffice/01-gestion-solicitudes.feature"><span class="name">01. Gestion de Solicitudes</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/backoffice/02-gestion-usuarios.feature"><span class="name">02. Gestion de Usuarios</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/backoffice/03-gestion-servicios.feature"><span class="name">03. Gestion de Servicios</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/backoffice/04-reembolsos.feature"><span class="name">04. Reembolsos</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/es/backoffice/05-reportes.feature"><span class="name">05. Reportes</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="info-box">
|
||||||
|
<h3>Spanish Keywords</h3>
|
||||||
|
<div class="keywords">
|
||||||
|
<span class="keyword">Caracteristica</span>
|
||||||
|
<span class="keyword">Escenario</span>
|
||||||
|
<span class="keyword">Dado</span>
|
||||||
|
<span class="keyword">Cuando</span>
|
||||||
|
<span class="keyword">Entonces</span>
|
||||||
|
<span class="keyword">Y</span>
|
||||||
|
<span class="keyword">Pero</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- English -->
|
||||||
|
<div id="lang-en" class="lang-section">
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Pet Owner</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/en/pet-owner/01-registro.feature"><span class="name">01. User Registration</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/pet-owner/02-reservar-turno.feature"><span class="name">02. Book Appointment</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/pet-owner/03-gestion-mascotas.feature"><span class="name">03. Pet Management</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/pet-owner/04-pago-turno.feature"><span class="name">04. Payment</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/pet-owner/05-historial-medico.feature"><span class="name">05. Medical History</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Veterinarian</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/en/veterinarian/01-aceptar-solicitud.feature"><span class="name">01. Accept/Reject Request</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/veterinarian/02-gestion-agenda.feature"><span class="name">02. Schedule Management</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/veterinarian/03-realizar-visita.feature"><span class="name">03. Conduct Visit</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/veterinarian/04-zonas-cobertura.feature"><span class="name">04. Coverage Zones</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/veterinarian/05-historial-pacientes.feature"><span class="name">05. Patient History</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="user-type">
|
||||||
|
<h2>Backoffice</h2>
|
||||||
|
<ul class="features-list">
|
||||||
|
<li><a href="/book/gherkin/en/backoffice/01-gestion-solicitudes.feature"><span class="name">01. Request Management</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/backoffice/02-gestion-usuarios.feature"><span class="name">02. User Management</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/backoffice/03-gestion-servicios.feature"><span class="name">03. Service Management</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/backoffice/04-reembolsos.feature"><span class="name">04. Refunds</span><span class="ext">.feature</span></a></li>
|
||||||
|
<li><a href="/book/gherkin/en/backoffice/05-reportes.feature"><span class="name">05. Reports</span><span class="ext">.feature</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="info-box">
|
||||||
|
<h3>English Keywords</h3>
|
||||||
|
<div class="keywords">
|
||||||
|
<span class="keyword">Feature</span>
|
||||||
|
<span class="keyword">Scenario</span>
|
||||||
|
<span class="keyword">Given</span>
|
||||||
|
<span class="keyword">When</span>
|
||||||
|
<span class="keyword">Then</span>
|
||||||
|
<span class="keyword">And</span>
|
||||||
|
<span class="keyword">But</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/">← Album</a> |
|
||||||
|
<a href="/book/ops-templates/">Source Templates</a> |
|
||||||
|
<a href="/book/feature-flow/">Feature Flow Pipeline</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showLang(lang) {
|
||||||
|
document.querySelectorAll('.lang-section').forEach(s => s.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.lang-tab').forEach(t => t.classList.remove('active'));
|
||||||
|
document.getElementById('lang-' + lang).classList.add('active');
|
||||||
|
event.target.classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
160
atlas/index.html
Normal file
160
atlas/index.html
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Album · Pawprint</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%2315803d'%3E%3Cpath d='M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z' opacity='0.3'/%3E%3Cpath d='M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z' opacity='0.5'/%3E%3Cpath d='M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cpath d='M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42' fill='none' stroke='%2315803d' stroke-width='2'/%3E%3Cline x1='24' y1='10' x2='24' y2='42' stroke='%2315803d' stroke-width='2'/%3E%3C/svg%3E">
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html { background: #0a0a0a; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #e5e5e5;
|
||||||
|
background: #163528;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.logo { width: 64px; height: 64px; color: white; }
|
||||||
|
h1 { font-size: 2.5rem; margin: 0; color: white; }
|
||||||
|
.tagline {
|
||||||
|
color: rgba(255,255,255,0.85);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.3);
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
background: #000000;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
section h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #86efac;
|
||||||
|
}
|
||||||
|
.composition {
|
||||||
|
background: #000000;
|
||||||
|
border: 2px solid #3e915d;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.composition h3 { margin: 0 0 0.75rem 0; font-size: 1.1rem; color: #86efac; }
|
||||||
|
.composition > p { margin: 0 0 1rem 0; font-size: 0.9rem; color: #a3a3a3; }
|
||||||
|
.components {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.component {
|
||||||
|
background: #0a0a0a;
|
||||||
|
border: 1px solid #6b665e;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.component h4 { margin: 0 0 0.25rem 0; font-size: 0.95rem; color: #86efac; }
|
||||||
|
.component p { margin: 0; font-size: 0.85rem; color: #a3a3a3; }
|
||||||
|
.books {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.books li {
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-bottom: 1px solid #6b665e;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.books li:last-child { border-bottom: none; }
|
||||||
|
.books a { color: #86efac; text-decoration: none; font-weight: 500; }
|
||||||
|
.books a:hover { text-decoration: underline; }
|
||||||
|
.status {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.3);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
}
|
||||||
|
footer a { color: white; }
|
||||||
|
footer .disabled { opacity: 0.5; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header style="position:relative;">
|
||||||
|
<!-- Open book -->
|
||||||
|
<svg class="logo" viewBox="0 0 48 48" fill="currentColor">
|
||||||
|
<path d="M4 8 C4 8 12 6 24 10 L24 42 C12 38 4 40 4 40 Z" opacity="0.3"/>
|
||||||
|
<path d="M44 8 C44 8 36 6 24 10 L24 42 C36 38 44 40 44 40 Z" opacity="0.5"/>
|
||||||
|
<path d="M4 8 C4 8 12 6 24 10 M44 8 C44 8 36 6 24 10" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M4 40 C4 40 12 38 24 42 M44 40 C44 40 36 38 24 42" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<line x1="24" y1="10" x2="24" y2="42" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
<h1>Album</h1>
|
||||||
|
{% if pawprint_url %}<a href="{{ pawprint_url }}" style="position:absolute;right:0;top:50%;transform:translateY(-50%);color:rgba(255,255,255,0.7);font-size:0.85rem;">← Pawprint</a>{% endif %}
|
||||||
|
</header>
|
||||||
|
<p class="tagline">conocimiento y toma de decisiones <!-- Actionable documentation --></p>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="composition">
|
||||||
|
<h3>Books</h3>
|
||||||
|
<div class="components">
|
||||||
|
<div class="component"><h4>Template</h4></div>
|
||||||
|
<div class="component"><h4>Larder</h4></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Books</h2>
|
||||||
|
<ul class="books">
|
||||||
|
{% for book in books %}
|
||||||
|
<li>
|
||||||
|
<a href="/book/{{ book.slug }}/">{{ book.title }}</a>
|
||||||
|
<span class="status {% if book.status == 'ready' %}ready{% elif book.status == 'building' %}building{% endif %}">{{ book.status | capitalize }}</span>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Templates</h2>
|
||||||
|
<ul class="books">
|
||||||
|
{% for template in templates %}
|
||||||
|
<li><a href="/template/{{ template.slug }}/">{{ template.title }}</a><span class="status {% if template.status == 'ready' %}ready{% elif template.status == 'building' %}building{% endif %}">{{ template.status | capitalize }}</span></li>
|
||||||
|
{% else %}
|
||||||
|
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Larders</h2>
|
||||||
|
<ul class="books">
|
||||||
|
{% for larder in larders %}
|
||||||
|
<li><a href="/book/feature-form-samples/larder/">{{ larder.title }}</a><span class="status {% if larder.status == 'ready' %}ready{% elif larder.status == 'building' %}building{% endif %}">{{ larder.status | capitalize }}</span></li>
|
||||||
|
{% else %}
|
||||||
|
<li><span style="color:#86efac;font-weight:500;">--</span><span class="status">Pending</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
{% if pawprint_url %}<a href="{{ pawprint_url }}">← Pawprint</a>{% else %}<span class="disabled">← Pawprint</span>{% endif %}
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
369
atlas/main.py
Normal file
369
atlas/main.py
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
"""
|
||||||
|
Album - Documentation system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
from pathlib import Path
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
app = FastAPI(title="Album", version="0.1.0")
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).parent.resolve()
|
||||||
|
BOOK_DIR = BASE_DIR / "book"
|
||||||
|
STATIC_DIR = BASE_DIR / "static"
|
||||||
|
|
||||||
|
# Create static directory if it doesn't exist
|
||||||
|
STATIC_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory=str(BASE_DIR))
|
||||||
|
|
||||||
|
# Serve static files
|
||||||
|
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
||||||
|
|
||||||
|
# Pawprint URL for data fetching
|
||||||
|
PAWPRINT_URL = os.getenv("PAWPRINT_URL", "http://localhost:12000")
|
||||||
|
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
"""Fetch data from pawprint hub."""
|
||||||
|
try:
|
||||||
|
resp = httpx.get(f"{PAWPRINT_URL}/api/data/album", timeout=5.0)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to fetch data from pawprint: {e}")
|
||||||
|
return {"templates": [], "larders": [], "books": []}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok", "service": "album"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def index(request: Request):
|
||||||
|
data = get_data()
|
||||||
|
return templates.TemplateResponse("index.html", {
|
||||||
|
"request": request,
|
||||||
|
"pawprint_url": os.getenv("PAWPRINT_EXTERNAL_URL", PAWPRINT_URL),
|
||||||
|
**data,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/data")
|
||||||
|
def api_data():
|
||||||
|
"""API endpoint for frontend data (proxied from pawprint)."""
|
||||||
|
return get_data()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Book: Feature Flow (HTML presentations) ---
|
||||||
|
@app.get("/book/feature-flow/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/feature-flow", response_class=HTMLResponse)
|
||||||
|
def feature_flow_index():
|
||||||
|
"""Redirect to English presentation by default"""
|
||||||
|
return """<!DOCTYPE html>
|
||||||
|
<html><head><meta charset="UTF-8"><title>Feature Flow</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: system-ui; background: #0f172a; color: #e2e8f0; min-height: 100vh;
|
||||||
|
display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
||||||
|
h1 { font-size: 2rem; margin-bottom: 2rem; }
|
||||||
|
.links { display: flex; gap: 1rem; }
|
||||||
|
a { background: #334155; color: #e2e8f0; padding: 1rem 2rem; border-radius: 8px;
|
||||||
|
text-decoration: none; font-size: 1.1rem; }
|
||||||
|
a:hover { background: #475569; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>Feature Flow - Standardization Pipeline</h1>
|
||||||
|
<div class="links">
|
||||||
|
<a href="/book/feature-flow/en">English</a>
|
||||||
|
<a href="/book/feature-flow/es">Español</a>
|
||||||
|
</div>
|
||||||
|
</body></html>"""
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/feature-flow/en", response_class=HTMLResponse)
|
||||||
|
def feature_flow_en():
|
||||||
|
html_file = BOOK_DIR / "feature-flow" / "index-en.html"
|
||||||
|
return HTMLResponse(html_file.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/feature-flow/es", response_class=HTMLResponse)
|
||||||
|
def feature_flow_es():
|
||||||
|
html_file = BOOK_DIR / "feature-flow" / "index-es.html"
|
||||||
|
return HTMLResponse(html_file.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
# --- Book: Feature Form Samples (templated book) ---
|
||||||
|
@app.get("/book/feature-form-samples/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/feature-form-samples", response_class=HTMLResponse)
|
||||||
|
def feature_form_samples_index(request: Request):
|
||||||
|
"""Templated book landing page"""
|
||||||
|
data = get_data()
|
||||||
|
book = next((b for b in data.get("books", []) if b["slug"] == "feature-form-samples"), None)
|
||||||
|
if not book:
|
||||||
|
return HTMLResponse("<h1>Book not found</h1>", status_code=404)
|
||||||
|
return templates.TemplateResponse("book-template.html", {
|
||||||
|
"request": request,
|
||||||
|
"book": book,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/feature-form-samples/template/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/feature-form-samples/template", response_class=HTMLResponse)
|
||||||
|
def feature_form_samples_template():
|
||||||
|
"""View the template - styled like actual feature forms"""
|
||||||
|
html = """<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Feature Form Template · Album</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #1e293b;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container { max-width: 800px; margin: 0 auto; padding: 2rem 1rem; }
|
||||||
|
header { margin-bottom: 1.5rem; }
|
||||||
|
.breadcrumb { font-size: 0.9rem; color: #64748b; margin-bottom: 0.5rem; }
|
||||||
|
.breadcrumb a { color: #15803d; text-decoration: none; }
|
||||||
|
.breadcrumb a:hover { text-decoration: underline; }
|
||||||
|
h1 { font-size: 1.5rem; color: #15803d; }
|
||||||
|
.meta { display: flex; gap: 0.5rem; margin-top: 0.5rem; font-size: 0.8rem; }
|
||||||
|
.meta span { background: #f1f5f9; padding: 0.2rem 0.5rem; border-radius: 4px; color: #64748b; }
|
||||||
|
.meta .template { background: #dbeafe; color: #1d4ed8; }
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.form-header {
|
||||||
|
background: linear-gradient(135deg, #15803d, #22c55e);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
.form-header h2 { font-size: 1.1rem; font-weight: 600; }
|
||||||
|
.form-body { padding: 1.5rem; }
|
||||||
|
|
||||||
|
.field { margin-bottom: 1.25rem; }
|
||||||
|
.field:last-child { margin-bottom: 0; }
|
||||||
|
.field-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
.field-value {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 2px dashed #cbd5e1;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-style: italic;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
}
|
||||||
|
.field-value.multiline { min-height: 4rem; }
|
||||||
|
|
||||||
|
.field-steps .field-value { padding-left: 0.5rem; }
|
||||||
|
.field-steps ol { list-style: none; padding-left: 0; margin: 0; }
|
||||||
|
.field-steps li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.field-steps li::before {
|
||||||
|
content: attr(data-step);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: #cbd5e1;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-problems .field-value { background: #fef2f2; border-color: #fecaca; }
|
||||||
|
.field-special .field-value { background: #fffbeb; border-color: #fde68a; }
|
||||||
|
.field-technical .field-value { background: #f0fdf4; border-color: #bbf7d0; font-family: monospace; font-size: 0.85rem; }
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
footer a { color: #15803d; text-decoration: none; }
|
||||||
|
footer a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">Album</a> / <a href="/book/feature-form-samples/">Feature Form Samples</a> / Template
|
||||||
|
</div>
|
||||||
|
<h1>Feature Form Template</h1>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="template">Template</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<h2>[Nombre del Flujo]</h2>
|
||||||
|
</div>
|
||||||
|
<div class="form-body">
|
||||||
|
<div class="field">
|
||||||
|
<label class="field-label">Tipo de Usuario</label>
|
||||||
|
<div class="field-value">[Dueno de mascota / Veterinario / Admin]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="field-label">Punto de Entrada</label>
|
||||||
|
<div class="field-value">[Que pagina/boton/link]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="field-label">Objetivo del Usuario</label>
|
||||||
|
<div class="field-value">[Objetivo en una oracion]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field field-steps">
|
||||||
|
<label class="field-label">Pasos</label>
|
||||||
|
<div class="field-value multiline">
|
||||||
|
<ol>
|
||||||
|
<li data-step="1">[Primera cosa que hace el usuario]</li>
|
||||||
|
<li data-step="2">[Segunda cosa que hace el usuario]</li>
|
||||||
|
<li data-step="3">[etc.]</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="field-label">Resultado Esperado</label>
|
||||||
|
<div class="field-value">[Resultado esperado cuando todo funciona]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field field-problems">
|
||||||
|
<label class="field-label">Problemas Comunes</label>
|
||||||
|
<div class="field-value multiline">[Problema 1]<br>[Problema 2]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field field-special">
|
||||||
|
<label class="field-label">Casos Especiales</label>
|
||||||
|
<div class="field-value multiline">[Caso especial 1]<br>[Caso especial 2]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="field-label">Flujos Relacionados</label>
|
||||||
|
<div class="field-value">[Otros flujos que se conectan con este]</div>
|
||||||
|
</div>
|
||||||
|
<div class="field field-technical">
|
||||||
|
<label class="field-label">Notas Tecnicas</label>
|
||||||
|
<div class="field-value">[Notas para el equipo de desarrollo]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/book/feature-form-samples/">← Feature Form Samples</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
return HTMLResponse(html)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/feature-form-samples/larder/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/feature-form-samples/larder", response_class=HTMLResponse)
|
||||||
|
def feature_form_samples_larder():
|
||||||
|
"""Browse the larder (actual data)"""
|
||||||
|
html_file = BOOK_DIR / "feature-form-samples" / "index.html"
|
||||||
|
if html_file.exists():
|
||||||
|
return HTMLResponse(html_file.read_text())
|
||||||
|
return HTMLResponse("<h1>Larder index not found</h1>", status_code=404)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/feature-form-samples/larder/{user_type}/{filename}", response_class=HTMLResponse)
|
||||||
|
def feature_form_samples_detail(request: Request, user_type: str, filename: str):
|
||||||
|
"""View a specific feature form"""
|
||||||
|
# Look in the larder subfolder (feature-form)
|
||||||
|
larder_dir = BOOK_DIR / "feature-form-samples" / "feature-form"
|
||||||
|
file_path = larder_dir / user_type / filename
|
||||||
|
if not file_path.exists():
|
||||||
|
return HTMLResponse("<h1>Not found</h1>", status_code=404)
|
||||||
|
|
||||||
|
content = file_path.read_text()
|
||||||
|
detail_file = BOOK_DIR / "feature-form-samples" / "detail.html"
|
||||||
|
return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), {
|
||||||
|
"request": request,
|
||||||
|
"user_type": user_type,
|
||||||
|
"filename": filename,
|
||||||
|
"content": content,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# --- Book: Gherkin Samples ---
|
||||||
|
@app.get("/book/gherkin-samples/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/gherkin-samples", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/gherkin/", response_class=HTMLResponse) # Alias
|
||||||
|
@app.get("/book/gherkin", response_class=HTMLResponse) # Alias
|
||||||
|
def gherkin_samples_index():
|
||||||
|
"""Browse gherkin samples"""
|
||||||
|
html_file = BOOK_DIR / "gherkin-samples" / "index.html"
|
||||||
|
return HTMLResponse(html_file.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/book/gherkin-samples/{lang}/{user_type}/{filename}", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/gherkin/{lang}/{user_type}/{filename}", response_class=HTMLResponse) # Alias
|
||||||
|
def gherkin_samples_detail(request: Request, lang: str, user_type: str, filename: str):
|
||||||
|
"""View a specific gherkin file"""
|
||||||
|
file_path = BOOK_DIR / "gherkin-samples" / lang / user_type / filename
|
||||||
|
if not file_path.exists() or not file_path.suffix == ".feature":
|
||||||
|
return HTMLResponse("<h1>Not found</h1>", status_code=404)
|
||||||
|
|
||||||
|
content = file_path.read_text()
|
||||||
|
detail_file = BOOK_DIR / "gherkin-samples" / "detail.html"
|
||||||
|
return templates.TemplateResponse(str(detail_file.relative_to(BASE_DIR)), {
|
||||||
|
"request": request,
|
||||||
|
"lang": lang,
|
||||||
|
"user_type": user_type,
|
||||||
|
"filename": filename,
|
||||||
|
"content": content,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# --- Book: Architecture Model (static site) ---
|
||||||
|
app.mount("/book/arch-model", StaticFiles(directory=str(BOOK_DIR / "arch-model"), html=True), name="arch-model")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Book: Drive Index ---
|
||||||
|
@app.get("/book/drive-index/", response_class=HTMLResponse)
|
||||||
|
@app.get("/book/drive-index", response_class=HTMLResponse)
|
||||||
|
def drive_index():
|
||||||
|
"""Browse drive index"""
|
||||||
|
html_file = BOOK_DIR / "drive-index" / "index.html"
|
||||||
|
return HTMLResponse(html_file.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(
|
||||||
|
"main:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=int(os.getenv("PORT", "12002")),
|
||||||
|
reload=os.getenv("DEV", "").lower() in ("1", "true"),
|
||||||
|
)
|
||||||
4
atlas/requirements.txt
Normal file
4
atlas/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi>=0.104.0
|
||||||
|
uvicorn>=0.24.0
|
||||||
|
jinja2>=3.1.0
|
||||||
|
httpx>=0.25.0
|
||||||
1
atlas/static/prism/prism-gherkin.min.js
vendored
Normal file
1
atlas/static/prism/prism-gherkin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
atlas/static/prism/prism-line-numbers.min.css
vendored
Normal file
1
atlas/static/prism/prism-line-numbers.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
|
||||||
1
atlas/static/prism/prism-line-numbers.min.js
vendored
Normal file
1
atlas/static/prism/prism-line-numbers.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);t<r&&(t=r),t>s&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("<span></span>");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;r<t.length;r++)void 0===t[r]&&(t[r]=n.children[i++].getBoundingClientRect().height)})),t.forEach((function(e){var n=e.sizer,t=e.element.querySelector(".line-numbers-rows");n.style.display="none",n.innerHTML="",e.lineHeights.forEach((function(e,n){t.children[n].style.height=e+"px"}))}))}}}();
|
||||||
1
atlas/static/prism/prism-tomorrow.min.css
vendored
Normal file
1
atlas/static/prism/prism-tomorrow.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
|
||||||
1
atlas/static/prism/prism.min.js
vendored
Normal file
1
atlas/static/prism/prism.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
build.py
23
build.py
@@ -142,6 +142,29 @@ def copy_cfg(output_dir: Path, cfg_name: str | None):
|
|||||||
log.info(f" Copying {cfg_name} databrowse depot...")
|
log.info(f" Copying {cfg_name} databrowse depot...")
|
||||||
target = output_dir / "station" / "monitors" / "databrowse" / "depot"
|
target = output_dir / "station" / "monitors" / "databrowse" / "depot"
|
||||||
copy_path(room_databrowse, target)
|
copy_path(room_databrowse, target)
|
||||||
|
|
||||||
|
# Copy room-specific tester tests if exists
|
||||||
|
room_tests = room_cfg / "tester" / "tests"
|
||||||
|
if room_tests.exists():
|
||||||
|
log.info(f" Copying {cfg_name} tester tests...")
|
||||||
|
target = output_dir / "station" / "tools" / "tester" / "tests"
|
||||||
|
copy_path(room_tests, target)
|
||||||
|
|
||||||
|
# Copy room-specific monitors if exists
|
||||||
|
room_monitors = room_cfg / "monitors"
|
||||||
|
if room_monitors.exists():
|
||||||
|
log.info(f" Copying {cfg_name} monitors...")
|
||||||
|
for monitor in room_monitors.iterdir():
|
||||||
|
if monitor.is_dir():
|
||||||
|
target = output_dir / "station" / "monitors" / monitor.name
|
||||||
|
copy_path(monitor, target)
|
||||||
|
|
||||||
|
# Copy room-specific models if exists
|
||||||
|
room_models = room_cfg / "models"
|
||||||
|
if room_models.exists():
|
||||||
|
log.info(f" Copying {cfg_name} models...")
|
||||||
|
target = output_dir / "models" / cfg_name
|
||||||
|
copy_path(room_models, target)
|
||||||
else:
|
else:
|
||||||
log.warning(f"Room config '{cfg_name}' not found at {room_cfg}")
|
log.warning(f"Room config '{cfg_name}' not found at {room_cfg}")
|
||||||
|
|
||||||
|
|||||||
156
cfg/amar/models/__init__.py
Normal file
156
cfg/amar/models/__init__.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
"""
|
||||||
|
Pawprint Models - Platform Agnostic Definitions
|
||||||
|
|
||||||
|
Portable to: TypeScript, Pydantic, Django, SQLAlchemy, etc.
|
||||||
|
|
||||||
|
Hierarchy:
|
||||||
|
pawprint (abstract)
|
||||||
|
├── artery → Pulse = Vein + Nest + Larder
|
||||||
|
├── album → Book = Template + Larder
|
||||||
|
└── ward → Table = Tools + Nest + Larder
|
||||||
|
|
||||||
|
Shared components: Nest, Larder
|
||||||
|
System-specific: Vein (artery), Template (album), Tools (ward)
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Larder in album generated from Template = "Book (written)"
|
||||||
|
- Same Larder exists independently in ward/artery
|
||||||
|
- Nest contains runtime configs, credentials, targets
|
||||||
|
- Larder contains data, provisions, stored content
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Optional, List
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class Status(Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
PLANNED = "planned"
|
||||||
|
BUILDING = "building"
|
||||||
|
DEV = "dev"
|
||||||
|
LIVE = "live"
|
||||||
|
READY = "ready"
|
||||||
|
|
||||||
|
|
||||||
|
class System(Enum):
|
||||||
|
ARTERY = "artery"
|
||||||
|
ALBUM = "album"
|
||||||
|
WARD = "ward"
|
||||||
|
|
||||||
|
|
||||||
|
# === Shared Components ===
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Nest:
|
||||||
|
"""Runtime environment configuration.
|
||||||
|
|
||||||
|
Contains: credentials, targets, runtime configs.
|
||||||
|
Shared across: artery, ward
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
# References to actual config files/secrets
|
||||||
|
config_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Larder:
|
||||||
|
"""Data storage / provisions.
|
||||||
|
|
||||||
|
Contains: data, transforms, parsers, dumps.
|
||||||
|
Shared across: artery, album, ward
|
||||||
|
|
||||||
|
Note: When generated from Template in album, appears as "Book (written)"
|
||||||
|
but exists as independent Larder in ward/artery.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
# Optional source template (if generated)
|
||||||
|
source_template: Optional[str] = None
|
||||||
|
# Path to data
|
||||||
|
data_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# === System-Specific Components ===
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Vein:
|
||||||
|
"""Connector (artery-specific).
|
||||||
|
|
||||||
|
Single responsibility data connector.
|
||||||
|
Examples: jira, google, slack, whatsapp, cash, vnc
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
system: System = field(default=System.ARTERY, init=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Template:
|
||||||
|
"""Documentation template (album-specific).
|
||||||
|
|
||||||
|
Gherkin, BDD patterns, generators.
|
||||||
|
Examples: feature-form, gherkin
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
system: System = field(default=System.ALBUM, init=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Tool:
|
||||||
|
"""Execution tool (ward-specific).
|
||||||
|
|
||||||
|
Test runners, seeders, scripts.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
system: System = field(default=System.WARD, init=False)
|
||||||
|
|
||||||
|
|
||||||
|
# === Composed Types ===
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Pulse:
|
||||||
|
"""Composed data flow (artery).
|
||||||
|
|
||||||
|
Pulse = Vein + Nest + Larder
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
vein: Optional[Vein] = None
|
||||||
|
nest: Optional[Nest] = None
|
||||||
|
larder: Optional[Larder] = None
|
||||||
|
system: System = field(default=System.ARTERY, init=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Book:
|
||||||
|
"""Composed documentation (album).
|
||||||
|
|
||||||
|
Book = Template + Larder
|
||||||
|
|
||||||
|
Note: Output larder can be referenced independently in other systems.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
template: Optional[Template] = None
|
||||||
|
larder: Optional[Larder] = None
|
||||||
|
# If this book produces a larder, it's tracked here
|
||||||
|
output_larder: Optional[Larder] = None
|
||||||
|
system: System = field(default=System.ALBUM, init=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Table:
|
||||||
|
"""Composed execution bundle (ward).
|
||||||
|
|
||||||
|
Table = Tools + Nest + Larder
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
status: Status = Status.PENDING
|
||||||
|
tools: List[Tool] = field(default_factory=list)
|
||||||
|
nest: Optional[Nest] = None
|
||||||
|
larder: Optional[Larder] = None
|
||||||
|
system: System = field(default=System.WARD, init=False)
|
||||||
191
cfg/amar/models/django/models.py
Normal file
191
cfg/amar/models/django/models.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
"""
|
||||||
|
Django models - Generated from schema.json
|
||||||
|
|
||||||
|
DO NOT EDIT MANUALLY - Regenerate from schema.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = "pending", "Pending"
|
||||||
|
PLANNED = "planned", "Planned"
|
||||||
|
BUILDING = "building", "Building"
|
||||||
|
DEV = "dev", "Dev"
|
||||||
|
LIVE = "live", "Live"
|
||||||
|
READY = "ready", "Ready"
|
||||||
|
|
||||||
|
|
||||||
|
class System(models.TextChoices):
|
||||||
|
ARTERY = "artery", "Artery"
|
||||||
|
ALBUM = "album", "Album"
|
||||||
|
WARD = "ward", "Ward"
|
||||||
|
|
||||||
|
|
||||||
|
# === Shared Components ===
|
||||||
|
|
||||||
|
class Nest(models.Model):
|
||||||
|
"""Runtime environment configuration. Shared across artery, ward."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="nests")
|
||||||
|
config_path = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_nest"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Larder(models.Model):
|
||||||
|
"""Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="larders")
|
||||||
|
source_template = models.CharField(max_length=255, blank=True, null=True, help_text="Template name if generated")
|
||||||
|
data_path = models.CharField(max_length=255, blank=True, null=True, help_text="Path to data files")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_larder"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
# === System-Specific Components ===
|
||||||
|
|
||||||
|
class Vein(models.Model):
|
||||||
|
"""Connector (artery). Single responsibility."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="veins")
|
||||||
|
system = models.CharField(max_length=20, default="artery", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_vein"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Template(models.Model):
|
||||||
|
"""Documentation template (album). Gherkin, BDD patterns."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="templates")
|
||||||
|
template_path = models.CharField(max_length=255, blank=True, null=True, help_text="Path to template files")
|
||||||
|
system = models.CharField(max_length=20, default="album", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_template"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(models.Model):
|
||||||
|
"""Execution tool (ward). Test runners, seeders."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="tools")
|
||||||
|
system = models.CharField(max_length=20, default="ward", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_tool"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Monitor(models.Model):
|
||||||
|
"""Service monitor (ward). Health checks, status watchers."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="monitors")
|
||||||
|
system = models.CharField(max_length=20, default="ward", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_monitor"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Cabinet(models.Model):
|
||||||
|
"""Tool cabinet (ward). Contains 0+ tools."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="cabinets")
|
||||||
|
tools = models.ManyToManyField(Tool, blank=True)
|
||||||
|
system = models.CharField(max_length=20, default="ward", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_cabinet"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
# === Composed Types ===
|
||||||
|
|
||||||
|
class Pulse(models.Model):
|
||||||
|
"""Composed data flow (artery). Pulse = Vein + Nest + Larder."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses")
|
||||||
|
vein = models.ForeignKey(Vein, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses")
|
||||||
|
nest = models.ForeignKey(Nest, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses")
|
||||||
|
larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="pulses")
|
||||||
|
system = models.CharField(max_length=20, default="artery", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_pulse"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Book(models.Model):
|
||||||
|
"""Composed documentation (album). Book = Template + Larder."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="books")
|
||||||
|
template = models.ForeignKey(Template, on_delete=models.SET_NULL, blank=True, null=True, related_name="books")
|
||||||
|
larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="books")
|
||||||
|
output_larder = models.ForeignKey(Larder, on_delete=models.SET_NULL, blank=True, null=True, related_name="books")
|
||||||
|
system = models.CharField(max_length=20, default="album", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_book"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Table(models.Model):
|
||||||
|
"""Composed execution bundle (ward). Table = Cabinet + Nest + Larders."""
|
||||||
|
name = models.CharField(max_length=255, help_text="Unique identifier")
|
||||||
|
slug = models.SlugField(max_length=255, unique=True, help_text="URL-friendly identifier")
|
||||||
|
title = models.CharField(max_length=255, help_text="Display title for UI")
|
||||||
|
status = models.ForeignKey(Status, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables")
|
||||||
|
cabinet = models.ForeignKey(Cabinet, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables")
|
||||||
|
nest = models.ForeignKey(Nest, on_delete=models.SET_NULL, blank=True, null=True, related_name="tables")
|
||||||
|
larders = models.ManyToManyField(Larder, blank=True)
|
||||||
|
system = models.CharField(max_length=20, default="ward", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "pawprint_table"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
213
cfg/amar/models/prisma/schema.prisma
Normal file
213
cfg/amar/models/prisma/schema.prisma
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
// Prisma schema - Generated from schema.json
|
||||||
|
//
|
||||||
|
// DO NOT EDIT MANUALLY - Regenerate from schema.json
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
PENDING
|
||||||
|
PLANNED
|
||||||
|
BUILDING
|
||||||
|
DEV
|
||||||
|
LIVE
|
||||||
|
READY
|
||||||
|
}
|
||||||
|
|
||||||
|
enum System {
|
||||||
|
ARTERY
|
||||||
|
ALBUM
|
||||||
|
WARD
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Shared Components ===
|
||||||
|
|
||||||
|
/// Runtime environment configuration. Shared across artery, ward.
|
||||||
|
model Nest {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
config_path String?
|
||||||
|
|
||||||
|
@@map("pawprint_nest")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery.
|
||||||
|
model Larder {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
source_template String?
|
||||||
|
data_path String?
|
||||||
|
|
||||||
|
@@map("pawprint_larder")
|
||||||
|
}
|
||||||
|
|
||||||
|
// === System-Specific Components ===
|
||||||
|
|
||||||
|
/// Connector (artery). Single responsibility.
|
||||||
|
model Vein {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
system String @default("artery")
|
||||||
|
|
||||||
|
@@map("pawprint_vein")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Documentation template (album). Gherkin, BDD patterns.
|
||||||
|
model Template {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
template_path String?
|
||||||
|
system String @default("album")
|
||||||
|
|
||||||
|
@@map("pawprint_template")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execution tool (ward). Test runners, seeders.
|
||||||
|
model Tool {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
system String @default("ward")
|
||||||
|
|
||||||
|
@@map("pawprint_tool")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service monitor (ward). Health checks, status watchers.
|
||||||
|
model Monitor {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
system String @default("ward")
|
||||||
|
|
||||||
|
@@map("pawprint_monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tool cabinet (ward). Contains 0+ tools.
|
||||||
|
model Cabinet {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
tools Tool[]
|
||||||
|
system String @default("ward")
|
||||||
|
|
||||||
|
@@map("pawprint_cabinet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Composed Types ===
|
||||||
|
|
||||||
|
/// Composed data flow (artery). Pulse = Vein + Nest + Larder.
|
||||||
|
model Pulse {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
vein Vein? @relation(fields: [veinId], references: [id])
|
||||||
|
veinId Int?
|
||||||
|
nest Nest? @relation(fields: [nestId], references: [id])
|
||||||
|
nestId Int?
|
||||||
|
larder Larder? @relation(fields: [larderId], references: [id])
|
||||||
|
larderId Int?
|
||||||
|
system String @default("artery")
|
||||||
|
|
||||||
|
@@map("pawprint_pulse")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Composed documentation (album). Book = Template + Larder.
|
||||||
|
model Book {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
template Template? @relation(fields: [templateId], references: [id])
|
||||||
|
templateId Int?
|
||||||
|
larder Larder? @relation(fields: [larderId], references: [id])
|
||||||
|
larderId Int?
|
||||||
|
output_larder Larder? @relation(fields: [output_larderId], references: [id])
|
||||||
|
output_larderId Int?
|
||||||
|
system String @default("album")
|
||||||
|
|
||||||
|
@@map("pawprint_book")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Composed execution bundle (ward). Table = Cabinet + Nest + Larders.
|
||||||
|
model Table {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
slug String @unique
|
||||||
|
title String
|
||||||
|
status Status? @relation(fields: [statusId], references: [id])
|
||||||
|
statusId Int?
|
||||||
|
cabinet Cabinet? @relation(fields: [cabinetId], references: [id])
|
||||||
|
cabinetId Int?
|
||||||
|
nest Nest? @relation(fields: [nestId], references: [id])
|
||||||
|
nestId Int?
|
||||||
|
larders Larder[]
|
||||||
|
system String @default("ward")
|
||||||
|
|
||||||
|
@@map("pawprint_table")
|
||||||
|
}
|
||||||
187
cfg/amar/models/pydantic/__init__.py
Normal file
187
cfg/amar/models/pydantic/__init__.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
"""
|
||||||
|
Pydantic models - Generated from schema.json
|
||||||
|
|
||||||
|
DO NOT EDIT MANUALLY - Regenerate from schema.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional, List, Literal
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class Status(str, Enum):
|
||||||
|
PENDING = "pending"
|
||||||
|
PLANNED = "planned"
|
||||||
|
BUILDING = "building"
|
||||||
|
DEV = "dev"
|
||||||
|
LIVE = "live"
|
||||||
|
READY = "ready"
|
||||||
|
|
||||||
|
|
||||||
|
class System(str, Enum):
|
||||||
|
ARTERY = "artery"
|
||||||
|
ALBUM = "album"
|
||||||
|
WARD = "ward"
|
||||||
|
|
||||||
|
|
||||||
|
class ToolType(str, Enum):
|
||||||
|
APP = "app"
|
||||||
|
CLI = "cli"
|
||||||
|
|
||||||
|
|
||||||
|
# === Shared Components ===
|
||||||
|
|
||||||
|
class Nest(BaseModel):
|
||||||
|
"""Runtime environment configuration. Shared across artery, ward."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
config_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Larder(BaseModel):
|
||||||
|
"""Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
source_template: Optional[str] = None
|
||||||
|
data_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# === System-Specific Components ===
|
||||||
|
|
||||||
|
class Vein(BaseModel):
|
||||||
|
"""Connector (artery). Single responsibility."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
system: Literal["artery"] = "artery"
|
||||||
|
mock: Optional[bool] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Template(BaseModel):
|
||||||
|
"""Documentation template (album). Gherkin, BDD patterns."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
template_path: Optional[str] = None
|
||||||
|
system: Literal["album"] = "album"
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(BaseModel):
|
||||||
|
"""Execution tool (ward). Test runners, seeders."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
system: Literal["ward"] = "ward"
|
||||||
|
type: Optional[ToolType] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
path: Optional[str] = None
|
||||||
|
url: Optional[str] = None
|
||||||
|
cli: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Monitor(BaseModel):
|
||||||
|
"""Service monitor (ward). Health checks, status watchers."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
system: Literal["ward"] = "ward"
|
||||||
|
|
||||||
|
|
||||||
|
class Cabinet(BaseModel):
|
||||||
|
"""Tool cabinet (ward). Contains 0+ tools."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
tools: List[Tool] = Field(default_factory=list)
|
||||||
|
system: Literal["ward"] = "ward"
|
||||||
|
|
||||||
|
|
||||||
|
# === Composed Types ===
|
||||||
|
|
||||||
|
class Pulse(BaseModel):
|
||||||
|
"""Composed data flow (artery). Pulse = Vein + Nest + Larder."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
vein: Optional[Vein] = None
|
||||||
|
nest: Optional[Nest] = None
|
||||||
|
larder: Optional[Larder] = None
|
||||||
|
system: Literal["artery"] = "artery"
|
||||||
|
|
||||||
|
|
||||||
|
class Book(BaseModel):
|
||||||
|
"""Composed documentation (album). Book = Template + Larder."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
template: Optional[Template] = None
|
||||||
|
larder: Optional[Larder] = None
|
||||||
|
output_larder: Optional[Larder] = None
|
||||||
|
system: Literal["album"] = "album"
|
||||||
|
|
||||||
|
|
||||||
|
class Table(BaseModel):
|
||||||
|
"""Composed execution bundle (ward). Table = Cabinet + Nest + Larders."""
|
||||||
|
name: str # Unique identifier
|
||||||
|
slug: str # URL-friendly identifier
|
||||||
|
title: str # Display title for UI
|
||||||
|
status: Optional[Status] = None
|
||||||
|
cabinet: Optional[Cabinet] = None
|
||||||
|
nest: Optional[Nest] = None
|
||||||
|
larders: List[Larder] = Field(default_factory=list)
|
||||||
|
system: Literal["ward"] = "ward"
|
||||||
|
|
||||||
|
|
||||||
|
# === Collection wrappers for JSON files ===
|
||||||
|
|
||||||
|
class NestCollection(BaseModel):
|
||||||
|
items: List[Nest] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class LarderCollection(BaseModel):
|
||||||
|
items: List[Larder] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class VeinCollection(BaseModel):
|
||||||
|
items: List[Vein] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateCollection(BaseModel):
|
||||||
|
items: List[Template] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCollection(BaseModel):
|
||||||
|
items: List[Tool] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class MonitorCollection(BaseModel):
|
||||||
|
items: List[Monitor] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class CabinetCollection(BaseModel):
|
||||||
|
items: List[Cabinet] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class PulseCollection(BaseModel):
|
||||||
|
items: List[Pulse] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class BookCollection(BaseModel):
|
||||||
|
items: List[Book] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class TableCollection(BaseModel):
|
||||||
|
items: List[Table] = Field(default_factory=list)
|
||||||
|
|
||||||
163
cfg/amar/models/schema.json
Normal file
163
cfg/amar/models/schema.json
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Pawprint Models",
|
||||||
|
"description": "Platform-agnostic model definitions. Portable to TypeScript, Pydantic, Django, Prisma.",
|
||||||
|
"definitions": {
|
||||||
|
"Status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["pending", "planned", "building", "dev", "live", "ready"]
|
||||||
|
},
|
||||||
|
"System": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["artery", "album", "ward"]
|
||||||
|
},
|
||||||
|
"Nest": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Runtime environment configuration. Shared across artery, ward.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"config_path": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Larder": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Data storage. When generated from Template = 'Book (written)'. Independent in ward/artery.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"source_template": { "type": "string", "description": "Template name if generated" },
|
||||||
|
"data_path": { "type": "string", "description": "Path to data files" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Vein": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Connector (artery). Single responsibility.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"system": { "const": "artery" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Template": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Documentation template (album). Gherkin, BDD patterns.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"template_path": { "type": "string", "description": "Path to template files" },
|
||||||
|
"system": { "const": "album" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"ToolType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["app", "cli"],
|
||||||
|
"description": "Type of tool: app (web UI) or cli (command line)"
|
||||||
|
},
|
||||||
|
"Tool": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Execution tool (ward). Test runners, seeders.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"system": { "const": "ward" },
|
||||||
|
"type": { "$ref": "#/definitions/ToolType" },
|
||||||
|
"description": { "type": "string", "description": "Human-readable description" },
|
||||||
|
"path": { "type": "string", "description": "Path to tool source" },
|
||||||
|
"url": { "type": "string", "description": "URL path for app tools" },
|
||||||
|
"cli": { "type": "string", "description": "CLI command for cli tools" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Monitor": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Service monitor (ward). Health checks, status watchers.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"system": { "const": "ward" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Cabinet": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Tool cabinet (ward). Contains 0+ tools.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"tools": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/Tool" }
|
||||||
|
},
|
||||||
|
"system": { "const": "ward" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Pulse": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Composed data flow (artery). Pulse = Vein + Nest + Larder.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"vein": { "$ref": "#/definitions/Vein" },
|
||||||
|
"nest": { "$ref": "#/definitions/Nest" },
|
||||||
|
"larder": { "$ref": "#/definitions/Larder" },
|
||||||
|
"system": { "const": "artery" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Book": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Composed documentation (album). Book = Template + Larder.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"template": { "$ref": "#/definitions/Template" },
|
||||||
|
"larder": { "$ref": "#/definitions/Larder" },
|
||||||
|
"output_larder": { "$ref": "#/definitions/Larder" },
|
||||||
|
"system": { "const": "album" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
},
|
||||||
|
"Table": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Composed execution bundle (ward). Table = Cabinet + Nest + Larders.",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "description": "Unique identifier" },
|
||||||
|
"slug": { "type": "string", "description": "URL-friendly identifier" },
|
||||||
|
"title": { "type": "string", "description": "Display title for UI" },
|
||||||
|
"status": { "$ref": "#/definitions/Status" },
|
||||||
|
"cabinet": { "$ref": "#/definitions/Cabinet" },
|
||||||
|
"nest": { "$ref": "#/definitions/Nest" },
|
||||||
|
"larders": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/Larder" }
|
||||||
|
},
|
||||||
|
"system": { "const": "ward" }
|
||||||
|
},
|
||||||
|
"required": ["name", "slug", "title"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
cfg/amar/monitors/turnos/__init__.py
Normal file
6
cfg/amar/monitors/turnos/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""
|
||||||
|
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).
|
||||||
|
"""
|
||||||
244
cfg/amar/monitors/turnos/index.html
Normal file
244
cfg/amar/monitors/turnos/index.html
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="refresh" content="30">
|
||||||
|
<title>Turnos · {{ nest_name }}</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #111827;
|
||||||
|
color: #f3f4f6;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0 1rem;
|
||||||
|
border-bottom: 1px solid #374151;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nest-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
background: #374151;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total small {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pipeline columns */
|
||||||
|
.pipeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
background: #1f2937;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-header {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-count {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-items {
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual request card */
|
||||||
|
.card {
|
||||||
|
background: #111827;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 0.6rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-left: 3px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-id {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-vet {
|
||||||
|
color: #60a5fa;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-petowner {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Age indicators */
|
||||||
|
.card.old {
|
||||||
|
background: #450a0a;
|
||||||
|
}
|
||||||
|
.card.warn {
|
||||||
|
background: #422006;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Indicators */
|
||||||
|
.indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
.indicator.paid { background: #22c55e; }
|
||||||
|
.indicator.scheduled { background: #3b82f6; }
|
||||||
|
|
||||||
|
/* Empty state */
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #6b7280;
|
||||||
|
padding: 2rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #374151;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6b7280;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-notice {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.5; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle a {
|
||||||
|
color: #6b7280;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.view-toggle a:hover { background: #374151; }
|
||||||
|
.view-toggle a.active { background: #374151; color: #f3f4f6; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
Turnos
|
||||||
|
<span class="nest-badge">{{ nest_name }}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="view-toggle">
|
||||||
|
<a href="?view=pipeline" class="active">Pipeline</a>
|
||||||
|
<a href="?view=list">List</a>
|
||||||
|
</div>
|
||||||
|
<div class="total">
|
||||||
|
{{ total }}
|
||||||
|
<small>activos</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% if total == 0 %}
|
||||||
|
<div class="empty">
|
||||||
|
No hay solicitudes activas
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="pipeline">
|
||||||
|
{% for state in active_states %}
|
||||||
|
{% set info = states.get(state, ('?', '#888', 99)) %}
|
||||||
|
{% set items = by_state.get(state, []) %}
|
||||||
|
<div class="column">
|
||||||
|
<div class="column-header" style="border-color: {{ info[1] }}; color: {{ info[1] }};">
|
||||||
|
<span>{{ info[0] }}</span>
|
||||||
|
<span class="column-count">{{ items|length }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="column-items">
|
||||||
|
{% for item in items %}
|
||||||
|
<div class="card {{ item.age_class }}" style="border-color: {{ info[1] }};">
|
||||||
|
<div class="card-id">#{{ item.id }}</div>
|
||||||
|
<div class="card-vet">{{ item.vet }}</div>
|
||||||
|
<div class="card-petowner">{{ item.petowner }}</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
{% if item.is_paid %}<span class="indicator paid" title="Pagado"></span>{% endif %}
|
||||||
|
{% if item.is_scheduled %}<span class="indicator scheduled" title="Coordinado"></span>{% endif %}
|
||||||
|
{{ item.age_hours }}h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<span class="refresh-notice">Auto-refresh 30s</span>
|
||||||
|
<span><a href="/health" style="color: #6b7280;">/health</a></span>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
188
cfg/amar/monitors/turnos/list.html
Normal file
188
cfg/amar/monitors/turnos/list.html
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="refresh" content="30">
|
||||||
|
<title>Turnos List · {{ nest_name }}</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #111827;
|
||||||
|
color: #f3f4f6;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0 1rem;
|
||||||
|
border-bottom: 1px solid #374151;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nest-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
background: #374151;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle a {
|
||||||
|
color: #6b7280;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.view-toggle a:hover { background: #374151; }
|
||||||
|
.view-toggle a.active { background: #374151; color: #f3f4f6; }
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-weight: 500;
|
||||||
|
border-bottom: 1px solid #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-bottom: 1px solid #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover td {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vet { color: #60a5fa; }
|
||||||
|
.petowner { color: #d1d5db; }
|
||||||
|
.id { color: #6b7280; font-family: monospace; }
|
||||||
|
.age { color: #6b7280; }
|
||||||
|
.age.warn { color: #fbbf24; }
|
||||||
|
.age.old { color: #f87171; }
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
.indicator.paid { background: #22c55e; }
|
||||||
|
.indicator.scheduled { background: #3b82f6; }
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #6b7280;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #374151;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6b7280;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
Turnos
|
||||||
|
<span class="nest-badge">{{ nest_name }}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="view-toggle">
|
||||||
|
<a href="?view=pipeline">Pipeline</a>
|
||||||
|
<a href="?view=list" class="active">List</a>
|
||||||
|
</div>
|
||||||
|
<div class="total">{{ total }}</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% if total == 0 %}
|
||||||
|
<div class="empty">No hay solicitudes activas</div>
|
||||||
|
{% else %}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Veterinario</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Flags</th>
|
||||||
|
<th>Edad</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in items %}
|
||||||
|
<tr>
|
||||||
|
<td class="id">{{ item.id }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="state-badge" style="background: {{ item.state_color }}20; color: {{ item.state_color }};">
|
||||||
|
{{ item.state_label }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="vet">{{ item.vet }}</td>
|
||||||
|
<td class="petowner">{{ item.petowner }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.is_paid %}<span class="indicator paid" title="Pagado"></span>{% endif %}
|
||||||
|
{% if item.is_scheduled %}<span class="indicator scheduled" title="Coordinado"></span>{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="age {{ item.age_class }}">{{ item.age_hours }}h</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<span>Auto-refresh 30s</span>
|
||||||
|
<span><a href="/health" style="color: #6b7280;">/health</a></span>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
270
cfg/amar/monitors/turnos/main.py
Normal file
270
cfg/amar/monitors/turnos/main.py
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
"""
|
||||||
|
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,
|
||||||
|
)
|
||||||
73
cfg/amar/tester/tests/README.md
Normal file
73
cfg/amar/tester/tests/README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 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/
|
||||||
|
```
|
||||||
2
cfg/amar/tester/tests/__init__.py
Normal file
2
cfg/amar/tester/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Contract tests - black-box HTTP tests that validate API contracts
|
||||||
|
# These tests are decoupled from Django and can run against any implementation
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user