This commit is contained in:
Flatlogic Bot 2026-05-06 12:13:41 +00:00
parent 937482724e
commit fca450d5e9
5 changed files with 2163 additions and 150 deletions

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react'
import React, { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'

View File

@ -7,6 +7,15 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
{
href: '/centro-cobranza',
label: 'Centro de cobranza',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiCashCheck' in icon ? icon['mdiCashCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_PAYMENTS',
},
{
href: '/users/users-list',

File diff suppressed because it is too large Load Diff

View File

@ -1,166 +1,279 @@
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import {
mdiAccountGroup,
mdiBank,
mdiCalendarMonth,
mdiCashCheck,
mdiChevronRight,
mdiReceiptTextOutline,
mdiViewDashboardOutline,
} from '@mdi/js';
import Head from 'next/head';
import Link from 'next/link';
import React, { ReactElement } from 'react';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import BaseIcon from '../components/BaseIcon';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
import LayoutGuest from '../layouts/Guest';
const features = [
{
icon: mdiAccountGroup,
title: 'Residentes por apartamento',
description:
'Registra titulares, familiares y contactos autorizados con cédula, teléfono, WhatsApp, email, fecha de nacimiento, mudanza y notas útiles.',
},
{
icon: mdiCashCheck,
title: 'Cobranza clara y multi-mes',
description:
'Identifica quién está al día y quién debe. Registra pagos completos de varias cuotas pendientes y deja el recibo listo en el sistema.',
},
{
icon: mdiBank,
title: 'Gastos y saldos visibles',
description:
'Controla en qué se va el dinero y cuánto queda en banco o efectivo, reflejado por cuenta y moneda.',
},
{
icon: mdiReceiptTextOutline,
title: 'Recibos y seguimiento',
description:
'Cada cobro genera recibos trazables para revisión y envío posterior por los canales que defina la administración.',
},
];
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('image');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'Condominio Pagos y Gastos'
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
const firstDeliverySteps = [
{
title: '1. Seleccionar apartamento moroso',
text: 'El panel resalta qué apartamentos tienen cuotas pendientes y muestra de inmediato su contacto principal y deuda acumulada.',
},
{
title: '2. Registrar pago completo',
text: 'Puedes escoger una o varias cuotas pendientes de la misma moneda, elegir cuenta y método de pago, y guardar la operación.',
},
{
title: '3. Generar recibos y revisar saldos',
text: 'El sistema crea recibos, actualiza las cuotas cobradas y deja a la vista el impacto en caja, bancos, pagos recientes y gastos.',
},
];
export default function HomePage() {
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<>
<Head>
<title>{getPageTitle('Starter Page')}</title>
<title>{getPageTitle('Condominio Pagos y Gastos')}</title>
</Head>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your Condominio Pagos y Gastos app!"/>
<div className="space-y-3">
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center text-gray-500'>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
<div className='min-h-screen bg-slate-950 text-slate-100'>
<header className='border-b border-white/10'>
<div className='mx-auto flex max-w-7xl items-center justify-between gap-4 px-6 py-5'>
<div>
<p className='text-xs font-semibold uppercase tracking-[0.35em] text-cyan-300'>
Condominio Pagos y Gastos
</p>
<p className='mt-2 text-sm text-slate-300'>
Administración residencial para cobros, recibos y control financiero.
</p>
</div>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
<BaseButtons noWrap className='justify-end'>
<BaseButton href='/login' label='Iniciar sesión' color='whiteDark' small />
<BaseButton
href='/login'
label='Entrar al panel administrativo'
color='info'
small
/>
</BaseButtons>
</CardBox>
</div>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
</div>
</header>
</div>
<main>
<section className='mx-auto grid max-w-7xl gap-12 px-6 py-16 lg:grid-cols-[1.15fr_0.85fr] lg:py-24'>
<div>
<div className='inline-flex items-center gap-2 rounded-full border border-cyan-400/20 bg-cyan-400/10 px-4 py-2 text-sm font-semibold text-cyan-200'>
<BaseIcon path={mdiViewDashboardOutline} size={18} />
Primera entrega enfocada en el flujo de cobranza
</div>
<h1 className='mt-6 max-w-4xl text-4xl font-semibold tracking-tight text-white md:text-6xl'>
Una base sólida para administrar tu edificio, cobrar mejor y ver cada bolívar con claridad.
</h1>
<p className='mt-6 max-w-3xl text-lg leading-8 text-slate-300'>
Pensado para un edificio de 15 pisos con 10 apartamentos por piso, con cuotas de mantenimiento fijas desde 2023. El objetivo es que la administración pueda registrar residentes, controlar morosidad, revisar gastos y generar recibos sin perder trazabilidad.
</p>
<div className='mt-8 flex flex-wrap gap-3'>
<BaseButton
href='/login'
label='Entrar al panel administrativo'
color='info'
/>
<a
href='#primera-entrega'
className='inline-flex items-center gap-2 rounded border border-white/15 px-4 py-2 font-semibold text-slate-100 transition hover:border-cyan-300 hover:text-cyan-200'
>
Ver la primera entrega
<BaseIcon path={mdiChevronRight} size={18} />
</a>
</div>
<div className='mt-10 grid gap-4 sm:grid-cols-3'>
<div className='rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur'>
<p className='text-sm text-slate-300'>Capacidad del edificio</p>
<p className='mt-2 text-3xl font-semibold text-white'>150 aptos</p>
<p className='mt-2 text-sm text-slate-400'>15 pisos × 10 unidades por piso</p>
</div>
<div className='rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur'>
<p className='text-sm text-slate-300'>Cobro objetivo</p>
<p className='mt-2 text-3xl font-semibold text-white'>Mensual</p>
<p className='mt-2 text-sm text-slate-400'>Con pagos adelantados por varios meses cuando aplique</p>
</div>
<div className='rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur'>
<p className='text-sm text-slate-300'>Visibilidad financiera</p>
<p className='mt-2 text-3xl font-semibold text-white'>Bs / $</p>
<p className='mt-2 text-sm text-slate-400'>Caja, bancos, gastos y recibos en una misma operación</p>
</div>
</div>
</div>
<div className='space-y-4'>
<div className='rounded-[32px] border border-white/10 bg-gradient-to-br from-indigo-500/20 to-cyan-400/10 p-6 backdrop-blur'>
<div className='flex items-start justify-between gap-4'>
<div>
<p className='text-xs font-semibold uppercase tracking-[0.25em] text-cyan-200'>
Ya disponible al iniciar sesión
</p>
<h2 className='mt-3 text-2xl font-semibold text-white'>Centro de cobranza</h2>
<p className='mt-3 text-sm leading-7 text-slate-300'>
Un panel diseñado para detectar morosidad, registrar pagos completos por cuota pendiente, generar recibos y revisar caja/bancos sin salir de la misma vista.
</p>
</div>
<div className='rounded-2xl bg-white/10 p-3 text-cyan-200'>
<BaseIcon path={mdiCashCheck} size={24} />
</div>
</div>
<div className='mt-6 grid gap-3 sm:grid-cols-2'>
<div className='rounded-3xl border border-white/10 bg-white/5 p-4'>
<p className='text-sm text-slate-300'>Qué resuelve primero</p>
<p className='mt-2 text-lg font-semibold text-white'>Cobranza + recibos + saldos</p>
</div>
<div className='rounded-3xl border border-white/10 bg-white/5 p-4'>
<p className='text-sm text-slate-300'>Cómo se descubre</p>
<p className='mt-2 text-lg font-semibold text-white'>Desde el landing y el menú principal</p>
</div>
</div>
</div>
<div className='grid gap-4 sm:grid-cols-2'>
<div className='rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur'>
<div className='flex items-center gap-3'>
<div className='rounded-2xl bg-white/10 p-3 text-cyan-200'>
<BaseIcon path={mdiCalendarMonth} size={22} />
</div>
<div>
<p className='text-lg font-semibold text-white'>Cuotas pendientes</p>
<p className='text-sm text-slate-400'>Mismo panel, mismas decisiones</p>
</div>
</div>
<p className='mt-4 text-sm leading-7 text-slate-300'>
Ve rápidamente quién debe, cuánto debe y desde cuándo.
</p>
</div>
<div className='rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur'>
<div className='flex items-center gap-3'>
<div className='rounded-2xl bg-white/10 p-3 text-cyan-200'>
<BaseIcon path={mdiBank} size={22} />
</div>
<div>
<p className='text-lg font-semibold text-white'>Caja y bancos</p>
<p className='text-sm text-slate-400'>Disponibilidad actualizada</p>
</div>
</div>
<p className='mt-4 text-sm leading-7 text-slate-300'>
El panel resume saldos por tipo de cuenta y te deja enlazar los gastos.
</p>
</div>
</div>
</div>
</section>
<section className='mx-auto max-w-7xl px-6 pb-8'>
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
{features.map((feature) => (
<div
key={feature.title}
className='rounded-[28px] border border-white/10 bg-white/5 p-6 backdrop-blur'
>
<div className='inline-flex rounded-2xl bg-cyan-400/10 p-3 text-cyan-200'>
<BaseIcon path={feature.icon} size={22} />
</div>
<h3 className='mt-5 text-xl font-semibold text-white'>{feature.title}</h3>
<p className='mt-3 text-sm leading-7 text-slate-300'>{feature.description}</p>
</div>
))}
</div>
</section>
<section id='primera-entrega' className='mx-auto max-w-7xl px-6 py-16'>
<div className='rounded-[36px] border border-white/10 bg-white/5 p-8 backdrop-blur lg:p-10'>
<div className='grid gap-10 lg:grid-cols-[0.9fr_1.1fr] lg:items-start'>
<div>
<p className='text-xs font-semibold uppercase tracking-[0.35em] text-cyan-300'>
Primera entrega
</p>
<h2 className='mt-4 text-3xl font-semibold text-white md:text-4xl'>
El primer MVP no es solo una pantalla: ya existe un circuito completo de cobro.
</h2>
<p className='mt-4 text-base leading-8 text-slate-300'>
En esta iteración inicial se prioriza el flujo que más valor genera desde el día uno: cobranza por apartamento, registro de pago, recibos y visibilidad financiera. Los CRUD genéricos ya existentes siguen disponibles para ampliar datos de residentes, cuotas, cuentas y gastos.
</p>
<div className='mt-8'>
<BaseButton
href='/login'
label='Entrar al panel y usar esta entrega'
color='info'
/>
</div>
</div>
<div className='grid gap-4'>
{firstDeliverySteps.map((step) => (
<div
key={step.title}
className='rounded-[28px] border border-white/10 bg-slate-900/60 p-5'
>
<h3 className='text-lg font-semibold text-white'>{step.title}</h3>
<p className='mt-3 text-sm leading-7 text-slate-300'>{step.text}</p>
</div>
))}
</div>
</div>
</div>
</section>
</main>
<footer className='border-t border-white/10'>
<div className='mx-auto flex max-w-7xl flex-col gap-4 px-6 py-6 text-sm text-slate-400 md:flex-row md:items-center md:justify-between'>
<p>
Diseño inicial orientado a administración de condominios, cobranzas, recibos y control de gastos.
</p>
<div className='flex flex-wrap gap-4'>
<a href='#primera-entrega' className='transition hover:text-cyan-200'>
Ver primera entrega
</a>
<Link href='/login' className='transition hover:text-cyan-200'>
Iniciar sesión
</Link>
<Link href='/login' className='transition hover:text-cyan-200'>
Ir al panel administrativo
</Link>
</div>
</div>
</footer>
</div>
</>
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
HomePage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};