CARONTE v1.0 - Plataforma de Gestão Social
This commit is contained in:
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/v1/__init__.py
Normal file
0
backend/app/api/v1/__init__.py
Normal file
38
backend/app/api/v1/auth.py
Normal file
38
backend/app/api/v1/auth.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security import hash_password, verify_password, create_access_token, get_current_user_id
|
||||
from app.models.usuario import Usuario
|
||||
from app.schemas.schemas import UserCreate, UserOut, Token
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
@router.post("/registro", response_model=UserOut)
|
||||
async def registro(data: UserCreate, db: AsyncSession = Depends(get_db)):
|
||||
existing = await db.execute(select(Usuario).where(Usuario.email == data.email))
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(400, "Email já cadastrado")
|
||||
user = Usuario(nome=data.nome, email=data.email, senha_hash=hash_password(data.senha), telefone=data.telefone)
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
return user
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(form: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Usuario).where(Usuario.email == form.username))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user or not verify_password(form.password, user.senha_hash):
|
||||
raise HTTPException(401, "Credenciais inválidas")
|
||||
token = create_access_token({"sub": str(user.id)})
|
||||
return {"access_token": token}
|
||||
|
||||
@router.get("/me", response_model=UserOut)
|
||||
async def me(user_id: int = Depends(get_current_user_id), db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Usuario).where(Usuario.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(404, "Usuário não encontrado")
|
||||
return user
|
||||
32
backend/app/api/v1/beneficios.py
Normal file
32
backend/app/api/v1/beneficios.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user_id
|
||||
from app.models.beneficio import Beneficio
|
||||
from app.models.falecido import Falecido
|
||||
from app.schemas.schemas import BeneficioOut
|
||||
from app.services.beneficio_scanner import escanear_beneficios
|
||||
|
||||
router = APIRouter(prefix="/familias/{familia_id}/beneficios", tags=["beneficios"])
|
||||
|
||||
@router.get("/", response_model=list[BeneficioOut])
|
||||
async def listar(familia_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Beneficio).where(Beneficio.familia_id == familia_id))
|
||||
return result.scalars().all()
|
||||
|
||||
@router.post("/scan", response_model=list[BeneficioOut])
|
||||
async def scan(familia_id: int, db: AsyncSession = Depends(get_db)):
|
||||
# Delete existing and rescan
|
||||
result = await db.execute(select(Beneficio).where(Beneficio.familia_id == familia_id))
|
||||
for b in result.scalars().all():
|
||||
await db.delete(b)
|
||||
await db.commit()
|
||||
|
||||
result = await db.execute(select(Falecido).where(Falecido.familia_id == familia_id))
|
||||
falecidos = result.scalars().all()
|
||||
all_bens = []
|
||||
for f in falecidos:
|
||||
bens = await escanear_beneficios(db, familia_id, f)
|
||||
all_bens.extend(bens)
|
||||
return all_bens
|
||||
41
backend/app/api/v1/checklist.py
Normal file
41
backend/app/api/v1/checklist.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user_id
|
||||
from app.models.checklist import ChecklistItem
|
||||
from app.schemas.schemas import ChecklistItemOut, ChecklistUpdateStatus
|
||||
from app.services.checklist_engine import get_proximo_passo
|
||||
|
||||
router = APIRouter(prefix="/familias/{familia_id}/checklist", tags=["checklist"])
|
||||
|
||||
@router.get("/", response_model=list[ChecklistItemOut])
|
||||
async def listar(familia_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(
|
||||
select(ChecklistItem).where(ChecklistItem.familia_id == familia_id).order_by(ChecklistItem.ordem)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
@router.put("/{item_id}", response_model=ChecklistItemOut)
|
||||
async def atualizar_status(familia_id: int, item_id: int, data: ChecklistUpdateStatus, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(ChecklistItem).where(ChecklistItem.id == item_id, ChecklistItem.familia_id == familia_id))
|
||||
item = result.scalar_one_or_none()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item não encontrado")
|
||||
item.status = data.status
|
||||
await db.commit()
|
||||
await db.refresh(item)
|
||||
return item
|
||||
|
||||
@router.get("/proximo", response_model=ChecklistItemOut | None)
|
||||
async def proximo_passo(familia_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(
|
||||
select(ChecklistItem).where(ChecklistItem.familia_id == familia_id).order_by(ChecklistItem.ordem)
|
||||
)
|
||||
items = [{"id": i.id, "status": i.status, "ordem": i.ordem, "titulo": i.titulo, "descricao": i.descricao,
|
||||
"fase": i.fase, "categoria": i.categoria, "prazo_dias": i.prazo_dias, "familia_id": i.familia_id,
|
||||
"falecido_id": i.falecido_id, "dependencia_id": i.dependencia_id} for i in result.scalars().all()]
|
||||
p = get_proximo_passo(items)
|
||||
if not p:
|
||||
return None
|
||||
return p
|
||||
54
backend/app/api/v1/dashboard.py
Normal file
54
backend/app/api/v1/dashboard.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user_id
|
||||
from app.models.familia import Familia
|
||||
from app.models.checklist import ChecklistItem
|
||||
from app.models.beneficio import Beneficio
|
||||
from app.models.documento import Documento
|
||||
from app.schemas.schemas import DashboardOut
|
||||
|
||||
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
|
||||
|
||||
@router.get("/", response_model=DashboardOut)
|
||||
async def dashboard(user_id: int = Depends(get_current_user_id), db: AsyncSession = Depends(get_db)):
|
||||
fams = await db.execute(select(Familia).where(Familia.usuario_id == user_id))
|
||||
familias = fams.scalars().all()
|
||||
fam_ids = [f.id for f in familias]
|
||||
|
||||
pendentes = 0
|
||||
bens_count = 0
|
||||
docs_count = 0
|
||||
fam_list = []
|
||||
|
||||
for fam in familias:
|
||||
ch = await db.execute(select(ChecklistItem).where(ChecklistItem.familia_id == fam.id))
|
||||
items = ch.scalars().all()
|
||||
total = len(items)
|
||||
done = len([i for i in items if i.status == "concluido"])
|
||||
pendentes += len([i for i in items if i.status == "pendente"])
|
||||
|
||||
bn = await db.execute(select(func.count()).select_from(Beneficio).where(Beneficio.familia_id == fam.id))
|
||||
bc = bn.scalar() or 0
|
||||
bens_count += bc
|
||||
|
||||
dc = await db.execute(select(func.count()).select_from(Documento).where(Documento.familia_id == fam.id))
|
||||
docs_count += dc.scalar() or 0
|
||||
|
||||
fam_list.append({
|
||||
"id": fam.id,
|
||||
"nome": fam.nome,
|
||||
"total_items": total,
|
||||
"concluidos": done,
|
||||
"progresso": round(done / total * 100) if total else 0,
|
||||
"beneficios": bc,
|
||||
})
|
||||
|
||||
return DashboardOut(
|
||||
familias_ativas=len(familias),
|
||||
itens_pendentes=pendentes,
|
||||
beneficios_encontrados=bens_count,
|
||||
documentos_gerados=docs_count,
|
||||
familias=fam_list,
|
||||
)
|
||||
62
backend/app/api/v1/documentos.py
Normal file
62
backend/app/api/v1/documentos.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user_id
|
||||
from app.models.documento import Documento
|
||||
from app.models.falecido import Falecido
|
||||
from app.models.familia import Familia, MembroFamilia
|
||||
from app.schemas.schemas import DocumentoOut, DocumentoGerarRequest
|
||||
from app.services.document_generator import GENERATORS
|
||||
import os
|
||||
|
||||
router = APIRouter(prefix="/familias/{familia_id}/documentos", tags=["documentos"])
|
||||
|
||||
@router.get("/", response_model=list[DocumentoOut])
|
||||
async def listar(familia_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Documento).where(Documento.familia_id == familia_id))
|
||||
return result.scalars().all()
|
||||
|
||||
@router.post("/gerar", response_model=DocumentoOut)
|
||||
async def gerar(familia_id: int, data: DocumentoGerarRequest, db: AsyncSession = Depends(get_db)):
|
||||
gen = GENERATORS.get(data.tipo)
|
||||
if not gen:
|
||||
raise HTTPException(400, f"Tipo de documento inválido: {data.tipo}")
|
||||
|
||||
fam = await db.execute(select(Familia).where(Familia.id == familia_id))
|
||||
familia = fam.scalar_one_or_none()
|
||||
if not familia:
|
||||
raise HTTPException(404, "Família não encontrada")
|
||||
|
||||
fal = await db.execute(select(Falecido).where(Falecido.id == data.falecido_id))
|
||||
falecido = fal.scalar_one_or_none()
|
||||
if not falecido:
|
||||
raise HTTPException(404, "Falecido não encontrado")
|
||||
|
||||
membros = await db.execute(select(MembroFamilia).where(MembroFamilia.familia_id == familia_id))
|
||||
membro = membros.scalars().first()
|
||||
membro_nome = membro.nome if membro else "Representante da Família"
|
||||
|
||||
path = gen(familia.nome, falecido.nome, membro_nome, falecido.cpf or "000.000.000-00")
|
||||
|
||||
doc = Documento(
|
||||
familia_id=familia_id,
|
||||
falecido_id=data.falecido_id,
|
||||
tipo=data.tipo,
|
||||
nome=f"{data.tipo.replace('_',' ').title()} - {falecido.nome}",
|
||||
arquivo_path=path,
|
||||
status="gerado",
|
||||
)
|
||||
db.add(doc)
|
||||
await db.commit()
|
||||
await db.refresh(doc)
|
||||
return doc
|
||||
|
||||
@router.get("/{doc_id}/download")
|
||||
async def download(familia_id: int, doc_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Documento).where(Documento.id == doc_id, Documento.familia_id == familia_id))
|
||||
doc = result.scalar_one_or_none()
|
||||
if not doc or not doc.arquivo_path or not os.path.exists(doc.arquivo_path):
|
||||
raise HTTPException(404, "Documento não encontrado")
|
||||
return FileResponse(doc.arquivo_path, filename=os.path.basename(doc.arquivo_path), media_type="application/pdf")
|
||||
61
backend/app/api/v1/familias.py
Normal file
61
backend/app/api/v1/familias.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user_id
|
||||
from app.models.familia import Familia, MembroFamilia
|
||||
from app.models.falecido import Falecido
|
||||
from app.schemas.schemas import FamiliaCreate, FamiliaOut, MembroCreate, MembroOut, FalecidoCreate, FalecidoOut
|
||||
from app.services.checklist_engine import gerar_checklist
|
||||
from app.services.beneficio_scanner import escanear_beneficios
|
||||
|
||||
router = APIRouter(prefix="/familias", tags=["familias"])
|
||||
|
||||
@router.get("/", response_model=list[FamiliaOut])
|
||||
async def listar(user_id: int = Depends(get_current_user_id), db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Familia).where(Familia.usuario_id == user_id))
|
||||
return result.scalars().all()
|
||||
|
||||
@router.post("/", response_model=FamiliaOut)
|
||||
async def criar(data: FamiliaCreate, user_id: int = Depends(get_current_user_id), db: AsyncSession = Depends(get_db)):
|
||||
f = Familia(nome=data.nome, usuario_id=user_id)
|
||||
db.add(f)
|
||||
await db.commit()
|
||||
await db.refresh(f)
|
||||
return f
|
||||
|
||||
@router.get("/{id}", response_model=FamiliaOut)
|
||||
async def detalhe(id: int, user_id: int = Depends(get_current_user_id), db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Familia).where(Familia.id == id, Familia.usuario_id == user_id))
|
||||
f = result.scalar_one_or_none()
|
||||
if not f:
|
||||
raise HTTPException(404, "Família não encontrada")
|
||||
return f
|
||||
|
||||
@router.post("/{id}/membros", response_model=MembroOut)
|
||||
async def add_membro(id: int, data: MembroCreate, db: AsyncSession = Depends(get_db)):
|
||||
m = MembroFamilia(familia_id=id, **data.model_dump())
|
||||
db.add(m)
|
||||
await db.commit()
|
||||
await db.refresh(m)
|
||||
return m
|
||||
|
||||
@router.get("/{id}/membros", response_model=list[MembroOut])
|
||||
async def listar_membros(id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(MembroFamilia).where(MembroFamilia.familia_id == id))
|
||||
return result.scalars().all()
|
||||
|
||||
@router.post("/{id}/falecido", response_model=FalecidoOut)
|
||||
async def registrar_falecido(id: int, data: FalecidoCreate, db: AsyncSession = Depends(get_db)):
|
||||
f = Falecido(familia_id=id, **data.model_dump())
|
||||
db.add(f)
|
||||
await db.commit()
|
||||
await db.refresh(f)
|
||||
await gerar_checklist(db, id, f)
|
||||
await escanear_beneficios(db, id, f)
|
||||
return f
|
||||
|
||||
@router.get("/{id}/falecidos", response_model=list[FalecidoOut])
|
||||
async def listar_falecidos(id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Falecido).where(Falecido.familia_id == id))
|
||||
return result.scalars().all()
|
||||
0
backend/app/core/__init__.py
Normal file
0
backend/app/core/__init__.py
Normal file
14
backend/app/core/config.py
Normal file
14
backend/app/core/config.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
class Settings(BaseSettings):
|
||||
APP_NAME: str = "CARONTE"
|
||||
SECRET_KEY: str = "caronte-secret-key-change-in-production"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///./caronte.db"
|
||||
UPLOAD_DIR: str = "./uploads"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
settings = Settings()
|
||||
17
backend/app/core/database.py
Normal file
17
backend/app/core/database.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
async def get_db():
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
async def init_db():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
31
backend/app/core/security.py
Normal file
31
backend/app/core/security.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from datetime import datetime, timedelta
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from app.core.config import settings
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
return pwd_context.verify(plain, hashed)
|
||||
|
||||
def create_access_token(data: dict) -> str:
|
||||
to_encode = data.copy()
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
|
||||
async def get_current_user_id(token: str = Depends(oauth2_scheme)) -> int:
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
user_id = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise HTTPException(status_code=401, detail="Token inválido")
|
||||
return int(user_id)
|
||||
except JWTError:
|
||||
raise HTTPException(status_code=401, detail="Token inválido")
|
||||
182
backend/app/data/checklist_templates.json
Normal file
182
backend/app/data/checklist_templates.json
Normal file
@@ -0,0 +1,182 @@
|
||||
[
|
||||
{
|
||||
"titulo": "Obter Declaração de Óbito",
|
||||
"descricao": "Solicitar a declaração de óbito no hospital ou IML onde ocorreu o falecimento.",
|
||||
"fase": "imediato",
|
||||
"categoria": "certidoes",
|
||||
"prazo_dias": 1,
|
||||
"ordem": 1,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Registrar Certidão de Óbito",
|
||||
"descricao": "Levar a declaração de óbito ao cartório de registro civil para emitir a certidão de óbito.",
|
||||
"fase": "imediato",
|
||||
"categoria": "certidoes",
|
||||
"prazo_dias": 2,
|
||||
"ordem": 2,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Providenciar Sepultamento/Cremação",
|
||||
"descricao": "Contratar serviço funerário e realizar o sepultamento ou cremação.",
|
||||
"fase": "imediato",
|
||||
"categoria": "certidoes",
|
||||
"prazo_dias": 3,
|
||||
"ordem": 3,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Comunicar Empregador",
|
||||
"descricao": "Informar o empregador sobre o falecimento para rescisão do contrato de trabalho.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "trabalhista",
|
||||
"prazo_dias": 7,
|
||||
"ordem": 4,
|
||||
"condicao": "tinha_carteira_assinada"
|
||||
},
|
||||
{
|
||||
"titulo": "Comunicar INSS",
|
||||
"descricao": "Informar o INSS sobre o óbito e verificar benefícios como pensão por morte.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "inss",
|
||||
"prazo_dias": 7,
|
||||
"ordem": 5,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Solicitar Pensão por Morte",
|
||||
"descricao": "Requerer pensão por morte no INSS (Meu INSS ou agência). Prazo ideal: até 90 dias do óbito para receber desde a data do falecimento.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "inss",
|
||||
"prazo_dias": 90,
|
||||
"ordem": 6,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Solicitar Saque FGTS",
|
||||
"descricao": "Comparecer à Caixa Econômica Federal com documentos para saque do FGTS do falecido.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "fgts",
|
||||
"prazo_dias": 14,
|
||||
"ordem": 7,
|
||||
"condicao": "tinha_fgts"
|
||||
},
|
||||
{
|
||||
"titulo": "Solicitar Saque PIS/PASEP",
|
||||
"descricao": "Verificar saldo PIS/PASEP e solicitar saque na Caixa (PIS) ou Banco do Brasil (PASEP).",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "fgts",
|
||||
"prazo_dias": 14,
|
||||
"ordem": 8,
|
||||
"condicao": "tinha_carteira_assinada"
|
||||
},
|
||||
{
|
||||
"titulo": "Comunicar Bancos",
|
||||
"descricao": "Informar os bancos onde o falecido tinha conta sobre o óbito. Solicitar extrato e bloqueio.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "financeiro",
|
||||
"prazo_dias": 10,
|
||||
"ordem": 9,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Acionar Seguro de Vida",
|
||||
"descricao": "Entrar em contato com a seguradora para acionar o seguro de vida.",
|
||||
"fase": "primeira_semana",
|
||||
"categoria": "seguros",
|
||||
"prazo_dias": 10,
|
||||
"ordem": 10,
|
||||
"condicao": "tinha_seguro_vida"
|
||||
},
|
||||
{
|
||||
"titulo": "Solicitar DPVAT (se acidente)",
|
||||
"descricao": "Se o óbito foi por acidente de trânsito, solicitar indenização do DPVAT.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "seguros",
|
||||
"prazo_dias": 30,
|
||||
"ordem": 11,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Consultar Receita Federal (e-CAC)",
|
||||
"descricao": "Verificar pendências fiscais do falecido e situação do CPF.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "fiscal",
|
||||
"prazo_dias": 30,
|
||||
"ordem": 12,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Transferir Veículos",
|
||||
"descricao": "Iniciar processo de transferência de veículos no DETRAN.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "patrimonio",
|
||||
"prazo_dias": 30,
|
||||
"ordem": 13,
|
||||
"condicao": "tinha_veiculos"
|
||||
},
|
||||
{
|
||||
"titulo": "Iniciar Inventário",
|
||||
"descricao": "Contratar advogado e iniciar inventário judicial ou extrajudicial. Prazo legal: 60 dias do óbito.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "inventario",
|
||||
"prazo_dias": 60,
|
||||
"ordem": 14,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Levantar Bens Imóveis",
|
||||
"descricao": "Solicitar certidões de matrícula dos imóveis nos cartórios de registro de imóveis.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "inventario",
|
||||
"prazo_dias": 45,
|
||||
"ordem": 15,
|
||||
"condicao": "tinha_imoveis"
|
||||
},
|
||||
{
|
||||
"titulo": "Fazer Declaração Final de Espólio (IR)",
|
||||
"descricao": "Declarar imposto de renda do falecido referente ao ano do óbito (declaração inicial de espólio).",
|
||||
"fase": "60_dias",
|
||||
"categoria": "fiscal",
|
||||
"prazo_dias": 180,
|
||||
"ordem": 16,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Cancelar CPF do Falecido",
|
||||
"descricao": "Solicitar a regularização do CPF como 'titular falecido' na Receita Federal.",
|
||||
"fase": "60_dias",
|
||||
"categoria": "fiscal",
|
||||
"prazo_dias": 90,
|
||||
"ordem": 17,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Cancelar Serviços e Assinaturas",
|
||||
"descricao": "Cancelar telefone, internet, plano de saúde, assinaturas e cartões de crédito.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "administrativo",
|
||||
"prazo_dias": 30,
|
||||
"ordem": 18,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Transferir Titularidade de Serviços",
|
||||
"descricao": "Transferir contas de água, luz, gás para nome de familiar.",
|
||||
"fase": "30_dias",
|
||||
"categoria": "administrativo",
|
||||
"prazo_dias": 45,
|
||||
"ordem": 19,
|
||||
"condicao": null
|
||||
},
|
||||
{
|
||||
"titulo": "Concluir Partilha de Bens",
|
||||
"descricao": "Finalizar o inventário com a partilha formal dos bens entre os herdeiros.",
|
||||
"fase": "60_dias",
|
||||
"categoria": "inventario",
|
||||
"prazo_dias": 365,
|
||||
"ordem": 20,
|
||||
"condicao": null
|
||||
}
|
||||
]
|
||||
31
backend/app/main.py
Normal file
31
backend/app/main.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
from app.core.database import init_db
|
||||
from app.api.v1 import auth, familias, checklist, beneficios, documentos, dashboard
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
await init_db()
|
||||
yield
|
||||
|
||||
app = FastAPI(title="CARONTE API", description="O barqueiro que guia famílias pelo rio burocrático pós-óbito", version="1.0.0", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(auth.router, prefix="/api/v1")
|
||||
app.include_router(familias.router, prefix="/api/v1")
|
||||
app.include_router(checklist.router, prefix="/api/v1")
|
||||
app.include_router(beneficios.router, prefix="/api/v1")
|
||||
app.include_router(documentos.router, prefix="/api/v1")
|
||||
app.include_router(dashboard.router, prefix="/api/v1")
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "🚣 CARONTE - Guiando famílias pelo rio burocrático", "docs": "/docs"}
|
||||
6
backend/app/models/__init__.py
Normal file
6
backend/app/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from app.models.usuario import Usuario
|
||||
from app.models.familia import Familia, MembroFamilia
|
||||
from app.models.falecido import Falecido
|
||||
from app.models.checklist import ChecklistItem
|
||||
from app.models.beneficio import Beneficio
|
||||
from app.models.documento import Documento
|
||||
16
backend/app/models/beneficio.py
Normal file
16
backend/app/models/beneficio.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class Beneficio(Base):
|
||||
__tablename__ = "beneficios"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
familia_id = Column(Integer, ForeignKey("familias.id"), nullable=False)
|
||||
falecido_id = Column(Integer, ForeignKey("falecidos.id"), nullable=False)
|
||||
tipo = Column(String, nullable=False) # fgts, pis, pensao_morte, seguro_vida, seguro_dpvat
|
||||
nome = Column(String, nullable=False)
|
||||
descricao = Column(String, nullable=True)
|
||||
status = Column(String, default="identificado") # identificado, em_processo, sacado
|
||||
valor_estimado = Column(Float, nullable=True)
|
||||
valor_sacado = Column(Float, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
19
backend/app/models/checklist.py
Normal file
19
backend/app/models/checklist.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class ChecklistItem(Base):
|
||||
__tablename__ = "checklist_items"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
familia_id = Column(Integer, ForeignKey("familias.id"), nullable=False)
|
||||
falecido_id = Column(Integer, ForeignKey("falecidos.id"), nullable=False)
|
||||
titulo = Column(String, nullable=False)
|
||||
descricao = Column(Text, nullable=True)
|
||||
fase = Column(String, nullable=False) # imediato, primeira_semana, 30_dias, 60_dias
|
||||
categoria = Column(String, nullable=False) # certidoes, inss, fgts, inventario, etc
|
||||
status = Column(String, default="pendente") # pendente, andamento, concluido
|
||||
prazo_dias = Column(Integer, nullable=True)
|
||||
ordem = Column(Integer, default=0)
|
||||
dependencia_id = Column(Integer, nullable=True)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
14
backend/app/models/documento.py
Normal file
14
backend/app/models/documento.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class Documento(Base):
|
||||
__tablename__ = "documentos"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
familia_id = Column(Integer, ForeignKey("familias.id"), nullable=False)
|
||||
falecido_id = Column(Integer, ForeignKey("falecidos.id"), nullable=True)
|
||||
tipo = Column(String, nullable=False) # procuracao, requerimento_fgts, peticao_pensao, alvara
|
||||
nome = Column(String, nullable=False)
|
||||
arquivo_path = Column(String, nullable=True)
|
||||
status = Column(String, default="gerado") # gerado, enviado, aprovado
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
23
backend/app/models/falecido.py
Normal file
23
backend/app/models/falecido.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Date, ForeignKey, Float
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class Falecido(Base):
|
||||
__tablename__ = "falecidos"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
familia_id = Column(Integer, ForeignKey("familias.id"), nullable=False)
|
||||
nome = Column(String, nullable=False)
|
||||
cpf = Column(String, nullable=True)
|
||||
data_nascimento = Column(Date, nullable=True)
|
||||
data_obito = Column(Date, nullable=False)
|
||||
causa_obito = Column(String, nullable=True)
|
||||
tipo_vinculo = Column(String, nullable=False) # empregado, aposentado, autonomo, servidor
|
||||
empregador = Column(String, nullable=True)
|
||||
tinha_carteira_assinada = Column(Integer, default=0)
|
||||
tinha_fgts = Column(Integer, default=0)
|
||||
era_aposentado = Column(Integer, default=0)
|
||||
tinha_seguro_vida = Column(Integer, default=0)
|
||||
tinha_imoveis = Column(Integer, default=0)
|
||||
tinha_veiculos = Column(Integer, default=0)
|
||||
salario_estimado = Column(Float, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
20
backend/app/models/familia.py
Normal file
20
backend/app/models/familia.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class Familia(Base):
|
||||
__tablename__ = "familias"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
nome = Column(String, nullable=False)
|
||||
usuario_id = Column(Integer, ForeignKey("usuarios.id"), nullable=False)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
class MembroFamilia(Base):
|
||||
__tablename__ = "membros_familia"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
familia_id = Column(Integer, ForeignKey("familias.id"), nullable=False)
|
||||
nome = Column(String, nullable=False)
|
||||
parentesco = Column(String, nullable=False)
|
||||
cpf = Column(String, nullable=True)
|
||||
telefone = Column(String, nullable=True)
|
||||
email = Column(String, nullable=True)
|
||||
12
backend/app/models/usuario.py
Normal file
12
backend/app/models/usuario.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
class Usuario(Base):
|
||||
__tablename__ = "usuarios"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
nome = Column(String, nullable=False)
|
||||
email = Column(String, unique=True, nullable=False)
|
||||
senha_hash = Column(String, nullable=False)
|
||||
telefone = Column(String, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
151
backend/app/schemas/schemas.py
Normal file
151
backend/app/schemas/schemas.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
|
||||
# Auth
|
||||
class UserCreate(BaseModel):
|
||||
nome: str
|
||||
email: str
|
||||
senha: str
|
||||
telefone: Optional[str] = None
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
email: str
|
||||
senha: str
|
||||
|
||||
class UserOut(BaseModel):
|
||||
id: int
|
||||
nome: str
|
||||
email: str
|
||||
telefone: Optional[str] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
# Familia
|
||||
class FamiliaCreate(BaseModel):
|
||||
nome: str
|
||||
|
||||
class FamiliaOut(BaseModel):
|
||||
id: int
|
||||
nome: str
|
||||
usuario_id: int
|
||||
created_at: Optional[datetime] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class MembroCreate(BaseModel):
|
||||
nome: str
|
||||
parentesco: str
|
||||
cpf: Optional[str] = None
|
||||
telefone: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
|
||||
class MembroOut(BaseModel):
|
||||
id: int
|
||||
familia_id: int
|
||||
nome: str
|
||||
parentesco: str
|
||||
cpf: Optional[str] = None
|
||||
telefone: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Falecido
|
||||
class FalecidoCreate(BaseModel):
|
||||
nome: str
|
||||
cpf: Optional[str] = None
|
||||
data_nascimento: Optional[date] = None
|
||||
data_obito: date
|
||||
causa_obito: Optional[str] = None
|
||||
tipo_vinculo: str
|
||||
empregador: Optional[str] = None
|
||||
tinha_carteira_assinada: int = 0
|
||||
tinha_fgts: int = 0
|
||||
era_aposentado: int = 0
|
||||
tinha_seguro_vida: int = 0
|
||||
tinha_imoveis: int = 0
|
||||
tinha_veiculos: int = 0
|
||||
salario_estimado: Optional[float] = None
|
||||
|
||||
class FalecidoOut(BaseModel):
|
||||
id: int
|
||||
familia_id: int
|
||||
nome: str
|
||||
cpf: Optional[str] = None
|
||||
data_nascimento: Optional[date] = None
|
||||
data_obito: date
|
||||
causa_obito: Optional[str] = None
|
||||
tipo_vinculo: str
|
||||
empregador: Optional[str] = None
|
||||
tinha_carteira_assinada: int = 0
|
||||
tinha_fgts: int = 0
|
||||
era_aposentado: int = 0
|
||||
tinha_seguro_vida: int = 0
|
||||
tinha_imoveis: int = 0
|
||||
tinha_veiculos: int = 0
|
||||
salario_estimado: Optional[float] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Checklist
|
||||
class ChecklistItemOut(BaseModel):
|
||||
id: int
|
||||
familia_id: int
|
||||
falecido_id: int
|
||||
titulo: str
|
||||
descricao: Optional[str] = None
|
||||
fase: str
|
||||
categoria: str
|
||||
status: str
|
||||
prazo_dias: Optional[int] = None
|
||||
ordem: int = 0
|
||||
dependencia_id: Optional[int] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class ChecklistUpdateStatus(BaseModel):
|
||||
status: str
|
||||
|
||||
# Beneficio
|
||||
class BeneficioOut(BaseModel):
|
||||
id: int
|
||||
familia_id: int
|
||||
falecido_id: int
|
||||
tipo: str
|
||||
nome: str
|
||||
descricao: Optional[str] = None
|
||||
status: str
|
||||
valor_estimado: Optional[float] = None
|
||||
valor_sacado: Optional[float] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Documento
|
||||
class DocumentoOut(BaseModel):
|
||||
id: int
|
||||
familia_id: int
|
||||
falecido_id: Optional[int] = None
|
||||
tipo: str
|
||||
nome: str
|
||||
arquivo_path: Optional[str] = None
|
||||
status: str
|
||||
created_at: Optional[datetime] = None
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class DocumentoGerarRequest(BaseModel):
|
||||
tipo: str
|
||||
falecido_id: int
|
||||
|
||||
# Dashboard
|
||||
class DashboardOut(BaseModel):
|
||||
familias_ativas: int
|
||||
itens_pendentes: int
|
||||
beneficios_encontrados: int
|
||||
documentos_gerados: int
|
||||
familias: list = []
|
||||
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
64
backend/app/services/beneficio_scanner.py
Normal file
64
backend/app/services/beneficio_scanner.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.beneficio import Beneficio
|
||||
from app.models.falecido import Falecido
|
||||
|
||||
BENEFICIOS_MAP = [
|
||||
{
|
||||
"condicao": "tinha_fgts",
|
||||
"tipo": "fgts",
|
||||
"nome": "Saque FGTS",
|
||||
"descricao": "Saldo do FGTS disponível para saque pelos dependentes habilitados.",
|
||||
"valor_mult": 3.5,
|
||||
},
|
||||
{
|
||||
"condicao": "tinha_carteira_assinada",
|
||||
"tipo": "pis",
|
||||
"nome": "Saque PIS/PASEP",
|
||||
"descricao": "Saldo de cotas do PIS/PASEP para saque pelos herdeiros.",
|
||||
"valor_mult": 0.8,
|
||||
},
|
||||
{
|
||||
"condicao": None,
|
||||
"tipo": "pensao_morte",
|
||||
"nome": "Pensão por Morte (INSS)",
|
||||
"descricao": "Benefício mensal pago pelo INSS aos dependentes do segurado falecido.",
|
||||
"valor_mult": 12,
|
||||
},
|
||||
{
|
||||
"condicao": "tinha_seguro_vida",
|
||||
"tipo": "seguro_vida",
|
||||
"nome": "Seguro de Vida",
|
||||
"descricao": "Indenização do seguro de vida contratado pelo falecido.",
|
||||
"valor_mult": 24,
|
||||
},
|
||||
{
|
||||
"condicao": "tinha_carteira_assinada",
|
||||
"tipo": "rescisao",
|
||||
"nome": "Verbas Rescisórias",
|
||||
"descricao": "Saldo de salário, férias proporcionais, 13º proporcional.",
|
||||
"valor_mult": 2,
|
||||
},
|
||||
]
|
||||
|
||||
async def escanear_beneficios(db: AsyncSession, familia_id: int, falecido: Falecido) -> list[Beneficio]:
|
||||
salario = falecido.salario_estimado or 2500.0
|
||||
beneficios = []
|
||||
for b in BENEFICIOS_MAP:
|
||||
cond = b.get("condicao")
|
||||
if cond:
|
||||
val = getattr(falecido, cond, 0)
|
||||
if not val:
|
||||
continue
|
||||
ben = Beneficio(
|
||||
familia_id=familia_id,
|
||||
falecido_id=falecido.id,
|
||||
tipo=b["tipo"],
|
||||
nome=b["nome"],
|
||||
descricao=b["descricao"],
|
||||
status="identificado",
|
||||
valor_estimado=round(salario * b["valor_mult"], 2),
|
||||
)
|
||||
db.add(ben)
|
||||
beneficios.append(ben)
|
||||
await db.commit()
|
||||
return beneficios
|
||||
43
backend/app/services/checklist_engine.py
Normal file
43
backend/app/services/checklist_engine.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import json
|
||||
import os
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.checklist import ChecklistItem
|
||||
from app.models.falecido import Falecido
|
||||
|
||||
TEMPLATES_PATH = os.path.join(os.path.dirname(__file__), "..", "data", "checklist_templates.json")
|
||||
|
||||
def load_templates():
|
||||
with open(TEMPLATES_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
async def gerar_checklist(db: AsyncSession, familia_id: int, falecido: Falecido) -> list[ChecklistItem]:
|
||||
templates = load_templates()
|
||||
items = []
|
||||
for t in templates:
|
||||
cond = t.get("condicao")
|
||||
if cond:
|
||||
val = getattr(falecido, cond, 0)
|
||||
if not val:
|
||||
continue
|
||||
item = ChecklistItem(
|
||||
familia_id=familia_id,
|
||||
falecido_id=falecido.id,
|
||||
titulo=t["titulo"],
|
||||
descricao=t.get("descricao"),
|
||||
fase=t["fase"],
|
||||
categoria=t["categoria"],
|
||||
status="pendente",
|
||||
prazo_dias=t.get("prazo_dias"),
|
||||
ordem=t.get("ordem", 0),
|
||||
)
|
||||
db.add(item)
|
||||
items.append(item)
|
||||
await db.commit()
|
||||
return items
|
||||
|
||||
def get_proximo_passo(items: list[dict]) -> dict | None:
|
||||
pendentes = [i for i in items if i.get("status") == "pendente"]
|
||||
if not pendentes:
|
||||
return None
|
||||
pendentes.sort(key=lambda x: x.get("ordem", 999))
|
||||
return pendentes[0]
|
||||
99
backend/app/services/document_generator.py
Normal file
99
backend/app/services/document_generator.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY
|
||||
from app.core.config import settings
|
||||
|
||||
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
||||
|
||||
def _styles():
|
||||
ss = getSampleStyleSheet()
|
||||
ss.add(ParagraphStyle(name="TitleCenter", parent=ss["Title"], alignment=TA_CENTER, fontSize=16))
|
||||
ss.add(ParagraphStyle(name="Body", parent=ss["Normal"], alignment=TA_JUSTIFY, fontSize=11, leading=16))
|
||||
return ss
|
||||
|
||||
def gerar_procuracao(familia_nome: str, falecido_nome: str, membro_nome: str, falecido_cpf: str = "000.000.000-00") -> str:
|
||||
filename = f"procuracao_{familia_nome.lower().replace(' ','_')}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
||||
path = os.path.join(settings.UPLOAD_DIR, filename)
|
||||
doc = SimpleDocTemplate(path, pagesize=A4, topMargin=3*cm)
|
||||
ss = _styles()
|
||||
story = [
|
||||
Paragraph("PROCURAÇÃO", ss["TitleCenter"]),
|
||||
Spacer(1, 1*cm),
|
||||
Paragraph(
|
||||
f"Pelo presente instrumento particular, eu, <b>{membro_nome}</b>, na qualidade de herdeiro(a) "
|
||||
f"de <b>{falecido_nome}</b> (CPF: {falecido_cpf}), da família <b>{familia_nome}</b>, nomeio e constituo "
|
||||
f"como meu(minha) bastante procurador(a) ______________________, para em meu nome representar "
|
||||
f"perante órgãos públicos, bancos e demais instituições, podendo requerer, assinar documentos, "
|
||||
f"receber valores e praticar todos os atos necessários ao cumprimento deste mandato.",
|
||||
ss["Body"]
|
||||
),
|
||||
Spacer(1, 1.5*cm),
|
||||
Paragraph(f"Local e data: ________________, {datetime.now().strftime('%d/%m/%Y')}", ss["Body"]),
|
||||
Spacer(1, 2*cm),
|
||||
Paragraph("_______________________________<br/>Assinatura do Outorgante", ss["Body"]),
|
||||
]
|
||||
doc.build(story)
|
||||
return path
|
||||
|
||||
def gerar_requerimento_fgts(familia_nome: str, falecido_nome: str, membro_nome: str, falecido_cpf: str = "000.000.000-00") -> str:
|
||||
filename = f"requerimento_fgts_{familia_nome.lower().replace(' ','_')}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
||||
path = os.path.join(settings.UPLOAD_DIR, filename)
|
||||
doc = SimpleDocTemplate(path, pagesize=A4, topMargin=3*cm)
|
||||
ss = _styles()
|
||||
story = [
|
||||
Paragraph("REQUERIMENTO DE SAQUE DO FGTS POR FALECIMENTO", ss["TitleCenter"]),
|
||||
Spacer(1, 1*cm),
|
||||
Paragraph("À Caixa Econômica Federal", ss["Body"]),
|
||||
Spacer(1, 0.5*cm),
|
||||
Paragraph(
|
||||
f"Eu, <b>{membro_nome}</b>, na qualidade de dependente/herdeiro(a) de <b>{falecido_nome}</b> "
|
||||
f"(CPF: {falecido_cpf}), venho por meio deste requerer o saque do saldo da(s) conta(s) vinculada(s) "
|
||||
f"ao FGTS do trabalhador falecido, conforme previsto na Lei nº 8.036/90, art. 20, inciso IV.",
|
||||
ss["Body"]
|
||||
),
|
||||
Spacer(1, 0.5*cm),
|
||||
Paragraph("Documentos em anexo: Certidão de Óbito, documento de identidade, comprovante de dependência.", ss["Body"]),
|
||||
Spacer(1, 1.5*cm),
|
||||
Paragraph(f"Local e data: ________________, {datetime.now().strftime('%d/%m/%Y')}", ss["Body"]),
|
||||
Spacer(1, 2*cm),
|
||||
Paragraph("_______________________________<br/>Assinatura do Requerente", ss["Body"]),
|
||||
]
|
||||
doc.build(story)
|
||||
return path
|
||||
|
||||
def gerar_peticao_pensao(familia_nome: str, falecido_nome: str, membro_nome: str, falecido_cpf: str = "000.000.000-00") -> str:
|
||||
filename = f"peticao_pensao_{familia_nome.lower().replace(' ','_')}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
||||
path = os.path.join(settings.UPLOAD_DIR, filename)
|
||||
doc = SimpleDocTemplate(path, pagesize=A4, topMargin=3*cm)
|
||||
ss = _styles()
|
||||
story = [
|
||||
Paragraph("REQUERIMENTO DE PENSÃO POR MORTE", ss["TitleCenter"]),
|
||||
Spacer(1, 1*cm),
|
||||
Paragraph("Ao Instituto Nacional do Seguro Social - INSS", ss["Body"]),
|
||||
Spacer(1, 0.5*cm),
|
||||
Paragraph(
|
||||
f"Eu, <b>{membro_nome}</b>, na qualidade de dependente de <b>{falecido_nome}</b> "
|
||||
f"(CPF: {falecido_cpf}), venho requerer a concessão do benefício de Pensão por Morte, "
|
||||
f"conforme previsto nos arts. 74 a 79 da Lei nº 8.213/91, em razão do falecimento do(a) "
|
||||
f"segurado(a) acima identificado(a).",
|
||||
ss["Body"]
|
||||
),
|
||||
Spacer(1, 0.5*cm),
|
||||
Paragraph("Documentos em anexo: Certidão de Óbito, documentos pessoais, comprovante de dependência econômica.", ss["Body"]),
|
||||
Spacer(1, 1.5*cm),
|
||||
Paragraph(f"Local e data: ________________, {datetime.now().strftime('%d/%m/%Y')}", ss["Body"]),
|
||||
Spacer(1, 2*cm),
|
||||
Paragraph("_______________________________<br/>Assinatura do Requerente", ss["Body"]),
|
||||
]
|
||||
doc.build(story)
|
||||
return path
|
||||
|
||||
GENERATORS = {
|
||||
"procuracao": gerar_procuracao,
|
||||
"requerimento_fgts": gerar_requerimento_fgts,
|
||||
"peticao_pensao": gerar_peticao_pensao,
|
||||
}
|
||||
11
backend/requirements.txt
Normal file
11
backend/requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
sqlalchemy[asyncio]==2.0.25
|
||||
aiosqlite==0.19.0
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-multipart==0.0.6
|
||||
reportlab==4.1.0
|
||||
pydantic-settings==2.1.0
|
||||
bcrypt==4.0.1
|
||||
asyncpg==0.30.0
|
||||
98
backend/seed_data.py
Normal file
98
backend/seed_data.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""Seed script - popula o banco com dados mock."""
|
||||
import asyncio
|
||||
from datetime import date
|
||||
from app.core.database import engine, async_session, Base
|
||||
from app.core.security import hash_password
|
||||
from app.models import *
|
||||
from app.services.checklist_engine import gerar_checklist
|
||||
from app.services.beneficio_scanner import escanear_beneficios
|
||||
|
||||
async def seed():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async with async_session() as db:
|
||||
# Usuário demo
|
||||
user = Usuario(nome="Maria da Silva", email="demo@caronte.com.br", senha_hash=hash_password("123456"), telefone="(11) 99999-0001")
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
# Famílias
|
||||
familias_data = [
|
||||
{"nome": "Família Silva", "membros": [
|
||||
{"nome": "Maria da Silva", "parentesco": "Esposa", "cpf": "123.456.789-00", "telefone": "(11) 99999-0001"},
|
||||
{"nome": "João da Silva Jr.", "parentesco": "Filho", "cpf": "123.456.789-01"},
|
||||
]},
|
||||
{"nome": "Família Oliveira", "membros": [
|
||||
{"nome": "Ana Oliveira", "parentesco": "Filha", "cpf": "234.567.890-00", "telefone": "(21) 98888-0002"},
|
||||
{"nome": "Carlos Oliveira", "parentesco": "Filho", "cpf": "234.567.890-01"},
|
||||
]},
|
||||
{"nome": "Família Santos", "membros": [
|
||||
{"nome": "Lucia Santos", "parentesco": "Esposa", "cpf": "345.678.901-00", "telefone": "(31) 97777-0003"},
|
||||
]},
|
||||
]
|
||||
|
||||
falecidos_data = [
|
||||
{"familia_idx": 0, "nome": "José da Silva", "cpf": "111.222.333-44", "data_nascimento": date(1955, 3, 15),
|
||||
"data_obito": date(2026, 1, 10), "causa_obito": "Infarto agudo do miocárdio", "tipo_vinculo": "empregado",
|
||||
"empregador": "Construtora Progresso Ltda", "tinha_carteira_assinada": 1, "tinha_fgts": 1,
|
||||
"era_aposentado": 0, "tinha_seguro_vida": 1, "tinha_imoveis": 1, "tinha_veiculos": 1, "salario_estimado": 4500.0},
|
||||
|
||||
{"familia_idx": 0, "nome": "Antônia da Silva", "cpf": "111.222.333-55", "data_nascimento": date(1930, 8, 20),
|
||||
"data_obito": date(2025, 11, 5), "causa_obito": "Causas naturais", "tipo_vinculo": "aposentado",
|
||||
"era_aposentado": 1, "salario_estimado": 2800.0},
|
||||
|
||||
{"familia_idx": 1, "nome": "Roberto Oliveira", "cpf": "222.333.444-55", "data_nascimento": date(1960, 12, 1),
|
||||
"data_obito": date(2026, 1, 25), "causa_obito": "Câncer de pulmão", "tipo_vinculo": "aposentado",
|
||||
"era_aposentado": 1, "tinha_seguro_vida": 1, "tinha_imoveis": 1, "salario_estimado": 5200.0},
|
||||
|
||||
{"familia_idx": 1, "nome": "Pedro Oliveira", "cpf": "222.333.444-66", "data_nascimento": date(1985, 5, 10),
|
||||
"data_obito": date(2026, 2, 1), "causa_obito": "Acidente de trânsito", "tipo_vinculo": "empregado",
|
||||
"empregador": "Tech Solutions SA", "tinha_carteira_assinada": 1, "tinha_fgts": 1,
|
||||
"tinha_seguro_vida": 0, "tinha_veiculos": 1, "salario_estimado": 8500.0},
|
||||
|
||||
{"familia_idx": 2, "nome": "Francisco Santos", "cpf": "333.444.555-66", "data_nascimento": date(1970, 7, 22),
|
||||
"data_obito": date(2026, 1, 15), "causa_obito": "AVC", "tipo_vinculo": "autonomo",
|
||||
"tinha_carteira_assinada": 0, "tinha_fgts": 0, "tinha_imoveis": 1, "salario_estimado": 3500.0},
|
||||
]
|
||||
|
||||
familias = []
|
||||
for fd in familias_data:
|
||||
f = Familia(nome=fd["nome"], usuario_id=user.id)
|
||||
db.add(f)
|
||||
await db.commit()
|
||||
await db.refresh(f)
|
||||
familias.append(f)
|
||||
for m in fd["membros"]:
|
||||
db.add(MembroFamilia(familia_id=f.id, **m))
|
||||
await db.commit()
|
||||
|
||||
for fd in falecidos_data:
|
||||
fam = familias[fd["familia_idx"]]
|
||||
data = {k: v for k, v in fd.items() if k != "familia_idx"}
|
||||
fal = Falecido(familia_id=fam.id, **data)
|
||||
db.add(fal)
|
||||
await db.commit()
|
||||
await db.refresh(fal)
|
||||
await gerar_checklist(db, fam.id, fal)
|
||||
await escanear_beneficios(db, fam.id, fal)
|
||||
|
||||
# Documentos mock
|
||||
doc_types = [
|
||||
(familias[0].id, 1, "procuracao", "Procuração - José da Silva"),
|
||||
(familias[0].id, 1, "requerimento_fgts", "Requerimento FGTS - José da Silva"),
|
||||
(familias[1].id, 3, "peticao_pensao", "Petição Pensão por Morte - Roberto Oliveira"),
|
||||
]
|
||||
for fam_id, fal_id, tipo, nome in doc_types:
|
||||
db.add(Documento(familia_id=fam_id, falecido_id=fal_id, tipo=tipo, nome=nome, status="gerado"))
|
||||
await db.commit()
|
||||
|
||||
print("✅ Seed concluído! Dados mock inseridos.")
|
||||
print(" - 1 usuário: demo@caronte.com.br / 123456")
|
||||
print(" - 3 famílias, 5 falecidos")
|
||||
print(" - Checklists e benefícios gerados automaticamente")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed())
|
||||
Reference in New Issue
Block a user