diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3a7d07 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Cloud Conversion + +Cloud Conversion es un proyecto Python para apoyar evaluaciones de migracion cloud. Descarga y transforma catalogos de precios de AWS y OCI, compara inventarios de servidores contra shapes cloud, genera archivos `MATCH-*` con recomendaciones y costos, y ofrece una interfaz grafica local en Streamlit para operar el pipeline sin depender de comandos manuales. + +La ruta recomendada para arquitectos es la interfaz grafica. Los scripts CLI siguen disponibles para automatizacion, troubleshooting o ejecuciones controladas. + +## Quick Start + +1. Crear y activar ambiente virtual: + +```bash +python3.12 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +2. Configurar variables opcionales: + +```bash +cp .env.example .env +``` + +3. Abrir la interfaz grafica: + +```bash +streamlit run cloud_conversion_ui/Home.py +``` + +4. En la GUI: + +- Selecciona o sube un inventario en `Inventario`. +- Actualiza catalogos en `Catalogos` si necesitas precios recientes. +- Ejecuta `AWS Match` y `OCI Match` en `Ejecucion`. +- Revisa costos y cobertura en `Dashboard`. +- Descarga CSVs, EDA y logs en `Logs y Artefactos`. + +## Flujo Operativo + +### Interfaz grafica + +La aplicacion Streamlit vive en `cloud_conversion_ui/` y usa `cloud_conversion_ui/Home.py` como entrada. Es el camino recomendado para uso diario porque agrupa inventarios, catalogos, ejecucion, resultados y descargas en una sola consola local. + +Al ejecutar matching desde la GUI, los inventarios no se archivan automaticamente. Internamente la GUI llama los pipelines con `move_to_done=False`; si quieres archivar un inventario, hazlo desde la pagina `Inventario`. + +### Scripts CLI + +Los scripts estan en `scripts/`. El pipeline AWS tipico es: + +```bash +python scripts/aws_fetch_offer.py +python scripts/aws_eda.py +python scripts/aws_match.py --inventory input/inventario.xlsx --skip-llm +``` + +El pipeline OCI tipico es: + +```bash +python scripts/oci_fetch_offer.py +python scripts/oci_match.py --inventory input/inventario.xlsx --skip-llm +``` + +## Documentacion + +- [Despliegue Local/VM](docs/DEPLOYMENT.md) +- [Guia de la Interfaz Grafica](docs/UI_GUIDE.md) +- [Scripts y Comandos](docs/SCRIPTS.md) +- [Diccionario de Datos](docs/DATA_DICTIONARY.md) +- [Estructura de Directorios](docs/DIRECTORY_STRUCTURE.md) + +## Comandos Utiles + +| Comando | Uso | +| --- | --- | +| `streamlit run cloud_conversion_ui/Home.py` | Inicia la GUI local. | +| `python scripts/aws_match.py --help` | Muestra parametros de AWS Match. | +| `python scripts/oci_match.py --help` | Muestra parametros de OCI Match. | +| `python scripts/cloud-chat.py --ask "Cuanto cuesta m7a.xlarge en virginia?"` | Consulta el catalogo AWS desde CLI. | +| `python scripts/whatsapp_chatbot.py` | Inicia webhook Flask para WhatsApp Cloud Chat. | +| `python -m pytest -q` | Ejecuta la suite de pruebas. | + +## Datos y Seguridad + +- `.env` contiene secretos locales y no debe subirse. +- `.env.example` es la plantilla segura para compartir. +- Los datos actuales se conservan para uso interno, pero antes de publicar el repo se debe revisar sensibilidad de inventarios, clientes, precios y logs. +- `data/` puede ser grande; en este workspace pesa aproximadamente 8 GB. +- `logs/`, `.venv/`, caches y archivos temporales estan ignorados por Git. + +## Validacion antes de Compartir + +Ejecuta: + +```bash +python -m pytest -q +python scripts/aws_match.py --help +python scripts/oci_match.py --help +python scripts/aws_eda.py --help +python scripts/cloud-chat.py --help +python scripts/whatsapp_send.py --help +streamlit run cloud_conversion_ui/Home.py +``` + +Para validar Streamlit en una VM, confirma que el navegador abre la pagina inicial y que el sidebar muestra `Inventario`, `Catalogos`, `Ejecucion`, `Dashboard` y `Logs y Artefactos`. + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ddd4718 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +pandas==2.2.3 +openpyxl==3.1.5 +pydantic==2.10.6 +requests==2.32.3 +langchain-ollama==1.1.0 +flask==3.1.3 +twilio==9.10.9 +python-dotenv==1.2.2 +streamlit==1.57.0 diff --git a/send.py b/send.py new file mode 100644 index 0000000..add34b6 --- /dev/null +++ b/send.py @@ -0,0 +1,20 @@ +import os +from dotenv import load_dotenv +from twilio.rest import Client + +load_dotenv() +account_sid = os.environ["TWILIO_ACCOUNT_SID"] +auth_token = os.environ["TWILIO_AUTH_TOKEN"] +from_whatsapp = os.environ["TWILIO_WHATSAPP_FROM"] +to_whatsapp = os.environ["MY_WHATSAPP_TO"] + +client = Client(account_sid, auth_token) +message = client.messages.create( + from_=from_whatsapp, + to=to_whatsapp, + body="Hola mundo desde Python usando Twilio WhatsApp." +) + +print("Mensaje enviado correctamente.") +print("SID:", message.sid) +print("Estado:", message.status) diff --git a/server.py b/server.py new file mode 100644 index 0000000..893a50c --- /dev/null +++ b/server.py @@ -0,0 +1,45 @@ +import os +from flask import Flask, request, abort, Response +from dotenv import load_dotenv +from twilio.request_validator import RequestValidator +from twilio.twiml.messaging_response import MessagingResponse + +load_dotenv() + +app = Flask(__name__) + +twilio_auth_token = os.environ["TWILIO_AUTH_TOKEN"] +public_webhook_url = os.environ["PUBLIC_WEBHOOK_URL"] + +validator = RequestValidator(twilio_auth_token) + + +@app.post("/whatsapp") +def whatsapp_webhook(): + signature = request.headers.get("X-Twilio-Signature", "") + + is_valid = validator.validate( + public_webhook_url, + request.form.to_dict(), + signature + ) + + if not is_valid: + print("Firma inválida. Request rechazado.") + abort(403) + + incoming_message = request.form.get("Body", "") + sender = request.form.get("From", "") + + print("Mensaje recibido desde WhatsApp") + print("De:", sender) + print("Texto:", incoming_message) + + response = MessagingResponse() + response.message(f"Hola. Recibí tu mensaje: {incoming_message}") + + return Response(str(response), mimetype="text/xml") + + +if __name__ == "__main__": + app.run(port=3000, debug=True)