Initial commit - estructura completa del proyecto

This commit is contained in:
2025-12-26 21:43:42 -06:00
commit 9bfb78c650
44 changed files with 721 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.env
*.log
__pycache__/
venv/
node_modules/
vendor/
.idea/
.vscode/

21
backend/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM python:3.11-slim
WORKDIR /app
# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Copiar requirements e instalar dependencias Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copiar el proyecto
COPY . .
EXPOSE 8000
CMD ["sh", "-c", "sleep 5 && python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class InventarioConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'inventario'

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

22
backend/manage.py Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pycore_db.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

16
backend/pycore/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for pycore project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pycore_db.settings')
application = get_asgi_application()

102
backend/pycore/settings.py Normal file
View File

@@ -0,0 +1,102 @@
import os
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY', 'clave-temporal-cambiar')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'backend', '0.0.0.0']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'inventario',
'ventas',
'sucursales',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'pycore.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'pycore.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'pycore_db',
'USER': 'Scorpion',
'PASSWORD': 'CyCoT256',
'HOST': 'db',
'PORT': '5432',
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'es-mx'
TIME_ZONE = 'America/Mexico_City'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
CORS_ALLOW_ALL_ORIGINS = True

22
backend/pycore/urls.py Normal file
View File

@@ -0,0 +1,22 @@
"""
URL configuration for pycore project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]

16
backend/pycore/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for pycore project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pycore.settings')
application = get_wsgi_application()

7
backend/requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
Django==4.2.7
djangorestframework==3.14.0
django-cors-headers==4.3.1
psycopg2-binary==2.9.7
python-decouple==3.8
Pillow==10.0.1
gunicorn==21.2.0

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SucursalesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'sucursales'

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

3
backend/ventas/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
backend/ventas/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class VentasConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ventas'

View File

3
backend/ventas/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
backend/ventas/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
backend/ventas/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

16
backend/wait-for-db.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
# wait-for-db.sh
set -e
host="$1"
shift
cmd="$@"
until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$host" -U "Scorpion" -d "pycore_db" -c '\q'; do
>&2 echo "PostgreSQL no está disponible - esperando..."
sleep 1
done
>&2 echo "PostgreSQL está listo - ejecutando comando"
exec $cmd

48
check-status.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
echo "🔍 Verificando estado de pycore ..."
echo "📊 Contenedores:"
docker-compose ps
echo ""
echo "🌐 Verificando servicios:"
# Esperar un poco para que todo esté listo
sleep 5
# Verificar backend
echo -n "Backend (8000): "
if curl -s http://localhost:8000 > /dev/null; then
echo "✅ OK"
else
echo "❌ Falló"
fi
# Verificar frontend
echo -n "Frontend (3000): "
if curl -s http://localhost:3000 > /dev/null; then
echo "✅ OK"
else
echo "❌ Falló"
fi
# Verificar pgadmin
echo -n "PgAdmin (5050): "
if curl -s http://localhost:5050 > /dev/null; then
echo "✅ OK"
else
echo "❌ Falló"
fi
# Verificar base de datos
echo -n "Base de datos: "
if docker-compose exec db pg_isready -U Scorpion -d pycore_db; then
echo "✅ OK"
else
echo "❌ Falló"
fi
echo ""
echo "📝 Logs recientes del backend:"
docker-compose logs backend --tail=5

5
clean.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
echo "🧹 Limpiando todo..."
docker-compose down -v
docker system prune -f
echo "✅ Limpieza completada"

View File

@@ -0,0 +1,12 @@
version: '3.8'
services:
backend:
command: >
sh -c "until pg_isready -h db -p 5432; do
echo 'Esperando a la base de datos...';
sleep 2;
done;
echo 'BD lista!';
python manage.py migrate;
python manage.py runserver 0.0.0.0:8000"

83
docker-compose.yml Normal file
View File

@@ -0,0 +1,83 @@
version: '3.8'
services:
# Base de datos PostgreSQL
db:
image: postgres:15
container_name: pycore_db
environment:
POSTGRES_DB: pycore_db
POSTGRES_USER: Scorpion
POSTGRES_PASSWORD: CyCoT256
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
networks:
- pycore_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U Scorpion -d pycore_db"]
interval: 5s
timeout: 5s
retries: 5
# PgAdmin
pgadmin:
image: dpage/pgadmin4
container_name: pycore_pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@pycore.com
PGADMIN_DEFAULT_PASSWORD: CyCoT256
ports:
- "5050:80"
depends_on:
- db
networks:
- pycore_network
# Backend Django
backend:
build: ./backend
container_name: pycore_backend
volumes:
- ./backend:/app
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://Scorpion:CyCoT256@db:5432/pycore_db
DEBUG: "True"
SECRET_KEY: "clave-secreta-temporal-2024"
networks:
- pycore_network
command: >
sh -c "echo 'Esperando a que la base de datos esté lista...' &&
sleep 10 &&
python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
# Frontend React
frontend:
build: ./frontend
container_name: pycore_frontend
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
depends_on:
- backend
environment:
VITE_API_URL: http://localhost:8000/api
networks:
- pycore_network
networks:
pycore_network:
driver: bridge
volumes:
postgres_data:

12
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:18-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

12
frontend/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>pycore</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

23
frontend/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "pycore-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.16.0",
"axios": "^1.5.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}

21
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,21 @@
import React from 'react';
const App: React.FC = () => {
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>🚀 pycore - ERP</h1>
<p>El sistema está funcionando correctamente.</p>
<div>
<h2>Servicios disponibles:</h2>
<ul>
<li>🌐 Frontend: http://localhost:3000</li>
<li>🔧 Backend API: http://localhost:8000</li>
<li>📊 PgAdmin: http://localhost:5050</li>
<li>🗄 PostgreSQL: localhost:5432</li>
</ul>
</div>
</div>
);
};
export default App;

9
frontend/src/main.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

10
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 3000
}
})

174
postgres/init/01-init.sql Normal file
View File

@@ -0,0 +1,174 @@
-- Script de inicialización para pycore
-- Verificar si la base de datos ya existe y solo crear tablas si no existen
-- Crear tipos ENUM personalizados si no existen
DO $$ BEGIN
CREATE TYPE tipo_producto_enum AS ENUM ('insumo', 'terminado');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- 1⃣ Sucursales
CREATE TABLE IF NOT EXISTS Sucursales (
id_sucursal SERIAL PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
direccion VARCHAR(255),
telefono VARCHAR(20),
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2⃣ Inventario
CREATE TABLE IF NOT EXISTS Inventario (
id_producto SERIAL PRIMARY KEY,
nombre_producto VARCHAR(100) NOT NULL,
tipo_producto tipo_producto_enum NOT NULL,
precio_unitario DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
id_sucursal INT,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (id_sucursal) REFERENCES Sucursales(id_sucursal)
);
-- 3⃣ Compras
CREATE TABLE IF NOT EXISTS Compras (
id_compra SERIAL PRIMARY KEY,
id_sucursal INT NOT NULL,
fecha_compra DATE NOT NULL,
total DECIMAL(10,2) NOT NULL,
FOREIGN KEY (id_sucursal) REFERENCES Sucursales(id_sucursal)
);
-- 4⃣ Detalle_compras
CREATE TABLE IF NOT EXISTS Detalle_compras (
id_detalle SERIAL PRIMARY KEY,
id_compra INT NOT NULL,
id_producto INT NOT NULL,
cantidad INT NOT NULL,
precio_unitario DECIMAL(10,2) NOT NULL,
FOREIGN KEY (id_compra) REFERENCES Compras(id_compra),
FOREIGN KEY (id_producto) REFERENCES Inventario(id_producto)
);
-- 5⃣ Promociones
CREATE TABLE IF NOT EXISTS Promociones (
id_promocion SERIAL PRIMARY KEY,
nombre_promocion VARCHAR(100) NOT NULL,
descripcion TEXT,
descuento DECIMAL(5,2), -- Porcentaje
fecha_inicio DATE,
fecha_fin DATE
);
-- 6⃣ Colaboradores
CREATE TABLE IF NOT EXISTS Colaboradores (
id_colaborador SERIAL PRIMARY KEY,
id_sucursal INT NOT NULL,
nombre VARCHAR(100) NOT NULL,
puesto VARCHAR(50),
salario DECIMAL(10,2),
fecha_ingreso DATE,
FOREIGN KEY (id_sucursal) REFERENCES Sucursales(id_sucursal)
);
-- 7⃣ Gastos_sucursal
CREATE TABLE IF NOT EXISTS Gastos_sucursal (
id_gasto SERIAL PRIMARY KEY,
id_sucursal INT NOT NULL,
concepto VARCHAR(255) NOT NULL,
monto DECIMAL(10,2) NOT NULL,
fecha_gasto DATE NOT NULL,
FOREIGN KEY (id_sucursal) REFERENCES Sucursales(id_sucursal)
);
-- 8⃣ Ventas
CREATE TABLE IF NOT EXISTS Ventas (
id_venta SERIAL PRIMARY KEY,
id_sucursal INT NOT NULL,
fecha_venta TIMESTAMP NOT NULL,
total DECIMAL(10,2) NOT NULL,
id_promocion INT,
FOREIGN KEY (id_sucursal) REFERENCES Sucursales(id_sucursal),
FOREIGN KEY (id_promocion) REFERENCES Promociones(id_promocion)
);
-- 9⃣ Detalle_ventas
CREATE TABLE IF NOT EXISTS Detalle_ventas (
id_detalle SERIAL PRIMARY KEY,
id_venta INT NOT NULL,
id_producto INT NOT NULL,
cantidad INT NOT NULL,
precio_unitario DECIMAL(10,2) NOT NULL,
FOREIGN KEY (id_venta) REFERENCES Ventas(id_venta),
FOREIGN KEY (id_producto) REFERENCES Inventario(id_producto)
);
-- ==========================
-- ÍNDICES PARA MEJOR PERFORMANCE
-- ==========================
DO $$ BEGIN
CREATE INDEX IF NOT EXISTS idx_inventario_sucursal ON Inventario(id_sucursal);
CREATE INDEX IF NOT EXISTS idx_ventas_fecha ON Ventas(fecha_venta);
CREATE INDEX IF NOT EXISTS idx_ventas_sucursal ON Ventas(id_sucursal);
CREATE INDEX IF NOT EXISTS idx_compras_fecha ON Compras(fecha_compra);
CREATE INDEX IF NOT EXISTS idx_detalle_ventas_producto ON Detalle_ventas(id_producto);
CREATE INDEX IF NOT EXISTS idx_detalle_compras_producto ON Detalle_compras(id_producto);
CREATE INDEX IF NOT EXISTS idx_colaboradores_sucursal ON Colaboradores(id_sucursal);
EXCEPTION
WHEN duplicate_table THEN null;
END $$;
-- ==========================
-- DATOS DE EJEMPLO
-- ==========================
-- Insertar sucursal principal si no existen
INSERT INTO Sucursales (id_sucursal, nombre, direccion, telefono) VALUES
(1, 'Alitas Downtown', 'Av. Principal 123, Ciudad', '+52-55-1234-5678'),
(2, 'Alitas Norte', 'Plaza Norte 456, Zona Norte', '+52-55-8765-4321')
ON CONFLICT (id_sucursal) DO UPDATE SET
nombre = EXCLUDED.nombre,
direccion = EXCLUDED.direccion,
telefono = EXCLUDED.telefono;
-- Insertar productos típicos de alitas
INSERT INTO Inventario (id_producto, nombre_producto, tipo_producto, precio_unitario, stock, id_sucursal) VALUES
(1, 'Alitas de pollo', 'insumo', 45.50, 200, 1),
(2, 'Salsa BBQ', 'insumo', 25.00, 50, 1),
(3, 'Salsa Buffalo', 'insumo', 28.00, 40, 1),
(4, 'Papas para freír', 'insumo', 18.00, 100, 1),
(5, 'Harina para empanizar', 'insumo', 12.50, 30, 1),
(6, 'Orden de alitas (6 pzas)', 'terminado', 120.00, 0, 1),
(7, 'Orden de alitas (12 pzas)', 'terminado', 220.00, 0, 1),
(8, 'Papas a la francesa', 'terminado', 45.00, 0, 1),
(9, 'Aros de cebolla', 'terminado', 55.00, 0, 1)
ON CONFLICT (id_producto) DO UPDATE SET
nombre_producto = EXCLUDED.nombre_producto,
tipo_producto = EXCLUDED.tipo_producto,
precio_unitario = EXCLUDED.precio_unitario,
stock = EXCLUDED.stock,
id_sucursal = EXCLUDED.id_sucursal;
-- Insertar promociones
INSERT INTO Promociones (id_promocion, nombre_promocion, descripcion, descuento, fecha_inicio, fecha_fin) VALUES
(1, 'Martes de Alitas', '2x1 en órdenes de alitas todos los martes', 50.00, '2024-01-01', '2024-12-31'),
(2, 'Happy Hour', '20% de descuento en bebidas de 4pm a 7pm', 20.00, '2024-01-01', '2024-12-31'),
(3, 'Combo Familiar', 'Descuento especial en combos familiares', 15.00, '2024-03-01', '2024-03-31')
ON CONFLICT (id_promocion) DO UPDATE SET
nombre_promocion = EXCLUDED.nombre_promocion,
descripcion = EXCLUDED.descripcion,
descuento = EXCLUDED.descuento,
fecha_inicio = EXCLUDED.fecha_inicio,
fecha_fin = EXCLUDED.fecha_fin;
-- Insertar colaboradores
INSERT INTO Colaboradores (id_colaborador, id_sucursal, nombre, puesto, salario, fecha_ingreso) VALUES
(1, 1, 'Juan Pérez García', 'Gerente', 15000.00, '2023-01-15'),
(2, 1, 'María Hernández López', 'Cocinero', 8000.00, '2023-03-20'),
(3, 1, 'Carlos Rodríguez Silva', 'Mesero', 6000.00, '2023-05-10')
ON CONFLICT (id_colaborador) DO UPDATE SET
id_sucursal = EXCLUDED.id_sucursal,
nombre = EXCLUDED.nombre,
puesto = EXCLUDED.puesto,
salario = EXCLUDED.salario,
fecha_ingreso = EXCLUDED.fecha_ingreso;

24
start.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
echo "🚀 Iniciando pycore ..."
# Verificar Docker
if ! command -v docker &> /dev/null; then
echo "❌ Docker no está instalado"
exit 1
fi
# Construir y levantar
echo "📦 Construyendo contenedores..."
docker-compose up --build -d
echo "⏳ Esperando inicialización..."
sleep 15
echo "✅ Servicios iniciados:"
echo " 🌐 Frontend: http://localhost:3000"
echo " 🔧 Backend: http://localhost:8000"
echo " 📊 PgAdmin: http://localhost:5050"
echo " 🗄️ PostgreSQL: localhost:5432"
echo ""
echo "🔑 PgAdmin: admin@pycore.com / CyCoT256"

4
stop.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
echo "🛑 Deteniendo pycore ..."
docker-compose down
echo "✅ Servicios detenidos"