feat: Implement main application shell with navigation and color scheme toggle

- Added Appshell component with responsive navbar and main content area
- Integrated ColorSchemeToggle for light/dark mode switching
- Created Welcome component with styled title and introductory text
- Developed ChatPage for LLM interaction with WebSocket support
- Implemented Biblioteca for managing notes with rich text editor
- Added LoginPage for user authentication with error handling
- Introduced MessageList and MessageBubble components for chat messages
- Styled components with CSS modules for consistent design
This commit is contained in:
2025-06-21 02:01:21 +02:00
parent 3d5deef0fb
commit aef8791151
101 changed files with 169 additions and 166 deletions
+75
View File
@@ -0,0 +1,75 @@
# src\ArquitectureLayer\Mapper.py
from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Type
import json
TDominio = TypeVar("TDominio")
TModelo = TypeVar("TModelo")
class Mapper_base(ABC, Generic[TDominio, TModelo]):
# ----------------------------
# Conversiones individuales
# ----------------------------
@staticmethod
@abstractmethod
def to_model(obj: TDominio) -> TModelo:
"""Convierte objeto de dominio a modelo ORM"""
pass
@staticmethod
@abstractmethod
def from_model(model: TModelo) -> TDominio:
"""Convierte modelo ORM a objeto de dominio"""
pass
@staticmethod
@abstractmethod
def to_dict(obj: TDominio) -> dict:
"""Convierte objeto de dominio a diccionario plano"""
pass
@staticmethod
@abstractmethod
def from_dict(d: dict) -> TDominio:
"""Convierte diccionario plano a objeto de dominio"""
pass
@classmethod
def to_json(cls, obj: TDominio) -> str:
"""Convierte objeto de dominio a JSON string"""
return json.dumps(cls.to_dict(obj), default=str)
@classmethod
def from_json(cls, json_str: str) -> TDominio:
"""Convierte JSON string a objeto de dominio"""
return cls.from_dict(json.loads(json_str))
# ----------------------------
# Conversiones en lote (bulk)
# ----------------------------
@classmethod
def to_model_list(cls, objs: list[TDominio]) -> list[TModelo]:
return [cls.to_model(o) for o in objs]
@classmethod
def from_model_list(cls, models: list[TModelo]) -> list[TDominio]:
return [cls.from_model(m) for m in models]
@classmethod
def to_dict_list(cls, objs: list[TDominio]) -> list[dict]:
return [cls.to_dict(o) for o in objs]
@classmethod
def from_dict_list(cls, dicts: list[dict]) -> list[TDominio]:
return [cls.from_dict(d) for d in dicts]
@classmethod
def to_json_list(cls, objs: list[TDominio]) -> str:
return json.dumps(cls.to_dict_list(objs), default=str)
@classmethod
def from_json_list(cls, json_str: str) -> list[TDominio]:
return cls.from_dict_list(json.loads(json_str))
+63
View File
@@ -0,0 +1,63 @@
# src\ArquitectureLayer\Model.py
from sqlalchemy import Column, DateTime, String, Integer, Text, func
from sqlalchemy.ext.declarative import declared_attr, as_declarative
from datetime import datetime
@as_declarative()
class Model_base:
__abstract__ = True
@declared_attr
def sys_created_at(cls):
return Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
@declared_attr
def sys_created_by(cls):
return Column(String, nullable=True)
@declared_attr
def sys_updated_at(cls):
return Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
@declared_attr
def sys_updated_by(cls):
return Column(String, nullable=True)
@declared_attr
def sys_version(cls):
return Column(Integer, default=1, nullable=False)
@declared_attr
def sys_notes(cls):
return Column(Text, nullable=True)
@declared_attr
def sys_deleted_at(cls):
return Column(DateTime(timezone=True), nullable=True)
def __repr__(self):
id_val = getattr(self, "id", None)
return f"<{self.__class__.__name__} id={id_val}>"
def __str__(self):
cls = self.__class__.__name__
id_val = getattr(self, "id", None)
return f"{cls}(id={id_val})"
def __json__(self) -> dict:
"""Devuelve una representación JSON serializable (dict plano)."""
out = {}
# Prevención de error: solo ejecuta si __table__ existe
if not hasattr(self, "__table__"):
return out
for attr in self.__table__.columns:
val = getattr(self, attr.name, None)
if isinstance(val, datetime):
out[attr.name] = val.isoformat()
else:
out[attr.name] = val
return out
+148
View File
@@ -0,0 +1,148 @@
# src\ArquitectureLayer\Repo.py
from abc import ABC
from typing import Type, TypeVar, Generic, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime
from domains.ArquitectureLayer.Mapper import Mapper_base # Asegúrate de importar tu ABC base
TModelo = TypeVar("TModelo")
TDominio = TypeVar("TDominio")
class Repo_base(ABC, Generic[TModelo, TDominio]):
def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):
self.session = session
self.Modelo = modelo
self.Mapper = mapper
# ----------------------
# ADD
# ----------------------
def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:
data = self.Mapper.to_dict(dominio)
data.update({
"sys_created_by": created_by,
"sys_notes": notes,
"sys_version": 1
})
model = self.Modelo(**data)
self.session.add(model)
self.session.commit()
return model.id
def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:
ids = []
for dominio in dominios:
data = self.Mapper.to_dict(dominio)
data.update({
"sys_created_by": created_by,
"sys_notes": notes,
"sys_version": 1
})
model = self.Modelo(**data)
self.session.add(model)
ids.append(model.id)
self.session.commit()
return ids
# ----------------------
# GET
# ----------------------
def get_by_id(self, id_: str) -> Optional[TDominio]:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
return self.Mapper.from_model(model) if model else None
def get_all(self) -> list[TDominio]:
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()
return self.Mapper.from_model_list(models)
def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()
return self.Mapper.from_model_list(models)
def get_deleted(self) -> list[TDominio]:
models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()
return self.Mapper.from_model_list(models)
# ----------------------
# UPDATE
# ----------------------
def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
if not model:
return False
for key, value in new_data.items():
if hasattr(model, key):
setattr(model, key, value)
model.sys_updated_by = updated_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:
count = 0
for id_, data in updates:
if self.update(id_, data, updated_by=updated_by, notes=notes):
count += 1
return count
# ----------------------
# DELETE
# ----------------------
def delete_by_id(self, id_: str) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_).first()
if model:
self.session.delete(model)
self.session.commit()
return True
return False
def delete_all(self) -> int:
deleted = self.session.query(self.Modelo).delete()
self.session.commit()
return deleted
# ----------------------
# SOFT DELETE
# ----------------------
def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
if model:
model.sys_deleted_at = datetime.now()
model.sys_updated_by = deleted_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
return False
def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_).first()
if model and model.sys_deleted_at is not None:
model.sys_deleted_at = None
model.sys_updated_by = restored_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
return False
# ----------------------
# OTROS
# ----------------------
def exists(self, id_: str) -> bool:
return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None
def count(self) -> int:
return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()