- Recupera el trabajo revertido (doble propuesta, retainer, niveles, API Python) desde el stash de GitHub Desktop - Dockerfile multi-stage para Next.js (migraciones automáticas al arrancar, seed opcional via RUN_SEED) - docker-compose.coolify.yml: postgres + web + api con healthchecks y SERVICE_FQDN_* - Endpoint público /api/health (verifica BD) para healthchecks - seed.ts parametrizado: conexión DB_* y credenciales SEED_* por entorno (sin passwords hardcodeadas) - .env.example, .dockerignore, uvicorn con --proxy-headers - Limpieza: .xlsx y __pycache__ fuera del repo Co-Authored-By: Claude Fable 5 <[email protected]>
80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
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
|
|
nivel: str | None = Field(None, description="Nivel/tier: Emprendedor, PYME, Empresario, Corporativo")
|
|
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
|
|
nivel: 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
|