1
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CategoriaBase(BaseModel):
|
||||
nombre: str = Field(..., min_length=1, description="Nombre de la categoría")
|
||||
descripcion: str | None = None
|
||||
color: str = Field("#6b7280", description="Color hex para UI")
|
||||
orden: int = Field(0, ge=0)
|
||||
|
||||
|
||||
class CategoriaCreate(CategoriaBase):
|
||||
pass
|
||||
|
||||
|
||||
class CategoriaUpdate(BaseModel):
|
||||
nombre: str | None = Field(None, min_length=1)
|
||||
descripcion: str | None = None
|
||||
color: str | None = None
|
||||
orden: int | None = Field(None, ge=0)
|
||||
activo: bool | None = None
|
||||
|
||||
|
||||
class CategoriaResponse(CategoriaBase):
|
||||
id: str
|
||||
activo: bool
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ServicioCatalogoBase(BaseModel):
|
||||
nombre: str = Field(..., min_length=1, description="Nombre del servicio")
|
||||
descripcion: str | None = None
|
||||
fase: int = Field(..., ge=0, le=3, description="Fase: 0=Auditoría, 1=Setup, 2=Publicidad, 3=Contenido/SEO")
|
||||
tipoPago: str = Field(..., pattern="^(unico|mensual)$", description="Tipo de pago")
|
||||
precioBase: float = Field(..., ge=0, description="Precio base en la moneda local")
|
||||
tiempoEntrega: str = Field("7 - 14 dias", description="Tiempo de entrega estimado")
|
||||
entregablesDefault: list[str] = Field(default_factory=list, description="Lista de entregables")
|
||||
categoriaId: str = Field(..., min_length=1, description="ID de la categoría")
|
||||
variante: str | None = None
|
||||
orden: int = Field(0, ge=0)
|
||||
|
||||
|
||||
class ServicioCatalogoCreate(ServicioCatalogoBase):
|
||||
pass
|
||||
|
||||
|
||||
class ServicioCatalogoUpdate(BaseModel):
|
||||
nombre: str | None = Field(None, min_length=1)
|
||||
descripcion: str | None = None
|
||||
fase: int | None = Field(None, ge=0, le=3)
|
||||
tipoPago: str | None = Field(None, pattern="^(unico|mensual)$")
|
||||
precioBase: float | None = Field(None, ge=0)
|
||||
tiempoEntrega: str | None = None
|
||||
entregablesDefault: list[str] | None = None
|
||||
categoriaId: str | None = None
|
||||
variante: str | None = None
|
||||
activo: bool | None = None
|
||||
orden: int | None = Field(None, ge=0)
|
||||
|
||||
|
||||
class ServicioCatalogoResponse(ServicioCatalogoBase):
|
||||
id: str
|
||||
activo: bool
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
categoriaRel: CategoriaResponse | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
from app.models.cliente import ClienteResponse
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ClienteBase(BaseModel):
|
||||
nombre: str = Field(..., min_length=1, description="Nombre completo del contacto")
|
||||
empresa: str | None = Field(None, description="Nombre de la empresa")
|
||||
email: str | None = Field(None, description="Email de contacto")
|
||||
telefono: str | None = Field(None, description="Teléfono de contacto")
|
||||
|
||||
|
||||
class ClienteCreate(ClienteBase):
|
||||
pass
|
||||
|
||||
|
||||
class ClienteUpdate(BaseModel):
|
||||
nombre: str | None = Field(None, min_length=1)
|
||||
empresa: str | None = None
|
||||
email: str | None = None
|
||||
telefono: str | None = None
|
||||
|
||||
|
||||
class ClienteResponse(ClienteBase):
|
||||
id: str
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ClienteWithCotizaciones(ClienteResponse):
|
||||
cotizaciones: list[dict] | None = None
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Meta(BaseModel):
|
||||
total: int = 0
|
||||
page: int = 1
|
||||
limit: int = 50
|
||||
pages: int = 0
|
||||
|
||||
|
||||
class PaginatedResponse(BaseModel, Generic[T]):
|
||||
data: list[T]
|
||||
meta: Meta
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
error: str
|
||||
detail: str | None = None
|
||||
code: str | None = None
|
||||
|
||||
|
||||
class OkResponse(BaseModel):
|
||||
ok: bool = True
|
||||
|
||||
|
||||
class ArchivedResponse(BaseModel):
|
||||
ok: bool = True
|
||||
archived: bool
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
status: str = "ok"
|
||||
timestamp: str
|
||||
database: str = "connected"
|
||||
|
||||
|
||||
class APIInfoResponse(BaseModel):
|
||||
name: str = "Cotizador E3 API"
|
||||
version: str = "1.0.0"
|
||||
description: str = "API para sistema de cotizaciones de marketing digital"
|
||||
capabilities: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
user: dict[str, Any]
|
||||
token: str | None = None
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
ALLOWED_CONFIG_KEYS: set[str] = {
|
||||
"color_primario",
|
||||
"color_secundario",
|
||||
"logo_base64",
|
||||
"razon_social",
|
||||
"rfc",
|
||||
"domicilio_fiscal",
|
||||
"cuenta_nacional",
|
||||
"clabe_interbancaria",
|
||||
"cuenta_internacional",
|
||||
"cuenta_internacional_swift",
|
||||
"hora_centinela",
|
||||
"anualidad_hosting",
|
||||
"iva",
|
||||
"terminos_condiciones",
|
||||
"no_incluye",
|
||||
"notas_adicionales",
|
||||
}
|
||||
|
||||
|
||||
class ConfigUpdate(BaseModel):
|
||||
config: dict[str, str] = Field(
|
||||
...,
|
||||
description="Key-value config pairs. Only whitelisted keys are processed.",
|
||||
json_schema_extra={
|
||||
"examples": [
|
||||
{"color_primario": "#ff0000", "razon_social": "Nueva Empresa SA de CV"}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
def get_allowed(self) -> dict[str, str]:
|
||||
return {k: v for k, v in self.config.items() if k in ALLOWED_CONFIG_KEYS}
|
||||
|
||||
|
||||
class ConfigResponse(BaseModel):
|
||||
config: dict[str, str]
|
||||
@@ -0,0 +1,133 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServicioCotizadoInput(BaseModel):
|
||||
catalogoId: str = Field(..., min_length=1, description="ID del servicio del catálogo")
|
||||
nombre: str = Field(..., min_length=1, description="Nombre del servicio")
|
||||
fase: int = Field(..., ge=0, le=3)
|
||||
tipoPago: str = Field(..., pattern="^(unico|mensual)$")
|
||||
precio: float = Field(..., ge=0, description="Precio (puede diferir del precioBase)")
|
||||
tiempoEntrega: str
|
||||
entregables: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PlanBucefaloInput(BaseModel):
|
||||
nivel: str = Field(..., description="Nivel: basico, estandar, premium, empresarial")
|
||||
precio: float = Field(..., ge=0)
|
||||
|
||||
|
||||
class ClienteInput(BaseModel):
|
||||
nombre: str = Field(..., min_length=1)
|
||||
empresa: str = ""
|
||||
email: str = ""
|
||||
telefono: str = ""
|
||||
|
||||
|
||||
class CotizacionCreate(BaseModel):
|
||||
numero: str = Field(..., min_length=1, description="Número de cotización (formato UJ{YY}{MM}{init}{seq})")
|
||||
fecha: datetime
|
||||
vigencia: datetime
|
||||
moneda: str = Field("MXN", pattern="^(MXN|USD)$")
|
||||
tipoCambio: str = "NA"
|
||||
proyecto: str = "MKT Digital"
|
||||
esquemaPago: str = Field("Pago Unico/Mensual", pattern="^(Pago Unico|Mensual|Pago Unico/Mensual)$")
|
||||
incluirBonos: bool = False
|
||||
incluirFinanciamiento: bool = False
|
||||
observaciones: str = ""
|
||||
asesorId: str = Field(..., min_length=1)
|
||||
cliente: ClienteInput
|
||||
servicios: list[ServicioCotizadoInput] = Field(..., min_length=1)
|
||||
planBucefalo: PlanBucefaloInput | None = None
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"numero": "UJ2605AG001",
|
||||
"fecha": "2026-05-03",
|
||||
"vigencia": "2026-05-24",
|
||||
"moneda": "MXN",
|
||||
"tipoCambio": "NA",
|
||||
"proyecto": "MKT Digital",
|
||||
"esquemaPago": "Pago Unico/Mensual",
|
||||
"incluirBonos": False,
|
||||
"incluirFinanciamiento": False,
|
||||
"observaciones": "",
|
||||
"asesorId": "cuid-asesor",
|
||||
"cliente": {
|
||||
"nombre": "Juan Pérez",
|
||||
"empresa": "ACME Corp",
|
||||
"email": "[email protected]",
|
||||
"telefono": "4421234567",
|
||||
},
|
||||
"servicios": [
|
||||
{
|
||||
"catalogoId": "cuid-servicio",
|
||||
"nombre": "SEO On-Page",
|
||||
"fase": 3,
|
||||
"tipoPago": "mensual",
|
||||
"precio": 2900,
|
||||
"tiempoEntrega": "7 - 14 dias",
|
||||
"entregables": ["Keyword research"],
|
||||
}
|
||||
],
|
||||
"planBucefalo": {"nivel": "basico", "precio": 1000},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CotizacionUpdate(BaseModel):
|
||||
fecha: datetime | None = None
|
||||
vigencia: datetime | None = None
|
||||
moneda: str | None = Field(None, pattern="^(MXN|USD)$")
|
||||
tipoCambio: str | None = None
|
||||
proyecto: str | None = None
|
||||
esquemaPago: str | None = Field(None, pattern="^(Pago Unico|Mensual|Pago Unico/Mensual)$")
|
||||
incluirBonos: bool | None = None
|
||||
incluirFinanciamiento: bool | None = None
|
||||
observaciones: str | None = None
|
||||
estado: str | None = Field(None, pattern="^(borrador|enviada|aprobada|rechazada)$")
|
||||
cliente: ClienteInput | None = None
|
||||
servicios: list[ServicioCotizadoInput] | None = None
|
||||
planBucefalo: PlanBucefaloInput | None = None
|
||||
|
||||
|
||||
class CambiarEstadoRequest(BaseModel):
|
||||
estado: str = Field(..., pattern="^(borrador|enviada|aprobada|rechazada)$")
|
||||
|
||||
|
||||
class ActualizarPrecioRequest(BaseModel):
|
||||
servicioId: str = Field(..., min_length=1)
|
||||
precio: float = Field(..., ge=0)
|
||||
|
||||
|
||||
class CotizacionResponse(BaseModel):
|
||||
id: str
|
||||
numero: str
|
||||
fecha: datetime
|
||||
vigencia: datetime
|
||||
moneda: str
|
||||
tipoCambio: str
|
||||
proyecto: str
|
||||
esquemaPago: str
|
||||
estado: str
|
||||
incluirBonos: bool
|
||||
incluirFinanciamiento: bool
|
||||
observaciones: str | None
|
||||
clienteId: str
|
||||
asesorId: str
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
cliente: dict[str, Any] | None = None
|
||||
asesor: dict[str, Any] | None = None
|
||||
servicios: list[dict[str, Any]] | None = None
|
||||
planBucefalo: dict[str, Any] | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServicioDraft(BaseModel):
|
||||
nombre: str
|
||||
fase: int
|
||||
tipoPago: str
|
||||
precio: float
|
||||
tiempoEntrega: str
|
||||
entregables: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ExportDraft(BaseModel):
|
||||
clienteNombre: str = ""
|
||||
clienteEmpresa: str = ""
|
||||
asesorNombre: str = ""
|
||||
fecha: str = ""
|
||||
moneda: str = "MXN"
|
||||
tipoCambio: str = "NA"
|
||||
proyecto: str = "MKT Digital"
|
||||
esquemaPago: str = "Pago Unico/Mensual"
|
||||
incluirBonos: bool = False
|
||||
servicios: list[ServicioDraft] = Field(default_factory=list)
|
||||
planBucefaloNivel: str | None = None
|
||||
observaciones: str = ""
|
||||
|
||||
|
||||
class FinanciamientoRequest(BaseModel):
|
||||
monto: float = Field(..., ge=0, description="Monto total a financiar en MXN")
|
||||
meses: int = Field(..., ge=3, le=12, description="Plazo en meses: 3, 6, 9, o 12")
|
||||
|
||||
|
||||
class FinanciamientoResponse(BaseModel):
|
||||
pagoMensual: float
|
||||
ivaMensual: float
|
||||
totalMensual: float
|
||||
comisionTotal: float
|
||||
granTotal: float
|
||||
meses: int
|
||||
tasa: float
|
||||
comision: float
|
||||
|
||||
|
||||
class BonoResponse(BaseModel):
|
||||
id: str
|
||||
numero: int
|
||||
titulo: str
|
||||
descripcion: str
|
||||
activo: bool
|
||||
|
||||
|
||||
class PlanBucefaloResponse(BaseModel):
|
||||
nivel: str
|
||||
label: str
|
||||
precio: float
|
||||
@@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class FasePaqueteInput(BaseModel):
|
||||
nombre: str = Field(..., min_length=1)
|
||||
orden: int = Field(0, ge=0)
|
||||
|
||||
|
||||
class PaqueteCreate(BaseModel):
|
||||
nombre: str = Field(..., min_length=1, description="Nombre del paquete")
|
||||
descripcion: str | None = None
|
||||
fases: list[FasePaqueteInput] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PaqueteUpdate(BaseModel):
|
||||
nombre: str | None = Field(None, min_length=1)
|
||||
descripcion: str | None = None
|
||||
activo: bool | None = None
|
||||
|
||||
|
||||
class ManageAction(BaseModel):
|
||||
action: str = Field(..., description="Acción: addFase, updateFase, deleteFase, addServicio, removeServicio")
|
||||
faseId: str | None = None
|
||||
nombre: str | None = None
|
||||
orden: int | None = None
|
||||
servicioCatalogoId: str | None = None
|
||||
fasePaqueteId: str | None = None
|
||||
|
||||
|
||||
class FasePaqueteResponse(BaseModel):
|
||||
id: str
|
||||
paqueteId: str
|
||||
nombre: str
|
||||
orden: int
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
servicios: list[dict] | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class PaqueteResponse(BaseModel):
|
||||
id: str
|
||||
nombre: str
|
||||
descripcion: str | None
|
||||
activo: bool
|
||||
createdAt: datetime
|
||||
updatedAt: datetime
|
||||
fases: list[FasePaqueteResponse] | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
Reference in New Issue
Block a user